How to add animated arrow as per the value I have entered in text box in open layers

424 views Asked by At

I tried to add the animated arrow one by one on the string as per the value we are entering in the text box, but it was not possible. can give me solation how I add this.

I want when we enter 4 in text box then click add arrow button then add 4 animated arrow icon one by one in gap of 2 seconds in the line string.

Click here for see Image

import Feature from 'ol/Feature';
import LineString from 'ol/geom/LineString';
import Map from 'ol/Map';
import Point from 'ol/geom/Point';
import Polyline from 'ol/format/Polyline';
import VectorSource from 'ol/source/Vector';
import View from 'ol/View';
import OSM from 'ol/source/OSM';
import {Icon, Stroke, Style} from 'ol/style';
import {Tile as TileLayer, Vector as VectorLayer} from 'ol/layer';
import {getVectorContext} from 'ol/render';

const center = [-5639523.95, -3501274.52];
const map = new Map({
  target: document.getElementById('map'),
  view: new View({
    center: center,
    zoom: 10,
    minZoom: 2,
    maxZoom: 19,
  }),
  layers: [
    new TileLayer({
      source: new OSM(),
    }),
  ],
});

// The polyline string is read from a JSON similiar to those returned
// by directions APIs such as Openrouteservice and Mapbox.
fetch('data/polyline/route.json').then(function (response) {
  response.json().then(function (result) {
    const polyline = result.routes[0].geometry;

    const route = new Polyline({
      factor: 1e6,
    }).readGeometry(polyline, {
      dataProjection: 'EPSG:4326',
      featureProjection: 'EPSG:3857',
    });

    const routeFeature = new Feature({
      type: 'route',
      geometry: route,
    });
    const startMarker = new Feature({
      type: 'icon',
      geometry: new Point(route.getFirstCoordinate()),
    });
    const endMarker = new Feature({
      type: 'icon',
      geometry: new Point(route.getLastCoordinate()),
    });
    const position = startMarker.getGeometry().clone();
    const geoMarker = new Feature({
      type: 'geoMarker',
      geometry: position,
    });

    const 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 Icon({
          src:
            'https://cdn1.iconfinder.com/data/icons/basic-ui-elements-color-round/3/19-32.png',
          rotation: getAngleAt(route, 0) + Math.PI / 2,
        }),
      }),
    };

    const vectorLayer = new VectorLayer({
      source: new 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;

    function getAngleAt(lineString, distance) {
      const length = lineString.getLength();
      const coordinates = lineString.getCoordinates();
      for (let i = 1, len = coordinates.length; i <= len; ++i) {
        if (
          new LineString(coordinates.slice(0, i + 1)).getLength() >=
          length * distance
        ) {
          return -Math.atan2(
            coordinates[i][1] - coordinates[i - 1][1],
            coordinates[i][0] - coordinates[i - 1][0]
          );
        }
      }
    }

    const lastTimes = [];
    function moveFeature(event) {
      const speed = Number(speedInput.value);
      const time = event.frameState.time;
      for (let i = 0, ii = lastTimes.length; i < ii; ++i) {
        let {lastTime, distance} = lastTimes[i];
        const elapsedTime = time - lastTime;
        distance = (distance + (speed * elapsedTime) / 1e6) % 2;
        lastTime = time;
        lastTimes[i] = {lastTime, distance};
        const lineDistance = distance > 1 ? 2 - distance : distance;
        const direction = distance > 1 ? -Math.PI / 2 : Math.PI / 2;
        const currentCoordinate = route.getCoordinateAt(lineDistance);
        const angle = getAngleAt(route, lineDistance) + direction;
        styles.geoMarker.getImage().setRotation(angle);
        position.setCoordinates(currentCoordinate);
        const vectorContext = getVectorContext(event);
        vectorContext.setStyle(styles.geoMarker);
        vectorContext.drawGeometry(position);
      }
      // tell OpenLayers to continue the postrender animation
      map.render();
    }

    function startAnimation() {
      lastTimes.push({lastTime: Date.now(), distance: 0});
      if (!animating) {
        animating = true;
        //startButton.textContent = 'Stop Animation';
        vectorLayer.on('postrender', moveFeature);
        // hide geoMarker and trigger map render through change event
        geoMarker.setGeometry(null);
      }
    }

    startButton.addEventListener('click', startAnimation);
  });
});

<!DOCTYPE html>
<html lang="en">
  <head>
    <meta charset="UTF-8">
    <title>Marker Animation</title>
    <link rel="stylesheet" href="node_modules/ol/ol.css">
    <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>
    <input type="text" id="textValue">
    <button id="btnClick">Add icon</button>
    <!-- Pointer events polyfill for old browsers, see https://caniuse.com/#feat=pointer -->
    <script src="https://unpkg.com/[email protected]/dist/elm-pep.js"></script>
    <script type="module" src="main.js"></script>
  </body>
</html>

I am try this code for add animated arrow as per text box value but not getting any solution for how to add this.

Please see my image for what I want to.

2

There are 2 answers

0
Mike On BEST ANSWER

You need to start each animation at 500 millisecond intervals until the required number of arrows have been started:

function startAnimations() {
  const arrows = Number(arrowsInput.value);
  if (arrows > 0) {
    let count = 0;
    const id = setInterval(function () {
      startAnimation();
      ++count;
      if (count === arrows) {
        clearInterval(id);
      }
    }, 500);
  }
}
startButton.addEventListener('click', startAnimations);

https://codesandbox.io/s/feature-move-animation-forked-2ckr74

1
Eliseo On

To move an element along a path it's simply use the method getPointAtLength

So imagine we have some like:

div class="wrapper">
  <svg viewBox="-5 -5 173 61" width="173" heigth="61">
    <path
      #path
      d="M153,20.89c0-45.88-74.95,0-74.95,0S.5,66.76.5,20.89s77.54,0,77.54,0S153,66.76,153,20.89Z"
      fill="none"
      stroke="#000"
      vector-effect="non-scaling-stroke"
      class="infinity"
    />
  </svg>
  <div #circle class="element"></div>
</div>
<button (click)="click()">start</button>

The .css

.wrapper{
  position:relative
}
.element{
  position:absolute;
  width:10px;
  height:10px;
  background-color:blueviolet;
  border-radius: 5px;
  top:0;
  left:0;
}

The code:

  @ViewChild('path',{static:true}) path:ElementRef
  @ViewChild('circle',{static:true}) circle:ElementRef

  subscription:any
  distance:number
  fps:number=32
  duration:number=2*this.fps
  time:number;
  ngOnInit()
  {
    this.distance=this.path.nativeElement.getTotalLength()
    const pos=this.path.nativeElement.getPointAtLength(0)
    this.circle.nativeElement.style.left=pos.x+'px'
    this.circle.nativeElement.style.top=pos.y+'px'

  }
  ngOnDestroy(){
    this.subscription && this.subscription.unsubscribe()
  }
  click(){
    this.subscription=timer(0,1000/this.fps).pipe(
      take(this.duration+1)
      ).subscribe(x=>{
      const pos=this.path.nativeElement.getPointAtLength(x*this.distance/this.duration)
      this.circle.nativeElement.style.left=pos.x+'px'
      this.circle.nativeElement.style.top=pos.y+'px'
    })

  }

The stackblitz