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(后面簡(jiǎn)稱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();
}
});
簡(jiǎn)單說下它的原理就是利用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-cli使用stimulsoft.reports.js的詳細(xì)教程
Stimulsoft?Reports.JS是一個(gè)使用JavaScript和HTML5生成報(bào)表的平臺(tái)。它擁有所有擁來設(shè)計(jì),編輯和查看報(bào)表的必需組件。該報(bào)表工具根據(jù)開發(fā)人員數(shù)量授權(quán)而不是根據(jù)應(yīng)用程序的用戶數(shù)量。接下來通過本文給大家介紹vue-cli使用stimulsoft.reports.js的方法,一起看看吧2021-12-12
vue.js中使用echarts實(shí)現(xiàn)數(shù)據(jù)動(dòng)態(tài)刷新功能
這篇文章主要介紹了vue.js中使用echarts實(shí)現(xiàn)數(shù)據(jù)動(dòng)態(tài)刷新功能,需要的朋友可以參考下2019-04-04
vue3使用particles插件實(shí)現(xiàn)粒子背景的方法詳解
這篇文章主要為大家詳細(xì)介紹了vue3使用particles插件實(shí)現(xiàn)粒子背景的方法,文中示例代碼介紹的非常詳細(xì),具有一定的參考價(jià)值,感興趣的小伙伴們可以參考一下,希望能夠給你帶來幫助2022-03-03
vue中el-tab如何點(diǎn)擊不同標(biāo)簽觸發(fā)不同函數(shù)的實(shí)現(xiàn)
el-tab本身的功能是點(diǎn)擊之后切換不同頁,本文主要介紹了vue中el-tab如何點(diǎn)擊不同標(biāo)簽觸發(fā)不同函數(shù)的實(shí)現(xiàn),具有一定的參考價(jià)值,感興趣的可以了解一下2024-03-03

