voxcity 0.6.16__py3-none-any.whl → 0.6.18__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/generator.py CHANGED
@@ -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
- tree_mask = (land_cover_grid == 4)
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
- # Filter kwargs to only those accepted by create_building_height_grid_from_gdf_polygon
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
- tree_mask = (land_cover_grid == 4)
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