基于OpenLayers+rbush實(shí)現(xiàn)高德軌跡樣式

一 前言

??近期翻閱博客,看到社區(qū)大神一休哥的一篇《canvas 奇巧淫技(二)繪制箭頭路徑效果》文章裸影,同樣柄粹,該大神還展示過一個(gè)使用rbush庫如何在前端快速從海量數(shù)據(jù)進(jìn)行空間檢索的案例:https://alex2wong.github.io/mapbox-plugins/examples/rbush/,很有分享精神的前端GIS專家昵宇,更多關(guān)于前端GIS檢索數(shù)據(jù)的技術(shù)可參考搜狐的干貨專訪:《深入理解空間搜索算法 ——數(shù)百萬數(shù)據(jù)中的瞬時(shí)搜索》逗宁。關(guān)于軌跡樣式帶導(dǎo)航箭頭這種常見問題柄慰,筆者基于興趣和朋友們的總結(jié),也試著用熟悉的OpenLayers的StyleFunction去實(shí)現(xiàn)一個(gè)這樣的玩具哩都,在此分享給大家魁兼。

高德軌跡箭頭.png

??基于已知的一條軌跡,實(shí)現(xiàn)這樣的一個(gè)導(dǎo)航軌跡箭頭漠嵌,需要解決三個(gè)問題:

  • 在軌跡上根據(jù)固定像素間隔咐汞,計(jì)算當(dāng)前地圖分辨率下箭頭總數(shù)量。
  • 計(jì)算當(dāng)前地圖分辨率下儒鹿,每個(gè)箭頭的繪制位置化撕。
  • 計(jì)算好箭頭的數(shù)量和位置后,要確定箭頭的方向约炎。

一 箭頭數(shù)量

??由高德軌跡箭頭圖可知植阴,每隔固定像素,打上一個(gè)箭頭圾浅。假設(shè)當(dāng)前的線LineString地理長度為length掠手,當(dāng)前固定像素間隔stpes=n像素,在當(dāng)前地圖比例尺res已知的情況下狸捕,n像素地理距離是resn喷鸽,那么箭頭總數(shù)count=length/(resn):

let length=line_geom.getLength();//線圖形的地理長度
const steps=40;//每隔40像素打一個(gè)箭頭點(diǎn)
let geo_steps=map_res*steps;//40像素長度在當(dāng)前地圖比例尺下地理長度。
let arrow_count=length*1.0/geo_steps;

多么淺顯易懂的道理啊灸拍,第一個(gè)問題很順利的解決了做祝。

二 箭頭位置

??第一步得到了箭頭的總數(shù),在獲取箭頭位置時(shí)鸡岗,一個(gè)重要的API是線條LineString的getCoordinateAt混槐,利用它我們在軌跡線上獲取箭頭點(diǎn)的位置。

/*
fraction:參考點(diǎn)的百分比轩性,如0就是LineString的起點(diǎn)声登,1就是LineString的終點(diǎn),0.5就是LineString的中點(diǎn)。
*/
linestring.getCoordinateAt(fraction, opt_dest)

??假如箭頭總數(shù)為arrowsNum捌刮,那么arrowsNum個(gè)箭頭的數(shù)量分別是

 for(let i=1;i<arrowsNum;i++){
       let arraw_coor=geometry.getCoordinateAt(i*1.0/arrowsNum);
       console.log(arraw_coor);//輸出每個(gè)箭頭的坐標(biāo)
 }

??得到每個(gè)箭頭的位置后,我們先可視化下吧舒岸,OpenLayers的地圖樣式完全由StyleFunction實(shí)現(xiàn)的绅作,完整樣式代碼如下:

/*
feature:地圖上的要素對象,既有屬性蛾派,也有坐標(biāo)圖形俄认。
res:當(dāng)前地圖分辨率參數(shù)。
return:返回一個(gè)定制的渲染樣式
*/
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;//像素步長間隔
        //將像素步長轉(zhuǎn)實(shí)際地理距離步長
        let geo_steps=stpes*res;
        //箭頭總數(shù)
        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;
}
箭頭位置計(jì)算與可視化結(jié)果.png

三 箭頭方向

??之前的邏輯洪乍,我們已經(jīng)計(jì)算了一個(gè)軌跡樣式的雛形了眯杏,把地圖上箭頭位置的黃點(diǎn)改成一個(gè)箭頭圖標(biāo),做下方向旋轉(zhuǎn)就可以了壳澳。在說明此前岂贩,需要說明下軌跡,segment線段巷波,箭頭點(diǎn)之間的關(guān)系萎津,如下圖:


軌跡,segment,箭頭位置之間的關(guān)系.png

??觀察示意圖抹镊,總結(jié)如下:

  • 一條完整的軌跡由多個(gè)連續(xù)的segment組成锉屈。
  • 通過getCoordinateAt方法計(jì)算得到的箭頭點(diǎn),一定是在軌跡線上的某個(gè)點(diǎn)垮耳。
  • 每個(gè)箭頭點(diǎn)的方向是由箭頭點(diǎn)落在的segment的方向決定的颈渊。
    很顯然,計(jì)算箭頭方向其實(shí)就是計(jì)算每個(gè)箭頭點(diǎn)到底落在了哪個(gè)segment上终佛,將segme方向賦予箭頭點(diǎn)俊嗽。這里我們引入了rbush庫構(gòu)建空間索引,計(jì)算軌跡點(diǎn)與segment對應(yīng)關(guān)系铃彰。
    ??之所以我要引入rbush庫乌询,是解決循環(huán)計(jì)算問題,想象下如果不引入rbush庫豌研,只能使用如下的偽代碼暴力計(jì)算了:
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]對應(yīng)的segments是segments[j]
                      break;
              }
      }
}

感覺邏輯很簡單啊妹田,這樣做難道不可以嗎?想象下鹃共,箭頭數(shù)量鬼佣,segment的數(shù)量其實(shí)都是不可控的,一個(gè)復(fù)雜的軌跡線可能由成百上千的近萬的segments霜浴,這樣一個(gè)個(gè)循環(huán)去匹配晶衷,效率是不是就有問題了?所以引入了空間索引。這里查詢晌纫,使用了rbush進(jìn)行btree查詢税迷,查詢的結(jié)果后再詳細(xì)比對是否和箭頭相交,累了锹漱,直接貼代碼了箭养,不詳述了:

 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();//路段數(shù)
        trackLine.forEachSegment(function(start, end) {
            var dx = end[0] - start[0];
            var dy = end[1] - start[1];
            //計(jì)算每個(gè)segment的方向,即箭頭旋轉(zhuǎn)方向
            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;//像素步長間隔
        //將像素步長轉(zhuǎn)實(shí)際地理距離步長
        let geo_steps=stpes*res;
        //箭頭總數(shù)
        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;//查詢設(shè)置的點(diǎn)的容差哥牍,測試地圖單位是米毕泌。如果是4326坐標(biāo)系單位為度的話,改成0.0001.
            let arraw_coor_buffer=[arraw_coor[0]-tol,arraw_coor[1]-tol,arraw_coor[0]+tol,arraw_coor[1]+tol];
            //進(jìn)行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;
            //只查詢一個(gè)嗅辣,那么肯定是它了撼泛,直接返回
            if(treeSearch.length==1)
              arrow_rotation=treeSearch[0].rotation;
            else if(treeSearch.length>1){
                let results=treeSearch.filter(function(item){
                  //箭頭點(diǎn)與segment相交,返回結(jié)果澡谭。該方法實(shí)測不是很準(zhǔn)愿题,可能是計(jì)算中間結(jié)果
                  //保存到小數(shù)精度導(dǎo)致查詢有點(diǎn)問題
                  // if(item.geom.intersectsCoordinate(arraw_coor))
                  //   return true;

                  //換一種方案,設(shè)置一個(gè)稍小的容差蛙奖,消除精度問題
                  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

看著還湊合吧抠忘,但其實(shí)要做到高德那個(gè)精細(xì)的樣式,才萬里第一步外永,祝諸君繼續(xù)研究崎脉,期待更好的效果。

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末伯顶,一起剝皮案震驚了整個(gè)濱河市囚灼,隨后出現(xiàn)的幾起案子,更是在濱河造成了極大的恐慌祭衩,老刑警劉巖灶体,帶你破解...
    沈念sama閱讀 222,000評論 6 515
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件,死亡現(xiàn)場離奇詭異掐暮,居然都是意外死亡蝎抽,警方通過查閱死者的電腦和手機(jī),發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 94,745評論 3 399
  • 文/潘曉璐 我一進(jìn)店門路克,熙熙樓的掌柜王于貴愁眉苦臉地迎上來樟结,“玉大人,你說我怎么就攤上這事精算∑盎拢” “怎么了?”我有些...
    開封第一講書人閱讀 168,561評論 0 360
  • 文/不壞的土叔 我叫張陵灰羽,是天一觀的道長驮履。 經(jīng)常有香客問我鱼辙,道長,這世上最難降的妖魔是什么玫镐? 我笑而不...
    開封第一講書人閱讀 59,782評論 1 298
  • 正文 為了忘掉前任倒戏,我火速辦了婚禮,結(jié)果婚禮上恐似,老公的妹妹穿的比我還像新娘杜跷。我一直安慰自己,他們只是感情好蹂喻,可當(dāng)我...
    茶點(diǎn)故事閱讀 68,798評論 6 397
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著捂寿,像睡著了一般口四。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發(fā)上秦陋,一...
    開封第一講書人閱讀 52,394評論 1 310
  • 那天蔓彩,我揣著相機(jī)與錄音,去河邊找鬼驳概。 笑死赤嚼,一個(gè)胖子當(dāng)著我的面吹牛,可吹牛的內(nèi)容都是我干的顺又。 我是一名探鬼主播更卒,決...
    沈念sama閱讀 40,952評論 3 421
  • 文/蒼蘭香墨 我猛地睜開眼,長吁一口氣:“原來是場噩夢啊……” “哼稚照!你這毒婦竟也來了蹂空?” 一聲冷哼從身側(cè)響起,我...
    開封第一講書人閱讀 39,852評論 0 276
  • 序言:老撾萬榮一對情侶失蹤果录,失蹤者是張志新(化名)和其女友劉穎上枕,沒想到半個(gè)月后,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體弱恒,經(jīng)...
    沈念sama閱讀 46,409評論 1 318
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡辨萍,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 38,483評論 3 341
  • 正文 我和宋清朗相戀三年,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了返弹。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片锈玉。...
    茶點(diǎn)故事閱讀 40,615評論 1 352
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡,死狀恐怖义起,靈堂內(nèi)的尸體忽然破棺而出嘲玫,到底是詐尸還是另有隱情,我是刑警寧澤并扇,帶...
    沈念sama閱讀 36,303評論 5 350
  • 正文 年R本政府宣布去团,位于F島的核電站,受9級特大地震影響,放射性物質(zhì)發(fā)生泄漏土陪。R本人自食惡果不足惜昼汗,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 41,979評論 3 334
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望鬼雀。 院中可真熱鬧顷窒,春花似錦、人聲如沸源哩。這莊子的主人今日做“春日...
    開封第一講書人閱讀 32,470評論 0 24
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽励烦。三九已至谓着,卻和暖如春,著一層夾襖步出監(jiān)牢的瞬間坛掠,已是汗流浹背赊锚。 一陣腳步聲響...
    開封第一講書人閱讀 33,571評論 1 272
  • 我被黑心中介騙來泰國打工, 沒想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留屉栓,地道東北人舷蒲。 一個(gè)月前我還...
    沈念sama閱讀 49,041評論 3 377
  • 正文 我出身青樓,卻偏偏與公主長得像友多,于是被迫代替她去往敵國和親牲平。 傳聞我的和親對象是個(gè)殘疾皇子,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 45,630評論 2 359

推薦閱讀更多精彩內(nèi)容