feat: report native junction quality metrics

This commit is contained in:
2026-08-14 16:21:26 +08:00
parent 3eea12c6ea
commit 822e6ef936
5 changed files with 27 additions and 3 deletions

View File

@@ -480,6 +480,17 @@ function offsetLine(line, offsetMeters) {
}
function lineLengthMeters(line) { return line.slice(1).reduce((sum, point, index) => { const previous = line[index]; const dx = (point[0] - previous[0]) * 111320 * Math.cos(point[1] * Math.PI / 180); const dy = (point[1] - previous[1]) * 111320; return sum + Math.hypot(dx, dy); }, 0); }
function polygonAreaMeters(ring) {
if (ring.length < 3) return 0;
const origin = ring[0];
const points = ring.map((point) => project(point, origin));
let twiceArea = 0;
for (let index = 0; index < points.length; index += 1) {
const next = points[(index + 1) % points.length];
twiceArea += points[index][0] * next[1] - next[0] * points[index][1];
}
return Math.abs(twiceArea) / 2;
}
function compileJunctionSurfaces(model, junctionPlans, connectors, movements, diagnostics) {
const result = [];
@@ -491,6 +502,7 @@ function compileJunctionSurfaces(model, junctionPlans, connectors, movements, di
diagnostics.push(diagnostic("warning", `junction:node/${nodeId}`, [nodeId], "junction-surface-deferred", "路口缺少足够的截面或转向路径,暂不生成路口面。", node));
continue;
}
const approachAreaMeters = polygonAreaMeters(boundary);
let ring = [...boundary, boundary[0]];
let boundaryMode = "approach-envelope";
if (hasSelfIntersection(ring) || !junctionConnectors.every((feature) => feature.geometry.coordinates.every((point) => pointInPolygon(point, ring)))) {
@@ -502,7 +514,9 @@ function compileJunctionSurfaces(model, junctionPlans, connectors, movements, di
diagnostics.push(diagnostic("error", `junction:node/${nodeId}`, [nodeId], "invalid-junction-surface", "路口截面边界发生自相交,未发布路口面。请检查道路方向或路口拓扑。", node));
continue;
}
result.push({ type: "Feature", properties: { native_id: `junction:node/${nodeId}`, osm_node_id: nodeId, kind: segmentIds.size === 3 ? "t" : "cross", source_road_ids: approaches.flatMap((approach) => approach.roadIds).join(","), cutback_m: cutbackMeters, movement_count: junctionMovements.length, connector_count: junctionConnectors.length, boundary_mode: boundaryMode, rule: "junction-shared-cutback/v4-shared-node-split" }, geometry: { type: "Polygon", coordinates: [ring] } });
const surfaceAreaMeters = polygonAreaMeters(ring);
const expansionRatio = approachAreaMeters > 0 ? surfaceAreaMeters / approachAreaMeters : null;
result.push({ type: "Feature", properties: { native_id: `junction:node/${nodeId}`, osm_node_id: nodeId, kind: segmentIds.size === 3 ? "t" : "cross", source_road_ids: approaches.flatMap((approach) => approach.roadIds).join(","), cutback_m: cutbackMeters, movement_count: junctionMovements.length, connector_count: junctionConnectors.length, boundary_mode: boundaryMode, approach_area_m2: Math.round(approachAreaMeters * 10) / 10, surface_area_m2: Math.round(surfaceAreaMeters * 10) / 10, expansion_ratio: expansionRatio === null ? null : Math.round(expansionRatio * 100) / 100, rule: "junction-shared-cutback/v4-shared-node-split" }, geometry: { type: "Polygon", coordinates: [ring] } });
if (boundaryMode === "connector-convex-fallback") diagnostics.push(diagnostic("warning", `junction:node/${nodeId}`, [nodeId], "junction-connector-envelope-fallback", "路口面需要按转向路径的凸包兜底生成;请检查外缘和路缘与步行带是否符合实际。", node));
diagnostics.push(diagnostic("info", `junction:node/${nodeId}`, [nodeId], "ordinary-junction-surface", "已按道路截面与转向路径生成普通路口面。", node));
}