Fix OSM multipolygon buildings
This commit is contained in:
@@ -46,7 +46,7 @@
|
||||
|
||||
| 模块 | 层 | 职责 |
|
||||
|---|---|---|
|
||||
| `osm.py` | 纯 | `parse_osm()` 读 OSM XML → (bounds, ways, points);`Projector` 局部米制投影;`parse_height()`、`tags()` |
|
||||
| `osm.py` | 纯 | `parse_osm()` 读 OSM XML → (bounds, ways, points);building multipolygon relation 会作为 synthetic way 进入 ways;`Projector` 局部米制投影;`parse_height()`、`tags()` |
|
||||
| `geom.py` | 纯 | 平面几何全家桶。**输入输出一律是投影后的米**,例外只有 `geometry_rings` / `feature_in_bounds`(收原始 GeoJSON 的经纬度) |
|
||||
| `catalog.py` | 纯 | `ROAD_LAYERS`、`MATERIALS`、`road_material_specs()`、`check_layers()` |
|
||||
| `mesh.py` | bpy | `MeshBatch`、`make_prism`、`add_roof`、`add_wall_panel`、`add_polyline`、集合管理 |
|
||||
@@ -104,7 +104,8 @@ def assemble(name, x, y, collection, materials):
|
||||
`len(ring) >= 3`,并且建筑要保持原始 OSM footprint 参与相机取景:
|
||||
|
||||
```python
|
||||
def assemble(ring, way_id, tag, office_overrides, collection, materials):
|
||||
def assemble(ring, way_id, tag, office_overrides, collection, materials,
|
||||
inner_rings=None):
|
||||
...
|
||||
return added, industrial_added, ring_pts
|
||||
```
|
||||
@@ -114,6 +115,36 @@ def assemble(ring, way_id, tag, office_overrides, collection, materials):
|
||||
`counts["industrial_count"]`,以及写 `scene["office_override_way_ids"]`。
|
||||
模块只负责 prism / roof / windows / bevel / building custom properties。
|
||||
|
||||
### Building multipolygon 与 height
|
||||
|
||||
`parse_osm()` 会把 `type=multipolygon` 且 relation 自身带 `building=*` 的 relation
|
||||
追加为 synthetic way:
|
||||
|
||||
```python
|
||||
{
|
||||
"id": relation_id,
|
||||
"coords": outer_ring_lon_lat,
|
||||
"inner_coords": [inner_ring_lon_lat, ...],
|
||||
"tags": relation_tags,
|
||||
"source": "relation",
|
||||
}
|
||||
```
|
||||
|
||||
member way 不需要带 building tags;relation-level `height` / `building` 才是事实源。
|
||||
`generate_scene.py` 在 `handle_building()` 里把 `inner_coords` 投影后传给
|
||||
`building.assemble(..., inner_rings=...)`。`building.py` 用 Blender
|
||||
`tessellate_polygon()` 生成带洞的 prism 和 roof;没有 inner rings 时仍走原来的
|
||||
`MeshBatch` / `make_prism` 路径。
|
||||
|
||||
建筑高度语义:
|
||||
|
||||
- relation / way 上有合法显式 `height`:`render_height == height`
|
||||
- `building=industrial`:沿用 OSM height / 默认高度
|
||||
- 普通 building 没有显式 height 且低于 tall threshold:使用办公楼视觉默认 `11.4m`
|
||||
|
||||
不要再把有显式 `height=8` 的普通建筑压成 `11.4m`;用户手工编辑 OSM 高度时,OSM
|
||||
tag 是更高优先级的事实。
|
||||
|
||||
`roads.py` 是 road layer 装配模块,不拥有图层表或计数:
|
||||
|
||||
```python
|
||||
|
||||
@@ -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."}
|
||||
@@ -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."}
|
||||
39
.trellis/tasks/08-03-fix-osm-multipolygon-buildings/prd.md
Normal file
39
.trellis/tasks/08-03-fix-osm-multipolygon-buildings/prd.md
Normal file
@@ -0,0 +1,39 @@
|
||||
# Fix OSM multipolygon building rendering
|
||||
|
||||
## Goal
|
||||
|
||||
Fix Blender scene generation so OSM `type=multipolygon` building relations are
|
||||
rendered as buildings, and explicit OSM `height` tags are respected for normal
|
||||
buildings.
|
||||
|
||||
## Requirements
|
||||
|
||||
1. Parse OSM multipolygon relations whose relation tags include `building=*`.
|
||||
2. Use relation-level tags, including `height`, when generating the building.
|
||||
3. Support the common case of closed outer way members and closed inner way
|
||||
members, as seen in nantaizi `relation -65`.
|
||||
4. Keep existing way-based building rendering working.
|
||||
5. Change building height semantics so an explicit `height` tag controls render
|
||||
height; fallback office default `11.4m` should apply only when height is not
|
||||
explicitly present and the building is not industrial / tall.
|
||||
6. Validate only `nantaizi-lake-innovation-valley`.
|
||||
|
||||
## Acceptance Criteria
|
||||
|
||||
- [x] `parse_osm()` exposes the nantaizi building multipolygon relation as a
|
||||
renderable building feature.
|
||||
- [x] Unit tests cover relation-level building tags, outer/inner members, and
|
||||
malformed / incomplete relation members.
|
||||
- [x] Existing way-based building parsing tests still pass.
|
||||
- [x] A relation building with `height=8` renders at `8m`, not the office
|
||||
fallback `11.4m`.
|
||||
- [x] nantaizi Blender generation succeeds and includes the multipolygon
|
||||
building in counts / scene digest.
|
||||
- [x] No hanyang validation is required unless requested.
|
||||
|
||||
## Notes
|
||||
|
||||
- Concrete reproduction in current input:
|
||||
`relation -65` has `building=yes`, `height=8`, `type=multipolygon`, outer way
|
||||
`-2948`, and inner way `-2952`. The member ways carry no building tags, so the
|
||||
current way-only dispatcher skips it.
|
||||
@@ -0,0 +1,48 @@
|
||||
# Multipolygon building fix report
|
||||
|
||||
## Root Cause
|
||||
|
||||
The edited nantaizi OSM has `relation -65` with:
|
||||
|
||||
- `type=multipolygon`
|
||||
- `building=yes`
|
||||
- `height=8`
|
||||
- outer way `-2948`
|
||||
- inner way `-2952`
|
||||
|
||||
The member ways have no building tags. `parse_osm()` only exposed ways, and
|
||||
`generate_scene.py` only dispatched ways, so the relation was skipped.
|
||||
|
||||
The height issue had a second cause: ordinary non-industrial buildings below
|
||||
`30m` were forced to the office fallback render height `11.4m`, even when OSM
|
||||
had an explicit `height`.
|
||||
|
||||
## Implemented
|
||||
|
||||
- `parse_osm()` now exposes building multipolygon relations as synthetic
|
||||
building features with relation-level tags and `inner_coords`.
|
||||
- Open relation member way fragments can be stitched into closed rings; malformed
|
||||
relation leftovers are skipped without aborting parsing.
|
||||
- `generate_scene.py` passes projected `inner_rings` to building assembly.
|
||||
- `building.py` creates prism and roof meshes with holes using Blender
|
||||
tessellation.
|
||||
- Explicit OSM `height` now controls render height. The `11.4m` office fallback
|
||||
applies only when height is not explicit.
|
||||
|
||||
## Validation
|
||||
|
||||
- `python3 -m unittest discover blender/tests`
|
||||
- `npm run build:area -- --config config/areas/nantaizi-lake-innovation-valley.json --stages blender`
|
||||
- `npm run build:area -- --config config/areas/nantaizi-lake-innovation-valley.json --stages cesium`
|
||||
- `node scripts/glb-digest.js outputs/nantaizi-lake-innovation-valley/nantaizi-lake-innovation-valley.glb`
|
||||
- `python3 ./.trellis/scripts/task.py validate .trellis/tasks/08-03-fix-osm-multipolygon-buildings`
|
||||
- `git diff --check`
|
||||
|
||||
Confirmed `Building_-65` in scene and GLB:
|
||||
|
||||
- `osm_height=8`
|
||||
- `render_height=8`
|
||||
- GLB contains `Building_-65`, `Building_-65_Roof`, and
|
||||
`Building_-65_Windows`
|
||||
|
||||
User verified the result visually in preview.
|
||||
@@ -0,0 +1,26 @@
|
||||
{
|
||||
"id": "fix-osm-multipolygon-buildings",
|
||||
"name": "fix-osm-multipolygon-buildings",
|
||||
"title": "Fix OSM multipolygon building rendering",
|
||||
"description": "",
|
||||
"status": "in_progress",
|
||||
"dev_type": null,
|
||||
"scope": null,
|
||||
"package": null,
|
||||
"priority": "P2",
|
||||
"creator": "dingkang",
|
||||
"assignee": "dingkang",
|
||||
"createdAt": "2026-08-03",
|
||||
"completedAt": null,
|
||||
"branch": null,
|
||||
"base_branch": "main",
|
||||
"worktree_path": null,
|
||||
"commit": null,
|
||||
"pr_url": null,
|
||||
"subtasks": [],
|
||||
"children": [],
|
||||
"parent": null,
|
||||
"relatedFiles": [],
|
||||
"notes": "",
|
||||
"meta": {}
|
||||
}
|
||||
Reference in New Issue
Block a user