Files
2026-05-27 23:29:53 +01:00

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 status reports nothing) and no in-flight worktrees with uncommitted work (git worktree list then git status in 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.md matches find packages -maxdepth 3 -name package.json count. Drift here means a prior change archived without regenerating the manifest.

1. Scaffold

  • Create universal/geo/osm-buildings/ with src/, tests/, src/__benchmarks__/ and all feature subdirectories per the layout in proposal.md.
  • Write package.json at version 0.1.0 with "type": "module", "sideEffects": false, correct exports map, dependencies, and devDependencies.
  • Write tsconfig.json extending @suaveplan/typescript-config/library.json.
  • Write vitest.config.ts using createTestConfig() from @suaveplan/testing/config.
  • Add lint, typecheck, test, test:coverage, build, docs:check scripts.

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 with building=* tag to footprints (outer ring from way nodes), handles multipolygon relations for buildings with holes. extractRoads(ways): OsmRoad[]: maps ways with highway=* 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); calls extractBuildings/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 using DataView; decodes string table; reconstructs nodes/ways; calls extractBuildings/extractRoads. Tests: small synthetic PBF buffer, bbox filter, malformed buffer throws OSM_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 to flat. 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, calls buildRoof. Optional elevation offset from HeightmapTileStore. LOD simplification via simplifyPolygon based on lodLevel. buildingsToMeshes maps 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, emits OsmBuilding | OsmRoad events. Tests: pipe a small OSM XML string, verify buildings/roads emitted, abort signal.

4. OTel instrumentation

  • Counter osm.buildings_parsed_total; span osm.parse with attribute osm.format (xml or pbf); histogram osm.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.indices is < vertices.length / 3.
  • Ramer-Douglas-Peucker: for arbitrary point arrays, output length ≤ input length.

6. Benchmarks (src/__benchmarks__/osm-buildings.bench.ts)

  • parseOsmXml for a 500 KB OSM XML file with 200 buildings.
  • buildingsToMeshes for 200 buildings.

7. Docs + verify

  • One .md per implementation file (≥200 words, both examples, API reference). Root README.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/.