Edit

Marker Animation

Demonstrates how to move a feature along a line.

This example shows how to use postrender events and a vector context to animate a marker feature along a line. In this example an encoded polyline is being used.

index.html<!DOCTYPE html>
<html lang="en">
  <head>
    <title>Marker Animation</title>
    <!-- The line below is only needed for old environments like Internet Explorer and Android 4.x -->
    <script src="https://cdn.polyfill.io/v2/polyfill.min.js?features=requestAnimationFrame,Element.prototype.classList,URL"></script>
    <style>
      .map {
        width: 100%;
        height:400px;
      }
    </style>
  </head>
  <body>
    <div id="map" class="map"></div>
    <label for="speed">
      speed:&nbsp;
      <input id="speed" type="range" min="10" max="999" step="10" value="60">
    </label>
    <button id="start-animation">Start Animation</button>
    <script src="index.js"></script>
  </body>
</html>
index.jsimport 'ol/ol.css';
import Feature from 'ol/Feature';
import Map from 'ol/Map';
import View from 'ol/View';
import Polyline from 'ol/format/Polyline';
import Point from 'ol/geom/Point';
import {Tile as TileLayer, Vector as VectorLayer} from 'ol/layer';
import BingMaps from 'ol/source/BingMaps';
import VectorSource from 'ol/source/Vector';
import {Circle as CircleStyle, Fill, Icon, Stroke, Style} from 'ol/style';
import {getVectorContext} from 'ol/render';

// This long string is placed here due to jsFiddle limitations.
// It is usually loaded with AJAX.
var polyline = [
  '[email protected]`[email protected]}@[email protected]??aC^[email protected][email protected]@b[wFdE??wFfE}N',
  'fIoGxB_I\\gG}@eHoCyTmPqGaBaHOoD\\??yVrGotA|N??o[N_STiwAtEmHGeHcAkiA}^',
  'aMyBiHOkFNoI`CcVvM??gG^[email protected]??eCcA]OoL}DwFyCaCgCcCwDcGwHsSoX??wI_E',
  '[email protected][email protected]\\[email protected]@{vA}[email protected]}{@iRaqE{[email protected]_T{]_',
  '[email protected]{PmhEwaA{[email protected]]wQeEgtAsZ}LiCarAkVwI}D??_}[email protected]',
  '[email protected][email protected]@gV}TiYs[uTwXoNmT{[email protected]]{[email protected][email protected]_G}YsFw]k',
  '[email protected][email protected][email protected]|@[email protected][email protected]@[email protected]@[email protected]@{[email protected]',
  '[email protected]@[email protected][email protected]@[email protected]@{[email protected]}[email protected]@[email protected]~C{[email protected]',
  '[email protected]@kmBS{[email protected]_~QHeU`IuyDrC_}@[email protected]?qMbD}{AIkeAgB',
  'k_A_A{[email protected]@qH{[email protected]@qH{`@[email protected]@kL{[email protected]@ymBgwE}[email protected]__',
  '[email protected]@[email protected][email protected]}|[email protected]^eaC}L{dAaJ_aAiOyjByH{nAuYu`GsAw',
  '[email protected]{[email protected]}@[email protected]`CkiAbFkhBlTgdDdPyiB`W}xDnSa}DbJyhCrX',
  'itAhT}[email protected]}[email protected][email protected]}A~JovAxCqW~WanB`XewBbK{_A`K}[email protected]',
  'xBycBeCauBoF}}@[email protected][email protected]@cDs[eRaiBkQstAsQkcByNma',
  '[email protected][email protected]@eZcDwjBoGw`BoMegBaU_`[email protected][email protected]_',
  '[email protected]@_eC_UmlB}MmaBeWkkDeHwqAoX}~DcBsZmLcxBqOwqE_DkyAuJmrJ\\o',
  '~CfIewG|[email protected]}RorAoVajA_nAodD{[y`[email protected]@[email protected]{dAm',
  '[email protected]|@ojBwzDaaJsmBwbEgdCsrFqhAihDquAi`[email protected]}[email protected][email protected]_',
  'lKszAu|OmaA{wKm}@clHs_A_rEahCssKo\\[email protected]_wAyTwpBmPc|BwZknF',
  'oFscB_GsaDiZmyMyLgtHgQonHqT{hKaPg}[email protected][email protected]`EuiBudIabB{hF{[email protected]',
  'w`[email protected]~BkoAi}[email protected]@}`@oaXi_C}[email protected]|[email protected]]kgPcaAu}SkDw',
  '[email protected]\\[email protected]@siO{[ol\\kCmjMe\\[email protected]}EqiBaCg}',
  '@[email protected][email protected]`[email protected]{[email protected]{[email protected]_yCu\\wyCwy',
  'A{[email protected]}rO{{[email protected]_bAumFo}DgqA_uByi',
  '@swC~AkzDlhA}xEvcBa}[email protected]@`rAo|@~bBq{@``[email protected]@[email protected]@nfC_eC',
  '|[email protected]}@``Fi~FpnAooC|[email protected]}[email protected]}[email protected]',
  '[email protected]{gCwGkgCc[[email protected][[email protected]@[email protected]@[email protected]@`[email protected]',
  '[email protected]`ExGuaBdEmbBpBssArAuqBBg}@[email protected]{AkB{[email protected]_bYmC}[email protected]@sPq_BuJ_',
  '[email protected]{X_{[email protected]{[email protected]@[email protected][email protected]@XcQ|@oNdCo',
  'SfFwXhEmOnLi\\lbAulB`[email protected]|[email protected]@[email protected]@bqC}{BhwDgcD`[email protected]@??bL{G|[email protected]@',
  'oS~][email protected]|[email protected]}Jv}[email protected]{[email protected]_]`|[email protected]@wSb{@[email protected]`RooQ~e',
  '[upZbuIolI|[email protected]@nMmJ|OeJn^{[email protected]@[email protected]@kAp~BkBxO{@|QsAfY',
  'gEtYiGd]}[email protected]`[email protected]@vgK}cJnSoSzQkVvUm^[email protected]`[email protected]\\[email protected]~k',
  'Dyq[[email protected]@[email protected]@[email protected]@neB}uBhqEesFjoGeyHtCoD|D}Ed|@ctAbIuOzqB',
  '_}D~NgY`\\[email protected][[email protected]{Cw`G`[email protected]{AdjAwzBh{C}`[email protected]@}[email protected]{[email protected]??jI',
  '[email protected]`CuOlC}YnAcV`@_^[email protected]}@[email protected]^uCkZiGk\\yGeY}[email protected][uWi[[email protected]',
  '[email protected]@[email protected]}qAwHkGi{@[email protected]_{B}IsJ',
  'uEeFymAssAkdAmhAyTcVkFeEoKiH}[email protected]@[email protected]@[email protected]@[email protected]@}EsFmG}Jk^[email protected][email protected]',
  '[email protected]@[email protected]@kBrCgDbA[email protected]@??kF}D??OL'
].join('');

var route = /** @type {import("../src/ol/geom/LineString.js").default} */ (new Polyline({
  factor: 1e6
}).readGeometry(polyline, {
  dataProjection: 'EPSG:4326',
  featureProjection: 'EPSG:3857'
}));

var routeCoords = route.getCoordinates();
var routeLength = routeCoords.length;

var routeFeature = new Feature({
  type: 'route',
  geometry: route
});
var geoMarker = /** @type Feature<import("../src/ol/geom/Point").default> */(new Feature({
  type: 'geoMarker',
  geometry: new Point(routeCoords[0])
}));
var startMarker = new Feature({
  type: 'icon',
  geometry: new Point(routeCoords[0])
});
var endMarker = new Feature({
  type: 'icon',
  geometry: new Point(routeCoords[routeLength - 1])
});

var styles = {
  'route': new Style({
    stroke: new Stroke({
      width: 6, color: [237, 212, 0, 0.8]
    })
  }),
  'icon': new Style({
    image: new Icon({
      anchor: [0.5, 1],
      src: 'data/icon.png'
    })
  }),
  'geoMarker': new Style({
    image: new CircleStyle({
      radius: 7,
      fill: new Fill({color: 'black'}),
      stroke: new Stroke({
        color: 'white', width: 2
      })
    })
  })
};

var animating = false;
var speed, now;
var speedInput = document.getElementById('speed');
var startButton = document.getElementById('start-animation');

var vectorLayer = new VectorLayer({
  source: new VectorSource({
    features: [routeFeature, geoMarker, startMarker, endMarker]
  }),
  style: function(feature) {
    // hide geoMarker if animation is active
    if (animating && feature.get('type') === 'geoMarker') {
      return null;
    }
    return styles[feature.get('type')];
  }
});

var center = [-5639523.95, -3501274.52];
var map = new Map({
  target: document.getElementById('map'),
  view: new View({
    center: center,
    zoom: 10,
    minZoom: 2,
    maxZoom: 19
  }),
  layers: [
    new TileLayer({
      source: new BingMaps({
        imagerySet: 'AerialWithLabelsOnDemand',
        key: 'Your Bing Maps Key from http://www.bingmapsportal.com/ here'
      })
    }),
    vectorLayer
  ]
});

var moveFeature = function(event) {
  var vectorContext = getVectorContext(event);
  var frameState = event.frameState;

  if (animating) {
    var elapsedTime = frameState.time - now;
    // here the trick to increase speed is to jump some indexes
    // on lineString coordinates
    var index = Math.round(speed * elapsedTime / 1000);

    if (index >= routeLength) {
      stopAnimation(true);
      return;
    }

    var currentPoint = new Point(routeCoords[index]);
    var feature = new Feature(currentPoint);
    vectorContext.drawFeature(feature, styles.geoMarker);
  }
  // tell OpenLayers to continue the postrender animation
  map.render();
};

function startAnimation() {
  if (animating) {
    stopAnimation(false);
  } else {
    animating = true;
    now = new Date().getTime();
    speed = speedInput.value;
    startButton.textContent = 'Cancel Animation';
    // hide geoMarker
    geoMarker.setStyle(null);
    // just in case you pan somewhere else
    map.getView().setCenter(center);
    vectorLayer.on('postrender', moveFeature);
    map.render();
  }
}


/**
 * @param {boolean} ended end of animation.
 */
function stopAnimation(ended) {
  animating = false;
  startButton.textContent = 'Start Animation';

  // if animation cancelled set the marker at the beginning
  var coord = ended ? routeCoords[routeLength - 1] : routeCoords[0];
  var geometry = geoMarker.getGeometry();
  geometry.setCoordinates(coord);
  //remove listener
  vectorLayer.un('postrender', moveFeature);
}

startButton.addEventListener('click', startAnimation, false);
package.json{
  "name": "feature-move-animation",
  "dependencies": {
    "ol": "6.1.1"
  },
  "devDependencies": {
    "parcel": "1.11.0"
  },
  "scripts": {
    "start": "parcel index.html",
    "build": "parcel build --experimental-scope-hoisting --public-url . index.html"
  }
}