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