voxcity 0.6.16__tar.gz → 0.6.17__tar.gz
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-0.6.16 → voxcity-0.6.17}/PKG-INFO +1 -1
- {voxcity-0.6.16 → voxcity-0.6.17}/pyproject.toml +1 -1
- {voxcity-0.6.16 → voxcity-0.6.17}/src/voxcity/downloader/osm.py +23 -7
- {voxcity-0.6.16 → voxcity-0.6.17}/src/voxcity/downloader/overture.py +26 -1
- {voxcity-0.6.16 → voxcity-0.6.17}/src/voxcity/generator.py +102 -7
- {voxcity-0.6.16 → voxcity-0.6.17}/src/voxcity/geoprocessor/grid.py +1738 -1732
- {voxcity-0.6.16 → voxcity-0.6.17}/AUTHORS.rst +0 -0
- {voxcity-0.6.16 → voxcity-0.6.17}/LICENSE +0 -0
- {voxcity-0.6.16 → voxcity-0.6.17}/README.md +0 -0
- {voxcity-0.6.16 → voxcity-0.6.17}/src/voxcity/__init__.py +0 -0
- {voxcity-0.6.16 → voxcity-0.6.17}/src/voxcity/downloader/__init__.py +0 -0
- {voxcity-0.6.16 → voxcity-0.6.17}/src/voxcity/downloader/citygml.py +0 -0
- {voxcity-0.6.16 → voxcity-0.6.17}/src/voxcity/downloader/eubucco.py +0 -0
- {voxcity-0.6.16 → voxcity-0.6.17}/src/voxcity/downloader/gee.py +0 -0
- {voxcity-0.6.16 → voxcity-0.6.17}/src/voxcity/downloader/mbfp.py +0 -0
- {voxcity-0.6.16 → voxcity-0.6.17}/src/voxcity/downloader/oemj.py +0 -0
- {voxcity-0.6.16 → voxcity-0.6.17}/src/voxcity/downloader/utils.py +0 -0
- {voxcity-0.6.16 → voxcity-0.6.17}/src/voxcity/exporter/__init__.py +0 -0
- {voxcity-0.6.16 → voxcity-0.6.17}/src/voxcity/exporter/cityles.py +0 -0
- {voxcity-0.6.16 → voxcity-0.6.17}/src/voxcity/exporter/envimet.py +0 -0
- {voxcity-0.6.16 → voxcity-0.6.17}/src/voxcity/exporter/magicavoxel.py +0 -0
- {voxcity-0.6.16 → voxcity-0.6.17}/src/voxcity/exporter/obj.py +0 -0
- {voxcity-0.6.16 → voxcity-0.6.17}/src/voxcity/geoprocessor/__init__.py +0 -0
- {voxcity-0.6.16 → voxcity-0.6.17}/src/voxcity/geoprocessor/draw.py +0 -0
- {voxcity-0.6.16 → voxcity-0.6.17}/src/voxcity/geoprocessor/mesh.py +0 -0
- {voxcity-0.6.16 → voxcity-0.6.17}/src/voxcity/geoprocessor/network.py +0 -0
- {voxcity-0.6.16 → voxcity-0.6.17}/src/voxcity/geoprocessor/polygon.py +0 -0
- {voxcity-0.6.16 → voxcity-0.6.17}/src/voxcity/geoprocessor/utils.py +0 -0
- {voxcity-0.6.16 → voxcity-0.6.17}/src/voxcity/simulator/__init__.py +0 -0
- {voxcity-0.6.16 → voxcity-0.6.17}/src/voxcity/simulator/solar.py +0 -0
- {voxcity-0.6.16 → voxcity-0.6.17}/src/voxcity/simulator/utils.py +0 -0
- {voxcity-0.6.16 → voxcity-0.6.17}/src/voxcity/simulator/view.py +0 -0
- {voxcity-0.6.16 → voxcity-0.6.17}/src/voxcity/utils/__init__.py +0 -0
- {voxcity-0.6.16 → voxcity-0.6.17}/src/voxcity/utils/lc.py +0 -0
- {voxcity-0.6.16 → voxcity-0.6.17}/src/voxcity/utils/material.py +0 -0
- {voxcity-0.6.16 → voxcity-0.6.17}/src/voxcity/utils/visualization.py +0 -0
- {voxcity-0.6.16 → voxcity-0.6.17}/src/voxcity/utils/weather.py +0 -0
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
[tool.poetry]
|
|
2
2
|
name = "voxcity"
|
|
3
|
-
version = "0.6.
|
|
3
|
+
version = "0.6.17"
|
|
4
4
|
description = "voxcity is an easy and one-stop tool to output 3d city models for microclimate simulation by integrating multiple geospatial open-data"
|
|
5
5
|
readme = "README.md"
|
|
6
6
|
license = "MIT"
|
|
@@ -370,7 +370,7 @@ def create_rings_from_ways(way_ids, ways, nodes):
|
|
|
370
370
|
|
|
371
371
|
return rings
|
|
372
372
|
|
|
373
|
-
def load_gdf_from_openstreetmap(rectangle_vertices):
|
|
373
|
+
def load_gdf_from_openstreetmap(rectangle_vertices, floor_height=3.0):
|
|
374
374
|
"""Download and process building footprint data from OpenStreetMap.
|
|
375
375
|
|
|
376
376
|
This function:
|
|
@@ -471,7 +471,7 @@ def load_gdf_from_openstreetmap(rectangle_vertices):
|
|
|
471
471
|
"""
|
|
472
472
|
return [coord for coord in geometry] # Keep original order since already (lon, lat)
|
|
473
473
|
|
|
474
|
-
def get_height_from_properties(properties):
|
|
474
|
+
def get_height_from_properties(properties, floor_height=3.0):
|
|
475
475
|
"""Helper function to extract height from properties.
|
|
476
476
|
|
|
477
477
|
Args:
|
|
@@ -487,9 +487,25 @@ def load_gdf_from_openstreetmap(rectangle_vertices):
|
|
|
487
487
|
except ValueError:
|
|
488
488
|
pass
|
|
489
489
|
|
|
490
|
+
# Infer from floors when available
|
|
491
|
+
floors_candidates = [
|
|
492
|
+
properties.get('building:levels'),
|
|
493
|
+
properties.get('levels'),
|
|
494
|
+
properties.get('num_floors')
|
|
495
|
+
]
|
|
496
|
+
for floors in floors_candidates:
|
|
497
|
+
if floors is None:
|
|
498
|
+
continue
|
|
499
|
+
try:
|
|
500
|
+
floors_val = float(floors)
|
|
501
|
+
if floors_val > 0:
|
|
502
|
+
return float(floor_height) * floors_val
|
|
503
|
+
except ValueError:
|
|
504
|
+
continue
|
|
505
|
+
|
|
490
506
|
return 0 # Default height if no valid height found
|
|
491
507
|
|
|
492
|
-
def extract_properties(element):
|
|
508
|
+
def extract_properties(element, floor_height=3.0):
|
|
493
509
|
"""Helper function to extract and process properties from an element.
|
|
494
510
|
|
|
495
511
|
Args:
|
|
@@ -501,7 +517,7 @@ def load_gdf_from_openstreetmap(rectangle_vertices):
|
|
|
501
517
|
properties = element.get('tags', {})
|
|
502
518
|
|
|
503
519
|
# Get height (now using the helper function)
|
|
504
|
-
height = get_height_from_properties(properties)
|
|
520
|
+
height = get_height_from_properties(properties, floor_height=floor_height)
|
|
505
521
|
|
|
506
522
|
# Get min_height and min_level
|
|
507
523
|
min_height = properties.get('min_height', '0')
|
|
@@ -526,7 +542,7 @@ def load_gdf_from_openstreetmap(rectangle_vertices):
|
|
|
526
542
|
"is_inner": False,
|
|
527
543
|
"levels": levels,
|
|
528
544
|
"height_source": "explicit" if properties.get('height') or properties.get('building:height')
|
|
529
|
-
else "levels" if levels is not None
|
|
545
|
+
else "levels" if (levels is not None) or (properties.get('num_floors') is not None)
|
|
530
546
|
else "default",
|
|
531
547
|
"min_level": min_level if min_level != '0' else None,
|
|
532
548
|
"building": properties.get('building', 'no'),
|
|
@@ -584,13 +600,13 @@ def load_gdf_from_openstreetmap(rectangle_vertices):
|
|
|
584
600
|
if element['type'] == 'way':
|
|
585
601
|
if 'geometry' in element:
|
|
586
602
|
coords = [(node['lon'], node['lat']) for node in element['geometry']]
|
|
587
|
-
properties = extract_properties(element)
|
|
603
|
+
properties = extract_properties(element, floor_height=floor_height)
|
|
588
604
|
feature = create_polygon_feature(coords, properties)
|
|
589
605
|
if feature:
|
|
590
606
|
features.append(feature)
|
|
591
607
|
|
|
592
608
|
elif element['type'] == 'relation':
|
|
593
|
-
properties = extract_properties(element)
|
|
609
|
+
properties = extract_properties(element, floor_height=floor_height)
|
|
594
610
|
|
|
595
611
|
# Process each member of the relation
|
|
596
612
|
for member in element['members']:
|
|
@@ -254,7 +254,7 @@ def join_gdfs_vertically(gdf1, gdf2):
|
|
|
254
254
|
|
|
255
255
|
return combined_gdf
|
|
256
256
|
|
|
257
|
-
def load_gdf_from_overture(rectangle_vertices):
|
|
257
|
+
def load_gdf_from_overture(rectangle_vertices, floor_height=3.0):
|
|
258
258
|
"""
|
|
259
259
|
Download and process building footprint data from Overture Maps.
|
|
260
260
|
|
|
@@ -287,6 +287,31 @@ def load_gdf_from_overture(rectangle_vertices):
|
|
|
287
287
|
# Combine both datasets into a single comprehensive building dataset
|
|
288
288
|
joined_building_gdf = join_gdfs_vertically(building_gdf, building_part_gdf)
|
|
289
289
|
|
|
290
|
+
# Ensure numeric height and infer from floors when missing
|
|
291
|
+
try:
|
|
292
|
+
joined_building_gdf['height'] = pd.to_numeric(joined_building_gdf.get('height', None), errors='coerce')
|
|
293
|
+
except Exception:
|
|
294
|
+
# Create height column if missing
|
|
295
|
+
joined_building_gdf['height'] = None
|
|
296
|
+
joined_building_gdf['height'] = pd.to_numeric(joined_building_gdf['height'], errors='coerce')
|
|
297
|
+
|
|
298
|
+
# Combine possible floors columns (first non-null among candidates)
|
|
299
|
+
floors_candidates = []
|
|
300
|
+
for col in ['building:levels', 'levels', 'num_floors', 'floors']:
|
|
301
|
+
if col in joined_building_gdf.columns:
|
|
302
|
+
floors_candidates.append(pd.to_numeric(joined_building_gdf[col], errors='coerce'))
|
|
303
|
+
if floors_candidates:
|
|
304
|
+
floors_series = floors_candidates[0]
|
|
305
|
+
for s in floors_candidates[1:]:
|
|
306
|
+
floors_series = floors_series.combine_first(s)
|
|
307
|
+
# Infer height where height is NaN/<=0 and floors > 0
|
|
308
|
+
mask_missing_height = (~joined_building_gdf['height'].notna()) | (joined_building_gdf['height'] <= 0)
|
|
309
|
+
if isinstance(floor_height, (int, float)):
|
|
310
|
+
inferred = floors_series * float(floor_height)
|
|
311
|
+
else:
|
|
312
|
+
inferred = floors_series * 3.0
|
|
313
|
+
joined_building_gdf.loc[mask_missing_height & (floors_series > 0), 'height'] = inferred
|
|
314
|
+
|
|
290
315
|
# Assign sequential IDs based on the final dataset index
|
|
291
316
|
joined_building_gdf['id'] = joined_building_gdf.index
|
|
292
317
|
|
|
@@ -226,12 +226,14 @@ def get_building_height_grid(rectangle_vertices, meshsize, source, output_dir, b
|
|
|
226
226
|
else:
|
|
227
227
|
# Fetch building data from primary source
|
|
228
228
|
# Each source has different data formats and processing requirements
|
|
229
|
+
# Floor height (m) for inferring heights from floors/levels
|
|
230
|
+
floor_height = kwargs.get("floor_height", 3.0)
|
|
229
231
|
if source == 'Microsoft Building Footprints':
|
|
230
232
|
# Machine learning-derived building footprints from satellite imagery
|
|
231
233
|
gdf = get_mbfp_gdf(output_dir, rectangle_vertices)
|
|
232
234
|
elif source == 'OpenStreetMap':
|
|
233
235
|
# Crowd-sourced building data with varying completeness
|
|
234
|
-
gdf = load_gdf_from_openstreetmap(rectangle_vertices)
|
|
236
|
+
gdf = load_gdf_from_openstreetmap(rectangle_vertices, floor_height=floor_height)
|
|
235
237
|
elif source == "Open Building 2.5D Temporal":
|
|
236
238
|
# Special case: this source provides both footprints and heights
|
|
237
239
|
# Skip GeoDataFrame processing and create grids directly
|
|
@@ -244,7 +246,7 @@ def get_building_height_grid(rectangle_vertices, meshsize, source, output_dir, b
|
|
|
244
246
|
# gdf = load_gdf_from_openmaptiles(rectangle_vertices, kwargs["maptiler_API_key"])
|
|
245
247
|
elif source == "Overture":
|
|
246
248
|
# Open building dataset from Overture Maps Foundation
|
|
247
|
-
gdf = load_gdf_from_overture(rectangle_vertices)
|
|
249
|
+
gdf = load_gdf_from_overture(rectangle_vertices, floor_height=floor_height)
|
|
248
250
|
elif source == "Local file":
|
|
249
251
|
# Handle user-provided local building data files
|
|
250
252
|
_, extension = os.path.splitext(kwargs["building_path"])
|
|
@@ -285,13 +287,13 @@ def get_building_height_grid(rectangle_vertices, meshsize, source, output_dir, b
|
|
|
285
287
|
if building_complementary_source == 'Microsoft Building Footprints':
|
|
286
288
|
gdf_comp = get_mbfp_gdf(output_dir, rectangle_vertices)
|
|
287
289
|
elif building_complementary_source == 'OpenStreetMap':
|
|
288
|
-
gdf_comp = load_gdf_from_openstreetmap(rectangle_vertices)
|
|
290
|
+
gdf_comp = load_gdf_from_openstreetmap(rectangle_vertices, floor_height=floor_height)
|
|
289
291
|
elif building_complementary_source == 'EUBUCCO v0.1':
|
|
290
292
|
gdf_comp = load_gdf_from_eubucco(rectangle_vertices, output_dir)
|
|
291
293
|
# elif building_complementary_source == "OpenMapTiles":
|
|
292
294
|
# gdf_comp = load_gdf_from_openmaptiles(rectangle_vertices, kwargs["maptiler_API_key"])
|
|
293
295
|
elif building_complementary_source == "Overture":
|
|
294
|
-
gdf_comp = load_gdf_from_overture(rectangle_vertices)
|
|
296
|
+
gdf_comp = load_gdf_from_overture(rectangle_vertices, floor_height=floor_height)
|
|
295
297
|
elif building_complementary_source == "Local file":
|
|
296
298
|
_, extension = os.path.splitext(kwargs["building_complementary_path"])
|
|
297
299
|
if extension == ".gpkg":
|
|
@@ -715,7 +717,12 @@ def get_voxcity(rectangle_vertices, building_source, land_cover_source, canopy_h
|
|
|
715
717
|
# Create uniform canopy height for all tree-covered areas (top grid)
|
|
716
718
|
canopy_height_grid = np.zeros_like(land_cover_grid, dtype=float)
|
|
717
719
|
static_tree_height = kwargs.get("static_tree_height", 10.0)
|
|
718
|
-
|
|
720
|
+
# Determine tree class indices based on source-specific class names
|
|
721
|
+
_classes = get_land_cover_classes(land_cover_source)
|
|
722
|
+
_class_to_int = {name: i for i, name in enumerate(_classes.values())}
|
|
723
|
+
_tree_labels = ["Tree", "Trees", "Tree Canopy"]
|
|
724
|
+
_tree_indices = [_class_to_int[label] for label in _tree_labels if label in _class_to_int]
|
|
725
|
+
tree_mask = np.isin(land_cover_grid, _tree_indices) if _tree_indices else np.zeros_like(land_cover_grid, dtype=bool)
|
|
719
726
|
canopy_height_grid[tree_mask] = static_tree_height
|
|
720
727
|
|
|
721
728
|
# Derive bottom from trunk_height_ratio
|
|
@@ -917,11 +924,79 @@ def get_voxcity_CityGML(rectangle_vertices, land_cover_source, canopy_height_sou
|
|
|
917
924
|
# get all required gdfs
|
|
918
925
|
building_gdf, terrain_gdf, vegetation_gdf = load_buid_dem_veg_from_citygml(url=url_citygml, citygml_path=citygml_path, base_dir=output_dir, rectangle_vertices=rectangle_vertices)
|
|
919
926
|
|
|
927
|
+
# Normalize CRS to WGS84 (EPSG:4326) to ensure consistent operations downstream
|
|
928
|
+
try:
|
|
929
|
+
import geopandas as gpd # noqa: F401
|
|
930
|
+
if building_gdf is not None:
|
|
931
|
+
if building_gdf.crs is None:
|
|
932
|
+
building_gdf = building_gdf.set_crs(epsg=4326)
|
|
933
|
+
elif getattr(building_gdf.crs, 'to_epsg', lambda: None)() != 4326 and building_gdf.crs != "EPSG:4326":
|
|
934
|
+
building_gdf = building_gdf.to_crs(epsg=4326)
|
|
935
|
+
if terrain_gdf is not None:
|
|
936
|
+
if terrain_gdf.crs is None:
|
|
937
|
+
terrain_gdf = terrain_gdf.set_crs(epsg=4326)
|
|
938
|
+
elif getattr(terrain_gdf.crs, 'to_epsg', lambda: None)() != 4326 and terrain_gdf.crs != "EPSG:4326":
|
|
939
|
+
terrain_gdf = terrain_gdf.to_crs(epsg=4326)
|
|
940
|
+
if vegetation_gdf is not None:
|
|
941
|
+
if vegetation_gdf.crs is None:
|
|
942
|
+
vegetation_gdf = vegetation_gdf.set_crs(epsg=4326)
|
|
943
|
+
elif getattr(vegetation_gdf.crs, 'to_epsg', lambda: None)() != 4326 and vegetation_gdf.crs != "EPSG:4326":
|
|
944
|
+
vegetation_gdf = vegetation_gdf.to_crs(epsg=4326)
|
|
945
|
+
except Exception:
|
|
946
|
+
pass
|
|
947
|
+
|
|
920
948
|
land_cover_grid = get_land_cover_grid(rectangle_vertices, meshsize, land_cover_source, output_dir, **kwargs)
|
|
921
949
|
|
|
922
950
|
# building_height_grid, building_min_height_grid, building_id_grid, building_gdf = get_building_height_grid(rectangle_vertices, meshsize, building_source, output_dir, **kwargs)
|
|
923
951
|
print("Creating building height grid")
|
|
924
|
-
#
|
|
952
|
+
# Prepare complementary building source if provided
|
|
953
|
+
building_complementary_source = kwargs.get("building_complementary_source")
|
|
954
|
+
gdf_comp = None
|
|
955
|
+
geotiff_path_comp = None
|
|
956
|
+
complement_building_footprints = kwargs.get("complement_building_footprints")
|
|
957
|
+
# Default to complement footprints when a complementary source is specified
|
|
958
|
+
if complement_building_footprints is None and (building_complementary_source not in (None, "None")):
|
|
959
|
+
complement_building_footprints = True
|
|
960
|
+
|
|
961
|
+
if (building_complementary_source is not None) and (building_complementary_source != "None"):
|
|
962
|
+
# Vector complementary sources
|
|
963
|
+
floor_height = kwargs.get("floor_height", 3.0)
|
|
964
|
+
if building_complementary_source == 'Microsoft Building Footprints':
|
|
965
|
+
gdf_comp = get_mbfp_gdf(kwargs.get("output_dir", "output"), rectangle_vertices)
|
|
966
|
+
elif building_complementary_source == 'OpenStreetMap':
|
|
967
|
+
gdf_comp = load_gdf_from_openstreetmap(rectangle_vertices, floor_height=floor_height)
|
|
968
|
+
elif building_complementary_source == 'EUBUCCO v0.1':
|
|
969
|
+
gdf_comp = load_gdf_from_eubucco(rectangle_vertices, kwargs.get("output_dir", "output"))
|
|
970
|
+
elif building_complementary_source == 'Overture':
|
|
971
|
+
gdf_comp = load_gdf_from_overture(rectangle_vertices, floor_height=floor_height)
|
|
972
|
+
elif building_complementary_source == 'Local file':
|
|
973
|
+
comp_path = kwargs.get("building_complementary_path")
|
|
974
|
+
if comp_path is not None:
|
|
975
|
+
_, extension = os.path.splitext(comp_path)
|
|
976
|
+
if extension == ".gpkg":
|
|
977
|
+
gdf_comp = get_gdf_from_gpkg(comp_path, rectangle_vertices)
|
|
978
|
+
# Ensure complementary GDF uses WGS84
|
|
979
|
+
if gdf_comp is not None:
|
|
980
|
+
try:
|
|
981
|
+
if gdf_comp.crs is None:
|
|
982
|
+
gdf_comp = gdf_comp.set_crs(epsg=4326)
|
|
983
|
+
elif getattr(gdf_comp.crs, 'to_epsg', lambda: None)() != 4326 and gdf_comp.crs != "EPSG:4326":
|
|
984
|
+
gdf_comp = gdf_comp.to_crs(epsg=4326)
|
|
985
|
+
except Exception:
|
|
986
|
+
pass
|
|
987
|
+
# Raster complementary sources (height only)
|
|
988
|
+
elif building_complementary_source == "Open Building 2.5D Temporal":
|
|
989
|
+
roi = get_roi(rectangle_vertices)
|
|
990
|
+
os.makedirs(kwargs.get("output_dir", "output"), exist_ok=True)
|
|
991
|
+
geotiff_path_comp = os.path.join(kwargs.get("output_dir", "output"), "building_height.tif")
|
|
992
|
+
save_geotiff_open_buildings_temporal(roi, geotiff_path_comp)
|
|
993
|
+
elif building_complementary_source in ["England 1m DSM - DTM", "Netherlands 0.5m DSM - DTM"]:
|
|
994
|
+
roi = get_roi(rectangle_vertices)
|
|
995
|
+
os.makedirs(kwargs.get("output_dir", "output"), exist_ok=True)
|
|
996
|
+
geotiff_path_comp = os.path.join(kwargs.get("output_dir", "output"), "building_height.tif")
|
|
997
|
+
save_geotiff_dsm_minus_dtm(roi, geotiff_path_comp, meshsize, building_complementary_source)
|
|
998
|
+
|
|
999
|
+
# Filter and assemble kwargs accepted by the grid function
|
|
925
1000
|
_allowed_building_kwargs = {
|
|
926
1001
|
"overlapping_footprint",
|
|
927
1002
|
"gdf_comp",
|
|
@@ -930,6 +1005,21 @@ def get_voxcity_CityGML(rectangle_vertices, land_cover_source, canopy_height_sou
|
|
|
930
1005
|
"complement_height",
|
|
931
1006
|
}
|
|
932
1007
|
_building_kwargs = {k: v for k, v in kwargs.items() if k in _allowed_building_kwargs}
|
|
1008
|
+
if gdf_comp is not None:
|
|
1009
|
+
_building_kwargs["gdf_comp"] = gdf_comp
|
|
1010
|
+
if geotiff_path_comp is not None:
|
|
1011
|
+
_building_kwargs["geotiff_path_comp"] = geotiff_path_comp
|
|
1012
|
+
if complement_building_footprints is not None:
|
|
1013
|
+
_building_kwargs["complement_building_footprints"] = complement_building_footprints
|
|
1014
|
+
|
|
1015
|
+
# Map user-provided building_complement_height -> complement_height for grid builder
|
|
1016
|
+
comp_height_user = kwargs.get("building_complement_height")
|
|
1017
|
+
if comp_height_user is not None:
|
|
1018
|
+
_building_kwargs["complement_height"] = comp_height_user
|
|
1019
|
+
# If footprints are being complemented and no height provided, default to 10
|
|
1020
|
+
if _building_kwargs.get("complement_building_footprints") and ("complement_height" not in _building_kwargs):
|
|
1021
|
+
_building_kwargs["complement_height"] = 10.0
|
|
1022
|
+
|
|
933
1023
|
building_height_grid, building_min_height_grid, building_id_grid, filtered_buildings = create_building_height_grid_from_gdf_polygon(
|
|
934
1024
|
building_gdf, meshsize, rectangle_vertices, **_building_kwargs
|
|
935
1025
|
)
|
|
@@ -953,7 +1043,12 @@ def get_voxcity_CityGML(rectangle_vertices, land_cover_source, canopy_height_sou
|
|
|
953
1043
|
|
|
954
1044
|
# Set default static height for trees (20 meters is a typical average tree height)
|
|
955
1045
|
static_tree_height = kwargs.get("static_tree_height", 10.0)
|
|
956
|
-
|
|
1046
|
+
# Determine tree class indices based on source-specific class names
|
|
1047
|
+
_classes = get_land_cover_classes(land_cover_source)
|
|
1048
|
+
_class_to_int = {name: i for i, name in enumerate(_classes.values())}
|
|
1049
|
+
_tree_labels = ["Tree", "Trees", "Tree Canopy"]
|
|
1050
|
+
_tree_indices = [_class_to_int[label] for label in _tree_labels if label in _class_to_int]
|
|
1051
|
+
tree_mask = np.isin(land_cover_grid, _tree_indices) if _tree_indices else np.zeros_like(land_cover_grid, dtype=bool)
|
|
957
1052
|
|
|
958
1053
|
# Set static height for tree cells
|
|
959
1054
|
canopy_height_grid_comp[tree_mask] = static_tree_height
|