voxcity 0.5.28__py3-none-any.whl → 0.5.30__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/geoprocessor/draw.py +103 -30
- voxcity/geoprocessor/polygon.py +1344 -1178
- {voxcity-0.5.28.dist-info → voxcity-0.5.30.dist-info}/METADATA +1 -1
- {voxcity-0.5.28.dist-info → voxcity-0.5.30.dist-info}/RECORD +8 -8
- {voxcity-0.5.28.dist-info → voxcity-0.5.30.dist-info}/WHEEL +0 -0
- {voxcity-0.5.28.dist-info → voxcity-0.5.30.dist-info}/licenses/AUTHORS.rst +0 -0
- {voxcity-0.5.28.dist-info → voxcity-0.5.30.dist-info}/licenses/LICENSE +0 -0
- {voxcity-0.5.28.dist-info → voxcity-0.5.30.dist-info}/top_level.txt +0 -0
voxcity/geoprocessor/draw.py
CHANGED
|
@@ -372,6 +372,7 @@ def display_buildings_and_draw_polygon(building_gdf=None, rectangle_vertices=Non
|
|
|
372
372
|
- Enables free-form polygon drawing
|
|
373
373
|
- Captures vertices in consistent (lon,lat) format
|
|
374
374
|
- Maintains GeoJSON compatibility
|
|
375
|
+
- Supports multiple polygons with unique IDs and colors
|
|
375
376
|
|
|
376
377
|
3. Map Initialization:
|
|
377
378
|
- Automatic centering based on input data
|
|
@@ -391,10 +392,10 @@ def display_buildings_and_draw_polygon(building_gdf=None, rectangle_vertices=Non
|
|
|
391
392
|
Default of 17 is optimized for building-level detail.
|
|
392
393
|
|
|
393
394
|
Returns:
|
|
394
|
-
tuple: (map_object,
|
|
395
|
+
tuple: (map_object, drawn_polygons)
|
|
395
396
|
- map_object: ipyleaflet Map instance with building footprints and drawing controls
|
|
396
|
-
-
|
|
397
|
-
|
|
397
|
+
- drawn_polygons: List of dictionaries with 'id', 'vertices', and 'color' keys for all drawn polygons.
|
|
398
|
+
Each polygon has a unique ID and color for easy identification.
|
|
398
399
|
|
|
399
400
|
Note:
|
|
400
401
|
- Building footprints are displayed in blue with 20% opacity
|
|
@@ -402,6 +403,8 @@ def display_buildings_and_draw_polygon(building_gdf=None, rectangle_vertices=Non
|
|
|
402
403
|
- Drawing tools are restricted to polygon creation only
|
|
403
404
|
- All coordinates are handled in (lon,lat) order internally
|
|
404
405
|
- The function automatically determines appropriate map bounds
|
|
406
|
+
- Each polygon gets a unique ID and different colors for easy identification
|
|
407
|
+
- Use get_polygon_vertices() helper function to extract specific polygon data
|
|
405
408
|
"""
|
|
406
409
|
# ---------------------------------------------------------
|
|
407
410
|
# 1. Determine a suitable map center via bounding box logic
|
|
@@ -452,7 +455,10 @@ def display_buildings_and_draw_polygon(building_gdf=None, rectangle_vertices=Non
|
|
|
452
455
|
# -----------------------------------------------------------------
|
|
453
456
|
# 3. Enable drawing of polygons, capturing the vertices in Lon-Lat
|
|
454
457
|
# -----------------------------------------------------------------
|
|
455
|
-
|
|
458
|
+
# Store multiple polygons with IDs and colors
|
|
459
|
+
drawn_polygons = [] # List of dicts with 'id', 'vertices', 'color' keys
|
|
460
|
+
polygon_counter = 0
|
|
461
|
+
polygon_colors = ['red', 'blue', 'green', 'orange', 'purple', 'brown', 'pink', 'gray', 'olive', 'cyan']
|
|
456
462
|
|
|
457
463
|
draw_control = DrawControl(
|
|
458
464
|
polygon={
|
|
@@ -475,27 +481,36 @@ def display_buildings_and_draw_polygon(building_gdf=None, rectangle_vertices=Non
|
|
|
475
481
|
ipyleaflet's DrawControl returns standard GeoJSON (lon, lat).
|
|
476
482
|
We'll keep them as (lon, lat).
|
|
477
483
|
"""
|
|
478
|
-
# Clear any previously stored vertices
|
|
479
|
-
drawn_polygon_vertices.clear()
|
|
480
|
-
|
|
481
484
|
if action == 'created' and geo_json['geometry']['type'] == 'Polygon':
|
|
485
|
+
nonlocal polygon_counter
|
|
486
|
+
polygon_counter += 1
|
|
487
|
+
|
|
482
488
|
# The polygon's first ring
|
|
483
489
|
coordinates = geo_json['geometry']['coordinates'][0]
|
|
484
|
-
|
|
485
|
-
|
|
486
|
-
#
|
|
487
|
-
|
|
488
|
-
|
|
489
|
-
|
|
490
|
-
|
|
491
|
-
|
|
490
|
+
vertices = [(coord[0], coord[1]) for coord in coordinates[:-1]]
|
|
491
|
+
|
|
492
|
+
# Assign color (cycle through colors)
|
|
493
|
+
color = polygon_colors[polygon_counter % len(polygon_colors)]
|
|
494
|
+
|
|
495
|
+
# Store polygon data
|
|
496
|
+
polygon_data = {
|
|
497
|
+
'id': polygon_counter,
|
|
498
|
+
'vertices': vertices,
|
|
499
|
+
'color': color
|
|
500
|
+
}
|
|
501
|
+
drawn_polygons.append(polygon_data)
|
|
502
|
+
|
|
503
|
+
print(f"Polygon {polygon_counter} drawn with {len(vertices)} vertices (color: {color}):")
|
|
504
|
+
for i, (lon, lat) in enumerate(vertices):
|
|
505
|
+
print(f" Vertex {i+1}: (lon, lat) = ({lon}, {lat})")
|
|
506
|
+
print(f"Total polygons: {len(drawn_polygons)}")
|
|
492
507
|
|
|
493
508
|
draw_control.on_draw(handle_draw)
|
|
494
509
|
m.add_control(draw_control)
|
|
495
510
|
|
|
496
|
-
return m,
|
|
511
|
+
return m, drawn_polygons
|
|
497
512
|
|
|
498
|
-
def draw_additional_buildings(building_gdf=None, initial_center=None, zoom=17):
|
|
513
|
+
def draw_additional_buildings(building_gdf=None, initial_center=None, zoom=17, rectangle_vertices=None):
|
|
499
514
|
"""
|
|
500
515
|
Creates an interactive map for drawing building footprints with height input.
|
|
501
516
|
|
|
@@ -513,7 +528,12 @@ def draw_additional_buildings(building_gdf=None, initial_center=None, zoom=17):
|
|
|
513
528
|
Args:
|
|
514
529
|
building_gdf (GeoDataFrame, optional): Existing building footprints to display.
|
|
515
530
|
If None, creates a new empty GeoDataFrame.
|
|
516
|
-
|
|
531
|
+
Expected columns: ['id', 'height', 'min_height', 'geometry', 'building_id']
|
|
532
|
+
- 'id': Integer ID from data sources (e.g., OSM building id)
|
|
533
|
+
- 'height': Building height in meters (set by user input)
|
|
534
|
+
- 'min_height': Minimum height in meters (defaults to 0.0)
|
|
535
|
+
- 'geometry': Building footprint polygon
|
|
536
|
+
- 'building_id': Unique building identifier
|
|
517
537
|
initial_center (tuple, optional): Initial map center as (lon, lat).
|
|
518
538
|
If None, centers on existing buildings or defaults to (-100, 40).
|
|
519
539
|
zoom (int): Initial zoom level (default=17).
|
|
@@ -534,16 +554,21 @@ def draw_additional_buildings(building_gdf=None, initial_center=None, zoom=17):
|
|
|
534
554
|
if building_gdf is None:
|
|
535
555
|
# Create empty GeoDataFrame with required columns
|
|
536
556
|
updated_gdf = gpd.GeoDataFrame(
|
|
537
|
-
columns=['
|
|
557
|
+
columns=['id', 'height', 'min_height', 'geometry', 'building_id'],
|
|
538
558
|
crs='EPSG:4326'
|
|
539
559
|
)
|
|
540
560
|
else:
|
|
541
561
|
# Make a copy to avoid modifying the original
|
|
542
562
|
updated_gdf = building_gdf.copy()
|
|
563
|
+
# Ensure all required columns exist
|
|
543
564
|
if 'height' not in updated_gdf.columns:
|
|
544
565
|
updated_gdf['height'] = 10.0 # Default height
|
|
566
|
+
if 'min_height' not in updated_gdf.columns:
|
|
567
|
+
updated_gdf['min_height'] = 0.0 # Default min_height
|
|
545
568
|
if 'building_id' not in updated_gdf.columns:
|
|
546
569
|
updated_gdf['building_id'] = range(len(updated_gdf))
|
|
570
|
+
if 'id' not in updated_gdf.columns:
|
|
571
|
+
updated_gdf['id'] = range(len(updated_gdf))
|
|
547
572
|
|
|
548
573
|
# Determine map center
|
|
549
574
|
if initial_center is not None:
|
|
@@ -553,6 +578,8 @@ def draw_additional_buildings(building_gdf=None, initial_center=None, zoom=17):
|
|
|
553
578
|
min_lon, min_lat, max_lon, max_lat = bounds
|
|
554
579
|
center_lon = (min_lon + max_lon) / 2
|
|
555
580
|
center_lat = (min_lat + max_lat) / 2
|
|
581
|
+
elif rectangle_vertices is not None:
|
|
582
|
+
center_lon, center_lat = (rectangle_vertices[0][0] + rectangle_vertices[2][0]) / 2, (rectangle_vertices[0][1] + rectangle_vertices[2][1]) / 2
|
|
556
583
|
else:
|
|
557
584
|
center_lon, center_lat = -100.0, 40.0
|
|
558
585
|
|
|
@@ -567,14 +594,19 @@ def draw_additional_buildings(building_gdf=None, initial_center=None, zoom=17):
|
|
|
567
594
|
lat_lon_coords = [(c[1], c[0]) for c in coords[:-1]]
|
|
568
595
|
|
|
569
596
|
height = row.get('height', 10.0)
|
|
597
|
+
min_height = row.get('min_height', 0.0)
|
|
598
|
+
building_id = row.get('building_id', idx)
|
|
599
|
+
bldg_id = row.get('id', idx)
|
|
570
600
|
bldg_layer = LeafletPolygon(
|
|
571
601
|
locations=lat_lon_coords,
|
|
572
602
|
color="blue",
|
|
573
603
|
fill_color="blue",
|
|
574
604
|
fill_opacity=0.3,
|
|
575
605
|
weight=2,
|
|
576
|
-
popup=HTML(f"<b>Building ID:</b> {
|
|
577
|
-
f"<b>
|
|
606
|
+
popup=HTML(f"<b>Building ID:</b> {building_id}<br>"
|
|
607
|
+
f"<b>ID:</b> {bldg_id}<br>"
|
|
608
|
+
f"<b>Height:</b> {height}m<br>"
|
|
609
|
+
f"<b>Min Height:</b> {min_height}m")
|
|
578
610
|
)
|
|
579
611
|
m.add_layer(bldg_layer)
|
|
580
612
|
building_layers[idx] = bldg_layer
|
|
@@ -664,17 +696,21 @@ def draw_additional_buildings(building_gdf=None, initial_center=None, zoom=17):
|
|
|
664
696
|
# Create polygon geometry
|
|
665
697
|
polygon = geom.Polygon(current_polygon['vertices'])
|
|
666
698
|
|
|
667
|
-
# Get next building ID
|
|
699
|
+
# Get next building ID and ID values (ensure uniqueness)
|
|
668
700
|
if len(updated_gdf) > 0:
|
|
669
|
-
|
|
701
|
+
next_building_id = int(updated_gdf['building_id'].max() + 1)
|
|
702
|
+
next_id = int(updated_gdf['id'].max() + 1)
|
|
670
703
|
else:
|
|
704
|
+
next_building_id = 1
|
|
671
705
|
next_id = 1
|
|
672
706
|
|
|
673
707
|
# Create new row data
|
|
674
708
|
new_row_data = {
|
|
675
709
|
'geometry': polygon,
|
|
676
710
|
'height': float(height_input.value),
|
|
677
|
-
'
|
|
711
|
+
'min_height': 0.0, # Default value as requested
|
|
712
|
+
'building_id': next_building_id,
|
|
713
|
+
'id': next_id
|
|
678
714
|
}
|
|
679
715
|
|
|
680
716
|
# Add any additional columns
|
|
@@ -696,8 +732,10 @@ def draw_additional_buildings(building_gdf=None, initial_center=None, zoom=17):
|
|
|
696
732
|
fill_color="blue",
|
|
697
733
|
fill_opacity=0.3,
|
|
698
734
|
weight=2,
|
|
699
|
-
popup=HTML(f"<b>Building ID:</b> {
|
|
700
|
-
f"<b>
|
|
735
|
+
popup=HTML(f"<b>Building ID:</b> {next_building_id}<br>"
|
|
736
|
+
f"<b>ID:</b> {next_id}<br>"
|
|
737
|
+
f"<b>Height:</b> {height_input.value}m<br>"
|
|
738
|
+
f"<b>Min Height:</b> 0.0m")
|
|
701
739
|
)
|
|
702
740
|
m.add_layer(new_layer)
|
|
703
741
|
|
|
@@ -707,8 +745,8 @@ def draw_additional_buildings(building_gdf=None, initial_center=None, zoom=17):
|
|
|
707
745
|
add_button.disabled = True
|
|
708
746
|
clear_button.disabled = True
|
|
709
747
|
|
|
710
|
-
print(f"Building {
|
|
711
|
-
print(f"Height: {height_input.value}m")
|
|
748
|
+
print(f"Building {next_building_id} added successfully!")
|
|
749
|
+
print(f"ID: {next_id}, Height: {height_input.value}m, Min Height: 0.0m")
|
|
712
750
|
print(f"Total buildings: {len(updated_gdf)}")
|
|
713
751
|
|
|
714
752
|
def clear_drawing_click(b):
|
|
@@ -737,8 +775,43 @@ def draw_additional_buildings(building_gdf=None, initial_center=None, zoom=17):
|
|
|
737
775
|
return m, updated_gdf
|
|
738
776
|
|
|
739
777
|
|
|
778
|
+
def get_polygon_vertices(drawn_polygons, polygon_id=None):
|
|
779
|
+
"""
|
|
780
|
+
Extract vertices from drawn polygons data structure.
|
|
781
|
+
|
|
782
|
+
This helper function provides a convenient way to extract polygon vertices
|
|
783
|
+
from the drawn_polygons list returned by display_buildings_and_draw_polygon().
|
|
784
|
+
|
|
785
|
+
Args:
|
|
786
|
+
drawn_polygons: The drawn_polygons list returned from display_buildings_and_draw_polygon()
|
|
787
|
+
polygon_id (int, optional): Specific polygon ID to extract. If None, returns all polygons.
|
|
788
|
+
|
|
789
|
+
Returns:
|
|
790
|
+
If polygon_id is specified: List of (lon, lat) tuples for that polygon
|
|
791
|
+
If polygon_id is None: List of lists, where each inner list contains (lon, lat) tuples
|
|
792
|
+
|
|
793
|
+
Example:
|
|
794
|
+
>>> m, polygons = display_buildings_and_draw_polygon()
|
|
795
|
+
>>> # Draw some polygons...
|
|
796
|
+
>>> vertices = get_polygon_vertices(polygons, polygon_id=1) # Get polygon 1
|
|
797
|
+
>>> all_vertices = get_polygon_vertices(polygons) # Get all polygons
|
|
798
|
+
"""
|
|
799
|
+
if not drawn_polygons:
|
|
800
|
+
return []
|
|
801
|
+
|
|
802
|
+
if polygon_id is not None:
|
|
803
|
+
# Return specific polygon
|
|
804
|
+
for polygon in drawn_polygons:
|
|
805
|
+
if polygon['id'] == polygon_id:
|
|
806
|
+
return polygon['vertices']
|
|
807
|
+
return [] # Polygon not found
|
|
808
|
+
else:
|
|
809
|
+
# Return all polygons
|
|
810
|
+
return [polygon['vertices'] for polygon in drawn_polygons]
|
|
811
|
+
|
|
812
|
+
|
|
740
813
|
# Simple convenience function
|
|
741
|
-
def create_building_editor(building_gdf=None, initial_center=None, zoom=17):
|
|
814
|
+
def create_building_editor(building_gdf=None, initial_center=None, zoom=17, rectangle_vertices=None):
|
|
742
815
|
"""
|
|
743
816
|
Creates and displays an interactive building editor.
|
|
744
817
|
|
|
@@ -755,6 +828,6 @@ def create_building_editor(building_gdf=None, initial_center=None, zoom=17):
|
|
|
755
828
|
>>> # Draw buildings on the displayed map
|
|
756
829
|
>>> print(buildings) # Automatically contains all drawn buildings
|
|
757
830
|
"""
|
|
758
|
-
m, gdf = draw_additional_buildings(building_gdf, initial_center, zoom)
|
|
831
|
+
m, gdf = draw_additional_buildings(building_gdf, initial_center, zoom, rectangle_vertices)
|
|
759
832
|
display(m)
|
|
760
833
|
return gdf
|