voxcity 0.3.27__tar.gz → 0.4.2__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.
- {voxcity-0.3.27 → voxcity-0.4.2}/PKG-INFO +12 -12
- {voxcity-0.3.27 → voxcity-0.4.2}/README.md +11 -11
- {voxcity-0.3.27 → voxcity-0.4.2}/pyproject.toml +1 -1
- {voxcity-0.3.27 → voxcity-0.4.2}/src/voxcity/exporter/obj.py +2 -2
- {voxcity-0.3.27 → voxcity-0.4.2}/src/voxcity/geoprocessor/grid.py +5 -1
- {voxcity-0.3.27 → voxcity-0.4.2}/src/voxcity/geoprocessor/mesh.py +21 -1
- {voxcity-0.3.27 → voxcity-0.4.2}/src/voxcity/geoprocessor/polygon.py +95 -1
- {voxcity-0.3.27 → voxcity-0.4.2}/src/voxcity/simulator/solar.py +656 -7
- {voxcity-0.3.27 → voxcity-0.4.2}/src/voxcity/simulator/view.py +635 -2
- {voxcity-0.3.27 → voxcity-0.4.2}/src/voxcity/utils/visualization.py +780 -168
- {voxcity-0.3.27 → voxcity-0.4.2}/src/voxcity/utils/weather.py +98 -5
- {voxcity-0.3.27 → voxcity-0.4.2}/src/voxcity.egg-info/PKG-INFO +12 -12
- {voxcity-0.3.27 → voxcity-0.4.2}/AUTHORS.rst +0 -0
- {voxcity-0.3.27 → voxcity-0.4.2}/CONTRIBUTING.rst +0 -0
- {voxcity-0.3.27 → voxcity-0.4.2}/HISTORY.rst +0 -0
- {voxcity-0.3.27 → voxcity-0.4.2}/LICENSE +0 -0
- {voxcity-0.3.27 → voxcity-0.4.2}/MANIFEST.in +0 -0
- {voxcity-0.3.27 → voxcity-0.4.2}/docs/Makefile +0 -0
- {voxcity-0.3.27 → voxcity-0.4.2}/docs/archive/README.rst +0 -0
- {voxcity-0.3.27 → voxcity-0.4.2}/docs/authors.rst +0 -0
- {voxcity-0.3.27 → voxcity-0.4.2}/docs/conf.py +0 -0
- {voxcity-0.3.27 → voxcity-0.4.2}/docs/index.rst +0 -0
- {voxcity-0.3.27 → voxcity-0.4.2}/docs/make.bat +0 -0
- {voxcity-0.3.27 → voxcity-0.4.2}/setup.cfg +0 -0
- {voxcity-0.3.27 → voxcity-0.4.2}/src/voxcity/__init__.py +0 -0
- {voxcity-0.3.27 → voxcity-0.4.2}/src/voxcity/downloader/__init__.py +0 -0
- {voxcity-0.3.27 → voxcity-0.4.2}/src/voxcity/downloader/eubucco.py +0 -0
- {voxcity-0.3.27 → voxcity-0.4.2}/src/voxcity/downloader/gee.py +0 -0
- {voxcity-0.3.27 → voxcity-0.4.2}/src/voxcity/downloader/mbfp.py +0 -0
- {voxcity-0.3.27 → voxcity-0.4.2}/src/voxcity/downloader/oemj.py +0 -0
- {voxcity-0.3.27 → voxcity-0.4.2}/src/voxcity/downloader/omt.py +0 -0
- {voxcity-0.3.27 → voxcity-0.4.2}/src/voxcity/downloader/osm.py +0 -0
- {voxcity-0.3.27 → voxcity-0.4.2}/src/voxcity/downloader/overture.py +0 -0
- {voxcity-0.3.27 → voxcity-0.4.2}/src/voxcity/downloader/utils.py +0 -0
- {voxcity-0.3.27 → voxcity-0.4.2}/src/voxcity/exporter/__init_.py +0 -0
- {voxcity-0.3.27 → voxcity-0.4.2}/src/voxcity/exporter/envimet.py +0 -0
- {voxcity-0.3.27 → voxcity-0.4.2}/src/voxcity/exporter/magicavoxel.py +0 -0
- {voxcity-0.3.27 → voxcity-0.4.2}/src/voxcity/generator.py +0 -0
- {voxcity-0.3.27 → voxcity-0.4.2}/src/voxcity/geoprocessor/__init_.py +0 -0
- {voxcity-0.3.27 → voxcity-0.4.2}/src/voxcity/geoprocessor/draw.py +0 -0
- {voxcity-0.3.27 → voxcity-0.4.2}/src/voxcity/geoprocessor/network.py +0 -0
- {voxcity-0.3.27 → voxcity-0.4.2}/src/voxcity/geoprocessor/utils.py +0 -0
- {voxcity-0.3.27 → voxcity-0.4.2}/src/voxcity/simulator/__init_.py +0 -0
- {voxcity-0.3.27 → voxcity-0.4.2}/src/voxcity/simulator/utils.py +0 -0
- {voxcity-0.3.27 → voxcity-0.4.2}/src/voxcity/utils/__init_.py +0 -0
- {voxcity-0.3.27 → voxcity-0.4.2}/src/voxcity/utils/lc.py +0 -0
- {voxcity-0.3.27 → voxcity-0.4.2}/src/voxcity/utils/material.py +0 -0
- {voxcity-0.3.27 → voxcity-0.4.2}/src/voxcity.egg-info/SOURCES.txt +0 -0
- {voxcity-0.3.27 → voxcity-0.4.2}/src/voxcity.egg-info/dependency_links.txt +0 -0
- {voxcity-0.3.27 → voxcity-0.4.2}/src/voxcity.egg-info/requires.txt +0 -0
- {voxcity-0.3.27 → voxcity-0.4.2}/src/voxcity.egg-info/top_level.txt +0 -0
- {voxcity-0.3.27 → voxcity-0.4.2}/tests/__init__.py +0 -0
- {voxcity-0.3.27 → voxcity-0.4.2}/tests/voxelcity.py +0 -0
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.2
|
|
2
2
|
Name: voxcity
|
|
3
|
-
Version: 0.
|
|
3
|
+
Version: 0.4.2
|
|
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
|
-
**
|
|
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.
|
|
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.
|
|
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.
|
|
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.
|
|
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.
|
|
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.
|
|
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.
|
|
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.
|
|
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.
|
|
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
|
-
**
|
|
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.
|
|
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.
|
|
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.
|
|
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.
|
|
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.
|
|
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.
|
|
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.
|
|
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.
|
|
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.
|
|
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.)
|
|
@@ -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
|
|
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 =
|
|
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
|