问题背景
使用 gopro 记录骑行过程 (参考《使用二手 gopro 做行车记录仪 》),事后将视频文件导出来回顾整个旅程,会发现将它们与地图对应起来是一件困难的事。想要视频和地图对应,首先需要上报每个时刻的位置,gopro 本身是支持的,然而要到版本 5 才可以,我的 3+ 太老了没这能力。为此我配备了专门的 GPS 定位器来记录骑行轨迹 (e.g. 途强定位),在官网上是可以看到整个骑行轨迹,像下面这样:
这个界面也可以回放轨迹,回放速度还能调整:
不过即使调到最慢,速度相对视频还是快,更最要命的是,这个回放看起来并不参考 GPS 时间,仅仅是按顺序播放。举例来说,间隔 10 秒的两个点和间隔 10 分钟的两个点,播放时没有差别,都是相同的速度播放。
所幸 GPS 轨迹数据是可以导出的:
如果能用导出的轨迹数据,根据 GPS 时间精准的制作骑行轨迹,是不是就能和视频同步了?抱着这个想法,有了下面的探索过程。
可行性研究
DashWare
根据 GPS 数据制作骑行轨迹并不算一个新鲜需求,早期玩极限运动的各位先驱早已经探索的明明白白,也有专业的软件支持这种需求,DashWare 就是其中的佼佼者。之前看到 B 站上一个旅游区 UP,他就介绍过一种基于 DashWare 给国外徒步旅行的游记视频加轨迹路线的方法 (参考附录 15),整个过程可以总结为三步:
- 抽取视频的 GPX 信息
- 编辑 GPX 然后快速跳转地图截图
- 用 Dashware 套用作者的模板生成轨迹路径
这个过程严重依赖视频记录的 GPX 信息,而我的硬件设备不支持,放弃。如果你的设备可以支持,其实用 DashWare 还是蛮方便的。
晒一下我自己配的 GPS 定位器:
这种硬件不太方便的地方是需要单独供电并插流量卡,后者只保两年,两年以后需要自己续费或买流量卡应付。GPS 数据量不大,据客服说一直不停上报大约需要 22M/月,我骑的少 3M 就够用,最后在某宝上配的 5M/月 的移动流量卡大约 8.7 元,给各位做个参考。流量查询和续费可以通过公众号进行:
百度路书
根据 GPS 数据绘制轨迹,其实国内各大地图服务商都提供了解决方案,偶然的一个机会看到使用百度地图的路书可以方便快捷的制作行驶轨迹 (参考附录 1),最终效果和我的场景非常相似:
源码不过寥寥一百行,其中关键的就是下面这几十行:
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8" />
<title>百度地图显示车辆运行轨迹(静态)</title>
<script type="text/javascript" src="http://api.map.baidu.com/api?v=2.0&ak=你在百度地图开放平台申请的ak"></script>
<!-- 你在百度地图开放平台申请的ak -->
<!-- 路书功能 -->
<script type="text/javascript" src="http://api.map.baidu.com/library/LuShu/1.2/src/LuShu_min.js"></script>
</head>
<body>
<div id="allmap" style="position: absolute; width: 100%; top: 0px; bottom: 0px"></div>
<script type="text/javascript">
...
//显示车辆轨迹线
//车辆轨迹坐标
var latLngArray = [
"113.408984,23.174023",
"113.406639,23.174023",
"113.403944,23.173566",
"113.400827,23.17394",
"113.397468,23.174496",
"113.391494,23.174513",
"113.389032,23.174588",
"113.388736,23.173217",
"113.388511,23.171888",
"113.388592,23.170501",
"113.38861,23.170219",
"113.38861,23.168342",
"113.388574,23.165218"
];
...
var pois = [];
for(var i = 0; i < latLngArray.length ; i++) {
var latLng = latLngArray[i];
var pointArray = latLng.split(",");
pois.push(new BMap.Point(pointArray[0], pointArray[1]));
}
...
var polyline = new BMap.Polyline(pois, {
enableEditing: false,//是否启用线编辑,默认为false
enableClicking: true,//是否响应点击事件,默认为true
icons: [icons],
strokeWeight: '8',//折线的宽度,以像素为单位
strokeOpacity: 0.8,//折线的透明度,取值范围0 - 1
strokeColor: "#18a45b" //折线颜色
});
map.addOverlay(polyline);
...
var lushu = new BMapLib.LuShu(map,pois,{
defaultContent: trackContent,
autoView:false,//是否开启自动视野调整,如果开启那么路书在运动过程中会根据视野自动调整
icon: icon_gps_car_run,
speed: 100,
enableRotation:true,//是否设置marker随着道路的走向进行旋转
});
lushu.start();
</script>
</body>
</html>
梳理一下其中的关键点:
- html header 中声明百度地图的 ak (申请方式原作者有说明)
- body 中创建 GPS 坐标数组 (latLngArray) 并转化为路书能接受的格式 (pois)
- 创建轨迹总览线 (polyline)
- 制作小车移动轨迹 (lushu.start)
可以看到核心功能其实都是通过百度地图 js 类 BMapLib.LuShu 实现的,下面好好研究一下它的接口。
BMapLib.LuShu
这个类的官方文档可参考附录 16,提供的方法主要如下:
- constructor
- start
- stop
- pause
非常简洁。其中 start / stop / pause 都不再提供参数,所以想进行更复杂的控制只能事先在构造函数中指定了:
BMapLib.LuShu(map, path, opts)
LuShu类的构造函数
参考示例:
var lushu = new BMapLib.LuShu(map,arrPois,{defaultContent:"从北京到天津",landmarkPois:[]});
参数:
{Map} map
Baidu map的实例对象.
{Array} path
构成路线的point的数组.
{Json Object} opts
可选的输入参数,非必填项。可输入选项包括:
{
"landmarkPois" : {Array} 要在覆盖物移动过程中,显示的特殊点。格式如下:landmarkPois:[
{lng:116.314782,lat:39.913508,html:'加油站',pauseTime:2},
{lng:116.315391,lat:39.964429,html:'高速公路收费站,pauseTime:3}]
"icon" : {Icon} 覆盖物的icon,
"speed" : {Number} 覆盖物移动速度,单位米/秒
"defaultContent" : {String} 覆盖物中的内容
"autoView" : {Boolean} 是否自动调整路线视野,默认不调整
"enableRotation" : {Boolean} 是否开启marker随路走向旋转,默认为false,即不随路走向旋转
}
除 map、path 是必需参数外,其它均为可选参数。下面对各个选项做个简单说明:
- speed:用于控制小车移动的速度,单位是 m/s,示例中给的值是 100,相当于 360 km/h,那是相当快了,如果按 72 km/h 算的话,才 20 m/s
- autoView:随着小车的移动,自动调整地图位置,以保证小车位于视野之内,一般是在小车走出视野边缘后进行调整。推荐打开,除非是鹰眼视图
- icon:小车的图形,可以指定本地文件
- defaultContent:对轨迹的文字说明,跟随在小车左右
- enableRotation:是否旋转小车图形以对准前进方向。推荐打开,以获取更好的演示效果
- landmarkPois:设置的途经点数组,及在途经点的经停时间,单位为秒,这个选项在示例中未使用
梳理了一遍 LuShu 的功能,发现即使能将小车移动速度调整到与实际平均速度一致,地图与视频仍然对不上。原因与之前一样,LuShu 中根本没有输入 GPS 时间参数的地方,所以它完全没有坐标点与时间对照的概念,所有坐标之间的时间间隔都是一致的,唯一可调节的地方就是 speed 参数,它用来控制这个间隔的大小。
回过头来看途强在线的界面,基本可以确论,这也是基于 LuShu 改的,所以它们的问题是相通的。
定时器
现在问题的关键就变成如何等待真实的时间间隔。一开始想手动 pause 和 start,写了个定时器来做这个事情:
...
var lushu = new BMapLib.LuShu(map,pois,{
defaultContent: trackContent,
autoView:false,//是否开启自动视野调整,如果开启那么路书在运动过程中会根据视野自动调整
icon: icon_gps_car_run,
speed: 100,
enableRotation:true,//是否设置marker随着道路的走向进行旋转
});
lushu.start();
let is_pause=false
setInterval(function(){
if (is_pause) {
lushu.start();
} else {
lushu.pause();
}
is_pause = !is_pause;
}, 1000);
...
先简单的设置成每秒一次,后面可以根据实际的时间差来控制等待时间。运行时发现,第一次定时器到期后小车暂停,然后就没有然后了…小车再也没有启动过。在定时器函数中加了一些日志进一步观察:
interval false, count 1
after true
interval true, count 2
interval true, count 3
interval true, count 4
interval true, count 5
interval true, count 6
interval true, count 7
interval true, count 8
interval true, count 9
interval true, count 10
interval true, count 11
发现函数结尾处只被调用了一次 (after true),之后就再也没打印,且 is_pause 的值一直为 true,可以确认反转 is_pause 值的代码没有被执行。
为了确认是否是变量作用域的问题,增加了一个全局变量 count,每次在函数入口处自增并打印它的值,可以看到能正常递增,排除 js 变量作用域的问题。
经过这番折腾,基本可以确认问题是出在了 start 接口,看现象再次调用它貌似没有返回,怀疑这个接口是不能重入的,或者就不能这样用,定时器方案走不下去了,放弃。
landmarkPois
正所谓“踏破铁鞋无觅处,柳暗花明又一村”
标签:11,lat,html,路书为,lng,pauseTime,data,骑行,百度 From: https://www.cnblogs.com/goodcitizen/p/add_synchronous_track_for_ride_video_by_baidu_roadma