CzRger 1 year ago
parent
commit
80c62bd306

+ 4 - 0
src/components/easyMap/initMapInfo.ts

@@ -1,7 +1,10 @@
 import * as layer from 'ol/layer'
 import * as source from 'ol/source'
+// @ts-ignore
 import HaituImg from './images/bg-ocean.png'
+// @ts-ignore
 import LutuImg from './images/bg-land.png'
+// @ts-ignore
 import WeixingImg from './images/bg-sky.png'
 import store from '@/store/index'
 
@@ -17,6 +20,7 @@ const initBaseLayer = (obj) => {
   const _layer = new layer.Tile({
     source: new source.XYZ({
       projection: "EPSG:4326",
+      // @ts-ignore
       url: `/${store.state.app.apiProxy.EzServer6Api}/EzServer6/Maps/${obj.key}/EzMap?Service=getImage&Type=RGB&ZoomOffset=0&Col={x}&Row={y}&Zoom={z}&V=0.3`,
     }),
     visible: obj.visible,

+ 91 - 0
src/components/easyMapGL/gl-map.vue

@@ -0,0 +1,91 @@
+<template>
+  <div class="easy-map-gl">
+    <div class="map" id="easyMapGL"/>
+  </div>
+</template>
+
+<script lang="ts">
+  import {
+    defineComponent,
+    onMounted,
+    ref,
+    toRefs,
+    reactive,
+    watch,
+    getCurrentInstance,
+    ComponentInternalInstance,
+    computed,
+    nextTick, onActivated,
+  } from "vue";
+  import { useStore } from "vuex";
+  import InitMapInfoClass from "./initMapInfo";
+  import mapboxgl from "mapbox-gl";
+
+  export default defineComponent({
+    name: "",
+    components: {
+    },
+    props: {
+    },
+    setup(props, { emit }) {
+      const store = useStore();
+      const that = (getCurrentInstance() as ComponentInternalInstance).appContext.config.globalProperties;
+      const state = reactive({
+        map: <any>null
+      });
+      const initMap = () => {
+        const style: any = {
+          version: 8,
+          sources: {
+            basic: {
+              type: 'raster',
+              tiles: [
+                'http://wprd04.is.autonavi.com/appmaptile?lang=zh_cn&size=1&style=7&x={x}&y={y}&z={z}' // 替换为您的XYZ瓦片服务URL
+              ],
+              tileSize: 256
+            }
+          },
+          layers: [
+            {
+              id: 'basic',
+              type: 'raster',
+              source: 'basic',
+              minzoom: 0,
+              maxzoom: 22
+            }
+          ],
+          glyphs: '/font-api/font/{fontstack}/{range}.pbf'
+        }
+        state.map = new mapboxgl.Map({
+          container: 'easyMapGL', // container ID
+          center: <any>InitMapInfoClass.baseMapInfo.center, // starting position [lng, lat]
+          zoom: InitMapInfoClass.baseMapInfo.zoom, // starting zoom
+          style: style
+        });
+        state.map.on('load', () => {
+          emit('glMapLoad', state.map)
+        })
+      }
+      onMounted(() => {
+        nextTick(() => {
+          initMap()
+        })
+      })
+      return {
+        ...toRefs(state),
+      }
+    },
+  });
+</script>
+<style scoped lang="scss">
+.easy-map-gl {
+  width: 100%;
+  height: 100%;
+  position: relative;
+  .map {
+    width: 100%;
+    height: 100%;
+    background-color: #bfdbf3;
+  }
+}
+</style>

+ 90 - 0
src/components/easyMapGL/index.vue

@@ -0,0 +1,90 @@
+<template>
+  <div class="easy-map" v-bind="$attrs">
+    <GLMap
+      @glMapLoad="glMapLoad"
+    />
+  </div>
+</template>
+
+<script lang="ts">
+import {
+  defineComponent,
+  onMounted,
+  ref,
+  toRefs,
+  reactive,
+  watch,
+  getCurrentInstance,
+  ComponentInternalInstance,
+  computed,
+  nextTick,
+} from "vue";
+import { useStore } from "vuex";
+import { useRouter } from "vue-router";
+import GLMap from "./gl-map.vue";
+import InitMapInfoClass from "./initMapInfo";
+import {ElMessage, ElMessageBox} from "element-plus";
+
+export default defineComponent({
+  name: "EasyMapGL",
+  components: {
+    GLMap,
+  },
+  props: {
+    baseMapLayers: {},
+    baseMapView: {},
+    layout: {}
+  },
+  emits: ['easyMapGLLoad'],
+  setup(props, { emit }) {
+    const store = useStore();
+    const router = useRouter();
+    const that = (getCurrentInstance() as ComponentInternalInstance).appContext.config.globalProperties;
+    const state = reactive({
+      map: <any>null
+    });
+    const glMapLoad = (map) => {
+      state.map = map
+      emit("easyMapGLLoad", map, {
+        getBBOX,
+        addImg
+      })
+    }
+    const getBBOX = () => {
+      const bounds = state.map.getBounds();
+      // 获取边界框的西南角和东北角坐标
+      const sw = bounds.getSouthWest();
+      const ne = bounds.getNorthEast();
+      const BBOX = [sw.lng, sw.lat, ne.lng, ne.lat]
+      return BBOX
+    };
+    const addImg = async (name, url) => {
+      const loadImage = (url) => {
+        return new Promise((resolve, reject) => {
+          const img = new Image();
+          img.src = url;
+          img.onload = () => {
+            resolve(img);
+            img.onerror = reject;
+          };
+        });
+      }
+      const img = await loadImage(url);
+      state.map.addImage(name, img, {sdf: true});
+    }
+    onMounted(() => {
+    });
+    return {
+      ...toRefs(state),
+      glMapLoad
+    };
+  },
+});
+</script>
+<style scoped lang="scss">
+.easy-map {
+  width: 100%;
+  height: 100%;
+  position: relative;
+}
+</style>

+ 14 - 0
src/components/easyMapGL/initMapInfo.ts

@@ -0,0 +1,14 @@
+import store from '@/store/index'
+
+const isInternet = window.location.origin.includes('74.10') ? false : true
+
+const baseMapInfo = {
+  center: [109.6915958479584, 19.111636735969318],
+  projection: "EPSG:4326",
+  zoom: 7
+  // extent: [120.8953306326286,31.3667480047968,121.37735577911297,31.692561298253832]
+}
+export default {
+  baseMapInfo,
+  isInternet
+}

+ 2 - 1
src/plugins/initComponent.ts

@@ -1,6 +1,6 @@
 import { App, Component } from 'vue'
 import EasyMapComponent from '@/components/easyMap/index.vue'
-
+import EasyMapGLComponent from '@/components/easyMapGL/index.vue'
 interface FileType {
   [key: string]: Component
 }
@@ -21,4 +21,5 @@ export default (app: App): void => {
   })
 
   app.component('EasyMapComponent', EasyMapComponent)
+  app.component('EasyMapGLComponent', EasyMapGLComponent)
 }

+ 1 - 0
src/store/modules/app.ts

@@ -3,6 +3,7 @@ const state = {
 	apiProxy: {
 		EzServer6Api: 'EzServer6-api',	// 地图底图代理
 		rhFindShipApi: 'rh-find-ship-api',	// 融合找船代理
+		shipPlaybackWSApi: 'ship-playback-ws-api',	// 船舶回放ws代理
 	},
 }
 const mutations = {

+ 1 - 0
src/styles/index.scss

@@ -1,5 +1,6 @@
 @import './webkit-scrollbar';
 @import './cus';
+@import './mapboxgl';
 @import '@/components/easyMap/easy-map-style.scss';
 
 * {

File diff suppressed because it is too large
+ 560 - 0
src/styles/mapboxgl.scss


+ 14 - 0
src/views/ship-playback/AIS.svg

@@ -0,0 +1,14 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<svg width="14px" height="21px" viewBox="0 0 14 21" version="1.1" xmlns="http://www.w3.org/2000/svg"
+     xmlns:xlink="http://www.w3.org/1999/xlink">
+    <title>融合船舶-AIS</title>
+    <g id="船舶状态2备份-2"
+       transform="translate(-48.000000, -1054.000000)"
+       style="fill: #fff;"
+       stroke="#000000"
+    >
+        <polygon id="融合船舶-AIS-"
+                 transform="translate(55.000000, 1064.000000) scale(-1, 1) translate(-55.000000, -1064.000000) "
+                 points="55 1054 61 1074 49 1074"></polygon>
+    </g>
+</svg>

+ 188 - 56
src/views/ship-playback/index.vue

@@ -31,7 +31,10 @@ export default defineComponent({
     const router = useRouter();
     const route = useRoute();
     const that = (getCurrentInstance() as ComponentInternalInstance).appContext.config.globalProperties
-    const state = reactive({})
+    const state = reactive({
+      map: <any>null,
+      popupHover: <any>null
+    })
     const initMap = () => {
       const style: any = {
         version: 8,
@@ -53,74 +56,203 @@ export default defineComponent({
             maxzoom: 22
           }
         ],
+        glyphs: '/font-api/font/{fontstack}/{range}.pbf'
       }
-      const map = new mapboxgl.Map({
+      const accessToken = "pk.eyJ1IjoibHVrYXNtYXJ0aW5lbGxpIiwiYSI6ImNpem85dmhwazAyajIyd284dGxhN2VxYnYifQ.HQCmyhEXZUTz3S98FMrVAQ";
+      // mapboxgl.accessToken = accessToken;
+      state.map = new mapboxgl.Map({
         container: 'map', // container ID
         center: [109.6915958479584, 19.111636735969318], // starting position [lng, lat]
         zoom: 7, // starting zoom
         style: style
       });
-      map.on('load', () => {
-        map.addSource('maine', {
-          type: 'geojson',
-          data: {
+      state.map.on('load', () => {
+        // initShip()
+        initAISjiete()
+      })
+    }
+    const initShip = () => {
+      state.map.addSource('maine', {
+        type: 'geojson',
+        data: {
+          type: 'FeatureCollection',
+          features: []
+        }
+      })
+      // state.map.addLayer({
+      //   id: '1',
+      //   type: 'fill',
+      //   source: 'maine',
+      //   layout: {},
+      //   paint: {
+      //     'fill-color': 'red'
+      //   }
+      // })
+      state.map.addLayer({
+        id: '2',
+        type: 'circle',
+        source: 'maine',
+        layout: {},
+        paint: {
+          'circle-color': 'red',
+          'circle-radius': 10,
+        }
+      })
+      state.map.addLayer({
+        "id": '3',
+        "type": "symbol",
+        "source": 'maine',
+        'layout': {
+          'text-field': ['get', 'title'],
+          "text-font": ["cus"],
+        }
+      });
+      // state.map.on('click', '2', (e) => {
+      //   const features = state.map.queryRenderedFeatures(e.point);
+      //   if (!features.length) {
+      //     return;
+      //   }
+      //   const properties: any = features[0].properties;
+      //   console.log(e)
+      //   console.log(features)
+      //   new mapboxgl.Popup()
+      //   .setLngLat(e.lngLat)
+      //   .setHTML(properties.title)
+      //   .addTo(state.map);
+      // });
+      state.map.on('mousemove', '2', (e) => {
+        const features = state.map.queryRenderedFeatures(e.point);
+        if (!features.length) {
+          return;
+        }
+        const properties: any = features[0].properties;
+        if (state.popupHover) {
+          state.popupHover.setLngLat(e.lngLat).setHTML(properties.title)
+        } else {
+          state.popupHover = new mapboxgl.Popup().setLngLat(e.lngLat).setHTML(properties.title).addTo(state.map);
+          state.popupHover.on('close', () => {
+            state.popupHover = null
+          })
+        }
+      });
+      state.map.on('mouseenter', '2', () => {
+        state.map.getCanvas().style.cursor = 'pointer';
+      });
+      state.map.on('mouseleave', '2', () => {
+        state.map.getCanvas().style.cursor = '';
+      });
+      const ws = new WebSocket(`ws://${location.host}/${store.state.app.apiProxy.shipPlaybackWSApi}`)
+      ws.onopen = (e) => {
+        const str = {
+          title: 'ssds'
+        }
+        ws.send(JSON.stringify(str))
+      }
+      ws.onmessage = (e) => {
+        try {
+          const json = JSON.parse(e.data)
+          state.map.getSource('maine')?.setData({
             type: 'FeatureCollection',
-            features: initPointsFeature(10000)
-          }
-        })
-        map.addLayer({
-          id: '1',
-          type: 'fill',
-          source: 'maine',
-          layout: {},
-          paint: {
-            'fill-color': 'red'
+            features: json
+          })
+        } catch (e) {
+        }
+      }
+    }
+    const getBBOX = () => {
+      // 获取当前地图视窗的边界框
+      const bounds = state.map.getBounds();
+      // 获取边界框的西南角和东北角坐标
+      const sw = bounds.getSouthWest();
+      const ne = bounds.getNorthEast();
+      const BBOX = `BBOX(location,${sw.lng},${sw.lat},${ne.lng},${ne.lat})`
+      return BBOX
+    }
+    const initAISjiete = () => {
+      const sourceId = 'source_aisJiete'
+      state.map.addSource(sourceId, {
+        type: 'geojson',
+        data: {
+          type: 'FeatureCollection',
+          features: []
+        }
+      })
+      const ws = new WebSocket('ws://218.77.183.103:7001/webSocket')
+      ws.onopen = (e) => {
+        state.map.on('moveend', () => {
+          const str = {
+            cql: getBBOX()
           }
+          ws.send(JSON.stringify(str))
         })
-        map.addLayer({
-          id: '2',
-          type: 'circle',
-          source: 'maine',
-          layout: {},
-          paint: {
-            'circle-color': 'red',
-            'circle-radius': 10,
+        const str = {
+          cql: getBBOX()
+        }
+        ws.send(JSON.stringify(str))
+      }
+      ws.onmessage = (e) => {
+        try {
+          const json = JSON.parse(e.data)
+          const formatData = (arr) => {
+            return arr.map(v => {
+              const obj = {
+                type: 'Feature',
+                geometry: {
+                  type: 'Point',
+                  coordinates: [v.longitude, v.latitude]
+                },
+                properties: v
+              }
+              return obj
+            })
           }
-        })
-        // map.addLayer({
-        //   "id": '3',
-        //   "type": "symbol",
-        //   "source": 'maine',
-        //   "layout": {
-        //     "text-field": "{title}",
-        //     "text-offset": [0, 0.6],
-        //     "text-anchor": "top"
-        //   },
-        // });
-
-        setInterval(() => {
-          map.getSource('maine')?.setData({
+          state.map.getSource(sourceId)?.setData({
             type: 'FeatureCollection',
-            features: initPointsFeature(10000)
+            features: formatData(json.data)
           })
-        }, 1000)
-      })
-    }
-    const initPointsFeature = (total = 1) => {
-      const arr: any = []
-      for (let i = 0; i < total; i++) {
-        arr.push({
-          type: 'Feature',
-          geometry: {
-            type: 'Point',
-            coordinates: [that.$util.randomNum(107, 112, 6), that.$util.randomNum(17, 20, 6)]
-          },
-          properties: {
-            title: i
-          }
-        })
+        } catch (e) {
+        }
       }
-      return arr
+      state.map.addLayer({
+        id: 'ais-jiete-1',
+        type: 'circle',
+        source: sourceId,
+        layout: {},
+        paint: {
+          'circle-color': 'red',
+          'circle-radius': 10,
+        }
+      })
+      state.map.addLayer({
+        "id": 'ais-jiete',
+        "type": "symbol",
+        "source": sourceId,
+        'layout': {
+          'text-field': '{gisId}',
+          "text-font": ["cus"],
+        }
+      });
+      state.map.on('mousemove', 'ais-jiete-1', (e) => {
+        const features = state.map.queryRenderedFeatures(e.point);
+        if (!features.length) {
+          return;
+        }
+        const properties: any = features[0].properties;
+        if (state.popupHover) {
+          state.popupHover.setLngLat(e.lngLat).setHTML(properties.gisId)
+        } else {
+          state.popupHover = new mapboxgl.Popup().setLngLat(e.lngLat).setHTML(properties.gisId).addTo(state.map);
+          state.popupHover.on('close', () => {
+            state.popupHover = null
+          })
+        }
+      });
+      state.map.on('mouseenter', 'ais-jiete-1', () => {
+        state.map.getCanvas().style.cursor = 'pointer';
+      });
+      state.map.on('mouseleave', 'ais-jiete-1', () => {
+        state.map.getCanvas().style.cursor = '';
+      });
     }
     onMounted(() => {
       initMap()

+ 325 - 0
src/views/ship-test/business/v2/index.vue

@@ -0,0 +1,325 @@
+<template>
+  <div class="business">
+    <ShipFilterCom class="ship-filter" :map="map" :mapFunc="mapFunc" @getShipFilter="handleShipFilter"/>
+    <div class="track-list">
+      <div class="track-item track-item-head">
+        <div class="source">目标来源</div>
+        <div class="key">轨迹标识</div>
+        <div class="time">轨迹持续时间</div>
+        <div class="do">操作</div>
+      </div>
+      <div class="track-item-body">
+        <template v-for="([key, item], index) in trackMap">
+          <div class="track-item">
+            <div class="source" :style="`color: ${item.config?.name};`">{{item.config?.name}}</div>
+            <div class="key" :style="`color: ${item.color};background: #ffffff;`">
+              <CusEllipsis :value="key"/>
+            </div>
+            <div class="time">{{item.trackTime}}</div>
+            <div class="do">
+<!--              <el-tooltip :enterable="false" placement="top" content="隐藏" v-if="item.show">-->
+<!--                <img class="__hover" src="@/assets/images/ship-test/ship-track-visible.svg" @click="handleTrackHide(key)"/>-->
+<!--              </el-tooltip>-->
+<!--              <el-tooltip :enterable="false" placement="top" content="显示" v-else>-->
+<!--                <img class="__hover" src="@/assets/images/ship-test/ship-track-invisible.svg" @click="handleTrackShow(key)"/>-->
+<!--              </el-tooltip>-->
+<!--              <el-tooltip :enterable="false" placement="top" content="定位">-->
+<!--                <img class="__hover" src="@/assets/images/ship-test/tools-location.svg" @click="handleTrackLocation(key)"/>-->
+<!--              </el-tooltip>-->
+<!--              <el-tooltip :enterable="false" placement="top" content="调色盘">-->
+<!--                <div class="color">-->
+<!--                  <img class="__hover" src="@/assets/images/ship-test/ship-track-speed.svg"/>-->
+<!--                  <el-color-picker v-model="item.color" @change="color => handleTrackColor(key, color)"/>-->
+<!--                </div>-->
+<!--              </el-tooltip>-->
+<!--              <el-tooltip :enterable="false" placement="top" content="删除">-->
+<!--                <img class="__hover" src="@/assets/images/ship-test/ship-track-delete.svg" @click="handleTrackDel(key)"/>-->
+<!--              </el-tooltip>-->
+            </div>
+          </div>
+        </template>
+      </div>
+    </div>
+  </div>
+</template>
+
+<script lang="ts">
+import {
+  defineComponent,
+  computed,
+  onMounted,
+  ref,
+  reactive,
+  watch,
+  getCurrentInstance,
+  ComponentInternalInstance,
+  toRefs,
+  nextTick, onUnmounted
+} from 'vue'
+import {useStore} from 'vuex'
+import {useRouter, useRoute} from 'vue-router'
+import {ElMessage, ElMessageBox} from "element-plus";
+import ShipFilterCom from "../ship-filter.vue";
+import AisImg from '../AIS.svg'
+import mapboxgl from "mapbox-gl";
+import {J} from "../../../../../seat-tools/assets/index-b9e83171";
+
+export default defineComponent({
+  name: '',
+  components: {
+    ShipFilterCom,
+  },
+  props: {
+    map: <any>{},
+    mapFunc: <any>{},
+  },
+  setup(props, {emit}) {
+    const store = useStore();
+    const router = useRouter();
+    const route = useRoute();
+    const that = (getCurrentInstance() as ComponentInternalInstance).appContext.config.globalProperties
+    const state = reactive({
+      shipFilter: {
+        cql: null,
+        config: <any>{}
+      },
+      currentWS: <any>null,
+      trackMap: new Map(),
+      popupHover: <any>null,
+      mapIds: {
+        current: {
+          source: {
+            ship: 'source-current_ship'
+          },
+          layer: {
+            ship: 'layer-current_ship'
+          }
+        }
+      }
+    })
+    const handleShipFilter = ({cql, shipParams}) => {
+      state.currentWS?.close()
+      state.currentWS = null
+
+      state.shipFilter.cql = cql
+      state.shipFilter.config = shipParams
+      console.log(state.shipFilter.config)
+      refreshMap()
+    }
+    const initMap = () => {
+      // 船舶图片
+      props.mapFunc.addImg('ais', AisImg)
+      // 船舶source
+      props.map.addSource(state.mapIds.current.source.ship, {
+        type: 'geojson',
+        data: {
+          type: 'FeatureCollection',
+          features: []
+        }
+      })
+      // 船舶layer
+      props.map?.addLayer({
+        id: state.mapIds.current.layer.ship,
+        type: "symbol",
+        source: state.mapIds.current.source.ship,
+        layout: {
+          'icon-allow-overlap': true,
+          'icon-ignore-placement': true,
+          // 'text-field': '{gisId}',
+          // "text-font": ["cus"],
+          'icon-image': [
+            'match',
+            ['get', 'type'], // 根据属性值'type'来匹配
+            'A', 'icon-A', // 当属性值为'A'时应用'icon-A'图片样式
+            'B', 'icon-B', // 当属性值为'B'时应用'icon-B'图片样式
+            'ais' // 默认样式
+          ],
+          "symbol-spacing": 1,
+          "icon-rotate": ["get", "__course"]
+        },
+        paint: {
+          'icon-color': '#000000',
+          'icon-halo-color': '#000000',
+          'icon-halo-width': 4,
+        }
+      })
+      props.map.on('mousemove', state.mapIds.current.layer.ship, (e) => {
+        const features = props.map.queryRenderedFeatures(e.point);
+        if (!features.length) {
+          return;
+        }
+        const properties: any = features[0].properties;
+        if (state.popupHover) {
+          state.popupHover.setLngLat(e.lngLat).setHTML(properties.gisId)
+        } else {
+          state.popupHover = new mapboxgl.Popup().setLngLat(e.lngLat).setHTML(properties.title).addTo(props.map);
+          state.popupHover.on('close', () => {
+            state.popupHover = null
+          })
+        }
+      });
+      props.map.on('mouseenter', state.mapIds.current.layer.ship, () => {
+        props.map.getCanvas().style.cursor = 'pointer';
+      });
+      props.map.on('mouseleave', state.mapIds.current.layer.ship, () => {
+        props.map.getCanvas().style.cursor = '';
+      });
+    }
+    const refreshMap = () => {
+      if (state.shipFilter.config) {
+        initWebSocketShip()
+      }
+    }
+    const initWebSocketShip = () => {
+      const str = {
+        cql: `(BBOX(location, ${props.mapFunc.getBBOX().join(',')})`,
+      }
+      if (state.shipFilter.cql) {
+        str.cql += ` and ${state.shipFilter.cql}`
+      }
+      str.cql += ')'
+      let ids: any = []
+      state.trackMap.forEach((value, key) => {
+        if (value.sourceId === state.shipFilter.config.id) {
+          ids.push(`'${key}'`)
+        }
+      })
+      if (ids.length > 0) {
+        str.cql += ` and (${state.shipFilter.config.track.trackKey.split('DATA.')[1]} not in (${ids.join(',')}))`
+      }
+      if (state.currentWS) {
+        if (state.currentWS.readyState === 1) {
+          state.currentWS.send(JSON.stringify(str))
+        }
+      } else {
+        const ws = new WebSocket(state.shipFilter.config.wsUrl)
+        state.currentWS = ws
+        ws.onopen = (e) => {
+          ws.send(JSON.stringify(str))
+        }
+        ws.onmessage = (e) => {
+          try {
+            const json = JSON.parse(e.data)
+            const s = 'json' + state.shipFilter.config.wsDataFlag
+            const data = eval(s)
+            initShip(data)
+          } catch (e) {
+          }
+        }
+      }
+    }
+    const initShip = (data) => {
+      const formatData = (arr) => {
+        return arr.map(DATA => {
+          const getKeyData = (key) => {
+            if (state.shipFilter.config.track[key]) {
+              return eval(state.shipFilter.config.track[key])
+            } else {
+              return null
+            }
+          }
+          const obj = {
+            type: 'Feature',
+            geometry: {
+              type: 'Point',
+              coordinates: that.$easyMap.formatPosition.wptTcpt(getKeyData('trackWktKey'))
+            },
+            properties: DATA
+          }
+          obj.properties.isHistory = false
+          obj.properties.__course = getKeyData('trackCourseKey')
+          obj.properties.config = JSON.parse(JSON.stringify(state.shipFilter.config))
+          return obj
+        })
+      }
+      props.map.getSource(state.mapIds.current.source.ship).setData({
+        type: 'FeatureCollection',
+        features: formatData(data)
+      })
+      props.map.setPaintProperty(state.mapIds.current.layer.ship, 'icon-color', state.shipFilter.config.color)
+    }
+    onMounted(() => {
+      initMap()
+    })
+    onUnmounted(() => {
+    })
+    return {
+      ...toRefs(state),
+      handleShipFilter
+    }
+  },
+})
+</script>
+
+<style scoped lang="scss">
+.business {
+  .ship-filter {
+    position: fixed;
+    top: 0;
+    left: 0;
+  }
+  .track-list {
+    position: fixed;
+    top: 0;
+    right: 0;
+    max-height: 600px;
+    background: linear-gradient(180deg, #3874C9 0%, #0043C4 100%);
+    padding: 10px 0;
+    color: #FFFFFF;
+    display: flex;
+    flex-direction: column;
+    overflow: hidden;
+    .track-item-head {
+      padding: 0 20px;
+      text-align: left;
+    }
+    .track-item-body {
+      padding: 0 20px;
+      flex: 1;
+      overflow-y: auto;
+      display: flex;
+      flex-direction: column;
+    }
+    .track-item {
+      display: flex;
+      font-size: 14px;
+      .key {
+        width: 200px;
+      }
+      .source {
+        width: 100px;
+      }
+      .time {
+        width: 100px;
+      }
+      .do {
+        display: flex;
+        >* {
+          width: 20px;
+          height: 20px;
+        }
+        .color {
+          position: relative;
+          width: 16px;
+          height: 16px;
+          display: flex;
+          >img {
+            width: 100%;
+            height: 100%;
+          }
+          :deep(.el-color-picker) {
+            width: 100%;
+            height: 100%;
+            position: absolute;
+            opacity: 0;
+            .el-color-picker__trigger {
+              width: 100%;
+              height: 100%;
+            }
+          }
+        }
+      }
+    }
+  }
+}
+</style>

+ 164 - 0
src/views/ship-test/business/v2/ship-hover.vue

@@ -0,0 +1,164 @@
+<template>
+  <div class="hover-info" ref="ref_shipHover">
+    ASDASDASDADADS
+<!--    <div class="hover-info-head">-->
+<!--      <span>{{shipHoverInfo?.config?.name}}_{{ shipHoverInfo?.isHistory ? '历史' : '实时'}}</span>-->
+<!--    </div>-->
+<!--    <div class="hover-info-close __hover" @click="shipHover.setPosition(undefined)">-->
+<!--      <img src="@/components/easyMap/images/close.png" alt=""/>-->
+<!--    </div>-->
+<!--    <div class="hover-main" v-if="shipHoverInfo?.data">-->
+<!--      <template v-for="item in shipHoverInfoCpt">-->
+<!--        <div class="hover-item">-->
+<!--          <div class="hover-item-label">{{item.label}}:</div>-->
+<!--          <div class="hover-item-value">{{item.value}}</div>-->
+<!--        </div>-->
+<!--      </template>-->
+<!--    </div>-->
+  </div>
+</template>
+
+<script lang="ts">
+import {
+  defineComponent,
+  computed,
+  onMounted,
+  ref,
+  reactive,
+  watch,
+  getCurrentInstance,
+  ComponentInternalInstance,
+  toRefs,
+  nextTick
+} from 'vue'
+import {useStore} from 'vuex'
+import {useRouter, useRoute} from 'vue-router'
+import {ElMessage, ElMessageBox} from "element-plus";
+
+export default defineComponent({
+  name: '',
+  components: {},
+  props: {
+    form: {
+      required: true,
+      default: <any>{}
+    }
+  },
+  setup(props, {emit}) {
+    const store = useStore();
+    const router = useRouter();
+    const route = useRoute();
+    const that = (getCurrentInstance() as ComponentInternalInstance).appContext.config.globalProperties
+    const state = reactive({})
+    const shipHoverInfoCpt = computed(() => {
+      const arr: any = []
+      // const DATA = props.form
+      // if (DATA.isHistory) {
+      //   state.shipHoverInfo.config.hover.forEach(h => {
+      //     if (h.historyValue) {
+      //       let value = eval(h.historyValue)
+      //       h.options?.forEach(v => {
+      //         if (String(value) == v.value) {
+      //           value = v.label
+      //         }
+      //       })
+      //       arr.push({
+      //         label: h.name,
+      //         value: value
+      //       })
+      //     }
+      //   })
+      // } else {
+      //   state.shipHoverInfo.config.hover.forEach(h => {
+      //     let value = eval(h.value)
+      //     h.options?.forEach(v => {
+      //       if (String(value) == v.value) {
+      //         value = v.label
+      //       }
+      //     })
+      //     arr.push({
+      //       label: h.name,
+      //       value: value
+      //     })
+      //   })
+      // }
+
+      return arr
+    })
+    onMounted(() => {
+      shipHoverInfoCpt
+    })
+    return {
+      ...toRefs(state),
+    }
+  },
+})
+</script>
+
+<style scoped lang="scss">
+.hover-info {
+  $footH: 10px;
+  width: 300px;
+  background: linear-gradient(180deg, #3874C9 0%, #0043C4 100%);
+  border-radius: 0px 4px 4px 4px;
+  position: relative;
+  display: flex;
+  justify-content: center;
+  &:after {
+    content: '';
+    position: absolute;
+    bottom: -$footH;
+    border-top: $footH solid #0043C4;
+    border-left: $footH solid transparent;
+    border-right: $footH solid transparent;
+  }
+  .hover-info-head {
+    padding: 0 4px;
+    height: 18px;
+    position: absolute;
+    top: -18px;
+    left: 0;
+    font-size: 12px;
+    font-family: PingFang SC;
+    font-weight: 500;
+    color: #FFFFFF;
+    display: flex;
+    align-items: center;
+    line-height: 8px;
+    background: linear-gradient(180deg, #3874C9 0%, #0043C4 100%);
+    border-radius: 2px 2px 0 0;/* 设置圆角 */
+    >img {
+      margin: 0 4px 0 6px;
+    }
+  }
+  .hover-info-close {
+    position: absolute;
+    right: 0;
+    top: -16px;
+  }
+  .hover-main {
+    width: 100%;
+    height: auto;
+    padding: 10px;
+    .hover-item {
+      display: flex;
+      .hover-item-label {
+        min-width: 42px;
+        font-size: 14px;
+        font-family: PingFang SC;
+        font-weight: 600;
+        color: #08FFFF;
+        line-height: 20px;
+      }
+      .hover-item-value {
+        flex: 1;
+        font-size: 14px;
+        font-family: PingFang SC;
+        font-weight: 400;
+        color: #FFFFFF;
+        line-height: 20px;
+      }
+    }
+  }
+}
+</style>

+ 37 - 6
src/views/ship-test/index.vue

@@ -1,13 +1,23 @@
 <template>
   <div class="main">
-    <EasyMapComponent
+    <template v-if="version === 'v1'">
+      <EasyMapComponent
         class="map"
         layout="info"
         @easyMapLoad="mapLoad"
-    />
-    <BusinessCom v-if="map" :map="map" :mapFunc="mapFunc"/>
+      />
+      <BusinessCom v-if="map" :map="map" :mapFunc="mapFunc"/>
+    </template>
+    <template v-if="version === 'v2'">
+      <EasyMapGLComponent
+        class="map"
+        @easyMapGLLoad="mapGLLoad"/>
+      <BusinessV2Com v-if="map" :map="map" :mapFunc="mapFunc"/>
+    </template>
     <div class="buttons">
       <el-button type="primary" @click="$router.push({name: '666089c1-0eac-4058-9b82-157cb4b9c277'})">配置管理</el-button>
+      <el-button v-if="version !== 'v1'" type="primary" @click="switchMapVersion('v1')">v1</el-button>
+      <el-button v-if="version !== 'v2'" type="primary" @click="switchMapVersion('v2')">v2</el-button>
     </div>
   </div>
 </template>
@@ -27,13 +37,14 @@ import {
 } from 'vue'
 import {useStore} from 'vuex'
 import {useRouter, useRoute} from 'vue-router'
-import {ElMessage, ElMessageBox} from "element-plus";
+import {ElLoading, ElMessage, ElMessageBox} from "element-plus";
 import BusinessCom from "@/views/ship-test/business/index.vue";
+import BusinessV2Com from "@/views/ship-test/business/v2/index.vue";
 
 export default defineComponent({
   name: '',
   components: {
-    BusinessCom
+    BusinessCom, BusinessV2Com
   },
   props: {},
   setup(props, {emit}) {
@@ -44,11 +55,29 @@ export default defineComponent({
     const state = reactive({
       map: <any>null,
       mapFunc: <any>null,
+      version: 'v2'
     })
     const mapLoad = (map, func) => {
       state.map = map
       state.mapFunc = func
     }
+    const mapGLLoad = (map, func) => {
+      state.map = map
+      state.mapFunc = func
+    }
+    const switchMapVersion = (version) => {
+      const loading = ElLoading.service({
+        lock: true,
+        text: 'Loading',
+        background: 'rgba(0, 0, 0, 0.7)',
+      })
+      state.map = null
+      state.version = ''
+      setTimeout(() => {
+        state.version = version
+        loading.close()
+      }, 1000)
+    }
     onMounted(() => {
       if (window.cusConfig?.internetAuth) {
         if (!localStorage.getItem('auth')) {
@@ -65,7 +94,9 @@ export default defineComponent({
     })
     return {
       ...toRefs(state),
-      mapLoad
+      mapLoad,
+      mapGLLoad,
+      switchMapVersion
     }
   },
 })

+ 15 - 0
vite.config.ts

@@ -63,6 +63,21 @@ export default defineConfig({
           return path
         }
       },
+      '/font-api/': {
+        target: 'http://localhost:1111/',
+        changeOrigin: true,
+        rewrite: path => {
+          return path.replace(/^\/font-api/, '')
+        }
+      },
+      '/ship-playback-ws-api': {
+        target: 'http://localhost:18062/',
+        ws: true,
+        changeOrigin: true,
+        rewrite: path => {
+          return path.replace(/^\/ship-playback-ws-api/, '')
+        }
+      },
     }
   },
   build: {