voxcity 0.5.27__py3-none-any.whl → 0.5.29__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/osm.py +1038 -1040
- voxcity/generator.py +55 -35
- voxcity/geoprocessor/draw.py +297 -3
- voxcity/geoprocessor/grid.py +5 -3
- {voxcity-0.5.27.dist-info → voxcity-0.5.29.dist-info}/METADATA +1 -1
- {voxcity-0.5.27.dist-info → voxcity-0.5.29.dist-info}/RECORD +10 -10
- {voxcity-0.5.27.dist-info → voxcity-0.5.29.dist-info}/WHEEL +0 -0
- {voxcity-0.5.27.dist-info → voxcity-0.5.29.dist-info}/licenses/AUTHORS.rst +0 -0
- {voxcity-0.5.27.dist-info → voxcity-0.5.29.dist-info}/licenses/LICENSE +0 -0
- {voxcity-0.5.27.dist-info → voxcity-0.5.29.dist-info}/top_level.txt +0 -0
voxcity/generator.py
CHANGED
|
@@ -6,12 +6,19 @@ It handles land cover, building heights, canopy heights, and digital elevation m
|
|
|
6
6
|
|
|
7
7
|
The main functions are:
|
|
8
8
|
- get_land_cover_grid: Creates a grid of land cover classifications
|
|
9
|
-
- get_building_height_grid: Creates a grid of building heights
|
|
9
|
+
- get_building_height_grid: Creates a grid of building heights (supports GeoDataFrame input)
|
|
10
10
|
- get_canopy_height_grid: Creates a grid of tree canopy heights
|
|
11
11
|
- get_dem_grid: Creates a digital elevation model grid
|
|
12
12
|
- create_3d_voxel: Combines the grids into a 3D voxel representation
|
|
13
13
|
- create_3d_voxel_individuals: Creates separate voxel grids for each component
|
|
14
|
-
- get_voxcity: Main function to generate a complete voxel city model
|
|
14
|
+
- get_voxcity: Main function to generate a complete voxel city model (supports GeoDataFrame input)
|
|
15
|
+
|
|
16
|
+
Key Features:
|
|
17
|
+
- Support for multiple data sources (OpenStreetMap, ESA WorldCover, Google Earth Engine, etc.)
|
|
18
|
+
- Direct GeoDataFrame input for building data (useful for custom datasets)
|
|
19
|
+
- 3D voxel generation with configurable resolution
|
|
20
|
+
- Visualization capabilities for both 2D grids and 3D models
|
|
21
|
+
- Data export in various formats (GeoTIFF, GeoJSON, pickle)
|
|
15
22
|
"""
|
|
16
23
|
|
|
17
24
|
# Standard library imports
|
|
@@ -87,6 +94,7 @@ def get_land_cover_grid(rectangle_vertices, meshsize, source, output_dir, **kwar
|
|
|
87
94
|
- esri_landcover_year: Year for ESRI land cover data
|
|
88
95
|
- dynamic_world_date: Date for Dynamic World data
|
|
89
96
|
- gridvis: Whether to visualize the grid
|
|
97
|
+
- default_land_cover_class: Default class for grid cells with no intersecting polygons (default: 'Developed space')
|
|
90
98
|
|
|
91
99
|
Returns:
|
|
92
100
|
numpy.ndarray: Grid of land cover classifications as integer values
|
|
@@ -142,7 +150,8 @@ def get_land_cover_grid(rectangle_vertices, meshsize, source, output_dir, **kwar
|
|
|
142
150
|
# Different processing for vector vs raster data sources
|
|
143
151
|
if source == 'OpenStreetMap':
|
|
144
152
|
# Process vector data directly from GeoDataFrame
|
|
145
|
-
|
|
153
|
+
default_class = kwargs.get('default_land_cover_class', 'Developed space')
|
|
154
|
+
land_cover_grid_str = create_land_cover_grid_from_gdf_polygon(land_cover_gdf, meshsize, source, rectangle_vertices, default_class=default_class)
|
|
146
155
|
else:
|
|
147
156
|
# Process raster data from GeoTIFF file
|
|
148
157
|
land_cover_grid_str = create_land_cover_grid_from_geotiff_polygon(geotiff_path, meshsize, land_cover_classes, rectangle_vertices)
|
|
@@ -164,14 +173,15 @@ def get_land_cover_grid(rectangle_vertices, meshsize, source, output_dir, **kwar
|
|
|
164
173
|
return land_cover_grid_int
|
|
165
174
|
|
|
166
175
|
# def get_building_height_grid(rectangle_vertices, meshsize, source, output_dir="output", visualization=True, maptiler_API_key=None, file_path=None):
|
|
167
|
-
def get_building_height_grid(rectangle_vertices, meshsize, source, output_dir, **kwargs):
|
|
176
|
+
def get_building_height_grid(rectangle_vertices, meshsize, source, output_dir, building_gdf=None, **kwargs):
|
|
168
177
|
"""Creates a grid of building heights.
|
|
169
178
|
|
|
170
179
|
Args:
|
|
171
180
|
rectangle_vertices: List of coordinates defining the area of interest
|
|
172
181
|
meshsize: Size of each grid cell in meters
|
|
173
|
-
source: Data source for buildings (e.g. 'OpenStreetMap', 'Microsoft Building Footprints')
|
|
182
|
+
source: Data source for buildings (e.g. 'OpenStreetMap', 'Microsoft Building Footprints', 'GeoDataFrame')
|
|
174
183
|
output_dir: Directory to save output files
|
|
184
|
+
building_gdf: Optional GeoDataFrame with building footprint, height and other information
|
|
175
185
|
**kwargs: Additional arguments including:
|
|
176
186
|
- maptiler_API_key: API key for MapTiler
|
|
177
187
|
- building_path: Path to local building data file
|
|
@@ -187,7 +197,7 @@ def get_building_height_grid(rectangle_vertices, meshsize, source, output_dir, *
|
|
|
187
197
|
"""
|
|
188
198
|
|
|
189
199
|
# Initialize Earth Engine for satellite-based building data sources
|
|
190
|
-
if source not in ["OpenStreetMap", "Overture", "Local file"]:
|
|
200
|
+
if source not in ["OpenStreetMap", "Overture", "Local file", "GeoDataFrame"]:
|
|
191
201
|
initialize_earth_engine()
|
|
192
202
|
|
|
193
203
|
print("Creating Building Height grid\n ")
|
|
@@ -195,32 +205,40 @@ def get_building_height_grid(rectangle_vertices, meshsize, source, output_dir, *
|
|
|
195
205
|
|
|
196
206
|
os.makedirs(output_dir, exist_ok=True)
|
|
197
207
|
|
|
198
|
-
#
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
#
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
|
|
208
|
-
|
|
209
|
-
|
|
210
|
-
|
|
211
|
-
|
|
212
|
-
|
|
213
|
-
|
|
214
|
-
|
|
215
|
-
|
|
216
|
-
|
|
217
|
-
|
|
218
|
-
|
|
219
|
-
|
|
220
|
-
#
|
|
221
|
-
|
|
222
|
-
|
|
223
|
-
gdf =
|
|
208
|
+
# If building_gdf is provided, use it directly
|
|
209
|
+
if building_gdf is not None:
|
|
210
|
+
gdf = building_gdf
|
|
211
|
+
print("Using provided GeoDataFrame for building data")
|
|
212
|
+
else:
|
|
213
|
+
# Fetch building data from primary source
|
|
214
|
+
# Each source has different data formats and processing requirements
|
|
215
|
+
if source == 'Microsoft Building Footprints':
|
|
216
|
+
# Machine learning-derived building footprints from satellite imagery
|
|
217
|
+
gdf = get_mbfp_gdf(output_dir, rectangle_vertices)
|
|
218
|
+
elif source == 'OpenStreetMap':
|
|
219
|
+
# Crowd-sourced building data with varying completeness
|
|
220
|
+
gdf = load_gdf_from_openstreetmap(rectangle_vertices)
|
|
221
|
+
elif source == "Open Building 2.5D Temporal":
|
|
222
|
+
# Special case: this source provides both footprints and heights
|
|
223
|
+
# Skip GeoDataFrame processing and create grids directly
|
|
224
|
+
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)
|
|
225
|
+
elif source == 'EUBUCCO v0.1':
|
|
226
|
+
# European building database with height information
|
|
227
|
+
gdf = load_gdf_from_eubucco(rectangle_vertices, output_dir)
|
|
228
|
+
# elif source == "OpenMapTiles":
|
|
229
|
+
# # Vector tiles service for building data
|
|
230
|
+
# gdf = load_gdf_from_openmaptiles(rectangle_vertices, kwargs["maptiler_API_key"])
|
|
231
|
+
elif source == "Overture":
|
|
232
|
+
# Open building dataset from Overture Maps Foundation
|
|
233
|
+
gdf = load_gdf_from_overture(rectangle_vertices)
|
|
234
|
+
elif source == "Local file":
|
|
235
|
+
# Handle user-provided local building data files
|
|
236
|
+
_, extension = os.path.splitext(kwargs["building_path"])
|
|
237
|
+
if extension == ".gpkg":
|
|
238
|
+
gdf = get_gdf_from_gpkg(kwargs["building_path"], rectangle_vertices)
|
|
239
|
+
elif source == "GeoDataFrame":
|
|
240
|
+
# This case is handled by the building_gdf parameter above
|
|
241
|
+
raise ValueError("When source is 'GeoDataFrame', building_gdf parameter must be provided")
|
|
224
242
|
|
|
225
243
|
# Handle complementary data sources to fill gaps or provide additional information
|
|
226
244
|
# This allows combining multiple sources for better coverage or accuracy
|
|
@@ -590,16 +608,17 @@ def create_3d_voxel_individuals(building_height_grid_ori, land_cover_grid_ori, d
|
|
|
590
608
|
|
|
591
609
|
return land_cover_voxel_grid, building_voxel_grid, tree_voxel_grid, dem_voxel_grid, layered_voxel_grid
|
|
592
610
|
|
|
593
|
-
def get_voxcity(rectangle_vertices, building_source, land_cover_source, canopy_height_source, dem_source, meshsize, **kwargs):
|
|
611
|
+
def get_voxcity(rectangle_vertices, building_source, land_cover_source, canopy_height_source, dem_source, meshsize, building_gdf=None, **kwargs):
|
|
594
612
|
"""Main function to generate a complete voxel city model.
|
|
595
613
|
|
|
596
614
|
Args:
|
|
597
615
|
rectangle_vertices: List of coordinates defining the area of interest
|
|
598
|
-
building_source: Source for building height data (e.g. 'OSM', 'EUBUCCO')
|
|
616
|
+
building_source: Source for building height data (e.g. 'OSM', 'EUBUCCO', 'GeoDataFrame')
|
|
599
617
|
land_cover_source: Source for land cover data (e.g. 'ESA', 'ESRI')
|
|
600
618
|
canopy_height_source: Source for tree canopy height data
|
|
601
619
|
dem_source: Source for digital elevation model data ('Flat' or other source)
|
|
602
620
|
meshsize: Size of each grid cell in meters
|
|
621
|
+
building_gdf: Optional GeoDataFrame with building footprint, height and other information
|
|
603
622
|
**kwargs: Additional keyword arguments including:
|
|
604
623
|
- output_dir: Directory to save output files (default: 'output')
|
|
605
624
|
- min_canopy_height: Minimum height threshold for tree canopy
|
|
@@ -607,6 +626,7 @@ def get_voxcity(rectangle_vertices, building_source, land_cover_source, canopy_h
|
|
|
607
626
|
- mapvis: Whether to visualize grids on map
|
|
608
627
|
- voxelvis: Whether to visualize 3D voxel model
|
|
609
628
|
- voxelvis_img_save_path: Path to save 3D visualization
|
|
629
|
+
- default_land_cover_class: Default class for land cover grid cells with no intersecting polygons (default: 'Developed space')
|
|
610
630
|
|
|
611
631
|
Returns:
|
|
612
632
|
tuple containing:
|
|
@@ -633,7 +653,7 @@ def get_voxcity(rectangle_vertices, building_source, land_cover_source, canopy_h
|
|
|
633
653
|
land_cover_grid = get_land_cover_grid(rectangle_vertices, meshsize, land_cover_source, output_dir, **kwargs)
|
|
634
654
|
|
|
635
655
|
# Building footprints and height information
|
|
636
|
-
building_height_grid, building_min_height_grid, building_id_grid, building_gdf = get_building_height_grid(rectangle_vertices, meshsize, building_source, output_dir, **kwargs)
|
|
656
|
+
building_height_grid, building_min_height_grid, building_id_grid, building_gdf = get_building_height_grid(rectangle_vertices, meshsize, building_source, output_dir, building_gdf=building_gdf, **kwargs)
|
|
637
657
|
|
|
638
658
|
# Save building data to file for later analysis or visualization
|
|
639
659
|
if not building_gdf.empty:
|
voxcity/geoprocessor/draw.py
CHANGED
|
@@ -25,10 +25,19 @@ Dependencies:
|
|
|
25
25
|
|
|
26
26
|
import math
|
|
27
27
|
from pyproj import Proj, transform
|
|
28
|
-
from ipyleaflet import
|
|
29
|
-
|
|
28
|
+
from ipyleaflet import (
|
|
29
|
+
Map,
|
|
30
|
+
DrawControl,
|
|
31
|
+
Rectangle,
|
|
32
|
+
Polygon as LeafletPolygon,
|
|
33
|
+
WidgetControl
|
|
34
|
+
)
|
|
30
35
|
from geopy import distance
|
|
31
36
|
import shapely.geometry as geom
|
|
37
|
+
import geopandas as gpd
|
|
38
|
+
from ipywidgets import VBox, HBox, Button, FloatText, Label, Output, HTML
|
|
39
|
+
import pandas as pd
|
|
40
|
+
from IPython.display import display, clear_output
|
|
32
41
|
|
|
33
42
|
from .utils import get_coordinates_from_cityname
|
|
34
43
|
|
|
@@ -484,4 +493,289 @@ def display_buildings_and_draw_polygon(building_gdf=None, rectangle_vertices=Non
|
|
|
484
493
|
draw_control.on_draw(handle_draw)
|
|
485
494
|
m.add_control(draw_control)
|
|
486
495
|
|
|
487
|
-
return m, drawn_polygon_vertices
|
|
496
|
+
return m, drawn_polygon_vertices
|
|
497
|
+
|
|
498
|
+
def draw_additional_buildings(building_gdf=None, initial_center=None, zoom=17):
|
|
499
|
+
"""
|
|
500
|
+
Creates an interactive map for drawing building footprints with height input.
|
|
501
|
+
|
|
502
|
+
This function provides an interface for users to:
|
|
503
|
+
1. Draw building footprints on an interactive map
|
|
504
|
+
2. Set building height values through a UI widget
|
|
505
|
+
3. Add new buildings to the existing building_gdf
|
|
506
|
+
|
|
507
|
+
The workflow is:
|
|
508
|
+
- User draws a polygon on the map
|
|
509
|
+
- Height input widget appears
|
|
510
|
+
- User enters height and clicks "Add Building"
|
|
511
|
+
- Building is added to GeoDataFrame and displayed on map
|
|
512
|
+
|
|
513
|
+
Args:
|
|
514
|
+
building_gdf (GeoDataFrame, optional): Existing building footprints to display.
|
|
515
|
+
If None, creates a new empty GeoDataFrame.
|
|
516
|
+
Expected columns: ['id', 'height', 'min_height', 'geometry', 'building_id']
|
|
517
|
+
- 'id': Integer ID from data sources (e.g., OSM building id)
|
|
518
|
+
- 'height': Building height in meters (set by user input)
|
|
519
|
+
- 'min_height': Minimum height in meters (defaults to 0.0)
|
|
520
|
+
- 'geometry': Building footprint polygon
|
|
521
|
+
- 'building_id': Unique building identifier
|
|
522
|
+
initial_center (tuple, optional): Initial map center as (lon, lat).
|
|
523
|
+
If None, centers on existing buildings or defaults to (-100, 40).
|
|
524
|
+
zoom (int): Initial zoom level (default=17).
|
|
525
|
+
|
|
526
|
+
Returns:
|
|
527
|
+
tuple: (map_object, updated_building_gdf)
|
|
528
|
+
- map_object: ipyleaflet Map instance with drawing controls
|
|
529
|
+
- updated_building_gdf: GeoDataFrame that automatically updates when buildings are added
|
|
530
|
+
|
|
531
|
+
Example:
|
|
532
|
+
>>> # Start with empty buildings
|
|
533
|
+
>>> m, buildings = draw_additional_buildings()
|
|
534
|
+
>>> # Draw buildings on the map...
|
|
535
|
+
>>> print(buildings) # Will contain all drawn buildings
|
|
536
|
+
"""
|
|
537
|
+
|
|
538
|
+
# Initialize or copy the building GeoDataFrame
|
|
539
|
+
if building_gdf is None:
|
|
540
|
+
# Create empty GeoDataFrame with required columns
|
|
541
|
+
updated_gdf = gpd.GeoDataFrame(
|
|
542
|
+
columns=['id', 'height', 'min_height', 'geometry', 'building_id'],
|
|
543
|
+
crs='EPSG:4326'
|
|
544
|
+
)
|
|
545
|
+
else:
|
|
546
|
+
# Make a copy to avoid modifying the original
|
|
547
|
+
updated_gdf = building_gdf.copy()
|
|
548
|
+
# Ensure all required columns exist
|
|
549
|
+
if 'height' not in updated_gdf.columns:
|
|
550
|
+
updated_gdf['height'] = 10.0 # Default height
|
|
551
|
+
if 'min_height' not in updated_gdf.columns:
|
|
552
|
+
updated_gdf['min_height'] = 0.0 # Default min_height
|
|
553
|
+
if 'building_id' not in updated_gdf.columns:
|
|
554
|
+
updated_gdf['building_id'] = range(len(updated_gdf))
|
|
555
|
+
if 'id' not in updated_gdf.columns:
|
|
556
|
+
updated_gdf['id'] = range(len(updated_gdf))
|
|
557
|
+
|
|
558
|
+
# Determine map center
|
|
559
|
+
if initial_center is not None:
|
|
560
|
+
center_lon, center_lat = initial_center
|
|
561
|
+
elif updated_gdf is not None and len(updated_gdf) > 0:
|
|
562
|
+
bounds = updated_gdf.total_bounds
|
|
563
|
+
min_lon, min_lat, max_lon, max_lat = bounds
|
|
564
|
+
center_lon = (min_lon + max_lon) / 2
|
|
565
|
+
center_lat = (min_lat + max_lat) / 2
|
|
566
|
+
else:
|
|
567
|
+
center_lon, center_lat = -100.0, 40.0
|
|
568
|
+
|
|
569
|
+
# Create the map
|
|
570
|
+
m = Map(center=(center_lat, center_lon), zoom=zoom, scroll_wheel_zoom=True)
|
|
571
|
+
|
|
572
|
+
# Display existing buildings
|
|
573
|
+
building_layers = {}
|
|
574
|
+
for idx, row in updated_gdf.iterrows():
|
|
575
|
+
if isinstance(row.geometry, geom.Polygon):
|
|
576
|
+
coords = list(row.geometry.exterior.coords)
|
|
577
|
+
lat_lon_coords = [(c[1], c[0]) for c in coords[:-1]]
|
|
578
|
+
|
|
579
|
+
height = row.get('height', 10.0)
|
|
580
|
+
min_height = row.get('min_height', 0.0)
|
|
581
|
+
building_id = row.get('building_id', idx)
|
|
582
|
+
bldg_id = row.get('id', idx)
|
|
583
|
+
bldg_layer = LeafletPolygon(
|
|
584
|
+
locations=lat_lon_coords,
|
|
585
|
+
color="blue",
|
|
586
|
+
fill_color="blue",
|
|
587
|
+
fill_opacity=0.3,
|
|
588
|
+
weight=2,
|
|
589
|
+
popup=HTML(f"<b>Building ID:</b> {building_id}<br>"
|
|
590
|
+
f"<b>ID:</b> {bldg_id}<br>"
|
|
591
|
+
f"<b>Height:</b> {height}m<br>"
|
|
592
|
+
f"<b>Min Height:</b> {min_height}m")
|
|
593
|
+
)
|
|
594
|
+
m.add_layer(bldg_layer)
|
|
595
|
+
building_layers[idx] = bldg_layer
|
|
596
|
+
|
|
597
|
+
# Create UI widgets
|
|
598
|
+
height_input = FloatText(
|
|
599
|
+
value=10.0,
|
|
600
|
+
description='Height (m):',
|
|
601
|
+
disabled=False,
|
|
602
|
+
style={'description_width': 'initial'}
|
|
603
|
+
)
|
|
604
|
+
|
|
605
|
+
add_button = Button(
|
|
606
|
+
description='Add Building',
|
|
607
|
+
button_style='success',
|
|
608
|
+
disabled=True
|
|
609
|
+
)
|
|
610
|
+
|
|
611
|
+
clear_button = Button(
|
|
612
|
+
description='Clear Drawing',
|
|
613
|
+
button_style='warning',
|
|
614
|
+
disabled=True
|
|
615
|
+
)
|
|
616
|
+
|
|
617
|
+
status_output = Output()
|
|
618
|
+
|
|
619
|
+
# Create control panel
|
|
620
|
+
control_panel = VBox([
|
|
621
|
+
HTML("<h3>Draw Building Tool</h3>"),
|
|
622
|
+
HTML("<p>1. Draw a polygon on the map<br>2. Set height<br>3. Click 'Add Building'</p>"),
|
|
623
|
+
height_input,
|
|
624
|
+
HBox([add_button, clear_button]),
|
|
625
|
+
status_output
|
|
626
|
+
])
|
|
627
|
+
|
|
628
|
+
# Add control panel to map
|
|
629
|
+
widget_control = WidgetControl(widget=control_panel, position='topright')
|
|
630
|
+
m.add_control(widget_control)
|
|
631
|
+
|
|
632
|
+
# Store the current drawn polygon
|
|
633
|
+
current_polygon = {'vertices': [], 'layer': None}
|
|
634
|
+
|
|
635
|
+
# Drawing control
|
|
636
|
+
draw_control = DrawControl(
|
|
637
|
+
polygon={
|
|
638
|
+
"shapeOptions": {
|
|
639
|
+
"color": "red",
|
|
640
|
+
"fillColor": "red",
|
|
641
|
+
"fillOpacity": 0.3,
|
|
642
|
+
"weight": 3
|
|
643
|
+
}
|
|
644
|
+
},
|
|
645
|
+
rectangle={},
|
|
646
|
+
circle={},
|
|
647
|
+
circlemarker={},
|
|
648
|
+
polyline={},
|
|
649
|
+
marker={}
|
|
650
|
+
)
|
|
651
|
+
|
|
652
|
+
def handle_draw(self, action, geo_json):
|
|
653
|
+
"""Handle polygon drawing events"""
|
|
654
|
+
with status_output:
|
|
655
|
+
clear_output()
|
|
656
|
+
|
|
657
|
+
if action == 'created' and geo_json['geometry']['type'] == 'Polygon':
|
|
658
|
+
# Store vertices
|
|
659
|
+
coordinates = geo_json['geometry']['coordinates'][0]
|
|
660
|
+
current_polygon['vertices'] = [(coord[0], coord[1]) for coord in coordinates[:-1]]
|
|
661
|
+
|
|
662
|
+
# Enable buttons
|
|
663
|
+
add_button.disabled = False
|
|
664
|
+
clear_button.disabled = False
|
|
665
|
+
|
|
666
|
+
with status_output:
|
|
667
|
+
print(f"Polygon drawn with {len(current_polygon['vertices'])} vertices")
|
|
668
|
+
print("Set height and click 'Add Building'")
|
|
669
|
+
|
|
670
|
+
def add_building_click(b):
|
|
671
|
+
"""Handle add building button click"""
|
|
672
|
+
# Use nonlocal to modify the outer scope variable
|
|
673
|
+
nonlocal updated_gdf
|
|
674
|
+
|
|
675
|
+
with status_output:
|
|
676
|
+
clear_output()
|
|
677
|
+
|
|
678
|
+
if current_polygon['vertices']:
|
|
679
|
+
# Create polygon geometry
|
|
680
|
+
polygon = geom.Polygon(current_polygon['vertices'])
|
|
681
|
+
|
|
682
|
+
# Get next building ID and ID values (ensure uniqueness)
|
|
683
|
+
if len(updated_gdf) > 0:
|
|
684
|
+
next_building_id = int(updated_gdf['building_id'].max() + 1)
|
|
685
|
+
next_id = int(updated_gdf['id'].max() + 1)
|
|
686
|
+
else:
|
|
687
|
+
next_building_id = 1
|
|
688
|
+
next_id = 1
|
|
689
|
+
|
|
690
|
+
# Create new row data
|
|
691
|
+
new_row_data = {
|
|
692
|
+
'geometry': polygon,
|
|
693
|
+
'height': float(height_input.value),
|
|
694
|
+
'min_height': 0.0, # Default value as requested
|
|
695
|
+
'building_id': next_building_id,
|
|
696
|
+
'id': next_id
|
|
697
|
+
}
|
|
698
|
+
|
|
699
|
+
# Add any additional columns
|
|
700
|
+
for col in updated_gdf.columns:
|
|
701
|
+
if col not in new_row_data:
|
|
702
|
+
new_row_data[col] = None
|
|
703
|
+
|
|
704
|
+
# Append the new building in-place
|
|
705
|
+
new_index = len(updated_gdf)
|
|
706
|
+
updated_gdf.loc[new_index] = new_row_data
|
|
707
|
+
|
|
708
|
+
# Add to map
|
|
709
|
+
coords = list(polygon.exterior.coords)
|
|
710
|
+
lat_lon_coords = [(c[1], c[0]) for c in coords[:-1]]
|
|
711
|
+
|
|
712
|
+
new_layer = LeafletPolygon(
|
|
713
|
+
locations=lat_lon_coords,
|
|
714
|
+
color="blue",
|
|
715
|
+
fill_color="blue",
|
|
716
|
+
fill_opacity=0.3,
|
|
717
|
+
weight=2,
|
|
718
|
+
popup=HTML(f"<b>Building ID:</b> {next_building_id}<br>"
|
|
719
|
+
f"<b>ID:</b> {next_id}<br>"
|
|
720
|
+
f"<b>Height:</b> {height_input.value}m<br>"
|
|
721
|
+
f"<b>Min Height:</b> 0.0m")
|
|
722
|
+
)
|
|
723
|
+
m.add_layer(new_layer)
|
|
724
|
+
|
|
725
|
+
# Clear drawing
|
|
726
|
+
draw_control.clear()
|
|
727
|
+
current_polygon['vertices'] = []
|
|
728
|
+
add_button.disabled = True
|
|
729
|
+
clear_button.disabled = True
|
|
730
|
+
|
|
731
|
+
print(f"Building {next_building_id} added successfully!")
|
|
732
|
+
print(f"ID: {next_id}, Height: {height_input.value}m, Min Height: 0.0m")
|
|
733
|
+
print(f"Total buildings: {len(updated_gdf)}")
|
|
734
|
+
|
|
735
|
+
def clear_drawing_click(b):
|
|
736
|
+
"""Handle clear drawing button click"""
|
|
737
|
+
with status_output:
|
|
738
|
+
clear_output()
|
|
739
|
+
draw_control.clear()
|
|
740
|
+
current_polygon['vertices'] = []
|
|
741
|
+
add_button.disabled = True
|
|
742
|
+
clear_button.disabled = True
|
|
743
|
+
print("Drawing cleared")
|
|
744
|
+
|
|
745
|
+
# Connect event handlers
|
|
746
|
+
draw_control.on_draw(handle_draw)
|
|
747
|
+
add_button.on_click(add_building_click)
|
|
748
|
+
clear_button.on_click(clear_drawing_click)
|
|
749
|
+
|
|
750
|
+
# Add draw control to map
|
|
751
|
+
m.add_control(draw_control)
|
|
752
|
+
|
|
753
|
+
# Display initial status
|
|
754
|
+
with status_output:
|
|
755
|
+
print(f"Total buildings loaded: {len(updated_gdf)}")
|
|
756
|
+
print("Draw a polygon to add a new building")
|
|
757
|
+
|
|
758
|
+
return m, updated_gdf
|
|
759
|
+
|
|
760
|
+
|
|
761
|
+
# Simple convenience function
|
|
762
|
+
def create_building_editor(building_gdf=None, initial_center=None, zoom=17):
|
|
763
|
+
"""
|
|
764
|
+
Creates and displays an interactive building editor.
|
|
765
|
+
|
|
766
|
+
Args:
|
|
767
|
+
building_gdf: Existing buildings GeoDataFrame (optional)
|
|
768
|
+
initial_center: Map center as (lon, lat) tuple (optional)
|
|
769
|
+
zoom: Initial zoom level (default=17)
|
|
770
|
+
|
|
771
|
+
Returns:
|
|
772
|
+
GeoDataFrame: The building GeoDataFrame that automatically updates
|
|
773
|
+
|
|
774
|
+
Example:
|
|
775
|
+
>>> buildings = create_building_editor()
|
|
776
|
+
>>> # Draw buildings on the displayed map
|
|
777
|
+
>>> print(buildings) # Automatically contains all drawn buildings
|
|
778
|
+
"""
|
|
779
|
+
m, gdf = draw_additional_buildings(building_gdf, initial_center, zoom)
|
|
780
|
+
display(m)
|
|
781
|
+
return gdf
|
voxcity/geoprocessor/grid.py
CHANGED
|
@@ -412,7 +412,7 @@ def create_land_cover_grid_from_geotiff_polygon(tiff_path, mesh_size, land_cover
|
|
|
412
412
|
# Flip grid vertically to match geographic orientation
|
|
413
413
|
return np.flipud(grid)
|
|
414
414
|
|
|
415
|
-
def create_land_cover_grid_from_gdf_polygon(gdf, meshsize, source, rectangle_vertices):
|
|
415
|
+
def create_land_cover_grid_from_gdf_polygon(gdf, meshsize, source, rectangle_vertices, default_class='Developed space'):
|
|
416
416
|
"""Create a grid of land cover classes from GeoDataFrame polygon data.
|
|
417
417
|
|
|
418
418
|
Args:
|
|
@@ -420,6 +420,8 @@ def create_land_cover_grid_from_gdf_polygon(gdf, meshsize, source, rectangle_ver
|
|
|
420
420
|
meshsize (float): Size of each grid cell in meters
|
|
421
421
|
source (str): Source of the land cover data to determine class priorities
|
|
422
422
|
rectangle_vertices (list): List of 4 (lon,lat) coordinate pairs defining the rectangle bounds
|
|
423
|
+
default_class (str, optional): Default land cover class for cells with no intersecting polygons.
|
|
424
|
+
Defaults to 'Developed space'.
|
|
423
425
|
|
|
424
426
|
Returns:
|
|
425
427
|
numpy.ndarray: 2D grid of land cover classes as strings
|
|
@@ -466,7 +468,7 @@ def create_land_cover_grid_from_gdf_polygon(gdf, meshsize, source, rectangle_ver
|
|
|
466
468
|
print(f"Adjusted mesh size: {adjusted_meshsize}")
|
|
467
469
|
|
|
468
470
|
# Initialize grid with default land cover class
|
|
469
|
-
grid = np.full(grid_size,
|
|
471
|
+
grid = np.full(grid_size, default_class, dtype=object)
|
|
470
472
|
|
|
471
473
|
# Calculate bounding box for spatial indexing
|
|
472
474
|
extent = [min(coord[1] for coord in rectangle_vertices), max(coord[1] for coord in rectangle_vertices),
|
|
@@ -485,7 +487,7 @@ def create_land_cover_grid_from_gdf_polygon(gdf, meshsize, source, rectangle_ver
|
|
|
485
487
|
# Iterate through each grid cell
|
|
486
488
|
for i in range(grid_size[0]):
|
|
487
489
|
for j in range(grid_size[1]):
|
|
488
|
-
land_cover_class =
|
|
490
|
+
land_cover_class = default_class
|
|
489
491
|
cell = create_cell_polygon(origin, i, j, adjusted_meshsize, u_vec, v_vec)
|
|
490
492
|
|
|
491
493
|
# Check intersections with polygons that could overlap this cell
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: voxcity
|
|
3
|
-
Version: 0.5.
|
|
3
|
+
Version: 0.5.29
|
|
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>
|
|
@@ -1,12 +1,12 @@
|
|
|
1
1
|
voxcity/__init__.py,sha256=el9v3gfybHOF_GUYPeSOqN0-vCrTW0eU1mcvi0sEfeU,252
|
|
2
|
-
voxcity/generator.py,sha256=
|
|
2
|
+
voxcity/generator.py,sha256=mEggM4FxE7LChTPCspAQmJlAoUz1PVcbaUfY11cMfzQ,54176
|
|
3
3
|
voxcity/downloader/__init__.py,sha256=o_T_EU7hZLGyXxX9wVWn1x-OAa3ThGYdnpgB1_2v3AE,151
|
|
4
4
|
voxcity/downloader/citygml.py,sha256=jVeHCLlJTf7k55OQGX0lZGQAngz_DD2V5TldSqRFlvc,36024
|
|
5
5
|
voxcity/downloader/eubucco.py,sha256=ln1YNaaOgJfxNfCtVbYaMm775-bUvpAA_LDv60_i22w,17875
|
|
6
6
|
voxcity/downloader/gee.py,sha256=O6HhQnUUumg_tTm4pP_cuyu5YjupDA1uKFxZWxD-i2E,23205
|
|
7
7
|
voxcity/downloader/mbfp.py,sha256=UXDVjsO0fnb0fSal9yqrSFEIBThnRmnutnp08kZTmCA,6595
|
|
8
8
|
voxcity/downloader/oemj.py,sha256=iDacTpiqn7RAXuqyEtHP29m0Cycwta5sMy9-GdvX3Fg,12293
|
|
9
|
-
voxcity/downloader/osm.py,sha256=
|
|
9
|
+
voxcity/downloader/osm.py,sha256=9nOVcVE50N76F5uquJbNIFr8Xajff4ac2Uj2oSGcFrc,42591
|
|
10
10
|
voxcity/downloader/overture.py,sha256=4YG2DMwUSSyZKUw_o8cGhMmAkPJon82aPqOFBvrre-Y,11987
|
|
11
11
|
voxcity/downloader/utils.py,sha256=tz6wt4B9BhEOyvoF5OYXlr8rUd5cBEDedWL3j__oT70,3099
|
|
12
12
|
voxcity/exporter/__init__.py,sha256=dvyWJ184Eik9tFc0VviGbzTQzZi7O0JNyrqi_n39pVI,94
|
|
@@ -15,8 +15,8 @@ voxcity/exporter/envimet.py,sha256=Sh7s1JdQ6SgT_L2Xd_c4gtEGWK2hTS87bccaoIqik-s,3
|
|
|
15
15
|
voxcity/exporter/magicavoxel.py,sha256=SfGEgTZRlossKx3Xrv9d3iKSX-HmfQJEL9lZHgWMDX4,12782
|
|
16
16
|
voxcity/exporter/obj.py,sha256=h1_aInpemcsu96fSTwjKMqX2VZAFYbZbElWd4M1ogyI,27973
|
|
17
17
|
voxcity/geoprocessor/__init__.py,sha256=JzPVhhttxBWvaZ0IGX2w7OWL5bCo_TIvpHefWeNXruA,133
|
|
18
|
-
voxcity/geoprocessor/draw.py,sha256=
|
|
19
|
-
voxcity/geoprocessor/grid.py,sha256=
|
|
18
|
+
voxcity/geoprocessor/draw.py,sha256=t0W7M8ZflJRjXIBuyvszQm_f6r39dK_fBO_7_0dKFAs,33354
|
|
19
|
+
voxcity/geoprocessor/grid.py,sha256=lhELyznlk4Jt7vnd0uOpMCLPCjrYQjX7qtQq-xHkYE4,64161
|
|
20
20
|
voxcity/geoprocessor/mesh.py,sha256=ElqAE2MA8KZs7yD7B1P88XYmryC6F9nkkP6cXv7FzIk,30777
|
|
21
21
|
voxcity/geoprocessor/network.py,sha256=YynqR0nq_NUra_cQ3Z_56KxfRia1b6-hIzGCj3QT-wE,25137
|
|
22
22
|
voxcity/geoprocessor/polygon.py,sha256=-LonxtW5du3UP61oygqtDJl6GGsCYnUuN9KYwl1UFdc,53707
|
|
@@ -30,9 +30,9 @@ voxcity/utils/lc.py,sha256=h2yOWLUIrrummkyMyhRK5VbyrsPtslS0MJov_y0WGIQ,18925
|
|
|
30
30
|
voxcity/utils/material.py,sha256=H8K8Lq4wBL6dQtgj7esUW2U6wLCOTeOtelkTDJoRgMo,10007
|
|
31
31
|
voxcity/utils/visualization.py,sha256=T-jKrCA4UMm93p-1O678RWM7e99iE0_Lj4wD07efcwI,112918
|
|
32
32
|
voxcity/utils/weather.py,sha256=2Jtg-rIVJcsTtiKE-KuDnhIqS1-MSS16_zFRzj6zmu4,36435
|
|
33
|
-
voxcity-0.5.
|
|
34
|
-
voxcity-0.5.
|
|
35
|
-
voxcity-0.5.
|
|
36
|
-
voxcity-0.5.
|
|
37
|
-
voxcity-0.5.
|
|
38
|
-
voxcity-0.5.
|
|
33
|
+
voxcity-0.5.29.dist-info/licenses/AUTHORS.rst,sha256=m82vkI5QokEGdcHof2OxK39lf81w1P58kG9ZNNAKS9U,175
|
|
34
|
+
voxcity-0.5.29.dist-info/licenses/LICENSE,sha256=s_jE1Df1nTPL4A_5GCGic5Zwex0CVaPKcAmSilxJPPE,1089
|
|
35
|
+
voxcity-0.5.29.dist-info/METADATA,sha256=ssD5wLFfzw97c49s0o1_CFpgBaKMeG2AxKzJ2-n3ufY,26724
|
|
36
|
+
voxcity-0.5.29.dist-info/WHEEL,sha256=_zCd3N1l69ArxyTb8rzEoP9TpbYXkqRFSNOD5OuxnTs,91
|
|
37
|
+
voxcity-0.5.29.dist-info/top_level.txt,sha256=00b2U-LKfDllt6RL1R33MXie5MvxzUFye0NGD96t_8I,8
|
|
38
|
+
voxcity-0.5.29.dist-info/RECORD,,
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|