cad-shp-to-geojson.py 5.2 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113
  1. # -*- coding: utf-8 -*-
  2. """Convert the exported CAD building layer to GeoJSON and report what actually lands in the scene.
  3. Run with ArcGIS Desktop Python 2.7:
  4. python.exe tools/city-massing/cad-shp-to-geojson.py <inDir> <outDir> <west,south,east,north>
  5. Only features whose envelope centre falls in the given window are written, so the GeoJSON stays a
  6. scene-sized extract of the full survey rather than a county-wide dump.
  7. """
  8. import sys
  9. import os
  10. import io
  11. import json
  12. import arcpy
  13. def main():
  14. if len(sys.argv) < 4:
  15. print('usage: cad-shp-to-geojson.py <inDir> <outDir> <west,south,east,north>')
  16. return 2
  17. in_dir = os.path.abspath(sys.argv[1])
  18. out_dir = os.path.abspath(sys.argv[2])
  19. west, south, east, north = [float(v) for v in sys.argv[3].split(',')]
  20. if not os.path.isdir(out_dir):
  21. os.makedirs(out_dir)
  22. sources = [
  23. ('Polygon-JMD.shp', 'cad-buildings.geojson', 'polygon'),
  24. ('Annotation-JMD.shp', 'cad-building-labels.geojson', 'annotation'),
  25. ('Polyline-JMD.shp', 'cad-building-outlines.geojson', 'polyline'),
  26. ]
  27. report = {'window': [west, south, east, north], 'layers': []}
  28. for filename, out_name, kind in sources:
  29. path = os.path.join(in_dir, filename)
  30. if not os.path.exists(path):
  31. print('missing %s' % path)
  32. continue
  33. fields = [f.name for f in arcpy.ListFields(path)]
  34. text_field = 'TextString' if 'TextString' in fields else None
  35. shape_type = arcpy.Describe(path).shapeType
  36. features = []
  37. in_window = 0
  38. total = 0
  39. areas = []
  40. with arcpy.da.SearchCursor(path, ['SHAPE@'] + ([text_field] if text_field else [])) as cursor:
  41. for row in cursor:
  42. total += 1
  43. shape = row[0]
  44. if shape is None:
  45. continue
  46. extent = shape.extent
  47. cx = (extent.XMin + extent.XMax) / 2.0
  48. cy = (extent.YMin + extent.YMax) / 2.0
  49. if not (west <= cx <= east and south <= cy <= north):
  50. continue
  51. in_window += 1
  52. geometry = None
  53. if shape_type == 'Polygon' and kind == 'annotation':
  54. # Annotation geometry is the label box; the point is the useful location.
  55. anchor = shape.labelPoint if hasattr(shape, 'labelPoint') else None
  56. geometry = {'type': 'Point', 'coordinates': [round(anchor.X, 7), round(anchor.Y, 7)]} if anchor else None
  57. elif shape_type == 'Polygon':
  58. rings = []
  59. for i in range(shape.partCount):
  60. part = shape.getPart(i)
  61. ring = [[round(p.X, 7), round(p.Y, 7)] for p in part if p is not None]
  62. if len(ring) >= 4:
  63. rings.append(ring)
  64. if rings:
  65. geometry = {'type': 'Polygon', 'coordinates': [rings[0]] + [[r] for r in rings[1:]]}
  66. try:
  67. areas.append(float(shape.area))
  68. except Exception:
  69. pass
  70. elif shape_type == 'Polyline':
  71. lines = []
  72. for i in range(shape.partCount):
  73. part = shape.getPart(i)
  74. line = [[round(p.X, 7), round(p.Y, 7)] for p in part if p is not None]
  75. if len(line) >= 2:
  76. lines.append(line)
  77. if lines:
  78. geometry = {'type': 'MultiLineString', 'coordinates': lines}
  79. if geometry is None:
  80. continue
  81. props = {}
  82. if text_field:
  83. props['text'] = row[1]
  84. features.append({'type': 'Feature', 'properties': props, 'geometry': geometry})
  85. out_path = os.path.join(out_dir, out_name)
  86. with io.open(out_path, 'w', encoding='utf-8') as handle:
  87. handle.write(unicode(json.dumps({'type': 'FeatureCollection', 'features': features}, ensure_ascii=False)))
  88. entry = {'source': filename, 'out': out_name, 'total': total, 'inWindow': in_window, 'written': len(features)}
  89. if areas:
  90. areas.sort()
  91. pick = lambda q: round(areas[int(q * (len(areas) - 1))], 1)
  92. entry['areaSqm'] = {'min': round(areas[0], 1), 'p25': pick(.25), 'median': pick(.5),
  93. 'p75': pick(.75), 'p95': pick(.95), 'max': round(areas[-1], 1),
  94. 'under15': sum(1 for a in areas if a < 15),
  95. 'over2000': sum(1 for a in areas if a > 2000)}
  96. report['layers'].append(entry)
  97. print('%-22s total=%6d inWindow=%6d written=%6d' % (filename, total, in_window, len(features)))
  98. if 'areaSqm' in entry:
  99. print(' area %s' % json.dumps(entry['areaSqm']))
  100. with io.open(os.path.join(out_dir, 'cad-extract-report.json'), 'w', encoding='utf-8') as handle:
  101. handle.write(unicode(json.dumps(report, ensure_ascii=False, indent=2, sort_keys=True)))
  102. return 0
  103. if __name__ == '__main__':
  104. sys.exit(main())