一.前言
有這樣一個需求:已知某條線上的n個點的經緯度數組 ,實現物體運行軌跡。
如果這些點中兩個距離很近,那么我們可以用一個定時器在地圖上每次重新畫一個點,這樣肉眼看到這個點上的運動效果,如下圖代碼:
var paths = [[116.2968, 39.90245], [116.297443, 39.902454], [116.297454, 39.90312], [116.296295, 39.903133], [116.296258, 39.902454], [116.296794, 39.902446]]; var ptGraphic = new Graphic(); map.add(this.ptGraphic); var index = 0; setInterval(function() { index++; var ptGeometry = new Point({ longitude: paths[index].longitude, latitude: paths[index].latitude }); var ptSymbol = new PictureMarkerSymbol({ url: 'car.png', height: 32, width: 18, type: 'esriPMS' }); ptGraphic.setGeometry(ptGeometry); ptGraphic.setSymbol(ptSymbol); }, 1000);
但是如果這些點鍾兩個點距離比較遠,那么這個軌跡運動效果就是一跳一跳那種,沒有連貫性。
二.實現思路
既然兩個點A,B因為距離比較遠,導致繪制完A點后再繪制B會出現那種A點一下跳到B點的感覺,那么我們可以在A點B點兩點之間再選取多個點,這些點的距離我們肉眼再屏幕上無法感覺到。然后從A點逐個繪制這些點,這樣我們肉眼就不會看到一下子跳到下一個點得感覺,肉眼上觀察就是那種平滑運動的效果。
不過在實現的過程中,要考慮一下幾個問題:
問題1.兩個點之間的距離有的長有的短,那么在兩個點之間到底選取多少個點比較合適;
問題2.如果點是一個圖標,如一個車輛得圖標,那么車輛圖標應該與軌跡線平行,並且車頭應該朝向運動的方向(也就是車輛行駛過程中轉彎的效果);
問題3.盡量采用WGS84進行相關計算,因為屏幕坐標點計算相關后會導致一定得誤差;
解決方案:
問題3:可以通過算法實現 WGS84 與 web 墨卡托之間的相互轉換,詳見:Coordinates.js;
問題2:在實例化 PictureMarkerSymbol 對象時,有一個"angle"屬性,這個就表示圖片的偏移角度。這個偏移角度可以通過AB兩點得經緯度計算得到,詳見:MeatureTool.js;
問題1:給物體設定兩個參數 運行速度 _speed(千米/秒)、定時器執行間隔 _seed (毫秒/次);
那么定時器每次運行的距離為:avg_distance = _speed * _seed / 1000 (千米/次);
再通過 MeatureTool.js 中提供的 distanceByLongLat 函數算法計算AB兩點間的距離為 distance(千米);
這樣我們要在AB兩個點中間選取點得個數就等價於定時器在AB兩個點之間執行的次數: times(單位:次) = distance / avg_distance
然后通過 MeatureTool.js 中提供的 getNextPoint 函數算法逐步計算這些點的經緯度坐標
三.實現代碼
Coordinates.js

/* WGS84與web墨卡托之間的相互轉換 */ define({ /* * 經緯度轉屏幕坐標 * 平面坐標x = 經度*20037508.34/108 * 平面坐標y = log(tan((90+緯度)*PI/360))/(PI/360)*20037508.34/180 */ longlat2WebMercator: function (longitude, latitude) { var x = longitude * 20037508.34 / 180; var y = Math.log(Math.tan((90 + latitude) * Math.PI / 360)) / (Math.PI / 180); y = y * 20037508.34 / 180; return { "x": x, "y": y }; }, /* * 屏幕坐標轉經緯度 * 經度 = 平面坐標x/20037508.34*180 * 緯度 = 180/(PI*(2*atan(exp(平面坐標y/20037508.34*180*PI/180))-PI/2) */ webMercator2LongLat: function (x, y) { var longitude = x / 20037508.34 * 180; var latitude = y / 20037508.34 * 180; latitude = 180 / Math.PI * (2 * Math.atan(Math.exp(latitude * Math.PI / 180)) - Math.PI / 2); return { "longitude": longitude, "latitude": latitude }; } });
MeatureTool.js

/* 測量工具 */ define(["extras/Coordinates"], function (Coordinates) { return { /* 測量兩個屏幕點之間的距離(單位:米) */ lengthByMercator: function (pt1, pt2) { var a_pow = Math.pow((pt1.x - pt2.x), 2); var b_pow = Math.pow((pt1.y - pt2.y), 2); var c_pow = a_pow + b_pow; var length = Math.sqrt(c_pow); return length; }, /* 測量三個屏幕點區域面積(單位:平方米) */ areaByMercator: function (pt1, pt2, pt3) { return ((pt1.x * pt2.y - pt2.x * pt1.y) + (pt2.x * pt3.y - pt3.x * pt2.y) + (pt3.x * pt1.y - pt1.x * pt3.y)) / 2; }, /* 測量兩個屏幕點之間的傾斜角 */ angleByMercator: function (pt1, pt2) { var x = pt2.x - pt1.x; var y = pt2.y - pt1.y; var angle = Math.atan2(y, x); angle = (angle - Math.PI / 2) / Math.PI * 180; return angle; }, /* 測量兩個經緯點之間的傾斜角 */ angleByLongLat: function (longitude1, latitude1, longitude2, latitude2) { var ptTemp1 = Coordinates.longlat2WebMercator(longitude1, latitude1); var ptTemp2 = Coordinates.longlat2WebMercator(longitude2, latitude2); var x = ptTemp2.x - ptTemp1.x; var y = ptTemp2.y - ptTemp1.y; var angle = Math.atan2(y, x); angle = (angle - Math.PI / 2) / Math.PI * 180; return angle; }, EARTH_RADIUS: 6378.137, //地球赤道半徑(單位:km) EARTH_ARC: 111.199, //地球每度的弧長(單位:km) _rad: function (val) { //轉化為弧度(rad) return val * Math.PI / 180.0;; }, /* 測量兩經緯度距離(單位:km) */ distanceByLongLat: function (longitude1, latitude1, longitude2, latitude2) { var r1 = this._rad(latitude1); var r2 = this._rad(longitude1); var a = this._rad(latitude2); var b = this._rad(longitude2); var s = Math.acos( Math.cos(r1) * Math.cos(a) * Math.cos(r2 - b) + Math.sin(r1) * Math.sin(a) ) * this.EARTH_RADIUS; return s; }, /* 測量兩經緯方向角(單位:°) */ azimuthByLongLat: function (longitude1, latitude1, longitude2, latitude2) { var azimuth = 0; if (longitude2 === longitude1 && latitude2 > latitude1) { azimuth = 0; } else if (longitude2 === longitude1 && latitude2 < latitude1) { azimuth = 180; } else if (latitude2 === latitude1 && longitude2 < longitude1) { azimuth = 270; } else if (latitude2 === latitude1 && longitude2 > longitude1) { azimuth = 360; } else { var radLongitude1 = this._rad(longitude1); var radLongitude2 = this._rad(longitude2); var radLatitude1 = this._rad(latitude1); var radLatitude2 = this._rad(latitude2); azimuth = Math.sin(radLatitude1) * Math.sin(radLatitude2) + Math.cos(radLatitude1) * Math.cos(radLatitude2) * Math.cos(radLongitude2 - radLongitude1); azimuth = Math.sqrt(1 - azimuth * azimuth); azimuth = Math.cos(radLatitude2) * Math.sin(radLongitude2 - radLongitude1) / azimuth; azimuth = Math.asin(azimuth) * 180 / Math.PI; if (latitude2 < latitude1) { //console.info("三四象限"); azimuth = 180 - azimuth; } else if (latitude2 > latitude1 && longitude2 < longitude1) { //console.info("第二象限"); azimuth = 360 + azimuth; } // else { // console.info("第一象限"); // } } //console.info(azimuth); return azimuth; }, /* 根據某個點經緯度,另一個點的距離和方向角,獲取另一個點的經緯度 */ getNextPoint: function (longitude1, latitude1, distance, azimuth) { // distance表示兩點間得距離(單位:km) azimuth = this._rad(azimuth); // 將距離轉換成經度的計算公式 var lon = longitude1 + (distance * Math.sin(azimuth)) / (this.EARTH_ARC * Math.cos(this._rad(latitude1))); // 將距離轉換成緯度的計算公式 var lat = latitude1 + (distance * Math.cos(azimuth)) / this.EARTH_ARC; return { "longitude": lon, "latitude": lat }; } } });
MovingLayer.js

define([ "dojo/_base/declare", 'esri/Color', 'esri/graphic', "esri/geometry/Point", 'esri/geometry/Polyline', "esri/geometry/webMercatorUtils", 'esri/symbols/SimpleLineSymbol', 'esri/symbols/PictureMarkerSymbol', 'esri/layers/GraphicsLayer', "extras/MeatureTool", "extras/Coordinates" ], function (declare, Color, Graphic, Point, Polyline, webMercatorUtils, SimpleLineSymbol, PictureMarkerSymbol, GraphicsLayer, MeatureTool, Coordinates) { return declare([GraphicsLayer], { _img: "", _pts: [], _ptIndex: 0, _ptGraphic: null, //圖形要素 _seed: 100, //多長時間執行一次,(單位:毫秒/次) _speed: 10, //物體運行速度(千米/秒) _timer: null, //定時器 _running: true, //定時器運行狀態 initial: function (options) { var _this = this; _this._img = options.img; _this._speed = options.speed || _this._speed; //定義線符號 var lineSymbol = new SimpleLineSymbol(SimpleLineSymbol.STYLE_SOLID, new Color([255, 0, 0]), 2); var lineGeometry = new Polyline({ "paths": options.paths }); var lineGraphic = new Graphic(lineGeometry, lineSymbol); _this.add(lineGraphic); _this._ptGraphic = new Graphic(); _this.add(this._ptGraphic); var pathLastIndex = options.paths[0].length - 1; for (var i = 0; i < pathLastIndex; i++) { var longitude1 = options.paths[0][i][0]; var latitude1 = options.paths[0][i][1]; var longitude2 = options.paths[0][i + 1][0]; var latitude2 = options.paths[0][i + 1][1]; //兩點之間的圖標傾斜角度 var angle = MeatureTool.angleByLongLat(longitude1, latitude1, longitude2, latitude2); //計算兩點之間的方向角(單位:度) var azimuth = MeatureTool.azimuthByLongLat(longitude1, latitude1, longitude2, latitude2); //console.info(azimuth); //將起點添加到數組中 _this._pts.push({ "longitude": longitude1, "latitude": latitude1, "angle": angle }); //計算兩點間的距離(單位:千米) var distance = MeatureTool.distanceByLongLat(longitude1, latitude1, longitude2, latitude2); //定時器平均每次能運行的距離(單位:千米/次) var avg_distance = (_this._speed * _this._seed) / 1000; //如果兩點間得距離小於定時器每次運行的距離,則不用在兩個經緯度點之間選取分割點 if (distance <= avg_distance) { continue; } //計算兩點間,定時器需要執行的次數 var times = distance / avg_distance; for (var j = 1; j < times; j++) { var curr_distance = avg_distance * j var pt = MeatureTool.getNextPoint(longitude1, latitude1, curr_distance, azimuth); pt.angle = angle; _this._pts.push(pt); } } var ptLast = { "longitude": options.paths[0][pathLastIndex][0], "latitude": options.paths[0][pathLastIndex][1], "angle": _this._pts[_this._pts.length - 1].angle }; _this._pts.push(ptLast); _this._ptDraw(); }, //運行動畫效果 run: function () { var _this = this; _this._timer = setInterval(function () { if (_this._running) { if (_this._ptIndex >= _this._pts.length) { clearInterval(_this._timer); } if (_this._ptIndex <= _this._pts.length - 1) { _this._ptDraw(); } } }, _this._seed); }, _ptDraw: function () { var _this = this; var pt = _this._pts[_this._ptIndex]; var ptGeometry = new Point({ "longitude": pt.longitude, "latitude": pt.latitude }); var ptSymbol = new PictureMarkerSymbol({ "url": _this._img, "height": 32, "width": 18, "type": "esriPMS", "angle": pt.angle, }); _this._ptGraphic.setGeometry(ptGeometry); _this._ptGraphic.setSymbol(ptSymbol); _this._ptIndex++; }, toggle: function () { var _this = this; _this._running = !_this._running; } }); });
頁面代碼
require(["esri/map", "arcgis_js_v320_api_ex/MovingLayer", "dojo/domReady!"], function (Map, MovingLayer) { var map = new Map("viewDiv", { "basemap": "streets", "scale": 50000, "center": [116.29, 39.90], }); var paths = [[ [116.2968, 39.90245], [116.297443, 39.902454], [116.297454, 39.90312], [116.296295, 39.903133], [116.296258, 39.902454], [116.296794, 39.902446] ]]; var movingLayer = new MovingLayer(); map.addLayer(movingLayer); movingLayer.initial({ "img": "/static/img/car.png", "paths": paths,
//物體運行速度(千米/秒) "speed": 0.011 }); movingLayer.run(); });
四.實現效果