需求说明
之前做了个在高德地图上用GLCustomLayer加载gltf模型的图层,还在沾沾自喜的时候,领导找到我说你这个图层有问题啊,路灯朝向不对,路灯不照公路转头去照路边小情侣吗,赶紧给我改过来。
一开始接到领导这个任务到时候我内心OS其实拒绝的,5万多个模拟灯杆数据要调朝向得调到什么时候,而且这么无聊又耗时的工作一点意思都没有;领导好像从我向上翻的眼神中看到了我的小心思,他说你可以写个脚本让灯杆自己转啊。不愧是领导,格局一下子就打开了。
实现思路
计算灯杆朝向的角度需要一点点几何知识,我们假设所有灯杆朝向V0未校正前都是正北0度,灯杆正确朝向V1应该与距离其最近道路线段为90度,那么自动偏移的角度应该为V0和V1的夹角θ。
我们可以用向量法求V0和V1的夹角θ,向量的点积公式为
// 已知
V0·V1 = |V0||V1|cosθ
V0·V1 = (x0,y0)·(x1,y1) = x0*x1 + y0*y1
// 将向量V0,V1归一化,即向量长度为1
v0 = Math.sqrt(Math.pow(x0,2)+Math.pow(x1,2))
x0' = x0 / v0
y0' = y0 / v0
// 由此可得
θ = acos(V0·V1/(|V0||V1|)) = Math.acos((x0' * x1' + y0' * y1') / 1)
核心原理是这样,但是在实际开发中不需要自己重头开始写代码,我们借助地理空间分析库Turf.js来简化一下工作。不得不说有轮子就是香。
- 接收一个点要素P和一个线要素L,计算出该点在该线上最近的点要素P1并返回
let P1 = turf.nearestPointOnLine(L,P)
- 计算获取两点之间的地理方位,并与正北方向所形成的角度θ(正北为0度,逆时针到180,顺时针到-180)
let θ = turf.bearing(P,P1)
- 将角度θ转换为0~360正值(即正北0度,逆时针为正方向)
let angle = turf.bearingToAzimuth(θ)
相关代码
// 生成点元素
function addMarker(info) {
let {lngLat, angle, id} = info
let marker = new AMap.Marker({
content: `<img src="./static/icon/arrow-up.svg" style="width: 30px; height: 30px;"/>`,
offset: [-15, -30],
position: lngLat,
angle: angle,
extData: {angle, id}
})
markers.push(marker)
marker.setMap(map)
}
// 自动校准单个点元素朝向
function fitDirection(marker){
let {lng, lat} = marker.getPosition()
let line = turf.lineString(roadPath)
let pt = turf.point([lng,lat])
// 获得在线上,距离点pt最近的点P1
let snapped = turf.nearestPointOnLine(line, pt, {units: 'miles'})
//计算2个点所成方向与正北方向形成的角度
let bear = turf.bearing([lng,lat], snapped.geometry.coordinates)
//接收与正北方向所形成的角度(-180 ~ 180),返回正值(0 ~ 360)
let angle = turf.bearingToAzimuth(bear)
//朝向角度
marker.setAngle(angle)
//更新extData
let extData = marker.getExtData()
marker.setExtData(Object.assign(extData, {angle}))
}
扩展思考
实例中仅仅是用了一条道路的数据做演示,实际项目中有成百上千条道路数据,几万个灯杆数据,需要先做个预处理,将灯杆与道路关联起来。怎么知道每个灯杆要朝哪条道路校正朝向呢,目前我能想到的有两种做法:
1. 每个灯杆数据增加所属道路属性,与道路id关联起来;
2. 通过现有地理信息自动关联,比如将道路L自动拓宽变成多边形面M,位于M里面的点即为L关联的灯杆数据。
批量自动校正的方法当然会有误差,个别数据还需要人工校正,总体来说工作量是大大简化了。
在这里还是强烈推荐一下地理空间分析库Turf.js,GIS开发必备工具箱。