|
|
@@ -86,6 +86,7 @@ export default {
|
|
|
statusIntersections: {},
|
|
|
currentZoomSize: 14, // 保存当前的动态尺寸
|
|
|
markerById: {}, // ID → marker 索引,加速 focusById 查找
|
|
|
+ subAreaOverlays: [], // 子区蒙层覆盖物
|
|
|
};
|
|
|
},
|
|
|
mounted() {
|
|
|
@@ -141,7 +142,10 @@ export default {
|
|
|
this.routeGroups = {};
|
|
|
}
|
|
|
|
|
|
- // 4. 销毁地图实例并清空引用
|
|
|
+ // 4. 清理子区蒙层
|
|
|
+ this.clearSubAreaOverlays();
|
|
|
+
|
|
|
+ // 6. 销毁地图实例并清空引用
|
|
|
if (this.map) {
|
|
|
try {
|
|
|
this.map.destroy();
|
|
|
@@ -151,7 +155,7 @@ export default {
|
|
|
this.map = null;
|
|
|
}
|
|
|
|
|
|
- // 5. 清理其他引用
|
|
|
+ // 7. 清理其他引用
|
|
|
this.AMap = null;
|
|
|
this.driving = null;
|
|
|
if (this.infoCloseTimer) {
|
|
|
@@ -1355,7 +1359,93 @@ export default {
|
|
|
lngLatToPixel(lng, lat) {
|
|
|
if (!this.map) return null;
|
|
|
return this.map.lngLatToContainer([lng, lat]);
|
|
|
- }
|
|
|
+ },
|
|
|
+
|
|
|
+ /**
|
|
|
+ * 对外暴露:为指定子区路口列表绘制凸包多边形蒙层,点击不同子区时自动替换上一个
|
|
|
+ * @param {Array<{lng: number, lat: number}>} leaves - 子区内所有叶子路口节点
|
|
|
+ */
|
|
|
+ drawSubAreaCircle(leaves) {
|
|
|
+ if (!this.isMapReady() || !leaves || leaves.length === 0) return;
|
|
|
+
|
|
|
+ this.clearSubAreaOverlays();
|
|
|
+
|
|
|
+ const pts = leaves.map(n => [Number(n.lng), Number(n.lat)]);
|
|
|
+ if (pts.length < 3) return;
|
|
|
+
|
|
|
+ const hull = this._convexHull(pts);
|
|
|
+ if (hull.length < 3) return;
|
|
|
+
|
|
|
+ // 以凸包重心为基准,向外扩展 10% 留出视觉间距
|
|
|
+ const cx = hull.reduce((s, p) => s + p[0], 0) / hull.length;
|
|
|
+ const cy = hull.reduce((s, p) => s + p[1], 0) / hull.length;
|
|
|
+ const paddedHull = hull.map(p => [
|
|
|
+ cx + (p[0] - cx) * 1.1,
|
|
|
+ cy + (p[1] - cy) * 1.1,
|
|
|
+ ]);
|
|
|
+
|
|
|
+ const polygon = new this.AMap.Polygon({
|
|
|
+ path: paddedHull,
|
|
|
+ fillColor: '#4A9EFF',
|
|
|
+ fillOpacity: 0.15,
|
|
|
+ strokeColor: '#4A9EFF',
|
|
|
+ strokeOpacity: 0.6,
|
|
|
+ strokeWeight: 1,
|
|
|
+ strokeStyle: 'dashed',
|
|
|
+ strokeDasharray: [4, 4],
|
|
|
+ zIndex: 10,
|
|
|
+ bubble: true,
|
|
|
+ });
|
|
|
+
|
|
|
+ this.subAreaOverlays.push(polygon);
|
|
|
+ this.map.add(polygon);
|
|
|
+ },
|
|
|
+
|
|
|
+ /**
|
|
|
+ * Graham Scan 凸包算法
|
|
|
+ * @param {Array<[number, number]>} points - 经纬度点数组
|
|
|
+ * @returns {Array<[number, number]>} 凸包顶点(逆时针)
|
|
|
+ */
|
|
|
+ _convexHull(points) {
|
|
|
+ if (points.length < 3) return [...points];
|
|
|
+
|
|
|
+ const p0 = points.reduce((best, p) =>
|
|
|
+ p[1] < best[1] || (p[1] === best[1] && p[0] < best[0]) ? p : best
|
|
|
+ );
|
|
|
+
|
|
|
+ const cross = (o, a, b) =>
|
|
|
+ (a[0] - o[0]) * (b[1] - o[1]) - (a[1] - o[1]) * (b[0] - o[0]);
|
|
|
+
|
|
|
+ const dist2 = (a, b) =>
|
|
|
+ (a[0] - b[0]) ** 2 + (a[1] - b[1]) ** 2;
|
|
|
+
|
|
|
+ const sorted = points
|
|
|
+ .filter(p => p !== p0)
|
|
|
+ .sort((a, b) => {
|
|
|
+ const c = cross(p0, a, b);
|
|
|
+ if (c !== 0) return c > 0 ? -1 : 1;
|
|
|
+ return dist2(p0, a) - dist2(p0, b);
|
|
|
+ });
|
|
|
+
|
|
|
+ const hull = [p0];
|
|
|
+ for (const p of sorted) {
|
|
|
+ while (hull.length >= 2 && cross(hull[hull.length - 2], hull[hull.length - 1], p) <= 0) {
|
|
|
+ hull.pop();
|
|
|
+ }
|
|
|
+ hull.push(p);
|
|
|
+ }
|
|
|
+ return hull;
|
|
|
+ },
|
|
|
+
|
|
|
+ /**
|
|
|
+ * 清除子区圆形蒙层
|
|
|
+ */
|
|
|
+ clearSubAreaOverlays() {
|
|
|
+ if (this.map && this.subAreaOverlays.length > 0) {
|
|
|
+ try { this.map.remove(this.subAreaOverlays); } catch (e) { void e; }
|
|
|
+ this.subAreaOverlays = [];
|
|
|
+ }
|
|
|
+ },
|
|
|
}
|
|
|
};
|
|
|
</script>
|