|
@@ -35,7 +35,7 @@
|
|
|
<div class="prevIcon" @click="toPrevTime" />
|
|
|
<div class="playIcon" @click="toPlayTime" />
|
|
|
<div class="nextIcon" @click="toNextTime(true)" />
|
|
|
- <div class="speed-btn" @click="changeSpeed">{{ timeAxisState.speed === 1 ? 'x1' : 'x2' }}</div>
|
|
|
+ <div class="speed-btn" @click="changeSpeed">{{ timeAxisState.speed === 2 ? 'x1' : 'x2' }}</div>
|
|
|
<div class="rankIcon" />
|
|
|
</div>
|
|
|
</div>
|
|
@@ -45,6 +45,15 @@
|
|
|
<script lang="ts" setup name="TimeAxis">
|
|
|
import { parseTime } from '@/utils/ruoyi';
|
|
|
import carImg from '@/assets/images/car.png';
|
|
|
+import VectorSource from 'ol/source/Vector';
|
|
|
+import Feature from 'ol/Feature';
|
|
|
+import Point from 'ol/geom/Point';
|
|
|
+import Icon from 'ol/style/Icon';
|
|
|
+import Style from 'ol/style/Style';
|
|
|
+import { LineString } from 'ol/geom';
|
|
|
+import VectorLayer from 'ol/layer/Vector';
|
|
|
+import { Stroke } from 'ol/style';
|
|
|
+import arrowImg from '@/assets/images/arrow.png';
|
|
|
|
|
|
const props = defineProps({
|
|
|
activeMap: String
|
|
@@ -69,7 +78,7 @@ const timeAxisState = reactive({
|
|
|
playing: false,
|
|
|
show: false,
|
|
|
showDialog: false,
|
|
|
- speed: 1,
|
|
|
+ speed: 2,
|
|
|
data: []
|
|
|
});
|
|
|
let originalData = [];
|
|
@@ -129,34 +138,230 @@ const initDataToPlay = (obj) => {
|
|
|
if (timeAxisState.type === 'track') {
|
|
|
mapUtils = getMapUtils();
|
|
|
map = mapUtils?.getMap();
|
|
|
- AMap = mapUtils?.getAMap();
|
|
|
if (AMapType.includes(props.activeMap)) {
|
|
|
- if (moveMarker) {
|
|
|
- // 不停止remove还是有
|
|
|
- moveMarker.stopMove();
|
|
|
- moveMarker.remove();
|
|
|
- }
|
|
|
- movePolyline?.remove();
|
|
|
- movePassedPolyline?.remove();
|
|
|
- // 绘制轨迹
|
|
|
- movePolyline = new AMap.Polyline({
|
|
|
- map: map,
|
|
|
- path: trackPathArr,
|
|
|
- showDir: true,
|
|
|
- strokeColor: '#28F', //线颜色
|
|
|
- // strokeOpacity: 1, //线透明度
|
|
|
- strokeWeight: 6 //线宽
|
|
|
- // strokeStyle: "solid" //线样式
|
|
|
- });
|
|
|
-
|
|
|
- movePassedPolyline = new AMap.Polyline({
|
|
|
- map: map,
|
|
|
- strokeColor: '#AF5', //线颜色
|
|
|
- strokeWeight: 6 //线宽
|
|
|
- });
|
|
|
- startPlay();
|
|
|
+ AMap = mapUtils?.getAMap();
|
|
|
+ trackPlayback1();
|
|
|
+ } else {
|
|
|
+ trackPlayback2();
|
|
|
+ }
|
|
|
+ }
|
|
|
+};
|
|
|
+// 高德地图轨迹
|
|
|
+const trackPlayback1 = () => {
|
|
|
+ clearObj();
|
|
|
+ // 绘制轨迹
|
|
|
+ movePolyline = new AMap.Polyline({
|
|
|
+ map: map,
|
|
|
+ path: trackPathArr,
|
|
|
+ showDir: true,
|
|
|
+ strokeColor: '#28F', //线颜色
|
|
|
+ // strokeOpacity: 1, //线透明度
|
|
|
+ strokeWeight: 6, //线宽
|
|
|
+ // strokeStyle: "solid" //线样式
|
|
|
+ });
|
|
|
+
|
|
|
+ movePassedPolyline = new AMap.Polyline({
|
|
|
+ map: map,
|
|
|
+ strokeColor: '#AF5', //线颜色
|
|
|
+ strokeWeight: 6 //线宽
|
|
|
+ });
|
|
|
+
|
|
|
+ startPlay();
|
|
|
+};
|
|
|
+
|
|
|
+let carLayer, carFeature, traceFeature, traceFeature2;
|
|
|
+let lastTime;
|
|
|
+let distance = 0;
|
|
|
+let animationFlag = false;
|
|
|
+let route,
|
|
|
+ lastRoute = [];
|
|
|
+let first = 0;
|
|
|
+
|
|
|
+const trackPlayback2 = () => {
|
|
|
+ if (!!carLayer) {
|
|
|
+ clearObj();
|
|
|
+ } else {
|
|
|
+ const source = new VectorSource();
|
|
|
+ carLayer = new VectorLayer({
|
|
|
+ source: source
|
|
|
+ });
|
|
|
+ map.addLayer(carLayer);
|
|
|
+ }
|
|
|
+
|
|
|
+ const angle = getAngle(trackPathArr[0], trackPathArr[1]);
|
|
|
+ lastTime = Date.now();
|
|
|
+ carFeature = new Feature({
|
|
|
+ geometry: new Point(trackPathArr[0]),
|
|
|
+ zIndex: 999
|
|
|
+ });
|
|
|
+ const icon = new Icon({
|
|
|
+ crossOrigin: 'anonymous',
|
|
|
+ src: carImg,
|
|
|
+ width: 13,
|
|
|
+ height: 26,
|
|
|
+ anchor: [0.5, 0.5],
|
|
|
+ rotation: angle
|
|
|
+ });
|
|
|
+ carFeature.setStyle(
|
|
|
+ new Style({
|
|
|
+ image: icon
|
|
|
+ })
|
|
|
+ );
|
|
|
+ route = new Feature({
|
|
|
+ geometry: new LineString(trackPathArr[0])
|
|
|
+ });
|
|
|
+ traceFeature = new Feature({
|
|
|
+ geometry: new LineString(trackPathArr),
|
|
|
+ zIndex: 8
|
|
|
+ });
|
|
|
+ traceFeature.setStyle(arrowStyleFunction);
|
|
|
+ traceFeature2 = new Feature({
|
|
|
+ geometry: new LineString(trackPathArr[0]),
|
|
|
+ zIndex: 9
|
|
|
+ });
|
|
|
+ lastRoute = [trackPathArr[0]];
|
|
|
+ traceFeature2.setStyle(
|
|
|
+ new Style({
|
|
|
+ stroke: new Stroke({
|
|
|
+ color: '#AF5',
|
|
|
+ width: 6
|
|
|
+ })
|
|
|
+ })
|
|
|
+ );
|
|
|
+ carLayer.getSource().addFeatures([carFeature, traceFeature, traceFeature2]);
|
|
|
+ carLayer.on('postrender', move);
|
|
|
+ timeAxisState.activeIndex = 0;
|
|
|
+ timeAxisState.playing = true;
|
|
|
+ // 触发地图渲染
|
|
|
+ const geo = carFeature.getGeometry().clone();
|
|
|
+ carFeature.setGeometry(geo);
|
|
|
+};
|
|
|
+const move = (e) => {
|
|
|
+ if (!timeAxisState.playing) return;
|
|
|
+ const time = e.frameState.time;
|
|
|
+ // 时间戳差(毫秒)
|
|
|
+ const elapsedTime = time - lastTime;
|
|
|
+ // 距离(其实是比例的概念)
|
|
|
+ distance = distance + ((timeAxisState.speed === 2 ? 1 : 2) * 200 * elapsedTime) / 1e6;
|
|
|
+ if (distance >= 1) {
|
|
|
+ if (timeAxisState.activeIndex < trackPathArr.length - 1) {
|
|
|
+ // 一段走完后
|
|
|
+ lastRoute.push(traceFeature2.getGeometry().getCoordinates());
|
|
|
+ console.log(lastRoute);
|
|
|
+ timeAxisState.activeIndex += 1;
|
|
|
+ distance = 0;
|
|
|
+ } else if (first === 0) {
|
|
|
+ // 所有走完后有时会0.9->1.2导致路没走完
|
|
|
+ first = 1;
|
|
|
+ distance = 1;
|
|
|
+ } else {
|
|
|
+ first = 0;
|
|
|
+ distance = 0;
|
|
|
+ animationFlag = false;
|
|
|
+ stopAnimation();
|
|
|
+ return;
|
|
|
}
|
|
|
}
|
|
|
+ const arr = trackPathArr.slice(timeAxisState.activeIndex, timeAxisState.activeIndex + 2);
|
|
|
+ route = new Feature({
|
|
|
+ geometry: new LineString(arr)
|
|
|
+ });
|
|
|
+ // 保存当前时间
|
|
|
+ lastTime = time;
|
|
|
+ // 上次坐标
|
|
|
+ const lastCoord = carFeature.getGeometry().getCoordinates();
|
|
|
+ // 获取新位置的坐标点
|
|
|
+ let curCoord = route.getGeometry().getCoordinateAt(distance);
|
|
|
+ if (isNaNCoord(curCoord)) {
|
|
|
+ curCoord = trackPathArr[0];
|
|
|
+ }
|
|
|
+ // 设置新坐标
|
|
|
+ carFeature.getGeometry().setCoordinates(curCoord);
|
|
|
+ // 获取当前轨迹坐标数组
|
|
|
+ const currentCoords = traceFeature2.getGeometry().getCoordinates();
|
|
|
+ currentCoords.push(curCoord);
|
|
|
+ traceFeature2.getGeometry().setCoordinates(currentCoords);
|
|
|
+ map.getView().setCenter(curCoord);
|
|
|
+ // 设置角度
|
|
|
+ carFeature.getStyle().getImage().setRotation(getAngle(lastCoord, curCoord));
|
|
|
+ // 调用地图渲染
|
|
|
+ map.render();
|
|
|
+};
|
|
|
+const isNaNCoord = (coord) => {
|
|
|
+ return JSON.stringify(coord) === '[null,null]'; // NaN 在 JSON.stringify 中会被转换为 null
|
|
|
+};
|
|
|
+const stopAnimation = () => {
|
|
|
+ if (carLayer) {
|
|
|
+ carLayer.un('postrender', move);
|
|
|
+ }
|
|
|
+};
|
|
|
+const getAngle = (point1, point2) => {
|
|
|
+ // 处理无效输入或非数组情况
|
|
|
+ if (!point1 || !point2 || !point1.length || !point2.length) {
|
|
|
+ return 0;
|
|
|
+ }
|
|
|
+
|
|
|
+ const dx = point2[0] - point1[0];
|
|
|
+ const dy = point2[1] - point1[1];
|
|
|
+
|
|
|
+ // 处理两点相同的情况
|
|
|
+ if (dx === 0 && dy === 0) {
|
|
|
+ return 0;
|
|
|
+ }
|
|
|
+
|
|
|
+ // 使用 Math.atan2 计算角度,并确保结果在 0 到 2π 之间
|
|
|
+ let angle = Math.atan2(dx, dy);
|
|
|
+ if (angle < 0) {
|
|
|
+ angle += 2 * Math.PI;
|
|
|
+ }
|
|
|
+
|
|
|
+ return angle;
|
|
|
+};
|
|
|
+// 箭头样式生成函数
|
|
|
+const arrowStyleFunction = (feature, resolution) => {
|
|
|
+ const geometry = feature.getGeometry();
|
|
|
+ const styles = [
|
|
|
+ // 基础线样式
|
|
|
+ new Style({
|
|
|
+ stroke: new Stroke({
|
|
|
+ color: '#2196F3',
|
|
|
+ width: 6
|
|
|
+ })
|
|
|
+ })
|
|
|
+ ];
|
|
|
+
|
|
|
+ // 计算箭头间隔(每50像素一个箭头)
|
|
|
+ const length = geometry.getLength();
|
|
|
+ const pixelInterval = 50;
|
|
|
+ const geoInterval = pixelInterval * resolution;
|
|
|
+ const arrowCount = Math.floor(length / geoInterval);
|
|
|
+
|
|
|
+ // 生成箭头样式
|
|
|
+ for (let i = 0; i <= arrowCount; i++) {
|
|
|
+ const ratio = i / arrowCount;
|
|
|
+ const coord = geometry.getCoordinateAt(ratio);
|
|
|
+
|
|
|
+ // 获取线段方向
|
|
|
+ const prevCoord = geometry.getCoordinateAt(Math.max(0, ratio - 0.001));
|
|
|
+ const dx = coord[0] - prevCoord[0];
|
|
|
+ const dy = coord[1] - prevCoord[1];
|
|
|
+ const rotation = Math.atan2(dy, dx);
|
|
|
+
|
|
|
+ // styles.push(
|
|
|
+ // new Style({
|
|
|
+ // geometry: new Point(coord),
|
|
|
+ // image: new Icon({
|
|
|
+ // src: arrowImg, // 替换为实际箭头图标路径
|
|
|
+ // anchor: [0.75, 0.5], // 图标锚点
|
|
|
+ // rotateWithView: true, // 随地图旋转
|
|
|
+ // rotation: -rotation, // 方向校准
|
|
|
+ // scale: 0.05
|
|
|
+ // }),
|
|
|
+ // zIndex: 10
|
|
|
+ // })
|
|
|
+ // );
|
|
|
+ }
|
|
|
+ return styles;
|
|
|
};
|
|
|
// 创建新标点 moveTo调用第二次不生效,重新生成marker
|
|
|
const createMarker = () => {
|
|
@@ -215,6 +420,13 @@ const playOrPauseTrack = () => {
|
|
|
moveMarker.pauseMove();
|
|
|
}
|
|
|
isNewMarkerPause = false;
|
|
|
+ } else {
|
|
|
+ if (!!timeAxisState.playing) {
|
|
|
+ lastTime = Date.now();
|
|
|
+ // 触发地图渲染
|
|
|
+ const geo = carFeature.getGeometry().clone();
|
|
|
+ carFeature.setGeometry(geo);
|
|
|
+ }
|
|
|
}
|
|
|
};
|
|
|
// 轨迹开始播放
|
|
@@ -230,19 +442,41 @@ const prevPauseTime = () => {
|
|
|
// 更新索引
|
|
|
timeAxisState.playing = false;
|
|
|
timeAxisState.activeIndex -= 1;
|
|
|
- moveMarker.stopMove();
|
|
|
- createMarker();
|
|
|
- movePassedPolyline.setPath(trackPathArr.slice(0, timeAxisState.activeIndex + 1));
|
|
|
- isNewMarkerPause = true;
|
|
|
+ if (AMapType.includes(props.activeMap)) {
|
|
|
+ moveMarker.stopMove();
|
|
|
+ createMarker();
|
|
|
+ movePassedPolyline.setPath(trackPathArr.slice(0, timeAxisState.activeIndex + 1));
|
|
|
+ isNewMarkerPause = true;
|
|
|
+ } else {
|
|
|
+ // 设置新坐标
|
|
|
+ const curCoord = trackPathArr[timeAxisState.activeIndex];
|
|
|
+ carFeature.getGeometry().setCoordinates(curCoord);
|
|
|
+ // 获取当前轨迹坐标数组
|
|
|
+ const currentCoords = lastRoute[timeAxisState.activeIndex];
|
|
|
+ lastRoute = lastRoute.slice(0, timeAxisState.activeIndex + 1);
|
|
|
+ traceFeature2.getGeometry().setCoordinates(currentCoords);
|
|
|
+ map.getView().setCenter(curCoord);
|
|
|
+ }
|
|
|
};
|
|
|
const nextPauseTime = () => {
|
|
|
if (timeAxisState.activeIndex <= 0) return;
|
|
|
// 更新索引
|
|
|
timeAxisState.playing = false;
|
|
|
timeAxisState.activeIndex += 1;
|
|
|
- createMarker();
|
|
|
- movePassedPolyline.setPath(trackPathArr.slice(0, timeAxisState.activeIndex + 1));
|
|
|
- isNewMarkerPause = true;
|
|
|
+ if (AMapType.includes(props.activeMap)) {
|
|
|
+ createMarker();
|
|
|
+ movePassedPolyline.setPath(trackPathArr.slice(0, timeAxisState.activeIndex + 1));
|
|
|
+ isNewMarkerPause = true;
|
|
|
+ } else {
|
|
|
+ // 设置新坐标
|
|
|
+ const curCoord = trackPathArr[timeAxisState.activeIndex];
|
|
|
+ carFeature.getGeometry().setCoordinates(curCoord);
|
|
|
+ // 获取当前轨迹坐标数组
|
|
|
+ const currentCoords = lastRoute[lastRoute.length - 1].concat([curCoord]);
|
|
|
+ lastRoute.push(currentCoords);
|
|
|
+ traceFeature2.getGeometry().setCoordinates(currentCoords);
|
|
|
+ map.getView().setCenter(curCoord);
|
|
|
+ }
|
|
|
};
|
|
|
// 轨迹播放下一个节点
|
|
|
const playNextNode = () => {
|
|
@@ -258,31 +492,6 @@ const playNextNode = () => {
|
|
|
duration: timeAxisState.speed === 1 ? 2000 : 1000
|
|
|
});
|
|
|
};
|
|
|
-//传入两个经纬度点得到车辆角度 设置车辆Marker角度
|
|
|
-const getAngle = (startPoint, endPoint) => {
|
|
|
- if (!(startPoint && endPoint)) {
|
|
|
- return 0;
|
|
|
- }
|
|
|
-
|
|
|
- let dRotateAngle = Math.atan2(
|
|
|
- Math.abs(startPoint.lng - endPoint.lng),
|
|
|
- Math.abs(startPoint.lat - endPoint.lat)
|
|
|
- );
|
|
|
- if (endPoint.lng >= startPoint.lng) {
|
|
|
- if (endPoint.lat >= startPoint.lat) {
|
|
|
- } else {
|
|
|
- dRotateAngle = Math.PI - dRotateAngle;
|
|
|
- }
|
|
|
- } else {
|
|
|
- if (endPoint.lat >= startPoint.lat) {
|
|
|
- dRotateAngle = 2 * Math.PI - dRotateAngle;
|
|
|
- } else {
|
|
|
- dRotateAngle = Math.PI + dRotateAngle;
|
|
|
- }
|
|
|
- }
|
|
|
- dRotateAngle = (dRotateAngle * 180) / Math.PI;
|
|
|
- return dRotateAngle;
|
|
|
-};
|
|
|
|
|
|
// 滚动到可见到当前点位置
|
|
|
watch(
|
|
@@ -301,10 +510,37 @@ watch(
|
|
|
}
|
|
|
);
|
|
|
|
|
|
+// 清除元素
|
|
|
+const clearObj = () => {
|
|
|
+ if (AMapType.includes(props.activeMap)) {
|
|
|
+ if (moveMarker) {
|
|
|
+ // 不停止remove还是有
|
|
|
+ moveMarker.stopMove();
|
|
|
+ moveMarker.remove();
|
|
|
+ }
|
|
|
+ movePolyline?.remove();
|
|
|
+ movePassedPolyline?.remove();
|
|
|
+ } else {
|
|
|
+ if (!!carFeature) {
|
|
|
+ carLayer.getSource().removeFeature(carFeature);
|
|
|
+ }
|
|
|
+ if (!!traceFeature) {
|
|
|
+ carLayer.getSource().removeFeature(traceFeature);
|
|
|
+ }
|
|
|
+ if (!!traceFeature2) {
|
|
|
+ carLayer.getSource().removeFeature(traceFeature2);
|
|
|
+ }
|
|
|
+ }
|
|
|
+};
|
|
|
+
|
|
|
onMounted(() => {
|
|
|
getInitTime();
|
|
|
});
|
|
|
|
|
|
+onUnmounted(() => {
|
|
|
+ clearObj();
|
|
|
+});
|
|
|
+
|
|
|
defineExpose({ initDataToPlay });
|
|
|
</script>
|
|
|
|