Search code examples
openlayersopenlayers-6

Openlayers Move geoMarker How to Ending


Openlayers In this case, it is possible to stop the movement not in the middle of the movement, but at the last point. https://openlayers.org/en/latest/examples/feature-move-animation.html


Solution

  • The behavior of the marker while animating is determined by the moveFeature function:

        function moveFeature(event) {
          const speed = Number(speedInput.value);
          const time = event.frameState.time;
          const elapsedTime = time - lastTime;
          distance = (distance + (speed * elapsedTime) / 1e6) % 2;
          lastTime = time;
    
          const currentCoordinate = route.getCoordinateAt(
            distance > 1 ? 2 - distance : distance
          );
          position.setCoordinates(currentCoordinate);
          const vectorContext = getVectorContext(event);
          vectorContext.setStyle(styles.geoMarker);
          vectorContext.drawGeometry(position);
          // tell OpenLayers to continue the postrender animation
          map.render();
        }
    

    distance==1 is where it changes direction:

    const currentCoordinate = route.getCoordinateAt(
            distance > 1 ? 2 - distance : distance
          );
    

    If you modify that to not change direction:

        function moveFeature(event) {
          const speed = Number(speedInput.value);
          const time = event.frameState.time;
          const elapsedTime = time - lastTime;
          distance = (distance + (speed * elapsedTime) / 1e6) % 2;
          lastTime = time;
    
          const currentCoordinate = route.getCoordinateAt(
            distance // don't change direction
          );
          if (distance > 1) stopAnimation(); // stop at end
          else {
            position.setCoordinates(currentCoordinate);
            const vectorContext = ol.render.getVectorContext(event);
            vectorContext.setStyle(styles.geoMarker);
            vectorContext.drawGeometry(position);
            // tell OpenLayers to continue the postrender animation
            map.render();
          }
        }
    

    And change the startAnimation function to start at the beginning if it is at the end:

        function startAnimation() {
          if (distance > 1) distance=0; // restart at the beginning if at the end
          animating = true;
          lastTime = Date.now();
          startButton.textContent = 'Stop Animation';
          vectorLayer.on('postrender', moveFeature);
          // hide geoMarker and trigger map render through change event
          geoMarker.setGeometry(null);
        }
    

    screenshot of map with circle at end of route code snippet (using the legacy build):

    const center = [-5639523.95, -3501274.52];
    const map = new ol.Map({
      target: document.getElementById('map'),
      view: new ol.View({
        center: center,
        zoom: 9,
        minZoom: 2,
        maxZoom: 19,
      }),
      layers: [
        new ol.layer.Tile({ // TileLayer({
          source: new ol.source.OSM()
        }),
      ],
    });
    
    // This long string is placed here due to code snippet limitations.
    // It is usually loaded with AJAX.
    var polyline = [
      'hldhx@lnau`BCG_EaC??cFjAwDjF??uBlKMd@}@z@??aC^yk@z_@se@b[wFdE??wFfE}N',
      'fIoGxB_I\\gG}@eHoCyTmPqGaBaHOoD\\??yVrGotA|N??o[N_STiwAtEmHGeHcAkiA}^',
      'aMyBiHOkFNoI`CcVvM??gG^gF_@iJwC??eCcA]OoL}DwFyCaCgCcCwDcGwHsSoX??wI_E',
      'kUFmq@hBiOqBgTwS??iYse@gYq\\cp@ce@{vA}s@csJqaE}{@iRaqE{lBeRoIwd@_T{]_',
      'Ngn@{PmhEwaA{SeF_u@kQuyAw]wQeEgtAsZ}LiCarAkVwI}D??_}RcjEinPspDwSqCgs@',
      'sPua@_OkXaMeT_Nwk@ob@gV}TiYs[uTwXoNmT{Uyb@wNg]{Nqa@oDgNeJu_@_G}YsFw]k',
      'DuZyDmm@i_@uyIJe~@jCg|@nGiv@zUi_BfNqaAvIow@dEed@dCcf@r@qz@Egs@{Acu@mC',
      'um@yIey@gGig@cK_m@aSku@qRil@we@{mAeTej@}Tkz@cLgr@aHko@qOmcEaJw~C{w@ka',
      'i@qBchBq@kmBS{kDnBscBnFu_Dbc@_~QHeU`IuyDrC_}@bByp@fCyoA?qMbD}{AIkeAgB',
      'k_A_A{UsDke@gFej@qH{o@qGgb@qH{`@mMgm@uQus@kL{_@yOmd@ymBgwE}x@ouBwtA__',
      'DuhEgaKuWct@gp@cnBii@mlBa_@}|Asj@qrCg^eaC}L{dAaJ_aAiOyjByH{nAuYu`GsAw',
      'Xyn@ywMyOyqD{_@cfIcDe}@y@aeBJmwA`CkiAbFkhBlTgdDdPyiB`W}xDnSa}DbJyhCrX',
      'itAhT}x@bE}Z_@qW_Kwv@qKaaAiBgXvIm}A~JovAxCqW~WanB`XewBbK{_A`K}fBvAmi@',
      'xBycBeCauBoF}}@qJioAww@gjHaPopA_NurAyJku@uGmi@cDs[eRaiBkQstAsQkcByNma',
      'CsK_uBcJgbEw@gkB_@ypEqDoqSm@eZcDwjBoGw`BoMegBaU_`Ce_@_uBqb@ytBwkFqiT_',
      'fAqfEwe@mfCka@_eC_UmlB}MmaBeWkkDeHwqAoX}~DcBsZmLcxBqOwqE_DkyAuJmrJ\\o',
      '~CfIewG|YibQxBssB?es@qGciA}RorAoVajA_nAodD{[y`AgPqp@mKwr@ms@umEaW{dAm',
      'b@umAw|@ojBwzDaaJsmBwbEgdCsrFqhAihDquAi`Fux@}_Dui@_eB_u@guCuyAuiHukA_',
      'lKszAu|OmaA{wKm}@clHs_A_rEahCssKo\\sgBsSglAqk@yvDcS_wAyTwpBmPc|BwZknF',
      'oFscB_GsaDiZmyMyLgtHgQonHqT{hKaPg}Dqq@m~Hym@c`EuiBudIabB{hF{pWifx@snA',
      'w`GkFyVqf@y~BkoAi}Lel@wtc@}`@oaXi_C}pZsi@eqGsSuqJ|Lqeb@e]kgPcaAu}SkDw',
      'zGhn@gjYh\\qlNZovJieBqja@ed@siO{[ol\\kCmjMe\\isHorCmec@uLebB}EqiBaCg}',
      '@m@qwHrT_vFps@kkI`uAszIrpHuzYxx@e{Crw@kpDhN{wBtQarDy@knFgP_yCu\\wyCwy',
      'A{kHo~@omEoYmoDaEcPiuAosDagD}rO{{AsyEihCayFilLaiUqm@_bAumFo}DgqA_uByi',
      '@swC~AkzDlhA}xEvcBa}Cxk@ql@`rAo|@~bBq{@``Bye@djDww@z_C_cAtn@ye@nfC_eC',
      '|gGahH~s@w}@``Fi~FpnAooC|u@wlEaEedRlYkrPvKerBfYs}Arg@m}AtrCkzElw@gjBb',
      'h@woBhR{gCwGkgCc[wtCuOapAcFoh@uBy[yBgr@c@iq@o@wvEv@sp@`FajBfCaq@fIipA',
      'dy@ewJlUc`ExGuaBdEmbBpBssArAuqBBg}@s@g{AkB{bBif@_bYmC}r@kDgm@sPq_BuJ_',
      's@{X_{AsK_d@eM{d@wVgx@oWcu@??aDmOkNia@wFoSmDyMyCkPiBePwAob@XcQ|@oNdCo',
      'SfFwXhEmOnLi\\lbAulB`X_d@|k@au@bc@oc@bqC}{BhwDgcD`l@ed@??bL{G|a@eTje@',
      'oS~]cLr~Bgh@|b@}Jv}EieAlv@sPluD{z@nzA_]`|KchCtd@sPvb@wSb{@ko@f`RooQ~e',
      '[upZbuIolI|gFafFzu@iq@nMmJ|OeJn^{Qjh@yQhc@uJ~j@iGdd@kAp~BkBxO{@|QsAfY',
      'gEtYiGd]}Jpd@wRhVoNzNeK`j@ce@vgK}cJnSoSzQkVvUm^rSgc@`Uql@xIq\\vIgg@~k',
      'Dyq[nIir@jNoq@xNwc@fYik@tk@su@neB}uBhqEesFjoGeyHtCoD|D}Ed|@ctAbIuOzqB',
      '_}D~NgY`\\um@v[gm@v{Cw`G`w@o{AdjAwzBh{C}`Gpp@ypAxn@}mAfz@{bBbNia@??jI',
      'ab@`CuOlC}YnAcV`@_^m@aeB}@yk@YuTuBg^uCkZiGk\\yGeY}Lu_@oOsZiTe[uWi[sl@',
      'mo@soAauAsrBgzBqgAglAyd@ig@asAcyAklA}qAwHkGi{@s~@goAmsAyDeEirB_{B}IsJ',
      'uEeFymAssAkdAmhAyTcVkFeEoKiH}l@kp@wg@sj@ku@ey@uh@kj@}EsFmG}Jk^_r@_f@m',
      '~@ym@yjA??a@cFd@kBrCgDbAUnAcBhAyAdk@et@??kF}D??OL'
    ].join('');
    
    var route = /** @type {module:ol/geom/LineString~LineString} */ (new ol.format.Polyline({
      factor: 1e6
    }).readGeometry(polyline, {
      dataProjection: 'EPSG:4326',
      featureProjection: 'EPSG:3857'
    }));
    
    
    const routeFeature = new ol.Feature({
      type: 'route',
      geometry: route,
    });
    const startMarker = new ol.Feature({
      type: 'icon',
      geometry: new ol.geom.Point(route.getFirstCoordinate()),
    });
    const endMarker = new ol.Feature({
      type: 'icon',
      geometry: new ol.geom.Point(route.getLastCoordinate()),
    });
    const position = startMarker.getGeometry().clone();
    const geoMarker = new ol.Feature({
      type: 'geoMarker',
      geometry: position,
    });
    
    const styles = {
      'route': new ol.style.Style({
        stroke: new ol.style.Stroke({
          width: 6,
          color: [237, 212, 0, 0.8],
        }),
      }),
      'icon': new ol.style.Style({
        image: new ol.style.Icon({
          anchor: [0.5, 1],
          src: 'https://openlayers.org/en/v7.2.2/examples/data/icon.png',
    
        }),
      }),
      'geoMarker': new ol.style.Style({
        image: new ol.style.Circle({ // CircleStyle({
          radius: 7,
          fill: new ol.style.Fill({
            color: 'black'
          }),
          stroke: new ol.style.Stroke({
            color: 'white',
            width: 2,
          }),
        }),
      }),
    };
    
    const vectorLayer = new ol.layer.Vector({ // VectorLayer({
      source: new ol.source.Vector({ // VectorSource({
        features: [routeFeature, geoMarker, startMarker, endMarker],
      }),
      style: function(feature) {
        return styles[feature.get('type')];
      },
    });
    
    map.addLayer(vectorLayer);
    
    const speedInput = document.getElementById('speed');
    const startButton = document.getElementById('start-animation');
    let animating = false;
    let distance = 0;
    let lastTime;
    
    function moveFeature(event) {
      const speed = Number(speedInput.value);
      const time = event.frameState.time;
      const elapsedTime = time - lastTime;
      distance = (distance + (speed * elapsedTime) / 1e6) % 2;
      lastTime = time;
    
      const currentCoordinate = route.getCoordinateAt(
        // distance > 1 ? 2 - distance : distance
        distance
      );
      if (distance > 1) stopAnimation();
      else {
        position.setCoordinates(currentCoordinate);
        const vectorContext = ol.render.getVectorContext(event);
        vectorContext.setStyle(styles.geoMarker);
        vectorContext.drawGeometry(position);
        // tell OpenLayers to continue the postrender animation
        map.render();
      }
    }
    
    function startAnimation() {
      if (distance > 1) distance = 0;
      animating = true;
      lastTime = Date.now();
      startButton.textContent = 'Stop Animation';
      vectorLayer.on('postrender', moveFeature);
      // hide geoMarker and trigger map render through change event
      geoMarker.setGeometry(null);
    }
    
    function stopAnimation() {
      animating = false;
      startButton.textContent = 'Start Animation';
    
      // Keep marker at current animation position
      geoMarker.setGeometry(position);
      vectorLayer.un('postrender', moveFeature);
    }
    
    startButton.addEventListener('click', function() {
      if (animating) {
        stopAnimation();
      } else {
        startAnimation();
      }
    });
    html,
    body {
      height: 100%;
      width: 100%;
      padding: 0px;
      margin: 0px;
    }
    
    .map {
      height: 90%;
      width: 100%;
    }
    <!DOCTYPE html>
    <html lang="en">
    
    <head>
      <meta charset="UTF-8">
      <title>Marker Animation</title>
      <link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/[email protected]/ol.css" type="text/css">
      <!-- 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>
      <script src="https://cdn.jsdelivr.net/npm/[email protected]/dist/ol.js"></script>
    </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>
      <!-- Pointer events polyfill for old browsers, see https://caniuse.com/#feat=pointer -->
      <script src="https://cdn.jsdelivr.net/npm/[email protected]/dist/elm-pep.js"></script>
    </body>
    
    </html>