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:
2026-08-21 11:59:03 +08:00
parent 9a8dbc1a74
commit 12aeda9a63
31 changed files with 2134 additions and 95 deletions

View File

@@ -0,0 +1 @@
{"_example": "Fill with {\"file\": \"<path>\", \"reason\": \"<why>\"}. Put spec/research files only — no code paths. Run `python3 .trellis/scripts/get_context.py --mode packages` to list available specs. Delete this line once real entries are added."}

View File

@@ -0,0 +1,57 @@
# Technical Design
## Boundary
The native compiler remains authoritative for OSM road identity, directed lanes,
connections, traffic signals, stop lines, and preview routes. A reference layer is
an optional geometry-calibration input consumed before junction surface publication.
## Proposed Flow
```text
optional reference GeoJSON (GCJ-02)
-> validate schema and coordinate bounds
-> GCJ-02 to WGS84 conversion
-> associate reference footprint with an OSM junction node
-> classify geometry by observable shape, not opaque vendor type alone
-> derive bounded junction-shape parameters
-> native compiler creates surface/connectors as usual
-> diagnostics compare generated geometry with reference
```
The first production validation keeps the reference overlay/diagnostic, then enables a
bounded `cross` junction template for explicitly configured references. Connector
endpoints and movement enumeration still come from the native model. T and complex
junctions remain on the existing algorithm until their templates are separately validated.
## Input Contract
Use an optional area-level reference configuration rather than a hard-coded filename.
The sample points to `inputs/osm/珠山湖大道(枫树二路)口.geojson`, declares `GCJ-02`,
and identifies the target through OSM node `8005332807` or a stable spatial/name match.
The file is not assumed to contain a CRS member; the configured source CRS is required.
## Geometry Safety
- Convert all reference coordinates to WGS84 before distance/overlap tests.
- Reject malformed or non-finite coordinates and references outside the OSM bounds plus a
bounded tolerance.
- Never let a reference polygon create a movement, lane, signal, or stop-line record.
- Keep a fallback path to the current `compileGeometry()` junction algorithm.
## Compatibility And Rollback
No-reference configs and existing output schemas remain unchanged. The experiment can be
disabled by omitting the reference section or setting it disabled. Generated diagnostics
must record source file hash, conversion mode, matched OSM node, and whether fallback was used.
No source reference file is rewritten.
## Open Technical Work
1. Select the smallest reusable geometry parameter set from the sample (approach cutback,
corner radius/rounding, island/marking envelopes, and tolerances).
2. Implement and test GCJ-02 conversion independently from compiler logic.
3. Compare the sample against all native junctions near the converted footprint before any
production surface override is enabled.
4. Add topology classification and template dispatch; only cross is enabled in this validation.
5. Preserve native connectors and controls while replacing only the bounded junction surface geometry.

View File

@@ -0,0 +1 @@
{"_example": "Fill with {\"file\": \"<path>\", \"reason\": \"<why>\"}. Put spec/research files only — no code paths. Run `python3 .trellis/scripts/get_context.py --mode packages` to list available specs. Delete this line once real entries are added."}

View File

@@ -0,0 +1,53 @@
# Validation Implementation Plan
进度标记于 2026-08-21。
1. [x] Add a read-only reference inspection utility or library helper that validates GeoJSON,
converts GCJ-02 to WGS84, computes bounds/center, and associates the sample with OSM node
`8005332807`. — `scripts/lib/gaode-junction-reference.js` + `scripts/inspect-junction-reference.js`
2. [x] Add focused tests for coordinate conversion, malformed input rejection, spatial association,
and the no-reference fallback. — `npm run test:gaode-junction-reference`
3. [x] Add a diagnostic comparison between the reference footprint and native
`intersection_surface.geojson`, including center offset, envelope overlap, and approach headings.
— 复杂路口的面写在 `road_surface.geojson``complex_part`)而非 `intersection_surface`
原实现匹配不到、恒返回 `nativeIntersection: null`。已扩展为可回落到复杂路口簇
(新增 `--native-road-surface` / `--cluster-id`schema 升至 v2
4. [x] Add a reference-backed, parameterized junction template and dispatch it only for
explicitly enabled samples. — 实际实现为 `complex-junction-v1`,非原计划的 cross 模板,
范围变更见 prd.md「Scope Drift」。
5. [x] Preserve connector, movement, signal, and stop-line generation; validate containment against
the new surface. — A/B 实测通过,数据见 prd.md「Verification Log」。
6. [x] Run native-road, preflight, preview-traffic, and package-contract tests, plus the sample
comparison. Inspect the generated overlay/diagnostic manually.
— 2026-08-21 全绿:`test:gaode-junction-reference``test:native-road``test:road-workbench`
`test:preflight``test:native-preview-traffic``test:package-contract``test:traffic-signals`
外加 `road:check``ok: true, errors: []`。人工视觉确认仍待用户。
7. [x] Stop at validation. Do not commit,
and do not modify the source OSM or reference GeoJSON. — 未 commit参考 GeoJSON 未改;
源 OSM 有改动但来自人工 JOSM 编辑,非管线写入。
## 本次会话追加的修复2026-08-21
- 车道中心线与车道标线越过斑马线:`compileLaneCenterlines` 现接收模板已产出的斑马线/停止线,
新增 `trimLaneOutsideControls`(按到路口中心的半径定向后再裁剪,因为车道几何两种朝向都存在)。
拓扑几何 `coordinates` 保持完整,仅裁剪发布几何与标线源几何。
实测越线要素 20 → 0分隔线越界 2 → 0connector 356 / movement 370 前后一致。
- 新增诊断 `complex-junction-configured-radius-ignored``lane-centerline-fully-inside-control`
- 删除死函数 `clusterApproachRing`
- **复杂路口人行道转角**`complex-junction.js`):直条带被裁在簇边界后,相邻两臂的步行带
各自留一个断头,中间整个楔形无覆盖。现沿圆角已定义的路缘补一条 2 m 宽转角带。
- 复用同一函数作用域内已算好的 `curve` / `tangents` / `edges` / `apex`,不重新拟合曲线
- 新增 `armCarriesSidewalk`(按臂的外向方向翻转道路自身的 `sidewalkLeft/Right`)、
`edgeRunToRadius``offsetPolylineAwayFromCenter``ringSelfIntersects`
-`islands` 通道并入 `generatedComplexSidewalks`
- 自交或几何无效时跳过并报 `complex-junction-sidewalk-corner-fallback`,不硬塞坏几何
- 实测:人行道要素 7 → 8生成角 1平分线 286°241°→331°57 顶点);无自交;
与两侧条带均相接;面积采样 331 点中 0 点落在车行道内
- **限制**:全区 139 条路仅 3 条带人行道4 个角中只有 286° 两侧齐备,
其余 3 个角当前不产出,缺少验证样本
## Rollback Points
- Delete/disable the optional reference configuration to restore the default native path.
- Remove only the reference diagnostic output; existing native-road outputs remain authoritative.
- Do not overwrite existing layers until the comparison is accepted.

View File

@@ -0,0 +1,110 @@
# Parameterize junction geometry with optional Gaode reference
## Goal
验证一种通用的、参数化的路口几何生成范式:借鉴高德路口 GeoJSON 的规整边界和标线组织方式,
但保持 OSM/native compiler 对道路身份、连接关系、车道可行驶性、信号和停止线的权威性。
当前阶段先做一个可回滚的生产几何验证,不提交代码或产物。
## Requirements
- R1: native 路口在没有参考数据时行为不变,现有 OSM/native 输出仍是默认路径。
- R2: 支持可选的路口参考输入至少记录输入路径、坐标来源GCJ-02和目标 OSM 路口关联方式。
- R3: 参考输入先转换为 WGS84禁止将 GCJ-02 坐标直接与 OSM/native 几何叠加。
- R4: 参考数据只允许影响路口面的规整化参数、边界形态和标线布局;不得改变 OSM 道路、车道、connector、
signal_uid、停止线归属或车辆路线拓扑。
- R5: 不机械复制高德 feature 的 `type=1..5`;先建立可审计的几何类别映射或只使用其可验证的形态特征。
- R6: 以“珠山湖大道(枫树二路)”路口作为验证样本,输出参考前后对比、偏差诊断和可回滚结果。
- R6.1: 生成器按路口拓扑选择模板,至少区分 cross、T 和复杂/多臂路口;不能用一个固定几何参数机械覆盖所有路口。
- R6.2: 首个生产几何验证只启用 cross 模板,利用参考样本校准 cutback、圆角和进口过渡T/复杂路口先保留现有 native 行为并记录诊断。
- R6.3: 模板生成的路口面必须由 OSM/native 道路宽度、方向和车道结构驱动;高德几何只用于校准参数或对比,不直接复制其 feature。
- R7: 参考文件缺失、坐标转换失败、无法关联 OSM 路口或几何越界时,构建必须回退默认 native 算法并给出诊断,
不得静默覆盖生产几何。
- R8: 保持当前阶段 manifest、GLB/package 契约和无参考区域的 parity 行为不变。
## Acceptance Criteria
> 措辞修订说明见文末「Scope Drift」。勾选项均为 2026-08-21 实测证据记录在「Verification Log」。
- [x] 不配置参考输入时,现有 native-road 测试和样本区域输出保持默认行为。
- [x] 配置枫树二路参考后,能稳定关联 OSM node `8005332807`,并生成 WGS84 参考诊断。
- [x] 参考几何与 native 路口的叠加结果可检查:路口中心、边界、进口方向有数值和 SVG 记录。
- [x] 参考模式不改变 OSM 衍生的 connector、车道连接、信号或停止线集合模板只新增自有
`complex-*` 命名空间要素。(原措辞「不会新增或删除…停止线」严于 R4 本意,已修订)
- [x] 参考模式失败时有明确 warning并成功回退默认 native 几何。
- [x] 模板生成的路口面视觉上接近高德参考。connector / 信号 / 停止线集合一致性已实测通过;
视觉接近度由用户 2026-08-21 在工作台确认(「基本 ok」其间提出的人行道转角缺口已修复并复核通过。
- [x] 无参考路口继续走默认 native 几何,不被模板误套。
- [x] 验证产物写入区域输出目录,未执行 git commit。源 OSM 有改动,但来自人工 JOSM 编辑,
非管线写入;原措辞未区分这两者,已修订)
## Scope Drift2026-08-21 记录)
R6.2 原定「首个生产几何验证只启用 cross 模板T/复杂路口先保留现有 native 行为」。
实际实现走的是 `complex-junction-v1`,对枫树二路 4 节点簇做整体路口面、斑马线、停止线、
角部安全岛和圆角生成。范围已越过 R6.2本文档据实修订R6.2 视为被本次决策取代。
cross 模板(`cross-v1` / `cross-cluster-v1`)代码路径仍在,未被删除。
## Verification Log2026-08-21
参考模式 A/B 对照(同一 OSM 输入,`junctionTemplates.enabled` 开/关,独立 outputRoot
| 指标 | ON | OFF | 判定 |
| --- | --- | --- | --- |
| connector 要素 | 356 | 356 | 集合逐一相同,无新增/缺失 |
| movement / published | 370 / 356 | 370 / 356 | 一致 |
| connection | 215 | 215 | 一致 |
| 信号 uid 集合 | 20 | 20 | 集合完全一致 |
| 普通斑马线 `native-road-crosswalk/v1` | 6 | 6 | 一致 |
| 模板新增 `complex-crosswalk` | 128 | 0 | 模板自有命名空间 |
| 模板新增 `complex-corner-crosswalk` | 24 | 0 | 同上 |
| 模板新增 `complex-stop-line` | 4 | 0 | 同上 |
| 预览车道 `cluster-approach-lane` | 24 | 0 | 同上 |
参考关联与叠加(`npm run reference:junction`
- 匹配方式 `node-id`,命中 `8005332807`,参考中心到节点 **8.98 m**
- 匹配到 `complex-cluster` = `zhushanhu-fengshu-complex`13 个要素
- native core 半径 **24 m**(配置 `coreRadiusMeters: 28`),进口 4 / 车行道 8
进口朝向 `[-118.5, -28, 62.2, 151.9]`
- 已知口径差异:检查工具用参考文件**全部** feature 算 bounds约 380×330 m含长引道
`readReferenceCalibration` 只取 `type ∈ [1,2,3,4]` 的子集。两者不同量级,
故 bbox IoU0.070)当前**不是**可用的质量指标,仅中心偏差和朝向可比。
失败回退(`referenceFile` 指向不存在路径):
- 编译成功不抛错;诊断由 `complex-junction-reference-calibrated` 降级为 `complex-junction-generated`
- connector 356 / movement 370 不变
- 该路径下 `lane-centerline-fully-inside-control` 触发 10 次:无参考时 coreRadius 仅 14.56 m
10 条车道整体落在控制带内,回退为未裁剪发布。非回归(改动前同样未裁剪),但属已知限制。
非簇路口未受模板影响A/B `intersection_surface`
| | ON | OFF |
| --- | --- | --- |
| 普通路口面总数 | 41 | 45 |
| 簇成员节点出现次数 | 0预期 0 | 4模板关闭时预期 4 |
| 非簇节点41 个) | — | 与 ON 逐点几何完全一致,无增无缺 |
| 模板 `complex_part` 要素 | 13全部归属唯一簇 `zhushanhu-fengshu-complex` | 0 |
结论:模板严格限定在 `nodeIds` 列表内,无外溢。
## Known Gaps2026-08-21
1. `way/858770823:segment/2`(长 19.2 m远端为 service 路口 `8613058676`)被 `native-road.js`
的 cluster 丢弃判据误伤,整段无路面,该节点周边 25×25 m 内 25.8% 露底。
判据应从「远端在 outerRadius 内」改为「远端也是 cluster 成员节点」,实测该判据分界干净。
**用户 2026-08-21 明确暂缓,未修改。**
2. `coreRadiusMeters` 在有参考文件时不参与计算(被 calibration 覆盖并 clamp 到 24
已加诊断 `complex-junction-configured-radius-ignored` 使其可见,行为未改。
3. `approachLengthMeters``complex-junction-v1` 无效(仅 `cross-v1` / `cross-cluster-v1` 使用)。
死函数 `clusterApproachRing` 已删除。
4. 无参考路径下普通停止线为 0`crossing-no-safe-stop-line` 告警 1 条),先于本任务存在。
## Constraints And Deferred Scope
- OSM WGS84 与 native 拓扑是交通语义的单一事实源。
- 高德参考暂不作为完整道路网络输入,也不直接替代 road surface 或 connector 几何。
- 暂不定义所有高德 `type` 值的业务语义;先验证几何范式和校准边界。
- 暂不提交代码、配置或生成产物;本阶段完成后由用户决定是否进入正式实现。

View File

@@ -0,0 +1,26 @@
{
"id": "gaode-junction-reference",
"name": "gaode-junction-reference",
"title": "Parameterize junction geometry with optional Gaode reference",
"description": "",
"status": "in_progress",
"dev_type": null,
"scope": null,
"package": null,
"priority": "P2",
"creator": "dingkang",
"assignee": "dingkang",
"createdAt": "2026-08-19",
"completedAt": null,
"branch": null,
"base_branch": "fengshu-er-road",
"worktree_path": null,
"commit": null,
"pr_url": null,
"subtasks": [],
"children": [],
"parent": null,
"relatedFiles": [],
"notes": "",
"meta": {}
}