Hwf 2 месяцев назад
Родитель
Сommit
856d827c9b

+ 9 - 0
src/api/videoMonitor/index.ts

@@ -89,3 +89,12 @@ export function getLxHyVideoTagInfo(params) {
     params: params
   });
 }
+
+// 全域地图-视频点位信息
+export function getVideoList(params) {
+  return request({
+    url: '/api/videoResource/videoinfo/get_video_list',
+    method: 'get',
+    params: params
+  });
+}

BIN
src/assets/images/arrow.png


+ 295 - 59
src/components/TimeAxis/index.vue

@@ -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>
 

+ 3 - 2
src/types/components.d.ts

@@ -27,6 +27,7 @@ declare module 'vue' {
     ElAutocomplete: typeof import('element-plus/es')['ElAutocomplete']
     ElBadge: typeof import('element-plus/es')['ElBadge']
     ElButton: typeof import('element-plus/es')['ElButton']
+    ElCheckbox: typeof import('element-plus/es')['ElCheckbox']
     ElCol: typeof import('element-plus/es')['ElCol']
     ElColorPicker: typeof import('element-plus/es')['ElColorPicker']
     ElConfigProvider: typeof import('element-plus/es')['ElConfigProvider']
@@ -51,15 +52,15 @@ declare module 'vue' {
     ElRow: typeof import('element-plus/es')['ElRow']
     ElScrollbar: typeof import('element-plus/es')['ElScrollbar']
     ElSelect: typeof import('element-plus/es')['ElSelect']
+    ElSlider: typeof import('element-plus/es')['ElSlider']
     ElSubMenu: typeof import('element-plus/es')['ElSubMenu']
     ElSwitch: typeof import('element-plus/es')['ElSwitch']
-    ElTable: typeof import('element-plus/es')['ElTable']
-    ElTableColumn: typeof import('element-plus/es')['ElTableColumn']
     ElTag: typeof import('element-plus/es')['ElTag']
     ElText: typeof import('element-plus/es')['ElText']
     ElTimeline: typeof import('element-plus/es')['ElTimeline']
     ElTimelineItem: typeof import('element-plus/es')['ElTimelineItem']
     ElTooltip: typeof import('element-plus/es')['ElTooltip']
+    ElTree: typeof import('element-plus/es')['ElTree']
     ElUpload: typeof import('element-plus/es')['ElUpload']
     ExcelEditor: typeof import('./../components/ExcelEditor/index.vue')['default']
     FileUpload: typeof import('./../components/FileUpload/index.vue')['default']

+ 11 - 16
src/views/emergencyCommandMap/LeftSection/VideoMonitorEdit.vue

@@ -73,7 +73,7 @@
 </template>
 
 <script lang="ts" setup>
-import { getEmergencyVideoCata, getUserVideoPoints, updateUserVideoPoints } from '@/api/videoMonitor';
+import { getEmergencyVideoCata, getUserVideoPoints, getVideoList, updateUserVideoPoints } from '@/api/videoMonitor';
 import { deepClone } from '@/utils';
 import useAppStore from '@/store/modules/app';
 import useMapStore from '@/store/modules/map';
@@ -144,20 +144,11 @@ const getList = () => {
       filterData(editData.value);
     }
   });
-  // 模拟分类数据
-  if (!!queryParams.dict_value) {
-    const data = [
-      {
-        "name": "桂NC0227",
-        "id": "桂NC0227",
-        "dataType": 43,
-        "longitude": "110.35366599999999",
-        "latitude": "22.030815999999998",
-        "type": "2"
-      }
-    ];
-    mapStore.setTrackState(data);
-  }
+};
+const getVideoInfoList = () => {
+  getVideoList(queryParams).then((res) => {
+    mapStore.setTrackState(res.data);
+  });
 };
 const selectItem = (item) => {
   if (editVideo.value) {
@@ -186,6 +177,7 @@ const reset = () => {
 const handleQuery = () => {
   queryParams.current = 1;
   getList();
+  // getVideoInfoList();
 };
 
 /** 重置按钮操作 */
@@ -228,7 +220,10 @@ const handleSave = () => {
   });
 };
 
-getList();
+onMounted(() => {
+  getList();
+  // getVideoInfoList();
+});
 </script>
 
 <style lang="scss" scoped>

+ 1 - 1
src/views/globalMap/RightMenu/KeyVehicles.vue

@@ -88,7 +88,7 @@ const handleCollaborate = () => {};
 const handleTrack = (item) => {
   getVehicleTrajectory(item.vehicle_no).then((res) => {
     const trajectory = [];
-    res.rows.forEach((item) => {
+    res.data.list.forEach((item) => {
       trajectory.push({
         time: !!item.gpsDate ? parseTime(item.gpsDate, '{h}:{i}') : '',
         lnglat: [item.lng, item.lat]