voxcity 0.5.14__py3-none-any.whl → 0.5.15__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/citygml.py +202 -28
- voxcity/downloader/eubucco.py +91 -14
- voxcity/downloader/gee.py +164 -22
- voxcity/downloader/mbfp.py +55 -9
- voxcity/downloader/oemj.py +110 -24
- voxcity/downloader/omt.py +74 -7
- voxcity/downloader/osm.py +109 -23
- voxcity/downloader/overture.py +108 -23
- voxcity/downloader/utils.py +37 -7
- voxcity/exporter/envimet.py +180 -61
- voxcity/exporter/magicavoxel.py +138 -28
- voxcity/exporter/obj.py +159 -36
- voxcity/generator.py +159 -76
- voxcity/geoprocessor/draw.py +180 -27
- voxcity/geoprocessor/grid.py +178 -38
- voxcity/geoprocessor/mesh.py +347 -43
- voxcity/geoprocessor/network.py +196 -63
- voxcity/geoprocessor/polygon.py +365 -88
- voxcity/geoprocessor/utils.py +283 -72
- voxcity/simulator/solar.py +596 -201
- voxcity/simulator/view.py +278 -723
- voxcity/utils/lc.py +183 -0
- voxcity/utils/material.py +99 -32
- voxcity/utils/visualization.py +2578 -1988
- voxcity/utils/weather.py +816 -615
- {voxcity-0.5.14.dist-info → voxcity-0.5.15.dist-info}/METADATA +10 -12
- voxcity-0.5.15.dist-info/RECORD +38 -0
- {voxcity-0.5.14.dist-info → voxcity-0.5.15.dist-info}/WHEEL +1 -1
- voxcity-0.5.14.dist-info/RECORD +0 -38
- {voxcity-0.5.14.dist-info → voxcity-0.5.15.dist-info}/licenses/AUTHORS.rst +0 -0
- {voxcity-0.5.14.dist-info → voxcity-0.5.15.dist-info}/licenses/LICENSE +0 -0
- {voxcity-0.5.14.dist-info → voxcity-0.5.15.dist-info}/top_level.txt +0 -0
voxcity/downloader/omt.py
CHANGED
|
@@ -4,6 +4,21 @@ Module for downloading and processing building data from OpenMapTiles vector til
|
|
|
4
4
|
This module provides functionality to download and process building footprint data from
|
|
5
5
|
OpenMapTiles vector tile service. It handles downloading PBF tiles, extracting building
|
|
6
6
|
geometries, and converting them to GeoJSON format with standardized properties.
|
|
7
|
+
|
|
8
|
+
Key Features:
|
|
9
|
+
- Downloads vector tiles from OpenMapTiles API
|
|
10
|
+
- Extracts building footprints and properties
|
|
11
|
+
- Converts coordinates from tile-local to WGS84
|
|
12
|
+
- Standardizes building height information
|
|
13
|
+
- Handles both Polygon and MultiPolygon geometries
|
|
14
|
+
- Separates inner and outer rings of building footprints
|
|
15
|
+
|
|
16
|
+
Dependencies:
|
|
17
|
+
- mercantile: For tile calculations and coordinate transformations
|
|
18
|
+
- mapbox_vector_tile: For decoding PBF vector tiles
|
|
19
|
+
- shapely: For geometry operations
|
|
20
|
+
- pyproj: For coordinate system transformations
|
|
21
|
+
- geopandas: For working with geospatial data
|
|
7
22
|
"""
|
|
8
23
|
|
|
9
24
|
import mercantile
|
|
@@ -16,15 +31,33 @@ import json
|
|
|
16
31
|
from pyproj import Transformer
|
|
17
32
|
import json
|
|
18
33
|
import geopandas as gpd
|
|
34
|
+
|
|
19
35
|
def load_gdf_from_openmaptiles(rectangle_vertices, API_KEY):
|
|
20
36
|
"""Download and process building footprint data from OpenMapTiles vector tiles.
|
|
21
37
|
|
|
38
|
+
This function downloads vector tiles covering the specified area, extracts building
|
|
39
|
+
footprints, and converts them to a standardized format in a GeoDataFrame.
|
|
40
|
+
|
|
22
41
|
Args:
|
|
23
|
-
rectangle_vertices: List of (lon, lat)
|
|
24
|
-
|
|
42
|
+
rectangle_vertices (list): List of (lon, lat) tuples defining the bounding box corners.
|
|
43
|
+
The coordinates should be in WGS84 (EPSG:4326) format.
|
|
44
|
+
API_KEY (str): OpenMapTiles API key for authentication. Must be valid for the v3 endpoint.
|
|
25
45
|
|
|
26
46
|
Returns:
|
|
27
|
-
geopandas.GeoDataFrame: GeoDataFrame containing building footprints with
|
|
47
|
+
geopandas.GeoDataFrame: A GeoDataFrame containing building footprints with the following columns:
|
|
48
|
+
- geometry: Building footprint geometry in WGS84 coordinates
|
|
49
|
+
- height: Building height in meters
|
|
50
|
+
- min_height: Minimum height (e.g., for elevated structures) in meters
|
|
51
|
+
- confidence: Confidence score (-1.0 for OpenMapTiles data)
|
|
52
|
+
- is_inner: Boolean indicating if the polygon is an inner ring
|
|
53
|
+
- role: String indicating 'inner' or 'outer' ring
|
|
54
|
+
- id: Unique identifier for each building feature
|
|
55
|
+
|
|
56
|
+
Notes:
|
|
57
|
+
- Uses zoom level 15 for optimal detail vs data size balance
|
|
58
|
+
- Converts coordinates from Web Mercator (EPSG:3857) to WGS84 (EPSG:4326)
|
|
59
|
+
- Handles both Polygon and MultiPolygon geometries
|
|
60
|
+
- Separates complex building footprints into their constituent parts
|
|
28
61
|
"""
|
|
29
62
|
# Extract longitudes and latitudes from vertices to find bounding box
|
|
30
63
|
lons = [coord[0] for coord in rectangle_vertices]
|
|
@@ -124,11 +157,26 @@ def load_gdf_from_openmaptiles(rectangle_vertices, API_KEY):
|
|
|
124
157
|
def get_height_from_properties(properties):
|
|
125
158
|
"""Extract building height from properties, using levels if height is not available.
|
|
126
159
|
|
|
160
|
+
This function implements a fallback strategy for determining building heights:
|
|
161
|
+
1. First tries to use explicit render_height property
|
|
162
|
+
2. If not available, estimates height from number of building levels
|
|
163
|
+
3. Returns 0 if no valid height information is found
|
|
164
|
+
|
|
127
165
|
Args:
|
|
128
|
-
properties: Dictionary containing building properties
|
|
166
|
+
properties (dict): Dictionary containing building properties from OpenMapTiles.
|
|
167
|
+
Expected keys:
|
|
168
|
+
- render_height: Direct height specification in meters
|
|
169
|
+
- building:levels: Number of building floors/levels
|
|
129
170
|
|
|
130
171
|
Returns:
|
|
131
|
-
float: Building height in meters
|
|
172
|
+
float: Building height in meters. Values can be:
|
|
173
|
+
- Explicit height from render_height property
|
|
174
|
+
- Estimated height (levels * 5.0 meters per level)
|
|
175
|
+
- 0.0 if no valid height information is found
|
|
176
|
+
|
|
177
|
+
Notes:
|
|
178
|
+
- Assumes average floor height of 5 meters when estimating from levels
|
|
179
|
+
- Handles potential invalid values gracefully by returning 0
|
|
132
180
|
"""
|
|
133
181
|
# First try explicit render_height property
|
|
134
182
|
height = properties.get('render_height')
|
|
@@ -152,11 +200,30 @@ def get_height_from_properties(properties):
|
|
|
152
200
|
def convert_geojson_format(features):
|
|
153
201
|
"""Convert building features to standardized format with height information.
|
|
154
202
|
|
|
203
|
+
This function processes raw OpenMapTiles building features into a standardized format,
|
|
204
|
+
handling complex geometries and adding consistent property attributes.
|
|
205
|
+
|
|
155
206
|
Args:
|
|
156
|
-
features: List of GeoJSON features containing building footprints
|
|
207
|
+
features (list): List of GeoJSON features containing building footprints.
|
|
208
|
+
Each feature should have:
|
|
209
|
+
- geometry: GeoJSON geometry (Polygon or MultiPolygon)
|
|
210
|
+
- properties: Dictionary of building properties
|
|
157
211
|
|
|
158
212
|
Returns:
|
|
159
|
-
list: List of standardized GeoJSON features
|
|
213
|
+
list: List of standardized GeoJSON features where:
|
|
214
|
+
- Complex MultiPolygons are split into individual Polygons
|
|
215
|
+
- Each Polygon ring (outer and inner) becomes a separate feature
|
|
216
|
+
- Properties are standardized to include:
|
|
217
|
+
- height: Building height in meters
|
|
218
|
+
- min_height: Minimum height in meters
|
|
219
|
+
- confidence: Set to -1.0 for OpenMapTiles data
|
|
220
|
+
- is_inner: Boolean flag for inner rings
|
|
221
|
+
- role: String indicating 'inner' or 'outer' ring
|
|
222
|
+
|
|
223
|
+
Notes:
|
|
224
|
+
- Preserves coordinate order as (longitude, latitude)
|
|
225
|
+
- Maintains topological relationships through is_inner and role properties
|
|
226
|
+
- Splits complex geometries for easier processing downstream
|
|
160
227
|
"""
|
|
161
228
|
new_features = []
|
|
162
229
|
|
voxcity/downloader/osm.py
CHANGED
|
@@ -4,6 +4,13 @@ Module for downloading and processing OpenStreetMap data.
|
|
|
4
4
|
This module provides functionality to download and process building footprints, land cover,
|
|
5
5
|
and other geographic features from OpenStreetMap. It handles downloading data via the Overpass API,
|
|
6
6
|
processing the responses, and converting them to standardized GeoJSON format with proper properties.
|
|
7
|
+
|
|
8
|
+
The module includes functions for:
|
|
9
|
+
- Converting OSM JSON to GeoJSON format
|
|
10
|
+
- Processing building footprints with height information
|
|
11
|
+
- Handling land cover classifications
|
|
12
|
+
- Managing coordinate systems and projections
|
|
13
|
+
- Processing roads and other geographic features
|
|
7
14
|
"""
|
|
8
15
|
|
|
9
16
|
import requests
|
|
@@ -174,7 +181,15 @@ def osm_json_to_geojson(osm_data):
|
|
|
174
181
|
}
|
|
175
182
|
|
|
176
183
|
def is_part_of_relation(way_id, osm_data):
|
|
177
|
-
"""Check if a way is part of any relation.
|
|
184
|
+
"""Check if a way is part of any relation in the OSM data.
|
|
185
|
+
|
|
186
|
+
Args:
|
|
187
|
+
way_id (int): The ID of the way to check
|
|
188
|
+
osm_data (dict): OSM JSON data containing elements
|
|
189
|
+
|
|
190
|
+
Returns:
|
|
191
|
+
bool: True if the way is part of a relation, False otherwise
|
|
192
|
+
"""
|
|
178
193
|
for element in osm_data['elements']:
|
|
179
194
|
if element['type'] == 'relation' and 'members' in element:
|
|
180
195
|
for member in element['members']:
|
|
@@ -183,7 +198,18 @@ def is_part_of_relation(way_id, osm_data):
|
|
|
183
198
|
return False
|
|
184
199
|
|
|
185
200
|
def is_way_polygon(way):
|
|
186
|
-
"""Determine if a way should be treated as a polygon.
|
|
201
|
+
"""Determine if a way should be treated as a polygon based on OSM tags and geometry.
|
|
202
|
+
|
|
203
|
+
A way is considered a polygon if:
|
|
204
|
+
1. It forms a closed loop (first and last nodes are the same)
|
|
205
|
+
2. It has tags indicating it represents an area (building, landuse, etc.)
|
|
206
|
+
|
|
207
|
+
Args:
|
|
208
|
+
way (dict): OSM way element with nodes and tags
|
|
209
|
+
|
|
210
|
+
Returns:
|
|
211
|
+
bool: True if the way should be treated as a polygon, False otherwise
|
|
212
|
+
"""
|
|
187
213
|
# Check if the way is closed (first and last nodes are the same)
|
|
188
214
|
if 'nodes' in way and way['nodes'][0] == way['nodes'][-1]:
|
|
189
215
|
# Check for tags that indicate this is an area
|
|
@@ -196,7 +222,16 @@ def is_way_polygon(way):
|
|
|
196
222
|
return False
|
|
197
223
|
|
|
198
224
|
def get_way_coords(way, nodes):
|
|
199
|
-
"""
|
|
225
|
+
"""Extract coordinates for a way from its node references.
|
|
226
|
+
|
|
227
|
+
Args:
|
|
228
|
+
way (dict): OSM way element containing node references
|
|
229
|
+
nodes (dict): Dictionary mapping node IDs to their coordinates
|
|
230
|
+
|
|
231
|
+
Returns:
|
|
232
|
+
list: List of coordinate pairs [(lon, lat), ...] for the way,
|
|
233
|
+
or empty list if any nodes are missing
|
|
234
|
+
"""
|
|
200
235
|
coords = []
|
|
201
236
|
if 'nodes' not in way:
|
|
202
237
|
return coords
|
|
@@ -211,16 +246,22 @@ def get_way_coords(way, nodes):
|
|
|
211
246
|
return coords
|
|
212
247
|
|
|
213
248
|
def create_rings_from_ways(way_ids, ways, nodes):
|
|
214
|
-
"""
|
|
215
|
-
|
|
249
|
+
"""Create continuous rings by connecting ways that share nodes.
|
|
250
|
+
|
|
251
|
+
This function handles complex relations by:
|
|
252
|
+
1. Connecting ways that share end nodes
|
|
253
|
+
2. Handling reversed way directions
|
|
254
|
+
3. Closing rings when possible
|
|
255
|
+
4. Converting node references to coordinates
|
|
216
256
|
|
|
217
257
|
Args:
|
|
218
|
-
way_ids: List of way IDs that make up the ring(s)
|
|
219
|
-
ways: Dictionary mapping way IDs to way elements
|
|
220
|
-
nodes: Dictionary mapping node IDs to coordinates
|
|
258
|
+
way_ids (list): List of way IDs that make up the ring(s)
|
|
259
|
+
ways (dict): Dictionary mapping way IDs to way elements
|
|
260
|
+
nodes (dict): Dictionary mapping node IDs to coordinates
|
|
221
261
|
|
|
222
262
|
Returns:
|
|
223
|
-
List of rings, where each ring is a list of
|
|
263
|
+
list: List of rings, where each ring is a list of coordinate pairs [(lon, lat), ...]
|
|
264
|
+
forming a closed polygon with at least 4 points
|
|
224
265
|
"""
|
|
225
266
|
if not way_ids:
|
|
226
267
|
return []
|
|
@@ -332,11 +373,23 @@ def create_rings_from_ways(way_ids, ways, nodes):
|
|
|
332
373
|
def load_gdf_from_openstreetmap(rectangle_vertices):
|
|
333
374
|
"""Download and process building footprint data from OpenStreetMap.
|
|
334
375
|
|
|
376
|
+
This function:
|
|
377
|
+
1. Downloads building data using the Overpass API
|
|
378
|
+
2. Processes complex relations and their members
|
|
379
|
+
3. Extracts height information and other properties
|
|
380
|
+
4. Converts features to a GeoDataFrame with standardized properties
|
|
381
|
+
|
|
335
382
|
Args:
|
|
336
|
-
rectangle_vertices: List of (lon, lat) coordinates defining the bounding box
|
|
383
|
+
rectangle_vertices (list): List of (lon, lat) coordinates defining the bounding box
|
|
337
384
|
|
|
338
385
|
Returns:
|
|
339
|
-
geopandas.GeoDataFrame: GeoDataFrame containing building footprints with
|
|
386
|
+
geopandas.GeoDataFrame: GeoDataFrame containing building footprints with properties:
|
|
387
|
+
- geometry: Polygon or MultiPolygon
|
|
388
|
+
- height: Building height in meters
|
|
389
|
+
- levels: Number of building levels
|
|
390
|
+
- min_height: Minimum height (for elevated structures)
|
|
391
|
+
- building_type: Type of building
|
|
392
|
+
- And other OSM tags as properties
|
|
340
393
|
"""
|
|
341
394
|
# Create a bounding box from the rectangle vertices
|
|
342
395
|
min_lon = min(v[0] for v in rectangle_vertices)
|
|
@@ -533,13 +586,23 @@ def load_gdf_from_openstreetmap(rectangle_vertices):
|
|
|
533
586
|
return gdf
|
|
534
587
|
|
|
535
588
|
def convert_feature(feature):
|
|
536
|
-
"""Convert a GeoJSON feature to
|
|
589
|
+
"""Convert a GeoJSON feature to a standardized format with height information.
|
|
590
|
+
|
|
591
|
+
This function:
|
|
592
|
+
1. Handles both Polygon and MultiPolygon geometries
|
|
593
|
+
2. Extracts and validates height information
|
|
594
|
+
3. Ensures coordinate order consistency (lon, lat)
|
|
595
|
+
4. Adds confidence scores for height estimates
|
|
537
596
|
|
|
538
597
|
Args:
|
|
539
|
-
feature (dict): Input GeoJSON feature
|
|
598
|
+
feature (dict): Input GeoJSON feature with geometry and properties
|
|
540
599
|
|
|
541
600
|
Returns:
|
|
542
|
-
dict: Converted feature with
|
|
601
|
+
dict: Converted feature with:
|
|
602
|
+
- Standardized geometry (always Polygon)
|
|
603
|
+
- Height information in properties
|
|
604
|
+
- Confidence score for height values
|
|
605
|
+
Or None if the feature is invalid or not a polygon
|
|
543
606
|
"""
|
|
544
607
|
new_feature = {}
|
|
545
608
|
new_feature['type'] = 'Feature'
|
|
@@ -736,13 +799,21 @@ tag_osm_key_value_mapping = {
|
|
|
736
799
|
}
|
|
737
800
|
|
|
738
801
|
def get_classification(tags):
|
|
739
|
-
"""Determine the
|
|
802
|
+
"""Determine the land cover/use classification based on OSM tags.
|
|
803
|
+
|
|
804
|
+
This function maps OSM tags to standardized land cover classes using:
|
|
805
|
+
1. A hierarchical classification system (codes 0-13)
|
|
806
|
+
2. Tag matching patterns for different feature types
|
|
807
|
+
3. Special cases for roads, water bodies, etc.
|
|
740
808
|
|
|
741
809
|
Args:
|
|
742
|
-
tags (dict): Dictionary of OSM tags
|
|
810
|
+
tags (dict): Dictionary of OSM tags (key-value pairs)
|
|
743
811
|
|
|
744
812
|
Returns:
|
|
745
|
-
tuple: (classification_code, classification_name)
|
|
813
|
+
tuple: (classification_code, classification_name) where:
|
|
814
|
+
- classification_code (int): Numeric code (0-13) for the land cover class
|
|
815
|
+
- classification_name (str): Human-readable name of the class
|
|
816
|
+
Or (None, None) if no matching classification is found
|
|
746
817
|
"""
|
|
747
818
|
# Iterate through each classification code and its associated info
|
|
748
819
|
for code, info in classification_mapping.items():
|
|
@@ -764,13 +835,18 @@ def get_classification(tags):
|
|
|
764
835
|
return None, None
|
|
765
836
|
|
|
766
837
|
def swap_coordinates(geom_mapping):
|
|
767
|
-
"""Swap
|
|
838
|
+
"""Swap coordinate order in a GeoJSON geometry object.
|
|
839
|
+
|
|
840
|
+
This function:
|
|
841
|
+
1. Handles nested coordinate structures (Polygons, MultiPolygons)
|
|
842
|
+
2. Preserves the original coordinate order if already correct
|
|
843
|
+
3. Works recursively for complex geometries
|
|
768
844
|
|
|
769
845
|
Args:
|
|
770
|
-
geom_mapping (dict): GeoJSON geometry object
|
|
846
|
+
geom_mapping (dict): GeoJSON geometry object with coordinates
|
|
771
847
|
|
|
772
848
|
Returns:
|
|
773
|
-
dict: Geometry with
|
|
849
|
+
dict: Geometry with coordinates in the correct order (lon, lat)
|
|
774
850
|
"""
|
|
775
851
|
coords = geom_mapping['coordinates']
|
|
776
852
|
|
|
@@ -786,13 +862,23 @@ def swap_coordinates(geom_mapping):
|
|
|
786
862
|
return geom_mapping
|
|
787
863
|
|
|
788
864
|
def load_land_cover_gdf_from_osm(rectangle_vertices_ori):
|
|
789
|
-
"""Load land cover data from OpenStreetMap
|
|
865
|
+
"""Load and classify land cover data from OpenStreetMap.
|
|
866
|
+
|
|
867
|
+
This function:
|
|
868
|
+
1. Downloads land cover features using the Overpass API
|
|
869
|
+
2. Classifies features based on OSM tags
|
|
870
|
+
3. Handles special cases like roads with width information
|
|
871
|
+
4. Projects geometries for accurate buffering
|
|
872
|
+
5. Creates a standardized GeoDataFrame with classifications
|
|
790
873
|
|
|
791
874
|
Args:
|
|
792
|
-
rectangle_vertices_ori (list): List of (lon, lat) coordinates defining the
|
|
875
|
+
rectangle_vertices_ori (list): List of (lon, lat) coordinates defining the area
|
|
793
876
|
|
|
794
877
|
Returns:
|
|
795
|
-
GeoDataFrame: GeoDataFrame
|
|
878
|
+
geopandas.GeoDataFrame: GeoDataFrame with:
|
|
879
|
+
- geometry: Polygon or MultiPolygon features
|
|
880
|
+
- class: Land cover classification name
|
|
881
|
+
- Additional properties from OSM tags
|
|
796
882
|
"""
|
|
797
883
|
# Close the rectangle polygon by adding first vertex at the end
|
|
798
884
|
rectangle_vertices = rectangle_vertices_ori.copy()
|
voxcity/downloader/overture.py
CHANGED
|
@@ -3,6 +3,18 @@ Module for downloading and processing building footprint data from Overture Maps
|
|
|
3
3
|
|
|
4
4
|
This module provides functionality to download and process building footprints,
|
|
5
5
|
handling the conversion of Overture Maps data to GeoJSON format with standardized properties.
|
|
6
|
+
|
|
7
|
+
The module includes functions for:
|
|
8
|
+
- Converting data types between numpy and Python native types
|
|
9
|
+
- Processing and validating building footprint data
|
|
10
|
+
- Handling geometric operations and coordinate transformations
|
|
11
|
+
- Combining and standardizing building data from multiple sources
|
|
12
|
+
|
|
13
|
+
Main workflow:
|
|
14
|
+
1. Download building data from Overture Maps using a bounding box
|
|
15
|
+
2. Process and standardize the data format
|
|
16
|
+
3. Combine building and building part data
|
|
17
|
+
4. Add unique identifiers and standardize properties
|
|
6
18
|
"""
|
|
7
19
|
|
|
8
20
|
from overturemaps import core
|
|
@@ -15,11 +27,26 @@ def convert_numpy_to_python(obj):
|
|
|
15
27
|
"""
|
|
16
28
|
Recursively convert numpy types to native Python types.
|
|
17
29
|
|
|
30
|
+
This function handles various numpy data types and complex nested structures,
|
|
31
|
+
ensuring all data is converted to Python native types for JSON serialization.
|
|
32
|
+
|
|
18
33
|
Args:
|
|
19
|
-
obj: Object to convert, can be
|
|
34
|
+
obj: Object to convert, can be:
|
|
35
|
+
- dict: Dictionary with potentially nested numpy types
|
|
36
|
+
- list/tuple: Sequence with potentially nested numpy types
|
|
37
|
+
- numpy.ndarray: Numpy array to be converted to list
|
|
38
|
+
- numpy.integer/numpy.floating: Numpy numeric types
|
|
39
|
+
- native Python types (bool, str, int, float)
|
|
40
|
+
- None values
|
|
20
41
|
|
|
21
42
|
Returns:
|
|
22
|
-
Converted object with numpy types replaced by native Python types
|
|
43
|
+
object: Converted object with all numpy types replaced by native Python types
|
|
44
|
+
|
|
45
|
+
Examples:
|
|
46
|
+
>>> convert_numpy_to_python(np.int64(42))
|
|
47
|
+
42
|
|
48
|
+
>>> convert_numpy_to_python({'a': np.array([1, 2, 3])})
|
|
49
|
+
{'a': [1, 2, 3]}
|
|
23
50
|
"""
|
|
24
51
|
# Handle dictionary case - recursively convert all values
|
|
25
52
|
if isinstance(obj, dict):
|
|
@@ -50,11 +77,21 @@ def is_valid_value(value):
|
|
|
50
77
|
"""
|
|
51
78
|
Check if a value is valid (not NA/null) and handle array-like objects.
|
|
52
79
|
|
|
80
|
+
This function is used to validate data before processing, ensuring that
|
|
81
|
+
null/NA values are handled appropriately while preserving array-like structures.
|
|
82
|
+
|
|
53
83
|
Args:
|
|
54
|
-
value: Value to check
|
|
84
|
+
value: Value to check, can be:
|
|
85
|
+
- numpy.ndarray: Always considered valid
|
|
86
|
+
- list: Always considered valid
|
|
87
|
+
- scalar values: Checked for NA/null status
|
|
55
88
|
|
|
56
89
|
Returns:
|
|
57
90
|
bool: True if value is valid (not NA/null or is array-like), False otherwise
|
|
91
|
+
|
|
92
|
+
Note:
|
|
93
|
+
Arrays and lists are always considered valid since they may contain
|
|
94
|
+
valid data that needs to be processed individually.
|
|
58
95
|
"""
|
|
59
96
|
# Arrays and lists are always considered valid since they may contain valid data
|
|
60
97
|
if isinstance(value, (np.ndarray, list)):
|
|
@@ -65,14 +102,32 @@ def is_valid_value(value):
|
|
|
65
102
|
def convert_gdf_to_geojson(gdf):
|
|
66
103
|
"""
|
|
67
104
|
Convert GeoDataFrame to GeoJSON format with coordinates in (lon, lat) order.
|
|
68
|
-
|
|
69
|
-
|
|
105
|
+
|
|
106
|
+
This function processes a GeoDataFrame containing building data and converts it
|
|
107
|
+
to a standardized GeoJSON format. It handles special cases for height values
|
|
108
|
+
and ensures all properties are properly converted to JSON-serializable types.
|
|
70
109
|
|
|
71
110
|
Args:
|
|
72
|
-
gdf (GeoDataFrame): Input GeoDataFrame
|
|
111
|
+
gdf (GeoDataFrame): Input GeoDataFrame containing building data with columns:
|
|
112
|
+
- geometry: Shapely geometry objects
|
|
113
|
+
- height: Building height (optional)
|
|
114
|
+
- min_height: Minimum building height (optional)
|
|
115
|
+
- Additional property columns
|
|
73
116
|
|
|
74
117
|
Returns:
|
|
75
|
-
list: List of GeoJSON feature dictionaries
|
|
118
|
+
list: List of GeoJSON feature dictionaries, each containing:
|
|
119
|
+
- type: Always "Feature"
|
|
120
|
+
- properties: Dictionary of building properties including:
|
|
121
|
+
- height: Building height (defaults to 0.0)
|
|
122
|
+
- min_height: Minimum height (defaults to 0.0)
|
|
123
|
+
- id: Sequential unique identifier
|
|
124
|
+
- All other columns from input GeoDataFrame
|
|
125
|
+
- geometry: GeoJSON geometry object
|
|
126
|
+
|
|
127
|
+
Note:
|
|
128
|
+
- Height values default to 0.0 if missing or invalid
|
|
129
|
+
- All numpy types are converted to native Python types
|
|
130
|
+
- Sequential IDs are assigned starting from 1
|
|
76
131
|
"""
|
|
77
132
|
features = []
|
|
78
133
|
id_count = 1
|
|
@@ -117,14 +172,22 @@ def convert_gdf_to_geojson(gdf):
|
|
|
117
172
|
|
|
118
173
|
def rectangle_to_bbox(vertices):
|
|
119
174
|
"""
|
|
120
|
-
Convert rectangle vertices in (lon, lat) format to a
|
|
121
|
-
|
|
175
|
+
Convert rectangle vertices in (lon, lat) format to a bounding box.
|
|
176
|
+
|
|
177
|
+
This function takes a list of coordinate pairs defining a rectangle and
|
|
178
|
+
converts them to a bounding box format required by the Overture Maps API.
|
|
122
179
|
|
|
123
180
|
Args:
|
|
124
181
|
vertices (list): List of tuples containing (lon, lat) coordinates
|
|
182
|
+
defining the corners of a rectangle
|
|
125
183
|
|
|
126
184
|
Returns:
|
|
127
|
-
tuple: Bounding box coordinates (min_lon, min_lat, max_lon, max_lat)
|
|
185
|
+
tuple: Bounding box coordinates in format (min_lon, min_lat, max_lon, max_lat)
|
|
186
|
+
suitable for use with Overture Maps API
|
|
187
|
+
|
|
188
|
+
Note:
|
|
189
|
+
The function calculates the minimum and maximum coordinates to ensure
|
|
190
|
+
the bounding box encompasses all provided vertices.
|
|
128
191
|
"""
|
|
129
192
|
# Extract lon, lat values from vertices
|
|
130
193
|
lons = [vertex[0] for vertex in vertices]
|
|
@@ -141,14 +204,26 @@ def rectangle_to_bbox(vertices):
|
|
|
141
204
|
|
|
142
205
|
def join_gdfs_vertically(gdf1, gdf2):
|
|
143
206
|
"""
|
|
144
|
-
Join two GeoDataFrames vertically, handling different
|
|
207
|
+
Join two GeoDataFrames vertically, handling different column structures.
|
|
208
|
+
|
|
209
|
+
This function combines two GeoDataFrames that may have different columns,
|
|
210
|
+
ensuring all columns from both datasets are preserved in the output.
|
|
211
|
+
It provides diagnostic information about the combining process.
|
|
145
212
|
|
|
146
213
|
Args:
|
|
147
|
-
gdf1 (GeoDataFrame): First GeoDataFrame
|
|
148
|
-
gdf2 (GeoDataFrame): Second GeoDataFrame
|
|
214
|
+
gdf1 (GeoDataFrame): First GeoDataFrame (e.g., buildings)
|
|
215
|
+
gdf2 (GeoDataFrame): Second GeoDataFrame (e.g., building parts)
|
|
149
216
|
|
|
150
217
|
Returns:
|
|
151
|
-
GeoDataFrame: Combined GeoDataFrame
|
|
218
|
+
GeoDataFrame: Combined GeoDataFrame containing:
|
|
219
|
+
- All rows from both input GeoDataFrames
|
|
220
|
+
- All columns from both inputs (filled with None where missing)
|
|
221
|
+
- Preserved geometry column
|
|
222
|
+
|
|
223
|
+
Note:
|
|
224
|
+
- Prints diagnostic information about column differences
|
|
225
|
+
- Handles missing columns by filling with None values
|
|
226
|
+
- Preserves the geometry column for spatial operations
|
|
152
227
|
"""
|
|
153
228
|
# Print diagnostic information about column differences
|
|
154
229
|
print("GDF1 columns:", list(gdf1.columns))
|
|
@@ -183,26 +258,36 @@ def load_gdf_from_overture(rectangle_vertices):
|
|
|
183
258
|
"""
|
|
184
259
|
Download and process building footprint data from Overture Maps.
|
|
185
260
|
|
|
261
|
+
This function serves as the main entry point for downloading building data.
|
|
262
|
+
It handles the complete workflow of downloading both building and building
|
|
263
|
+
part data, combining them, and preparing them for further processing.
|
|
264
|
+
|
|
186
265
|
Args:
|
|
187
|
-
rectangle_vertices (list): List of (lon, lat) coordinates defining
|
|
266
|
+
rectangle_vertices (list): List of (lon, lat) coordinates defining
|
|
267
|
+
the bounding box for data download
|
|
188
268
|
|
|
189
269
|
Returns:
|
|
190
|
-
|
|
270
|
+
GeoDataFrame: Combined dataset containing:
|
|
271
|
+
- Building and building part geometries
|
|
272
|
+
- Standardized properties
|
|
273
|
+
- Sequential numeric IDs
|
|
274
|
+
|
|
275
|
+
Note:
|
|
276
|
+
- Downloads both building and building_part data from Overture Maps
|
|
277
|
+
- Combines the datasets while preserving all properties
|
|
278
|
+
- Assigns sequential IDs based on the final dataset index
|
|
191
279
|
"""
|
|
192
|
-
# Convert vertices to bounding box format
|
|
280
|
+
# Convert input vertices to Overture Maps API bounding box format
|
|
193
281
|
bbox = rectangle_to_bbox(rectangle_vertices)
|
|
194
282
|
|
|
195
|
-
# Download building and building part data
|
|
283
|
+
# Download primary building footprints and additional building part data
|
|
196
284
|
building_gdf = core.geodataframe("building", bbox=bbox)
|
|
197
285
|
building_part_gdf = core.geodataframe("building_part", bbox=bbox)
|
|
198
286
|
|
|
199
|
-
# Combine
|
|
287
|
+
# Combine both datasets into a single comprehensive building dataset
|
|
200
288
|
joined_building_gdf = join_gdfs_vertically(building_gdf, building_part_gdf)
|
|
201
289
|
|
|
202
|
-
#
|
|
203
|
-
# geojson_features = convert_gdf_to_geojson(joined_building_gdf)
|
|
204
|
-
|
|
205
|
-
# Replace id column with index numbers
|
|
290
|
+
# Assign sequential IDs based on the final dataset index
|
|
206
291
|
joined_building_gdf['id'] = joined_building_gdf.index
|
|
207
292
|
|
|
208
293
|
return joined_building_gdf
|
voxcity/downloader/utils.py
CHANGED
|
@@ -1,21 +1,36 @@
|
|
|
1
|
+
# Utility functions for downloading files from various sources
|
|
1
2
|
import requests
|
|
2
3
|
import gdown
|
|
3
4
|
|
|
4
5
|
def download_file(url, filename):
|
|
5
6
|
"""Download a file from a URL and save it locally.
|
|
6
7
|
|
|
8
|
+
This function uses the requests library to download a file from any publicly
|
|
9
|
+
accessible URL and save it to the local filesystem. It handles the download
|
|
10
|
+
process and provides feedback on the operation's success or failure.
|
|
11
|
+
|
|
7
12
|
Args:
|
|
8
|
-
url (str): URL of the file to download
|
|
9
|
-
filename (str): Local path where the downloaded file will be saved
|
|
13
|
+
url (str): URL of the file to download. Must be a valid, accessible URL.
|
|
14
|
+
filename (str): Local path where the downloaded file will be saved.
|
|
15
|
+
Include the full path and filename with extension.
|
|
10
16
|
|
|
11
17
|
Returns:
|
|
12
18
|
None
|
|
13
19
|
|
|
14
20
|
Prints:
|
|
15
|
-
Success
|
|
21
|
+
- Success message with filename if download is successful (status code 200)
|
|
22
|
+
- Error message with status code if download fails
|
|
23
|
+
|
|
24
|
+
Example:
|
|
25
|
+
>>> download_file('https://example.com/file.pdf', 'local_file.pdf')
|
|
26
|
+
File downloaded successfully and saved as local_file.pdf
|
|
16
27
|
"""
|
|
28
|
+
# Attempt to download the file from the provided URL
|
|
17
29
|
response = requests.get(url)
|
|
30
|
+
|
|
31
|
+
# Check if the download was successful (HTTP status code 200)
|
|
18
32
|
if response.status_code == 200:
|
|
33
|
+
# Open the local file in binary write mode and save the content
|
|
19
34
|
with open(filename, 'wb') as file:
|
|
20
35
|
file.write(response.content)
|
|
21
36
|
print(f"File downloaded successfully and saved as {filename}")
|
|
@@ -25,18 +40,33 @@ def download_file(url, filename):
|
|
|
25
40
|
def download_file_google_drive(file_id, output_path):
|
|
26
41
|
"""Download a file from Google Drive using its file ID.
|
|
27
42
|
|
|
43
|
+
This function specifically handles downloads from Google Drive using the gdown
|
|
44
|
+
library, which is designed to bypass Google Drive's download restrictions.
|
|
45
|
+
It's useful for downloading large files or files that require authentication.
|
|
46
|
+
|
|
28
47
|
Args:
|
|
29
|
-
file_id (str): Google Drive file ID
|
|
30
|
-
|
|
48
|
+
file_id (str): Google Drive file ID. This is the unique identifier in the
|
|
49
|
+
sharing URL after '/d/' or 'id='.
|
|
50
|
+
output_path (str): Local path where the downloaded file will be saved.
|
|
51
|
+
Include the full path and filename with extension.
|
|
31
52
|
|
|
32
53
|
Returns:
|
|
33
|
-
bool: True if download successful, False
|
|
54
|
+
bool: True if download was successful, False if any error occurred
|
|
34
55
|
|
|
35
56
|
Prints:
|
|
36
|
-
Error message if download fails
|
|
57
|
+
Error message with exception details if download fails
|
|
58
|
+
|
|
59
|
+
Example:
|
|
60
|
+
>>> success = download_file_google_drive('1234abcd...', 'downloaded_file.zip')
|
|
61
|
+
>>> if success:
|
|
62
|
+
>>> print("Download completed successfully")
|
|
37
63
|
"""
|
|
64
|
+
# Construct the direct download URL using the file ID
|
|
38
65
|
url = f"https://drive.google.com/uc?id={file_id}"
|
|
66
|
+
|
|
39
67
|
try:
|
|
68
|
+
# Use gdown to handle the Google Drive download
|
|
69
|
+
# quiet=False enables download progress display
|
|
40
70
|
gdown.download(url, output_path, quiet=False)
|
|
41
71
|
return True
|
|
42
72
|
except Exception as e:
|