vue利用openlayers實(shí)現(xiàn)動(dòng)態(tài)軌跡
實(shí)現(xiàn)效果
今天介紹一個(gè)有趣的gis小功能:動(dòng)態(tài)軌跡播放!效果就像這樣:
這效果看著還很絲滑!別急,接下來教你怎么實(shí)現(xiàn)。代碼示例基于parcel打包工具和es6語法,本文假設(shè)你已經(jīng)掌握相關(guān)知識(shí)和技巧。
gis初學(xué)者可能對(duì)openlayers(后面簡稱ol)不熟悉,這里暫時(shí)不介紹ol了,直接上代碼,先體驗(yàn)下感覺。
創(chuàng)建一個(gè)地圖容器
引入地圖相關(guān)對(duì)象
import Map from 'ol/Map'; import View from 'ol/View'; import XYZ from 'ol/source/XYZ'; import {Tile as TileLayer, Vector as VectorLayer} from 'ol/layer';
創(chuàng)建地圖對(duì)象
const center = [-5639523.95, -3501274.52]; const map = new Map({ target: document.getElementById('map'), view: new View({ center: center, zoom: 10, minZoom: 2, maxZoom: 19, }), layers: [ new TileLayer({ source: new XYZ({ attributions: attributions, url: 'https://api.maptiler.com/maps/hybrid/{z}/{x}/{y}.jpg?key=' + key, tileSize: 512, }), }), ], });
創(chuàng)建一條線路
畫一條線路
可以用這個(gè)geojson網(wǎng)站隨意畫一條線,然后把數(shù)據(jù)內(nèi)容復(fù)制下來,保存為json文件格式,作為圖層數(shù)據(jù)添加到地圖容器中。
你可以用異步加載的方式,也可以用require方式,這里都介紹下吧:
// fetch fetch('data/route.json').then(function (response) { response.json().then(function (result) { const polyline = result.routes[0].geometry; }), }; // require var roadData = require('data/route.json')
后面基本一樣了,就以fetch為準(zhǔn),現(xiàn)在把線路加載的剩余部分補(bǔ)充完整:
fetch('data/route.json').then(function (response) { response.json().then(function (result) { const polyline = result.routes[0].geometry; // 線路數(shù)據(jù)坐標(biāo)系轉(zhuǎn)換 const route = new Polyline({ factor: 1e6, }).readGeometry(polyline, { dataProjection: 'EPSG:4326', featureProjection: 'EPSG:3857', }); // 線路圖層要素 const routeFeature = new Feature({ type: 'route', geometry: route, }); // 起點(diǎn)要素 const startMarker = new Feature({ type: 'icon', geometry: new Point(route.getFirstCoordinate()), }); // 終點(diǎn)要素 const endMarker = new Feature({ type: 'icon', geometry: new Point(route.getLastCoordinate()), }); // 取起點(diǎn)值 const position = startMarker.getGeometry().clone(); // 游標(biāo)要素 const geoMarker = new Feature({ type: 'geoMarker', geometry: position, }); // 樣式組合 const styles = { // 路線 'route': new Style({ stroke: new Stroke({ width: 6, color: [237, 212, 0, 0.8], }), }), 'icon': new Style({ image: new Icon({ anchor: [0.5, 1], src: 'data/icon.png', }), }), 'geoMarker': new Style({ image: new CircleStyle({ radius: 7, fill: new Fill({color: 'black'}), stroke: new Stroke({ color: 'white', width: 2, }), }), }), }; // 創(chuàng)建圖層并添加以上要素集合 const vectorLayer = new VectorLayer({ source: new VectorSource({ features: [routeFeature, geoMarker, startMarker, endMarker], }), style: function (feature) { return styles[feature.get('type')]; }, }); // 在地圖容器中添加圖層 map.addLayer(vectorLayer);
以上代碼很完整,我加了注釋,整體思路總結(jié)如下:
- 先加載路線數(shù)據(jù)
- 構(gòu)造路線、起始點(diǎn)及游標(biāo)對(duì)應(yīng)圖層要素對(duì)象
- 構(gòu)造圖層并把要素添加進(jìn)去
- 在地圖容器中添加圖層
添加起、終點(diǎn)
這個(gè)上面的代碼已經(jīng)包括了,我這里列出來是為了讓你更清晰,就是startMarker
和endMarker
對(duì)應(yīng)的代碼。
添加小車
同樣的,這里的代碼在上面也寫過了,就是geoMarker
所對(duì)應(yīng)的代碼。
準(zhǔn)備開車
線路有了,車也有了,現(xiàn)在就到了激動(dòng)人心的開車時(shí)刻了,接下來才是本文最核心的代碼!
const speedInput = document.getElementById('speed'); const startButton = document.getElementById('start-animation'); let animating = false; let distance = 0; let lastTime; function moveFeature(event) { const speed = Number(speedInput.value); // 獲取當(dāng)前渲染幀狀態(tài)時(shí)刻 const time = event.frameState.time; // 渲染時(shí)刻減去開始播放軌跡的時(shí)間 const elapsedTime = time - lastTime; // 求得距離比 distance = (distance + (speed * elapsedTime) / 1e6) % 2; // 刷新上一時(shí)刻 lastTime = time; // 反減可實(shí)現(xiàn)反向運(yùn)動(dòng),獲取坐標(biāo)點(diǎn) const currentCoordinate = route.getCoordinateAt( distance > 1 ? 2 - distance : distance ); position.setCoordinates(currentCoordinate); // 獲取渲染圖層的畫布 const vectorContext = getVectorContext(event); vectorContext.setStyle(styles.geoMarker); vectorContext.drawGeometry(position); map.render(); } function startAnimation() { animating = true; lastTime = Date.now(); startButton.textContent = 'Stop Animation'; vectorLayer.on('postrender', moveFeature); // 隱藏小車前一刻位置同時(shí)觸發(fā)事件 geoMarker.setGeometry(null); } function stopAnimation() { animating = false; startButton.textContent = '開車了'; // 將小車固定在當(dāng)前位置 geoMarker.setGeometry(position); vectorLayer.un('postrender', moveFeature); } startButton.addEventListener('click', function () { if (animating) { stopAnimation(); } else { startAnimation(); } });
簡單說下它的原理就是利用postrender
事件觸發(fā)一個(gè)函數(shù),這個(gè)事件本來是地圖渲染結(jié)束事件,但是它的回調(diào)函數(shù)中,小車的坐標(biāo)位置一直在變,那就會(huì)不停地觸發(fā)地圖渲染,當(dāng)然最終也會(huì)觸發(fā)postrender
。這樣就實(shí)現(xiàn)的小車沿著軌跡的動(dòng)畫效果了。這段代碼有點(diǎn)難理解,最好自己嘗試體驗(yàn)下,比較難理解部分我都加上了注釋。
好了,ol動(dòng)態(tài)巡查已經(jīng)介紹完了,動(dòng)手試下吧!看你的車能否開起來?
完整代碼
index.html
<!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8"> <title>Marker Animation</title> <!-- Pointer events polyfill for old browsers, see https://caniuse.com/#feat=pointer --> <script src="https://unpkg.com/elm-pep"></script> <style> .map { width: 100%; height:400px; } </style> </head> <body> <div id="map" class="map"></div> <label for="speed"> speed: <input id="speed" type="range" min="10" max="999" step="10" value="60"> </label> <button id="start-animation">Start Animation</button> <script src="main.js"></script> </body> </html>
main.js
import 'ol/ol.css'; import Feature from 'ol/Feature'; import Map from 'ol/Map'; import Point from 'ol/geom/Point'; import Polyline from 'ol/format/Polyline'; import VectorSource from 'ol/source/Vector'; import View from 'ol/View'; import XYZ from 'ol/source/XYZ'; import { Circle as CircleStyle, Fill, Icon, Stroke, Style, } from 'ol/style'; import {Tile as TileLayer, Vector as VectorLayer} from 'ol/layer'; import {getVectorContext} from 'ol/render'; const key = 'Get your own API key at https://www.maptiler.com/cloud/'; const attributions = '<a rel="external nofollow" target="_blank">© MapTiler</a> ' + '<a rel="external nofollow" target="_blank">© OpenStreetMap contributors</a>'; const center = [-5639523.95, -3501274.52]; const map = new Map({ target: document.getElementById('map'), view: new View({ center: center, zoom: 10, minZoom: 2, maxZoom: 19, }), layers: [ new TileLayer({ source: new XYZ({ attributions: attributions, url: 'https://api.maptiler.com/maps/hybrid/{z}/{x}/{y}.jpg?key=' + key, tileSize: 512, }), }), ], }); // The polyline string is read from a JSON similiar to those returned // by directions APIs such as Openrouteservice and Mapbox. fetch('data/polyline/route.json').then(function (response) { response.json().then(function (result) { const polyline = result.routes[0].geometry; const route = new Polyline({ factor: 1e6, }).readGeometry(polyline, { dataProjection: 'EPSG:4326', featureProjection: 'EPSG:3857', }); const routeFeature = new Feature({ type: 'route', geometry: route, }); const startMarker = new Feature({ type: 'icon', geometry: new Point(route.getFirstCoordinate()), }); const endMarker = new Feature({ type: 'icon', geometry: new Point(route.getLastCoordinate()), }); const position = startMarker.getGeometry().clone(); const geoMarker = new Feature({ type: 'geoMarker', geometry: position, }); const styles = { 'route': new Style({ stroke: new Stroke({ width: 6, color: [237, 212, 0, 0.8], }), }), 'icon': new Style({ image: new Icon({ anchor: [0.5, 1], src: 'data/icon.png', }), }), 'geoMarker': new Style({ image: new CircleStyle({ radius: 7, fill: new Fill({color: 'black'}), stroke: new Stroke({ color: 'white', width: 2, }), }), }), }; const vectorLayer = new VectorLayer({ source: new VectorSource({ features: [routeFeature, geoMarker, startMarker, endMarker], }), style: function (feature) { return styles[feature.get('type')]; }, }); map.addLayer(vectorLayer); const speedInput = document.getElementById('speed'); const startButton = document.getElementById('start-animation'); let animating = false; let distance = 0; let lastTime; function moveFeature(event) { const speed = Number(speedInput.value); const time = event.frameState.time; const elapsedTime = time - lastTime; distance = (distance + (speed * elapsedTime) / 1e6) % 2; lastTime = time; const currentCoordinate = route.getCoordinateAt( distance > 1 ? 2 - distance : distance ); position.setCoordinates(currentCoordinate); const vectorContext = getVectorContext(event); vectorContext.setStyle(styles.geoMarker); vectorContext.drawGeometry(position); // tell OpenLayers to continue the postrender animation map.render(); } function startAnimation() { animating = true; lastTime = Date.now(); startButton.textContent = 'Stop Animation'; vectorLayer.on('postrender', moveFeature); geoMarker.setGeometry(null); } function stopAnimation() { animating = false; startButton.textContent = '開車了'; geoMarker.setGeometry(position); vectorLayer.un('postrender', moveFeature); } startButton.addEventListener('click', function () { if (animating) { stopAnimation(); } else { startAnimation(); } }); }); });
package.json
{ "name": "feature-move-animation", "dependencies": { "ol": "6.9.0" }, "devDependencies": { "parcel": "^2.0.0-beta.1" }, "scripts": { "start": "parcel index.html", "build": "parcel build --public-url . index.html" } }
參考資源:
https://openlayers.org/en/latest/examples/feature-move-animation.html
以上就是vue利用openlayers實(shí)現(xiàn)動(dòng)態(tài)軌跡的詳細(xì)內(nèi)容,更多關(guān)于vue openlayers動(dòng)態(tài)軌跡的資料請(qǐng)關(guān)注腳本之家其它相關(guān)文章!
- 在Vue?3中使用OpenLayers加載GPX數(shù)據(jù)并顯示圖形效果
- vue+openlayers+nodejs+postgis實(shí)現(xiàn)軌跡運(yùn)動(dòng)效果
- Vue使用openlayers加載天地圖
- Vue+OpenLayers?創(chuàng)建地圖并顯示鼠標(biāo)所在經(jīng)緯度(完整代碼)
- vue?openlayers實(shí)現(xiàn)臺(tái)風(fēng)軌跡示例詳解
- Vue結(jié)合openlayers按照經(jīng)緯度坐標(biāo)實(shí)現(xiàn)錨地標(biāo)記及繪制多邊形區(qū)域
- Vue openLayers實(shí)現(xiàn)圖層數(shù)據(jù)切換與加載流程詳解
- Vue利用openlayers實(shí)現(xiàn)點(diǎn)擊彈窗的方法詳解
- Vue使用openlayers實(shí)現(xiàn)繪制圓形和多邊形
- 在Vue 3中使用OpenLayers讀取WKB數(shù)據(jù)并顯示圖形效果
相關(guān)文章
Vue中使用video.js實(shí)現(xiàn)截圖和視頻錄制與下載
這篇文章主要為大家詳細(xì)介紹了Vue中如何使用video.js實(shí)現(xiàn)截圖和視頻錄制與下載,文中的示例代碼講解詳細(xì),感興趣的小伙伴可以跟隨小編一起學(xué)習(xí)一下2024-03-03VuePress 靜態(tài)網(wǎng)站生成方法步驟
這篇文章主要介紹了VuePress 靜態(tài)網(wǎng)站生成方法步驟,小編覺得挺不錯(cuò)的,現(xiàn)在分享給大家,也給大家做個(gè)參考。一起跟隨小編過來看看吧2019-02-02如何使用yarn創(chuàng)建vite項(xiàng)目+vue3
這篇文章主要介紹了如何使用yarn創(chuàng)建vite項(xiàng)目+vue3,詳細(xì)介紹了使用vite創(chuàng)建vue3過程,本文給大家介紹的非常詳細(xì),感興趣的朋友跟隨小編一起看看吧2024-03-03vite+vue3+ts項(xiàng)目新建以及解決遇到的問題
vite是一個(gè)基于Vue3單文件組件的非打包開發(fā)服務(wù)器,它具有快速的冷啟動(dòng),不需要等待打包操作,下面這篇文章主要給大家介紹了關(guān)于vite+vue3+ts項(xiàng)目新建以及解決遇到的問題的相關(guān)資料,需要的朋友可以參考下2023-06-06vue同一個(gè)瀏覽器登錄不同賬號(hào)數(shù)據(jù)覆蓋問題解決方案
同一個(gè)瀏覽器登錄不同賬號(hào)session一致,這就導(dǎo)致后面登錄的用戶數(shù)據(jù)會(huì)把前面登錄的用戶數(shù)據(jù)覆蓋掉,這個(gè)問題很常見,當(dāng)前我這邊解決的就是同一個(gè)瀏覽器不同窗口只能登錄一個(gè)用戶,對(duì)vue同一個(gè)瀏覽器登錄不同賬號(hào)數(shù)據(jù)覆蓋問題解決方法感興趣的朋友一起看看吧2024-01-01解決父組件將子組件作為彈窗調(diào)用只執(zhí)行一次created的問題
這篇文章主要介紹了解決父組件將子組件作為彈窗調(diào)用只執(zhí)行一次created的問題,具有很好的參考價(jià)值,希望對(duì)大家有所幫助。一起跟隨小編過來看看吧2020-07-07