1. 程式人生 > >基於Leaflet實現路徑軌跡回放功能

基於Leaflet實現路徑軌跡回放功能

效果圖:

說明:

       1.該功能是在這篇部落格基礎上完成的:ArcGIS JS API 實現路徑軌跡回放  但是裡面有點小問題:首先,小車並不是勻速運動的,而是每一段的執行時間固定,所以在該部落格上進行了修改;另一方面,Leaflet中沒有提供設定圖示旋轉角度的方法,因此需要先對Marker類進行擴充套件。

       2.另外還參考了百度地圖路書開源庫,本來是想直接把js檔案拿過來用,但是裡面很多方法都和Bmap物件繫結,不能直接使用。

       3.百度開源庫中是先將經緯度座標轉為平面座標再進行插值,插值之後再轉為經緯度座標,這樣做的目的就只為了計算出真實距離,然後根據設定的速度進行真實模擬;Leaflet中沒有提供地理座標轉平面座標的方法,因此是直接用的經緯度進行插值,因此回放速度只是一個相對速度。

實現程式碼:

<!DOCTYPE html>
<html lang="en">
<head>
  <meta charset="UTF-8">
  <title>路徑軌跡回放</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>
  /**
   * 為Marker類新增方法
   */
  (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 //允許小車旋轉
  };
  //移動到當前點的索引
  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;
    //不是第一次點選開始,並且小車還沒到達終點
    if (me.i && me.i < len - 1) {
      //沒按pause再按start不做處理
      if (!me._fromPause) {
        return;
      }else if(!me._fromStop){
        //按了pause按鈕,並且再按start,直接移動到下一點
        //並且此過程中,沒有按stop按鈕
        //防止先stop,再pause,然後連續不停的start的異常
        me._moveNext(++me.i);
      }
    }else {
      //第一次點選開始,或者點了stop之後點開始
      me._addMarker();

      me._moveNext(me.i);
    }
    //重置狀態
    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;
  }

  /**
   * 移動到下一個點
   */
  function _moveNext(index) {
    var me = this;
    if (index < this._path.length - 1) {
      this._move(me._path[index], me._path[index + 1], me._tween);
    }
  }

  /**
   * 移動小車
   * @param {Number} poi 當前的步長.
   * @param {Point} initPos 經緯度座標初始點.
   * @param {Point} targetPos 經緯度座標目標點.
   * @param {Function} effect 緩動效果,實現插值
   * @return 無返回值.
   */
  function _move(initPos,targetPos,effect) {
    var me = this,
      //當前的幀數
      currentCount = 0,
      //步長
      timer = 10, //10毫秒為一步
      step = 0.1,
      //總步數
      count = Math.round(me._getDistance(initPos[0], initPos[1],targetPos[0],targetPos[1]) / step);

    //如果小於1直接移動到下一點
    if (count < 1) {
      this._moveNext(++me.i);
      return;
    }
    //兩點之間勻速移動
    var angle;
    me._intervalFlag = setInterval(function() {
      //兩點之間當前幀數大於總幀數的時候,則說明已經完成移動
      if (currentCount >= count) {
        clearInterval(me._intervalFlag);
        //移動的點已經超過總的長度
        if(me.i > me._path.length){
          return;
        }
        //執行下一個點
        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);

        //設定marker
        if(currentCount == 1){
          if(me._opts.enableRotation == true){
            //initPos=[lat,lng],leaflet中座標對的格式為(緯度,經度),因此要計算角度的話,X對應經度,即initPos[1]
            angle = me._getAngle(initPos[1], initPos[0],targetPos[1],targetPos[0]);
          }
        }
        //正在移動
        me._marker.remove();//先刪除
        me._marker.setRotationAngle(angle);
        me._marker._latlng = pos;//設定圖示位置
        me._marker.addTo(map);
      }
    },timer);
  }

  /**
   * 緩動效果
   * 初始座標,目標座標,當前的步長,總的步長
   * @private
   */
    function _tween(initPos, targetPos, currentCount, count) {
      var b = initPos, c = targetPos - initPos, t = currentCount,
        d = count;
      return c * t / d + b;
    }

  /**
   * 計算兩點間的距離
   */
  function _getDistance(pxA,pyA, pxB,pyB) {
    return Math.sqrt(Math.pow(pxA - pxB, 2) + Math.pow(pyA - pyB, 2));
  }

  /**
   * 計算角度
   * @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)//第一象限
    {
      return -tan;
    } else if (endx > startx && endy < starty)//第四象限
    {
      return tan;
    } else if (endx < startx && endy > starty)//第二象限
    {
      return tan - 180;
    } else {
      return 180 - tan;  //第三象限
    }
  }

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

  /**
   * 暫停
   */
  function pause() {
    clearInterval(this._intervalFlag);
    //標識是否是按過pause按鈕
    this._fromPause = true;
  }

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

檔案下載地址:https://download.csdn.net/download/wml00000/10769999