一.前言
有这样一个需求:已知某条线上的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(); });
四.实现效果