一.前言
有這樣一個需求:已知某條線上的n個點的經緯度數組 [[116.2968, 39.90245],[116.297443, 39.902454],[116.297454, 39.90312],[116.296295, 39.903133],[116.296258, 39.902454],[116.296794, 39.902446]] ,實現物體運行軌跡。
如果這些點中兩個距離很近,那麼我們可以用一個定時器在地圖上每次重新畫一個點,這樣肉眼看到這個點上的運動效果。
但是如果這些點鐘兩個點距離比較遠,那麼這個軌跡運動效果就是一跳一跳那種,沒有那種連貫性。
二.實現思路
既然兩個點A,B因爲距離比較遠,導致繪製完A點後再繪製B會出現那種A點一下跳到B點的感覺,那麼我們可以在A點B點兩點之間再平均選擇n個點,在繪製完A點之後,再逐個繪製這n個點,最後再繪製B點,這樣就可以達到那種平滑運動的效果。
不過在實現的過程中,要考慮一下幾個問題:
問題1.兩個點之間的距離有的長有的短,那麼在兩個點之間到底選取多少個點比較合適;
問題2.如果點是一個圖標,如一個車輛得圖標,那麼車輛圖標應該與軌跡線平行,並且車頭應該朝向運動的方向;
問題3.儘量採用WGS84進行相關計算,因爲屏幕座標點計算相關後會導致一定得誤差;
解決方案:給物體設定一個運行速度 _speed(千米/秒),假設定時器 _seed (毫秒/次),那麼定時器每次運行的距離爲:_avg_distance = _speed * _seed / 1000 (千米/次)
再通過算法計算AB兩點間的距離爲 _distance(千米),這樣就可以判斷再AB兩點之間定時器要執行多少次也就是要選取多少個點了
然後計算AB兩點得方向角,這個方向角也是選取的N個點得方向角,最後從A點開始,根據A點的經緯度座標、方向角、_avg_distance 逐個計算這n個點的經緯度座標
接下來就可以繪製這些點了,並且是那種平滑得運動效果
三.實現代碼
//WGS84與 web墨卡託相互轉換 define({ // 核心公式 // 平面座標x = 經度*20037508.34/108 // 平面座標y = log(tan((90+緯度)*PI/360))/(PI/360)*20037508.34/180 // 經度= 平面座標x/20037508.34*180 // 緯度= 180/(PI*(2*atan(exp(平面座標y/20037508.34*180*PI/180))-PI/2) longlat2WebMercator: function (longitude, latitude) { var x = longitude * 20037508.34 / 180; // Math.log:求對數函數 var y = Math.log(Math.tan((90 + latitude) * Math.PI / 360)) / (Math.PI / 180); y = y * 20037508.34 / 180; return { "x": x, "y": y }; }, 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 }; } });
//測量計算模塊 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; }, 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; }, 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; }, EARTH_RADIUS: 6378.137, //地球赤道半徑(單位:km) EARTH_ARC: 111.199, //地球每度的弧長(單位:km) _rad: function (val) { //轉化爲弧度(rad) return val * Math.PI / 180.0;; }, distanceByLongLat: function (longitude1, latitude1, longitude2, latitude2) { //求兩經緯度距離(單位:km) 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; //console.info("------------------------------------"); 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 }; } } });
define([ "dojo/_base/declare", 'esri/Color', 'esri/graphic', "esri/geometry/Point", 'esri/geometry/Polyline', 'esri/symbols/SimpleLineSymbol', 'esri/symbols/PictureMarkerSymbol', 'esri/layers/GraphicsLayer', "extras/MeatureTool" ], function (declare, Color, Graphic, Point, Polyline, SimpleLineSymbol, PictureMarkerSymbol, GraphicsLayer, MeatureTool) { 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; _this._pts.length = 0; _this._ptIndex = 0; _this._timer = null; _this._running = true; //定義線符號 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); }, toggle: function () { var _this = this; _this._running = !_this._running; }, _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++; } }); });
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(); });
四.實現效果