// Expand old-town candidates into actual ENU footprints and bind them to original DEM samples. import { readFileSync, writeFileSync } from 'node:fs' import { Cartesian3, Cartographic, Matrix4, Transforms } from 'cesium' const dir = 'tools/city-massing/city/oldtown/', plan = JSON.parse(readFileSync(dir + 'plan.json')) const origin = { lon: 110.395, lat: 28.454 }, transform = Transforms.eastNorthUpToFixedFrame(Cartesian3.fromDegrees(origin.lon, origin.lat)), inverse = Matrix4.inverseTransformation(transform, new Matrix4()) const toLocal = (lon, lat) => Matrix4.multiplyByPoint(inverse, Cartesian3.fromDegrees(lon, lat), new Cartesian3()) const toGeo = (q) => { const c = Cartographic.fromCartesian(Matrix4.multiplyByPoint(transform, new Cartesian3(...q), new Cartesian3())) return { lon: (c.longitude * 180) / Math.PI, lat: (c.latitude * 180) / Math.PI } } const modules = [] const samples = [] for (const b of plan.buildings) { const center = toLocal(b.lon, b.lat) const u = [Math.cos(b.heading), Math.sin(b.heading)], v = [-u[1], u[0]] const corners = [ [-b.width / 2, -b.depth / 2], [b.width / 2, -b.depth / 2], [b.width / 2, b.depth / 2], [-b.width / 2, b.depth / 2], ].map(([x, y]) => [center.x + u[0] * x + v[0] * y, center.y + u[1] * x + v[1] * y, 0]) const pts = [...corners, [center.x, center.y, 0]] // Interior samples at 5 m spacing so terrain relief is measured across the real footprint, // not inferred from four corners. const cols = Math.max(1, Math.ceil(b.width / 5)) const rows = Math.max(1, Math.ceil(b.depth / 5)) for (let row = 0; row <= rows; row++) for (let col = 0; col <= cols; col++) { const lx = -b.width / 2 + (b.width * col) / cols const ly = -b.depth / 2 + (b.depth * row) / rows pts.push([center.x + u[0] * lx + v[0] * ly, center.y + u[1] * lx + v[1] * ly, 0]) } const sampleIndices = pts.map((p) => { samples.push({ ...toGeo(p), building: b.id }) return samples.length - 1 }) modules.push({ ...b, sourceId: b.sourceId || b.id, center: toGeo([center.x, center.y, 0]), centerENU: [center.x, center.y, 0], footprintENU: corners, archetype: b.archetype || 'house', sampleIndices, }) } const out = { ...plan, origin, transform: Matrix4.toArray(transform), modules, samples, source: 'residential masks + bounded road/water exclusion + direct original DEM corners/center', disclaimer: plan.disclaimer + ' Terrain seating is validated separately against the original quantized mesh.', } writeFileSync(dir + 'plan.json', JSON.stringify(out, null, 2)) console.log({ modules: modules.length, samples: samples.length })