一.前言

  有这样一个需求:已知某条线上的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
};
}
});

Coordinates.js

  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 };
}
}
});

MeatureTool.js

  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;
}
});
});

MovingLayer.js

  页面代码

        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();
});

四.实现效果

  

Arcgis api for javascript学习笔记(3.2版本) - 匀速行驶轨迹动画效果的更多相关文章

  1. Arcgis api for javascript学习笔记(4.5版本)-三维地图的飞行效果

    其实就只是用到了 view.goTo()  函数,再利用 window.setInterval()  函数(定时器)定时执行goTo().代码如下: <!DOCTYPE html> < ...

  2. Arcgis api for javascript学习笔记(4.5版本) - 获取FeatureLayer中的graphics集合

    在Arcgis api for javascript 3.x 版本中,我们可以直接通过某个FeatureLayer对象中的graphics属性获取要素集合. graphics属性 但是在4.x版本中, ...

  3. Arcgis api for javascript学习笔记(4.5版本) - 本地部署及代理配置

    在开发过程中,由于api的文件比较多,没必要每个项目都将api加入到解决方案中.况且在VS中如果将api加入解决方案,在编写css或js代码时,由于智能提示需要扫描脚本等文件,会导致VS很卡.所以个人 ...

  4. Arcgis api for javascript学习笔记(3.2X版本)-初步尝试

    Arcgis api for javascript(3.22版本)官方地址 :https://developers.arcgis.com/javascript/3/ 1. 根据官方示例实现一个简单地图 ...

  5. Arcgis api for javascript学习笔记(4.6版本) - 二维MapView中的FeatureLayer显示标注

    4.6版本api的FeatureLayer中有提供 labelsVisible 和 labelingInfo 两个属性,设置这两个属性可以实现显示将属性中某个字段作为标注.但是这两个属性只针对三维Sc ...

  6. Arcgis api for javascript学习笔记(4.5版本) - 点击多边形(Polygon)并高亮显示

    在现在的 arcgis_js_v45_api 版本中并没有直接提供点击Polygon对象高亮显示.需要实现如下几个步骤: 1.点击地图时,获取Polygon的Graphic对象: 2.对获取到的Gra ...

  7. Arcgis api for javascript学习笔记(4.5版本)-三维地图实现弹窗功能

    1. 对于Graphic对象,在初始化Graphic对象时设置popupTemplate属性,即可实现点击Graphic时显示弹窗. <!DOCTYPE html> <html> ...

  8. Arcgis api for javascript学习笔记(4.5版本)-三维地图并叠加天地图标注

    1.三维地图实现 在官网的demo中就有三维地图的实现,如下图所示 <!DOCTYPE html> <html> <head> <meta charset=& ...

  9. Arcgis api for javascript学习笔记(3.2X版本)-Map图层叠加以及基本操作

    1. 不设置默认底图,第一个图层作为底图,然后叠加另外一个图层 先添加图层1,第一个图层1作为默认底图,然后在图层1上叠加图层2,并设置图层2的透明度为50%. <!DOCTYPE html&g ...

随机推荐

  1. 将nginx搜集到的日志通过flume转到hive

    背景介绍: Nginx为app打点数据,打点日志每小时滚动一次.目录结构如下 文件中的数据如下( cat -A 2019072414r.log 后的结果,-A为显示隐形的符号,下方^A为指定的分隔符. ...

  2. ubuntu 使用glfw.h 出现函数无法调用

    最近在学习在Ubuntu下使用qt进行opengl开发,使用到了glfw这个库.我安装官网的编译和安装方法进行了配置安装,在usr/local/include的下产生了glfw.h文件. 于是我在我的 ...

  3. jquery 即点即改

    //在html中建立表单. <table border=""> <th>编号</th> <th>用户名</th> < ...

  4. Java练习 SDUT-1211_英文金曲大赛

    英文金曲大赛 Time Limit: 1000 ms Memory Limit: 65536 KiB Problem Description 我们在"渊子数"的题目中已经了解了渊子 ...

  5. ROS开发过程中遇到:Could not find a package configuration file provided by "qt_build" with any of the following names: qt_buildConfig.cmake qt_build-config.cmake........

    最近在搭建QT开发ROS 界面的环境,遇到了很多问题,参考了很多资料,最后发现有些问题其实没有那么复杂,只是我们对整体环境还不了解,熟悉了以后你会发现有些问题就迎刃而解了. 在这个过程中,我首先新建了 ...

  6. Google 各国地址

    google各国域名大全 香港www.google.com.hk 台湾www.google.com.tw 日本www.google.co.jp 中国www.google.cn 韩国www.google ...

  7. 在 windows 安装 Jekyll

    本文告诉大家一个简单的方法在 Windows 安装 Jekyll 下载 ps1 文件 首先需要安装 Chocolatey ,这个工具可以快速安装 Jekyll 先下载Chocolatey,如果无法从这 ...

  8. @loj - 2674@ 「NOI2012」美食节

    目录 @description@ @solution@ @accepted code@ @details@ @description@ CZ 市为了欢迎全国各地的同学,特地举办了一场盛大的美食节. 作 ...

  9. HZOJ matrix

    完全没有思路,状压到死没调出来……吐槽一下这题目描述的好不清楚啊好多人都理解错题了…… 题解: 真的挺神仙的,因为有每列最多放1个的限制,所以考虑按列dp,设f[i][j]表示考虑前i列在[1,i]中 ...

  10. 添加ODBC数据源

    添加ODBC数据源