Flight animation example

Demonstrates how to animate flights.

This example shows how to use postcompose and vectorContext to animate flights. A great circle arc between two airports is calculated using arc.js and then the flight paths are animated with postcompose. The flight data is provided by OpenFlights (a simplified data set from the Mapbox.js documentation is used).

animation, vector, feature, flights, arc
<!DOCTYPE html>
<html>
<head>
<title>Flight animation example</title>
<script src="https://code.jquery.com/jquery-1.11.2.min.js"></script>
<link rel="stylesheet" href="https://maxcdn.bootstrapcdn.com/bootstrap/3.3.5/css/bootstrap.min.css">
<script src="https://maxcdn.bootstrapcdn.com/bootstrap/3.3.5/js/bootstrap.min.js"></script>
<link rel="stylesheet" href="http://openlayers.org/en/v3.8.2/css/ol.css" type="text/css">
<script src="http://openlayers.org/en/v3.8.2/build/ol.js"></script>
<script src="https://api.mapbox.com/mapbox.js/plugins/arc.js/v0.1.0/arc.js"></script>
</head>
<body>
<div class="container-fluid">

<div class="row">
  <div class="span8">
    <div id="map" class="map"></div>
  </div>
</div>

</div>
<script>
var map = new ol.Map({
  layers: [
    new ol.layer.Tile({
      source: new ol.source.Stamen({
        layer: 'toner'
      })
    })
  ],
  controls: ol.control.defaults({
    attributionOptions: /** @type {olx.control.AttributionOptions} */ ({
      collapsible: false
    })
  }),
  renderer: 'canvas',
  target: 'map',
  view: new ol.View({
    center: [0, 0],
    zoom: 2
  })
});

var defaultStroke = new ol.style.Stroke({
  color: '#EAE911',
  width: 2
});
var defaultStyle = new ol.style.Style({
  stroke: defaultStroke
});

var pointsPerMs = 0.1;
var animateFlights = function(event) {
  var vectorContext = event.vectorContext;
  var frameState = event.frameState;
  vectorContext.setFillStrokeStyle(null, defaultStroke);

  var features = flightsSource.getFeatures();
  for (var i = 0; i < features.length; i++) {
    var feature = features[i];
    if (!feature.get('finished')) {
      var coords = feature.getGeometry().getCoordinates();
      var elapsedTime = frameState.time - feature.get('start');
      var elapsedPoints = elapsedTime * pointsPerMs;

      if (elapsedPoints >= coords.length) {
        feature.set('finished', true);
      }

      var maxIndex = Math.min(elapsedPoints, coords.length);
      var currentLine = new ol.geom.LineString(coords.slice(0, maxIndex));

      vectorContext.drawLineStringGeometry(currentLine, feature);
    }
  }
  // tell OL3 to continue postcompose animation
  frameState.animate = true;
};

var addLater = function(feature, timeout) {
  window.setTimeout(function() {
    feature.set('start', new Date().getTime());
    flightsSource.addFeature(feature);
  }, timeout);
};

var flightsSource = new ol.source.Vector({
  wrapX: false,
  attributions: [new ol.Attribution({
    html: 'Flight data by ' +
        '<a href="http://openflights.org/data.html">OpenFlights</a>,'
  })]
});

var url = 'data/openflights/flights.json';
$.ajax({url: url, dataType: 'json', success: function(response) {
  var flightsData = response.flights;
  for (var i = 0; i < flightsData.length; i++) {
    var flight = flightsData[i];
    var from = flight[0];
    var to = flight[1];

    // create an arc circle between the two locations
    var arcGenerator = new arc.GreatCircle(
        {x: from[1], y: from[0]},
        {x: to[1], y: to[0]});

    var arcLine = arcGenerator.Arc(100, {offset: 10});
    if (arcLine.geometries.length === 1) {
      var line = new ol.geom.LineString(arcLine.geometries[0].coords);
      line.transform(ol.proj.get('EPSG:4326'), ol.proj.get('EPSG:3857'));

      var feature = new ol.Feature({
        geometry: line,
        finished: false
      });
      addLater(feature, i * 50);
    }
  }
  map.on('postcompose', animateFlights);
}});

var flightsLayer = new ol.layer.Vector({
  source: flightsSource,
  style: function(feature, resolution) {
    // if the animation is still active for a feature, do not
    // render the feature with the layer style
    if (feature.get('finished')) {
      return [defaultStyle];
    } else {
      return [];
    }
  }
});
map.addLayer(flightsLayer);

</script>
</body>
</html>