Bläddra i källkod

Merge branch 'feat_map' into feat_theme_zyh

zhouyuhao 11 månader sedan
förälder
incheckning
b7a029175f
1 ändrade filer med 323 tillägg och 98 borttagningar
  1. 323 98
      src/views/Tracking/src/components/TrackingDetail/src/components/MapView.vue

+ 323 - 98
src/views/Tracking/src/components/TrackingDetail/src/components/MapView.vue

@@ -1,5 +1,10 @@
 <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"
+  ></div>
 </template>
 <script setup lang="ts">
 import L from 'leaflet'
@@ -45,7 +50,7 @@ const initMap = () => {
     return
   }
 
-  map = L.map('tracking-map').setView([51.505, -0.09], 3)
+  map = L.map('tracking-map', {}).setView([51.505, -0.09], 3)
 
   // 添加 TileLayer
   L.tileLayer('https://map.kerryapex.com/osm_tiles/{z}/{x}/{y}.png', {
@@ -54,8 +59,6 @@ const initMap = () => {
   }).addTo(map)
 }
 
-const track_base_line = ref([])
-const track_first_pt = null
 // 修正经度
 const fixLng = (lng: number) => {
   while (lng > 180) lng = lng - 360
@@ -121,74 +124,84 @@ const getBBox = () => {
   if (bbox3.length > 0) ll.push(bbox3)
   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 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, HasArrow, opts) => {
+const addMapLine = (l, IsDash, opts) => {
   let mpts = l.pts
   if (mpts == null || mpts.length == 0) return
 
   let bnd = map.getBounds()
   let ww = bnd.getEast() - bnd.getWest()
   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 cc2 = Math.ceil(bnd.getEast() / 360)
   cc = cc2 - cc1 + 1
 
   let kk = cc1
-  // let llIndex = 0
-  let lines_list = [mpts]
+  // let lines_list = [mpts]
   while (kk >= cc1 && kk <= cc2 && cc > 0) {
     lines_list.forEach((a) => {
       let ii = 0
@@ -197,16 +210,15 @@ const addMapLine = (l, IsDash, HasArrow, opts) => {
       for (ii = 0; ii < a.length; ii++) pts[jj++] = [a[ii][0], a[ii][1] + kk * 360]
 
       if (jj > 0) {
-        if (IsDash)
+        if (IsDash) {
           showTrackLine(
             pts,
             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 +227,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)
-
-  // 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) => {
@@ -280,10 +267,14 @@ let initialCenter: L.LatLng | null = null
 let initialZoomLevel: number | null = null
 let isFirstRender = true // 标记是否为首次渲染
 
+let allMarkers = []
+let visibleMarkers = new Set()
+
 // 添加标记后更新中心和缩放级别
 const addMarkersToMap = () => {
+  // debugger
   if (!map) return // 确保地图已经初始化
-  const latLngBounds: any = [] // 用来存储所有标记的坐标
+
   markerPositions.value.forEach((position) => {
     const marker = L.marker([position.lat, position.lng], { icon: position.icon }).addTo(map)
 
@@ -303,11 +294,14 @@ const addMarkersToMap = () => {
         closeOnClick: false
       })
       .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] })
     setTimeout(() => {
       if (isFirstRender) {
@@ -320,6 +314,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 = () => {
   $api
@@ -329,9 +470,10 @@ const getMarker = () => {
     })
     .then((res) => {
       if (res.code === 200) {
+        allMapData.value = res.data
         const { data } = res
-        data &&
-          data.forEach((item) => {
+        data?.point &&
+          data?.point.forEach((item) => {
             const iconColorList = {
               Destination: { color: '#24ca5a', icon: destinationIcon },
               Origin: { color: '#ED6D00', icon: originIcon },
@@ -339,15 +481,31 @@ const getMarker = () => {
             }
             markerPositions.value.push({
               lat: item.lat,
-              lng: item.lng,
+              lng: (Number(item.lng) + 360) % 360,
               city: item.infor,
               label: item.label,
               icon: iconColorList[item.label].icon,
               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()
+        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 +525,10 @@ watch(
 onMounted(() => {
   initMap() // 初始化地图,不加标记
 })
+
+onUnmounted(() => {
+  map?.remove()
+})
 </script>
 
 <style lang="scss">
@@ -436,3 +598,66 @@ onMounted(() => {
   padding: 6px;
 }
 </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>