前言
天地一逆旅,人生无觅迹,你我皆行人,只此一迹,记录地图相关系列知识
解决打车时车辆朝向感知难题,提前50米预知车辆来向!
在现代化生活中,打车已经成为许多人出行的首选方式。然而,打车时常常会遇到一个问题:我们不知道车辆的具体位置和朝向,不确定车辆会从哪个方向驶来。这不仅增加了等待的焦虑感,有时还可能因为车辆来向不明而导致错过或延误乘车。于是乎随机挑选一个厂家,腾讯地图,决定去实际探究,看看现有的地图组件如何满足这种日常必要的需求?
重点:计算导航路线50m处点位位置与车辆朝向
一、引入腾讯地图
开发web端地图,首要任务就是引入地图库,腾讯地图的在线JS库地址为:map.qq.com/api/gljs?v=…标签内添加 script 标签来完成。腾讯地图提供了多种引入方式,包括通过CDN引入和使用模块化引入等。
<script charset="utf-8"
src="https://map.qq.com/api/gljs?v=1.exp&key=OB4BZ-D4W3U-B7VVO-4PJWW-6TKDJ-WPB77&libraries=geometry,tools"></script>
注意:
- v=1.exp为API版本号,目前可用1.exp或者2.exp。
- key为官方控制台申请的密钥(可用官方提供的OB4BZ-D4W3U-B7VVO-4PJWW-6TKDJ-WPB77)
- libraies为需要加载的插件(本文用到geometry几何库与tools工具库)
二、初始化地图
首先需要在HTML页面中初始化一个腾讯地图的div实例。这通常包括设置地图的中心点、缩放级别。
map = new TMap.Map('mapContainer', {
center: new TMap.LatLng(39.980619,116.321277),//地图显示中心点
zoom:14 //缩放级别
});
三、请求导航数据与路线绘制
3.1 JSONP请求导航数据
在JavaScript中,你可以使用JSONP来跨域请求Web服务API。由于JSONP是基于 script 标签的,它会自动处理跨域问题,但它只支持GET请求。对于你提供的腾讯地图路线规划API的URL,因此我们只需要去组织url的参数即可:
//WebServiceAPI请求URL(驾车路线规划默认会参考实时路况进行计算)
var url="https://apis.map.qq.com/ws/direction/v1/driving/"; //请求路径
url+="?from=39.984039,116.307630";
url+="&waypoints=39.981491,116.317167;39.985266,116.331934"//轨迹点坐标
url+="&to=39.977263,116.337063"; //终点坐标
url+="&output=jsonp&callback=cb" ; //指定JSONP回调函数名,本例为cb
url+="&key=OB4BZ-D4W3U-B7VVO-4PJWW-6TKDJ-WPB77"; //开发key
var script=document.createElement('script');
script.src=url;
document.body.appendChild(script);
注意:
- url中参数分别包含from起点坐标,to终点坐标,waypoint途经点,callback回调函数以及key密钥
- 起终点与途经点坐标可以用官方示例去拾取
- 导航接口的返回值可在回调函数中调用
3.2 绘制导航线路数据
此步骤是用来处理上步骤返回的导航返回数据,利用导航返回数据去绘制起点和终点,并绘制导航路线示意图。
//定义请求回调函数,在此拿到计算得到的路线,并进行绘制
function cb(ret){
var coords = ret.result.routes[0].polyline, pl = [];
var kr = 1000000;
for (var i = 2; i < coords.length; i++) {
coords[i] = Number(coords[i - 2]) + Number(coords[i]) / kr;
}
for (var i = 0; i < coords.length; i += 2) {
pl.push(new TMap.LatLng(coords[i], coords[i+1]));
}
//标记起终点以及途经点marker
var marker = new TMap.MultiMarker({
id: 'marker-layer',
map: map,
styles: {
"start": new TMap.MarkerStyle({
"width": 25,
"height": 35,
"anchor": { x: 16, y: 32 },
"src": 'https://mapapi.qq.com/web/lbs/javascriptGL/demo/img/start.png'
}),
"waypoint": new TMap.MarkerStyle({
"width": 25,
"height": 35,
"anchor": { x: 16, y: 32 },
}),
"end": new TMap.MarkerStyle({
"width": 25,
"height": 35,
"anchor": { x: 16, y: 32 },
"src": 'https://mapapi.qq.com/web/lbs/javascriptGL/demo/img/end.png'
})
},
geometries: [{
"id": 'start',
"styleId": 'start',
"position": new TMap.LatLng(39.984039,116.307630307503)
},
{
"id": 'marker',
"styleId": 'waypoint',
"position": new TMap.LatLng(39.981491,116.317167)
},
{
"id": 'marker',
"styleId": 'waypoint',
"position": new TMap.LatLng(39.977128,116.324964)
}, {
"id": 'end',
"styleId": 'end',
"position": new TMap.LatLng(39.977263,116.337063)
}]
});
}
function display_polyline(pl){
//创建 MultiPolyline显示折线
var polylineLayer = new TMap.MultiPolyline({
id: 'polyline-layer',
map: map,
styles: {
style_blue: new TMap.PolylineStyle({
color: '#3777FF',
width: 8,
borderWidth: 0,
showArrow: true,
arrowOptions: {
space: 70
},
lineCap: 'round',
})
},
geometries: [{
id: 'pl_1',
styleId: 'style_blue',//绑定样式名
paths: pl
}]
});
}
注意:
- cb函数为导航请求回调函数(负责接受并处理导航数据,并绘制起终点及途经点)
- display_polyline函数为绘制折线函数(负责根据处理后的数据进行在地图上绘制折线)
四、计算距离下一个途经点50m处的位置与车辆朝向
讲解这一步骤之前,需要弄清楚几个概念,我在下图中已列出。
其中:
- 导航路线由轨迹段和轨迹点组成
- 导航会根据途经点,将其归化到最近的导航线路上,此点解释为归化点。
首先确定归化站点(途经点)在导航轨迹点组中的序号,利用序号,依次去倒序遍历导航轨迹点数组,计算每一个轨迹段的距离,通过距离累加值和50m去做判断。此处根据50m位置在归化点是否在上一个轨迹段分两种情况。完整步骤如下:
- 获取轨迹站点的序号
- 遍历途经点
- 绘制途经点标记
- 计算累计距离并查找导航点
- 计算航向和导航点位置
- 显示信息窗口
// 获取轨迹站点的序号
let waypointIndex = data.waypoints.map(item => item.polyline_idx);
// 在返回轨迹数组中,计算轨迹站点往前的累计距离
for (let i = 0; i < waypointIndex.length; i++) {
let maxDistance = 50
let addDistance = 0
// 绘制导航轨迹上的途经点(即归化点)
new TMap.MultiMarker({
map: this.map,
styles: {
"marker": new TMap.MarkerStyle({
"width": 25,
"height": 35,
"anchor": { x: 16, y: 32 },
})
},
geometries: [{
"id": 'marker',
"styleId": 'marker',
"position": new TMap.LatLng(data.polyline[waypointIndex[i]],data.polyline[waypointIndex[i]+1])
}]
});
// 按照归化点的序号去倒序循环导航轨迹点数组,计算距离,依次相加,计算出满足要求点位,并计算航向
for (let j = waypointIndex[i]; j > 0; j-=2) {
let point1 = new TMap.LatLng(data.polyline[j], data.polyline[j+1]);
let point2 = new TMap.LatLng(data.polyline[j-2], data.polyline[j-1]);
let distance = TMap.geometry.computeDistance([point1,point2]);
// 计算累加距离
addDistance += distance
// 如果累加距离大于规定最大距离,则进行航线和最大距离点查找
if(addDistance>=maxDistance) {
// 计算整体航向
let head = TMap.geometry.computeHeading(point2, point1, true)
// 去除负值航向
if(head<0){head = 360+head}
// 计算最大距离50m点
let lastpoint
// 注意:此处要区别最大距离点与归化点,在第一段还是其他段,根据循环判断,若循环为第一次,则证明为第一段
// computeDestination函数作用为,输入起始点和朝向以及距离,计算起始点沿朝向固定距离出的点
if(waypointIndex[i]==j){
let heading1 = TMap.geometry.computeHeading(point1, point2, true)
lastpoint = TMap.geometry.computeDestination(point1, heading1, maxDistance)
} else {
let heading2 = TMap.geometry.computeHeading(new TMap.LatLng(data.polyline[j], data.polyline[j+3]), new TMap.LatLng(data.polyline[j-2], data.polyline[j+1]), true)
lastpoint = TMap.geometry.computeDestination(
new TMap.LatLng(data.polyline[j], data.polyline[j+3]),
heading2,
maxDistance-addDistance+distance
)
}
new TMap.InfoWindow({
map: map,
position: lastpoint, //设置信息框位置
content: '距离最近途经点为50m'+'车头朝向为:'+ head//设置信息框内容
});
new TMap.MultiMarker({
map: map,
styles: {
"marker": new TMap.MarkerStyle({
"width": 25,
"height": 35,
"anchor": { x: 16, y: 32 },
})
},
geometries: [{
"id": 'marker',
"styleId": 'marker',
"position": lastpoint
}]
});
break
}
}
}
五、全部代码
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<meta http-equiv="X-UA-Compatible" content="ie=edge">
<title>TMap地图导航</title>
</head>
<script charset="utf-8"
src="https://map.qq.com/api/gljs?v=1.exp&key=OB4BZ-D4W3U-B7VVO-4PJWW-6TKDJ-WPB77&libraries=geometry,tools"></script>
<style>
html,
body {
height: 100%;
margin: 0px;
padding: 0px;
}
#mapContainer {
width: 100%;
height: 100%;
}
</style>
<body onload="initMap()">
<div id="mapContainer"></div>
</body>
</html>
<script>
//注:本例仅为说明流程,不保证严谨
let map;
let measureTool
function initMap(){
//初始化地图
map = new TMap.Map('mapContainer', {
center: new TMap.LatLng(39.980619,116.321277),//地图显示中心点
zoom:14 //缩放级别
});
// 创建测量工具
measureTool = new TMap.tools.MeasureTool({
// TMap.tools.MeasureTool文档地址:https://lbs.qq.com/webApi/javascriptGL/glDoc/glDocEditor#5F
map: map,
});
//WebServiceAPI请求URL(驾车路线规划默认会参考实时路况进行计算)
var url="https://apis.map.qq.com/ws/direction/v1/driving/"; //请求路径
url+="?from=39.984039,116.307630"; //起点坐标
// 轨迹点坐标拾取网址:https://lbs.qq.com/webDemoCenter/glAPI/glMap/mapPosition
url+="&waypoints=39.981491,116.317167;39.985266,116.331934"//轨迹点坐标
url+="&to=39.977263,116.337063"; //终点坐标
url+="&output=jsonp&callback=cb" ; //指定JSONP回调函数名,本例为cb
url+="&key=OB4BZ-D4W3U-B7VVO-4PJWW-6TKDJ-WPB77"; //开发key
//发起JSONP请求,获取路线规划结果
jsonp_request(url);
}
//浏览器调用WebServiceAPI需要通过Jsonp的方式,此处定义了发送JOSNP请求的函数
function jsonp_request(url){
var script=document.createElement('script');
script.src=url;
document.body.appendChild(script);
}
//定义请求回调函数,在此拿到计算得到的路线,并进行绘制
function cb(ret){
measureTool.enable();
measureTool.measureDistance()
var coords = ret.result.routes[0].polyline, pl = [];
var kr = 1000000;
for (var i = 2; i < coords.length; i++) {
coords[i] = Number(coords[i - 2]) + Number(coords[i]) / kr;
}
for (var i = 0; i < coords.length; i += 2) {
pl.push(new TMap.LatLng(coords[i], coords[i+1]));
}
display_polyline(pl)//显示路线
foundPoint(ret.result.routes[0],pl)
//标记起终点marker
var marker = new TMap.MultiMarker({
id: 'marker-layer',
map: map,
styles: {
"start": new TMap.MarkerStyle({
"width": 25,
"height": 35,
"anchor": { x: 16, y: 32 },
"src": 'https://mapapi.qq.com/web/lbs/javascriptGL/demo/img/start.png'
}),
"waypoint": new TMap.MarkerStyle({
"width": 25,
"height": 35,
"anchor": { x: 16, y: 32 },
}),
"end": new TMap.MarkerStyle({
"width": 25,
"height": 35,
"anchor": { x: 16, y: 32 },
"src": 'https://mapapi.qq.com/web/lbs/javascriptGL/demo/img/end.png'
})
},
geometries: [{
"id": 'start',
"styleId": 'start',
"position": new TMap.LatLng(39.984039,116.307630307503)
},
{
"id": 'marker',
"styleId": 'waypoint',
"position": new TMap.LatLng(39.981491,116.317167)
},
{
"id": 'marker',
"styleId": 'waypoint',
"position": new TMap.LatLng(39.977128,116.324964)
}, {
"id": 'end',
"styleId": 'end',
"position": new TMap.LatLng(39.977263,116.337063)
}]
});
}
function display_polyline(pl){
//创建 MultiPolyline显示折线
var polylineLayer = new TMap.MultiPolyline({
id: 'polyline-layer',
map: map,
styles: {
style_blue: new TMap.PolylineStyle({
color: '#3777FF',
width: 8,
borderWidth: 0,
showArrow: true,
arrowOptions: {
space: 70
},
lineCap: 'round',
})
},
geometries: [{
id: 'pl_1',
styleId: 'style_blue',//绑定样式名
paths: pl
}]
});
}
// 寻找45-50m的路线段
// 首先找到轨迹点在路径中的序号,然后去根据序号在路径点组中往前找点位坐标,
// 并依次计算点位坐标的距离,依次相加,直到大于45m
function foundPoint(data,pl){
for (let i = 0; i < data.polyline.length-1; i += 2) {
new TMap.MultiMarker({
map: map,
styles: {
"start": new TMap.MarkerStyle({
"width": 25,
"height": 35,
"anchor": { x: 16, y: 32 },
"src": 'https://mapapi.qq.com/web/lbs/javascriptGL/demo/img/start.png'
})
},
geometries: [{
"id": 'start',
"styleId": 'start',
"position": new TMap.LatLng(data.polyline[i],data.polyline[i+1])
}]
});
}
// 获取轨迹站点的序号
let waypointIndex = data.waypoints.map(item => item.polyline_idx);
// 在返回轨迹数组中,计算轨迹站点往前的累计距离
for (let i = 0; i < waypointIndex.length; i++) {
let maxDistance = 50
let addDistance = 0
// 绘制导航轨迹上的途经点(即归化点)
new TMap.MultiMarker({
map: this.map,
styles: {
"marker": new TMap.MarkerStyle({
"width": 25,
"height": 35,
"anchor": { x: 16, y: 32 },
})
},
geometries: [{
"id": 'marker',
"styleId": 'marker',
"position": new TMap.LatLng(data.polyline[waypointIndex[i]],data.polyline[waypointIndex[i]+1])
}]
});
// 按照归化点的序号去倒序循环导航轨迹点数组,计算距离,依次相加,计算出满足要求点位,并计算航向
for (let j = waypointIndex[i]; j > 0; j-=2) {
let point1 = new TMap.LatLng(data.polyline[j], data.polyline[j+1]);
let point2 = new TMap.LatLng(data.polyline[j-2], data.polyline[j-1]);
let distance = TMap.geometry.computeDistance([point1,point2]);
// 计算累加距离
addDistance += distance
// 如果累加距离大于规定最大距离,则进行航线和最大距离点查找
if(addDistance>=maxDistance) {
// 计算整体航向
let head = TMap.geometry.computeHeading(point2, point1, true)
// 去除负值航向
if(head<0){head = 360+head}
// 计算最大距离50m点
let lastpoint
// 注意:此处要区别最大距离点与归化点,在第一段还是其他段,根据循环判断,若循环为第一次,则证明为第一段
// computeDestination函数作用为,输入起始点和朝向以及距离,计算起始点沿朝向固定距离出的点
if(waypointIndex[i]==j){
let heading1 = TMap.geometry.computeHeading(point1, point2, true)
lastpoint = TMap.geometry.computeDestination(point1, heading1, maxDistance)
} else {
let heading2 = TMap.geometry.computeHeading(new TMap.LatLng(data.polyline[j], data.polyline[j+3]), new TMap.LatLng(data.polyline[j-2], data.polyline[j+1]), true)
lastpoint = TMap.geometry.computeDestination(
new TMap.LatLng(data.polyline[j], data.polyline[j+3]),
heading2,
maxDistance-addDistance+distance
)
}
new TMap.InfoWindow({
map: map,
position: lastpoint, //设置信息框位置
content: '距离最近途经点为50m'+'车头朝向为:'+ head//设置信息框内容
});
new TMap.MultiMarker({
map: map,
styles: {
"marker": new TMap.MarkerStyle({
"width": 25,
"height": 35,
"anchor": { x: 16, y: 32 },
})
},
geometries: [{
"id": 'marker',
"styleId": 'marker',
"position": lastpoint
}]
});
break
}
}
}
}
</script>
原文链接:https://juejin.cn/post/7347221064428716082 作者:人生陈迹