voxcity 0.3.27__tar.gz → 0.4.1__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.

Files changed (53) hide show
  1. {voxcity-0.3.27 → voxcity-0.4.1}/PKG-INFO +12 -12
  2. {voxcity-0.3.27 → voxcity-0.4.1}/README.md +11 -11
  3. {voxcity-0.3.27 → voxcity-0.4.1}/pyproject.toml +1 -1
  4. {voxcity-0.3.27 → voxcity-0.4.1}/src/voxcity/exporter/obj.py +2 -2
  5. {voxcity-0.3.27 → voxcity-0.4.1}/src/voxcity/geoprocessor/grid.py +5 -1
  6. {voxcity-0.3.27 → voxcity-0.4.1}/src/voxcity/geoprocessor/mesh.py +21 -1
  7. {voxcity-0.3.27 → voxcity-0.4.1}/src/voxcity/geoprocessor/polygon.py +95 -1
  8. {voxcity-0.3.27 → voxcity-0.4.1}/src/voxcity/simulator/solar.py +656 -7
  9. {voxcity-0.3.27 → voxcity-0.4.1}/src/voxcity/simulator/view.py +635 -2
  10. {voxcity-0.3.27 → voxcity-0.4.1}/src/voxcity/utils/visualization.py +767 -168
  11. {voxcity-0.3.27 → voxcity-0.4.1}/src/voxcity.egg-info/PKG-INFO +12 -12
  12. {voxcity-0.3.27 → voxcity-0.4.1}/AUTHORS.rst +0 -0
  13. {voxcity-0.3.27 → voxcity-0.4.1}/CONTRIBUTING.rst +0 -0
  14. {voxcity-0.3.27 → voxcity-0.4.1}/HISTORY.rst +0 -0
  15. {voxcity-0.3.27 → voxcity-0.4.1}/LICENSE +0 -0
  16. {voxcity-0.3.27 → voxcity-0.4.1}/MANIFEST.in +0 -0
  17. {voxcity-0.3.27 → voxcity-0.4.1}/docs/Makefile +0 -0
  18. {voxcity-0.3.27 → voxcity-0.4.1}/docs/archive/README.rst +0 -0
  19. {voxcity-0.3.27 → voxcity-0.4.1}/docs/authors.rst +0 -0
  20. {voxcity-0.3.27 → voxcity-0.4.1}/docs/conf.py +0 -0
  21. {voxcity-0.3.27 → voxcity-0.4.1}/docs/index.rst +0 -0
  22. {voxcity-0.3.27 → voxcity-0.4.1}/docs/make.bat +0 -0
  23. {voxcity-0.3.27 → voxcity-0.4.1}/setup.cfg +0 -0
  24. {voxcity-0.3.27 → voxcity-0.4.1}/src/voxcity/__init__.py +0 -0
  25. {voxcity-0.3.27 → voxcity-0.4.1}/src/voxcity/downloader/__init__.py +0 -0
  26. {voxcity-0.3.27 → voxcity-0.4.1}/src/voxcity/downloader/eubucco.py +0 -0
  27. {voxcity-0.3.27 → voxcity-0.4.1}/src/voxcity/downloader/gee.py +0 -0
  28. {voxcity-0.3.27 → voxcity-0.4.1}/src/voxcity/downloader/mbfp.py +0 -0
  29. {voxcity-0.3.27 → voxcity-0.4.1}/src/voxcity/downloader/oemj.py +0 -0
  30. {voxcity-0.3.27 → voxcity-0.4.1}/src/voxcity/downloader/omt.py +0 -0
  31. {voxcity-0.3.27 → voxcity-0.4.1}/src/voxcity/downloader/osm.py +0 -0
  32. {voxcity-0.3.27 → voxcity-0.4.1}/src/voxcity/downloader/overture.py +0 -0
  33. {voxcity-0.3.27 → voxcity-0.4.1}/src/voxcity/downloader/utils.py +0 -0
  34. {voxcity-0.3.27 → voxcity-0.4.1}/src/voxcity/exporter/__init_.py +0 -0
  35. {voxcity-0.3.27 → voxcity-0.4.1}/src/voxcity/exporter/envimet.py +0 -0
  36. {voxcity-0.3.27 → voxcity-0.4.1}/src/voxcity/exporter/magicavoxel.py +0 -0
  37. {voxcity-0.3.27 → voxcity-0.4.1}/src/voxcity/generator.py +0 -0
  38. {voxcity-0.3.27 → voxcity-0.4.1}/src/voxcity/geoprocessor/__init_.py +0 -0
  39. {voxcity-0.3.27 → voxcity-0.4.1}/src/voxcity/geoprocessor/draw.py +0 -0
  40. {voxcity-0.3.27 → voxcity-0.4.1}/src/voxcity/geoprocessor/network.py +0 -0
  41. {voxcity-0.3.27 → voxcity-0.4.1}/src/voxcity/geoprocessor/utils.py +0 -0
  42. {voxcity-0.3.27 → voxcity-0.4.1}/src/voxcity/simulator/__init_.py +0 -0
  43. {voxcity-0.3.27 → voxcity-0.4.1}/src/voxcity/simulator/utils.py +0 -0
  44. {voxcity-0.3.27 → voxcity-0.4.1}/src/voxcity/utils/__init_.py +0 -0
  45. {voxcity-0.3.27 → voxcity-0.4.1}/src/voxcity/utils/lc.py +0 -0
  46. {voxcity-0.3.27 → voxcity-0.4.1}/src/voxcity/utils/material.py +0 -0
  47. {voxcity-0.3.27 → voxcity-0.4.1}/src/voxcity/utils/weather.py +0 -0
  48. {voxcity-0.3.27 → voxcity-0.4.1}/src/voxcity.egg-info/SOURCES.txt +0 -0
  49. {voxcity-0.3.27 → voxcity-0.4.1}/src/voxcity.egg-info/dependency_links.txt +0 -0
  50. {voxcity-0.3.27 → voxcity-0.4.1}/src/voxcity.egg-info/requires.txt +0 -0
  51. {voxcity-0.3.27 → voxcity-0.4.1}/src/voxcity.egg-info/top_level.txt +0 -0
  52. {voxcity-0.3.27 → voxcity-0.4.1}/tests/__init__.py +0 -0
  53. {voxcity-0.3.27 → voxcity-0.4.1}/tests/voxelcity.py +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.2
2
2
  Name: voxcity
3
- Version: 0.3.27
3
+ Version: 0.4.1
4
4
  Summary: voxcity is an easy and one-stop tool to output 3d city models for microclimate simulation by integrating multiple geospatial open-data
5
5
  Author-email: Kunihiko Fujiwara <kunihiko@nus.edu.sg>
6
6
  Maintainer-email: Kunihiko Fujiwara <kunihiko@nus.edu.sg>
@@ -69,7 +69,7 @@ Requires-Dist: ruff; extra == "dev"
69
69
 
70
70
  # VoxCity
71
71
 
72
- **VoxCity** is a Python package that provides a one-stop solution for grid-based 3D city model generation and urban simulation for cities worldwide. It integrates various geospatial datasets—such as building footprints, land cover, canopy height, and digital elevation models (DEMs)—to generate 2D and 3D representations of urban areas. It can export data in formats compatible with popular simulation tools like ENVI-MET, as well as visualization tools like MagicaVoxel, and supports simulations such as sky view index and green view index calculations.
72
+ **voxcity** is a Python package that provides a seamless solution for grid-based 3D city model generation and urban simulation for cities worldwide. VoxCity's generator module automatically downloads building heights, tree canopy heights, land cover, and terrain elevation within a specified target area, and voxelizes buildings, trees, land cover, and terrain to generate an integrated voxel city model. The simulator module enables users to conduct environmental simulations, including solar radiation and view index analyses. Users can export the generated models using several file formats compatible with external software, such as ENVI-met (INX), Blender, and Rhino (OBJ). Try it out using the [Google Colab Demo](https://colab.research.google.com/drive/1Lofd3RawKMr6QuUsamGaF48u2MN0hfrP?usp=sharing) or your local environment.
73
73
 
74
74
  <!-- <p align="center">
75
75
  <picture>
@@ -180,7 +180,7 @@ rectangle_vertices = [
180
180
  Use the GUI map interface to draw a rectangular domain of interest.
181
181
 
182
182
  ```python
183
- from voxcity.geo.draw import draw_rectangle_map_cityname
183
+ from voxcity.geoprocessor.draw import draw_rectangle_map_cityname
184
184
 
185
185
  cityname = "tokyo"
186
186
  m, rectangle_vertices = draw_rectangle_map_cityname(cityname, zoom=15)
@@ -191,7 +191,7 @@ m
191
191
  Choose the width and height in meters and select the center point on the map.
192
192
 
193
193
  ```python
194
- from voxcity.geo.draw import center_location_map_cityname
194
+ from voxcity.geoprocessor.draw import center_location_map_cityname
195
195
 
196
196
  width = 500
197
197
  height = 500
@@ -224,7 +224,7 @@ kwargs = {
224
224
  Generate voxel data grids and corresponding building geoJSON:
225
225
 
226
226
  ```python
227
- from voxcity import get_voxcity
227
+ from voxcity.generator import get_voxcity
228
228
 
229
229
  voxcity_grid, building_height_grid, building_min_height_grid, \
230
230
  building_id_grid, canopy_height_grid, land_cover_grid, dem_grid, \
@@ -245,7 +245,7 @@ building_gdf = get_voxcity(
245
245
  [ENVI-MET](https://www.envi-met.com/) is an advanced microclimate simulation software specialized in modeling urban environments. It simulates the interactions between buildings, vegetation, and various climate parameters like temperature, wind flow, humidity, and radiation. The software is used widely in urban planning, architecture, and environmental studies (Commercial, offers educational licenses).
246
246
 
247
247
  ```python
248
- from voxcity.file.envimet import export_inx, generate_edb_file
248
+ from voxcity.exporter.envimet import export_inx, generate_edb_file
249
249
 
250
250
  envimet_kwargs = {
251
251
  "output_directory": "output", # Directory where output files will be saved
@@ -271,7 +271,7 @@ generate_edb_file(**envimet_kwargs)
271
271
  #### OBJ Files:
272
272
 
273
273
  ```python
274
- from voxcity.file.obj import export_obj
274
+ from voxcity.exporter.obj import export_obj
275
275
 
276
276
  output_directory = "output" # Directory where output files will be saved
277
277
  output_file_name = "voxcity" # Base name for the output OBJ file
@@ -295,7 +295,7 @@ The generated OBJ files can be opened and rendered in the following 3D visualiza
295
295
  [MagicaVoxel](https://ephtracy.github.io/) is a lightweight and user-friendly voxel art editor. It allows users to create, edit, and render voxel-based 3D models with an intuitive interface, making it perfect for modifying and visualizing voxelized city models. The software is free and available for Windows and Mac.
296
296
 
297
297
  ```python
298
- from voxcity.file.magicavoxel import export_magicavoxel_vox
298
+ from voxcity.exporter.magicavoxel import export_magicavoxel_vox
299
299
 
300
300
  output_path = "output"
301
301
  base_filename = "voxcity"
@@ -313,7 +313,7 @@ export_magicavoxel_vox(voxcity_grid, output_path, base_filename=base_filename)
313
313
  #### Compute Solar Irradiance:
314
314
 
315
315
  ```python
316
- from voxcity.sim.solar import get_global_solar_irradiance_using_epw
316
+ from voxcity.simulator.solar import get_global_solar_irradiance_using_epw
317
317
 
318
318
  solar_kwargs = {
319
319
  "download_nearest_epw": True, # Whether to automatically download nearest EPW weather file based on location from Climate.OneBuilding.Org
@@ -369,7 +369,7 @@ cum_solar_grid = get_global_solar_irradiance_using_epw(
369
369
  #### Compute Green View Index (GVI) and Sky View Index (SVI):
370
370
 
371
371
  ```python
372
- from voxcity.sim.view import get_view_index
372
+ from voxcity.simulator.view import get_view_index
373
373
 
374
374
  view_kwargs = {
375
375
  "view_point_height": 1.5, # Height of observer viewpoint in meters
@@ -401,7 +401,7 @@ svi_grid = get_view_index(voxcity_grid, meshsize, mode='sky', **view_kwargs)
401
401
  #### Landmark Visibility Map:
402
402
 
403
403
  ```python
404
- from voxcity.sim.view import get_landmark_visibility_map
404
+ from voxcity.simulator.view import get_landmark_visibility_map
405
405
 
406
406
  # Dictionary of parameters for landmark visibility analysis
407
407
  landmark_kwargs = {
@@ -425,7 +425,7 @@ landmark_vis_map = get_landmark_visibility_map(voxcity_grid, building_id_grid, b
425
425
  #### Network Analysis:
426
426
 
427
427
  ```python
428
- from voxcity.geo.network import get_network_values
428
+ from voxcity.geoprocessor.network import get_network_values
429
429
 
430
430
  network_kwargs = {
431
431
  "network_type": "walk", # Type of network to download from OSM (walk, drive, all, etc.)
@@ -8,7 +8,7 @@
8
8
 
9
9
  # VoxCity
10
10
 
11
- **VoxCity** is a Python package that provides a one-stop solution for grid-based 3D city model generation and urban simulation for cities worldwide. It integrates various geospatial datasets—such as building footprints, land cover, canopy height, and digital elevation models (DEMs)—to generate 2D and 3D representations of urban areas. It can export data in formats compatible with popular simulation tools like ENVI-MET, as well as visualization tools like MagicaVoxel, and supports simulations such as sky view index and green view index calculations.
11
+ **voxcity** is a Python package that provides a seamless solution for grid-based 3D city model generation and urban simulation for cities worldwide. VoxCity's generator module automatically downloads building heights, tree canopy heights, land cover, and terrain elevation within a specified target area, and voxelizes buildings, trees, land cover, and terrain to generate an integrated voxel city model. The simulator module enables users to conduct environmental simulations, including solar radiation and view index analyses. Users can export the generated models using several file formats compatible with external software, such as ENVI-met (INX), Blender, and Rhino (OBJ). Try it out using the [Google Colab Demo](https://colab.research.google.com/drive/1Lofd3RawKMr6QuUsamGaF48u2MN0hfrP?usp=sharing) or your local environment.
12
12
 
13
13
  <!-- <p align="center">
14
14
  <picture>
@@ -119,7 +119,7 @@ rectangle_vertices = [
119
119
  Use the GUI map interface to draw a rectangular domain of interest.
120
120
 
121
121
  ```python
122
- from voxcity.geo.draw import draw_rectangle_map_cityname
122
+ from voxcity.geoprocessor.draw import draw_rectangle_map_cityname
123
123
 
124
124
  cityname = "tokyo"
125
125
  m, rectangle_vertices = draw_rectangle_map_cityname(cityname, zoom=15)
@@ -130,7 +130,7 @@ m
130
130
  Choose the width and height in meters and select the center point on the map.
131
131
 
132
132
  ```python
133
- from voxcity.geo.draw import center_location_map_cityname
133
+ from voxcity.geoprocessor.draw import center_location_map_cityname
134
134
 
135
135
  width = 500
136
136
  height = 500
@@ -163,7 +163,7 @@ kwargs = {
163
163
  Generate voxel data grids and corresponding building geoJSON:
164
164
 
165
165
  ```python
166
- from voxcity import get_voxcity
166
+ from voxcity.generator import get_voxcity
167
167
 
168
168
  voxcity_grid, building_height_grid, building_min_height_grid, \
169
169
  building_id_grid, canopy_height_grid, land_cover_grid, dem_grid, \
@@ -184,7 +184,7 @@ building_gdf = get_voxcity(
184
184
  [ENVI-MET](https://www.envi-met.com/) is an advanced microclimate simulation software specialized in modeling urban environments. It simulates the interactions between buildings, vegetation, and various climate parameters like temperature, wind flow, humidity, and radiation. The software is used widely in urban planning, architecture, and environmental studies (Commercial, offers educational licenses).
185
185
 
186
186
  ```python
187
- from voxcity.file.envimet import export_inx, generate_edb_file
187
+ from voxcity.exporter.envimet import export_inx, generate_edb_file
188
188
 
189
189
  envimet_kwargs = {
190
190
  "output_directory": "output", # Directory where output files will be saved
@@ -210,7 +210,7 @@ generate_edb_file(**envimet_kwargs)
210
210
  #### OBJ Files:
211
211
 
212
212
  ```python
213
- from voxcity.file.obj import export_obj
213
+ from voxcity.exporter.obj import export_obj
214
214
 
215
215
  output_directory = "output" # Directory where output files will be saved
216
216
  output_file_name = "voxcity" # Base name for the output OBJ file
@@ -234,7 +234,7 @@ The generated OBJ files can be opened and rendered in the following 3D visualiza
234
234
  [MagicaVoxel](https://ephtracy.github.io/) is a lightweight and user-friendly voxel art editor. It allows users to create, edit, and render voxel-based 3D models with an intuitive interface, making it perfect for modifying and visualizing voxelized city models. The software is free and available for Windows and Mac.
235
235
 
236
236
  ```python
237
- from voxcity.file.magicavoxel import export_magicavoxel_vox
237
+ from voxcity.exporter.magicavoxel import export_magicavoxel_vox
238
238
 
239
239
  output_path = "output"
240
240
  base_filename = "voxcity"
@@ -252,7 +252,7 @@ export_magicavoxel_vox(voxcity_grid, output_path, base_filename=base_filename)
252
252
  #### Compute Solar Irradiance:
253
253
 
254
254
  ```python
255
- from voxcity.sim.solar import get_global_solar_irradiance_using_epw
255
+ from voxcity.simulator.solar import get_global_solar_irradiance_using_epw
256
256
 
257
257
  solar_kwargs = {
258
258
  "download_nearest_epw": True, # Whether to automatically download nearest EPW weather file based on location from Climate.OneBuilding.Org
@@ -308,7 +308,7 @@ cum_solar_grid = get_global_solar_irradiance_using_epw(
308
308
  #### Compute Green View Index (GVI) and Sky View Index (SVI):
309
309
 
310
310
  ```python
311
- from voxcity.sim.view import get_view_index
311
+ from voxcity.simulator.view import get_view_index
312
312
 
313
313
  view_kwargs = {
314
314
  "view_point_height": 1.5, # Height of observer viewpoint in meters
@@ -340,7 +340,7 @@ svi_grid = get_view_index(voxcity_grid, meshsize, mode='sky', **view_kwargs)
340
340
  #### Landmark Visibility Map:
341
341
 
342
342
  ```python
343
- from voxcity.sim.view import get_landmark_visibility_map
343
+ from voxcity.simulator.view import get_landmark_visibility_map
344
344
 
345
345
  # Dictionary of parameters for landmark visibility analysis
346
346
  landmark_kwargs = {
@@ -364,7 +364,7 @@ landmark_vis_map = get_landmark_visibility_map(voxcity_grid, building_id_grid, b
364
364
  #### Network Analysis:
365
365
 
366
366
  ```python
367
- from voxcity.geo.network import get_network_values
367
+ from voxcity.geoprocessor.network import get_network_values
368
368
 
369
369
  network_kwargs = {
370
370
  "network_type": "walk", # Type of network to download from OSM (walk, drive, all, etc.)
@@ -1,6 +1,6 @@
1
1
  [project]
2
2
  name = "voxcity"
3
- version = "0.3.27"
3
+ version = "0.4.01"
4
4
  requires-python = ">=3.10,<3.13"
5
5
  classifiers = [
6
6
  "Programming Language :: Python :: 3.10",
@@ -9,7 +9,7 @@ import numpy as np
9
9
  import os
10
10
  from numba import njit, prange
11
11
  import matplotlib.pyplot as plt
12
- from ..utils.visualization import get_default_voxel_color_map
12
+ from ..utils.visualization import get_voxel_color_map
13
13
 
14
14
  def convert_colormap_indices(original_map):
15
15
  """
@@ -209,7 +209,7 @@ def export_obj(array, output_dir, file_name, voxel_size, voxel_color_map=None):
209
209
  If None, uses default color map.
210
210
  """
211
211
  if voxel_color_map is None:
212
- voxel_color_map = get_default_voxel_color_map()
212
+ voxel_color_map = get_voxel_color_map()
213
213
 
214
214
  # Extract unique voxel values (excluding zero)
215
215
  unique_voxel_values = np.unique(array)
@@ -26,7 +26,8 @@ from ..geoprocessor.polygon import (
26
26
  filter_buildings,
27
27
  extract_building_heights_from_geotiff,
28
28
  extract_building_heights_from_gdf,
29
- complement_building_heights_from_gdf
29
+ complement_building_heights_from_gdf,
30
+ process_building_footprints_by_overlap
30
31
  )
31
32
  from ..utils.lc import (
32
33
  get_class_priority,
@@ -555,6 +556,9 @@ def create_building_height_grid_from_gdf_polygon(
555
556
  filtered_gdf = extract_building_heights_from_gdf(filtered_gdf, filtered_gdf_comp)
556
557
  elif geotiff_path_comp:
557
558
  filtered_gdf = extract_building_heights_from_geotiff(geotiff_path_comp, filtered_gdf)
559
+
560
+ # After filtering and complementing heights, process overlapping buildings
561
+ filtered_gdf = process_building_footprints_by_overlap(filtered_gdf, overlap_threshold=0.5)
558
562
 
559
563
  # --------------------------------------------------------------------------
560
564
  # 2) PREPARE BUILDING POLYGONS & SPATIAL INDEX
@@ -4,7 +4,7 @@ import matplotlib.colors as mcolors
4
4
  import matplotlib.cm as cm
5
5
  import matplotlib.pyplot as plt
6
6
 
7
- def create_voxel_mesh(voxel_array, class_id, meshsize=1.0):
7
+ def create_voxel_mesh(voxel_array, class_id, meshsize=1.0, building_id_grid=None):
8
8
  """
9
9
  Create a mesh from voxels preserving sharp edges, scaled by meshsize.
10
10
 
@@ -16,15 +16,21 @@ def create_voxel_mesh(voxel_array, class_id, meshsize=1.0):
16
16
  The ID of the class to extract.
17
17
  meshsize : float
18
18
  The real-world size of each voxel in meters, for x, y, and z.
19
+ building_id_grid : np.ndarray (2D), optional
20
+ 2D grid of building IDs, shape (X, Y). Used when class_id=-3 (buildings).
19
21
 
20
22
  Returns
21
23
  -------
22
24
  mesh : trimesh.Trimesh or None
23
25
  The resulting mesh for the given class_id (or None if no voxels).
26
+ If class_id=-3, mesh.metadata['building_id'] contains building IDs.
24
27
  """
25
28
  # Find voxels of the current class
26
29
  voxel_coords = np.argwhere(voxel_array == class_id)
27
30
 
31
+ if building_id_grid is not None:
32
+ building_id_grid_flipud = np.flipud(building_id_grid)
33
+
28
34
  if len(voxel_coords) == 0:
29
35
  return None
30
36
 
@@ -57,8 +63,14 @@ def create_voxel_mesh(voxel_array, class_id, meshsize=1.0):
57
63
  vertices = []
58
64
  faces = []
59
65
  face_normals_list = []
66
+ building_ids = [] # List to store building IDs for each face
60
67
 
61
68
  for x, y, z in voxel_coords:
69
+ # For buildings, get the building ID from the grid
70
+ building_id = None
71
+ if class_id == -3 and building_id_grid is not None:
72
+ building_id = building_id_grid_flipud[x, y]
73
+
62
74
  # Check each face of the current voxel
63
75
  adjacent_coords = [
64
76
  (x, y, z+1), # Front
@@ -95,6 +107,10 @@ def create_voxel_mesh(voxel_array, class_id, meshsize=1.0):
95
107
  ])
96
108
  # Add face normals for both triangles
97
109
  face_normals_list.extend([face_normals[face_idx], face_normals[face_idx]])
110
+
111
+ # Store building ID for both triangles if this is a building
112
+ if class_id == -3 and building_id_grid is not None:
113
+ building_ids.extend([building_id, building_id])
98
114
 
99
115
  if not vertices:
100
116
  return None
@@ -112,6 +128,10 @@ def create_voxel_mesh(voxel_array, class_id, meshsize=1.0):
112
128
 
113
129
  # Merge vertices that are at the same position
114
130
  mesh.merge_vertices()
131
+
132
+ # Add building IDs as metadata for buildings
133
+ if class_id == -3 and building_id_grid is not None and building_ids:
134
+ mesh.metadata = {'building_id': np.array(building_ids)}
115
135
 
116
136
  return mesh
117
137
 
@@ -19,6 +19,7 @@ from pyproj import Transformer, CRS
19
19
  import rasterio
20
20
  from rasterio.mask import mask
21
21
  import copy
22
+ from rtree import index
22
23
 
23
24
  from .utils import validate_polygon_coordinates
24
25
 
@@ -794,4 +795,97 @@ def get_buildings_in_drawn_polygon(building_gdf, drawn_polygon_vertices,
794
795
  else:
795
796
  raise ValueError("operation must be 'intersect' or 'within'")
796
797
 
797
- return included_building_ids
798
+ return included_building_ids
799
+
800
+ def process_building_footprints_by_overlap(filtered_gdf, overlap_threshold=0.5):
801
+ """
802
+ Process building footprints to merge overlapping buildings.
803
+
804
+ Args:
805
+ filtered_gdf (geopandas.GeoDataFrame): GeoDataFrame containing building footprints
806
+ overlap_threshold (float): Threshold for overlap ratio (0.0-1.0) to merge buildings
807
+
808
+ Returns:
809
+ geopandas.GeoDataFrame: Processed GeoDataFrame with updated IDs
810
+ """
811
+ # Make a copy to avoid modifying the original
812
+ gdf = filtered_gdf.copy()
813
+
814
+ # Ensure 'id' column exists
815
+ if 'id' not in gdf.columns:
816
+ gdf['id'] = gdf.index
817
+
818
+ # Calculate areas and sort by area (descending)
819
+ gdf['area'] = gdf.geometry.area
820
+ gdf = gdf.sort_values(by='area', ascending=False)
821
+ gdf = gdf.reset_index(drop=True)
822
+
823
+ # Create spatial index for efficient querying
824
+ spatial_idx = index.Index()
825
+ for i, geom in enumerate(gdf.geometry):
826
+ if geom.is_valid:
827
+ spatial_idx.insert(i, geom.bounds)
828
+ else:
829
+ # Fix invalid geometries
830
+ fixed_geom = geom.buffer(0)
831
+ if fixed_geom.is_valid:
832
+ spatial_idx.insert(i, fixed_geom.bounds)
833
+
834
+ # Track ID replacements to avoid repeated processing
835
+ id_mapping = {}
836
+
837
+ # Process each building (skip the largest one)
838
+ for i in range(1, len(gdf)):
839
+ current_poly = gdf.iloc[i].geometry
840
+ current_area = gdf.iloc[i].area
841
+ current_id = gdf.iloc[i]['id']
842
+
843
+ # Skip if already mapped
844
+ if current_id in id_mapping:
845
+ continue
846
+
847
+ # Ensure geometry is valid
848
+ if not current_poly.is_valid:
849
+ current_poly = current_poly.buffer(0)
850
+ if not current_poly.is_valid:
851
+ continue
852
+
853
+ # Find potential overlaps with larger polygons
854
+ potential_overlaps = [j for j in spatial_idx.intersection(current_poly.bounds) if j < i]
855
+
856
+ for j in potential_overlaps:
857
+ larger_poly = gdf.iloc[j].geometry
858
+ larger_id = gdf.iloc[j]['id']
859
+
860
+ # Skip if already processed
861
+ if larger_id in id_mapping:
862
+ larger_id = id_mapping[larger_id]
863
+
864
+ # Ensure geometry is valid
865
+ if not larger_poly.is_valid:
866
+ larger_poly = larger_poly.buffer(0)
867
+ if not larger_poly.is_valid:
868
+ continue
869
+
870
+ try:
871
+ # Calculate overlap
872
+ if current_poly.intersects(larger_poly):
873
+ overlap = current_poly.intersection(larger_poly)
874
+ overlap_ratio = overlap.area / current_area
875
+
876
+ # Replace ID if overlap exceeds threshold
877
+ if overlap_ratio > overlap_threshold:
878
+ id_mapping[current_id] = larger_id
879
+ gdf.at[i, 'id'] = larger_id
880
+ break # Stop at first significant overlap
881
+ except (GEOSException, ValueError) as e:
882
+ # Handle geometry errors gracefully
883
+ continue
884
+
885
+ # Propagate ID changes through the original DataFrame
886
+ for i, row in filtered_gdf.iterrows():
887
+ orig_id = row.get('id')
888
+ if orig_id in id_mapping:
889
+ filtered_gdf.at[i, 'id'] = id_mapping[orig_id]
890
+
891
+ return filtered_gdf