ship-map.ts 13 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401
  1. import {defineStore} from "pinia";
  2. import {ElMessage} from "element-plus";
  3. import {computed, reactive, toRefs} from "vue";
  4. import * as format from "ol/format";
  5. import * as layer from "ol/layer";
  6. import * as source from "ol/source";
  7. import MapStyle from "./map-style";
  8. import * as ol from "ol";
  9. import {randomColor, YMDHms} from "@/utils/util";
  10. import {formatPosition, getShapeView} from "@/utils/easyMap";
  11. import { v4 } from "uuid";
  12. export const useShipMapStore = defineStore('shipMap', () => {
  13. const state: any = reactive({
  14. warningOpen: true,
  15. warningInfo: {},
  16. map: null,
  17. mapFunc: null,
  18. zoom: 0,
  19. layerWMS: null,
  20. ws: {
  21. instance: null,
  22. layerShip: null,
  23. overlayTrack: null,
  24. },
  25. trackHoverData: null,
  26. trackMap: new Map([['123', {
  27. data: {
  28. targetSourceJson: '[]'
  29. },
  30. color: randomColor(1),
  31. history: [],
  32. real: [{}],
  33. trackId: '123',
  34. moveToTrack: () => {},
  35. // 直接用图层的话declutter: true会报错,Uncaught TypeError: Failed to execute 'clip' on 'CanvasRenderingContext2D': parameter 1 is not of type 'Path2D'.
  36. lineLayer: 'lineLayer_' + 123,
  37. pointsLayer: 'pointsLayer_' + 123,
  38. del: () => {},
  39. visibleTrack: (visible) => {},
  40. refreshTrackStyle: () => {},
  41. showTrack: true,
  42. showArchive: true,
  43. archiveLayout: {
  44. width: 480,
  45. top: 10,
  46. left: 200
  47. },
  48. archiveParams: {
  49. tab: 1,
  50. }
  51. }]])
  52. })
  53. const initMap = (map, mapFunc, {trackPointDom}) => {
  54. state.map = map
  55. state.mapFunc = mapFunc
  56. state.zoom = state.map.getView().getZoom()
  57. state.map.on('movestart', e => {
  58. map.un('pointermove', mapPointerMove)
  59. map.un('singleclick', mapSingleClick)
  60. })
  61. state.map.on('moveend', e => {
  62. state.trackHover = new Date().getTime()
  63. state.zoom = e.map.getView().getZoom()
  64. switchZoom()
  65. map.on('singleclick', mapSingleClick)
  66. map.on('pointermove', mapPointerMove)
  67. })
  68. state.ws.overlayTrack = new ol.Overlay({
  69. element: trackPointDom,
  70. autoPan: false,
  71. offset: [0, -22],
  72. positioning: 'bottom-center',
  73. stopEvent: true,
  74. })
  75. state.map.addOverlay(state.ws.overlayTrack)
  76. // 预警助手
  77. initWarningWS()
  78. }
  79. const mapPointerMove = (ev) => {
  80. let pixel = ev.pixel
  81. let feature = state.map.forEachFeatureAtPixel(pixel, function (feature) {
  82. return feature
  83. })
  84. if (feature && (feature.get('_featureType') == 'ship' || feature.get('_featureType') == 'trackPoint')) {
  85. state.map.getTargetElement().style.cursor = 'pointer'
  86. state.trackHoverData = feature.get('_data')
  87. state.ws.overlayTrack.setPosition(feature.getGeometry().getCoordinates())
  88. } else {
  89. state.map.getTargetElement().style.cursor = ''
  90. state.trackHoverData = null
  91. state.ws.overlayTrack.setPosition(undefined)
  92. }
  93. }
  94. const mapSingleClick = (ev) => {
  95. let pixel = ev.pixel
  96. let feature = state.map.forEachFeatureAtPixel(pixel, function (feature) {
  97. return feature
  98. })
  99. if (feature && feature.get('_featureType') == 'ship') {
  100. if (!state.trackMap.has(feature.get('_id'))) {
  101. const d = feature.get('_data')
  102. state.trackMap.set(feature.get('_id'), {
  103. data: feature.get('_data'),
  104. color: randomColor(1),
  105. history: [],
  106. real: [d],
  107. trackId: feature.get('_trackId'),
  108. moveToTrack: () => {
  109. const position = []
  110. const t = state.trackMap.get(feature.get('_id'))
  111. const arr = [...t.history, ...t.real]
  112. arr.forEach(v => {
  113. position.push([v.targetLongitude, v.targetLatitude])
  114. })
  115. getShapeView(state.map, position)
  116. },
  117. // 直接用图层的话declutter: true会报错,Uncaught TypeError: Failed to execute 'clip' on 'CanvasRenderingContext2D': parameter 1 is not of type 'Path2D'.
  118. lineLayer: 'lineLayer_' + feature.get('_id'),
  119. pointsLayer: 'pointsLayer_' + feature.get('_id'),
  120. del: () => {
  121. state.map.removeLayer(state.map.getLayers().getArray().filter(v => v.get('__layerName') === state.trackMap.get(feature.get('_id')).lineLayer)[0])
  122. state.map.removeLayer(state.map.getLayers().getArray().filter(v => v.get('__layerName') === state.trackMap.get(feature.get('_id')).pointsLayer)[0])
  123. state.trackMap.delete(feature.get('_id'))
  124. },
  125. visibleTrack: (visible) => {
  126. const t = state.trackMap.get(feature.get('_id'))
  127. state.map.getLayers().getArray().filter(v => v.get('__layerName') === t.lineLayer)[0]?.setVisible(visible)
  128. state.map.getLayers().getArray().filter(v => v.get('__layerName') === t.pointsLayer)[0]?.setVisible(visible)
  129. t.showTrack = visible
  130. if (visible) {
  131. t.moveToTrack()
  132. }
  133. },
  134. refreshTrackStyle: () => {
  135. const t = state.trackMap.get(feature.get('_id'))
  136. const arr = [...t.history, ...t.real]
  137. let lineWkt = ''
  138. if (arr.length > 1) {
  139. arr.forEach((v, i) => {
  140. if (i === 0) {
  141. lineWkt += `LINESTRING(${v.targetLongitude} ${v.targetLatitude}`
  142. } else if (i === arr.length - 1) {
  143. lineWkt += `,${v.targetLongitude} ${v.targetLatitude})`
  144. } else {
  145. lineWkt += `,${v.targetLongitude} ${v.targetLatitude}`
  146. }
  147. v.wkt = `POINT(${v.targetLongitude} ${v.targetLatitude})`
  148. })
  149. }
  150. if (lineWkt) {
  151. const lS = new source.Vector({
  152. features: [],
  153. wrapX: false
  154. })
  155. const lineF: any = new format.WKT().readFeature(lineWkt)
  156. lineF.set('trackPointList', arr)
  157. lineF.setStyle((f, r) => MapStyle.trackStyle(f, r, state.map, t.color, (_s, pointList) => {
  158. const pointFeatures: any = []
  159. pointList.forEach(DATA => {
  160. try {
  161. const feat: any = new format.WKT().readFeature(DATA.wkt)
  162. feat.set('_featureType', 'trackPoint')
  163. feat.set('_data', DATA)
  164. feat.setStyle(MapStyle.trackPointNormalStyle(t.color))
  165. pointFeatures.push(feat)
  166. } catch (e) {
  167. console.log(e)
  168. }
  169. })
  170. lS.clear()
  171. lS.addFeatures(pointFeatures)
  172. return _s
  173. }))
  174. state.map.getLayers().getArray().filter(v => v.get('__layerName') === t.lineLayer)[0].setSource(new source.Vector({
  175. features: [lineF],
  176. wrapX: false
  177. }))
  178. state.map.getLayers().getArray().filter(v => v.get('__layerName') === t.pointsLayer)[0].setSource(lS)
  179. }
  180. },
  181. showTrack: true,
  182. showArchive: true,
  183. archiveLayout: {
  184. width: 480,
  185. top: 10,
  186. left: state.mapFunc.mapWidth - 350 - 10
  187. },
  188. archiveParams: {
  189. tab: 1,
  190. }
  191. })
  192. const lineLayer = new layer.Vector({
  193. zIndex: 4000,
  194. __layerName: state.trackMap.get(feature.get('_id')).lineLayer
  195. })
  196. const pointsLayer = new layer.Vector({
  197. zIndex: 4100,
  198. declutter: true,
  199. __layerName: state.trackMap.get(feature.get('_id')).pointsLayer
  200. })
  201. state.map.addLayer(lineLayer)
  202. state.map.addLayer(pointsLayer)
  203. const ws = new WebSocket(`ws://${location.host}/history-track-ws-api/history-fkShips-track`)
  204. ws.onopen = (e) => {
  205. const str = {
  206. shipId: feature.get('_id'),
  207. startTime: YMDHms(new Date(d.mergeTime).getTime() - 1000 * 60 * 60),
  208. endTime: YMDHms(d.mergeTime),
  209. searchType: 1,
  210. sendInterval: 1
  211. }
  212. ws.send(JSON.stringify(str))
  213. }
  214. ws.onmessage = (e) => {
  215. try {
  216. const json = JSON.parse(e.data)
  217. if (json.message === '查询结束') {
  218. ws.close()
  219. } else if (json.data?.length > 0) {
  220. state.trackMap.get(feature.get('_id')).history.push(...json.data.map(v => ({
  221. targetName: v.shipName,
  222. mergeTarget: v.shipId,
  223. targetLongitude: v.shipLon,
  224. targetLatitude: v.shipLat,
  225. targetCourse: v.shipCourse,
  226. targetSpeed: v.shipSpeed,
  227. mergeTime: v.trackTime,
  228. targetSourceJson: v.targetSource,
  229. })))
  230. state.trackMap.get(feature.get('_id')).refreshTrackStyle()
  231. }
  232. } catch (e) {
  233. }
  234. }
  235. initWebSocket()
  236. }
  237. }
  238. }
  239. const zoomWMS = computed(() => {
  240. return state.zoom <= 14
  241. })
  242. const switchZoom = () => {
  243. if (zoomWMS.value) {
  244. if (state.ws.instance && state.trackMap.size === 0) {
  245. state.ws.instance.close()
  246. state.ws.instance = null
  247. state.map.removeLayer(state.ws.layerShip)
  248. state.ws.layerShip = null
  249. }
  250. // 瓦片
  251. initWMS()
  252. // ws
  253. if (state.trackMap.size > 0) {
  254. initWebSocket()
  255. }
  256. } else {
  257. if (state.layerWMS) {
  258. state.map.removeLayer(state.layerWMS)
  259. state.layerWMS = null
  260. }
  261. // ws
  262. initWebSocket()
  263. }
  264. }
  265. const initWMS = () => {
  266. const __flag = 'shipWMS'
  267. if (!state.layerWMS) {
  268. const _l = state.map?.getLayers().getArray().filter(v => v.get('__layerName') === __flag)
  269. if (_l?.length > 0) {
  270. _l.forEach(v => {
  271. state.map.removeLayer(v)
  272. })
  273. }
  274. const _tileWMS = new source.TileWMS({
  275. url: `/geoserver-api/geoserver/redis/wms`,
  276. params: {
  277. 'FORMAT': 'image/png8',
  278. 'VERSION': '1.1.1',
  279. LAYERS: 'redis:geo_fusion_ship',
  280. "exceptions": 'application/vnd.ogc.se_inimage',
  281. // CQL_FILTER: CQL,
  282. refresh: new Date().getTime()
  283. }
  284. })
  285. state.layerWMS = new layer.Tile({
  286. source: _tileWMS,
  287. zIndex: 3333,
  288. __layerName: __flag,
  289. })
  290. state.map.addLayer(state.layerWMS)
  291. }
  292. }
  293. const getBBOX = () => {
  294. return `BBOX(location, ${state.map.getView().calculateExtent(state.map.getSize()).join(',')})`;
  295. };
  296. const getWSParams = () => {
  297. const param = {
  298. cql: '',
  299. realCql: '',
  300. userId: "18889231165"
  301. }
  302. let idCql = ''
  303. if (state.trackMap.size > 0) {
  304. let arr = []
  305. state.trackMap.forEach((v, k) => {
  306. arr.push(`'${k}'`)
  307. })
  308. idCql = `mergeTarget in (${arr.join(',')})`
  309. }
  310. if (zoomWMS.value) {
  311. param.cql = idCql
  312. } else {
  313. param.cql = getBBOX()
  314. param.realCql = idCql
  315. }
  316. return JSON.stringify(param)
  317. }
  318. const initWebSocket = () => {
  319. if (!state.ws.instance) {
  320. state.ws.instance = new WebSocket(`ws://${location.host}/rh-ws-api/webSocket`)
  321. state.ws.instance.onopen = (e) => {
  322. state.ws.instance.send(getWSParams())
  323. }
  324. state.ws.instance.onmessage = (e) => {
  325. try {
  326. const json = JSON.parse(e.data)
  327. const shipData = json.data
  328. initShip(shipData)
  329. } catch (e) {
  330. }
  331. }
  332. } else {
  333. if (state.ws.instance.readyState == 1) {
  334. state.ws.instance.send(getWSParams())
  335. }
  336. }
  337. }
  338. const initShip = (data) => {
  339. if (!state.ws.layerShip) {
  340. state.ws.layerShip = new layer.Vector({
  341. zIndex: 4444,
  342. style: (f) => {
  343. return MapStyle.ShipNormalStyle({
  344. course: f.get('_course'),
  345. speed: f.get('_speed'),
  346. head: f.get('_head'),
  347. mergeType: f.get('_mergeType'),
  348. color: state.trackMap.get(f.get('_id'))?.color
  349. })
  350. }
  351. })
  352. state.map.addLayer(state.ws.layerShip)
  353. }
  354. // 动态拼接数据的唯一标识DATA,不可修改
  355. const features = data.map(v => {
  356. try {
  357. const feat: any = new format.WKT().readFeature(`POINT(${v.targetLongitude} ${v.targetLatitude})`)
  358. feat.set('_course', v.targetCourse || 0)
  359. feat.set('_speed', v.targetSpeed || 0)
  360. feat.set('_head', v.targetHeading)
  361. feat.set('_mergeType', v.mergeType)
  362. feat.set('_id', v.mergeTarget)
  363. feat.set('_trackId', v.mergeId)
  364. feat.set('_data', v)
  365. feat.set('_featureType', 'ship')
  366. // 实时轨迹
  367. const t = state.trackMap.get(feat.get('_id'))
  368. if (t && t.trackId !== feat.get('_trackId')) {
  369. t.real.push(feat.get('_data'))
  370. t.trackId = feat.get('_trackId')
  371. t.data = feat.get('_data')
  372. t.refreshTrackStyle()
  373. }
  374. return feat
  375. } catch (e) {
  376. console.log(e)
  377. }
  378. })
  379. const vectorSource = new source.Vector({
  380. features: features,
  381. wrapX: false
  382. });
  383. state.ws.layerShip.setSource(vectorSource)
  384. }
  385. const mockWarningWS = () => {
  386. state.warningInfo = {
  387. name: v4(),
  388. time: new Date().getTime(),
  389. }
  390. }
  391. const initWarningWS = () => {
  392. }
  393. return {
  394. ...toRefs(state),
  395. initMap,
  396. mockWarningWS,
  397. }
  398. })