近期由於業務的需求,讓我這從未想過要碰Web Gis的業餘前端開發者,走了Web Gis的開發道路。功能需求很簡單,但卻也是讓自己難為了好幾天。如,應該選擇那個Gis框架,Gis框架的相容性如何,直接Ie哪些版,能不能簡單到只有一張圖片就行解決問題,等等。。。。。。 在如此多的技術盲點,以及不確定的 ...
近期由於業務的需求,讓我這從未想過要碰Web Gis的業餘前端開發者,走了Web Gis的開發道路。功能需求很簡單,但卻也是讓自己難為了好幾天。如,應該選擇那個Gis框架,Gis框架的相容性如何,直接Ie哪些版,能不能簡單到只有一張圖片就行解決問題,等等。。。。。。
在如此多的技術盲點,以及不確定的因素,我開始了徵程,現將一些心得做些記錄。
一、需求分析
客戶需要的功能就是能在一張Gis圖上實現小車根據路徑進行移動,為什麼一定要Gis呢(這是客戶指定需求,無語一該)。並且客戶還說底圖要很容易更換,但他想要用Gis表現的卻是室內的地理信息,我也沒辦法用baidu, 高德等現成的Gis介面。
針對上述需求,我沒有去瞭解過多的web gis框架。因為客戶對Gis的概念就是能放大,縮小,可以做路徑規劃等。所以我就選擇ol,利用他的靜態圖片(選擇這個是為滿足客戶靈活更新底圖的需求)做Gis底圖的功能來解決此問題。
二、效果展示
三、偽代碼實現
由於是技術驗證代碼, 有些雜亂,現只給出關鍵性代碼。如有業務需要歡迎共同討論。
3.1 實現路徑的繪製
此步驟還是相對簡單的,主要用到Ol的Draw對象,代碼哪下:
draw(type){ this.stopdraw(); this._draw = new Draw({ source: this.layer.getSource(), type: type == 'Icon' ? 'Point' : type }); this._draw.on('drawend', (event)=>{ if(type == 'LineString'){ this.traceLine = event.feature; } if(type != 'Icon') return; let f = event.feature; f.setStyle(new Style({ image: new Icon({ src: '/content/battery.gif' }), text: new Text({ text: 'new item', fill: new Fill({ color: "red" }) }) })); f.type = 'battery'; }); this.map.addInteraction(this._draw); this._snap = new Snap({source: this.layer.getSource()}); this.map.addInteraction(this._snap); }
關鍵代碼在於drawend事件的監聽,如果是LineString情況,就將此feature放在一個共公變數,方便路徑運行時使用。
3.2 分解路徑數據
此部分就是獲取到3.1步驟的路徑路徑,然後進行解析,因為3.1上的linestring是多個線段的集合,但運動其本質就是改變圖標的坐標,使其快速且連續的變化就形成了移動效果。所以這裡有一個方法進行路徑細分,代碼如下:
cutTrace(){ let traceCroods = this.traceLine.getGeometry().getCoordinates(); let len = traceCroods.length; let destCroods = []; for(let i = 0; i < len - 1; ++i){ let bPoint = traceCroods[i]; let ePoint = traceCroods[i+1]; let bevelling = Math.sqrt(Math.pow(ePoint[0] - bPoint[0], 2) + Math.pow(ePoint[1] - bPoint[1], 2) ); let cosA = (ePoint[0] - bPoint[0]) / bevelling; let sinA = (ePoint[1] - bPoint[1]) / bevelling; let curStep = 0; let step = 5; destCroods.push(new Point([bPoint[0], bPoint[1]])); do{ curStep++; let nextPoint; if(curStep * step >= bevelling){ nextPoint = new Point([ePoint[0], ePoint[1]]); }else{ nextPoint = new Point([ cosA * curStep * step + bPoint[0] , sinA * curStep * step + bPoint[1] ]); } destCroods.push(nextPoint); }while(curStep * step < bevelling); } return destCroods; }
其中用到了一些數學上的三角函數和計算方法。此方法最終選一個根據步長計算後的坐標集合。
3.3 利用postcompose實現運動效果
代碼如下:
tracerun(){ if(!this.traceLine) return; this.traceCroods = this.cutTrace(); this.now = new Date().getTime(); this.map.on('postcompose', this.moveFeature.bind(this)); this.map.render(); } moveFeature(event){ let vCxt = event.vectorContext; let fState = event.frameState; let elapsedTime = fState.time - this.now; let index = Math.round(300 * elapsedTime / 1000); let len = this.traceCroods.length; if(index >= len){ //stop this.map.un('postcompose', this.moveFeature); return; } let dx, dy, rotation; if(this.traceCroods[index] && this.traceCroods[index + 1]){ let isRigth = false; let bCrood = this.traceCroods[index].getCoordinates(); let eCrood = this.traceCroods[index + 1].getCoordinates(); if(bCrood[0] < eCrood[0]){ //左->右 isRigth = true } dx = bCrood[0] - eCrood[0]; dy = bCrood[1] - eCrood[1]; rotation = Math.atan2(dy,dx); if(rotation > (Math.PI / 2)){ //修正 rotation = Math.PI - rotation; }else if(rotation < -1 * (Math.PI / 2)){ rotation = -1 * Math.PI - rotation; }else{ rotation = -rotation; } console.log(dx + ' ' + dy + ' ' + rotation); let curPoint = this.traceCroods[index]; var anchor = new Feature(curPoint); let style = new Style({ image: new Icon({ img: isRigth ? this.carRight : this.carImg, imgSize: [32,32], rotateWithView: false, rotation: rotation }), text: new Text({ text: 'Car', fill: new Fill({ color: 'red' }), offsetY: -20 }) }); vCxt.drawFeature(anchor, style); //this.map.getView().setCenter(bCrood); } this.map.render(); }
此移動代碼的是用ol的postcompose事件進行實現的,因為render方法執行完成後會觸發postcompose事件,所以就代替了定時器的的實現方案。其中rotation根據兩點坐標計算出移動圖標的斜度、以及移動的方向等,更為影響的展示。