基於OpenLayers+rbush實現高德軌跡樣式

一 前言

  近期翻閱博客,看到社區大神一休哥的一篇《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

 

  觀察示意圖,總結以下:

  • 一條完整的軌跡由多個連續的segment組成。
  • 經過getCoordinateAt方法計算獲得的箭頭點,必定是在軌跡線上的某個點。
  • 每一個箭頭點的方向是由箭頭點落在的segment的方向決定的。
    很顯然,計算箭頭方向其實就是計算每一個箭頭點到底落在了哪一個segment上,將segme方向賦予箭頭點。這裏咱們引入了rbush庫構建空間索引,計算軌跡點與segment對應關係。
      之因此我要引入rbush庫,是解決循環計算問題,想象下若是不引入rbush庫,只能使用以下的僞代碼暴力計算了:
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 來源:簡書 簡書著做權歸做者全部,任何形式的轉載都請聯繫做者得到受權並註明出處。

相關文章
相關標籤/搜索