seat-regular-estate.mjs 2.3 KB

12345678910111213141516171819202122232425262728293031323334353637383940414243444546474849505152535455565758
  1. // Seat estate modules on the original DEM; explicitly leave steep footprints for stepped construction.
  2. import { readFileSync, writeFileSync } from 'node:fs'
  3. import { Cartesian3, Matrix4 } from 'cesium'
  4. const dir = 'tools/city-massing/city/regular-estate/'
  5. const plan = JSON.parse(readFileSync(dir + 'plan.json'))
  6. const sampled = JSON.parse(readFileSync(dir + 'terrain-samples.json'))
  7. if (plan.samplesSha256 !== sampled.samplesSha256)
  8. throw new Error('DEM sample coordinates differ from the footprint plan')
  9. const inverse = Matrix4.inverseTransformation(Matrix4.fromArray(plan.transform), new Matrix4())
  10. const local = (lon, lat, height) =>
  11. Matrix4.multiplyByPoint(inverse, Cartesian3.fromDegrees(lon, lat, height), new Cartesian3())
  12. const buildings = [],
  13. excluded = []
  14. for (const module of plan.modules) {
  15. const heights = module.sampleIndices.map((i) => sampled.heights[i])
  16. if (heights.some((h) => !Number.isFinite(h))) throw new Error(`Missing original DEM sample: ${module.id}`)
  17. const min = Math.min(...heights),
  18. max = Math.max(...heights),
  19. relief = max - min
  20. if (relief > 2.5) {
  21. excluded.push({
  22. id: module.id,
  23. sourceId: module.sourceId,
  24. reason: 'DEM relief exceeds 2.5m; requires stepped construction',
  25. relief,
  26. })
  27. continue
  28. }
  29. const floorHeight = max - 0.25
  30. const floor = local(module.center.lon, module.center.lat, floorHeight)
  31. const bottom = local(module.center.lon, module.center.lat, min - 3)
  32. buildings.push({
  33. ...module,
  34. positionENU: [floor.x, floor.y, floor.z],
  35. foundationBottom: bottom.z,
  36. terrainMin: min,
  37. terrainMax: max,
  38. terrainRelief: relief,
  39. floorHeight,
  40. groundControls: module.sampleIndices.map((i) => [plan.samples[i].lon, plan.samples[i].lat, sampled.heights[i]]),
  41. })
  42. }
  43. const output = {
  44. ...plan,
  45. buildings,
  46. excluded,
  47. notice: plan.disclaimer + ' Steep modules are excluded from this straight-floor batch.',
  48. }
  49. writeFileSync(dir + 'seated-plan.json', JSON.stringify(output, null, 2))
  50. console.log(
  51. JSON.stringify({
  52. candidateModules: plan.modules.length,
  53. accepted: buildings.length,
  54. excluded: excluded.length,
  55. maxRelief: Math.max(...buildings.map((m) => m.terrainRelief), 0),
  56. })
  57. )