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.

@@ -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, drawn_polygon_vertices)
395
+ tuple: (map_object, drawn_polygons)
395
396
  - map_object: ipyleaflet Map instance with building footprints and drawing controls
396
- - drawn_polygon_vertices: List that gets updated with (lon,lat) coordinates
397
- whenever a new polygon is drawn. Coordinates are in GeoJSON order.
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
- drawn_polygon_vertices = [] # We'll store the newly drawn polygon's vertices here (lon, lat).
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
- print("Vertices of the drawn polygon (Lon-Lat):")
485
-
486
- # Keep GeoJSON (lon,lat) format, skip last repeated coordinate
487
- for coord in coordinates[:-1]:
488
- lon = coord[0]
489
- lat = coord[1]
490
- drawn_polygon_vertices.append((lon, lat))
491
- print(f" - (lon, lat) = ({lon}, {lat})")
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, drawn_polygon_vertices
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
- Must have 'geometry' column and optionally 'height' column.
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=['geometry', 'height', 'building_id'],
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> {row.get('building_id', idx)}<br>"
577
- f"<b>Height:</b> {height}m")
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
- next_id = int(updated_gdf['building_id'].max() + 1)
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
- 'building_id': next_id
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> {next_id}<br>"
700
- f"<b>Height:</b> {height_input.value}m")
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 {next_id} added successfully!")
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