|
@@ -1362,7 +1362,7 @@ export default {
|
|
|
},
|
|
},
|
|
|
|
|
|
|
|
/**
|
|
/**
|
|
|
- * 对外暴露:为指定子区路口列表绘制圆形蒙层,点击不同子区时自动替换上一个
|
|
|
|
|
|
|
+ * 对外暴露:为指定子区路口列表绘制凸包多边形蒙层,点击不同子区时自动替换上一个
|
|
|
* @param {Array<{lng: number, lat: number}>} leaves - 子区内所有叶子路口节点
|
|
* @param {Array<{lng: number, lat: number}>} leaves - 子区内所有叶子路口节点
|
|
|
*/
|
|
*/
|
|
|
drawSubAreaCircle(leaves) {
|
|
drawSubAreaCircle(leaves) {
|
|
@@ -1370,25 +1370,22 @@ export default {
|
|
|
|
|
|
|
|
this.clearSubAreaOverlays();
|
|
this.clearSubAreaOverlays();
|
|
|
|
|
|
|
|
- // 计算圆心(路口坐标均值)
|
|
|
|
|
- const cx = leaves.reduce((s, n) => s + Number(n.lng), 0) / leaves.length;
|
|
|
|
|
- const cy = leaves.reduce((s, n) => s + Number(n.lat), 0) / leaves.length;
|
|
|
|
|
-
|
|
|
|
|
- // 计算各路口到圆心的最大距离(米),cosLat 修正经度方向的比例
|
|
|
|
|
- const cosLat = Math.cos(cy * Math.PI / 180);
|
|
|
|
|
- let maxDist = 0;
|
|
|
|
|
- for (const n of leaves) {
|
|
|
|
|
- const dx = (Number(n.lng) - cx) * 111000 * cosLat;
|
|
|
|
|
- const dy = (Number(n.lat) - cy) * 111000;
|
|
|
|
|
- const d = Math.sqrt(dx * dx + dy * dy);
|
|
|
|
|
- if (d > maxDist) maxDist = d;
|
|
|
|
|
- }
|
|
|
|
|
- // 半径 = 最大距离 × 1.3,最小保底 500m
|
|
|
|
|
- const radius = Math.max(maxDist * 1.3, 500);
|
|
|
|
|
|
|
+ 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 circle = new this.AMap.Circle({
|
|
|
|
|
- center: [cx, cy],
|
|
|
|
|
- radius,
|
|
|
|
|
|
|
+ const polygon = new this.AMap.Polygon({
|
|
|
|
|
+ path: paddedHull,
|
|
|
fillColor: '#4A9EFF',
|
|
fillColor: '#4A9EFF',
|
|
|
fillOpacity: 0.15,
|
|
fillOpacity: 0.15,
|
|
|
strokeColor: '#4A9EFF',
|
|
strokeColor: '#4A9EFF',
|
|
@@ -1400,8 +1397,44 @@ export default {
|
|
|
bubble: true,
|
|
bubble: true,
|
|
|
});
|
|
});
|
|
|
|
|
|
|
|
- this.subAreaOverlays.push(circle);
|
|
|
|
|
- this.map.add(circle);
|
|
|
|
|
|
|
+ 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;
|
|
|
},
|
|
},
|
|
|
|
|
|
|
|
/**
|
|
/**
|