Files
osmWorkflow/scripts/test-road-workbench.js
que01 12aeda9a63 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=[]。
2026-08-21 11:59:03 +08:00

90 lines
5.0 KiB
JavaScript

#!/usr/bin/env node
"use strict";
const assert = require("assert");
const fs = require("fs");
const path = require("path");
const html = fs.readFileSync(path.join(__dirname, "workbench", "index.html"), "utf8");
assert.match(html, /id="width" type="number" min="1" step="0\.01"/);
assert.match(html, /data-layer="sidewalks" type="checkbox" checked> 路缘与步行带/);
assert.match(html, /data-layer="gaodeReference" type="checkbox" checked> 高德规整路口参考/);
assert.match(html, /id="scene-preview" type="checkbox"/);
assert.match(html, /id="selected-junction" hidden/);
const app = fs.readFileSync(path.join(__dirname, "workbench", "app.js"), "utf8");
assert.match(app, /async function saveStagedChanges\(\)/);
assert.match(app, /if \(!await saveStagedChanges\(\)\) return;/);
assert.match(app, /function stageRoadOverride\(road, changes\)/);
assert.match(app, /road\.segmentId === selectedRoad\.segmentId/);
assert.match(app, /sidewalkLeft: rightInput\.checked, sidewalkRight: leftInput\.checked/);
assert.match(app, /function nativeSurfaceStyle\(feature\)/);
assert.match(app, /reference: new VectorLayer\(\{ source: source\(\), visible: false/);
assert.match(app, /gaodeReference: new VectorLayer/);
assert.match(app, /state\.junctionReference\?\.converted/);
assert.match(app, /gaodeReferenceColors/);
assert.match(app, /scene mode must render fills only/);
assert.match(app, /readFeatures\(state\.layers\.nativeRoadSurface\)/);
assert.doesNotMatch(app, /nativeRoadSurface, \(feature\) => !feature\.properties\?\.cluster_id/);
assert.doesNotMatch(app, /&& !feature\.properties\?\.cluster_preview\)/);
assert.match(app, /scenePreviewToggle\.onchange/);
assert.match(app, /layers\.sidewalks\.setVisible\(document\.querySelector\('\[data-layer="sidewalks"\]'\)\.checked\)/);
assert.match(app, /function selectJunction\(feature\)/);
assert.match(app, /candidate\.get\("native_id"\) === item\.subjectId/);
assert.match(app, /外缘扩张倍率/);
assert.match(app, /最大外缘扩张/);
assert.match(app, /道路方向箭头/);
assert.match(app, /native-road-direction-arrow\/v1/);
assert.match(app, /data-layer="centerLines" type="checkbox" checked> 道路中心线/);
assert.match(app, /centerLines: new VectorLayer/);
assert.match(app, /state\.layers\.centerLines/);
assert.match(app, /native-road-center-line\/v1/);
assert.match(app, /lane-separator-style/);
assert.match(app, /车道分隔线/);
assert.match(app, /center-line-style/);
assert.match(app, /centerLineStyleInput\.onchange = stageSelectedCenterLineStyle/);
assert.match(html, /道路中心线样式/);
assert.match(html, /white-solid/);
assert.match(app, /double-yellow-solid/);
assert.match(app, /const double = parts\[0\] === "double"/);
assert.match(app, /function selectEdgeLine\(feature\)/);
assert.match(app, /edge-line-style/);
assert.match(app, /道路外缘线样式/);
assert.match(app, /data-layer="edgeLines" type="checkbox"> 道路外缘线/);
assert.match(app, /edgeLines: new VectorLayer\(\{ source: source\(\), visible: false/);
assert.match(html, /id="marking-style-heading"/);
assert.match(app, /data-layer="controls" type="checkbox" checked> 斑马线与停止线/);
assert.match(app, /controls: new VectorLayer/);
assert.match(app, /state\.layers\.crosswalks/);
assert.match(app, /state\.layers\.vehicleStopLines/);
assert.match(app, /native-road-crosswalk\/v1/);
assert.match(app, /native-road-stop-line\/v1/);
assert.match(app, /term\.textContent = label; detail\.textContent = value; summary\.append\(term, detail\)/);
assert.match(app, /原生红绿灯/);
assert.match(app, /function selectSignal\(feature\)/);
assert.match(app, /\/api\/traffic-signals/);
assert.match(app, /从 OSM 生成缺失信号灯/);
assert.match(app, /data-layer="signals" type="checkbox" checked> 红绿灯设施/);
assert.match(app, /红绿灯设施/);
assert.match(app, /signalAssemblyStyle/);
assert.match(app, /signal_component: "mast"/);
assert.match(app, /signal_component: "face"/);
assert.match(app, /signal_component: "head"/);
assert.match(app, /faceHeadingDegrees/);
assert.match(app, /横杆方向(度)/);
assert.match(app, /横杆长度(米)/);
assert.match(app, /灯面朝向(度)/);
assert.match(app, /检查信号灯<select name="signal-picker">/);
assert.match(app, /signalPicker\.onchange/);
assert.match(app, /map\.getView\(\)\.fit\(\[x - 25, y - 25, x \+ 25, y \+ 25\]/);
assert.match(app, /fromLonLat/);
assert.match(app, /armFeatures\.push\(new Feature/);
assert.match(app, /headFeatures\.push\(new Feature/);
assert.match(app, /faceFeatures\.push\(new Feature/);
const server = fs.readFileSync(path.join(__dirname, "road-workbench.js"), "utf8");
assert.match(server, /\/api\/traffic-signals\/generate/);
assert.match(server, /function compileFresh\(configPath\)/);
assert.match(server, /execFileSync\(process\.execPath, \[path\.join\(repoRoot, "scripts", "compile-native-roads\.js"\)/, "workbench regeneration must load the current compiler in a fresh process");
assert.doesNotMatch(app, /导入 QGIS|导出 QGIS/);
assert.doesNotMatch(server, /traffic-signals\/(?:import|export)-qgis/);
console.log("road workbench tests passed");