最近產品提出一個需求,在我們使用的騰訊地圖上為線路polyline
添加線路方向。例如下圖所示:
查找騰訊地圖JS API提供的API,沒有找到對應的支持,詢問負責騰訊地圖的人也得到了同樣的答案,即地圖JS API不支持線路畫方向。於是否就利用地圖的Marker
類配合旋轉來實現這個功能。
實現原理
因為是利用Marker
來實現Polyline
帶方向箭頭功能,所以要根據線路不同局部的具體走向來旋轉Marker的Icon,從而實現該功能。
另外,我們需要知道:
Marker
的旋轉方向是跟時針方向保持一致的,角度為正表示順時針旋轉,負表示逆時針旋轉。
騰訊地圖的JS API雖然沒有提供畫箭頭的支持,但是可喜的是,騰訊地圖提供了一個類qq.maps.geometry.spherical
,它提供了一些方法用於計算面積、角度和距離,具體可以參考這里。
其中,對於我們實現方向箭頭有用的是以下兩個api:
-
computeHeading(from:LatLng, to:LatLng)
: 返回從一個坐標到另一個坐標的航向。航向是指從一個坐標指向另一個坐標的向量與正北方向的夾角,范圍為[-180,180)。 -
computeDistanceBetween(from:LatLng, to:LatLng, radius?:Number)
: 返回兩坐標點間的距離。
結合上面所描述的,具體的實現原理圖如下圖展示:
具體實現步驟:
- 利用
computeHeading
方法計算航向,然后由其計算Marker
旋轉的角度。
注意:
由航向計算
Marker
旋轉角度,需要根據具體的Marker的Icon圖形來具體分析,不能一概而論。比如本人項目使用的Marker icon圖為水平方向的箭頭,如下圖:
那么,根據該icon圖可以計算對應的marker旋轉角度,具體計算規則如下圖所示。其它方向的Icon可以推算出對應的計算規則。
-
利用
computeDistanceBetween
方法計算兩坐標點中間位置的經緯度 -
創建Marker實例,並設置其Icon和用marker實例的
setRotation
方法來旋轉角度
注意:
有官方聲明marker實例的
setRotation
方法的旋轉角度范圍為0~360
,所以根據計算的旋轉角度必須為這一范圍,否則可能會出現圖形走樣的情況。
實現代碼
正如上面描述的實現原理,下面即是實現為Polyline
實例添加方向箭頭Marker的實現代碼:
function setIcon(marker){
var size = new qq.maps.Size(9, 8); //marker icon圖片大小為18px * 16px, 等比例縮放
var anchor = new qq.maps.Point(5, 4); //經緯度點在圖標中的位置點
var image = require('imgs/arrow.png');
var icon = new qq.maps.MarkerImage(image, size, undefined, anchor, size);
marker.setIcon(icon);
}
//畫marker
function addMarkers(lat, lng, opts){
var position = new qq.maps.LatLng(lat, lng);
var defaultOps = {
map: mapInstance, //mapInstance為對應的qq map實例
position,
zIndex: 8,
visible: true,
draggable: false
}
var options = Object.assign({}, defaultOpts, opts || {});
var marker = new qq.maps.Marker(options);
setIcon(marker);
return marker
}
//計算線路方向箭頭旋轉的方向,heading為兩個經緯度點之間的航向(兩點之間與正北方向的夾角),其范圍為[-180, 180)
function computeRotaion(heading){
let rotation;
if(heading < 0) {
rotation = 270 + heading;
}else {
rotation = heading - 90;
}
return rotation
}
//為polyline添加方向marker
function addArrowMarkers(polyline){
var defaultOps = {
cursor: 'normal',
zIndex: polyline.getZIndex() + 1,
clickable: false,
draggable: false
};
var linePoint = polyline.getPath();//線的經緯度坐標
var arrowCount= linePoint.length;
for(let i = 1; i < arrowCount; i+=2){//不是每兩個點之間都畫箭頭,而是每隔一個間隔畫一個箭頭
let pixelStart = linePoint.getAt(i-1);
let pixelEnd = linePoint.getAt(i);
let heading, rotation, arrowLatLng, marker;
let spherical = qq.maps.geometry.spherical;
let distance = spherical.computeDistanceBetween(pixelStart, pixelEnd); //計算兩經緯度坐標件的距離
if(distance <= 15) {//距離太近小於15m的兩經緯度坐標點間不畫方向
continue;
}
heading = spherical.computeHeading(pixelStart, pixelEnd);//兩經緯度坐標點之間的航向
//計算兩經緯度坐標點中間位置的經緯度
arrowLatLng = spherical.computeOffsetOrigin(pixelEnd, distance/2, heading);
marker = addMarker(arrowLatLng.lat, arrowLatLng.lng, defaultOps);
rotation = computeRotaion(heading); //由兩坐標點之間的航向計算marker要旋轉的角度
marker.setRotation(rotation);
}
}
至此,帶方向的polyline線路就帶有方向箭頭了,可以很清晰的看出線路的走向了。