近期翻閱博客,看到社區大神一休哥的一篇《canvas 奇巧淫技(二)繪製箭頭路徑效果》文章,一樣,該大神還展現過一個使用rbush庫如何在前端快速從海量數據進行空間檢索的案例:https://alex2wong.github.io/mapbox-plugins/examples/rbush/,頗有分享精神的前端GIS專家,更多關於前端GIS檢索數據的技術可參考搜狐的乾貨專訪:《深刻理解空間搜索算法 ——數百萬數據中的瞬時搜索》。關於軌跡樣式帶導航箭頭這種常見問題,筆者基於興趣和朋友們的總結,也試着用熟悉的OpenLayers的StyleFunction去實現一個這樣的玩具,在此分享給你們。前端
高德軌跡箭頭.pnggit
基於已知的一條軌跡,實現這樣的一個導航軌跡箭頭,須要解決三個問題:github
由高德軌跡箭頭圖可知,每隔固定像素,打上一個箭頭。假設當前的線LineString地理長度爲length,當前固定像素間隔stpes=n像素,在當前地圖比例尺res已知的狀況下,n像素地理距離是resn,那麼箭頭總數count=length/(resn):算法
let length=line_geom.getLength();//線圖形的地理長度 const steps=40;//每隔40像素打一個箭頭點 let geo_steps=map_res*steps;//40像素長度在當前地圖比例尺下地理長度。 let arrow_count=length*1.0/geo_steps;
多麼淺顯易懂的道理啊,第一個問題很順利的解決了。canvas
第一步獲得了箭頭的總數,在獲取箭頭位置時,一個重要的API是線條LineString的getCoordinateAt,利用它咱們在軌跡線上獲取箭頭點的位置。app
/* fraction:參考點的百分比,如0就是LineString的起點,1就是LineString的終點,0.5就是LineString的中點。 */ linestring.getCoordinateAt(fraction, opt_dest)
假如箭頭總數爲arrowsNum,那麼arrowsNum個箭頭的數量分別是post
for(let i=1;i<arrowsNum;i++){ let arraw_coor=geometry.getCoordinateAt(i*1.0/arrowsNum); console.log(arraw_coor);//輸出每一個箭頭的座標 }
獲得每一個箭頭的位置後,咱們先可視化下吧,OpenLayers的地圖樣式徹底由StyleFunction實現的,完整樣式代碼以下:測試
/* feature:地圖上的要素對象,既有屬性,也有座標圖形。 res:當前地圖分辨率參數。 return:返回一個定製的渲染樣式 */ var styleFunction = function(feature,res){ //軌跡線圖形 var trackLine= feature.getGeometry(); var styles = [ new ol.style.Style({ stroke: new ol.style.Stroke({ color: '#2E8B57', width: 10 }) }) ]; //軌跡地理長度 let length=trackLine.getLength(); //像素間隔步長 let stpes=40;//像素步長間隔 //將像素步長轉實際地理距離步長 let geo_steps=stpes*res; //箭頭總數 let arrowsNum=parseInt(length/geo_steps); for(let i=1;i<arrowsNum;i++){ let arraw_coor=trackLine.getCoordinateAt(i*1.0/arrowsNum); styles.push(new ol.style.Style({ geometry: new ol.geom.Point(arraw_coor), image: new ol.style.Circle({ radius: 7, fill: new ol.style.Fill({ color: '#ffcc33' }) }) })); } return styles; }
箭頭位置計算與可視化結果.pngspa
以前的邏輯,咱們已經計算了一個軌跡樣式的雛形了,把地圖上箭頭位置的黃點改爲一個箭頭圖標,作下方向旋轉就能夠了。在說明此前,須要說明下軌跡,segment線段,箭頭點之間的關係,以下圖:code
軌跡,segment,箭頭位置之間的關係.png
觀察示意圖,總結以下:
for(let i=0;i<arrows.length;i++){ for(let j=0;j<segments.length;j++){ if(instersects(arrows[i],segments[j])===true){ // arrows[i]對應的segments是segments[j] break; } } }
感受邏輯很簡單啊,這樣作難道不能夠嗎?想象下,箭頭數量,segment的數量其實都是不可控的,一個複雜的軌跡線可能由成百上千的近萬的segments,這樣一個個循環去匹配,效率是否是就有問題了?因此引入了空間索引。這裏查詢,使用了rbush進行btree查詢,查詢的結果後再詳細比對是否和箭頭相交,累了,直接貼代碼了,不詳述了:
var styleFunction = function(feature,res){ //軌跡線圖形 var trackLine= feature.getGeometry(); var styles = [ new ol.style.Style({ stroke: new ol.style.Stroke({ color: '#2E8B57', width: 10 }) }) ]; //對segments創建btree索引 let tree= rbush();//路段數 trackLine.forEachSegment(function(start, end) { var dx = end[0] - start[0]; var dy = end[1] - start[1]; //計算每一個segment的方向,即箭頭旋轉方向 let rotation = Math.atan2(dy, dx); let geom=new ol.geom.LineString([start,end]); let extent=geom.getExtent(); var item = { minX: extent[0], minY: extent[1], maxX: extent[2], maxY: extent[3], geom: geom, rotation:rotation }; tree.insert(item); }); //軌跡地理長度 let length=trackLine.getLength(); //像素間隔步長 let stpes=40;//像素步長間隔 //將像素步長轉實際地理距離步長 let geo_steps=stpes*res; //箭頭總數 let arrowsNum=parseInt(length/geo_steps); for(let i=1;i<arrowsNum;i++){ let arraw_coor=trackLine.getCoordinateAt(i*1.0/arrowsNum); let tol=10;//查詢設置的點的容差,測試地圖單位是米。若是是4326座標系單位爲度的話,改爲0.0001. let arraw_coor_buffer=[arraw_coor[0]-tol,arraw_coor[1]-tol,arraw_coor[0]+tol,arraw_coor[1]+tol]; //進行btree查詢 var treeSearch = tree.search({ minX: arraw_coor_buffer[0], minY: arraw_coor_buffer[1], maxX: arraw_coor_buffer[2], maxY: arraw_coor_buffer[3] }); let arrow_rotation; //只查詢一個,那麼確定是它了,直接返回 if(treeSearch.length==1) arrow_rotation=treeSearch[0].rotation; else if(treeSearch.length>1){ let results=treeSearch.filter(function(item){ //箭頭點與segment相交,返回結果。該方法實測不是很準,多是計算中間結果 //保存到小數精度致使查詢有點問題 // if(item.geom.intersectsCoordinate(arraw_coor)) // return true; //換一種方案,設置一個稍小的容差,消除精度問題 let _tol=1;//消除精度偏差的容差 if(item.geom.intersectsExtent([arraw_coor[0]-_tol,arraw_coor[1]-_tol,arraw_coor[0]+_tol,arraw_coor[1]+_tol])) return true; }) if(results.length>0) arrow_rotation=results[0].rotation; } styles.push(new ol.style.Style({ geometry: new ol.geom.Point(arraw_coor), image: new ol.style.Icon({ src: '../static/content/images/arrowright.png', anchor: [0.75, 0.5], rotateWithView: true, rotation: -arrow_rotation }) })); } return styles; }
軌跡箭頭效果圖.png
看着還湊合吧,但其實要作到高德那個精細的樣式,才萬里第一步,祝諸君繼續研究,期待更好的效果。
做者:遙想公瑾當年 連接:https://www.jianshu.com/p/e68e8e1b7474 來源:簡書 簡書著做權歸做者全部,任何形式的轉載都請聯繫做者得到受權並註明出處。