ship-map.ts 12 KB

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