feat: add OSM turn lane arrows
This commit is contained in:
@@ -51,6 +51,7 @@ cp config/examples/template.json config/areas/my-area.json
|
||||
| `blenderApp` | | `/Applications/Blender.app` | |
|
||||
| `stages` | | 见下 | 各阶段默认开关 |
|
||||
| `qgis` | | 见下 | QGIS/osm2streets 旋钮 |
|
||||
| `turnLaneArrows` | | 见下 | 从 OSM `turn:lanes:*` 生成自定义车道箭头的发布开关 |
|
||||
| `osm2streets` | | 见下 | 透传给 osm2streets 的选项 |
|
||||
| `blender` | | 见下 | Blender 侧选项 |
|
||||
| `compress` | | 见下 | 显式 `compress` 阶段的 GLB 压缩选项 |
|
||||
@@ -97,6 +98,15 @@ cp config/examples/template.json config/areas/my-area.json
|
||||
> 这四个 arrow/corner 旋钮的默认值都是调出来的,**改之前先看 README 里记的理由**。
|
||||
> 尤其 `arrowOutlineSimplifyMeters`——调大会开始削箭头头部。
|
||||
|
||||
### `turnLaneArrows`
|
||||
|
||||
| 字段 | 默认 | 说明 |
|
||||
|---|---|---|
|
||||
| `enabled` | `false` | 仅在样张经用户确认后启用。启用时从 `turn:lanes:forward` / `turn:lanes:backward` 追加经过测试的自定义箭头;未测试素材永不参与映射。 |
|
||||
|
||||
该开关经 `normalizeAreaConfig()` 和 `writeDerivedConfig()` 传入 intermediates 阶段。必须用
|
||||
`??` 保留 `false`;不要将它改为按隐式标签或环境变量自动启用。
|
||||
|
||||
### `osm2streets`
|
||||
|
||||
原样透传给 `JsStreetNetwork` 构造函数(`build-osm2streets-qgis.js:77`)。默认:
|
||||
|
||||
@@ -83,6 +83,18 @@ cesium-preview.js 浏览器
|
||||
|
||||
## 本项目真实踩过的坑
|
||||
|
||||
### 坑 0:用原始 OSM 节点度数代替归一化路网拓扑
|
||||
|
||||
OSM way 的端点不一定在原始 XML 中有三个以上相连 way;osm2streets 可能把相邻 way
|
||||
合并、切分或通过 `network.intersections[*].osm_ids` 表达路口。任何需要判断道路是否
|
||||
进入路口的中间层逻辑,都必须优先使用已经生成的 normalized `network.json` 事实源,
|
||||
原始节点度数只能作为没有 normalized network 的纯单元测试回退。
|
||||
|
||||
### 坑 0.1:普通 JSON 误走 FeatureCollection 写入器
|
||||
|
||||
`writeJson()` 的隐式契约是传入带 `features` 数组的图层集合;诊断 manifest、计数摘要等
|
||||
普通对象必须用显式 `JSON.stringify` 写入,不能为了复用日志代码把它们塞进图层写入器。
|
||||
|
||||
### 坑 1:同一份事实存了四份
|
||||
|
||||
九个图层的顺序曾同时存在于 z_index 表、样式 JSON、QGIS 工程、README。
|
||||
|
||||
@@ -189,6 +189,158 @@ const osm = parseOsm(fs.readFileSync(area.input, "utf8"));
|
||||
const preflight = analyzeOsmPreflight(osm);
|
||||
```
|
||||
|
||||
## OSM 转向车道箭头
|
||||
|
||||
### 1. 范围与触发条件
|
||||
|
||||
区域配置设为 `turnLaneArrows.enabled: true` 时,`intermediates` 阶段会将已支持的
|
||||
OSM `turn:lanes:forward` / `turn:lanes:backward` 标线追加到既有的
|
||||
`lane_arrows_webscale` 图层。这只是既有图层的新增数据来源,不能新增 QGIS、Blender
|
||||
或 Cesium 图层。
|
||||
|
||||
### 2. 调用形式
|
||||
|
||||
```json
|
||||
{
|
||||
"turnLaneArrows": { "enabled": true }
|
||||
}
|
||||
```
|
||||
|
||||
该字段由 `normalizeAreaConfig()` 归一化、`writeDerivedConfig()` 写入派生配置,
|
||||
并由 `build-osm2streets-qgis.js` 消费。
|
||||
|
||||
### 3. 契约
|
||||
|
||||
- 已支持且已测试的转向为:`through`、`left`、`right`、`through;left`、
|
||||
`through;right`、`through;left;right`。
|
||||
- `assets/lane-icons/manifest.json` 是上游 CC0 来源、原始 SVG、镜像规则、箭杆轴线、
|
||||
支持状态和测试状态的唯一事实源。未同时标记为 `supported` 和 `tested` 的素材不得写入
|
||||
生产 GeoJSON。
|
||||
- 自定义要素必须带有 `source="osm_turn_lanes"`、`osm_way_id`、`direction`、
|
||||
`lane_index`、`maneuver`、`source_asset`、`arrow_part` 与确定性的
|
||||
`custom_arrow_id`。
|
||||
- `turn_lane_arrow_diagnostics.json` 记录 `generated` 和结构化跳过原因;它是普通 JSON,
|
||||
不是 FeatureCollection。
|
||||
- 归一化后的 `network.intersections[*].osm_ids` 用来判断有效路口端点。只有单元测试中
|
||||
没有 network 时,才可退回原始 OSM 节点度数。
|
||||
- 可直接关联时,`Driving` 面通过 `osm_way_ids` 匹配,中心线取其相对两边的中点;闭合
|
||||
四边形车道面也必须有效。JOSM 拆分产生的临时 ID 未被渲染面保留时,只能匹配距离近且
|
||||
行驶方向一致的车道中心线,并按实际横向位置排序,不得单独依赖渲染器的 `index`。
|
||||
|
||||
### 4. 校验与错误矩阵
|
||||
|
||||
| 条件 | 结果 |
|
||||
|---|---|
|
||||
| 功能关闭 | 不生成自定义要素;诊断原因为 `disabled` |
|
||||
| 缺少车道数或无法判定路口端点 | 带原因跳过,不能猜测位置 |
|
||||
| 转向未支持或未测试 | 以 `unsupported_or_untested_maneuver` 跳过 |
|
||||
| SVG 缺少受支持路径命令或 manifest 锚点 | 写入 GeoJSON 前抛错 |
|
||||
| 合法且已支持的 OSM 车道 | 每个 SVG 填充或扩展描边部件追加一个 Polygon |
|
||||
|
||||
### 5. 正常、基础与错误示例
|
||||
|
||||
- 正常:`through;right` 生成多个合法 Polygon 部件,但共享同一 OSM 溯源元组,直行杆
|
||||
轴线与车道中心对齐。
|
||||
- 基础:`enabled: false` 保持 osm2streets 原有箭头输出不变。
|
||||
- 错误:将组合箭头写为一个 `MultiPolygon`。既有 QGIS 归一化器只接受逐个 Polygon。
|
||||
|
||||
### 6. 必需测试
|
||||
|
||||
- `npm run test:turn-lane-arrows`:覆盖转向归一化、正反向放置、确定性 ID、箭杆锚点、
|
||||
关闭行为及未测试素材排除。
|
||||
- 目标区域 `intermediates` 构建:确认 `lane_arrows_webscale.geojson` 含自定义 `source`
|
||||
要素,且诊断可读。
|
||||
- 用户确认样张后,运行完整目标区域构建和
|
||||
`npm run check:area -- --config <area>`。
|
||||
|
||||
### 7. 错误与正确写法
|
||||
|
||||
错误:
|
||||
|
||||
```js
|
||||
if ((roadCounts.get(endpoint.id) || 0) < 3) return null;
|
||||
writeJson(diagnosticsPath, diagnostics);
|
||||
```
|
||||
|
||||
正确:
|
||||
|
||||
```js
|
||||
const networkSaysIntersection = networkIntersectionNodes.has(endpoint.id);
|
||||
if (!networkSaysIntersection && (roadCounts.get(endpoint.id) || 0) < 3) return null;
|
||||
fs.writeFileSync(diagnosticsPath, `${JSON.stringify(diagnostics, null, 2)}\n`);
|
||||
```
|
||||
|
||||
## 斑马线与停止线来源
|
||||
|
||||
### 1. 范围与触发条件
|
||||
|
||||
`intermediates` 阶段从带标记的 OSM `highway=crossing` 节点生成路口标线,并独占
|
||||
`crosswalks` 与 `vehicle_stop_lines` 两个场景图层的数据来源。
|
||||
|
||||
### 2. 调用形式
|
||||
|
||||
```js
|
||||
const crosswalkData = buildCrosswalks(osm, lanePolygons.features);
|
||||
// split.vehicleStopLines === crosswalkData.stopLines
|
||||
// split.crosswalks === crosswalkData.stripes
|
||||
```
|
||||
|
||||
### 3. 契约
|
||||
|
||||
- `buildCrosswalks()` 是输出 `vehicle_stop_lines` 的唯一生产者。不得将 osm2streets
|
||||
`lane_markings` 中类型为 `vehicle stop line` 的要素追加回来,否则会产生位置不同的
|
||||
重复标线。
|
||||
- 每个输出的过街进口固定生成 6 条 `crosswalk stripe`,以及恰好 1 条带有
|
||||
`source="crosswalk"` 和 `crossing_node_id` 的 `vehicle stop line`。
|
||||
- 斑马线参考点先沿进口方向放在过街节点簇中心外 7 米处;再以相邻归一化 `Driving` 面
|
||||
修正横向中心和方向:取局部车道中心锚点的平均值,并使用方向一致的车道切线。只有没有
|
||||
匹配的渲染车道时,才回退到原始 OSM way 几何。
|
||||
- 停止线从同一校正后的斑马线坐标系,以外侧固定 1.2 米偏移推导。osm2streets 的道路
|
||||
横断面与原始 OSM 几何不同,斑马线与停止线也必须同步移动。
|
||||
|
||||
### 4. 校验与错误矩阵
|
||||
|
||||
| 条件 | 结果 |
|
||||
|---|---|
|
||||
| 有匹配渲染车道的标记过街 | 中心与方向跟随实际渲染道路横断面 |
|
||||
| 没有匹配渲染车道的标记过街 | 安全回退到原始 OSM 方向向量 |
|
||||
| 多个源节点投影到同一进口中心 | 写入斑马线和停止线前去重 |
|
||||
| 存在 osm2streets 原生停止线 | 忽略,不写入输出图层 |
|
||||
|
||||
### 5. 正常、基础与错误示例
|
||||
|
||||
- 正常:两个四向路口产生 48 条斑马线条带和 8 条停止线,全部为
|
||||
`source="crosswalk"`。
|
||||
- 基础:归一化 `Driving` 面之外的过街仍可按原始 way 方向渲染。
|
||||
- 错误:因为原生 `vehicle stop line` 与人工线相距数米就保留它;这会重新引入第二个
|
||||
生产者和视觉重复。
|
||||
|
||||
### 6. 必需测试
|
||||
|
||||
- 运行目标区域 `intermediates` 构建。
|
||||
- 断言 `vehicle_stop_lines.geojson` 每个要素的 `crossing_node_id` 均不重复,且所有要素
|
||||
都有 `source="crosswalk"`。
|
||||
- 检查四向路口的 QGIS 预览:条带必须横向居中于渲染道路宽度,每条停止线必须位于其
|
||||
对应斑马线之后。
|
||||
|
||||
### 7. 错误与正确写法
|
||||
|
||||
错误:
|
||||
|
||||
```js
|
||||
if (feature.properties.type === "vehicle stop line") {
|
||||
out.vehicleStopLines.features.push(feature);
|
||||
}
|
||||
```
|
||||
|
||||
正确:
|
||||
|
||||
```js
|
||||
const crosswalkData = buildCrosswalks(osm, lanePolygons.features);
|
||||
out.vehicleStopLines = crosswalkData.stopLines;
|
||||
// 原生 lane_markings 停止线不得复制到输出。
|
||||
```
|
||||
|
||||
## 区域诊断命令
|
||||
|
||||
### 1. Scope / Trigger
|
||||
|
||||
Reference in New Issue
Block a user