Realization of path path playback function based on Leaflet

Keywords: Web Development IE Mobile less

Design sketch:

Explain:

1. This function is based on this blog: Path path playback with ArcGIS JS API But there are some small problems: first of all, the car does not move at a constant speed, but the running time of each section is fixed, so it has been modified on this blog; on the other hand, there is no way to set the rotation angle of the icon in Leaflet, so the Marker class needs to be expanded first.

2. Also refer to Baidu map road book open source library , I wanted to use js files directly, but many of them are bound to Bmap objects and cannot be used directly.

3. Baidu open source library first converts the latitude and longitude coordinates to plane coordinates and then interpolates them, and then converts them to latitude and longitude coordinates. The purpose of doing this is only to calculate the real distance, and then carry out the real simulation according to the set speed. Leaflet does not provide the method of converting the geographical coordinates to plane coordinates, so it directly uses the latitude and longitude to interpolate, so it goes back to The release speed is only a relative speed.

Implementation code:

<!DOCTYPE html>
<html lang="en">
<head>
  <meta charset="UTF-8">
  <title>Path path playback</title>
  <meta charset="utf-8" />
  <meta name="viewport" content="width=device-width, initial-scale=1.0">
  <link rel="stylesheet" href="./lib/leaflet/leaflet.css" />
  <script src="./lib/leaflet/leaflet.js"></script>

</head>

<style>
  * { margin: 0; padding: 0; }
  html, body { height: 100%; }
  #mapid { width:100%; height:100%; }
  .input-card{
    z-index: 50;
    display: flex;
    flex-direction: column;
    min-width: 0;
    word-wrap: break-word;
    background-color: #fff;
    background-clip: border-box;
    border-radius: .25rem;
    width: 8rem;
    border-width: 0;
    border-radius: 0.4rem;
    box-shadow: 0 2px 6px 0 rgba(114, 124, 245, .5);
    position: fixed;
    bottom: 1rem;
    right: 1rem;
    -ms-flex: 1 1 auto;
    flex: 1 1 auto;
    padding: 0.75rem 1.25rem;
  }
</style>

<body>

<div id="mapid" style="z-index: 10" ></div>
<div class="input-card">
  <button id="run"  onclick="start()">run</button>
  <button id="stop" onclick="stop()">stop</button>
  <button id="pause" onclick="pause()">pause</button>
</div>


<script>
  /**
   * Add methods to Marker class
   */
  (function() {
    // save these original methods before they are overwritten
    var proto_initIcon = L.Marker.prototype._initIcon;
    var proto_setPos = L.Marker.prototype._setPos;

    var oldIE = (L.DomUtil.TRANSFORM === 'msTransform');

    L.Marker.addInitHook(function () {
      var iconOptions = this.options.icon && this.options.icon.options;
      var iconAnchor = iconOptions && this.options.icon.options.iconAnchor;
      if (iconAnchor) {
        iconAnchor = (iconAnchor[0] + 'px ' + iconAnchor[1] + 'px');
      }
      this.options.rotationOrigin = this.options.rotationOrigin || iconAnchor || 'center center' ;
      this.options.rotationAngle = this.options.rotationAngle || 0;

      // Ensure marker keeps rotated during dragging
      this.on('drag', function(e) { e.target._applyRotation(); });
    });

    L.Marker.include({
      _initIcon: function() {
        proto_initIcon.call(this);
      },

      _setPos: function (pos) {
        proto_setPos.call(this, pos);
        this._applyRotation();
      },

      _applyRotation: function () {
        if(this.options.rotationAngle) {
          this._icon.style[L.DomUtil.TRANSFORM+'Origin'] = this.options.rotationOrigin;

          if(oldIE) {
            // for IE 9, use the 2D rotation
            this._icon.style[L.DomUtil.TRANSFORM] = 'rotate(' + this.options.rotationAngle + 'deg)';
          } else {
            // for modern browsers, prefer the 3D accelerated version
            this._icon.style[L.DomUtil.TRANSFORM] += ' rotateZ(' + this.options.rotationAngle + 'deg)';
          }
        }
      },

      setRotationAngle: function(angle) {
        this.options.rotationAngle = angle;
        this.update();
        return this;
      },

      setRotationOrigin: function(origin) {
        this.options.rotationOrigin = origin;
        this.update();
        return this;
      }
    });
  })();


  var map = L.map('mapid', {
    center: [38.8631169, 2.3708919],
    zoom: 5,
    crs: L.CRS.EPSG3857,
    layers: [
      L.tileLayer('https://{s}.tile.openstreetmap.org/{z}/{x}/{y}.png', {
        attribution: '&copy; <a href="http://osm.org/copyright">OpenStreetMap</a> contributors'
      })
    ]
  });
  var _opts = {
    icon: null,
    enableRotation:true //Allow trolley to rotate
  };
  //Index moved to current point
  this.i = 0;

  var latlngs = [
    [45.51, 2.3708919],
    [37.77, 8.54235],
    [34.04, 9.52532],
    [36.04, 10.52532],
    [40.04, 14.52532],
    [47.04, 15.52532]
  ];
  var _path = latlngs;
  var polyline = L.polyline(latlngs, {color: 'red'}).addTo(map);
  var myIcon = L.icon({
    iconUrl: 'car.png',
    iconSize: [24, 24]
  });


  function start(){
    var me = this,
      len = me._path.length;
    //It's not the first time to click on the start, and the car hasn't reached the end yet
    if (me.i && me.i < len - 1) {
      //Don't press pause and then press start
      if (!me._fromPause) {
        return;
      }else if(!me._fromStop){
        //Press the pause button, and then press start to move to the next point
        //In the process, the stop button is not pressed
        //Prevent the exception of stop first, then pause, and then start continuously
        me._moveNext(++me.i);
      }
    }else {
      //Click start for the first time, or click stop to start
      me._addMarker();

      me._moveNext(me.i);
    }
    //reset state
    this._fromPause = false;
    this._fromStop = false;
  }


  function _addMarker(callback) {
    if (this._marker) {
      this.stop();
      this._marker.remove();
    }
    var marker =new L.Marker(_path[0],{icon: myIcon }).addTo(map)
    this._marker = marker;
  }

  /**
   * Move to next point
   */
  function _moveNext(index) {
    var me = this;
    if (index < this._path.length - 1) {
      this._move(me._path[index], me._path[index + 1], me._tween);
    }
  }

  /**
   * Mobile car
   * @param {Number} poi Current step size
   * @param {Point} initPos Initial point of latitude and longitude coordinate
   * @param {Point} targetPos Longitude and latitude coordinate target point
   * @param {Function} effect Slow motion for interpolation
   * @return No return value
   */
  function _move(initPos,targetPos,effect) {
    var me = this,
      //Current frames
      currentCount = 0,
      //step
      timer = 10, //10 ms in one step
      step = 0.1,
      //Total steps
      count = Math.round(me._getDistance(initPos[0], initPos[1],targetPos[0],targetPos[1]) / step);

    //If less than 1, move directly to the next point
    if (count < 1) {
      this._moveNext(++me.i);
      return;
    }
    //Moving evenly between two points
    var angle;
    me._intervalFlag = setInterval(function() {
      //When the current number of frames between two points is greater than the total number of frames, the move is completed
      if (currentCount >= count) {
        clearInterval(me._intervalFlag);
        //The point moved has exceeded the total length
        if(me.i > me._path.length){
          return;
        }
        //Run next point
        me._moveNext(++me.i);
      }else {
        currentCount++;
        var x = effect(initPos[0], targetPos[0], currentCount, count),
          y = effect(initPos[1], targetPos[1], currentCount, count);
        var pos =L.latLng(x,y);

        //Set marker
        if(currentCount == 1){
          if(me._opts.enableRotation == true){
            //Initpos = [lat, LNG], the format of coordinate pair in leaflet is (latitude, longitude), so to calculate the angle, X corresponds to longitude, that is, initPos[1]
            angle = me._getAngle(initPos[1], initPos[0],targetPos[1],targetPos[0]);
          }
        }
        //Moving
        me._marker.remove();//Delete first
        me._marker.setRotationAngle(angle);
        me._marker._latlng = pos;//Set icon location
        me._marker.addTo(map);
      }
    },timer);
  }

  /**
   * Slow motion effect
   * Initial coordinate, target coordinate, current step, total step
   * @private
   */
    function _tween(initPos, targetPos, currentCount, count) {
      var b = initPos, c = targetPos - initPos, t = currentCount,
        d = count;
      return c * t / d + b;
    }

  /**
   * Calculate the distance between two points
   */
  function _getDistance(pxA,pyA, pxB,pyB) {
    return Math.sqrt(Math.pow(pxA - pxB, 2) + Math.pow(pyA - pyB, 2));
  }

  /**
   * Calculating angle
   * @param startx
   * @param starty
   * @param endx
   * @param endy
   * @returns {number}
   */
  function _getAngle(startx, starty, endx, endy) {
    var tan = 0
    if (endx == startx) {
      tan = 90;
    } else {
      tan = Math.atan(Math.abs((endy - starty) / (endx - startx))) * 180 / Math.PI;
      console.log(tan);
    }

    if (endx >= startx && endy >= starty)//First quadrant
    {
      return -tan;
    } else if (endx > startx && endy < starty)//Delta Quadrant
    {
      return tan;
    } else if (endx < startx && endy > starty)//Beta Quadrant
    {
      return tan - 180;
    } else {
      return 180 - tan;  //third quadrant
    }
  }

  /**
   * Stop it
   */
  function stop() {
    this.i = 0;
    this._fromStop = true;
    clearInterval(this._intervalFlag);
  }

  /**
   * suspend
   */
  function pause() {
    clearInterval(this._intervalFlag);
    //Mark whether the pause button has been pressed
    this._fromPause = true;
  }

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

File download address: https://download.csdn.net/download/wml00000/10769999

Posted by xsist10 on Mon, 09 Dec 2019 13:15:25 -0800