voxcity 0.7.0__py3-none-any.whl → 1.0.13__py3-none-any.whl

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (81) hide show
  1. voxcity/__init__.py +14 -14
  2. voxcity/downloader/ocean.py +559 -0
  3. voxcity/exporter/__init__.py +12 -12
  4. voxcity/exporter/cityles.py +633 -633
  5. voxcity/exporter/envimet.py +733 -728
  6. voxcity/exporter/magicavoxel.py +333 -333
  7. voxcity/exporter/netcdf.py +238 -238
  8. voxcity/exporter/obj.py +1480 -1480
  9. voxcity/generator/__init__.py +47 -44
  10. voxcity/generator/api.py +727 -675
  11. voxcity/generator/grids.py +394 -379
  12. voxcity/generator/io.py +94 -94
  13. voxcity/generator/pipeline.py +582 -282
  14. voxcity/generator/update.py +429 -0
  15. voxcity/generator/voxelizer.py +18 -6
  16. voxcity/geoprocessor/__init__.py +75 -75
  17. voxcity/geoprocessor/draw.py +1494 -1219
  18. voxcity/geoprocessor/merge_utils.py +91 -91
  19. voxcity/geoprocessor/mesh.py +806 -806
  20. voxcity/geoprocessor/network.py +708 -708
  21. voxcity/geoprocessor/raster/__init__.py +2 -0
  22. voxcity/geoprocessor/raster/buildings.py +435 -428
  23. voxcity/geoprocessor/raster/core.py +31 -0
  24. voxcity/geoprocessor/raster/export.py +93 -93
  25. voxcity/geoprocessor/raster/landcover.py +178 -51
  26. voxcity/geoprocessor/raster/raster.py +1 -1
  27. voxcity/geoprocessor/utils.py +824 -824
  28. voxcity/models.py +115 -113
  29. voxcity/simulator/solar/__init__.py +66 -43
  30. voxcity/simulator/solar/integration.py +336 -336
  31. voxcity/simulator/solar/sky.py +668 -0
  32. voxcity/simulator/solar/temporal.py +792 -434
  33. voxcity/simulator_gpu/__init__.py +115 -0
  34. voxcity/simulator_gpu/common/__init__.py +9 -0
  35. voxcity/simulator_gpu/common/geometry.py +11 -0
  36. voxcity/simulator_gpu/core.py +322 -0
  37. voxcity/simulator_gpu/domain.py +262 -0
  38. voxcity/simulator_gpu/environment.yml +11 -0
  39. voxcity/simulator_gpu/init_taichi.py +154 -0
  40. voxcity/simulator_gpu/integration.py +15 -0
  41. voxcity/simulator_gpu/kernels.py +56 -0
  42. voxcity/simulator_gpu/radiation.py +28 -0
  43. voxcity/simulator_gpu/raytracing.py +623 -0
  44. voxcity/simulator_gpu/sky.py +9 -0
  45. voxcity/simulator_gpu/solar/__init__.py +178 -0
  46. voxcity/simulator_gpu/solar/core.py +66 -0
  47. voxcity/simulator_gpu/solar/csf.py +1249 -0
  48. voxcity/simulator_gpu/solar/domain.py +561 -0
  49. voxcity/simulator_gpu/solar/epw.py +421 -0
  50. voxcity/simulator_gpu/solar/integration.py +2953 -0
  51. voxcity/simulator_gpu/solar/radiation.py +3019 -0
  52. voxcity/simulator_gpu/solar/raytracing.py +686 -0
  53. voxcity/simulator_gpu/solar/reflection.py +533 -0
  54. voxcity/simulator_gpu/solar/sky.py +907 -0
  55. voxcity/simulator_gpu/solar/solar.py +337 -0
  56. voxcity/simulator_gpu/solar/svf.py +446 -0
  57. voxcity/simulator_gpu/solar/volumetric.py +1151 -0
  58. voxcity/simulator_gpu/solar/voxcity.py +2953 -0
  59. voxcity/simulator_gpu/temporal.py +13 -0
  60. voxcity/simulator_gpu/utils.py +25 -0
  61. voxcity/simulator_gpu/view.py +32 -0
  62. voxcity/simulator_gpu/visibility/__init__.py +109 -0
  63. voxcity/simulator_gpu/visibility/geometry.py +278 -0
  64. voxcity/simulator_gpu/visibility/integration.py +808 -0
  65. voxcity/simulator_gpu/visibility/landmark.py +753 -0
  66. voxcity/simulator_gpu/visibility/view.py +944 -0
  67. voxcity/utils/__init__.py +11 -0
  68. voxcity/utils/classes.py +194 -0
  69. voxcity/utils/lc.py +80 -39
  70. voxcity/utils/shape.py +230 -0
  71. voxcity/visualizer/__init__.py +24 -24
  72. voxcity/visualizer/builder.py +43 -43
  73. voxcity/visualizer/grids.py +141 -141
  74. voxcity/visualizer/maps.py +187 -187
  75. voxcity/visualizer/renderer.py +1146 -928
  76. {voxcity-0.7.0.dist-info → voxcity-1.0.13.dist-info}/METADATA +56 -52
  77. voxcity-1.0.13.dist-info/RECORD +116 -0
  78. voxcity-0.7.0.dist-info/RECORD +0 -77
  79. {voxcity-0.7.0.dist-info → voxcity-1.0.13.dist-info}/WHEEL +0 -0
  80. {voxcity-0.7.0.dist-info → voxcity-1.0.13.dist-info}/licenses/AUTHORS.rst +0 -0
  81. {voxcity-0.7.0.dist-info → voxcity-1.0.13.dist-info}/licenses/LICENSE +0 -0
@@ -1,379 +1,394 @@
1
- import os
2
- import numpy as np
3
-
4
- from ..downloader.mbfp import get_mbfp_gdf
5
- from ..downloader.osm import load_gdf_from_openstreetmap, load_land_cover_gdf_from_osm
6
- from ..downloader.oemj import save_oemj_as_geotiff
7
- from ..downloader.eubucco import load_gdf_from_eubucco
8
- from ..downloader.overture import load_gdf_from_overture
9
- from ..downloader.gba import load_gdf_from_gba
10
-
11
- from ..downloader.gee import (
12
- initialize_earth_engine,
13
- get_roi,
14
- get_ee_image_collection,
15
- get_ee_image,
16
- save_geotiff,
17
- get_dem_image,
18
- save_geotiff_esa_land_cover,
19
- save_geotiff_esri_landcover,
20
- save_geotiff_dynamic_world_v1,
21
- save_geotiff_open_buildings_temporal,
22
- save_geotiff_dsm_minus_dtm,
23
- )
24
-
25
- from ..geoprocessor.raster import (
26
- process_grid,
27
- create_land_cover_grid_from_geotiff_polygon,
28
- create_height_grid_from_geotiff_polygon,
29
- create_building_height_grid_from_gdf_polygon,
30
- create_dem_grid_from_geotiff_polygon,
31
- create_land_cover_grid_from_gdf_polygon,
32
- create_building_height_grid_from_open_building_temporal_polygon,
33
- create_canopy_grids_from_tree_gdf,
34
- )
35
-
36
- from ..utils.lc import convert_land_cover_array, get_land_cover_classes
37
- from ..geoprocessor.io import get_gdf_from_gpkg
38
- from ..visualizer.grids import visualize_land_cover_grid, visualize_numerical_grid
39
-
40
-
41
- # Track last effective land cover source to help downstream components (e.g., voxelizer)
42
- _LAST_EFFECTIVE_LC_SOURCE = None
43
-
44
- def get_last_effective_land_cover_source():
45
- return _LAST_EFFECTIVE_LC_SOURCE
46
-
47
-
48
- def get_land_cover_grid(rectangle_vertices, meshsize, source, output_dir, **kwargs):
49
- print("Creating Land Use Land Cover grid\n ")
50
- print(f"Data source: {source}")
51
-
52
- if source not in ["OpenStreetMap", "OpenEarthMapJapan"]:
53
- try:
54
- initialize_earth_engine()
55
- except Exception as e:
56
- print("Earth Engine unavailable (", str(e), ") — falling back to OpenStreetMap for land cover.")
57
- source = 'OpenStreetMap'
58
-
59
- os.makedirs(output_dir, exist_ok=True)
60
- geotiff_path = os.path.join(output_dir, "land_cover.tif")
61
-
62
- # Track effective source to allow fallback behavior
63
- effective_source = source
64
-
65
- if source == 'Urbanwatch':
66
- roi = get_roi(rectangle_vertices)
67
- collection_name = "projects/sat-io/open-datasets/HRLC/urban-watch-cities"
68
- try:
69
- image = get_ee_image_collection(collection_name, roi)
70
- # If collection is empty, image operations may fail; guard with try/except
71
- save_geotiff(image, geotiff_path)
72
- if (not os.path.exists(geotiff_path)) or (os.path.getsize(geotiff_path) == 0):
73
- raise RuntimeError("Urbanwatch export produced no file")
74
- except Exception as e:
75
- print("Urbanwatch coverage not found for AOI; falling back to OpenStreetMap (reason:", str(e), ")")
76
- effective_source = 'OpenStreetMap'
77
- land_cover_gdf = load_land_cover_gdf_from_osm(rectangle_vertices)
78
- elif source == 'ESA WorldCover':
79
- roi = get_roi(rectangle_vertices)
80
- save_geotiff_esa_land_cover(roi, geotiff_path)
81
- elif source == 'ESRI 10m Annual Land Cover':
82
- esri_landcover_year = kwargs.get("esri_landcover_year")
83
- roi = get_roi(rectangle_vertices)
84
- save_geotiff_esri_landcover(roi, geotiff_path, year=esri_landcover_year)
85
- elif source == 'Dynamic World V1':
86
- dynamic_world_date = kwargs.get("dynamic_world_date")
87
- roi = get_roi(rectangle_vertices)
88
- save_geotiff_dynamic_world_v1(roi, geotiff_path, dynamic_world_date)
89
- elif source == 'OpenEarthMapJapan':
90
- ssl_verify = kwargs.get('ssl_verify', kwargs.get('verify', True))
91
- allow_insecure_ssl = kwargs.get('allow_insecure_ssl', False)
92
- allow_http_fallback = kwargs.get('allow_http_fallback', False)
93
- timeout_s = kwargs.get('timeout', 30)
94
-
95
- save_oemj_as_geotiff(
96
- rectangle_vertices,
97
- geotiff_path,
98
- ssl_verify=ssl_verify,
99
- allow_insecure_ssl=allow_insecure_ssl,
100
- allow_http_fallback=allow_http_fallback,
101
- timeout_s=timeout_s,
102
- )
103
- if not os.path.exists(geotiff_path):
104
- raise FileNotFoundError(
105
- f"OEMJ download failed; expected GeoTIFF not found: {geotiff_path}. "
106
- "You can try setting ssl_verify=False or allow_http_fallback=True in kwargs."
107
- )
108
- elif source == 'OpenStreetMap':
109
- land_cover_gdf = load_land_cover_gdf_from_osm(rectangle_vertices)
110
-
111
- land_cover_classes = get_land_cover_classes(effective_source)
112
-
113
- if effective_source == 'OpenStreetMap':
114
- default_class = kwargs.get('default_land_cover_class', 'Developed space')
115
- land_cover_grid_str = create_land_cover_grid_from_gdf_polygon(land_cover_gdf, meshsize, effective_source, rectangle_vertices, default_class=default_class)
116
- else:
117
- land_cover_grid_str = create_land_cover_grid_from_geotiff_polygon(geotiff_path, meshsize, land_cover_classes, rectangle_vertices)
118
-
119
- color_map = {cls: [r/255, g/255, b/255] for (r,g,b), cls in land_cover_classes.items()}
120
-
121
- grid_vis = kwargs.get("gridvis", True)
122
- if grid_vis:
123
- visualize_land_cover_grid(np.flipud(land_cover_grid_str), meshsize, color_map, land_cover_classes)
124
-
125
- # Record effective source for downstream consumers
126
- global _LAST_EFFECTIVE_LC_SOURCE
127
- _LAST_EFFECTIVE_LC_SOURCE = effective_source
128
-
129
- land_cover_grid_int = convert_land_cover_array(land_cover_grid_str, land_cover_classes)
130
- return land_cover_grid_int
131
-
132
-
133
- def get_building_height_grid(rectangle_vertices, meshsize, source, output_dir, building_gdf=None, **kwargs):
134
- ee_required_sources = {"Open Building 2.5D Temporal"}
135
- if source in ee_required_sources:
136
- initialize_earth_engine()
137
-
138
- print("Creating Building Height grid\n ")
139
- print(f"Base data source: {source}")
140
-
141
- os.makedirs(output_dir, exist_ok=True)
142
-
143
- if building_gdf is not None:
144
- gdf = building_gdf
145
- print("Using provided GeoDataFrame for building data")
146
- else:
147
- floor_height = kwargs.get("floor_height", 3.0)
148
- if source == 'Microsoft Building Footprints':
149
- gdf = get_mbfp_gdf(output_dir, rectangle_vertices)
150
- elif source == 'OpenStreetMap':
151
- gdf = load_gdf_from_openstreetmap(rectangle_vertices, floor_height=floor_height)
152
- elif source == "Open Building 2.5D Temporal":
153
- building_height_grid, building_min_height_grid, building_id_grid, filtered_buildings = create_building_height_grid_from_open_building_temporal_polygon(meshsize, rectangle_vertices, output_dir)
154
- elif source == 'EUBUCCO v0.1':
155
- gdf = load_gdf_from_eubucco(rectangle_vertices, output_dir)
156
- elif source == "Overture":
157
- gdf = load_gdf_from_overture(rectangle_vertices, floor_height=floor_height)
158
- elif source in ("GBA", "Global Building Atlas"):
159
- clip_gba = kwargs.get("gba_clip", False)
160
- gba_download_dir = kwargs.get("gba_download_dir")
161
- gdf = load_gdf_from_gba(rectangle_vertices, download_dir=gba_download_dir, clip_to_rectangle=clip_gba)
162
- elif source == "Local file":
163
- _, extension = os.path.splitext(kwargs["building_path"])
164
- if extension == ".gpkg":
165
- gdf = get_gdf_from_gpkg(kwargs["building_path"], rectangle_vertices)
166
- elif source == "GeoDataFrame":
167
- raise ValueError("When source is 'GeoDataFrame', building_gdf parameter must be provided")
168
-
169
- building_complementary_source = kwargs.get("building_complementary_source")
170
- try:
171
- comp_label = building_complementary_source if building_complementary_source not in (None, "") else "None"
172
- print(f"Complementary data source: {comp_label}")
173
- except Exception:
174
- pass
175
- building_complement_height = kwargs.get("building_complement_height")
176
- overlapping_footprint = kwargs.get("overlapping_footprint", "auto")
177
-
178
- if (building_complementary_source is None) or (building_complementary_source=='None'):
179
- if source != "Open Building 2.5D Temporal":
180
- building_height_grid, building_min_height_grid, building_id_grid, filtered_buildings = create_building_height_grid_from_gdf_polygon(gdf, meshsize, rectangle_vertices, complement_height=building_complement_height, overlapping_footprint=overlapping_footprint)
181
- else:
182
- if building_complementary_source == "Open Building 2.5D Temporal":
183
- try:
184
- roi = get_roi(rectangle_vertices)
185
- os.makedirs(output_dir, exist_ok=True)
186
- geotiff_path_comp = os.path.join(output_dir, "building_height.tif")
187
- save_geotiff_open_buildings_temporal(roi, geotiff_path_comp)
188
- building_height_grid, building_min_height_grid, building_id_grid, filtered_buildings = create_building_height_grid_from_gdf_polygon(gdf, meshsize, rectangle_vertices, geotiff_path_comp=geotiff_path_comp, complement_height=building_complement_height, overlapping_footprint=overlapping_footprint)
189
- except Exception as e:
190
- print("Open Building 2.5D Temporal requires Earth Engine (", str(e), ") — proceeding without complementary raster.")
191
- building_height_grid, building_min_height_grid, building_id_grid, filtered_buildings = create_building_height_grid_from_gdf_polygon(gdf, meshsize, rectangle_vertices, complement_height=building_complement_height, overlapping_footprint=overlapping_footprint)
192
- elif building_complementary_source in ["England 1m DSM - DTM", "Netherlands 0.5m DSM - DTM"]:
193
- try:
194
- roi = get_roi(rectangle_vertices)
195
- os.makedirs(output_dir, exist_ok=True)
196
- geotiff_path_comp = os.path.join(output_dir, "building_height.tif")
197
- save_geotiff_dsm_minus_dtm(roi, geotiff_path_comp, meshsize, building_complementary_source)
198
- building_height_grid, building_min_height_grid, building_id_grid, filtered_buildings = create_building_height_grid_from_gdf_polygon(gdf, meshsize, rectangle_vertices, geotiff_path_comp=geotiff_path_comp, complement_height=building_complement_height, overlapping_footprint=overlapping_footprint)
199
- except Exception as e:
200
- print("DSM-DTM complementary raster requires Earth Engine (", str(e), ") — proceeding without complementary raster.")
201
- building_height_grid, building_min_height_grid, building_id_grid, filtered_buildings = create_building_height_grid_from_gdf_polygon(gdf, meshsize, rectangle_vertices, complement_height=building_complement_height, overlapping_footprint=overlapping_footprint)
202
- else:
203
- if building_complementary_source == 'Microsoft Building Footprints':
204
- gdf_comp = get_mbfp_gdf(output_dir, rectangle_vertices)
205
- elif building_complementary_source == 'OpenStreetMap':
206
- gdf_comp = load_gdf_from_openstreetmap(rectangle_vertices, floor_height=floor_height)
207
- elif building_complementary_source == 'EUBUCCO v0.1':
208
- gdf_comp = load_gdf_from_eubucco(rectangle_vertices, output_dir)
209
- elif building_complementary_source == "Overture":
210
- gdf_comp = load_gdf_from_overture(rectangle_vertices, floor_height=floor_height)
211
- elif building_complementary_source in ("GBA", "Global Building Atlas"):
212
- clip_gba = kwargs.get("gba_clip", False)
213
- gba_download_dir = kwargs.get("gba_download_dir")
214
- gdf_comp = load_gdf_from_gba(rectangle_vertices, download_dir=gba_download_dir, clip_to_rectangle=clip_gba)
215
- elif building_complementary_source == "Local file":
216
- _, extension = os.path.splitext(kwargs["building_complementary_path"])
217
- if extension == ".gpkg":
218
- gdf_comp = get_gdf_from_gpkg(kwargs["building_complementary_path"], rectangle_vertices)
219
-
220
- complement_building_footprints = kwargs.get("complement_building_footprints")
221
- building_height_grid, building_min_height_grid, building_id_grid, filtered_buildings = create_building_height_grid_from_gdf_polygon(gdf, meshsize, rectangle_vertices, gdf_comp=gdf_comp, complement_building_footprints=complement_building_footprints, complement_height=building_complement_height, overlapping_footprint=overlapping_footprint)
222
-
223
- grid_vis = kwargs.get("gridvis", True)
224
- if grid_vis:
225
- building_height_grid_nan = building_height_grid.copy()
226
- building_height_grid_nan[building_height_grid_nan == 0] = np.nan
227
- visualize_numerical_grid(np.flipud(building_height_grid_nan), meshsize, "building height (m)", cmap='viridis', label='Value')
228
-
229
- return building_height_grid, building_min_height_grid, building_id_grid, filtered_buildings
230
-
231
-
232
- def get_canopy_height_grid(rectangle_vertices, meshsize, source, output_dir, **kwargs):
233
- print("Creating Canopy Height grid\n ")
234
- print(f"Data source: {source}")
235
-
236
- os.makedirs(output_dir, exist_ok=True)
237
-
238
- # Explicit static path (no EE): use land cover mask with static height
239
- if source == 'Static':
240
- land_cover_grid = kwargs.get('land_cover_like')
241
- if land_cover_grid is None:
242
- # Minimal fallback if caller didn't provide land_cover_like
243
- canopy_top = np.zeros((1, 1), dtype=float)
244
- trunk_height_ratio = kwargs.get('trunk_height_ratio')
245
- if trunk_height_ratio is None:
246
- trunk_height_ratio = 11.76 / 19.98
247
- canopy_bottom = canopy_top * float(trunk_height_ratio)
248
- return canopy_top, canopy_bottom
249
-
250
- from ..utils.lc import get_land_cover_classes
251
- land_cover_source = kwargs.get('land_cover_source', 'OpenStreetMap')
252
- classes_map = get_land_cover_classes(land_cover_source)
253
- class_to_int = {name: i for i, name in enumerate(classes_map.values())}
254
- tree_labels = ["Tree", "Trees", "Tree Canopy"]
255
- tree_indices = [class_to_int[label] for label in tree_labels if label in class_to_int]
256
-
257
- canopy_top = np.zeros_like(land_cover_grid, dtype=float)
258
- static_tree_height = kwargs.get('static_tree_height', 10.0)
259
- tree_mask = np.isin(land_cover_grid, tree_indices) if tree_indices else np.zeros_like(land_cover_grid, dtype=bool)
260
- canopy_top[tree_mask] = static_tree_height
261
-
262
- trunk_height_ratio = kwargs.get('trunk_height_ratio')
263
- if trunk_height_ratio is None:
264
- trunk_height_ratio = 11.76 / 19.98
265
- canopy_bottom = canopy_top * float(trunk_height_ratio)
266
-
267
- grid_vis = kwargs.get("gridvis", True)
268
- if grid_vis:
269
- vis = canopy_top.copy(); vis[vis == 0] = np.nan
270
- visualize_numerical_grid(np.flipud(vis), meshsize, "Tree canopy height (top)", cmap='Greens', label='Tree canopy height (m)')
271
- return canopy_top, canopy_bottom
272
-
273
- if source in ('GeoDataFrame', 'tree_gdf', 'Tree_GeoDataFrame', 'GDF'):
274
- tree_gdf = kwargs.get('tree_gdf')
275
- tree_gdf_path = kwargs.get('tree_gdf_path')
276
- if tree_gdf is None and tree_gdf_path is not None:
277
- _, ext = os.path.splitext(tree_gdf_path)
278
- if ext.lower() == '.gpkg':
279
- tree_gdf = get_gdf_from_gpkg(tree_gdf_path, rectangle_vertices)
280
- else:
281
- raise ValueError("Unsupported tree file format. Use .gpkg or pass a GeoDataFrame.")
282
- if tree_gdf is None:
283
- raise ValueError("When source='GeoDataFrame', provide 'tree_gdf' or 'tree_gdf_path'.")
284
-
285
- canopy_top, canopy_bottom = create_canopy_grids_from_tree_gdf(tree_gdf, meshsize, rectangle_vertices)
286
-
287
- grid_vis = kwargs.get("gridvis", True)
288
- if grid_vis:
289
- vis = canopy_top.copy()
290
- vis[vis == 0] = np.nan
291
- visualize_numerical_grid(np.flipud(vis), meshsize, "Tree canopy height (top)", cmap='Greens', label='Tree canopy height (m)')
292
-
293
- return canopy_top, canopy_bottom
294
-
295
- try:
296
- initialize_earth_engine()
297
- except Exception as e:
298
- print("Earth Engine unavailable (", str(e), ") — falling back to Static canopy heights.")
299
- # Re-enter with explicit Static logic using land cover mask
300
- return get_canopy_height_grid(rectangle_vertices, meshsize, 'Static', output_dir, **kwargs)
301
-
302
- geotiff_path = os.path.join(output_dir, "canopy_height.tif")
303
-
304
- roi = get_roi(rectangle_vertices)
305
- if source == 'High Resolution 1m Global Canopy Height Maps':
306
- collection_name = "projects/meta-forest-monitoring-okw37/assets/CanopyHeight"
307
- image = get_ee_image_collection(collection_name, roi)
308
- elif source == 'ETH Global Sentinel-2 10m Canopy Height (2020)':
309
- collection_name = "users/nlang/ETH_GlobalCanopyHeight_2020_10m_v1"
310
- image = get_ee_image(collection_name, roi)
311
- else:
312
- raise ValueError(f"Unsupported canopy source: {source}")
313
-
314
- save_geotiff(image, geotiff_path, resolution=meshsize)
315
- canopy_height_grid = create_height_grid_from_geotiff_polygon(geotiff_path, meshsize, rectangle_vertices)
316
-
317
- trunk_height_ratio = kwargs.get("trunk_height_ratio")
318
- if trunk_height_ratio is None:
319
- trunk_height_ratio = 11.76 / 19.98
320
- canopy_bottom_grid = canopy_height_grid * float(trunk_height_ratio)
321
-
322
- grid_vis = kwargs.get("gridvis", True)
323
- if grid_vis:
324
- canopy_height_grid_nan = canopy_height_grid.copy()
325
- canopy_height_grid_nan[canopy_height_grid_nan == 0] = np.nan
326
- visualize_numerical_grid(np.flipud(canopy_height_grid_nan), meshsize, "Tree canopy height", cmap='Greens', label='Tree canopy height (m)')
327
- return canopy_height_grid, canopy_bottom_grid
328
-
329
-
330
- def get_dem_grid(rectangle_vertices, meshsize, source, output_dir, **kwargs):
331
- print("Creating Digital Elevation Model (DEM) grid\n ")
332
- print(f"Data source: {source}")
333
-
334
- if source == "Local file":
335
- geotiff_path = kwargs["dem_path"]
336
- else:
337
- try:
338
- initialize_earth_engine()
339
- except Exception as e:
340
- print("Earth Engine unavailable (", str(e), ") — falling back to flat DEM.")
341
- dem_interpolation = kwargs.get("dem_interpolation")
342
- # Return flat DEM (zeros) with same shape as would be produced after rasterization
343
- # We defer to downstream to handle zeros appropriately.
344
- # To avoid shape inference here, we'll build after default path below.
345
- geotiff_path = None
346
- # Bypass EE path and produce zeros later
347
- dem_grid = np.zeros((1, 1), dtype=float)
348
- # Build shape using land cover grid shape if provided via kwargs for robustness
349
- lc_like = kwargs.get("land_cover_like")
350
- if lc_like is not None:
351
- dem_grid = np.zeros_like(lc_like)
352
- return dem_grid
353
-
354
- geotiff_path = os.path.join(output_dir, "dem.tif")
355
-
356
- buffer_distance = 100
357
- roi = get_roi(rectangle_vertices)
358
- roi_buffered = roi.buffer(buffer_distance)
359
-
360
- image = get_dem_image(roi_buffered, source)
361
-
362
- if source in ["England 1m DTM", 'DEM France 1m', 'DEM France 5m', 'AUSTRALIA 5M DEM', 'Netherlands 0.5m DTM']:
363
- save_geotiff(image, geotiff_path, scale=meshsize, region=roi_buffered, crs='EPSG:4326')
364
- elif source == 'USGS 3DEP 1m':
365
- scale = max(meshsize, 1.25)
366
- save_geotiff(image, geotiff_path, scale=scale, region=roi_buffered, crs='EPSG:4326')
367
- else:
368
- save_geotiff(image, geotiff_path, scale=30, region=roi_buffered)
369
-
370
- dem_interpolation = kwargs.get("dem_interpolation")
371
- dem_grid = create_dem_grid_from_geotiff_polygon(geotiff_path, meshsize, rectangle_vertices, dem_interpolation=dem_interpolation)
372
-
373
- grid_vis = kwargs.get("gridvis", True)
374
- if grid_vis:
375
- visualize_numerical_grid(np.flipud(dem_grid), meshsize, title='Digital Elevation Model', cmap='terrain', label='Elevation (m)')
376
-
377
- return dem_grid
378
-
379
-
1
+ import os
2
+ import numpy as np
3
+
4
+ from ..downloader.mbfp import get_mbfp_gdf
5
+ from ..downloader.osm import load_gdf_from_openstreetmap, load_land_cover_gdf_from_osm
6
+ from ..downloader.oemj import save_oemj_as_geotiff
7
+ from ..downloader.eubucco import load_gdf_from_eubucco
8
+ from ..downloader.overture import load_gdf_from_overture
9
+ from ..downloader.gba import load_gdf_from_gba
10
+
11
+ from ..downloader.gee import (
12
+ initialize_earth_engine,
13
+ get_roi,
14
+ get_ee_image_collection,
15
+ get_ee_image,
16
+ save_geotiff,
17
+ get_dem_image,
18
+ save_geotiff_esa_land_cover,
19
+ save_geotiff_esri_landcover,
20
+ save_geotiff_dynamic_world_v1,
21
+ save_geotiff_open_buildings_temporal,
22
+ save_geotiff_dsm_minus_dtm,
23
+ )
24
+
25
+ from ..geoprocessor.raster import (
26
+ process_grid,
27
+ create_land_cover_grid_from_geotiff_polygon,
28
+ create_height_grid_from_geotiff_polygon,
29
+ create_building_height_grid_from_gdf_polygon,
30
+ create_dem_grid_from_geotiff_polygon,
31
+ create_land_cover_grid_from_gdf_polygon,
32
+ create_building_height_grid_from_open_building_temporal_polygon,
33
+ create_canopy_grids_from_tree_gdf,
34
+ )
35
+
36
+ from ..utils.lc import convert_land_cover_array, get_land_cover_classes, get_source_class_descriptions
37
+ from ..geoprocessor.io import get_gdf_from_gpkg
38
+ from ..visualizer.grids import visualize_land_cover_grid, visualize_numerical_grid
39
+
40
+
41
+ # Track last effective land cover source to help downstream components (e.g., voxelizer)
42
+ _LAST_EFFECTIVE_LC_SOURCE = None
43
+
44
+ def get_last_effective_land_cover_source():
45
+ return _LAST_EFFECTIVE_LC_SOURCE
46
+
47
+
48
+ def get_land_cover_grid(rectangle_vertices, meshsize, source, output_dir, print_class_info=True, **kwargs):
49
+ quiet = kwargs.get('quiet', False)
50
+ if not quiet:
51
+ print("Creating Land Use Land Cover grid\n ")
52
+ print(f"Data source: {source}")
53
+ if print_class_info:
54
+ print(get_source_class_descriptions(source))
55
+
56
+ if source not in ["OpenStreetMap", "OpenEarthMapJapan"]:
57
+ try:
58
+ initialize_earth_engine()
59
+ except Exception as e:
60
+ if not quiet:
61
+ print("Earth Engine unavailable (", str(e), ") — falling back to OpenStreetMap for land cover.")
62
+ source = 'OpenStreetMap'
63
+
64
+ os.makedirs(output_dir, exist_ok=True)
65
+ geotiff_path = os.path.join(output_dir, "land_cover.tif")
66
+
67
+ # Track effective source to allow fallback behavior
68
+ effective_source = source
69
+
70
+ if source == 'Urbanwatch':
71
+ roi = get_roi(rectangle_vertices)
72
+ collection_name = "projects/sat-io/open-datasets/HRLC/urban-watch-cities"
73
+ try:
74
+ image = get_ee_image_collection(collection_name, roi)
75
+ # If collection is empty, image operations may fail; guard with try/except
76
+ save_geotiff(image, geotiff_path, scale=meshsize, region=roi, crs='EPSG:4326')
77
+ if (not os.path.exists(geotiff_path)) or (os.path.getsize(geotiff_path) == 0):
78
+ raise RuntimeError("Urbanwatch export produced no file")
79
+ except Exception as e:
80
+ if not quiet:
81
+ print("Urbanwatch coverage not found for AOI; falling back to OpenStreetMap (reason:", str(e), ")")
82
+ effective_source = 'OpenStreetMap'
83
+ land_cover_gdf = load_land_cover_gdf_from_osm(rectangle_vertices)
84
+ elif source == 'ESA WorldCover':
85
+ roi = get_roi(rectangle_vertices)
86
+ save_geotiff_esa_land_cover(roi, geotiff_path)
87
+ elif source == 'ESRI 10m Annual Land Cover':
88
+ esri_landcover_year = kwargs.get("esri_landcover_year")
89
+ roi = get_roi(rectangle_vertices)
90
+ save_geotiff_esri_landcover(roi, geotiff_path, year=esri_landcover_year)
91
+ elif source == 'Dynamic World V1':
92
+ dynamic_world_date = kwargs.get("dynamic_world_date")
93
+ roi = get_roi(rectangle_vertices)
94
+ save_geotiff_dynamic_world_v1(roi, geotiff_path, dynamic_world_date)
95
+ elif source == 'OpenEarthMapJapan':
96
+ ssl_verify = kwargs.get('ssl_verify', kwargs.get('verify', True))
97
+ allow_insecure_ssl = kwargs.get('allow_insecure_ssl', False)
98
+ allow_http_fallback = kwargs.get('allow_http_fallback', False)
99
+ timeout_s = kwargs.get('timeout', 30)
100
+
101
+ save_oemj_as_geotiff(
102
+ rectangle_vertices,
103
+ geotiff_path,
104
+ ssl_verify=ssl_verify,
105
+ allow_insecure_ssl=allow_insecure_ssl,
106
+ allow_http_fallback=allow_http_fallback,
107
+ timeout_s=timeout_s,
108
+ )
109
+ if not os.path.exists(geotiff_path):
110
+ raise FileNotFoundError(
111
+ f"OEMJ download failed; expected GeoTIFF not found: {geotiff_path}. "
112
+ "You can try setting ssl_verify=False or allow_http_fallback=True in kwargs."
113
+ )
114
+ elif source == 'OpenStreetMap':
115
+ land_cover_gdf = load_land_cover_gdf_from_osm(rectangle_vertices)
116
+
117
+ land_cover_classes = get_land_cover_classes(effective_source)
118
+
119
+ if effective_source == 'OpenStreetMap':
120
+ default_class = kwargs.get('default_land_cover_class', 'Developed space')
121
+ detect_ocean = kwargs.get('detect_ocean', True) # Default True for OSM
122
+ land_cover_grid_str = create_land_cover_grid_from_gdf_polygon(
123
+ land_cover_gdf, meshsize, effective_source, rectangle_vertices,
124
+ default_class=default_class, detect_ocean=detect_ocean
125
+ )
126
+ else:
127
+ land_cover_grid_str = create_land_cover_grid_from_geotiff_polygon(geotiff_path, meshsize, land_cover_classes, rectangle_vertices)
128
+
129
+ color_map = {cls: [r/255, g/255, b/255] for (r,g,b), cls in land_cover_classes.items()}
130
+
131
+ grid_vis = kwargs.get("gridvis", True)
132
+ if grid_vis:
133
+ visualize_land_cover_grid(np.flipud(land_cover_grid_str), meshsize, color_map, land_cover_classes)
134
+
135
+ # Record effective source for downstream consumers
136
+ global _LAST_EFFECTIVE_LC_SOURCE
137
+ _LAST_EFFECTIVE_LC_SOURCE = effective_source
138
+
139
+ land_cover_grid_int = convert_land_cover_array(land_cover_grid_str, land_cover_classes)
140
+ return land_cover_grid_int
141
+
142
+
143
+ def get_building_height_grid(rectangle_vertices, meshsize, source, output_dir, building_gdf=None, **kwargs):
144
+ ee_required_sources = {"Open Building 2.5D Temporal"}
145
+ if source in ee_required_sources:
146
+ initialize_earth_engine()
147
+
148
+ quiet = kwargs.get('quiet', False)
149
+ if not quiet:
150
+ print("Creating Building Height grid\n ")
151
+ print(f"Base data source: {source}")
152
+
153
+ os.makedirs(output_dir, exist_ok=True)
154
+
155
+ if building_gdf is not None:
156
+ gdf = building_gdf
157
+ if not quiet:
158
+ print("Using provided GeoDataFrame for building data")
159
+ else:
160
+ floor_height = kwargs.get("floor_height", 3.0)
161
+ if source == 'Microsoft Building Footprints':
162
+ gdf = get_mbfp_gdf(output_dir, rectangle_vertices)
163
+ elif source == 'OpenStreetMap':
164
+ gdf = load_gdf_from_openstreetmap(rectangle_vertices, floor_height=floor_height)
165
+ elif source == "Open Building 2.5D Temporal":
166
+ building_height_grid, building_min_height_grid, building_id_grid, filtered_buildings = create_building_height_grid_from_open_building_temporal_polygon(meshsize, rectangle_vertices, output_dir)
167
+ elif source == 'EUBUCCO v0.1':
168
+ gdf = load_gdf_from_eubucco(rectangle_vertices, output_dir)
169
+ elif source == "Overture":
170
+ gdf = load_gdf_from_overture(rectangle_vertices, floor_height=floor_height)
171
+ elif source in ("GBA", "Global Building Atlas"):
172
+ clip_gba = kwargs.get("gba_clip", False)
173
+ gba_download_dir = kwargs.get("gba_download_dir")
174
+ gdf = load_gdf_from_gba(rectangle_vertices, download_dir=gba_download_dir, clip_to_rectangle=clip_gba)
175
+ elif source == "Local file":
176
+ _, extension = os.path.splitext(kwargs["building_path"])
177
+ if extension == ".gpkg":
178
+ gdf = get_gdf_from_gpkg(kwargs["building_path"], rectangle_vertices)
179
+ elif source == "GeoDataFrame":
180
+ raise ValueError("When source is 'GeoDataFrame', building_gdf parameter must be provided")
181
+
182
+ building_complementary_source = kwargs.get("building_complementary_source")
183
+ try:
184
+ comp_label = building_complementary_source if building_complementary_source not in (None, "") else "None"
185
+ if not quiet:
186
+ print(f"Complementary data source: {comp_label}")
187
+ except Exception:
188
+ pass
189
+ building_complement_height = kwargs.get("building_complement_height")
190
+ overlapping_footprint = kwargs.get("overlapping_footprint", "auto")
191
+
192
+ if (building_complementary_source is None) or (building_complementary_source=='None'):
193
+ if source != "Open Building 2.5D Temporal":
194
+ building_height_grid, building_min_height_grid, building_id_grid, filtered_buildings = create_building_height_grid_from_gdf_polygon(gdf, meshsize, rectangle_vertices, complement_height=building_complement_height, overlapping_footprint=overlapping_footprint)
195
+ else:
196
+ if building_complementary_source == "Open Building 2.5D Temporal":
197
+ try:
198
+ roi = get_roi(rectangle_vertices)
199
+ os.makedirs(output_dir, exist_ok=True)
200
+ geotiff_path_comp = os.path.join(output_dir, "building_height.tif")
201
+ save_geotiff_open_buildings_temporal(roi, geotiff_path_comp)
202
+ building_height_grid, building_min_height_grid, building_id_grid, filtered_buildings = create_building_height_grid_from_gdf_polygon(gdf, meshsize, rectangle_vertices, geotiff_path_comp=geotiff_path_comp, complement_height=building_complement_height, overlapping_footprint=overlapping_footprint)
203
+ except Exception as e:
204
+ if not quiet:
205
+ print("Open Building 2.5D Temporal requires Earth Engine (", str(e), ") — proceeding without complementary raster.")
206
+ building_height_grid, building_min_height_grid, building_id_grid, filtered_buildings = create_building_height_grid_from_gdf_polygon(gdf, meshsize, rectangle_vertices, complement_height=building_complement_height, overlapping_footprint=overlapping_footprint)
207
+ elif building_complementary_source in ["England 1m DSM - DTM", "Netherlands 0.5m DSM - DTM"]:
208
+ try:
209
+ roi = get_roi(rectangle_vertices)
210
+ os.makedirs(output_dir, exist_ok=True)
211
+ geotiff_path_comp = os.path.join(output_dir, "building_height.tif")
212
+ save_geotiff_dsm_minus_dtm(roi, geotiff_path_comp, meshsize, building_complementary_source)
213
+ building_height_grid, building_min_height_grid, building_id_grid, filtered_buildings = create_building_height_grid_from_gdf_polygon(gdf, meshsize, rectangle_vertices, geotiff_path_comp=geotiff_path_comp, complement_height=building_complement_height, overlapping_footprint=overlapping_footprint)
214
+ except Exception as e:
215
+ if not quiet:
216
+ print("DSM-DTM complementary raster requires Earth Engine (", str(e), ") — proceeding without complementary raster.")
217
+ building_height_grid, building_min_height_grid, building_id_grid, filtered_buildings = create_building_height_grid_from_gdf_polygon(gdf, meshsize, rectangle_vertices, complement_height=building_complement_height, overlapping_footprint=overlapping_footprint)
218
+ else:
219
+ if building_complementary_source == 'Microsoft Building Footprints':
220
+ gdf_comp = get_mbfp_gdf(output_dir, rectangle_vertices)
221
+ elif building_complementary_source == 'OpenStreetMap':
222
+ gdf_comp = load_gdf_from_openstreetmap(rectangle_vertices, floor_height=floor_height)
223
+ elif building_complementary_source == 'EUBUCCO v0.1':
224
+ gdf_comp = load_gdf_from_eubucco(rectangle_vertices, output_dir)
225
+ elif building_complementary_source == "Overture":
226
+ gdf_comp = load_gdf_from_overture(rectangle_vertices, floor_height=floor_height)
227
+ elif building_complementary_source in ("GBA", "Global Building Atlas"):
228
+ clip_gba = kwargs.get("gba_clip", False)
229
+ gba_download_dir = kwargs.get("gba_download_dir")
230
+ gdf_comp = load_gdf_from_gba(rectangle_vertices, download_dir=gba_download_dir, clip_to_rectangle=clip_gba)
231
+ elif building_complementary_source == "Local file":
232
+ _, extension = os.path.splitext(kwargs["building_complementary_path"])
233
+ if extension == ".gpkg":
234
+ gdf_comp = get_gdf_from_gpkg(kwargs["building_complementary_path"], rectangle_vertices)
235
+
236
+ complement_building_footprints = kwargs.get("complement_building_footprints")
237
+ building_height_grid, building_min_height_grid, building_id_grid, filtered_buildings = create_building_height_grid_from_gdf_polygon(gdf, meshsize, rectangle_vertices, gdf_comp=gdf_comp, complement_building_footprints=complement_building_footprints, complement_height=building_complement_height, overlapping_footprint=overlapping_footprint)
238
+
239
+ grid_vis = kwargs.get("gridvis", True)
240
+ if grid_vis:
241
+ building_height_grid_nan = building_height_grid.copy()
242
+ building_height_grid_nan[building_height_grid_nan == 0] = np.nan
243
+ visualize_numerical_grid(np.flipud(building_height_grid_nan), meshsize, "building height (m)", cmap='viridis', label='Value')
244
+
245
+ return building_height_grid, building_min_height_grid, building_id_grid, filtered_buildings
246
+
247
+
248
+ def get_canopy_height_grid(rectangle_vertices, meshsize, source, output_dir, **kwargs):
249
+ quiet = kwargs.get('quiet', False)
250
+ if not quiet:
251
+ print("Creating Canopy Height grid\n ")
252
+ print(f"Data source: {source}")
253
+
254
+ os.makedirs(output_dir, exist_ok=True)
255
+
256
+ # Explicit static path (no EE): use land cover mask with static height
257
+ if source == 'Static':
258
+ land_cover_grid = kwargs.get('land_cover_like')
259
+ if land_cover_grid is None:
260
+ # Minimal fallback if caller didn't provide land_cover_like
261
+ canopy_top = np.zeros((1, 1), dtype=float)
262
+ trunk_height_ratio = kwargs.get('trunk_height_ratio')
263
+ if trunk_height_ratio is None:
264
+ trunk_height_ratio = 11.76 / 19.98
265
+ canopy_bottom = canopy_top * float(trunk_height_ratio)
266
+ return canopy_top, canopy_bottom
267
+
268
+ from ..utils.lc import get_land_cover_classes
269
+ land_cover_source = kwargs.get('land_cover_source', 'OpenStreetMap')
270
+ classes_map = get_land_cover_classes(land_cover_source)
271
+ class_to_int = {name: i for i, name in enumerate(classes_map.values())}
272
+ tree_labels = ["Tree", "Trees", "Tree Canopy"]
273
+ tree_indices = [class_to_int[label] for label in tree_labels if label in class_to_int]
274
+
275
+ canopy_top = np.zeros_like(land_cover_grid, dtype=float)
276
+ static_tree_height = kwargs.get('static_tree_height', 10.0)
277
+ tree_mask = np.isin(land_cover_grid, tree_indices) if tree_indices else np.zeros_like(land_cover_grid, dtype=bool)
278
+ canopy_top[tree_mask] = static_tree_height
279
+
280
+ trunk_height_ratio = kwargs.get('trunk_height_ratio')
281
+ if trunk_height_ratio is None:
282
+ trunk_height_ratio = 11.76 / 19.98
283
+ canopy_bottom = canopy_top * float(trunk_height_ratio)
284
+
285
+ grid_vis = kwargs.get("gridvis", True)
286
+ if grid_vis:
287
+ vis = canopy_top.copy(); vis[vis == 0] = np.nan
288
+ visualize_numerical_grid(np.flipud(vis), meshsize, "Tree canopy height (top)", cmap='Greens', label='Tree canopy height (m)')
289
+ return canopy_top, canopy_bottom
290
+
291
+ if source in ('GeoDataFrame', 'tree_gdf', 'Tree_GeoDataFrame', 'GDF'):
292
+ tree_gdf = kwargs.get('tree_gdf')
293
+ tree_gdf_path = kwargs.get('tree_gdf_path')
294
+ if tree_gdf is None and tree_gdf_path is not None:
295
+ _, ext = os.path.splitext(tree_gdf_path)
296
+ if ext.lower() == '.gpkg':
297
+ tree_gdf = get_gdf_from_gpkg(tree_gdf_path, rectangle_vertices)
298
+ else:
299
+ raise ValueError("Unsupported tree file format. Use .gpkg or pass a GeoDataFrame.")
300
+ if tree_gdf is None:
301
+ raise ValueError("When source='GeoDataFrame', provide 'tree_gdf' or 'tree_gdf_path'.")
302
+
303
+ canopy_top, canopy_bottom = create_canopy_grids_from_tree_gdf(tree_gdf, meshsize, rectangle_vertices)
304
+
305
+ grid_vis = kwargs.get("gridvis", True)
306
+ if grid_vis:
307
+ vis = canopy_top.copy()
308
+ vis[vis == 0] = np.nan
309
+ visualize_numerical_grid(np.flipud(vis), meshsize, "Tree canopy height (top)", cmap='Greens', label='Tree canopy height (m)')
310
+
311
+ return canopy_top, canopy_bottom
312
+
313
+ try:
314
+ initialize_earth_engine()
315
+ except Exception as e:
316
+ if not quiet:
317
+ print("Earth Engine unavailable (", str(e), ") — falling back to Static canopy heights.")
318
+ # Re-enter with explicit Static logic using land cover mask
319
+ return get_canopy_height_grid(rectangle_vertices, meshsize, 'Static', output_dir, **kwargs)
320
+
321
+ geotiff_path = os.path.join(output_dir, "canopy_height.tif")
322
+
323
+ roi = get_roi(rectangle_vertices)
324
+ if source == 'High Resolution 1m Global Canopy Height Maps':
325
+ collection_name = "projects/meta-forest-monitoring-okw37/assets/CanopyHeight"
326
+ image = get_ee_image_collection(collection_name, roi)
327
+ elif source == 'ETH Global Sentinel-2 10m Canopy Height (2020)':
328
+ collection_name = "users/nlang/ETH_GlobalCanopyHeight_2020_10m_v1"
329
+ image = get_ee_image(collection_name, roi)
330
+ else:
331
+ raise ValueError(f"Unsupported canopy source: {source}")
332
+
333
+ save_geotiff(image, geotiff_path, scale=meshsize, region=roi, crs='EPSG:4326')
334
+ canopy_height_grid = create_height_grid_from_geotiff_polygon(geotiff_path, meshsize, rectangle_vertices)
335
+
336
+ trunk_height_ratio = kwargs.get("trunk_height_ratio")
337
+ if trunk_height_ratio is None:
338
+ trunk_height_ratio = 11.76 / 19.98
339
+ canopy_bottom_grid = canopy_height_grid * float(trunk_height_ratio)
340
+
341
+ grid_vis = kwargs.get("gridvis", True)
342
+ if grid_vis:
343
+ canopy_height_grid_nan = canopy_height_grid.copy()
344
+ canopy_height_grid_nan[canopy_height_grid_nan == 0] = np.nan
345
+ visualize_numerical_grid(np.flipud(canopy_height_grid_nan), meshsize, "Tree canopy height", cmap='Greens', label='Tree canopy height (m)')
346
+ return canopy_height_grid, canopy_bottom_grid
347
+
348
+
349
+ def get_dem_grid(rectangle_vertices, meshsize, source, output_dir, **kwargs):
350
+ quiet = kwargs.get('quiet', False)
351
+ if not quiet:
352
+ print("Creating Digital Elevation Model (DEM) grid\n ")
353
+ print(f"Data source: {source}")
354
+
355
+ if source == "Local file":
356
+ geotiff_path = kwargs["dem_path"]
357
+ else:
358
+ try:
359
+ initialize_earth_engine()
360
+ except Exception as e:
361
+ if not quiet:
362
+ print("Earth Engine unavailable (", str(e), ") falling back to flat DEM.")
363
+ # Compute grid shape directly from rectangle_vertices and meshsize
364
+ from ..geoprocessor.raster.core import compute_grid_shape
365
+ grid_shape = compute_grid_shape(rectangle_vertices, meshsize)
366
+ dem_grid = np.zeros(grid_shape, dtype=float)
367
+ return dem_grid
368
+
369
+ geotiff_path = os.path.join(output_dir, "dem.tif")
370
+
371
+ buffer_distance = 100
372
+ roi = get_roi(rectangle_vertices)
373
+ roi_buffered = roi.buffer(buffer_distance)
374
+
375
+ image = get_dem_image(roi_buffered, source)
376
+
377
+ if source in ["England 1m DTM", 'DEM France 1m', 'DEM France 5m', 'AUSTRALIA 5M DEM', 'Netherlands 0.5m DTM']:
378
+ save_geotiff(image, geotiff_path, scale=meshsize, region=roi_buffered, crs='EPSG:4326')
379
+ elif source == 'USGS 3DEP 1m':
380
+ scale = max(meshsize, 1.25)
381
+ save_geotiff(image, geotiff_path, scale=scale, region=roi_buffered, crs='EPSG:4326')
382
+ else:
383
+ save_geotiff(image, geotiff_path, scale=30, region=roi_buffered)
384
+
385
+ dem_interpolation = kwargs.get("dem_interpolation")
386
+ dem_grid = create_dem_grid_from_geotiff_polygon(geotiff_path, meshsize, rectangle_vertices, dem_interpolation=dem_interpolation)
387
+
388
+ grid_vis = kwargs.get("gridvis", True)
389
+ if grid_vis:
390
+ visualize_numerical_grid(np.flipud(dem_grid), meshsize, title='Digital Elevation Model', cmap='terrain', label='Elevation (m)')
391
+
392
+ return dem_grid
393
+
394
+