openlayers 轨迹回放(历史轨迹)(postrender事件和render方法)
本篇介绍一下使用openlayers轨迹回放(历史轨迹)(postrender事件和render方法)
1 需求
- 轨迹回放(历史轨迹)
2 分析
上篇是使用定时器实现的,本篇使用postrender事件和render方法
- 轨迹回放(历史轨迹),一般是一次性拿到所有坐标点,使用postrender事件和render方法
Map的render方法:当在下一个动画帧时渲染地图
Layer的postrender事件:图层被渲染后触发
3 实现
<template>
<div id="map" class="map"></div>
<div class="toolbar">
<el-button type="primary" @click="handleClick">{{ animationFlag ? '停止' : '开始' }}</el-button>
<el-input v-model.number="speed"></el-input><span>速度</span>
</div>
</template>
<script setup lang="ts">
import { Feature, Map, View } from 'ol';
import { LineString, Point } from 'ol/geom';
import { Tile as TileLayer, Vector as VectorLayer } from 'ol/layer';
import { get } from 'ol/proj';
import { Vector as VectorSource, XYZ } from 'ol/source';
import { Circle, Fill, Icon, Stroke, Style } from 'ol/style';
import iconSrc from '@/assets/image/truck.png';
const projection = get('EPSG:4326');
const layerTypeMap = {
vector: ['vec', 'cva'], // [矢量底图, 矢量注记]
image: ['img', 'cia'], // [影像底图, 影像注记]
terrain: ['ter', 'cta'] // [地形晕渲, 地形注记]
};
const map = shallowRef();
const vectorLayer = shallowRef();
const source = shallowRef<VectorSource>(new VectorSource());
const trace = ref();
const animationFlag = ref(false);
const speed = ref(50);
const lastTime = ref(Date.now());
const distance = ref(0);
const iconFeature = shallowRef();
const traceFeature = shallowRef();
const route = shallowRef();
const angle = ref(0);
onMounted(() => {
initMap('image');
initTraceAndFeature();
});
const initMap = (layerType = 'image') => {
const key = '替换为天地图key';
vectorLayer.value = new VectorLayer({
source: source.value,
style: new Style({
stroke: new Stroke({
color: 'rgba(228, 147, 87, 1)',
width: 3
})
})
});
// c: 经纬度投影 w: 墨卡托投影
const matrixSet = 'c';
map.value = new Map({
target: 'map',
layers: [
// 底图
new TileLayer({
source: new XYZ({
url: `https://t{0-7}.tianditu.gov.cn/DataServer?T=${layerTypeMap[layerType][0]}_${matrixSet}&tk=${key}&x={x}&y={y}&l={z}`,
projection
})
}),
// 注记
new TileLayer({
source: new XYZ({
url: `https://t{0-7}.tianditu.gov.cn/DataServer?T=${layerTypeMap[layerType][1]}_${matrixSet}&tk=${key}&x={x}&y={y}&l={z}`,
projection
})
}),
vectorLayer.value
],
view: new View({
center: [116.406393, 39.909006],
projection: projection,
zoom: 5,
maxZoom: 17,
minZoom: 1
})
});
};
const initTraceAndFeature = () => {
trace.value = [
[110, 30],
[110.2, 30],
[110.4, 30.2],
[110.8, 30.4],
[111, 31],
[111.3, 31],
[111.6, 31],
[111.9, 31],
[112, 31],
[112.3, 31],
[112.5, 31],
[112.8, 31],
[113, 31],
[114, 31],
[115.3, 32],
[115.5, 32],
[115.8, 31.8],
[116, 31.4],
[116.2, 31.1],
[116.5, 30.5],
[115, 30.2],
[114, 29.8],
[113, 29.6],
[112, 29.4],
[111, 30.2],
[110, 30.4],
[109, 30.6],
[108, 31]
];
angle.value = getAngle(trace.value[0], trace.value[1]);
iconFeature.value = new Feature({
geometry: new Point(trace.value[0])
});
const icon = new Icon({
crossOrigin: 'anonymous',
src: iconSrc,
color: 'red',
opacity: 1,
width: 30,
height: 30,
rotation: angle.value
});
iconFeature.value.setStyle(
new Style({
image: icon
})
);
traceFeature.value = new Feature({
geometry: new LineString(trace.value)
});
source.value?.addFeatures([iconFeature.value, traceFeature.value]);
route.value = new LineString(trace.value);
};
const handleClick = () => {
if (!animationFlag.value) {
startAnimation();
} else {
stopAnimation();
}
animationFlag.value = !animationFlag.value;
};
const startAnimation = () => {
lastTime.value = Date.now();
vectorLayer.value.on('postrender', move);
// 触发地图渲染
const geo = iconFeature.value.getGeometry().clone();
iconFeature.value.setGeometry(geo);
};
const move = e => {
const time = e.frameState.time;
// 时间戳差(毫秒)
const elapsedTime = time - lastTime.value;
// 距离(其实是比例的概念)
distance.value = distance.value + (speed.value * elapsedTime) / 1e6;
if (distance.value >= 1) {
distance.value = 0;
iconFeature.value.getGeometry().setCoordinates(trace.value[0]);
animationFlag.value = false;
iconFeature.value.getStyle().getImage().setRotation(getAngle(trace.value[0], trace.value[1]));
stopAnimation();
return;
}
// 保存当前时间
lastTime.value = time;
// 上次坐标
const lastCoord = iconFeature.value.getGeometry().getCoordinates();
// 获取新位置的坐标点
const curCoord = route.value.getCoordinateAt(distance.value);
// 设置新坐标
iconFeature.value.getGeometry().setCoordinates(curCoord);
// 设置角度
iconFeature.value.getStyle().getImage().setRotation(getAngle(lastCoord, curCoord));
// 调用地图渲染
map.value.render();
};
const stopAnimation = () => {
vectorLayer.value.un('postrender', move);
};
const getAngle = (point1, point2) => {
let arc = 0;
if (point2 && point2.length && point1 && point1.length) {
if (
(point2[0] - point1[0] >= 0 && point2[1] - point1[1] >= 0) ||
(point2[0] - point1[0] < 0 && point2[1] - point1[1] > 0)
) {
arc = Math.atan((point2[0] - point1[0]) / (point2[1] - point1[1]));
} else if (
(point2[0] - point1[0] > 0 && point2[1] - point1[1] < 0) ||
(point2[0] - point1[0] < 0 && point2[1] - point1[1] < 0)
) {
arc = Math.PI + Math.atan((point2[0] - point1[0]) / (point2[1] - point1[1]));
}
}
return arc;
};
</script>
<style scoped lang="scss">
.map {
width: 100%;
height: 100%;
}
.toolbar {
position: absolute;
top: 20px;
left: 100px;
display: flex;
justify-content: center;
align-items: center;
.el-input {
width: 100px;
margin: 0 20px;
}
}
</style>
4 总结
- 动画比使用定时器丝滑