5.8 KiB
5.8 KiB
Tasks: enterprise-osm-buildings
Change ID: enterprise-osm-buildings
Package: @suaveplan/osm-buildings
Target path: universal/geo/osm-buildings
Prerequisite packages on Verdaccio: @suaveplan/error, @suaveplan/types, @suaveplan/geo, @suaveplan/heightmap-tiles, @suaveplan/serialize
0. Pre-flight (non-negotiable)
- 0.1 Confirm clean working tree (
git statusreports nothing) and no in-flight worktrees with uncommitted work (git worktree listthengit statusin each) - 0.2 If any worktrees exist under
.claude/worktrees/, account for each one: still-active agent, work-pending-merge, or stale-and-prune. Stale worktrees MUST be removed (git worktree remove) before opening a new one — code that has been lost twice on main is the failure mode this guards against. - 0.3 Confirm the package set in
MANIFEST.mdmatchesfind packages -maxdepth 3 -name package.jsoncount. Drift here means a prior change archived without regenerating the manifest.
1. Scaffold
- Create
universal/geo/osm-buildings/withsrc/,tests/,src/__benchmarks__/and all feature subdirectories per the layout inproposal.md. - Write
package.jsonat version0.1.0with"type": "module","sideEffects": false, correctexportsmap,dependencies, anddevDependencies. - Write
tsconfig.jsonextending@suaveplan/typescript-config/library.json. - Write
vitest.config.tsusingcreateTestConfig()from@suaveplan/testing/config. - Add
lint,typecheck,test,test:coverage,build,docs:checkscripts.
2. Types + errors
src/types/common.ts—OsmBuilding,RoofShape,OsmRoad,OsmFeatureSet,ParseOptions,BuildingMesh,MeshOptions,OsmFeatureStream.src/types/common.md(≥200 words, both examples, API reference).src/error-codes.ts—OsmBuildingsErrorCode(6 codes), metadata, register, eager call.src/types/schemas.ts+schemas.md— Zod schemas.
3. Core modules
src/geometry/simplifier/—simplifyPolygon(ring, toleranceMetres): LatLng[]: Ramer-Douglas-Peucker algorithm; tolerance in metres (converted to degrees via approximate factor).simplifyRoad(points, toleranceMetres): LatLng[]. Tests: tolerance=0 preserves all points, high tolerance reduces to 2-4 points, first and last points always preserved.src/parsers/feature-extractor/—extractBuildings(nodes, ways, relations): OsmBuilding[]: maps ways withbuilding=*tag to footprints (outer ring from way nodes), handles multipolygon relations for buildings with holes.extractRoads(ways): OsmRoad[]: maps ways withhighway=*tag. Tests: simple building way, multipolygon building with hole, road with name/lanes, skip ways without building/highway tags.src/parsers/osm-xml-parser/—parseOsmXml(xml, options): OsmFeatureSet: parses<node>,<way>,<relation>elements using a SAX-style incremental parser (no DOM dependency); callsextractBuildings/extractRoads. Tests: minimal OSM XML with 2 buildings and 1 road, bbox filter, signal abort mid-parse.src/parsers/osm-pbf-parser/—parseOsmPbfLite(buffer, options): OsmFeatureSet: parses OSM PBF blob header + primitive blocks usingDataView; decodes string table; reconstructs nodes/ways; callsextractBuildings/extractRoads. Tests: small synthetic PBF buffer, bbox filter, malformed buffer throwsOSM_PARSE_FAILED.src/mesh/roof-builder/—buildRoof(footprint, height, minHeight, roofShape): { vertices: number[], indices: number[] }: generates roof geometry.flat: single quad.pitched: ridge along long axis.hipped,pyramidal: point apex. Unknown shape falls back toflat. Tests: flat roof vertex count, pitched ridge runs along correct axis, all indices reference valid vertices.src/mesh/building-mesher/—buildingToMesh(building, options): BuildingMesh: triangulates footprint (ear clipping for convex, fan for convex shortcut), extrudes walls, callsbuildRoof. Optional elevation offset fromHeightmapTileStore. LOD simplification viasimplifyPolygonbased onlodLevel.buildingsToMeshesmaps over array. Tests: unit square footprint → correct vertex count, hole in footprint handled, LOD=2 reduces vertex count vs LOD=0.src/streaming/osm-feature-stream/—createOsmFeatureStream(options): OsmFeatureStream: Web Streams Transform; buffers bytes, detects format (XML starts with<, PBF has magic header), dispatches to appropriate parser incrementally, emitsOsmBuilding | OsmRoadevents. Tests: pipe a small OSM XML string, verify buildings/roads emitted, abort signal.
4. OTel instrumentation
- Counter
osm.buildings_parsed_total; spanosm.parsewith attributeosm.format(xmlorpbf); histogramosm.mesh_build_duration_ms.
5. Property-based tests (tests/property.test.ts)
- Simplification: for arbitrary polylines,
simplifyRoad(points, tolerance)never produces self-intersections and always preserves first/last point. - Mesh index validity: for arbitrary convex polygons as footprints, every index in
BuildingMesh.indicesis< vertices.length / 3. - Ramer-Douglas-Peucker: for arbitrary point arrays, output length ≤ input length.
6. Benchmarks (src/__benchmarks__/osm-buildings.bench.ts)
parseOsmXmlfor a 500 KB OSM XML file with 200 buildings.buildingsToMeshesfor 200 buildings.
7. Docs + verify
- One
.mdper implementation file (≥200 words, both examples, API reference). RootREADME.md. bunx turbo lint typecheck test --filter=@suaveplan/osm-buildings— green.bunx turbo test:coverage --filter=@suaveplan/osm-buildings— 100%.bun scripts/publish.ts --package @suaveplan/osm-buildings.- Archive:
openspec/archive/2026-04-24-enterprise-osm-buildings/.