Edit

Flight Animation

animation5 vector66 feature12 flights1 arc1

Demonstrates how to animate flights with ┬┤postrender┬┤.

This example shows how to use postrender 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 postrender. The flight data is provided by OpenFlights (a simplified data set from the Mapbox.js documentation is used).

main.js
import 'ol/ol.css';
import Feature from 'ol/Feature';
import LineString from 'ol/geom/LineString';
import Map from 'ol/Map';
import Stamen from 'ol/source/Stamen';
import VectorSource from 'ol/source/Vector';
import View from 'ol/View';
import {Stroke, Style} from 'ol/style';
import {Tile as TileLayer, Vector as VectorLayer} from 'ol/layer';
import {getVectorContext} from 'ol/render';

const tileLayer = new TileLayer({
  source: new Stamen({
    layer: 'toner',
  }),
});

const map = new Map({
  layers: [tileLayer],
  target: 'map',
  view: new View({
    center: [0, 0],
    zoom: 2,
  }),
});

const style = new Style({
  stroke: new Stroke({
    color: '#EAE911',
    width: 2,
  }),
});

const flightsSource = new VectorSource({
  wrapX: false,
  attributions:
    'Flight data by ' +
    '<a href="https://openflights.org/data.html">OpenFlights</a>,',
  loader: function () {
    const url = 'data/openflights/flights.json';
    fetch(url)
      .then(function (response) {
        return response.json();
      })
      .then(function (json) {
        const flightsData = json.flights;
        for (let i = 0; i < flightsData.length; i++) {
          const flight = flightsData[i];
          const from = flight[0];
          const to = flight[1];

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

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

            const feature = new Feature({
              geometry: line,
              finished: false,
            });
            // add the feature with a delay so that the animation
            // for all features does not start at the same time
            addLater(feature, i * 50);
          }
        }
        tileLayer.on('postrender', animateFlights);
      });
  },
});

const flightsLayer = new VectorLayer({
  source: flightsSource,
  style: function (feature) {
    // if the animation is still active for a feature, do not
    // render the feature with the layer style
    if (feature.get('finished')) {
      return style;
    } else {
      return null;
    }
  },
});

map.addLayer(flightsLayer);

const pointsPerMs = 0.1;
function animateFlights(event) {
  const vectorContext = getVectorContext(event);
  const frameState = event.frameState;
  vectorContext.setStyle(style);

  const features = flightsSource.getFeatures();
  for (let i = 0; i < features.length; i++) {
    const feature = features[i];
    if (!feature.get('finished')) {
      // only draw the lines for which the animation has not finished yet
      const coords = feature.getGeometry().getCoordinates();
      const elapsedTime = frameState.time - feature.get('start');
      const elapsedPoints = elapsedTime * pointsPerMs;

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

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

      // directly draw the line with the vector context
      vectorContext.drawGeometry(currentLine);
    }
  }
  // tell OpenLayers to continue the animation
  map.render();
}

function addLater(feature, timeout) {
  window.setTimeout(function () {
    feature.set('start', Date.now());
    flightsSource.addFeature(feature);
  }, timeout);
}
index.html
<!DOCTYPE html>
<html lang="en">
  <head>
    <meta charset="UTF-8">
    <title>Flight Animation</title>
    <!-- Pointer events polyfill for old browsers, see https://caniuse.com/#feat=pointer -->
    <script src="https://unpkg.com/elm-pep"></script>
    <!-- The line below is only needed for old environments like Internet Explorer and Android 4.x -->
    <script src="https://cdn.polyfill.io/v3/polyfill.min.js?features=fetch,requestAnimationFrame,Element.prototype.classList,URL,TextDecoder,Number.isInteger"></script>
    <script src="https://api.mapbox.com/mapbox.js/plugins/arc.js/v0.1.0/arc.js"></script>
    <style>
      .map {
        width: 100%;
        height:400px;
      }
    </style>
  </head>
  <body>
    <div id="map" class="map"></div>
    <script src="main.js"></script>
  </body>
</html>
package.json
{
  "name": "flight-animation",
  "dependencies": {
    "ol": "6.9.0"
  },
  "devDependencies": {
    "parcel": "^2.0.0-beta.1"
  },
  "scripts": {
    "start": "parcel index.html",
    "build": "parcel build --public-url . index.html"
  }
}