基於Leaflet實現路徑軌跡回放功能
阿新 • • 發佈:2018-11-08
效果圖:
說明:
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: '© <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>