Forráskód Böngészése

fix(map): 修复 Polyline 路径报错并重构优化 TongzhouTrafficMap 逻辑

sequoia tungfang 2 hete%!(EXTRA string=óta)
szülő
commit
79f44c23fb
1 módosított fájl, 105 hozzáadás és 124 törlés
  1. 105 124
      src/components/TongzhouTrafficMap.vue

+ 105 - 124
src/components/TongzhouTrafficMap.vue

@@ -60,7 +60,6 @@ export default {
 
 
       routeGroups: {},
-      polylines: [],
       privateStyle: {
         legend: {}
       },
@@ -463,11 +462,24 @@ export default {
         basePath = this.buildFallbackLinePath(line.start, line.end, 30);
       }
 
+      // --- 核心优化:物理分散偏移 (Dispersal Offset) ---
+      // 根据 lineIdx 为每条路径应用微小偏移,避免多条线路完全重合
+      // 0.00015 度约等于 15 米,(lineIdx - 3) 将多条线在中心点两侧排开
+      const offsetVal = (Number(lineIdx) - 3) * 0.00015;
+      const applyOffset = (p) => {
+        const lng = p.lng || (p.getLng ? p.getLng() : (Array.isArray(p) ? Number(p[0]) : 0));
+        const lat = p.lat || (p.getLat ? p.getLat() : (Array.isArray(p) ? Number(p[1]) : 0));
+        return [lng + offsetVal, lat + offsetVal];
+      };
+
       const segments = this.extractMainStraightSegments(basePath);
       const overlays = [];
 
-      segments.forEach((segmentPath, segmentIdx) => {
-        if (!Array.isArray(segmentPath) || segmentPath.length < 2) return;
+      segments.forEach((rawSegmentPath, segmentIdx) => {
+        if (!Array.isArray(rawSegmentPath) || rawSegmentPath.length < 2) return;
+
+        // 应用偏移到当前段的所有点
+        const segmentPath = rawSegmentPath.map(p => applyOffset(p));
 
         const polyline = new this.AMap.Polyline({
           path: segmentPath,
@@ -509,15 +521,15 @@ export default {
           if (p1 && p2) {
             // 在 p1 和 p2 之间线性插值
             const ratio = (currentTargetDist - pathDistances[foundIdx]) / (pathDistances[foundIdx + 1] - pathDistances[foundIdx]);
-            const lng1 = p1.lng || (Array.isArray(p1) ? Number(p1[0]) : 0);
-            const lat1 = p1.lat || (Array.isArray(p1) ? Number(p1[1]) : 0);
-            const lng2 = p2.lng || (Array.isArray(p2) ? Number(p2[0]) : 0);
-            const lat2 = p2.lat || (Array.isArray(p2) ? Number(p2[1]) : 0);
+            const lng1 = Number(p1[0]);
+            const lat1 = Number(p1[1]);
+            const lng2 = Number(p2[0]);
+            const lat2 = Number(p2[1]);
             
             const midLng = lng1 + (lng2 - lng1) * ratio;
             const midLat = lat1 + (lat2 - lat1) * ratio;
 
-            const bearing = this.calcBearingDeg({ lng: lng1, lat: lat1 }, { lng: lng2, lat: lat2 });
+            const bearing = this.calcBearingDeg(p1, p2);
             const rotation = bearing - 90;
 
             const directionMarker = new this.AMap.Marker({
@@ -543,8 +555,8 @@ export default {
         for (let i = 0; i < indices.length; i++) {
           const idx = indices[i];
           const p = segmentPath[idx];
-          const lng = p && typeof p.lng === 'number' ? p.lng : (Array.isArray(p) ? Number(p[0]) : NaN);
-          const lat = p && typeof p.lat === 'number' ? p.lat : (Array.isArray(p) ? Number(p[1]) : NaN);
+          const lng = Number(p[0]);
+          const lat = Number(p[1]);
           if (Number.isNaN(lng) || Number.isNaN(lat)) continue;
 
           // 确定圆点类型:第一个为start,最后一个为end,其余为normal
@@ -570,18 +582,11 @@ export default {
     pickEvenlySpacedIndices(totalPoints, count) {
       const total = Math.max(Number(totalPoints) || 0, 0);
       const target = Math.max(Number(count) || 0, 0);
-      if (total <= 0) return [];
-      if (target <= 0) return [];
+      if (total <= 0 || target <= 0) return [];
       if (target >= total) return Array.from({ length: total }, (_, i) => i);
       if (target === 1) return [0];
 
-      const set = new Set();
-      const last = total - 1;
-      for (let k = 0; k < target; k += 1) {
-        const idx = Math.round((k * last) / (target - 1));
-        set.add(idx);
-      }
-      return Array.from(set).sort((a, b) => a - b);
+      return Array.from({ length: target }, (_, k) => Math.round((k * (total - 1)) / (target - 1)));
     },
 
     buildFallbackLinePath(start, end, pointCount) {
@@ -604,7 +609,14 @@ export default {
     },
 
     extractMainStraightSegments(path) {
-      const points = (path || []).filter(p => p && typeof p.lng === 'number' && typeof p.lat === 'number');
+      const getCoord = (p) => {
+        if (!p) return { lng: NaN, lat: NaN };
+        if (Array.isArray(p)) return { lng: Number(p[0]), lat: Number(p[1]) };
+        if (p.getLng) return { lng: p.getLng(), lat: p.getLat() };
+        return { lng: Number(p.lng), lat: Number(p.lat) };
+      };
+
+      const points = (path || []).map(p => getCoord(p)).filter(p => !isNaN(p.lng) && !isNaN(p.lat));
       if (points.length < 2) return [];
 
       const thresholdDeg = 18;
@@ -658,17 +670,24 @@ export default {
       return finalSegments.length > 0 ? finalSegments : [points];
     },
 
+    // 内部通用工具:从不同格式的点中提取经纬度数组 [lng, lat]
+    _getCoords(p) {
+      if (!p) return [0, 0];
+      const lng = p.lng || (p.getLng ? p.getLng() : (Array.isArray(p) ? Number(p[0]) : 0));
+      const lat = p.lat || (p.getLat ? p.getLat() : (Array.isArray(p) ? Number(p[1]) : 0));
+      return [lng, lat];
+    },
+
     calcBearingDeg(a, b) {
-      const latRad = ((a.lat + b.lat) / 2) * Math.PI / 180;
-      const dx = (b.lng - a.lng) * Math.cos(latRad);
-      const dy = (b.lat - a.lat);
+      const [alng, alat] = this._getCoords(a);
+      const [blng, blat] = this._getCoords(b);
+
+      const latRad = ((alat + blat) / 2) * Math.PI / 180;
+      const dx = (blng - alng) * Math.cos(latRad);
+      const dy = (blat - alat);
       
-      // 使用地理方位角公式:0度为北,顺时针增加
-      // atan2(dy, dx) 返回数学角度(0为东,逆时针)
-      // geographic_bearing = (90 - math_angle + 360) % 360
       const mathAngle = Math.atan2(dy, dx) * 180 / Math.PI;
-      let bearing = (90 - mathAngle + 360) % 360;
-      return bearing;
+      return (90 - mathAngle + 360) % 360;
     },
 
     calcAngleDiffDeg(a, b) {
@@ -678,9 +697,12 @@ export default {
     },
 
     calcApproxDistance(a, b) {
-      const latRad = ((a.lat + b.lat) / 2) * Math.PI / 180;
-      const dx = (b.lng - a.lng) * Math.cos(latRad);
-      const dy = (b.lat - a.lat);
+      const [alng, alat] = this._getCoords(a);
+      const [blng, blat] = this._getCoords(b);
+
+      const latRad = ((alat + blat) / 2) * Math.PI / 180;
+      const dx = (blng - alng) * Math.cos(latRad);
+      const dy = (blat - alat);
       return Math.sqrt(dx * dx + dy * dy);
     },
 
@@ -688,8 +710,8 @@ export default {
       if (!position || !config) return null;
 
       try {
-        const lng = Number(position[0] || position.lng);
-        const lat = Number(position[1] || position.lat);
+        const lng = position.getLng ? position.getLng() : Number(position[0] !== undefined ? position[0] : position.lng);
+        const lat = position.getLat ? position.getLat() : Number(position[1] !== undefined ? position[1] : position.lat);
         if (isNaN(lng) || isNaN(lat)) return null;
 
         // 状态文字:起、终、或者状态配置的首字母
@@ -699,99 +721,58 @@ export default {
 
         const isAbnormal = ["离线", "降级", "故障"].includes(config.name);
         const isRoute = ["干线协调", "勤务路线"].includes(config.name);
+        const isStartEnd = type === 'start' || type === 'end';
+
+        // 核心配置映射:减少嵌套逻辑
+        const markerStyle = isStartEnd ? {
+          size: '24px',
+          height: '30px',
+          offset: [-12, -30],
+          zIndex: 120,
+          border: '2px solid #fff'
+        } : (isAbnormal ? {
+          size: '30px',
+          height: '30px',
+          offset: [-15, -15],
+          zIndex: 110,
+          border: 'none'
+        } : {
+          size: '14px',
+          height: '14px',
+          offset: [-9, -9], // 14px + 2px padding * 2 = 18px total
+          zIndex: 100,
+          border: isRoute ? 'none' : '1.5px solid rgba(255,255,255,0.7)'
+        });
 
-      // 动态计算尺寸:起终点最大,异常点次之,普通点最小
-      const isStartEnd = type === 'start' || type === 'end';
-      const size = isStartEnd ? '24px' : (isAbnormal ? '30px' : '14px');
-      const sizeNumber = parseInt(size, 10);
-      const paddingNumber = (isAbnormal || isStartEnd) ? 0 : 2;
-      const outerSizeNumber = sizeNumber + paddingNumber * 2;
-
-      // 边框样式:起终点用实白边框
-      const borderStyle = isStartEnd
-        ? '2px solid #fff'
-        : '1.5px solid rgba(255,255,255,0.7)';
-
-      // 生成标记内容
-      let markerContent = '';
-      if (isStartEnd) {
-        const iconPath = type === 'start' 
-          ? require('@/assets/map/start.png') 
-          : require('@/assets/map/end.png');
-        markerContent = `
-          <div class="pure-light-node start-end-node"
-              style="
-                width: ${size};
-                height: 30px;
-                background: transparent;
-                border: none;
-                display: flex;
-                justify-content: center;
-                align-items: flex-end;
-                cursor: pointer;
-                transform-origin: bottom center;
-              ">
-            <img src="${iconPath}" style="width: 100%; height: auto; object-fit: contain; pointer-events: none;" />
-          </div>
-        `;
-      } else if (isAbnormal) {
-        const iconName = config.name === '离线' ? 'lixian' : config.name === '降级' ? 'jiangji' : 'guzhang';
-        markerContent = `
-          <div class="pure-light-node ${isAbnormal ? 'breathe' : ''}"
-              style="
-                width: ${size};
-                height: ${size};
-                background: transparent;
-                box-shadow: none;
-                border: none;
-                box-sizing: content-box;
-                display: flex;
-                justify-content: center;
-                align-items: center;
-                cursor: pointer;
-                padding: ${paddingNumber}px;
-              ">
-            <img src="${require(`@/assets/images/icon_${iconName}.png`)}" style="width: 100%; height: 100%; object-fit: contain;" />
-          </div>
-        `;
-      } else {
-        markerContent = `
-          <div class="pure-light-node ${isAbnormal ? 'breathe' : ''} ${isRoute ? 'route-node' : ''}"
-              style="
-                width: ${size};
-                height: ${size};
-                background: ${config.color || '#999'};
-                box-shadow: ${isRoute ? 'none' : `0 0 8px ${config.color}`};
-                border: ${isRoute ? 'none' : borderStyle};
-                box-sizing: content-box;
-                display: flex;
-                justify-content: center;
-                align-items: center;
-                color: #fff;
-                border-radius: 50%;
-                cursor: pointer;
-                padding: ${paddingNumber}px;
-              ">
-            <span style="transform: scale(0.8); font-weight: bold; font-size: 12px;">${displayText}</span>
-          </div>
-        `;
-      }
-
-      // 计算偏移量:起点终点底部对齐,其他中心对齐
-      let markerOffset;
-      if (isStartEnd) {
-        // 设置 24px 宽 30px 高,偏移量设置为底部中心对齐
-        markerOffset = new this.AMap.Pixel(-12, -30);
-      } else {
-        markerOffset = new this.AMap.Pixel(-Math.round(outerSizeNumber / 2), -Math.round(outerSizeNumber / 2));
-      }
+        // 生成标记内容
+        let markerContent = '';
+        if (isStartEnd) {
+          markerContent = `
+            <div class="pure-light-node start-end-node" style="width: ${markerStyle.size}; height: ${markerStyle.height}; background: transparent; border: none; display: flex; justify-content: center; align-items: flex-end; cursor: pointer; transform-origin: bottom center;">
+              <img src="${require(`@/assets/map/${type}.png`)}" style="width: 100%; height: auto; object-fit: contain; pointer-events: none;" />
+            </div>
+          `;
+        } else if (isAbnormal) {
+          const iconName = config.name === '离线' ? 'lixian' : config.name === '降级' ? 'jiangji' : 'guzhang';
+          markerContent = `
+            <div class="pure-light-node breathe" style="width: ${markerStyle.size}; height: ${markerStyle.height}; background: transparent; border: none; box-sizing: content-box; display: flex; justify-content: center; align-items: center; cursor: pointer; padding: 0;">
+              <img src="${require(`@/assets/images/icon_${iconName}.png`)}" style="width: 100%; height: 100%; object-fit: contain;" />
+            </div>
+          `;
+        } else {
+          markerContent = `
+            <div class="pure-light-node ${isRoute ? 'route-node' : ''}" style="width: ${markerStyle.size}; height: ${markerStyle.height}; background: ${config.color || '#999'}; box-shadow: ${isRoute ? 'none' : `0 0 8px ${config.color}`}; border: ${markerStyle.border}; box-sizing: content-box; display: flex; justify-content: center; align-items: center; color: #fff; border-radius: 50%; cursor: pointer; padding: 2px;">
+              <span style="transform: scale(0.8); font-weight: bold; font-size: 12px;">${displayText}</span>
+            </div>
+          `;
+        }
 
-      const marker = new this.AMap.Marker({
-        position: [lng, lat],
-        zIndex: isStartEnd ? 120 : (isAbnormal ? 110 : 100),
-        content: markerContent,
-        offset: markerOffset,
-        extData: {
+        const marker = new this.AMap.Marker({
+          position: [lng, lat],
+          zIndex: markerStyle.zIndex,
+          content: markerContent,
+          offset: new this.AMap.Pixel(...markerStyle.offset),
+          extData: {
             ...config,
             position: [lng, lat],
             statusColor: config.color || '#999',