feat: parameterize complex junction geometry with Gaode reference
- 高德 GeoJSON 参考流程: `scripts/lib/gaode-junction-reference.js` 与 `scripts/inspect-junction-reference.js` 将 GCJ-02 参考转换为 WGS84, 按 node id/最近距离关联 OSM, 支持普通路口面和 `complex-cluster` 两种匹配。 - 复合路口模板 `complex-junction-v1`: `scripts/lib/complex-junction.js` 用参考 几何校准 core 半径, 生成路口面、进口路面、斑马线、停止线、角部圆角与安全岛; 拓扑/信号/连接全部沿用 OSM/native。 - 车道中心线控制要素避让: `compileLaneCenterlines` 现接收模板已产出的斑马线/停止线, 新增 `trimLaneOutsideControls` 按到路口中心的半径定向裁剪; 标线源几何同步裁剪, 不再 越过斑马线继续画到核心区。拓扑几何不变, connector 集合前后一致。 - 复合路口人行道转角: `buildComplexJunctionGeometry` 沿已定义的路缘生成 2m 宽转角带, 复用圆角曲线, 通过 `islands` 通道并入 `sidewalk_surface`; 自交或坐标非有限时报 `complex-junction-sidewalk-corner-fallback` 并跳过。 - 新增诊断: `complex-junction-configured-radius-ignored`、 `lane-centerline-fully-inside-control`、`complex-junction-sidewalk-corner-fallback`。 - 死码清理: 移除未被调用的 `clusterApproachRing`。 - spec 更新: `.trellis/spec/pipeline/cli-and-stages.md` 复合路口小节补充控制要素 避让顺序、人行道转角契约、Validation 矩阵三行; 索引新增导航。 - 任务产物 `08-19-gaode-junction-reference`: 8 条验收标准全部实测记录, Scope Drift / Verification Log / Known Gaps 三节沉淀本次工作。 Regression: test:native-road / test:road-workbench / test:preflight / test:native-preview-traffic / test:package-contract / test:traffic-signals / test:gaode-junction-reference 全绿; road:check ok=true, errors=[]。
This commit is contained in:
@@ -113,6 +113,7 @@ function normalizeAreaConfig(raw, options = {}) {
|
||||
},
|
||||
nativeRoad: {
|
||||
edgeLines: booleanOption(raw.nativeRoad?.edgeLines, false, "nativeRoad.edgeLines"),
|
||||
junctionTemplates: normalizeJunctionTemplates(raw.nativeRoad?.junctionTemplates, repoRoot),
|
||||
},
|
||||
osm2streets: raw.osm2streets || {
|
||||
debug_each_step: false,
|
||||
@@ -132,6 +133,44 @@ function normalizeAreaConfig(raw, options = {}) {
|
||||
};
|
||||
}
|
||||
|
||||
function normalizeJunctionTemplates(raw, repoRoot) {
|
||||
if (raw === undefined || raw === null) return { enabled: false, references: [] };
|
||||
if (typeof raw !== "object" || Array.isArray(raw)) throw new Error("nativeRoad.junctionTemplates must be an object");
|
||||
const enabled = booleanOption(raw.enabled, false, "nativeRoad.junctionTemplates.enabled");
|
||||
if (raw.references !== undefined && !Array.isArray(raw.references)) throw new Error("nativeRoad.junctionTemplates.references must be an array");
|
||||
const references = (raw.references || []).map((item, index) => {
|
||||
if (!item || typeof item !== "object" || Array.isArray(item)) throw new Error(`nativeRoad.junctionTemplates.references[${index}] must be an object`);
|
||||
const nodeId = requireText(item.nodeId, `nativeRoad.junctionTemplates.references[${index}].nodeId`);
|
||||
const template = item.template ?? "cross-v1";
|
||||
if (template !== "cross-v1") throw new Error(`nativeRoad.junctionTemplates.references[${index}].template must be \"cross-v1\"`);
|
||||
const referenceFile = item.referenceFile ? path.resolve(item.referenceFile) : null;
|
||||
const coordinateSystem = item.coordinateSystem ?? "GCJ-02";
|
||||
if (coordinateSystem !== "GCJ-02") throw new Error(`nativeRoad.junctionTemplates.references[${index}].coordinateSystem must be \"GCJ-02\"`);
|
||||
const cornerRadiusMultiplier = numberOption(item.cornerRadiusMultiplier, 1, `nativeRoad.junctionTemplates.references[${index}].cornerRadiusMultiplier`, 0.75, 1.25);
|
||||
const cutbackMultiplier = numberOption(item.cutbackMultiplier, 1, `nativeRoad.junctionTemplates.references[${index}].cutbackMultiplier`, 1, 1.35);
|
||||
const approachWidthMultiplier = numberOption(item.approachWidthMultiplier, 1, `nativeRoad.junctionTemplates.references[${index}].approachWidthMultiplier`, 1, 1.8);
|
||||
const approachLengthMeters = numberOption(item.approachLengthMeters, 24, `nativeRoad.junctionTemplates.references[${index}].approachLengthMeters`, 10, 50);
|
||||
return { nodeId, template, referenceFile, coordinateSystem, cornerRadiusMultiplier, cutbackMultiplier, approachWidthMultiplier, approachLengthMeters };
|
||||
});
|
||||
if (raw.clusters !== undefined && !Array.isArray(raw.clusters)) throw new Error("nativeRoad.junctionTemplates.clusters must be an array");
|
||||
const clusters = (raw.clusters || []).map((item, index) => {
|
||||
if (!item || typeof item !== "object" || Array.isArray(item)) throw new Error(`nativeRoad.junctionTemplates.clusters[${index}] must be an object`);
|
||||
const id = requireText(item.id, `nativeRoad.junctionTemplates.clusters[${index}].id`);
|
||||
const nodeIds = item.nodeIds;
|
||||
if (!Array.isArray(nodeIds) || nodeIds.length < 2 || nodeIds.some((value) => typeof value !== "string" && typeof value !== "number")) throw new Error(`nativeRoad.junctionTemplates.clusters[${index}].nodeIds must contain at least two node ids`);
|
||||
const template = item.template ?? "complex-junction-v1";
|
||||
if (template !== "cross-cluster-v1" && template !== "complex-junction-v1") throw new Error(`nativeRoad.junctionTemplates.clusters[${index}].template must be \"cross-cluster-v1\" or \"complex-junction-v1\"`);
|
||||
const referenceFile = item.referenceFile ? path.resolve(item.referenceFile) : null;
|
||||
const approachWidthMultiplier = numberOption(item.approachWidthMultiplier, 1.45, `nativeRoad.junctionTemplates.clusters[${index}].approachWidthMultiplier`, 1, 1.8);
|
||||
const approachLengthMeters = numberOption(item.approachLengthMeters, 32, `nativeRoad.junctionTemplates.clusters[${index}].approachLengthMeters`, 10, 50);
|
||||
const coreRadiusMeters = numberOption(item.coreRadiusMeters, 28, `nativeRoad.junctionTemplates.clusters[${index}].coreRadiusMeters`, 12, 80);
|
||||
const cornerRadiusMeters = numberOption(item.cornerRadiusMeters, 12, `nativeRoad.junctionTemplates.clusters[${index}].cornerRadiusMeters`, 4, 25);
|
||||
const outerRadiusExtraMeters = numberOption(item.outerRadiusExtraMeters, 18, `nativeRoad.junctionTemplates.clusters[${index}].outerRadiusExtraMeters`, 18, 35);
|
||||
return { id, nodeIds: nodeIds.map(String), template, referenceFile, approachWidthMultiplier, approachLengthMeters, coreRadiusMeters, cornerRadiusMeters, outerRadiusExtraMeters };
|
||||
});
|
||||
return { enabled, references, clusters };
|
||||
}
|
||||
|
||||
function normalizeBudgetConfig(raw) {
|
||||
if (raw !== undefined && raw !== null && (typeof raw !== "object" || Array.isArray(raw))) {
|
||||
throw new Error("budget must be an object");
|
||||
|
||||
Reference in New Issue
Block a user