seat-district.mjs 2.6 KB

1234567891011121314151617181920212223242526272829303132333435363738394041424344454647484950515253545556575859606162636465666768
  1. // Seats only measured footprint modules; excludes relief beyond the sample's foundation budget.
  2. import { readFileSync, writeFileSync } from 'node:fs'
  3. import { Cartesian3, Cartographic, Matrix4, Math as CM } from 'cesium'
  4. const dir = 'tools/city-massing/district/',
  5. p = JSON.parse(readFileSync(dir + 'plan.json'))
  6. const s = JSON.parse(readFileSync(dir + 'terrain-samples.json'))
  7. if (JSON.stringify(p.samples) !== JSON.stringify(s.planPoints))
  8. throw Error('DEM cache belongs to another footprint plan')
  9. const inv = Matrix4.inverseTransformation(Matrix4.fromArray(p.transform), new Matrix4())
  10. const buildings = [],
  11. excluded = [...p.excluded.map((x) => ({ ...x, stage: 'source-outline' }))]
  12. for (const m of p.modules) {
  13. const h = m.sampleIndices.map((i) => s.heights[i])
  14. if (h.some((x) => !Number.isFinite(x))) throw Error('Missing DEM ' + m.id)
  15. const min = Math.min(...h),
  16. max = Math.max(...h),
  17. range = max - min
  18. // Avoid fabricating a tall platform to compensate for a hillside footprint.
  19. if (range > 2.5) {
  20. excluded.push({
  21. id: m.id,
  22. reason: 'footprint relief exceeds 2.5m sample limit; needs stepped/custom model',
  23. range,
  24. })
  25. continue
  26. }
  27. const floorHeight = max - 0.25,
  28. foot = Matrix4.multiplyByPoint(
  29. inv,
  30. Cartesian3.fromDegrees(m.center.lon, m.center.lat, floorHeight),
  31. new Cartesian3()
  32. )
  33. const foundationBottom = Matrix4.multiplyByPoint(
  34. inv,
  35. Cartesian3.fromDegrees(m.center.lon, m.center.lat, min - 3),
  36. new Cartesian3()
  37. ).z
  38. const groundControls = m.sampleIndices.map((i) => {
  39. let q = p.samples[i]
  40. let v = Matrix4.multiplyByPoint(inv, Cartesian3.fromDegrees(q.lon, q.lat, s.heights[i]), new Cartesian3())
  41. return [v.x, v.y, v.z]
  42. })
  43. buildings.push({
  44. ...m,
  45. positionENU: [foot.x, foot.y, foot.z],
  46. floorHeight,
  47. terrainMin: min,
  48. terrainMax: max,
  49. terrainRelief: range,
  50. foundationBottom,
  51. groundControls,
  52. })
  53. }
  54. const report = {
  55. ...p,
  56. modules: undefined,
  57. samples: undefined,
  58. buildings,
  59. excluded,
  60. notice: 'Prototype neighborhood, individually traced/OSM footprints; facade and floor counts estimated. Steep modules explicitly excluded, not sunk or lifted.',
  61. }
  62. writeFileSync(dir + 'seated-plan.json', JSON.stringify(report, null, 2))
  63. console.log({
  64. buildings: buildings.length,
  65. sources: new Set(buildings.map((b) => b.sourceId)).size,
  66. excluded: excluded.length,
  67. maxVisibleFoundation: Math.max(...buildings.map((b) => b.floorHeight - b.terrainMin)).toFixed(2),
  68. })