Bladeren bron

轨迹标绘

CzRger 1 jaar geleden
bovenliggende
commit
8253edc3f7

+ 1 - 1
src/components/easyMap/initMapInfo.ts

@@ -5,7 +5,7 @@ import LutuImg from './images/bg-land.png'
 import WeixingImg from './images/bg-sky.png'
 import store from '@/store/index'
 
-const isInternet = window.location.origin.includes('74.10') ? false : true
+const isInternet = false
 
 const baseMapView = {
   center: [109.6915958479584, 19.111636735969318],

+ 14 - 0
src/views/ship-test/business/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>

+ 482 - 9
src/views/ship-test/business/index.vue

@@ -1,7 +1,21 @@
 <template>
   <div class="business">
-    <ShipFilterCom class="ship-filter" :map="map" :mapFunc="mapFunc"/>
-    <ShipInfoCom class="ship-info" :map="map" :mapFunc="mapFunc"/>
+    <ShipFilterCom class="ship-filter" :map="map" :mapFunc="mapFunc" @getShipFilter="handleShipFilter"/>
+    <TrackListCom class="track-list" :map="map" :mapFunc="mapFunc"/>
+    <div class="hover-info" ref="ref_shipHover">
+      <div class="hover-info-head">
+        <span>{{ 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">
+        <div class="hover-item">
+          <div class="hover-item-label">名称:</div>
+          <div class="hover-item-value">{{shipHoverInfo.data}}</div>
+        </div>
+      </div>
+    </div>
   </div>
 </template>
 
@@ -16,34 +30,414 @@ import {
   getCurrentInstance,
   ComponentInternalInstance,
   toRefs,
-  nextTick
+  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 ShipInfoCom from "./ship-info.vue";
+import TrackListCom from "./track-list.vue";
+import * as source from "ol/source";
+import * as layer from "ol/layer";
+import ShipStyle from "@/views/ship-test/business/shipStyle";
+import * as format from "ol/format";
+import * as ol from "ol";
 
 export default defineComponent({
   name: '',
   components: {
     ShipFilterCom,
-    ShipInfoCom
+    TrackListCom
   },
   props: {
-    map: {},
-    mapFunc: {},
+    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({})
+    const state = reactive({
+      zoomWMS: true,
+      wsMap: new Map(),
+      wsLayer: <any>null,
+      shipFilter: {
+        cql: null,
+        config: <any>{}
+      },
+      trackMap: new Map(),
+      shipHover: <any>null,
+      shipHoverInfo: <any>{}
+    })
+    const ref_shipHover = ref()
+    const initMap = () => {
+      props.map?.on('moveend', (e) => {
+        const zoom = e.map.getView().getZoom()
+        if (zoom > 13) {
+          state.zoomWMS = false
+          initWebSocketShip()
+        } else {
+          state.zoomWMS = true
+        }
+      })
+      props.map?.on('singleclick', (e) => {
+        if (!store.state.easyMap.isDrawing) {
+          let isFeature = false
+          e.map.forEachFeatureAtPixel(e.pixel, (f) => {
+            if (!isFeature) {
+              if (f.get('_trackId') && !state.trackMap.has(f.get('_trackId'))) {
+                isFeature = true
+                if (f.get('featureType') === 'ship') {
+                  handleShipClick(f)
+                }
+              }
+            }
+          }, {
+            hitTolerance: 0,
+          });
+        }
+      })
+      props.map?.on('pointermove', (e) => {
+        if (!store.state.easyMap.isDrawing) {
+          let isFeature = false
+          e.map.forEachFeatureAtPixel(e.pixel, (f) => {
+            if (!isFeature) {
+              if (['ship', 'shipPoint'].includes(f.get('featureType'))) {
+                isFeature = true
+                state.shipHover.setPosition(f.getGeometry().getCoordinates())
+                state.shipHoverInfo = f.get('_hover')
+              }
+            }
+          }, {
+            hitTolerance: 0,
+          });
+        }
+      })
+      state.shipHover = new ol.Overlay({
+        id: 'shipHover',
+        element: ref_shipHover.value,
+        autoPan: false,
+        offset: [0, -30],
+        positioning: 'bottom-center',
+        stopEvent: true,
+        autoPanAnimation: {
+          duration: 250
+        }
+      })
+      props.map.addOverlay(state.shipHover)
+    }
+    const handleShipClick = (clickFeat) => {
+      const color = `rgb(${that.$util.randomNum(0, 255)}, ${that.$util.randomNum(0, 255)}, ${that.$util.randomNum(0, 255)})`
+      const trackLayer: any = new layer.VectorImage({
+        zIndex: 10
+      })
+      props.map.addLayer(trackLayer)
+      const trackId = clickFeat.get('_trackId')
+      const rWs = new WebSocket(clickFeat.get('_wsUrl'))
+      state.trackMap.set(trackId, {
+        color: color,
+        webSocket: rWs,
+        layer: trackLayer,
+        historyData: [],
+        realData: [],
+        sourceId: clickFeat.get('_sourceId')
+      })
+      initWebSocketShip()
+      const hWs = new WebSocket(clickFeat.get('_historyWsUrl'))
+      hWs.onopen = (e) => {
+        const str = {
+          ...clickFeat.get('_historyWsOtherParams'),
+          endTime: that.$util.YMDHms(new Date().getTime() + 1000 * 60 * 10),
+          shipId: trackId,
+          startTime: that.$util.YMDHms(new Date().getTime() - 1000 * 60 * 60 * 4)
+        }
+        hWs.send(JSON.stringify(str))
+      }
+      hWs.onmessage = (e) => {
+        try {
+          const json = JSON.parse(e.data)
+          const s = 'json' + clickFeat.get('_historyWsDataFlag')
+          const data = eval(s)
+          state.trackMap.get(trackId).historyData = data
+        } catch (e) {
+        }
+      }
+      rWs.onopen = (e) => {
+        const str = {
+          cql: `${clickFeat.get('_trackKey')} = '${trackId}'`
+        }
+        rWs.send(JSON.stringify(str))
+      }
+      rWs.onmessage = (e) => {
+        try {
+          const json = JSON.parse(e.data)
+          const s = 'json' + clickFeat.get('_wsDataFlag')
+          const data = eval(s)
+          const features: any = []
+          // 实时船舶
+          const DATA = data?.[0]
+          if (DATA) {
+            try {
+              const getKeyData = (key) => {
+                if (clickFeat.get(key)) {
+                  return eval(clickFeat.get(key))
+                } else {
+                  return null
+                }
+              }
+              const feat: any = new format.WKT().readFeature(getKeyData('trackWktKey'))
+              feat.set('featureType', 'ship')
+              feat.setId(getKeyData('trackPointKey'))
+              feat.set('_hover', {
+                data: DATA,
+                config: clickFeat.get('_config'),
+                isHistory: false
+              })
+              feat.setStyle(ShipStyle.ShipNormalStyle({
+                course: clickFeat.get('_course'),
+                speed: clickFeat.get('_speed'),
+                head: clickFeat.get('_head'),
+                color: color
+              }))
+              features.push(feat)
+              state.trackMap.get(trackId).realData.push(DATA)
+            } catch (e) {
+              console.log(e)
+            }
+          }
+          // 轨迹线
+          const lineArr: any = []
+          state.trackMap.get(trackId).historyData.forEach(DATA => {
+            DATA.isHistory = true
+            const getKeyData = (key) => {
+              if (clickFeat.get(key)) {
+                return eval(clickFeat.get(key))
+              } else {
+                return null
+              }
+            }
+            lineArr.push(getKeyData('historyTrackWktKey'))
+          })
+          state.trackMap.get(trackId).realData.forEach(DATA => {
+            DATA.isHistory = false
+            const getKeyData = (key) => {
+              if (clickFeat.get(key)) {
+                return eval(clickFeat.get(key))
+              } else {
+                return null
+              }
+            }
+            lineArr.push(getKeyData('trackWktKey'))
+          })
+          if (lineArr.length > 0) {
+            const lineWkt = that.$easyMap.formatPosition.wptTwl(lineArr)
+            const feat: any = new format.WKT().readFeature(lineWkt)
+            feat.set('trackPointList', [...state.trackMap.get(trackId).historyData, ...state.trackMap.get(trackId).realData])
+            feat.set('featureType', 'shipTrack')
+            feat.setStyle((f, r) => ShipStyle.trackStyle(f, r, props.map, color, (s, pointList) => {
+              const pointFeatures: any = []
+              pointList.forEach(DATA => {
+                try {
+                  const getKeyData = (key) => {
+                    if (clickFeat.get(key)) {
+                      return eval(clickFeat.get(key))
+                    } else {
+                      return null
+                    }
+                  }
+                  const feat: any = new format.WKT().readFeature(DATA.isHistory ? getKeyData('historyTrackWktKey') : getKeyData('trackWktKey'))
+                  feat.set('featureType', 'shipPoint')
+                  feat.set('_hover', {
+                    data: DATA,
+                    config: clickFeat.get('_config'),
+                    isHistory: DATA.isHistory
+                  })
+                  feat.setStyle(ShipStyle.trackPointNormalStyle(color))
+                  pointFeatures.push(feat)
+                } catch (e) {
+                  console.log(e)
+                }
+              })
+              trackLayer.getSource().addFeatures(pointFeatures)
+              return s
+            }))
+            features.push(feat)
+          }
+          const vectorSource = new source.Vector({
+            features: features,
+            wrapX: false
+          });
+          trackLayer.setSource(vectorSource)
+        } catch (e) {
+        }
+      }
+    }
+    const handleShipFilter = ({cql, shipParams}) => {
+      state.shipFilter.cql = cql
+      state.shipFilter.config = shipParams
+      console.log(state.shipFilter.config)
+      refreshMap()
+    }
+    const refreshMap = () => {
+      if (state.shipFilter.config) {
+        if (state.zoomWMS) {
+          initWMSShip()
+        } else {
+          const hasLayer = props.map.getLayers().getArray().filter(v => v.get('layerName') === 'WMSShip')?.[0]
+          if (hasLayer) {
+            props.map.removeLayer(hasLayer)
+          }
+          initWebSocketShip()
+        }
+      }
+    }
+    const initWMSShip = () => {
+      if (state.wsLayer) {
+        state.wsLayer.getSource().clear()
+      }
+      for (let key of state.wsMap.keys()) {
+        state.wsMap.get(key).close()
+        state.wsMap.delete(key)
+      }
+      const hasLayer = props.map.getLayers().getArray().filter(v => v.get('layerName') === 'WMSShip')?.[0]
+      if (hasLayer) {
+        props.map.removeLayer(hasLayer)
+      }
+      const p:any = {
+        'FORMAT': 'image/png8',
+        'VERSION': '1.1.1',
+        "LAYERS": state.shipFilter.config.tileName,
+        "exceptions": 'application/vnd.ogc.se_inimage',
+        refresh: new Date().getTime()
+      }
+      if (state.shipFilter.cql) {
+        p.CQL_FILTER = state.shipFilter.cql
+      }
+      const _tileWMS = new source.TileWMS({
+        url: state.shipFilter.config.tileUrl,
+        params: p
+      })
+      const layerTile = new layer.Tile({
+        source: _tileWMS,
+        zIndex: 3,
+      })
+      layerTile.set('layerName', 'WMSShip')
+      props.map.addLayer(layerTile)
+    }
+    const initWebSocketShip = () => {
+      const str = {
+        cql: <any>null
+      }
+      str.cql = `(BBOX(location, ${props.mapFunc.getBBOX().join(',')}) and ${state.shipFilter.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.wsMap.has(state.shipFilter.config.id)) {
+        if (state.wsMap.get(state.shipFilter.config.id).readyState === 1) {
+          state.wsMap.get(state.shipFilter.config.id).send(JSON.stringify(str))
+        }
+      } else {
+        const ws = new WebSocket(state.shipFilter.config.wsUrl)
+        state.wsMap.set(state.shipFilter.config.id, 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) => {
+      if (!state.wsLayer) {
+        state.wsLayer = new layer.VectorImage({
+          zIndex: 10,
+          style: (f) => {
+            return ShipStyle.ShipNormalStyle({
+              course: f.get('_course'),
+              speed: f.get('_speed'),
+              head: f.get('_head')
+            })
+          }
+        })
+        state.wsLayer.set('layerName', 'WSShip')
+        props.map.addLayer(state.wsLayer)
+      }
+      const ships = data
+      //  动态拼接数据的唯一标识DATA,不可修改
+      const features = ships.map(DATA => {
+        try {
+          const getKeyData = (key) => {
+            if (state.shipFilter.config.track[key]) {
+              return eval(state.shipFilter.config.track[key])
+            } else {
+              return null
+            }
+          }
+          const feat: any = new format.WKT().readFeature(getKeyData('trackWktKey'))
+          feat.set('featureType', 'ship')
+          feat.setId(getKeyData('trackPointKey'))
+          feat.set('_trackId', getKeyData('trackKey'))
+          feat.set('_trackKey', state.shipFilter.config.track.trackKey.split('DATA.')[1])
+          feat.set('_course', getKeyData('trackCourseKey'))
+          feat.set('_speed', getKeyData('trackSpeedKey'))
+          feat.set('_head', getKeyData('trackDeadKey'))
+          feat.set('_wsDataFlag', state.shipFilter.config.wsDataFlag)
+          feat.set('_wsUrl', state.shipFilter.config.wsUrl)
+          feat.set('_sourceId', state.shipFilter.config.id)
+          feat.set('_historyWsUrl', state.shipFilter.config.historyWsUrl)
+          feat.set('_historyWsDataFlag', state.shipFilter.config.historyWsDataFlag)
+          feat.set('_historyWsOtherParams', JSON.parse(state.shipFilter.config.historyWsOtherParams))
+          feat.set('_config', state.shipFilter.config)
+          feat.set('_hover', {
+            data: DATA,
+            config: feat.get('_config'),
+            isHistory: false
+          })
+          for (let [key, value] of Object.entries(state.shipFilter.config.track)) {
+            feat.set(key, value)
+          }
+          return feat
+        } catch (e) {
+          console.log(e)
+        }
+      })
+      const vectorSource = new source.Vector({
+        features: features,
+        wrapX: false
+      });
+      state.wsLayer.setSource(vectorSource)
+    }
+    watch(() => state.zoomWMS, () => {
+      refreshMap()
+    })
     onMounted(() => {
+      initMap()
+    })
+    onUnmounted(() => {
+      for (let key of state.wsMap.keys()) {
+        state.wsMap.get(key).close()
+      }
+      for (let key of state.trackMap.keys()) {
+        state.trackMap.get(key).webSocket.close()
+      }
     })
     return {
       ...toRefs(state),
+      ref_shipHover,
+      handleShipFilter
     }
   },
 })
@@ -56,10 +450,89 @@ export default defineComponent({
     top: 0;
     left: 0;
   }
-  .ship-info {
+  .track-list {
     position: fixed;
     top: 0;
     right: 0;
   }
 }
+.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 {
+    min-width: 68px;
+    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;
+    &:before {
+      z-index: -1;
+      content: '';
+      position: absolute;
+      width: 100%;
+      height: 100%;
+      background: linear-gradient(180deg, #3874C9 0%, #0043C4 100%);
+      border-radius: 2px 2px 0 0;/* 设置圆角 */
+      transform: perspective(20px)rotateX(4deg);
+      /* 镜头距离元素表面的位置为8px,x轴为1.1倍y轴为1.3倍,绕x轴旋转5度 */
+      transform-origin: bottom left;
+      /* bottom left = left bottom = 0 100% 中心点偏移量*/
+    }
+    >span {
+      margin-left: 10px;
+    }
+    >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 {
+        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>

+ 14 - 133
src/views/ship-test/business/ship-filter.vue

@@ -71,9 +71,6 @@ import {
 import {useStore} from 'vuex'
 import {useRouter, useRoute} from 'vue-router'
 import {ElMessage, ElMessageBox} from "element-plus";
-import * as source from "ol/source";
-import * as layer from "ol/layer";
-import * as format from "ol/format";
 
 export default defineComponent({
   name: '',
@@ -88,41 +85,26 @@ export default defineComponent({
     const route = useRoute();
     const that = (getCurrentInstance() as ComponentInternalInstance).appContext.config.globalProperties
     const state = reactive({
-      isActive: true,
+      isActive: false,
       shipSource: <any>[],
       shipParams: <any>null,
-      zoomWMS: true,
-      wsMap: new Map(),
-      wsLayer: <any>null
     })
     const initSource = () => {
       that.$api.shipTestShipFilterGetConfig().then(res => {
         state.shipSource = res.data
-        if (state.shipSource?.[0]) {
+        state.shipSource.forEach(s => {
+          s.params.forEach(p => {
+            p.dict.forEach(d => {
+              d.active = d.active === 0 ? true : false
+            })
+          })
+        })
+        if (state.shipSource[0]) {
           state.shipSource[0].active = true
           onSubmit()
         }
       })
     }
-    watch(() => props.map, (n) => {
-      if (n) {
-        initMap()
-      }
-    })
-    const initMap = () => {
-      props.map?.on('moveend', (e) => {
-        const zoom = e.map.getView().getZoom()
-        if (zoom > 13) {
-          state.zoomWMS = false
-          initWebSocketShip()
-        } else {
-          state.zoomWMS = true
-        }
-      })
-    }
-    watch(() => state.zoomWMS, () => {
-      refreshMap()
-    })
     const handleSource = (source) => {
       state.shipSource.forEach(v => {
         v.active = source.id === v.id ? true : false
@@ -135,12 +117,9 @@ export default defineComponent({
       })
     }
     const onSubmit = () => {
-      state.shipParams = JSON.parse(JSON.stringify(state.shipSource.filter(v => v.active)[0]))
-      refreshMap()
-    }
-    const shipCqlComputer = computed(() => {
+      const shipParams = JSON.parse(JSON.stringify(state.shipSource.filter(v => v.active)[0]))
       let strArr: any = []
-      state.shipParams.params.forEach(p => {
+      shipParams.params.forEach(p => {
         if (p.dict.length > 0) {
           if (p.dict.every(v => !v.active)) {
             strArr.push(`(${p.defaultCql})`)
@@ -149,108 +128,10 @@ export default defineComponent({
           }
         }
       })
-      return strArr.length > 0 ? strArr.map(v => `(${v})`).join(' and ') : null
-    })
-    const initWMSShip = () => {
-      const hasLayer = props.map.getLayers().getArray().filter(v => v.get('layerName') === 'WMSShip')?.[0]
-      if (hasLayer) {
-        props.map.removeLayer(hasLayer)
-      }
-      const p:any = {
-        'FORMAT': 'image/png8',
-        'VERSION': '1.1.1',
-        "LAYERS": state.shipParams.tileName,
-        "exceptions": 'application/vnd.ogc.se_inimage',
-        refresh: new Date().getTime()
-      }
-      if (shipCqlComputer.value) {
-        p.CQL_FILTER = shipCqlComputer.value
-      }
-      const _tileWMS = new source.TileWMS({
-        url: state.shipParams.tileUrl,
-        params: p
-      })
-      const layerTile = new layer.Tile({
-        source: _tileWMS,
-        zIndex: 3,
-      })
-      layerTile.set('layerName', 'WMSShip')
-      props.map.addLayer(layerTile)
-    }
-    const initWebSocketShip = () => {
-      const str = {
-        cql: <any>null,
-      }
-      if (state.zoomWMS) {
-        str.cql = ''
-      } else {
-        str.cql = `(BBOX(location, ${props.mapFunc.getBBOX().join(',')}) and ${shipCqlComputer.value})`
-      }
-      if (state.wsMap.has(state.shipParams.id)) {
-        if (state.wsMap.get(state.shipParams.id).readyState === 1) {
-          state.wsMap.get(state.shipParams.id).send(JSON.stringify(str))
-        }
-      } else {
-        const ws = new WebSocket(state.shipParams.wsUrl)
-        state.wsMap.set(state.shipParams.id, ws)
-        ws.onopen = (e) => {
-          ws.send(JSON.stringify(str))
-        }
-        ws.onmessage = (e) => {
-          try {
-            const json = JSON.parse(e.data)
-            const s = 'json' + state.shipParams.wsDataFlag
-            const data = eval(s)
-            initShip(data)
-          } catch (e) {
-          }
-        }
-      }
-    }
-    const refreshMap = () => {
-      if (state.shipParams) {
-        if (state.zoomWMS) {
-          initWMSShip()
-        } else {
-          const hasLayer = props.map.getLayers().getArray().filter(v => v.get('layerName') === 'WMSShip')?.[0]
-          if (hasLayer) {
-            props.map.removeLayer(hasLayer)
-          }
-          initWebSocketShip()
-        }
-      }
-    }
-    const initShip = (data) => {
-      if (!state.wsLayer) {
-        state.wsLayer = new layer.VectorImage({
-          zIndex: 10,
-        })
-        state.wsLayer.set('layerName', 'WSShip')
-        props.map.addLayer(state.wsLayer)
-      }
-      const ships = data
-      //  动态拼接数据的唯一标识DATA,不可修改
-      const features = ships.map(DATA => {
-        try {
-          const getKeyData = (key) => {
-            if (state.shipParams.track[key]) {
-              return eval(state.shipParams.track[key])
-            } else {
-              return null
-            }
-          }
-          const feat: any = new format.WKT().readFeature(getKeyData('trackWktKey'))
-          feat.setId(getKeyData('trackPointKey'))
-          return feat
-        } catch (e) {
-          console.log(e)
-        }
+      emit('getShipFilter', {
+        cql: strArr.length > 0 ? strArr.map(v => `(${v})`).join(' and ') : null,
+        shipParams: shipParams
       })
-      const vectorSource = new source.Vector({
-        features: features,
-        wrapX: false
-      });
-      state.wsLayer.setSource(vectorSource)
     }
     onMounted(() => {
       initSource()

+ 240 - 0
src/views/ship-test/business/shipStyle.ts

@@ -0,0 +1,240 @@
+import * as ol from 'ol'
+import * as style from 'ol/style'
+import * as layer from 'ol/layer'
+import * as source from 'ol/source'
+import * as geom from 'ol/geom'
+import * as proj from 'ol/proj'
+import * as interaction from 'ol/interaction'
+import {isValue} from "@/utils/util";
+import * as extent from "ol/extent";
+// @ts-ignore
+import AIS from './AIS.svg'
+
+const speedStyle = (lineRadius, rotation) => {
+  return new style.Style({
+    image: new style.RegularShape({
+      stroke: new style.Stroke({
+        color: '#000000',
+        width: 1
+      }),
+      displacement: [0, lineRadius],
+      radius: lineRadius,
+      rotation,
+      points: 2
+    })
+  })
+}
+const headingStyle = (lineRadius, headAngle, rotation) => {
+  const headRadius = 6
+  return new style.Style({
+    image: new style.RegularShape({
+      stroke: new style.Stroke({
+        color: '#ee1919',
+        width: 1
+      }),
+      displacement: [
+        headRadius * Math.sin(headAngle / 180 * Math.PI),
+        (lineRadius * 2) + headRadius * Math.cos(headAngle / 180 * Math.PI)
+      ], //这是偏移量
+      radius: headRadius,
+      rotation,
+      angle: Math.PI / (180 / headAngle), //这是角度计算出的地图上显示的角度
+      points: 2
+    })
+  })
+}
+const shipActiveStyle = (color = '#9F2EFF') => {
+  const radius = 25
+  const longRadius = radius * Math.SQRT2
+  return new style.Style({
+    image: new style.RegularShape({
+      stroke: new style.Stroke({
+        color,
+        width: 2,
+        lineDash: [
+          (longRadius * 3) / 10,
+          (longRadius * 4) / 10,
+          (longRadius * 3) / 10,
+          0
+        ]
+      }),
+      radius1: radius,
+      rotation: Math.PI / (180 / 45),
+      points: 4
+    })
+  })
+}
+
+const ShipNormalStyle = ({
+  course = 0,
+  speed = 0,
+  head = null,
+  color = '#01f200'
+}) => {
+  const _style: any = []
+  const allRotation = Math.PI / (180 / course)
+  const lineRadius = speed
+  // 船速
+  _style.push(speedStyle(lineRadius, allRotation))
+  if (head) {
+    const headAngle = head + (360 - course)
+    // 船首向
+    _style.push(headingStyle(lineRadius, headAngle, allRotation))
+  }
+  _style.push(new style.Style({
+    image: new style.Icon({
+      color: color,
+      src: AIS,
+      rotation: allRotation,
+      scale: 0.6
+    }),
+  }))
+  return _style
+}
+
+const trackStyle = (feature, resolution, map, color, callback) => {
+  const geometry = feature.getGeometry();
+  const length = geometry.getLength();//获取线段长度
+  const radio = (200 * resolution) / length;
+  const dradio = 1;//投影坐标系,如3857等,在EPSG:4326下可以设置dradio=10000
+  const _style = [
+    new style.Style({
+      stroke: new style.Stroke({
+        color: color,
+        width: 2,
+      })
+    })
+  ];
+  const radius = 10
+  const longRadius = radius * Math.SQRT2;
+  const judgeIs = (p1, p2, p3) => {
+    const k1 = (ps, pe) => {
+      return (pe[1] - ps[1]) / (pe[0] - ps[0])
+    }
+    const k2 = (ps, pe) => {
+      return (pe[0] - ps[0]) / (pe[1] - ps[0])
+    }
+    const a = (ps, pe) => {
+      return Math.abs(k1(p1, ps) - k1(p1, pe)) <= E && Math.abs(k1(p1, ps) - k1(p1, pe)) >= -E
+    }
+    const d = (ps, pe) => {
+      return Math.abs(k2(p1, ps) - k2(p1, pe)) <= E && Math.abs(k2(p1, ps) - k2(p1, pe)) >= -E
+    }
+    const s = (p) => {
+      return p3[0] === p[0] && p3[1] === p[1]
+    }
+    const E = 0.00000001
+    return s(p1) || s(p2) || a(p2, p3) || d(p2, p3)
+  }
+  for (let i = 0; i <= 1; i += radio) {
+    const arrowLocation = geometry.getCoordinateAt(i);
+    if (extent.containsCoordinate(map.getView().calculateExtent(map.getSize()), arrowLocation)) {
+      geometry.forEachSegment((start, end) => {
+        if (!judgeIs(start, end, arrowLocation)) {
+          return
+        }
+        let rotation = 0;
+        const dx = end[0] - start[0];
+        const dy = end[1] - start[1];
+        rotation = Math.atan2(dy, dx);
+        const pushStyle = (position) => {
+          _style.push(new style.Style({
+            geometry: new geom.Point(position),
+            image: new style.RegularShape({
+              stroke: new style.Stroke({
+                color,
+                width: 2,
+                lineDash: [
+                  longRadius - (4 * (radius / 10)),
+                  longRadius + (5.5 * (radius / 10)),
+                  longRadius,
+                  0
+                ]
+              }),
+              radius: radius / Math.SQRT2,
+              rotation: -rotation,
+              angle: Math.PI / (180 / 90),
+              points: 4
+            })
+          }));
+        }
+        if (map.getView().getZoom() < map.getView().getMaxZoom()) {
+          const dx1 = end[0] - arrowLocation[0];
+          const dy1 = end[1] - arrowLocation[1];
+          const dx2 = arrowLocation[0] - start[0];
+          const dy2 = arrowLocation[1] - start[1];
+          if (dx1 != dx2 && dy1 != dy2) {
+            if (Math.abs(dradio * dx1 * dy2 - dradio * dx2 * dy1) < 0.001) {
+              pushStyle(arrowLocation)
+            }
+          }
+        } else {
+          if (Math.sqrt(Math.pow(start[0] - end[0], 2) + Math.pow(start[1] - end[1], 2)) > resolution * 100) {
+            pushStyle([(start[0] + end[0]) / 2, (start[1] + end[1]) / 2])
+          }
+        }
+      });
+    }
+  }
+  const trackPointList = feature.get('trackPointList')
+  const pList: any = []
+  let trackPointListIndex = 0
+  let lC = 0
+  pList.push(trackPointList[0])
+  if (map.getView().getZoom() < map.getView().getMaxZoom() - 2) {
+    geometry.forEachSegment((start, end) => {
+      trackPointListIndex++
+      const l = new geom.LineString([start,end])
+      lC += l.getLength()
+      if (extent.containsCoordinate(map.getView().calculateExtent(map.getSize()), end) && lC > 200 * resolution) {
+        pList.push(trackPointList[trackPointListIndex])
+        lC = 0
+      }
+    });
+  } else {
+    geometry.forEachSegment((start, end) => {
+      trackPointListIndex++
+      const l = new geom.LineString([start,end])
+      if (extent.containsCoordinate(map.getView().calculateExtent(map.getSize()), end)) {
+        pList.push(trackPointList[trackPointListIndex])
+      }
+    });
+  }
+  return callback(_style, pList)
+}
+
+const trackPointNormalStyle = (color) => {
+  const _style: any = []
+  _style.push(new style.Style({
+    image: new style.Circle({
+      radius: 4,
+      fill: new style.Fill({
+        color: color,
+      }),
+      stroke: new style.Stroke({
+        color: '#ffffff',
+        width: 2,
+      }),
+    })
+  }))
+  return _style
+}
+const trackPointHoverStyle = (color) => {
+  const _style: any = []
+  _style.push(new style.Style({
+    image: new style.Circle({
+      radius: 6,
+      fill: new style.Fill({
+        color: color,
+      }),
+    })
+  }))
+  return _style
+}
+export default {
+  ShipNormalStyle,
+  shipActiveStyle,
+  trackStyle,
+  trackPointNormalStyle,
+  trackPointHoverStyle
+}

+ 74 - 0
src/views/ship-test/business/track-list.vue

@@ -0,0 +1,74 @@
+<template>
+  <div class="track-list">
+    <template v-if="isActive">
+      <div class="track-list-block">
+        轨迹列表
+      </div>
+    </template>
+    <template v-else>
+      <div class="track-list-icon __hover" @click="isActive = true">
+        <SvgIcon name="tools-ship" color="#ffffff" size="24"/>
+      </div>
+    </template>
+  </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: {},
+  setup(props, {emit}) {
+    const store = useStore();
+    const router = useRouter();
+    const route = useRoute();
+    const that = (getCurrentInstance() as ComponentInternalInstance).appContext.config.globalProperties
+    const state = reactive({
+      isActive: false
+    })
+    onMounted(() => {
+    })
+    return {
+      ...toRefs(state),
+    }
+  },
+})
+</script>
+
+<style scoped lang="scss">
+.track-list {
+  .track-list-block{
+  }
+  .track-list-icon {
+    width: 44px;
+    height: 44px;
+    border-radius: 16px;
+    padding: 10px;
+    box-sizing: border-box;
+    margin-bottom: 8px;
+    background: linear-gradient(180deg, #783fd4 0%, #5634e0 100%);
+    display: flex;
+    align-items: center;
+    justify-content: center;
+    &.shadow {
+      background: rgba(255,255,255,.2);
+    }
+  }
+}
+</style>

+ 1 - 1
src/views/ship-test/index.vue

@@ -5,7 +5,7 @@
         layout="info"
         @easyMapLoad="mapLoad"
     />
-    <BusinessCom :map="map" :mapFunc="mapFunc"/>
+    <BusinessCom v-if="map" :map="map" :mapFunc="mapFunc"/>
     <div class="buttons">
       <el-button type="primary" @click="$router.push({name: '666089c1-0eac-4058-9b82-157cb4b9c277'})">配置管理</el-button>
     </div>

+ 20 - 0
src/views/ship-test/manage/source/detail.vue

@@ -76,6 +76,26 @@
             </el-tooltip>
           </template>
         </CusFormColumn>
+        <CusFormColumn
+            :span="24"
+            label="额外参数(JSON)"
+            v-model:param="cusDetail.historyWsOtherParams"
+            :rules="[
+              {
+                handle: (val) => {
+                  if (!val) {
+                    return true
+                  }
+                  try {
+                    const result = JSON.parse(val)
+                    return true
+                  } catch (e) {
+                    return false
+                  }
+                },
+                message: 'JSON格式错误'
+              }
+          ]"/>
       </CusForm>
     </div>
   </CusDialog>

+ 10 - 0
src/views/ship-test/manage/track/index.vue

@@ -56,6 +56,11 @@
             :span="12"
             label="船艏向"
             v-model:param="form.trackHeadKey"/>
+        <CusFormColumn
+            :span="12"
+            required
+            label="时间"
+            v-model:param="form.trackTimeKey"/>
         <div class="__cus-title_2">历史轨迹唯一标识(DATA.xxx形式,可任意拼接或使用字符串模板`POINT(${DATA.xxx} ${DATA.xxx})`或DATA.xxx + DATA.xxx)</div>
         <CusFormColumn
             :span="12"
@@ -86,6 +91,11 @@
             :span="12"
             label="船艏向"
             v-model:param="form.historyTrackHeadKey"/>
+        <CusFormColumn
+            :span="12"
+            required
+            label="时间"
+            v-model:param="form.historyTrackTimeKey"/>
       </CusForm>
       <div class="buttons">
         <el-button type="primary" @click="onSubmit">保存</el-button>