summarize-city-coverage.mjs 4.2 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687
  1. // Truthful coverage ledger: source-backed, approximate and excluded artifacts stay separate.
  2. import { readFileSync, writeFileSync, existsSync } from 'node:fs'
  3. const read = (p) => JSON.parse(readFileSync(p, 'utf8'))
  4. const inventory = read('tools/city-massing/city/city-inventory.json')
  5. const regular = read('tools/city-massing/city/regular-estate/lod-preview/manifest.json')
  6. const oldtown = read('tools/city-massing/city/oldtown/lod-preview/manifest.json')
  7. const sample = read('tools/city-massing/district/lod-preview/manifest.json')
  8. const estatePlan = read('tools/city-massing/city/regular-estate/plan.json')
  9. const oldPlan = read('tools/city-massing/city/oldtown/plan.json')
  10. const seated = read('tools/city-massing/city/oldtown/seated-plan.json')
  11. const verification = existsSync('tools/city-massing/city/city-model-verification.json')
  12. ? read('tools/city-massing/city/city-model-verification.json')
  13. : null
  14. const imageryReport = existsSync('tools/city-massing/city/imagery-footprints-report.json')
  15. ? read('tools/city-massing/city/imagery-footprints-report.json')
  16. : null
  17. const placementSources = {}
  18. for (const building of oldPlan.buildings)
  19. placementSources[building.placementSource] = (placementSources[building.placementSource] || 0) + 1
  20. const construction = {}
  21. for (const building of seated.buildings)
  22. construction[building.construction || 'flat'] = (construction[building.construction || 'flat'] || 0) + 1
  23. const exclusionReasons = {}
  24. for (const entry of oldPlan.excluded) exclusionReasons[entry.reason] = (exclusionReasons[entry.reason] || 0) + 1
  25. const output = {
  26. bounds: inventory.bounds,
  27. sourceInventory: inventory.originalSources,
  28. districts: inventory.districts,
  29. sourceBacked: {
  30. scope: regular.scope,
  31. roofTraces: estatePlan.roofs.length,
  32. acceptedModules: regular.count,
  33. excludedModules: estatePlan.modules.length - regular.count,
  34. excludedReasons: 'original DEM relief >2.5m',
  35. },
  36. oldTownBacked: {
  37. scope: oldtown.scope,
  38. placementSources,
  39. footprintPlanned: oldPlan.buildings.length,
  40. footprintSources: oldPlan.sources.length,
  41. seated: seated.buildings.length,
  42. seatedSources: new Set(seated.buildings.map((b) => b.sourceId)).size,
  43. construction,
  44. excludedFootprints: oldPlan.excluded.length,
  45. excludedModules: seated.excluded.length,
  46. exclusionReasons,
  47. imagerySegmentation: imageryReport
  48. ? {
  49. accepted: imageryReport.totals.accepted,
  50. trustedMasks: [65, 66, 126],
  51. untrustedMasks: { 63: 'river/farmland sliver; greenhouse roofs', 130: 'industry park; bright yards read as roof' },
  52. limitations: imageryReport.limitations,
  53. }
  54. : null,
  55. },
  56. previousSample: { scope: sample.scope, accepted: sample.count },
  57. runtimeVerification: verification
  58. ? {
  59. passed: verification.passed,
  60. checkedAt: verification.checkedAt,
  61. trees: verification.summary,
  62. sourceConflictCount: verification.sourceConflictCount,
  63. }
  64. : null,
  65. runtime: {
  66. defaultModelEnabled: false,
  67. reviewUrl: 'http://127.0.0.1:8092/preview.html',
  68. lightingFlag: 'map.tilesets[].lighting=true',
  69. lod: 'coarse spatial parents with REPLACE LOD1/LOD0 children',
  70. },
  71. disclaimer:
  72. 'This ledger counts generated artifacts, not surveyed coverage. Heights and facades are estimated; imagery-segmented footprints are per-roof rectangles, not survey geometry; the estate source conflicts listed in city-model-verification.json still need a human decision.',
  73. }
  74. writeFileSync('tools/city-massing/city/coverage-summary.json', JSON.stringify(output, null, 2))
  75. console.log(
  76. JSON.stringify({
  77. regular: regular.count,
  78. oldtownPlaced: oldtown.count,
  79. oldtownSeatedSources: output.oldTownBacked.seatedSources,
  80. construction,
  81. imageryTrustedMasks: [65, 66, 126],
  82. omittedEstate: output.sourceBacked.excludedModules,
  83. omittedOldtownModules: seated.excluded.length,
  84. verificationPassed: verification?.passed ?? null,
  85. conflicts: verification?.sourceConflictCount ?? null,
  86. })
  87. )