prepare-oldtown-dem.mjs 2.7 KB

12345678910111213141516171819202122232425262728293031323334353637383940414243444546474849505152535455565758596061
  1. // Expand old-town candidates into actual ENU footprints and bind them to original DEM samples.
  2. import { readFileSync, writeFileSync } from 'node:fs'
  3. import { Cartesian3, Cartographic, Matrix4, Transforms } from 'cesium'
  4. const dir = 'tools/city-massing/city/oldtown/',
  5. plan = JSON.parse(readFileSync(dir + 'plan.json'))
  6. const origin = { lon: 110.395, lat: 28.454 },
  7. transform = Transforms.eastNorthUpToFixedFrame(Cartesian3.fromDegrees(origin.lon, origin.lat)),
  8. inverse = Matrix4.inverseTransformation(transform, new Matrix4())
  9. const toLocal = (lon, lat) => Matrix4.multiplyByPoint(inverse, Cartesian3.fromDegrees(lon, lat), new Cartesian3())
  10. const toGeo = (q) => {
  11. const c = Cartographic.fromCartesian(Matrix4.multiplyByPoint(transform, new Cartesian3(...q), new Cartesian3()))
  12. return { lon: (c.longitude * 180) / Math.PI, lat: (c.latitude * 180) / Math.PI }
  13. }
  14. const modules = []
  15. const samples = []
  16. for (const b of plan.buildings) {
  17. const center = toLocal(b.lon, b.lat)
  18. const u = [Math.cos(b.heading), Math.sin(b.heading)],
  19. v = [-u[1], u[0]]
  20. const corners = [
  21. [-b.width / 2, -b.depth / 2],
  22. [b.width / 2, -b.depth / 2],
  23. [b.width / 2, b.depth / 2],
  24. [-b.width / 2, b.depth / 2],
  25. ].map(([x, y]) => [center.x + u[0] * x + v[0] * y, center.y + u[1] * x + v[1] * y, 0])
  26. const pts = [...corners, [center.x, center.y, 0]]
  27. // Interior samples at 5 m spacing so terrain relief is measured across the real footprint,
  28. // not inferred from four corners.
  29. const cols = Math.max(1, Math.ceil(b.width / 5))
  30. const rows = Math.max(1, Math.ceil(b.depth / 5))
  31. for (let row = 0; row <= rows; row++)
  32. for (let col = 0; col <= cols; col++) {
  33. const lx = -b.width / 2 + (b.width * col) / cols
  34. const ly = -b.depth / 2 + (b.depth * row) / rows
  35. pts.push([center.x + u[0] * lx + v[0] * ly, center.y + u[1] * lx + v[1] * ly, 0])
  36. }
  37. const sampleIndices = pts.map((p) => {
  38. samples.push({ ...toGeo(p), building: b.id })
  39. return samples.length - 1
  40. })
  41. modules.push({
  42. ...b,
  43. sourceId: b.sourceId || b.id,
  44. center: toGeo([center.x, center.y, 0]),
  45. centerENU: [center.x, center.y, 0],
  46. footprintENU: corners,
  47. archetype: b.archetype || 'house',
  48. sampleIndices,
  49. })
  50. }
  51. const out = {
  52. ...plan,
  53. origin,
  54. transform: Matrix4.toArray(transform),
  55. modules,
  56. samples,
  57. source: 'residential masks + bounded road/water exclusion + direct original DEM corners/center',
  58. disclaimer: plan.disclaimer + ' Terrain seating is validated separately against the original quantized mesh.',
  59. }
  60. writeFileSync(dir + 'plan.json', JSON.stringify(out, null, 2))
  61. console.log({ modules: modules.length, samples: samples.length })