Source code for voxcity.geoprocessor.draw.polygon

"""
Building footprint display and polygon drawing utilities.

Provides:
- display_buildings_and_draw_polygon: Visualise buildings and draw polygons
- get_polygon_vertices: Extract vertices from drawn polygons
"""

from __future__ import annotations

import shapely.geometry as geom
from ipyleaflet import (
    Map,
    DrawControl,
    Polygon as LeafletPolygon,
)

# Import VoxCity for type checking (avoid circular import with TYPE_CHECKING)
try:
    from typing import TYPE_CHECKING

    if TYPE_CHECKING:
        from ...models import VoxCity
except ImportError:
    pass


[docs] def display_buildings_and_draw_polygon( voxcity=None, building_gdf=None, rectangle_vertices=None, zoom=17, ): """ Display building footprints and enable polygon drawing on an interactive map. Args: voxcity (VoxCity, optional): VoxCity object to extract data from. building_gdf (GeoDataFrame, optional): Building footprints. rectangle_vertices (list, optional): [lon, lat] rectangle corners. zoom (int): Initial zoom level. Default=17. Returns: tuple: (Map, drawn_polygons list of dicts with 'id', 'vertices', 'color') """ # Extract data from VoxCity if provided if voxcity is not None: if building_gdf is None: building_gdf = voxcity.extras.get("building_gdf", None) if rectangle_vertices is None: rectangle_vertices = voxcity.extras.get("rectangle_vertices", None) # Determine map center if rectangle_vertices is not None: lons = [v[0] for v in rectangle_vertices] lats = [v[1] for v in rectangle_vertices] center_lon = (min(lons) + max(lons)) / 2 center_lat = (min(lats) + max(lats)) / 2 elif building_gdf is not None and len(building_gdf) > 0: bounds = building_gdf.total_bounds center_lon = (bounds[0] + bounds[2]) / 2 center_lat = (bounds[1] + bounds[3]) / 2 else: center_lon, center_lat = -100.0, 40.0 m = Map(center=(center_lat, center_lon), zoom=zoom, scroll_wheel_zoom=True) # Add building footprints if building_gdf is not None: for _idx, row in building_gdf.iterrows(): if isinstance(row.geometry, geom.Polygon): coords = list(row.geometry.exterior.coords) lat_lon_coords = [(c[1], c[0]) for c in coords[:-1]] bldg_layer = LeafletPolygon( locations=lat_lon_coords, color="blue", fill_color="blue", fill_opacity=0.2, weight=2, ) m.add_layer(bldg_layer) # Polygon drawing drawn_polygons: list[dict] = [] polygon_counter = 0 polygon_colors = [ "red", "blue", "green", "orange", "purple", "brown", "pink", "gray", "olive", "cyan", ] draw_control = DrawControl( polygon={ "shapeOptions": {"color": "red", "fillColor": "red", "fillOpacity": 0.2} }, rectangle={}, circle={}, circlemarker={}, polyline={}, marker={}, ) def handle_draw(self, action, geo_json): if action == "created" and geo_json["geometry"]["type"] == "Polygon": nonlocal polygon_counter polygon_counter += 1 coordinates = geo_json["geometry"]["coordinates"][0] vertices = [(coord[0], coord[1]) for coord in coordinates[:-1]] color = polygon_colors[polygon_counter % len(polygon_colors)] polygon_data = {"id": polygon_counter, "vertices": vertices, "color": color} drawn_polygons.append(polygon_data) print( f"Polygon {polygon_counter} drawn with {len(vertices)} vertices " f"(color: {color}):" ) for i, (lon, lat) in enumerate(vertices): print(f" Vertex {i + 1}: (lon, lat) = ({lon}, {lat})") print(f"Total polygons: {len(drawn_polygons)}") draw_control.on_draw(handle_draw) m.add_control(draw_control) return m, drawn_polygons
[docs] def get_polygon_vertices(drawn_polygons, polygon_id=None): """ Extract vertices from drawn polygons data structure. Args: drawn_polygons: List returned from display_buildings_and_draw_polygon(). polygon_id (int, optional): Specific polygon ID. If None, returns all. Returns: List of (lon, lat) tuples for the specified polygon, or list of lists for all. """ if not drawn_polygons: return [] if polygon_id is not None: for polygon in drawn_polygons: if polygon["id"] == polygon_id: return polygon["vertices"] return [] return [polygon["vertices"] for polygon in drawn_polygons]