voxcity 0.3.13__py3-none-any.whl → 0.3.14__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/eubucco.py +20 -34
- voxcity/downloader/mbfp.py +4 -5
- voxcity/downloader/omt.py +11 -4
- voxcity/downloader/osm.py +31 -35
- voxcity/downloader/overture.py +7 -4
- voxcity/generator.py +31 -30
- voxcity/geoprocessor/draw.py +13 -21
- voxcity/geoprocessor/grid.py +197 -88
- voxcity/geoprocessor/polygon.py +145 -167
- voxcity/simulator/view.py +4 -5
- {voxcity-0.3.13.dist-info → voxcity-0.3.14.dist-info}/METADATA +1 -1
- {voxcity-0.3.13.dist-info → voxcity-0.3.14.dist-info}/RECORD +16 -16
- {voxcity-0.3.13.dist-info → voxcity-0.3.14.dist-info}/AUTHORS.rst +0 -0
- {voxcity-0.3.13.dist-info → voxcity-0.3.14.dist-info}/LICENSE +0 -0
- {voxcity-0.3.13.dist-info → voxcity-0.3.14.dist-info}/WHEEL +0 -0
- {voxcity-0.3.13.dist-info → voxcity-0.3.14.dist-info}/top_level.txt +0 -0
voxcity/downloader/eubucco.py
CHANGED
|
@@ -17,6 +17,7 @@ from shapely.ops import transform
|
|
|
17
17
|
from fiona.transform import transform_geom
|
|
18
18
|
import logging
|
|
19
19
|
import shapely
|
|
20
|
+
import geopandas as gpd
|
|
20
21
|
|
|
21
22
|
from ..geoprocessor.utils import get_country_name
|
|
22
23
|
|
|
@@ -246,7 +247,7 @@ def download_extract_open_gpkg_from_eubucco(url, output_dir):
|
|
|
246
247
|
logging.info(f"GeoPackage file found: {gpkg_file}")
|
|
247
248
|
return gpkg_file
|
|
248
249
|
|
|
249
|
-
def
|
|
250
|
+
def get_gdf_from_eubucco(rectangle_vertices, country_links, output_dir, file_name):
|
|
250
251
|
"""
|
|
251
252
|
Downloads, extracts, filters, and converts GeoPackage data to GeoJSON based on the rectangle vertices.
|
|
252
253
|
|
|
@@ -270,28 +271,22 @@ def save_geojson_from_eubucco(rectangle_vertices, country_links, output_dir, fil
|
|
|
270
271
|
# Download and extract GPKG file
|
|
271
272
|
gpkg_file = download_extract_open_gpkg_from_eubucco(url, output_dir)
|
|
272
273
|
|
|
273
|
-
#
|
|
274
|
-
|
|
275
|
-
|
|
276
|
-
|
|
277
|
-
|
|
278
|
-
|
|
279
|
-
|
|
280
|
-
|
|
281
|
-
|
|
282
|
-
|
|
283
|
-
|
|
284
|
-
|
|
285
|
-
|
|
286
|
-
|
|
287
|
-
|
|
288
|
-
|
|
289
|
-
output_geojson=file_path
|
|
290
|
-
)
|
|
291
|
-
|
|
292
|
-
logging.info(f"GeoJSON file has been created at: {file_path}")
|
|
293
|
-
|
|
294
|
-
def load_geojson_from_eubucco(rectangle_vertices, output_dir):
|
|
274
|
+
# Read GeoPackage file while preserving its CRS
|
|
275
|
+
gdf = gpd.read_file(gpkg_file)
|
|
276
|
+
|
|
277
|
+
# Only set CRS if not already set
|
|
278
|
+
if gdf.crs is None:
|
|
279
|
+
gdf.set_crs(epsg=4326, inplace=True)
|
|
280
|
+
# Transform to WGS84 if needed
|
|
281
|
+
elif gdf.crs != "EPSG:4326":
|
|
282
|
+
gdf = gdf.to_crs(epsg=4326)
|
|
283
|
+
|
|
284
|
+
# Replace id column with index numbers
|
|
285
|
+
gdf['id'] = gdf.index
|
|
286
|
+
|
|
287
|
+
return gdf
|
|
288
|
+
|
|
289
|
+
def load_gdf_from_eubucco(rectangle_vertices, output_dir):
|
|
295
290
|
"""
|
|
296
291
|
Downloads EUBUCCO data and loads it as GeoJSON.
|
|
297
292
|
|
|
@@ -307,15 +302,6 @@ def load_geojson_from_eubucco(rectangle_vertices, output_dir):
|
|
|
307
302
|
file_path = f"{output_dir}/{file_name}"
|
|
308
303
|
|
|
309
304
|
# Download and save GeoJSON
|
|
310
|
-
|
|
311
|
-
|
|
312
|
-
# Load and return GeoJSON features
|
|
313
|
-
with open(file_path, 'r') as f:
|
|
314
|
-
raw_data = json.load(f)
|
|
315
|
-
geojson_data = raw_data['features']
|
|
316
|
-
|
|
317
|
-
# Add id to each building's properties
|
|
318
|
-
for i, feature in enumerate(geojson_data):
|
|
319
|
-
feature['properties']['id'] = i + 1
|
|
305
|
+
gdf = get_gdf_from_eubucco(rectangle_vertices, country_links, output_dir, file_name)
|
|
320
306
|
|
|
321
|
-
return
|
|
307
|
+
return gdf
|
voxcity/downloader/mbfp.py
CHANGED
|
@@ -10,7 +10,7 @@ import pandas as pd
|
|
|
10
10
|
import os
|
|
11
11
|
from .utils import download_file
|
|
12
12
|
from ..geoprocessor.utils import tile_from_lat_lon, quadkey_to_tile
|
|
13
|
-
from ..geoprocessor.polygon import
|
|
13
|
+
from ..geoprocessor.polygon import load_gdf_from_multiple_gz, swap_coordinates
|
|
14
14
|
|
|
15
15
|
def get_geojson_links(output_dir):
|
|
16
16
|
"""Download and load the dataset links CSV file containing building footprint URLs.
|
|
@@ -68,7 +68,7 @@ def find_row_for_location(df, lon, lat):
|
|
|
68
68
|
print(f"Error processing row {index}: {e}")
|
|
69
69
|
return None
|
|
70
70
|
|
|
71
|
-
def
|
|
71
|
+
def get_mbfp_gdf(output_dir, rectangle_vertices):
|
|
72
72
|
"""Download and process building footprint data for a rectangular region.
|
|
73
73
|
|
|
74
74
|
Args:
|
|
@@ -98,7 +98,6 @@ def get_mbfp_geojson(output_dir, rectangle_vertices):
|
|
|
98
98
|
print("No matching row found.")
|
|
99
99
|
|
|
100
100
|
# Load GeoJSON data from downloaded files and fix coordinate ordering
|
|
101
|
-
|
|
102
|
-
swap_coordinates(geojson_data)
|
|
101
|
+
gdf = load_gdf_from_multiple_gz(filenames)
|
|
103
102
|
|
|
104
|
-
return
|
|
103
|
+
return gdf
|
voxcity/downloader/omt.py
CHANGED
|
@@ -15,8 +15,8 @@ import shapely.ops
|
|
|
15
15
|
import json
|
|
16
16
|
from pyproj import Transformer
|
|
17
17
|
import json
|
|
18
|
-
|
|
19
|
-
def
|
|
18
|
+
import geopandas as gpd
|
|
19
|
+
def load_gdf_from_openmaptiles(rectangle_vertices, API_KEY):
|
|
20
20
|
"""Download and process building footprint data from OpenMapTiles vector tiles.
|
|
21
21
|
|
|
22
22
|
Args:
|
|
@@ -24,7 +24,7 @@ def load_geojsons_from_openmaptiles(rectangle_vertices, API_KEY):
|
|
|
24
24
|
API_KEY: OpenMapTiles API key for authentication
|
|
25
25
|
|
|
26
26
|
Returns:
|
|
27
|
-
|
|
27
|
+
geopandas.GeoDataFrame: GeoDataFrame containing building footprints with standardized properties
|
|
28
28
|
"""
|
|
29
29
|
# Extract longitudes and latitudes from vertices to find bounding box
|
|
30
30
|
lons = [coord[0] for coord in rectangle_vertices]
|
|
@@ -112,7 +112,14 @@ def load_geojsons_from_openmaptiles(rectangle_vertices, API_KEY):
|
|
|
112
112
|
|
|
113
113
|
# Convert features to standardized format with height information
|
|
114
114
|
converted_geojson_data = convert_geojson_format(building_features)
|
|
115
|
-
|
|
115
|
+
|
|
116
|
+
gdf = gpd.GeoDataFrame.from_features(converted_geojson_data)
|
|
117
|
+
gdf.set_crs(epsg=4326, inplace=True)
|
|
118
|
+
|
|
119
|
+
# Replace id column with index numbers
|
|
120
|
+
gdf['id'] = gdf.index
|
|
121
|
+
|
|
122
|
+
return gdf
|
|
116
123
|
|
|
117
124
|
def get_height_from_properties(properties):
|
|
118
125
|
"""Extract building height from properties, using levels if height is not available.
|
voxcity/downloader/osm.py
CHANGED
|
@@ -6,9 +6,6 @@ and other geographic features from OpenStreetMap. It handles downloading data vi
|
|
|
6
6
|
processing the responses, and converting them to standardized GeoJSON format with proper properties.
|
|
7
7
|
"""
|
|
8
8
|
|
|
9
|
-
import requests
|
|
10
|
-
from shapely.geometry import Polygon
|
|
11
|
-
# Import libraries
|
|
12
9
|
import requests
|
|
13
10
|
from osm2geojson import json2geojson
|
|
14
11
|
from shapely.geometry import Polygon, shape, mapping
|
|
@@ -21,15 +18,17 @@ from shapely.geometry import shape, mapping, Polygon
|
|
|
21
18
|
from shapely.ops import transform
|
|
22
19
|
import pyproj
|
|
23
20
|
from osm2geojson import json2geojson
|
|
21
|
+
import pandas as pd
|
|
22
|
+
import geopandas as gpd
|
|
24
23
|
|
|
25
|
-
def
|
|
24
|
+
def load_gdf_from_openstreetmap(rectangle_vertices):
|
|
26
25
|
"""Download and process building footprint data from OpenStreetMap.
|
|
27
26
|
|
|
28
27
|
Args:
|
|
29
28
|
rectangle_vertices: List of (lon, lat) coordinates defining the bounding box
|
|
30
29
|
|
|
31
30
|
Returns:
|
|
32
|
-
|
|
31
|
+
geopandas.GeoDataFrame: GeoDataFrame containing building footprints with standardized properties
|
|
33
32
|
"""
|
|
34
33
|
# Create a bounding box from the rectangle vertices
|
|
35
34
|
min_lon = min(v[0] for v in rectangle_vertices)
|
|
@@ -61,7 +60,7 @@ def load_geojsons_from_openstreetmap(rectangle_vertices):
|
|
|
61
60
|
for element in data['elements']:
|
|
62
61
|
id_map[(element['type'], element['id'])] = element
|
|
63
62
|
|
|
64
|
-
# Process the response and create
|
|
63
|
+
# Process the response and create features list
|
|
65
64
|
features = []
|
|
66
65
|
|
|
67
66
|
def process_coordinates(geometry):
|
|
@@ -210,8 +209,20 @@ def load_geojsons_from_openstreetmap(rectangle_vertices):
|
|
|
210
209
|
if feature:
|
|
211
210
|
feature['properties']['role'] = member['role']
|
|
212
211
|
features.append(feature)
|
|
212
|
+
|
|
213
|
+
# Convert features list to GeoDataFrame
|
|
214
|
+
if not features:
|
|
215
|
+
return gpd.GeoDataFrame()
|
|
216
|
+
|
|
217
|
+
geometries = []
|
|
218
|
+
properties_list = []
|
|
219
|
+
|
|
220
|
+
for feature in features:
|
|
221
|
+
geometries.append(shape(feature['geometry']))
|
|
222
|
+
properties_list.append(feature['properties'])
|
|
213
223
|
|
|
214
|
-
|
|
224
|
+
gdf = gpd.GeoDataFrame(properties_list, geometry=geometries, crs="EPSG:4326")
|
|
225
|
+
return gdf
|
|
215
226
|
|
|
216
227
|
def convert_feature(feature):
|
|
217
228
|
"""Convert a GeoJSON feature to the desired format with height information.
|
|
@@ -466,14 +477,14 @@ def swap_coordinates(geom_mapping):
|
|
|
466
477
|
geom_mapping['coordinates'] = swap_coords(coords)
|
|
467
478
|
return geom_mapping
|
|
468
479
|
|
|
469
|
-
def
|
|
480
|
+
def load_land_cover_gdf_from_osm(rectangle_vertices_ori):
|
|
470
481
|
"""Load land cover data from OpenStreetMap within a given rectangular area.
|
|
471
482
|
|
|
472
483
|
Args:
|
|
473
484
|
rectangle_vertices_ori (list): List of (lon, lat) coordinates defining the rectangle
|
|
474
485
|
|
|
475
486
|
Returns:
|
|
476
|
-
|
|
487
|
+
GeoDataFrame: GeoDataFrame containing land cover classifications
|
|
477
488
|
"""
|
|
478
489
|
# Close the rectangle polygon by adding first vertex at the end
|
|
479
490
|
rectangle_vertices = rectangle_vertices_ori.copy()
|
|
@@ -558,8 +569,9 @@ def load_land_cover_geojson_from_osm(rectangle_vertices_ori):
|
|
|
558
569
|
project = pyproj.Transformer.from_crs(wgs84, aea, always_xy=True).transform
|
|
559
570
|
project_back = pyproj.Transformer.from_crs(aea, wgs84, always_xy=True).transform
|
|
560
571
|
|
|
561
|
-
#
|
|
562
|
-
|
|
572
|
+
# Lists to store geometries and properties for GeoDataFrame
|
|
573
|
+
geometries = []
|
|
574
|
+
properties = []
|
|
563
575
|
|
|
564
576
|
for feature in geojson_data['features']:
|
|
565
577
|
# Convert feature geometry to shapely object
|
|
@@ -621,31 +633,15 @@ def load_land_cover_geojson_from_osm(rectangle_vertices_ori):
|
|
|
621
633
|
if geom.is_empty:
|
|
622
634
|
continue
|
|
623
635
|
|
|
624
|
-
#
|
|
636
|
+
# Add geometries and properties
|
|
625
637
|
if geom.geom_type == 'Polygon':
|
|
626
|
-
|
|
627
|
-
|
|
628
|
-
geom_mapping = swap_coordinates(geom_mapping)
|
|
629
|
-
new_feature = {
|
|
630
|
-
'type': 'Feature',
|
|
631
|
-
'properties': {
|
|
632
|
-
'class': classification_name
|
|
633
|
-
},
|
|
634
|
-
'geometry': geom_mapping
|
|
635
|
-
}
|
|
636
|
-
filtered_features.append(new_feature)
|
|
638
|
+
geometries.append(geom)
|
|
639
|
+
properties.append({'class': classification_name})
|
|
637
640
|
elif geom.geom_type == 'MultiPolygon':
|
|
638
|
-
# Split into separate polygon features
|
|
639
641
|
for poly in geom.geoms:
|
|
640
|
-
|
|
641
|
-
|
|
642
|
-
new_feature = {
|
|
643
|
-
'type': 'Feature',
|
|
644
|
-
'properties': {
|
|
645
|
-
'class': classification_name
|
|
646
|
-
},
|
|
647
|
-
'geometry': geom_mapping
|
|
648
|
-
}
|
|
649
|
-
filtered_features.append(new_feature)
|
|
642
|
+
geometries.append(poly)
|
|
643
|
+
properties.append({'class': classification_name})
|
|
650
644
|
|
|
651
|
-
|
|
645
|
+
# Create GeoDataFrame
|
|
646
|
+
gdf = gpd.GeoDataFrame(properties, geometry=geometries, crs="EPSG:4326")
|
|
647
|
+
return gdf
|
voxcity/downloader/overture.py
CHANGED
|
@@ -179,7 +179,7 @@ def join_gdfs_vertically(gdf1, gdf2):
|
|
|
179
179
|
|
|
180
180
|
return combined_gdf
|
|
181
181
|
|
|
182
|
-
def
|
|
182
|
+
def load_gdf_from_overture(rectangle_vertices):
|
|
183
183
|
"""
|
|
184
184
|
Download and process building footprint data from Overture Maps.
|
|
185
185
|
|
|
@@ -199,7 +199,10 @@ def load_geojsons_from_overture(rectangle_vertices):
|
|
|
199
199
|
# Combine building and building part data into single dataset
|
|
200
200
|
joined_building_gdf = join_gdfs_vertically(building_gdf, building_part_gdf)
|
|
201
201
|
|
|
202
|
-
# Convert combined dataset to GeoJSON format
|
|
203
|
-
geojson_features = convert_gdf_to_geojson(joined_building_gdf)
|
|
202
|
+
# # Convert combined dataset to GeoJSON format
|
|
203
|
+
# geojson_features = convert_gdf_to_geojson(joined_building_gdf)
|
|
204
204
|
|
|
205
|
-
|
|
205
|
+
# Replace id column with index numbers
|
|
206
|
+
joined_building_gdf['id'] = joined_building_gdf.index
|
|
207
|
+
|
|
208
|
+
return joined_building_gdf
|
voxcity/generator.py
CHANGED
|
@@ -18,12 +18,12 @@ import numpy as np
|
|
|
18
18
|
import os
|
|
19
19
|
|
|
20
20
|
# Local application/library specific imports
|
|
21
|
-
from .downloader.mbfp import
|
|
22
|
-
from .downloader.osm import
|
|
21
|
+
from .downloader.mbfp import get_mbfp_gdf
|
|
22
|
+
from .downloader.osm import load_gdf_from_openstreetmap, load_land_cover_gdf_from_osm
|
|
23
23
|
from .downloader.oemj import save_oemj_as_geotiff
|
|
24
|
-
from .downloader.omt import
|
|
25
|
-
from .downloader.eubucco import
|
|
26
|
-
from .downloader.overture import
|
|
24
|
+
from .downloader.omt import load_gdf_from_openmaptiles
|
|
25
|
+
from .downloader.eubucco import load_gdf_from_eubucco
|
|
26
|
+
from .downloader.overture import load_gdf_from_overture
|
|
27
27
|
from .downloader.gee import (
|
|
28
28
|
initialize_earth_engine,
|
|
29
29
|
get_roi,
|
|
@@ -41,13 +41,13 @@ from .geoprocessor.grid import (
|
|
|
41
41
|
process_grid,
|
|
42
42
|
create_land_cover_grid_from_geotiff_polygon,
|
|
43
43
|
create_height_grid_from_geotiff_polygon,
|
|
44
|
-
|
|
44
|
+
create_building_height_grid_from_gdf_polygon,
|
|
45
45
|
create_dem_grid_from_geotiff_polygon,
|
|
46
|
-
|
|
46
|
+
create_land_cover_grid_from_gdf_polygon,
|
|
47
47
|
create_building_height_grid_from_open_building_temporal_polygon
|
|
48
48
|
)
|
|
49
49
|
from .utils.lc import convert_land_cover, convert_land_cover_array
|
|
50
|
-
from .geoprocessor.polygon import
|
|
50
|
+
from .geoprocessor.polygon import get_gdf_from_gpkg, save_geojson
|
|
51
51
|
from .utils.visualization import (
|
|
52
52
|
get_land_cover_classes,
|
|
53
53
|
visualize_land_cover_grid,
|
|
@@ -108,14 +108,14 @@ def get_land_cover_grid(rectangle_vertices, meshsize, source, output_dir, **kwar
|
|
|
108
108
|
save_oemj_as_geotiff(rectangle_vertices, geotiff_path)
|
|
109
109
|
elif source == 'OpenStreetMap':
|
|
110
110
|
# For OSM, we get data directly as GeoJSON instead of GeoTIFF
|
|
111
|
-
|
|
111
|
+
land_cover_gdf = load_land_cover_gdf_from_osm(rectangle_vertices)
|
|
112
112
|
|
|
113
113
|
# Get mapping of land cover classes for the selected source
|
|
114
114
|
land_cover_classes = get_land_cover_classes(source)
|
|
115
115
|
|
|
116
116
|
# Create grid from either GeoJSON (OSM) or GeoTIFF (other sources)
|
|
117
117
|
if source == 'OpenStreetMap':
|
|
118
|
-
land_cover_grid_str =
|
|
118
|
+
land_cover_grid_str = create_land_cover_grid_from_gdf_polygon(land_cover_gdf, meshsize, source, rectangle_vertices)
|
|
119
119
|
else:
|
|
120
120
|
land_cover_grid_str = create_land_cover_grid_from_geotiff_polygon(geotiff_path, meshsize, land_cover_classes, rectangle_vertices)
|
|
121
121
|
|
|
@@ -165,23 +165,23 @@ def get_building_height_grid(rectangle_vertices, meshsize, source, output_dir, *
|
|
|
165
165
|
|
|
166
166
|
# Get building data from primary source
|
|
167
167
|
if source == 'Microsoft Building Footprints':
|
|
168
|
-
|
|
168
|
+
gdf = get_mbfp_gdf(output_dir, rectangle_vertices)
|
|
169
169
|
elif source == 'OpenStreetMap':
|
|
170
|
-
|
|
170
|
+
gdf = load_gdf_from_openstreetmap(rectangle_vertices)
|
|
171
171
|
elif source == "Open Building 2.5D Temporal":
|
|
172
172
|
# Special case: directly creates grids without intermediate GeoJSON
|
|
173
173
|
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)
|
|
174
174
|
elif source == 'EUBUCCO v0.1':
|
|
175
|
-
|
|
175
|
+
gdf = load_gdf_from_eubucco(rectangle_vertices, output_dir)
|
|
176
176
|
elif source == "OpenMapTiles":
|
|
177
|
-
|
|
177
|
+
gdf = load_gdf_from_openmaptiles(rectangle_vertices, kwargs["maptiler_API_key"])
|
|
178
178
|
elif source == "Overture":
|
|
179
|
-
|
|
179
|
+
gdf = load_gdf_from_overture(rectangle_vertices)
|
|
180
180
|
elif source == "Local file":
|
|
181
181
|
# Handle local GPKG files
|
|
182
182
|
_, extension = os.path.splitext(kwargs["building_path"])
|
|
183
183
|
if extension == ".gpkg":
|
|
184
|
-
|
|
184
|
+
gdf = get_gdf_from_gpkg(kwargs["building_path"], rectangle_vertices)
|
|
185
185
|
|
|
186
186
|
# Check for complementary building data source
|
|
187
187
|
building_complementary_source = kwargs.get("building_complementary_source")
|
|
@@ -189,7 +189,7 @@ def get_building_height_grid(rectangle_vertices, meshsize, source, output_dir, *
|
|
|
189
189
|
if (building_complementary_source is None) or (building_complementary_source=='None'):
|
|
190
190
|
# Use only primary source
|
|
191
191
|
if source != "Open Building 2.5D Temporal":
|
|
192
|
-
building_height_grid, building_min_height_grid, building_id_grid, filtered_buildings =
|
|
192
|
+
building_height_grid, building_min_height_grid, building_id_grid, filtered_buildings = create_building_height_grid_from_gdf_polygon(gdf, meshsize, rectangle_vertices)
|
|
193
193
|
else:
|
|
194
194
|
# Handle complementary source
|
|
195
195
|
if building_complementary_source == "Open Building 2.5D Temporal":
|
|
@@ -198,29 +198,29 @@ def get_building_height_grid(rectangle_vertices, meshsize, source, output_dir, *
|
|
|
198
198
|
os.makedirs(output_dir, exist_ok=True)
|
|
199
199
|
geotiff_path_comp = os.path.join(output_dir, "building_height.tif")
|
|
200
200
|
save_geotiff_open_buildings_temporal(roi, geotiff_path_comp)
|
|
201
|
-
building_height_grid, building_min_height_grid, building_id_grid, filtered_buildings =
|
|
201
|
+
building_height_grid, building_min_height_grid, building_id_grid, filtered_buildings = create_building_height_grid_from_gdf_polygon(gdf, meshsize, rectangle_vertices, geotiff_path_comp=geotiff_path_comp)
|
|
202
202
|
else:
|
|
203
203
|
# Get complementary data from other sources
|
|
204
204
|
if building_complementary_source == 'Microsoft Building Footprints':
|
|
205
|
-
|
|
205
|
+
gdf_comp = get_mbfp_gdf(output_dir, rectangle_vertices)
|
|
206
206
|
elif building_complementary_source == 'OpenStreetMap':
|
|
207
|
-
|
|
207
|
+
gdf_comp = load_gdf_from_openstreetmap(rectangle_vertices)
|
|
208
208
|
elif building_complementary_source == 'OSM Buildings':
|
|
209
|
-
|
|
209
|
+
gdf_comp = load_gdf_from_osmbuildings(rectangle_vertices)
|
|
210
210
|
elif building_complementary_source == 'EUBUCCO v0.1':
|
|
211
|
-
|
|
211
|
+
gdf_comp = load_gdf_from_eubucco(rectangle_vertices, output_dir)
|
|
212
212
|
elif building_complementary_source == "OpenMapTiles":
|
|
213
|
-
|
|
213
|
+
gdf_comp = load_gdf_from_openmaptiles(rectangle_vertices, kwargs["maptiler_API_key"])
|
|
214
214
|
elif building_complementary_source == "Overture":
|
|
215
|
-
|
|
215
|
+
gdf_comp = load_gdf_from_overture(rectangle_vertices)
|
|
216
216
|
elif building_complementary_source == "Local file":
|
|
217
217
|
_, extension = os.path.splitext(kwargs["building_complementary_path"])
|
|
218
218
|
if extension == ".gpkg":
|
|
219
|
-
|
|
219
|
+
gdf_comp = get_gdf_from_gpkg(kwargs["building_complementary_path"], rectangle_vertices)
|
|
220
220
|
|
|
221
221
|
# Option to complement footprints only or both footprints and heights
|
|
222
222
|
complement_building_footprints = kwargs.get("complement_building_footprints")
|
|
223
|
-
building_height_grid, building_min_height_grid, building_id_grid, filtered_buildings =
|
|
223
|
+
building_height_grid, building_min_height_grid, building_id_grid, filtered_buildings = create_building_height_grid_from_gdf_polygon(gdf, meshsize, rectangle_vertices, gdf_comp=gdf_comp, complement_building_footprints=complement_building_footprints)
|
|
224
224
|
|
|
225
225
|
# Visualize grid if requested
|
|
226
226
|
grid_vis = kwargs.get("gridvis", True)
|
|
@@ -552,11 +552,12 @@ def get_voxcity(rectangle_vertices, building_source, land_cover_source, canopy_h
|
|
|
552
552
|
|
|
553
553
|
# Generate all required 2D grids
|
|
554
554
|
land_cover_grid = get_land_cover_grid(rectangle_vertices, meshsize, land_cover_source, output_dir, **kwargs)
|
|
555
|
-
building_height_grid, building_min_height_grid, building_id_grid,
|
|
555
|
+
building_height_grid, building_min_height_grid, building_id_grid, building_gdf = get_building_height_grid(rectangle_vertices, meshsize, building_source, output_dir, **kwargs)
|
|
556
556
|
|
|
557
557
|
# Save building data to GeoJSON
|
|
558
|
-
|
|
559
|
-
|
|
558
|
+
if not building_gdf.empty:
|
|
559
|
+
save_path = f"{output_dir}/building.gpkg"
|
|
560
|
+
building_gdf.to_file(save_path, driver='GPKG')
|
|
560
561
|
|
|
561
562
|
# Get canopy height data
|
|
562
563
|
canopy_height_grid = get_canopy_height_grid(rectangle_vertices, meshsize, canopy_height_source, output_dir, **kwargs)
|
|
@@ -662,7 +663,7 @@ def get_voxcity(rectangle_vertices, building_source, land_cover_source, canopy_h
|
|
|
662
663
|
voxcity_grid_vis[-1, -1, -1] = -99 # Add marker to fix camera location and angle of view
|
|
663
664
|
visualize_3d_voxel(voxcity_grid_vis, voxel_size=meshsize, save_path=kwargs["voxelvis_img_save_path"])
|
|
664
665
|
|
|
665
|
-
return voxcity_grid, building_height_grid, building_min_height_grid, building_id_grid, canopy_height_grid, land_cover_grid, dem_grid,
|
|
666
|
+
return voxcity_grid, building_height_grid, building_min_height_grid, building_id_grid, canopy_height_grid, land_cover_grid, dem_grid, building_gdf
|
|
666
667
|
|
|
667
668
|
def replace_nan_in_nested(arr, replace_value=10.0):
|
|
668
669
|
"""Replace NaN values in a nested array structure with a specified value.
|
voxcity/geoprocessor/draw.py
CHANGED
|
@@ -223,15 +223,15 @@ def center_location_map_cityname(cityname, east_west_length, north_south_length,
|
|
|
223
223
|
|
|
224
224
|
return m, rectangle_vertices
|
|
225
225
|
|
|
226
|
-
def display_buildings_and_draw_polygon(
|
|
226
|
+
def display_buildings_and_draw_polygon(building_gdf, zoom=17):
|
|
227
227
|
"""
|
|
228
228
|
Displays building footprints (in Lon-Lat order) on an ipyleaflet map,
|
|
229
229
|
and allows the user to draw a polygon whose vertices are returned
|
|
230
230
|
in a Python list (also in Lon-Lat).
|
|
231
231
|
|
|
232
232
|
Args:
|
|
233
|
-
|
|
234
|
-
|
|
233
|
+
building_gdf (GeoDataFrame): A GeoDataFrame containing building footprints,
|
|
234
|
+
with geometry in [lon, lat] order.
|
|
235
235
|
zoom (int): Initial zoom level for the map. Default=17.
|
|
236
236
|
|
|
237
237
|
Returns:
|
|
@@ -243,22 +243,13 @@ def display_buildings_and_draw_polygon(building_geojson, zoom=17):
|
|
|
243
243
|
# ---------------------------------------------------------
|
|
244
244
|
# 1. Determine a suitable map center via bounding box logic
|
|
245
245
|
# ---------------------------------------------------------
|
|
246
|
-
|
|
247
|
-
all_lats = []
|
|
248
|
-
for feature in building_geojson:
|
|
249
|
-
# Handle only Polygons here; skip MultiPolygon if present
|
|
250
|
-
if feature['geometry']['type'] == 'Polygon':
|
|
251
|
-
# Coordinates in this data are [ [lon, lat], [lon, lat], ... ]
|
|
252
|
-
coords = feature['geometry']['coordinates'][0] # outer ring
|
|
253
|
-
all_lons.extend(pt[0] for pt in coords)
|
|
254
|
-
all_lats.extend(pt[1] for pt in coords)
|
|
255
|
-
|
|
256
|
-
if not all_lats or not all_lons:
|
|
246
|
+
if len(building_gdf) == 0:
|
|
257
247
|
# Fallback: If no footprints or invalid data, pick a default
|
|
258
248
|
center_lon, center_lat = -100.0, 40.0
|
|
259
249
|
else:
|
|
260
|
-
|
|
261
|
-
|
|
250
|
+
# Get bounds from GeoDataFrame
|
|
251
|
+
bounds = building_gdf.total_bounds # Returns [minx, miny, maxx, maxy]
|
|
252
|
+
min_lon, min_lat, max_lon, max_lat = bounds
|
|
262
253
|
center_lon = (min_lon + max_lon) / 2
|
|
263
254
|
center_lat = (min_lat + max_lat) / 2
|
|
264
255
|
|
|
@@ -268,12 +259,13 @@ def display_buildings_and_draw_polygon(building_geojson, zoom=17):
|
|
|
268
259
|
# -----------------------------------------
|
|
269
260
|
# 2. Add each building footprint to the map
|
|
270
261
|
# -----------------------------------------
|
|
271
|
-
for
|
|
262
|
+
for idx, row in building_gdf.iterrows():
|
|
272
263
|
# Only handle simple Polygons
|
|
273
|
-
if
|
|
274
|
-
|
|
275
|
-
|
|
276
|
-
|
|
264
|
+
if isinstance(row.geometry, geom.Polygon):
|
|
265
|
+
# Get coordinates from geometry
|
|
266
|
+
coords = list(row.geometry.exterior.coords)
|
|
267
|
+
# Convert to (lat,lon) for ipyleaflet, skip last repeated coordinate
|
|
268
|
+
lat_lon_coords = [(c[1], c[0]) for c in coords[:-1]]
|
|
277
269
|
|
|
278
270
|
# Create the polygon layer
|
|
279
271
|
bldg_layer = LeafletPolygon(
|