voxcity 0.5.26__py3-none-any.whl → 0.5.28__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.
Potentially problematic release.
This version of voxcity might be problematic. Click here for more details.
- voxcity/downloader/osm.py +1038 -1040
- voxcity/exporter/__init__.py +2 -1
- voxcity/exporter/cityles.py +368 -0
- voxcity/generator.py +55 -35
- voxcity/geoprocessor/draw.py +276 -3
- voxcity/geoprocessor/grid.py +5 -3
- {voxcity-0.5.26.dist-info → voxcity-0.5.28.dist-info}/METADATA +1 -1
- {voxcity-0.5.26.dist-info → voxcity-0.5.28.dist-info}/RECORD +12 -11
- {voxcity-0.5.26.dist-info → voxcity-0.5.28.dist-info}/WHEEL +0 -0
- {voxcity-0.5.26.dist-info → voxcity-0.5.28.dist-info}/licenses/AUTHORS.rst +0 -0
- {voxcity-0.5.26.dist-info → voxcity-0.5.28.dist-info}/licenses/LICENSE +0 -0
- {voxcity-0.5.26.dist-info → voxcity-0.5.28.dist-info}/top_level.txt +0 -0
voxcity/exporter/__init__.py
CHANGED
|
@@ -0,0 +1,368 @@
|
|
|
1
|
+
"""
|
|
2
|
+
CityLES Exporter Module for VoxCity
|
|
3
|
+
|
|
4
|
+
This module provides functionality to export VoxCity grid data to the CityLES input file format.
|
|
5
|
+
CityLES is a large-eddy simulation (LES) model for urban environments, requiring specific input files
|
|
6
|
+
describing land use, building geometry, vegetation, and terrain.
|
|
7
|
+
|
|
8
|
+
Key Features:
|
|
9
|
+
- Converts VoxCity grids to CityLES-compatible input files (topog.txt, landuse.txt, dem.txt, vmap.txt, lonlat.txt)
|
|
10
|
+
- Handles land cover, building heights, canopy heights, and digital elevation models
|
|
11
|
+
- Supports flexible mapping from land cover and building types to CityLES codes
|
|
12
|
+
- Generates all required text files and metadata for CityLES runs
|
|
13
|
+
|
|
14
|
+
Main Functions:
|
|
15
|
+
- export_cityles: Main function to export all required CityLES input files
|
|
16
|
+
- export_topog: Exports building geometry (topog.txt)
|
|
17
|
+
- export_landuse: Exports land use grid (landuse.txt)
|
|
18
|
+
- export_dem: Exports digital elevation model (dem.txt)
|
|
19
|
+
- export_vmap: Exports vegetation map (vmap.txt)
|
|
20
|
+
- export_lonlat: Exports longitude/latitude grid (lonlat.txt)
|
|
21
|
+
|
|
22
|
+
Dependencies:
|
|
23
|
+
- numpy: For array operations
|
|
24
|
+
- pathlib: For file and directory management
|
|
25
|
+
- os: For file system operations
|
|
26
|
+
"""
|
|
27
|
+
|
|
28
|
+
import os
|
|
29
|
+
import numpy as np
|
|
30
|
+
from pathlib import Path
|
|
31
|
+
|
|
32
|
+
|
|
33
|
+
# Land cover to CityLES land use mapping
|
|
34
|
+
# Based on common land cover classifications to CityLES codes
|
|
35
|
+
LANDCOVER_TO_CITYLES_LANDUSE = {
|
|
36
|
+
# Built-up areas
|
|
37
|
+
'building': 4, # Concrete building
|
|
38
|
+
'road': 2, # High reflective asphalt without AH
|
|
39
|
+
'parking': 2, # High reflective asphalt without AH
|
|
40
|
+
'pavement': 11, # Concrete (proxy of block)
|
|
41
|
+
|
|
42
|
+
# Vegetation
|
|
43
|
+
'grass': 10, # Grassland
|
|
44
|
+
'forest': 16, # Deciduous broadleaf forest
|
|
45
|
+
'tree': 16, # Deciduous broadleaf forest
|
|
46
|
+
'agriculture': 7, # Dryland cropland and pasture
|
|
47
|
+
'cropland': 7, # Dryland cropland and pasture
|
|
48
|
+
'paddy': 6, # Paddy
|
|
49
|
+
|
|
50
|
+
# Water and bare land
|
|
51
|
+
'water': 9, # Water
|
|
52
|
+
'bare_soil': 8, # Barren or sparsely vegetated
|
|
53
|
+
'sand': 8, # Barren or sparsely vegetated
|
|
54
|
+
|
|
55
|
+
# Default
|
|
56
|
+
'default': 10 # Grassland as default
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
# Building material mapping
|
|
60
|
+
# Maps building types to CityLES building attribute codes
|
|
61
|
+
BUILDING_MATERIAL_MAPPING = {
|
|
62
|
+
'concrete': 104, # Concrete building
|
|
63
|
+
'residential': 105, # Slate roof (ordinal wooden house)
|
|
64
|
+
'commercial': 104, # Concrete building
|
|
65
|
+
'industrial': 104, # Concrete building
|
|
66
|
+
'default': 104 # Default to concrete building
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
# Tree type mapping
|
|
70
|
+
TREE_TYPE_MAPPING = {
|
|
71
|
+
'deciduous': 101, # Leaf
|
|
72
|
+
'evergreen': 101, # Leaf (simplified)
|
|
73
|
+
'default': 101 # Default to leaf
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
|
|
77
|
+
def create_cityles_directories(output_directory):
|
|
78
|
+
"""Create necessary directories for CityLES output"""
|
|
79
|
+
output_path = Path(output_directory)
|
|
80
|
+
output_path.mkdir(parents=True, exist_ok=True)
|
|
81
|
+
return output_path
|
|
82
|
+
|
|
83
|
+
|
|
84
|
+
def get_land_use_code(land_cover_value, land_cover_source=None):
|
|
85
|
+
"""
|
|
86
|
+
Convert land cover value to CityLES land use code
|
|
87
|
+
|
|
88
|
+
Parameters:
|
|
89
|
+
-----------
|
|
90
|
+
land_cover_value : int or str
|
|
91
|
+
Land cover value from VoxCity
|
|
92
|
+
land_cover_source : str, optional
|
|
93
|
+
Source of land cover data (e.g., 'esri', 'esa', 'osm')
|
|
94
|
+
|
|
95
|
+
Returns:
|
|
96
|
+
--------
|
|
97
|
+
int : CityLES land use code (1-17)
|
|
98
|
+
"""
|
|
99
|
+
# If using numeric codes, you might need source-specific mappings
|
|
100
|
+
# This is a simplified example
|
|
101
|
+
if isinstance(land_cover_value, str):
|
|
102
|
+
return LANDCOVER_TO_CITYLES_LANDUSE.get(land_cover_value.lower(),
|
|
103
|
+
LANDCOVER_TO_CITYLES_LANDUSE['default'])
|
|
104
|
+
|
|
105
|
+
# Example mapping for ESRI land cover (adjust based on actual data source)
|
|
106
|
+
if land_cover_source == 'esri':
|
|
107
|
+
esri_mapping = {
|
|
108
|
+
1: 9, # Water -> Water
|
|
109
|
+
2: 16, # Trees -> Deciduous broadleaf forest
|
|
110
|
+
4: 8, # Flooded vegetation -> Barren
|
|
111
|
+
5: 10, # Crops -> Grassland (simplified)
|
|
112
|
+
7: 4, # Built Area -> Concrete building
|
|
113
|
+
8: 8, # Bare ground -> Barren
|
|
114
|
+
9: 3, # Snow/Ice -> Concrete (proxy of jari)
|
|
115
|
+
10: 9, # Clouds -> Water (simplified)
|
|
116
|
+
11: 10 # Rangeland -> Grassland
|
|
117
|
+
}
|
|
118
|
+
return esri_mapping.get(land_cover_value, LANDCOVER_TO_CITYLES_LANDUSE['default'])
|
|
119
|
+
|
|
120
|
+
# Default mapping
|
|
121
|
+
return LANDCOVER_TO_CITYLES_LANDUSE['default']
|
|
122
|
+
|
|
123
|
+
|
|
124
|
+
def export_topog(building_height_grid, building_id_grid, output_path,
|
|
125
|
+
building_material='default'):
|
|
126
|
+
"""
|
|
127
|
+
Export topog.txt file for CityLES
|
|
128
|
+
|
|
129
|
+
Parameters:
|
|
130
|
+
-----------
|
|
131
|
+
building_height_grid : numpy.ndarray
|
|
132
|
+
2D array of building heights
|
|
133
|
+
building_id_grid : numpy.ndarray
|
|
134
|
+
2D array of building IDs
|
|
135
|
+
output_path : Path
|
|
136
|
+
Output directory path
|
|
137
|
+
building_material : str
|
|
138
|
+
Building material type for mapping
|
|
139
|
+
"""
|
|
140
|
+
filename = output_path / 'topog.txt'
|
|
141
|
+
|
|
142
|
+
# Get building positions (where height > 0)
|
|
143
|
+
building_positions = np.argwhere(building_height_grid > 0)
|
|
144
|
+
n_buildings = len(building_positions)
|
|
145
|
+
|
|
146
|
+
material_code = BUILDING_MATERIAL_MAPPING.get(building_material,
|
|
147
|
+
BUILDING_MATERIAL_MAPPING['default'])
|
|
148
|
+
|
|
149
|
+
with open(filename, 'w') as f:
|
|
150
|
+
# Write number of buildings
|
|
151
|
+
f.write(f"{n_buildings}\n")
|
|
152
|
+
|
|
153
|
+
# Write building data
|
|
154
|
+
for idx, (j, i) in enumerate(building_positions):
|
|
155
|
+
# CityLES uses 1-based indexing
|
|
156
|
+
i_1based = i + 1
|
|
157
|
+
j_1based = j + 1
|
|
158
|
+
height = building_height_grid[j, i]
|
|
159
|
+
|
|
160
|
+
# Format: i j height material_code depth1 depth2 changed_material
|
|
161
|
+
f.write(f"{i_1based} {j_1based} {height:.1f} {material_code} 0.0 0.0 102\n")
|
|
162
|
+
|
|
163
|
+
|
|
164
|
+
def export_landuse(land_cover_grid, output_path, land_cover_source=None):
|
|
165
|
+
"""
|
|
166
|
+
Export landuse.txt file for CityLES
|
|
167
|
+
|
|
168
|
+
Parameters:
|
|
169
|
+
-----------
|
|
170
|
+
land_cover_grid : numpy.ndarray
|
|
171
|
+
2D array of land cover values
|
|
172
|
+
output_path : Path
|
|
173
|
+
Output directory path
|
|
174
|
+
land_cover_source : str, optional
|
|
175
|
+
Source of land cover data
|
|
176
|
+
"""
|
|
177
|
+
filename = output_path / 'landuse.txt'
|
|
178
|
+
|
|
179
|
+
# Flatten the grid and convert to CityLES codes
|
|
180
|
+
flat_grid = land_cover_grid.flatten()
|
|
181
|
+
|
|
182
|
+
with open(filename, 'w') as f:
|
|
183
|
+
for value in flat_grid:
|
|
184
|
+
cityles_code = get_land_use_code(value, land_cover_source)
|
|
185
|
+
f.write(f"{cityles_code}\n")
|
|
186
|
+
|
|
187
|
+
|
|
188
|
+
def export_dem(dem_grid, output_path):
|
|
189
|
+
"""
|
|
190
|
+
Export dem.txt file for CityLES
|
|
191
|
+
|
|
192
|
+
Parameters:
|
|
193
|
+
-----------
|
|
194
|
+
dem_grid : numpy.ndarray
|
|
195
|
+
2D array of elevation values
|
|
196
|
+
output_path : Path
|
|
197
|
+
Output directory path
|
|
198
|
+
"""
|
|
199
|
+
filename = output_path / 'dem.txt'
|
|
200
|
+
|
|
201
|
+
ny, nx = dem_grid.shape
|
|
202
|
+
|
|
203
|
+
with open(filename, 'w') as f:
|
|
204
|
+
for j in range(ny):
|
|
205
|
+
for i in range(nx):
|
|
206
|
+
# CityLES uses 1-based indexing
|
|
207
|
+
i_1based = i + 1
|
|
208
|
+
j_1based = j + 1
|
|
209
|
+
elevation = dem_grid[j, i]
|
|
210
|
+
|
|
211
|
+
f.write(f"{i_1based} {j_1based} {elevation:.1f}\n")
|
|
212
|
+
|
|
213
|
+
|
|
214
|
+
def export_vmap(canopy_height_grid, output_path, tree_base_ratio=0.3, tree_type='default'):
|
|
215
|
+
"""
|
|
216
|
+
Export vmap.txt file for CityLES
|
|
217
|
+
|
|
218
|
+
Parameters:
|
|
219
|
+
-----------
|
|
220
|
+
canopy_height_grid : numpy.ndarray
|
|
221
|
+
2D array of canopy heights
|
|
222
|
+
output_path : Path
|
|
223
|
+
Output directory path
|
|
224
|
+
tree_base_ratio : float
|
|
225
|
+
Ratio of tree base height to total canopy height
|
|
226
|
+
tree_type : str
|
|
227
|
+
Tree type for mapping
|
|
228
|
+
"""
|
|
229
|
+
filename = output_path / 'vmap.txt'
|
|
230
|
+
|
|
231
|
+
# Get tree positions (where canopy height > 0)
|
|
232
|
+
tree_positions = np.argwhere(canopy_height_grid > 0)
|
|
233
|
+
n_trees = len(tree_positions)
|
|
234
|
+
|
|
235
|
+
tree_code = TREE_TYPE_MAPPING.get(tree_type, TREE_TYPE_MAPPING['default'])
|
|
236
|
+
|
|
237
|
+
with open(filename, 'w') as f:
|
|
238
|
+
# Write number of trees
|
|
239
|
+
f.write(f"{n_trees}\n")
|
|
240
|
+
|
|
241
|
+
# Write tree data
|
|
242
|
+
for idx, (j, i) in enumerate(tree_positions):
|
|
243
|
+
# CityLES uses 1-based indexing
|
|
244
|
+
i_1based = i + 1
|
|
245
|
+
j_1based = j + 1
|
|
246
|
+
total_height = canopy_height_grid[j, i]
|
|
247
|
+
lower_height = total_height * tree_base_ratio
|
|
248
|
+
upper_height = total_height
|
|
249
|
+
|
|
250
|
+
# Format: i j lower_height upper_height tree_type
|
|
251
|
+
f.write(f"{i_1based} {j_1based} {lower_height:.1f} {upper_height:.1f} {tree_code}\n")
|
|
252
|
+
|
|
253
|
+
|
|
254
|
+
def export_lonlat(rectangle_vertices, grid_shape, output_path):
|
|
255
|
+
"""
|
|
256
|
+
Export lonlat.txt file for CityLES
|
|
257
|
+
|
|
258
|
+
Parameters:
|
|
259
|
+
-----------
|
|
260
|
+
rectangle_vertices : list of tuples
|
|
261
|
+
List of (lon, lat) vertices defining the area
|
|
262
|
+
grid_shape : tuple
|
|
263
|
+
Shape of the grid (ny, nx)
|
|
264
|
+
output_path : Path
|
|
265
|
+
Output directory path
|
|
266
|
+
"""
|
|
267
|
+
filename = output_path / 'lonlat.txt'
|
|
268
|
+
|
|
269
|
+
ny, nx = grid_shape
|
|
270
|
+
|
|
271
|
+
# Extract bounds from vertices
|
|
272
|
+
lons = [v[0] for v in rectangle_vertices]
|
|
273
|
+
lats = [v[1] for v in rectangle_vertices]
|
|
274
|
+
min_lon, max_lon = min(lons), max(lons)
|
|
275
|
+
min_lat, max_lat = min(lats), max(lats)
|
|
276
|
+
|
|
277
|
+
# Create coordinate grids
|
|
278
|
+
lon_vals = np.linspace(min_lon, max_lon, nx)
|
|
279
|
+
lat_vals = np.linspace(min_lat, max_lat, ny)
|
|
280
|
+
|
|
281
|
+
with open(filename, 'w') as f:
|
|
282
|
+
for j in range(ny):
|
|
283
|
+
for i in range(nx):
|
|
284
|
+
# CityLES uses 1-based indexing
|
|
285
|
+
i_1based = i + 1
|
|
286
|
+
j_1based = j + 1
|
|
287
|
+
lon = lon_vals[i]
|
|
288
|
+
lat = lat_vals[j]
|
|
289
|
+
|
|
290
|
+
f.write(f"{i_1based} {j_1based} {lon:.7f} {lat:.8f}\n")
|
|
291
|
+
|
|
292
|
+
|
|
293
|
+
def export_cityles(building_height_grid, building_id_grid, canopy_height_grid,
|
|
294
|
+
land_cover_grid, dem_grid, meshsize, land_cover_source,
|
|
295
|
+
rectangle_vertices, output_directory="output/cityles",
|
|
296
|
+
building_material='default', tree_type='default',
|
|
297
|
+
tree_base_ratio=0.3, **kwargs):
|
|
298
|
+
"""
|
|
299
|
+
Export VoxCity data to CityLES format
|
|
300
|
+
|
|
301
|
+
Parameters:
|
|
302
|
+
-----------
|
|
303
|
+
building_height_grid : numpy.ndarray
|
|
304
|
+
2D array of building heights
|
|
305
|
+
building_id_grid : numpy.ndarray
|
|
306
|
+
2D array of building IDs
|
|
307
|
+
canopy_height_grid : numpy.ndarray
|
|
308
|
+
2D array of canopy heights
|
|
309
|
+
land_cover_grid : numpy.ndarray
|
|
310
|
+
2D array of land cover values
|
|
311
|
+
dem_grid : numpy.ndarray
|
|
312
|
+
2D array of elevation values
|
|
313
|
+
meshsize : float
|
|
314
|
+
Grid cell size in meters
|
|
315
|
+
land_cover_source : str
|
|
316
|
+
Source of land cover data (e.g., 'esri', 'esa', 'osm')
|
|
317
|
+
rectangle_vertices : list of tuples
|
|
318
|
+
List of (lon, lat) vertices defining the area
|
|
319
|
+
output_directory : str
|
|
320
|
+
Output directory path
|
|
321
|
+
building_material : str
|
|
322
|
+
Building material type for mapping
|
|
323
|
+
tree_type : str
|
|
324
|
+
Tree type for mapping
|
|
325
|
+
tree_base_ratio : float
|
|
326
|
+
Ratio of tree base height to total canopy height
|
|
327
|
+
**kwargs : dict
|
|
328
|
+
Additional parameters (for compatibility)
|
|
329
|
+
|
|
330
|
+
Returns:
|
|
331
|
+
--------
|
|
332
|
+
str : Path to output directory
|
|
333
|
+
"""
|
|
334
|
+
# Create output directory
|
|
335
|
+
output_path = create_cityles_directories(output_directory)
|
|
336
|
+
|
|
337
|
+
print(f"Exporting CityLES files to: {output_path}")
|
|
338
|
+
|
|
339
|
+
# Export individual files
|
|
340
|
+
print("Exporting topog.txt...")
|
|
341
|
+
export_topog(building_height_grid, building_id_grid, output_path, building_material)
|
|
342
|
+
|
|
343
|
+
print("Exporting landuse.txt...")
|
|
344
|
+
export_landuse(land_cover_grid, output_path, land_cover_source)
|
|
345
|
+
|
|
346
|
+
print("Exporting dem.txt...")
|
|
347
|
+
export_dem(dem_grid, output_path)
|
|
348
|
+
|
|
349
|
+
print("Exporting vmap.txt...")
|
|
350
|
+
export_vmap(canopy_height_grid, output_path, tree_base_ratio, tree_type)
|
|
351
|
+
|
|
352
|
+
print("Exporting lonlat.txt...")
|
|
353
|
+
export_lonlat(rectangle_vertices, building_height_grid.shape, output_path)
|
|
354
|
+
|
|
355
|
+
# Create metadata file for reference
|
|
356
|
+
metadata_file = output_path / 'cityles_metadata.txt'
|
|
357
|
+
with open(metadata_file, 'w') as f:
|
|
358
|
+
f.write("CityLES Export Metadata\n")
|
|
359
|
+
f.write("====================\n")
|
|
360
|
+
f.write(f"Grid shape: {building_height_grid.shape}\n")
|
|
361
|
+
f.write(f"Mesh size: {meshsize} m\n")
|
|
362
|
+
f.write(f"Land cover source: {land_cover_source}\n")
|
|
363
|
+
f.write(f"Building material: {building_material}\n")
|
|
364
|
+
f.write(f"Tree type: {tree_type}\n")
|
|
365
|
+
f.write(f"Bounds: {rectangle_vertices}\n")
|
|
366
|
+
|
|
367
|
+
print(f"CityLES export completed successfully!")
|
|
368
|
+
return str(output_path)
|
voxcity/generator.py
CHANGED
|
@@ -6,12 +6,19 @@ It handles land cover, building heights, canopy heights, and digital elevation m
|
|
|
6
6
|
|
|
7
7
|
The main functions are:
|
|
8
8
|
- get_land_cover_grid: Creates a grid of land cover classifications
|
|
9
|
-
- get_building_height_grid: Creates a grid of building heights
|
|
9
|
+
- get_building_height_grid: Creates a grid of building heights (supports GeoDataFrame input)
|
|
10
10
|
- get_canopy_height_grid: Creates a grid of tree canopy heights
|
|
11
11
|
- get_dem_grid: Creates a digital elevation model grid
|
|
12
12
|
- create_3d_voxel: Combines the grids into a 3D voxel representation
|
|
13
13
|
- create_3d_voxel_individuals: Creates separate voxel grids for each component
|
|
14
|
-
- get_voxcity: Main function to generate a complete voxel city model
|
|
14
|
+
- get_voxcity: Main function to generate a complete voxel city model (supports GeoDataFrame input)
|
|
15
|
+
|
|
16
|
+
Key Features:
|
|
17
|
+
- Support for multiple data sources (OpenStreetMap, ESA WorldCover, Google Earth Engine, etc.)
|
|
18
|
+
- Direct GeoDataFrame input for building data (useful for custom datasets)
|
|
19
|
+
- 3D voxel generation with configurable resolution
|
|
20
|
+
- Visualization capabilities for both 2D grids and 3D models
|
|
21
|
+
- Data export in various formats (GeoTIFF, GeoJSON, pickle)
|
|
15
22
|
"""
|
|
16
23
|
|
|
17
24
|
# Standard library imports
|
|
@@ -87,6 +94,7 @@ def get_land_cover_grid(rectangle_vertices, meshsize, source, output_dir, **kwar
|
|
|
87
94
|
- esri_landcover_year: Year for ESRI land cover data
|
|
88
95
|
- dynamic_world_date: Date for Dynamic World data
|
|
89
96
|
- gridvis: Whether to visualize the grid
|
|
97
|
+
- default_land_cover_class: Default class for grid cells with no intersecting polygons (default: 'Developed space')
|
|
90
98
|
|
|
91
99
|
Returns:
|
|
92
100
|
numpy.ndarray: Grid of land cover classifications as integer values
|
|
@@ -142,7 +150,8 @@ def get_land_cover_grid(rectangle_vertices, meshsize, source, output_dir, **kwar
|
|
|
142
150
|
# Different processing for vector vs raster data sources
|
|
143
151
|
if source == 'OpenStreetMap':
|
|
144
152
|
# Process vector data directly from GeoDataFrame
|
|
145
|
-
|
|
153
|
+
default_class = kwargs.get('default_land_cover_class', 'Developed space')
|
|
154
|
+
land_cover_grid_str = create_land_cover_grid_from_gdf_polygon(land_cover_gdf, meshsize, source, rectangle_vertices, default_class=default_class)
|
|
146
155
|
else:
|
|
147
156
|
# Process raster data from GeoTIFF file
|
|
148
157
|
land_cover_grid_str = create_land_cover_grid_from_geotiff_polygon(geotiff_path, meshsize, land_cover_classes, rectangle_vertices)
|
|
@@ -164,14 +173,15 @@ def get_land_cover_grid(rectangle_vertices, meshsize, source, output_dir, **kwar
|
|
|
164
173
|
return land_cover_grid_int
|
|
165
174
|
|
|
166
175
|
# def get_building_height_grid(rectangle_vertices, meshsize, source, output_dir="output", visualization=True, maptiler_API_key=None, file_path=None):
|
|
167
|
-
def get_building_height_grid(rectangle_vertices, meshsize, source, output_dir, **kwargs):
|
|
176
|
+
def get_building_height_grid(rectangle_vertices, meshsize, source, output_dir, building_gdf=None, **kwargs):
|
|
168
177
|
"""Creates a grid of building heights.
|
|
169
178
|
|
|
170
179
|
Args:
|
|
171
180
|
rectangle_vertices: List of coordinates defining the area of interest
|
|
172
181
|
meshsize: Size of each grid cell in meters
|
|
173
|
-
source: Data source for buildings (e.g. 'OpenStreetMap', 'Microsoft Building Footprints')
|
|
182
|
+
source: Data source for buildings (e.g. 'OpenStreetMap', 'Microsoft Building Footprints', 'GeoDataFrame')
|
|
174
183
|
output_dir: Directory to save output files
|
|
184
|
+
building_gdf: Optional GeoDataFrame with building footprint, height and other information
|
|
175
185
|
**kwargs: Additional arguments including:
|
|
176
186
|
- maptiler_API_key: API key for MapTiler
|
|
177
187
|
- building_path: Path to local building data file
|
|
@@ -187,7 +197,7 @@ def get_building_height_grid(rectangle_vertices, meshsize, source, output_dir, *
|
|
|
187
197
|
"""
|
|
188
198
|
|
|
189
199
|
# Initialize Earth Engine for satellite-based building data sources
|
|
190
|
-
if source not in ["OpenStreetMap", "Overture", "Local file"]:
|
|
200
|
+
if source not in ["OpenStreetMap", "Overture", "Local file", "GeoDataFrame"]:
|
|
191
201
|
initialize_earth_engine()
|
|
192
202
|
|
|
193
203
|
print("Creating Building Height grid\n ")
|
|
@@ -195,32 +205,40 @@ def get_building_height_grid(rectangle_vertices, meshsize, source, output_dir, *
|
|
|
195
205
|
|
|
196
206
|
os.makedirs(output_dir, exist_ok=True)
|
|
197
207
|
|
|
198
|
-
#
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
#
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
|
|
208
|
-
|
|
209
|
-
|
|
210
|
-
|
|
211
|
-
|
|
212
|
-
|
|
213
|
-
|
|
214
|
-
|
|
215
|
-
|
|
216
|
-
|
|
217
|
-
|
|
218
|
-
|
|
219
|
-
|
|
220
|
-
#
|
|
221
|
-
|
|
222
|
-
|
|
223
|
-
gdf =
|
|
208
|
+
# If building_gdf is provided, use it directly
|
|
209
|
+
if building_gdf is not None:
|
|
210
|
+
gdf = building_gdf
|
|
211
|
+
print("Using provided GeoDataFrame for building data")
|
|
212
|
+
else:
|
|
213
|
+
# Fetch building data from primary source
|
|
214
|
+
# Each source has different data formats and processing requirements
|
|
215
|
+
if source == 'Microsoft Building Footprints':
|
|
216
|
+
# Machine learning-derived building footprints from satellite imagery
|
|
217
|
+
gdf = get_mbfp_gdf(output_dir, rectangle_vertices)
|
|
218
|
+
elif source == 'OpenStreetMap':
|
|
219
|
+
# Crowd-sourced building data with varying completeness
|
|
220
|
+
gdf = load_gdf_from_openstreetmap(rectangle_vertices)
|
|
221
|
+
elif source == "Open Building 2.5D Temporal":
|
|
222
|
+
# Special case: this source provides both footprints and heights
|
|
223
|
+
# Skip GeoDataFrame processing and create grids directly
|
|
224
|
+
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)
|
|
225
|
+
elif source == 'EUBUCCO v0.1':
|
|
226
|
+
# European building database with height information
|
|
227
|
+
gdf = load_gdf_from_eubucco(rectangle_vertices, output_dir)
|
|
228
|
+
# elif source == "OpenMapTiles":
|
|
229
|
+
# # Vector tiles service for building data
|
|
230
|
+
# gdf = load_gdf_from_openmaptiles(rectangle_vertices, kwargs["maptiler_API_key"])
|
|
231
|
+
elif source == "Overture":
|
|
232
|
+
# Open building dataset from Overture Maps Foundation
|
|
233
|
+
gdf = load_gdf_from_overture(rectangle_vertices)
|
|
234
|
+
elif source == "Local file":
|
|
235
|
+
# Handle user-provided local building data files
|
|
236
|
+
_, extension = os.path.splitext(kwargs["building_path"])
|
|
237
|
+
if extension == ".gpkg":
|
|
238
|
+
gdf = get_gdf_from_gpkg(kwargs["building_path"], rectangle_vertices)
|
|
239
|
+
elif source == "GeoDataFrame":
|
|
240
|
+
# This case is handled by the building_gdf parameter above
|
|
241
|
+
raise ValueError("When source is 'GeoDataFrame', building_gdf parameter must be provided")
|
|
224
242
|
|
|
225
243
|
# Handle complementary data sources to fill gaps or provide additional information
|
|
226
244
|
# This allows combining multiple sources for better coverage or accuracy
|
|
@@ -590,16 +608,17 @@ def create_3d_voxel_individuals(building_height_grid_ori, land_cover_grid_ori, d
|
|
|
590
608
|
|
|
591
609
|
return land_cover_voxel_grid, building_voxel_grid, tree_voxel_grid, dem_voxel_grid, layered_voxel_grid
|
|
592
610
|
|
|
593
|
-
def get_voxcity(rectangle_vertices, building_source, land_cover_source, canopy_height_source, dem_source, meshsize, **kwargs):
|
|
611
|
+
def get_voxcity(rectangle_vertices, building_source, land_cover_source, canopy_height_source, dem_source, meshsize, building_gdf=None, **kwargs):
|
|
594
612
|
"""Main function to generate a complete voxel city model.
|
|
595
613
|
|
|
596
614
|
Args:
|
|
597
615
|
rectangle_vertices: List of coordinates defining the area of interest
|
|
598
|
-
building_source: Source for building height data (e.g. 'OSM', 'EUBUCCO')
|
|
616
|
+
building_source: Source for building height data (e.g. 'OSM', 'EUBUCCO', 'GeoDataFrame')
|
|
599
617
|
land_cover_source: Source for land cover data (e.g. 'ESA', 'ESRI')
|
|
600
618
|
canopy_height_source: Source for tree canopy height data
|
|
601
619
|
dem_source: Source for digital elevation model data ('Flat' or other source)
|
|
602
620
|
meshsize: Size of each grid cell in meters
|
|
621
|
+
building_gdf: Optional GeoDataFrame with building footprint, height and other information
|
|
603
622
|
**kwargs: Additional keyword arguments including:
|
|
604
623
|
- output_dir: Directory to save output files (default: 'output')
|
|
605
624
|
- min_canopy_height: Minimum height threshold for tree canopy
|
|
@@ -607,6 +626,7 @@ def get_voxcity(rectangle_vertices, building_source, land_cover_source, canopy_h
|
|
|
607
626
|
- mapvis: Whether to visualize grids on map
|
|
608
627
|
- voxelvis: Whether to visualize 3D voxel model
|
|
609
628
|
- voxelvis_img_save_path: Path to save 3D visualization
|
|
629
|
+
- default_land_cover_class: Default class for land cover grid cells with no intersecting polygons (default: 'Developed space')
|
|
610
630
|
|
|
611
631
|
Returns:
|
|
612
632
|
tuple containing:
|
|
@@ -633,7 +653,7 @@ def get_voxcity(rectangle_vertices, building_source, land_cover_source, canopy_h
|
|
|
633
653
|
land_cover_grid = get_land_cover_grid(rectangle_vertices, meshsize, land_cover_source, output_dir, **kwargs)
|
|
634
654
|
|
|
635
655
|
# Building footprints and height information
|
|
636
|
-
building_height_grid, building_min_height_grid, building_id_grid, building_gdf = get_building_height_grid(rectangle_vertices, meshsize, building_source, output_dir, **kwargs)
|
|
656
|
+
building_height_grid, building_min_height_grid, building_id_grid, building_gdf = get_building_height_grid(rectangle_vertices, meshsize, building_source, output_dir, building_gdf=building_gdf, **kwargs)
|
|
637
657
|
|
|
638
658
|
# Save building data to file for later analysis or visualization
|
|
639
659
|
if not building_gdf.empty:
|