|
@@ -1,5 +1,11 @@
|
|
|
<template>
|
|
<template>
|
|
|
- <div id="tracking-map" style="width: 100%; height: 520px" class="tracking-map"></div>
|
|
|
|
|
|
|
+ <div
|
|
|
|
|
+ id="tracking-map"
|
|
|
|
|
+ ref="mapContainer"
|
|
|
|
|
+ style="width: 100%; height: 520px"
|
|
|
|
|
+ class="tracking-map"
|
|
|
|
|
+ :class="{ 'dark-mode': themeStore.theme === 'dark' }"
|
|
|
|
|
+ ></div>
|
|
|
</template>
|
|
</template>
|
|
|
<script setup lang="ts">
|
|
<script setup lang="ts">
|
|
|
import L from 'leaflet'
|
|
import L from 'leaflet'
|
|
@@ -8,6 +14,10 @@ import OriginIcon from '../images/originIcon.png'
|
|
|
import TransferIcon from '../images/transferIcon.png'
|
|
import TransferIcon from '../images/transferIcon.png'
|
|
|
import { onMounted, ref, watch } from 'vue'
|
|
import { onMounted, ref, watch } from 'vue'
|
|
|
import * as turf from '@turf/turf'
|
|
import * as turf from '@turf/turf'
|
|
|
|
|
+import { mapData } from './mapData'
|
|
|
|
|
+import { useThemeStore } from '@/stores/modules/theme'
|
|
|
|
|
+
|
|
|
|
|
+const themeStore = useThemeStore()
|
|
|
|
|
|
|
|
const props = defineProps<{
|
|
const props = defineProps<{
|
|
|
serial_no?: string
|
|
serial_no?: string
|
|
@@ -45,7 +55,7 @@ const initMap = () => {
|
|
|
return
|
|
return
|
|
|
}
|
|
}
|
|
|
|
|
|
|
|
- map = L.map('tracking-map').setView([51.505, -0.09], 3)
|
|
|
|
|
|
|
+ map = L.map('tracking-map', {}).setView([51.505, -0.09], 3)
|
|
|
|
|
|
|
|
// 添加 TileLayer
|
|
// 添加 TileLayer
|
|
|
L.tileLayer('https://map.kerryapex.com/osm_tiles/{z}/{x}/{y}.png', {
|
|
L.tileLayer('https://map.kerryapex.com/osm_tiles/{z}/{x}/{y}.png', {
|
|
@@ -54,8 +64,6 @@ const initMap = () => {
|
|
|
}).addTo(map)
|
|
}).addTo(map)
|
|
|
}
|
|
}
|
|
|
|
|
|
|
|
-const track_base_line = ref([])
|
|
|
|
|
-const track_first_pt = null
|
|
|
|
|
// 修正经度
|
|
// 修正经度
|
|
|
const fixLng = (lng: number) => {
|
|
const fixLng = (lng: number) => {
|
|
|
while (lng > 180) lng = lng - 360
|
|
while (lng > 180) lng = lng - 360
|
|
@@ -121,74 +129,84 @@ const getBBox = () => {
|
|
|
if (bbox3.length > 0) ll.push(bbox3)
|
|
if (bbox3.length > 0) ll.push(bbox3)
|
|
|
return ll
|
|
return ll
|
|
|
}
|
|
}
|
|
|
-const draw_marker = () => {
|
|
|
|
|
- track_base_line.value.forEach((l) => {
|
|
|
|
|
- addMapLine(l, true, false, { color: '#ff7500', weight: 2 })
|
|
|
|
|
|
|
+let track_added_marker = []
|
|
|
|
|
+
|
|
|
|
|
+const clear_marker = () => {
|
|
|
|
|
+ track_added_marker.forEach((v) => {
|
|
|
|
|
+ map!.removeLayer(v)
|
|
|
})
|
|
})
|
|
|
|
|
+
|
|
|
|
|
+ track_added_marker = []
|
|
|
}
|
|
}
|
|
|
-const addMapLine = (l, IsDash, HasArrow, opts) => {
|
|
|
|
|
|
|
+const draw_marker = (dottedLine = [], solidLine = []) => {
|
|
|
|
|
+ clear_marker()
|
|
|
|
|
+ dottedLine.forEach((l) => {
|
|
|
|
|
+ addMapLine(l, true, { color: '#ff7500', weight: 2 })
|
|
|
|
|
+ })
|
|
|
|
|
+ solidLine.forEach((l) => {
|
|
|
|
|
+ addMapLine(l, false, { color: '#ff7500', weight: 2 })
|
|
|
|
|
+ })
|
|
|
|
|
+}
|
|
|
|
|
+const addMapLine = (l, IsDash, opts) => {
|
|
|
let mpts = l.pts
|
|
let mpts = l.pts
|
|
|
if (mpts == null || mpts.length == 0) return
|
|
if (mpts == null || mpts.length == 0) return
|
|
|
|
|
|
|
|
let bnd = map.getBounds()
|
|
let bnd = map.getBounds()
|
|
|
let ww = bnd.getEast() - bnd.getWest()
|
|
let ww = bnd.getEast() - bnd.getWest()
|
|
|
let cc = Math.ceil(ww / 360)
|
|
let cc = Math.ceil(ww / 360)
|
|
|
- // let cent_pt = map.getCenter()
|
|
|
|
|
- // let count = Math.ceil(cent_pt.lng / 360)
|
|
|
|
|
-
|
|
|
|
|
- // let boxlist = getBBox()
|
|
|
|
|
-
|
|
|
|
|
- // let ll = []
|
|
|
|
|
-
|
|
|
|
|
- // for (let ii = 0; ii < mpts.length; ii++) {
|
|
|
|
|
- // ll[ii] = [mpts[ii][1], mpts[ii][0]]
|
|
|
|
|
- // }
|
|
|
|
|
- // let pline = turf.lineString(ll, { name: '' })
|
|
|
|
|
- // let level = map.getZoom()
|
|
|
|
|
- // let options = {
|
|
|
|
|
- // tolerance:
|
|
|
|
|
- // Math.round(
|
|
|
|
|
- // ((level < 8 ? 0.0005 : level < 12 ? 0.00005 : 0) / (level == 0 ? 1 : level)) * 1000000
|
|
|
|
|
- // ) / 1000000,
|
|
|
|
|
- // highQuality: false
|
|
|
|
|
- // }
|
|
|
|
|
- // let simplified = turf.simplify(pline, options)
|
|
|
|
|
-
|
|
|
|
|
- // let lines_list = []
|
|
|
|
|
- // let clipped: any = simplified
|
|
|
|
|
- // boxlist.forEach((bbox) => {
|
|
|
|
|
- // let bb = bbox
|
|
|
|
|
- // clipped = turf.bboxClip(simplified, bb)
|
|
|
|
|
- // if (clipped != null) {
|
|
|
|
|
- // if (clipped.geometry.type === 'LineString') {
|
|
|
|
|
- // let line_pts = clipped.geometry.coordinates
|
|
|
|
|
- // let pta = []
|
|
|
|
|
- // let jj = 0
|
|
|
|
|
- // for (let ii = 0; ii < line_pts.length; ii++) {
|
|
|
|
|
- // pta[jj++] = [line_pts[ii][1], line_pts[ii][0]]
|
|
|
|
|
- // }
|
|
|
|
|
- // lines_list.push(pta)
|
|
|
|
|
- // } else if (clipped.geometry.type === 'MultiLineString') {
|
|
|
|
|
- // clipped.geometry.coordinates.forEach((_pts) => {
|
|
|
|
|
- // let line_pts = _pts
|
|
|
|
|
- // let pta = []
|
|
|
|
|
- // let jj = 0
|
|
|
|
|
- // for (let ii = 0; ii < line_pts.length; ii++) {
|
|
|
|
|
- // pta[jj++] = [line_pts[ii][1], line_pts[ii][0]]
|
|
|
|
|
- // }
|
|
|
|
|
- // lines_list.push(pta)
|
|
|
|
|
- // })
|
|
|
|
|
- // }
|
|
|
|
|
- // }
|
|
|
|
|
- // })
|
|
|
|
|
|
|
+
|
|
|
|
|
+ let boxlist = getBBox()
|
|
|
|
|
+
|
|
|
|
|
+ let ll = []
|
|
|
|
|
+
|
|
|
|
|
+ for (let ii = 0; ii < mpts.length; ii++) {
|
|
|
|
|
+ ll[ii] = [mpts[ii][1], mpts[ii][0]]
|
|
|
|
|
+ }
|
|
|
|
|
+ let pline = turf.lineString(ll, { name: '' })
|
|
|
|
|
+ let level = map.getZoom()
|
|
|
|
|
+ let options = {
|
|
|
|
|
+ tolerance:
|
|
|
|
|
+ Math.round(
|
|
|
|
|
+ ((level < 8 ? 0.0005 : level < 12 ? 0.00005 : 0) / (level == 0 ? 1 : level)) * 1000000
|
|
|
|
|
+ ) / 1000000,
|
|
|
|
|
+ highQuality: false
|
|
|
|
|
+ }
|
|
|
|
|
+ let simplified = turf.simplify(pline, options)
|
|
|
|
|
+
|
|
|
|
|
+ let lines_list = []
|
|
|
|
|
+ let clipped: any = simplified
|
|
|
|
|
+ boxlist.forEach((bbox) => {
|
|
|
|
|
+ let bb = bbox
|
|
|
|
|
+ clipped = turf.bboxClip(simplified, bb)
|
|
|
|
|
+ if (clipped != null) {
|
|
|
|
|
+ if (clipped.geometry.type === 'LineString') {
|
|
|
|
|
+ let line_pts = clipped.geometry.coordinates
|
|
|
|
|
+ let pta = []
|
|
|
|
|
+ let jj = 0
|
|
|
|
|
+ for (let ii = 0; ii < line_pts.length; ii++) {
|
|
|
|
|
+ pta[jj++] = [line_pts[ii][1], line_pts[ii][0]]
|
|
|
|
|
+ }
|
|
|
|
|
+ lines_list.push(pta)
|
|
|
|
|
+ } else if (clipped.geometry.type === 'MultiLineString') {
|
|
|
|
|
+ clipped.geometry.coordinates.forEach((_pts) => {
|
|
|
|
|
+ let line_pts = _pts
|
|
|
|
|
+ let pta = []
|
|
|
|
|
+ let jj = 0
|
|
|
|
|
+ for (let ii = 0; ii < line_pts.length; ii++) {
|
|
|
|
|
+ pta[jj++] = [line_pts[ii][1], line_pts[ii][0]]
|
|
|
|
|
+ }
|
|
|
|
|
+ lines_list.push(pta)
|
|
|
|
|
+ })
|
|
|
|
|
+ }
|
|
|
|
|
+ }
|
|
|
|
|
+ })
|
|
|
|
|
|
|
|
let cc1 = Math.floor(bnd.getWest() / 360)
|
|
let cc1 = Math.floor(bnd.getWest() / 360)
|
|
|
let cc2 = Math.ceil(bnd.getEast() / 360)
|
|
let cc2 = Math.ceil(bnd.getEast() / 360)
|
|
|
cc = cc2 - cc1 + 1
|
|
cc = cc2 - cc1 + 1
|
|
|
|
|
|
|
|
let kk = cc1
|
|
let kk = cc1
|
|
|
- // let llIndex = 0
|
|
|
|
|
- let lines_list = [mpts]
|
|
|
|
|
|
|
+ // let lines_list = [mpts]
|
|
|
while (kk >= cc1 && kk <= cc2 && cc > 0) {
|
|
while (kk >= cc1 && kk <= cc2 && cc > 0) {
|
|
|
lines_list.forEach((a) => {
|
|
lines_list.forEach((a) => {
|
|
|
let ii = 0
|
|
let ii = 0
|
|
@@ -197,16 +215,15 @@ const addMapLine = (l, IsDash, HasArrow, opts) => {
|
|
|
for (ii = 0; ii < a.length; ii++) pts[jj++] = [a[ii][0], a[ii][1] + kk * 360]
|
|
for (ii = 0; ii < a.length; ii++) pts[jj++] = [a[ii][0], a[ii][1] + kk * 360]
|
|
|
|
|
|
|
|
if (jj > 0) {
|
|
if (jj > 0) {
|
|
|
- if (IsDash)
|
|
|
|
|
|
|
+ if (IsDash) {
|
|
|
showTrackLine(
|
|
showTrackLine(
|
|
|
pts,
|
|
pts,
|
|
|
jj,
|
|
jj,
|
|
|
- Object.assign({}, { color: '#ff7500', dashArray: '10', weight: 2 }, opts),
|
|
|
|
|
- HasArrow
|
|
|
|
|
|
|
+ Object.assign({}, { color: '#ff7500', dashArray: '10', weight: 2 }, opts)
|
|
|
)
|
|
)
|
|
|
- else
|
|
|
|
|
- showTrackLine(pts, jj, Object.assign({}, { color: '#ff7500', weight: 1 }, opts), HasArrow)
|
|
|
|
|
- // llIndex++
|
|
|
|
|
|
|
+ } else {
|
|
|
|
|
+ showTrackLine(pts, jj, Object.assign({}, { color: '#ff7500', weight: 1 }, opts))
|
|
|
|
|
+ }
|
|
|
}
|
|
}
|
|
|
})
|
|
})
|
|
|
|
|
|
|
@@ -215,34 +232,9 @@ const addMapLine = (l, IsDash, HasArrow, opts) => {
|
|
|
}
|
|
}
|
|
|
}
|
|
}
|
|
|
|
|
|
|
|
-const showTrackLine = (pts, jj, opts, HasArrow) => {
|
|
|
|
|
|
|
+const showTrackLine = (pts, jj, opts) => {
|
|
|
let arrow = L.polyline(pts, Object.assign({}, { color: '#ff7500', weight: 2 }, opts)).addTo(map)
|
|
let arrow = L.polyline(pts, Object.assign({}, { color: '#ff7500', weight: 2 }, opts)).addTo(map)
|
|
|
-
|
|
|
|
|
- // if (HasArrow) {
|
|
|
|
|
- // let arrowHead = L.polylineDecorator(arrow, {
|
|
|
|
|
- // patterns: [
|
|
|
|
|
- // {
|
|
|
|
|
- // offset: 0,
|
|
|
|
|
- // repeat: 25,
|
|
|
|
|
- // symbol: L.Symbol.arrowHead({
|
|
|
|
|
- // pixelSize: 10,
|
|
|
|
|
- // pathOptions: Object.assign({}, { color: '#ff7500', fillOpacity: 0.5, weight: 0.8 }, opts)
|
|
|
|
|
- // })
|
|
|
|
|
- // }
|
|
|
|
|
- // ]
|
|
|
|
|
- // }).addTo(map)
|
|
|
|
|
- // }
|
|
|
|
|
-}
|
|
|
|
|
-
|
|
|
|
|
-const test = () => {
|
|
|
|
|
- track_base_line.value = mapData
|
|
|
|
|
- draw_marker()
|
|
|
|
|
- // map.on('moveend', function (e) {
|
|
|
|
|
- // draw_marker()
|
|
|
|
|
- // })
|
|
|
|
|
- // map.on('zoomend', function (e) {
|
|
|
|
|
- // draw_marker()
|
|
|
|
|
- // })
|
|
|
|
|
|
|
+ track_added_marker.push(arrow)
|
|
|
}
|
|
}
|
|
|
|
|
|
|
|
const addResetZoomButton = (center: L.LatLng, zoom: number) => {
|
|
const addResetZoomButton = (center: L.LatLng, zoom: number) => {
|
|
@@ -280,10 +272,14 @@ let initialCenter: L.LatLng | null = null
|
|
|
let initialZoomLevel: number | null = null
|
|
let initialZoomLevel: number | null = null
|
|
|
let isFirstRender = true // 标记是否为首次渲染
|
|
let isFirstRender = true // 标记是否为首次渲染
|
|
|
|
|
|
|
|
|
|
+let allMarkers = []
|
|
|
|
|
+let visibleMarkers = new Set()
|
|
|
|
|
+
|
|
|
// 添加标记后更新中心和缩放级别
|
|
// 添加标记后更新中心和缩放级别
|
|
|
const addMarkersToMap = () => {
|
|
const addMarkersToMap = () => {
|
|
|
|
|
+ // debugger
|
|
|
if (!map) return // 确保地图已经初始化
|
|
if (!map) return // 确保地图已经初始化
|
|
|
- const latLngBounds: any = [] // 用来存储所有标记的坐标
|
|
|
|
|
|
|
+
|
|
|
markerPositions.value.forEach((position) => {
|
|
markerPositions.value.forEach((position) => {
|
|
|
const marker = L.marker([position.lat, position.lng], { icon: position.icon }).addTo(map)
|
|
const marker = L.marker([position.lat, position.lng], { icon: position.icon }).addTo(map)
|
|
|
|
|
|
|
@@ -303,11 +299,14 @@ const addMarkersToMap = () => {
|
|
|
closeOnClick: false
|
|
closeOnClick: false
|
|
|
})
|
|
})
|
|
|
.openPopup()
|
|
.openPopup()
|
|
|
- latLngBounds.push([position.lat, position.lng])
|
|
|
|
|
|
|
+ allMarkers[`${position.lat},${position.lng}`] = marker
|
|
|
})
|
|
})
|
|
|
|
|
|
|
|
- if (latLngBounds.length > 0) {
|
|
|
|
|
- const bounds = L.latLngBounds(latLngBounds)
|
|
|
|
|
|
|
+ updateVisibleMarkers()
|
|
|
|
|
+
|
|
|
|
|
+ if (viewData.value.length > 0) {
|
|
|
|
|
+ // 根据标记的位置设置中心点以及缩放级别
|
|
|
|
|
+ const bounds = L.latLngBounds(viewData.value)
|
|
|
map!.fitBounds(bounds, { paddingTopLeft: [20, 70], paddingBottomRight: [400, 0] })
|
|
map!.fitBounds(bounds, { paddingTopLeft: [20, 70], paddingBottomRight: [400, 0] })
|
|
|
setTimeout(() => {
|
|
setTimeout(() => {
|
|
|
if (isFirstRender) {
|
|
if (isFirstRender) {
|
|
@@ -320,6 +319,153 @@ const addMarkersToMap = () => {
|
|
|
}
|
|
}
|
|
|
}
|
|
}
|
|
|
|
|
|
|
|
|
|
+// 新增轮船当前位置标记
|
|
|
|
|
+const addShipMarker = (x: number) => {
|
|
|
|
|
+ const solidLine = allMapData.value.solidLine
|
|
|
|
|
+ // 如果轮船还未出发,则显示起点轮船标记
|
|
|
|
|
+ if (solidLine.length === 0) {
|
|
|
|
|
+ // 创建轮船图标
|
|
|
|
|
+ const arrowIcon = L.divIcon({
|
|
|
|
|
+ html: `
|
|
|
|
|
+ <div class="container">
|
|
|
|
|
+ <div class="circle"></div>
|
|
|
|
|
+ <span style="padding: 0; color:white; border:1px solid white" class="font_family icon-icon_ocean_b"></span>
|
|
|
|
|
+ </div>
|
|
|
|
|
+ `,
|
|
|
|
|
+ className: 'arrow-icon',
|
|
|
|
|
+ iconSize: [50, 50],
|
|
|
|
|
+ iconAnchor: [25, 25], // 箭头的中心点
|
|
|
|
|
+ popupAnchor: [0, -25] // 弹出框的锚点
|
|
|
|
|
+ })
|
|
|
|
|
+
|
|
|
|
|
+ let curMarkerLocation = markerPositions.value.find((item) => item.label === 'Origin')
|
|
|
|
|
+ const arrowMarker = L.marker([curMarkerLocation.lat, curMarkerLocation.lng + x * 360], {
|
|
|
|
|
+ icon: arrowIcon
|
|
|
|
|
+ }).addTo(map)
|
|
|
|
|
+ track_added_marker.push(arrowMarker)
|
|
|
|
|
+ } else if (solidLine.length > 0) {
|
|
|
|
|
+ // 如果轮船已经出发,则显示轮船当前位置标记
|
|
|
|
|
+ // 如果线段至少有两个点,才添加箭头
|
|
|
|
|
+ // 获取线段的最后一个点和倒数第二个点
|
|
|
|
|
+ const lastPoint = solidLine[solidLine.length - 1]
|
|
|
|
|
+ const secondLastPoint = solidLine[solidLine.length - 2]
|
|
|
|
|
+ console.log(lastPoint, secondLastPoint, 'lastPoint, secondLastPoint')
|
|
|
|
|
+ // 计算线段末端的角度(以弧度为单位)
|
|
|
|
|
+ const angle =
|
|
|
|
|
+ (Math.atan2(
|
|
|
|
|
+ Number(lastPoint.lon) - Number(secondLastPoint.lon), // Δlon (x)
|
|
|
|
|
+ Number(lastPoint.lat) - Number(secondLastPoint.lat) // Δlat (y)
|
|
|
|
|
+ ) *
|
|
|
|
|
+ (180 / Math.PI) +
|
|
|
|
|
+ 360) %
|
|
|
|
|
+ 360
|
|
|
|
|
+ // 创建自定义箭头图标
|
|
|
|
|
+ const arrowIcon = L.divIcon({
|
|
|
|
|
+ html: `
|
|
|
|
|
+ <div style="transform: rotate(${angle}deg);" class="container">
|
|
|
|
|
+ <div class="circle"></div>
|
|
|
|
|
+ <span style="color:white;border:1px solid white" class="font_family icon-icon_arrow_b"></span>
|
|
|
|
|
+ </div>
|
|
|
|
|
+ `,
|
|
|
|
|
+ className: 'arrow-icon',
|
|
|
|
|
+ iconSize: [50, 50],
|
|
|
|
|
+ iconAnchor: [25, 25], // 箭头的中心点
|
|
|
|
|
+ popupAnchor: [0, -25] // 弹出框的锚点
|
|
|
|
|
+ })
|
|
|
|
|
+ // 创建箭头标记,并根据计算出的角度旋转箭头
|
|
|
|
|
+ const arrowMarker = L.marker([Number(lastPoint.lat), Number(lastPoint.lon) + x * 360], {
|
|
|
|
|
+ icon: arrowIcon
|
|
|
|
|
+ }).addTo(map)
|
|
|
|
|
+ // 将箭头标记也存储在 track_added_marker 数组中,以便后续管理
|
|
|
|
|
+ track_added_marker.push(arrowMarker)
|
|
|
|
|
+ }
|
|
|
|
|
+}
|
|
|
|
|
+
|
|
|
|
|
+// 更新可见标记
|
|
|
|
|
+const updateVisibleMarkers = () => {
|
|
|
|
|
+ const newVisibleMarkers = new Set()
|
|
|
|
|
+
|
|
|
|
|
+ let bnd = map.getBounds()
|
|
|
|
|
+ let ww = bnd.getEast() - bnd.getWest()
|
|
|
|
|
+
|
|
|
|
|
+ let cc = Math.ceil(ww / 360)
|
|
|
|
|
+
|
|
|
|
|
+ let cc1 = Math.floor(bnd.getWest() / 360)
|
|
|
|
|
+ let cc2 = Math.ceil(bnd.getEast() / 360)
|
|
|
|
|
+ cc = cc2 - cc1 + 1
|
|
|
|
|
+
|
|
|
|
|
+ let x = cc1
|
|
|
|
|
+
|
|
|
|
|
+ // 移除不再可见的标记
|
|
|
|
|
+ visibleMarkers.forEach((marker: any) => {
|
|
|
|
|
+ if (!newVisibleMarkers.has(marker)) {
|
|
|
|
|
+ map.removeLayer(marker)
|
|
|
|
|
+ delete allMarkers[`${marker.getLatLng().lat},${marker._lng},${marker._x}`]
|
|
|
|
|
+ }
|
|
|
|
|
+ })
|
|
|
|
|
+
|
|
|
|
|
+ // 计算当前视图中的标记,包括多地球的情况
|
|
|
|
|
+ while (x >= cc1 && x <= cc2 && cc > 0) {
|
|
|
|
|
+ Object.values(allMarkers).forEach((marker) => {
|
|
|
|
|
+ const latLng = marker.getLatLng()
|
|
|
|
|
+ const key = `${latLng.lat},${latLng.lng},${x}`
|
|
|
|
|
+ if (!allMarkers[key]) {
|
|
|
|
|
+ const newMarker: any = L.marker([latLng.lat, latLng.lng + x * 360], {
|
|
|
|
|
+ icon: marker.options.icon
|
|
|
|
|
+ })
|
|
|
|
|
+ .bindPopup(marker.getPopup().getContent(), marker.getPopup().options)
|
|
|
|
|
+ .openPopup()
|
|
|
|
|
+ newMarker._x = x
|
|
|
|
|
+ // 使用原始的经度作为标记的唯一标识
|
|
|
|
|
+ newMarker._lng = latLng.lng
|
|
|
|
|
+ allMarkers[key] = newMarker
|
|
|
|
|
+ map.addLayer(newMarker)
|
|
|
|
|
+ }
|
|
|
|
|
+ newVisibleMarkers.add(allMarkers[key])
|
|
|
|
|
+ })
|
|
|
|
|
+
|
|
|
|
|
+ addShipMarker(x)
|
|
|
|
|
+
|
|
|
|
|
+ x++
|
|
|
|
|
+ cc--
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ // 更新可见标记集合
|
|
|
|
|
+ visibleMarkers = newVisibleMarkers
|
|
|
|
|
+}
|
|
|
|
|
+
|
|
|
|
|
+// 处理得到的数据
|
|
|
|
|
+const handleData = (data) => {
|
|
|
|
|
+ let key = 0
|
|
|
|
|
+ let curLine = []
|
|
|
|
|
+ let resultLine = []
|
|
|
|
|
+ data.forEach((item, index) => {
|
|
|
|
|
+ if (item.sn === '1' && key === 0) {
|
|
|
|
|
+ key++
|
|
|
|
|
+ curLine.push([Number(item.lat), Number(item.lon)])
|
|
|
|
|
+ } else if (item.sn === '1' && key !== 0) {
|
|
|
|
|
+ resultLine.push({
|
|
|
|
|
+ name: key,
|
|
|
|
|
+ pts: curLine
|
|
|
|
|
+ })
|
|
|
|
|
+ curLine = [[Number(item.lat), Number(item.lon)]]
|
|
|
|
|
+ key++
|
|
|
|
|
+ }
|
|
|
|
|
+ if (item.sn !== '1') {
|
|
|
|
|
+ curLine.push([Number(item.lat), Number(item.lon)])
|
|
|
|
|
+ }
|
|
|
|
|
+ if (index === data.length - 1 && item.sn !== '1') {
|
|
|
|
|
+ resultLine.push({
|
|
|
|
|
+ name: key,
|
|
|
|
|
+ pts: curLine
|
|
|
|
|
+ })
|
|
|
|
|
+ }
|
|
|
|
|
+ })
|
|
|
|
|
+ return resultLine
|
|
|
|
|
+}
|
|
|
|
|
+
|
|
|
|
|
+const allMapData = ref()
|
|
|
|
|
+const viewData = ref([])
|
|
|
// 请求接口并处理标记
|
|
// 请求接口并处理标记
|
|
|
const getMarker = () => {
|
|
const getMarker = () => {
|
|
|
$api
|
|
$api
|
|
@@ -329,9 +475,10 @@ const getMarker = () => {
|
|
|
})
|
|
})
|
|
|
.then((res) => {
|
|
.then((res) => {
|
|
|
if (res.code === 200) {
|
|
if (res.code === 200) {
|
|
|
|
|
+ allMapData.value = res.data
|
|
|
const { data } = res
|
|
const { data } = res
|
|
|
- data &&
|
|
|
|
|
- data.forEach((item) => {
|
|
|
|
|
|
|
+ data?.point &&
|
|
|
|
|
+ data?.point.forEach((item) => {
|
|
|
const iconColorList = {
|
|
const iconColorList = {
|
|
|
Destination: { color: '#24ca5a', icon: destinationIcon },
|
|
Destination: { color: '#24ca5a', icon: destinationIcon },
|
|
|
Origin: { color: '#ED6D00', icon: originIcon },
|
|
Origin: { color: '#ED6D00', icon: originIcon },
|
|
@@ -339,15 +486,31 @@ const getMarker = () => {
|
|
|
}
|
|
}
|
|
|
markerPositions.value.push({
|
|
markerPositions.value.push({
|
|
|
lat: item.lat,
|
|
lat: item.lat,
|
|
|
- lng: item.lng,
|
|
|
|
|
|
|
+ lng: (Number(item.lng) + 360) % 360,
|
|
|
city: item.infor,
|
|
city: item.infor,
|
|
|
label: item.label,
|
|
label: item.label,
|
|
|
icon: iconColorList[item.label].icon,
|
|
icon: iconColorList[item.label].icon,
|
|
|
iconColor: iconColorList[item.label].color
|
|
iconColor: iconColorList[item.label].color
|
|
|
})
|
|
})
|
|
|
})
|
|
})
|
|
|
|
|
+ viewData.value = (data?.rangePoint.length > 0 ? data?.rangePoint : data?.point)?.map(
|
|
|
|
|
+ (item) => {
|
|
|
|
|
+ return [Number(item.lat), (Number(item.lon || item.lng) + 360) % 360]
|
|
|
|
|
+ }
|
|
|
|
|
+ )
|
|
|
// 请求成功后添加标记,并动态添加重置按钮
|
|
// 请求成功后添加标记,并动态添加重置按钮
|
|
|
addMarkersToMap()
|
|
addMarkersToMap()
|
|
|
|
|
+ if (data?.dottedLine) {
|
|
|
|
|
+ draw_marker(handleData(data.dottedLine), handleData(data.solidLine))
|
|
|
|
|
+ map.on('moveend', function () {
|
|
|
|
|
+ draw_marker(handleData(data.dottedLine), handleData(data.solidLine))
|
|
|
|
|
+ updateVisibleMarkers()
|
|
|
|
|
+ })
|
|
|
|
|
+ map.on('zoomend', function () {
|
|
|
|
|
+ draw_marker(handleData(data.dottedLine), handleData(data.solidLine))
|
|
|
|
|
+ updateVisibleMarkers()
|
|
|
|
|
+ })
|
|
|
|
|
+ }
|
|
|
}
|
|
}
|
|
|
})
|
|
})
|
|
|
}
|
|
}
|
|
@@ -367,6 +530,10 @@ watch(
|
|
|
onMounted(() => {
|
|
onMounted(() => {
|
|
|
initMap() // 初始化地图,不加标记
|
|
initMap() // 初始化地图,不加标记
|
|
|
})
|
|
})
|
|
|
|
|
+
|
|
|
|
|
+onUnmounted(() => {
|
|
|
|
|
+ map?.remove()
|
|
|
|
|
+})
|
|
|
</script>
|
|
</script>
|
|
|
|
|
|
|
|
<style lang="scss">
|
|
<style lang="scss">
|
|
@@ -387,12 +554,13 @@ onMounted(() => {
|
|
|
margin-left: -2px;
|
|
margin-left: -2px;
|
|
|
font-size: 12px;
|
|
font-size: 12px;
|
|
|
font-weight: 700;
|
|
font-weight: 700;
|
|
|
|
|
+ color: #2b2f36;
|
|
|
}
|
|
}
|
|
|
}
|
|
}
|
|
|
.label {
|
|
.label {
|
|
|
margin-bottom: 4px;
|
|
margin-bottom: 4px;
|
|
|
font-size: 12px;
|
|
font-size: 12px;
|
|
|
- color: let(--color-neutral-2);
|
|
|
|
|
|
|
+ color: #646a73;
|
|
|
}
|
|
}
|
|
|
}
|
|
}
|
|
|
}
|
|
}
|
|
@@ -401,19 +569,91 @@ onMounted(() => {
|
|
|
.leaflet-popup-tip {
|
|
.leaflet-popup-tip {
|
|
|
display: none;
|
|
display: none;
|
|
|
}
|
|
}
|
|
|
- .transport-map {
|
|
|
|
|
- .leaflet-touch {
|
|
|
|
|
- .leaflet-bar {
|
|
|
|
|
- border: 0;
|
|
|
|
|
- border-radius: 4px;
|
|
|
|
|
- overflow: hidden;
|
|
|
|
|
|
|
+
|
|
|
|
|
+ .leaflet-control-zoom {
|
|
|
|
|
+ span {
|
|
|
|
|
+ color: #2b2f36;
|
|
|
|
|
+ }
|
|
|
|
|
+ }
|
|
|
|
|
+}
|
|
|
|
|
+.leaflet-control {
|
|
|
|
|
+ a.leaflet-control-zoom-in,
|
|
|
|
|
+ a.leaflet-control-zoom-out,
|
|
|
|
|
+ a.leaflet-bar-part {
|
|
|
|
|
+ width: 26px;
|
|
|
|
|
+ height: 26px;
|
|
|
|
|
+ font-size: 18px;
|
|
|
|
|
+ }
|
|
|
|
|
+}
|
|
|
|
|
+.dark-mode {
|
|
|
|
|
+ div.leaflet-control-zoom.leaflet-bar.leaflet-control {
|
|
|
|
|
+ border: 0;
|
|
|
|
|
+ border-radius: 4px;
|
|
|
|
|
+ box-shadow: none;
|
|
|
|
|
+ a {
|
|
|
|
|
+ background-color: #3c414a;
|
|
|
|
|
+ border-bottom: none;
|
|
|
|
|
+ span {
|
|
|
|
|
+ color: var(--color-neutral-1);
|
|
|
|
|
+ }
|
|
|
|
|
+ &:first-child {
|
|
|
|
|
+ span {
|
|
|
|
|
+ display: inline-block;
|
|
|
|
|
+ width: 24px;
|
|
|
|
|
+ border-bottom: 2px solid #575c64;
|
|
|
|
|
+ }
|
|
|
}
|
|
}
|
|
|
}
|
|
}
|
|
|
}
|
|
}
|
|
|
|
|
+ .reset-zoom-control {
|
|
|
|
|
+ border: none;
|
|
|
|
|
+ background-color: #3c414a;
|
|
|
|
|
+ }
|
|
|
|
|
+ a.leaflet-bar-part {
|
|
|
|
|
+ background-color: #3c414a;
|
|
|
|
|
+ border-radius: 4px;
|
|
|
|
|
+ box-shadow: none;
|
|
|
|
|
+ overflow: hidden;
|
|
|
|
|
+ div {
|
|
|
|
|
+ border-color: var(--color-neutral-1) !important;
|
|
|
|
|
+ div {
|
|
|
|
|
+ background-color: var(--color-neutral-1) !important;
|
|
|
|
|
+ }
|
|
|
|
|
+ }
|
|
|
|
|
+ }
|
|
|
|
|
+}
|
|
|
|
|
+
|
|
|
|
|
+/* 示例:将所有地图图片的颜色反转 */
|
|
|
|
|
+.dark-mode img:not(.leaflet-marker-icon) {
|
|
|
|
|
+ filter: invert(1) hue-rotate(170deg);
|
|
|
|
|
+}
|
|
|
|
|
+// 防止暗黑模式下地图超出容器
|
|
|
|
|
+.tracking-map {
|
|
|
|
|
+ overflow: hidden;
|
|
|
|
|
+}
|
|
|
|
|
+// 修改暗黑模式下的背景色
|
|
|
|
|
+.leaflet-container.dark-mode,
|
|
|
|
|
+.leaflet-map-pane.dark-mode,
|
|
|
|
|
+.leaflet-tile-container.dark-mode {
|
|
|
|
|
+ background-color: #2b2f36;
|
|
|
|
|
+}
|
|
|
|
|
+// 处理版权信息在切换模式后样式错误bug
|
|
|
|
|
+.leaflet-right .leaflet-control-attribution {
|
|
|
|
|
+ background: rgba(255, 255, 255, 0.8);
|
|
|
|
|
+ margin: 0 4px 4px 0;
|
|
|
|
|
+ color: #2b2f36;
|
|
|
|
|
+ span {
|
|
|
|
|
+ font-size: 12px;
|
|
|
|
|
+ color: #2b2f36;
|
|
|
|
|
+ }
|
|
|
|
|
+ a {
|
|
|
|
|
+ color: #0078a8;
|
|
|
|
|
+ font-size: 12px;
|
|
|
|
|
+ }
|
|
|
}
|
|
}
|
|
|
/* 自定义重置缩放按钮控件样式 */
|
|
/* 自定义重置缩放按钮控件样式 */
|
|
|
.reset-zoom-control {
|
|
.reset-zoom-control {
|
|
|
- margin-top: 10px; /* 增加上边距,使按钮与默认缩放按钮之间有间距 */
|
|
|
|
|
|
|
+ margin-top: 10px;
|
|
|
border-radius: 4px;
|
|
border-radius: 4px;
|
|
|
background-color: white;
|
|
background-color: white;
|
|
|
border: 1px solid #ccc;
|
|
border: 1px solid #ccc;
|
|
@@ -427,3 +667,66 @@ onMounted(() => {
|
|
|
padding: 6px;
|
|
padding: 6px;
|
|
|
}
|
|
}
|
|
|
</style>
|
|
</style>
|
|
|
|
|
+
|
|
|
|
|
+<style lang="scss">
|
|
|
|
|
+.tracking-map {
|
|
|
|
|
+ .container {
|
|
|
|
|
+ position: relative;
|
|
|
|
|
+ display: flex;
|
|
|
|
|
+ justify-content: center;
|
|
|
|
|
+ align-items: center;
|
|
|
|
|
+ height: 50px;
|
|
|
|
|
+ width: 50px;
|
|
|
|
|
+ /* background-color: #d9dddf; */
|
|
|
|
|
+ .font_family {
|
|
|
|
|
+ position: absolute;
|
|
|
|
|
+ top: 50%;
|
|
|
|
|
+ left: 50%;
|
|
|
|
|
+ transform: translate(-12px, -12px);
|
|
|
|
|
+ display: flex;
|
|
|
|
|
+ justify-content: center;
|
|
|
|
|
+ align-items: center;
|
|
|
|
|
+ width: 24px;
|
|
|
|
|
+ height: 24px;
|
|
|
|
|
+ padding-bottom: 2px;
|
|
|
|
|
+ padding-left: 1px;
|
|
|
|
|
+ color: white;
|
|
|
|
|
+ background-color: rgba(255, 117, 0, 1);
|
|
|
|
|
+ border-radius: 50%;
|
|
|
|
|
+ font-size: 14px;
|
|
|
|
|
+ border: 1px solid white;
|
|
|
|
|
+ }
|
|
|
|
|
+ }
|
|
|
|
|
+ .circle {
|
|
|
|
|
+ display: flex;
|
|
|
|
|
+ align-items: center;
|
|
|
|
|
+ justify-content: center;
|
|
|
|
|
+ width: 24px;
|
|
|
|
|
+ height: 24px;
|
|
|
|
|
+ background-color: rgba(255, 117, 0, 1);
|
|
|
|
|
+ border-radius: 50%;
|
|
|
|
|
+ position: relative;
|
|
|
|
|
+ animation: expandAndFade 1.3s linear infinite;
|
|
|
|
|
+ overflow: hidden; /* 确保子元素不会溢出 */
|
|
|
|
|
+
|
|
|
|
|
+ opacity: 0.9;
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ @keyframes expandAndFade {
|
|
|
|
|
+ 0% {
|
|
|
|
|
+ transform: scale(1);
|
|
|
|
|
+ opacity: 0.9;
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ 50% {
|
|
|
|
|
+ transform: scale(1.5);
|
|
|
|
|
+ opacity: 0.7;
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ 100% {
|
|
|
|
|
+ transform: scale(2);
|
|
|
|
|
+ opacity: 0.2;
|
|
|
|
|
+ }
|
|
|
|
|
+ }
|
|
|
|
|
+}
|
|
|
|
|
+</style>
|