// Seats only measured footprint modules; excludes relief beyond the sample's foundation budget. import { readFileSync, writeFileSync } from 'node:fs' import { Cartesian3, Cartographic, Matrix4, Math as CM } from 'cesium' const dir = 'tools/city-massing/district/', p = JSON.parse(readFileSync(dir + 'plan.json')) const s = JSON.parse(readFileSync(dir + 'terrain-samples.json')) if (JSON.stringify(p.samples) !== JSON.stringify(s.planPoints)) throw Error('DEM cache belongs to another footprint plan') const inv = Matrix4.inverseTransformation(Matrix4.fromArray(p.transform), new Matrix4()) const buildings = [], excluded = [...p.excluded.map((x) => ({ ...x, stage: 'source-outline' }))] for (const m of p.modules) { const h = m.sampleIndices.map((i) => s.heights[i]) if (h.some((x) => !Number.isFinite(x))) throw Error('Missing DEM ' + m.id) const min = Math.min(...h), max = Math.max(...h), range = max - min // Avoid fabricating a tall platform to compensate for a hillside footprint. if (range > 2.5) { excluded.push({ id: m.id, reason: 'footprint relief exceeds 2.5m sample limit; needs stepped/custom model', range, }) continue } const floorHeight = max - 0.25, foot = Matrix4.multiplyByPoint( inv, Cartesian3.fromDegrees(m.center.lon, m.center.lat, floorHeight), new Cartesian3() ) const foundationBottom = Matrix4.multiplyByPoint( inv, Cartesian3.fromDegrees(m.center.lon, m.center.lat, min - 3), new Cartesian3() ).z const groundControls = m.sampleIndices.map((i) => { let q = p.samples[i] let v = Matrix4.multiplyByPoint(inv, Cartesian3.fromDegrees(q.lon, q.lat, s.heights[i]), new Cartesian3()) return [v.x, v.y, v.z] }) buildings.push({ ...m, positionENU: [foot.x, foot.y, foot.z], floorHeight, terrainMin: min, terrainMax: max, terrainRelief: range, foundationBottom, groundControls, }) } const report = { ...p, modules: undefined, samples: undefined, buildings, excluded, notice: 'Prototype neighborhood, individually traced/OSM footprints; facade and floor counts estimated. Steep modules explicitly excluded, not sunk or lifted.', } writeFileSync(dir + 'seated-plan.json', JSON.stringify(report, null, 2)) console.log({ buildings: buildings.length, sources: new Set(buildings.map((b) => b.sourceId)).size, excluded: excluded.length, maxVisibleFoundation: Math.max(...buildings.map((b) => b.floorHeight - b.terrainMin)).toFixed(2), })