voxcity 0.3.1__tar.gz → 0.3.3__tar.gz
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-0.3.1 → voxcity-0.3.3}/PKG-INFO +57 -1
- {voxcity-0.3.1 → voxcity-0.3.3}/README.md +56 -0
- {voxcity-0.3.1 → voxcity-0.3.3}/pyproject.toml +1 -1
- {voxcity-0.3.1 → voxcity-0.3.3}/src/voxcity/file/geojson.py +72 -1
- {voxcity-0.3.1 → voxcity-0.3.3}/src/voxcity/geo/draw.py +112 -2
- {voxcity-0.3.1 → voxcity-0.3.3}/src/voxcity/sim/solar.py +293 -69
- {voxcity-0.3.1 → voxcity-0.3.3}/src/voxcity/sim/view.py +184 -32
- {voxcity-0.3.1 → voxcity-0.3.3}/src/voxcity.egg-info/PKG-INFO +57 -1
- {voxcity-0.3.1 → voxcity-0.3.3}/AUTHORS.rst +0 -0
- {voxcity-0.3.1 → voxcity-0.3.3}/CONTRIBUTING.rst +0 -0
- {voxcity-0.3.1 → voxcity-0.3.3}/HISTORY.rst +0 -0
- {voxcity-0.3.1 → voxcity-0.3.3}/LICENSE +0 -0
- {voxcity-0.3.1 → voxcity-0.3.3}/MANIFEST.in +0 -0
- {voxcity-0.3.1 → voxcity-0.3.3}/docs/Makefile +0 -0
- {voxcity-0.3.1 → voxcity-0.3.3}/docs/archive/README.rst +0 -0
- {voxcity-0.3.1 → voxcity-0.3.3}/docs/authors.rst +0 -0
- {voxcity-0.3.1 → voxcity-0.3.3}/docs/conf.py +0 -0
- {voxcity-0.3.1 → voxcity-0.3.3}/docs/index.rst +0 -0
- {voxcity-0.3.1 → voxcity-0.3.3}/docs/make.bat +0 -0
- {voxcity-0.3.1 → voxcity-0.3.3}/setup.cfg +0 -0
- {voxcity-0.3.1 → voxcity-0.3.3}/src/voxcity/__init__.py +0 -0
- {voxcity-0.3.1 → voxcity-0.3.3}/src/voxcity/download/__init__.py +0 -0
- {voxcity-0.3.1 → voxcity-0.3.3}/src/voxcity/download/eubucco.py +0 -0
- {voxcity-0.3.1 → voxcity-0.3.3}/src/voxcity/download/gee.py +0 -0
- {voxcity-0.3.1 → voxcity-0.3.3}/src/voxcity/download/mbfp.py +0 -0
- {voxcity-0.3.1 → voxcity-0.3.3}/src/voxcity/download/oemj.py +0 -0
- {voxcity-0.3.1 → voxcity-0.3.3}/src/voxcity/download/omt.py +0 -0
- {voxcity-0.3.1 → voxcity-0.3.3}/src/voxcity/download/osm.py +0 -0
- {voxcity-0.3.1 → voxcity-0.3.3}/src/voxcity/download/overture.py +0 -0
- {voxcity-0.3.1 → voxcity-0.3.3}/src/voxcity/download/utils.py +0 -0
- {voxcity-0.3.1 → voxcity-0.3.3}/src/voxcity/file/__init_.py +0 -0
- {voxcity-0.3.1 → voxcity-0.3.3}/src/voxcity/file/envimet.py +0 -0
- {voxcity-0.3.1 → voxcity-0.3.3}/src/voxcity/file/magicavoxel.py +0 -0
- {voxcity-0.3.1 → voxcity-0.3.3}/src/voxcity/file/obj.py +0 -0
- {voxcity-0.3.1 → voxcity-0.3.3}/src/voxcity/geo/__init_.py +0 -0
- {voxcity-0.3.1 → voxcity-0.3.3}/src/voxcity/geo/grid.py +0 -0
- {voxcity-0.3.1 → voxcity-0.3.3}/src/voxcity/geo/utils.py +0 -0
- {voxcity-0.3.1 → voxcity-0.3.3}/src/voxcity/sim/__init_.py +0 -0
- {voxcity-0.3.1 → voxcity-0.3.3}/src/voxcity/sim/utils.py +0 -0
- {voxcity-0.3.1 → voxcity-0.3.3}/src/voxcity/utils/__init_.py +0 -0
- {voxcity-0.3.1 → voxcity-0.3.3}/src/voxcity/utils/lc.py +0 -0
- {voxcity-0.3.1 → voxcity-0.3.3}/src/voxcity/utils/visualization.py +0 -0
- {voxcity-0.3.1 → voxcity-0.3.3}/src/voxcity/utils/weather.py +0 -0
- {voxcity-0.3.1 → voxcity-0.3.3}/src/voxcity/voxcity.py +0 -0
- {voxcity-0.3.1 → voxcity-0.3.3}/src/voxcity.egg-info/SOURCES.txt +0 -0
- {voxcity-0.3.1 → voxcity-0.3.3}/src/voxcity.egg-info/dependency_links.txt +0 -0
- {voxcity-0.3.1 → voxcity-0.3.3}/src/voxcity.egg-info/requires.txt +0 -0
- {voxcity-0.3.1 → voxcity-0.3.3}/src/voxcity.egg-info/top_level.txt +0 -0
- {voxcity-0.3.1 → voxcity-0.3.3}/tests/__init__.py +0 -0
- {voxcity-0.3.1 → voxcity-0.3.3}/tests/voxelcity.py +0 -0
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.1
|
|
2
2
|
Name: voxcity
|
|
3
|
-
Version: 0.3.
|
|
3
|
+
Version: 0.3.3
|
|
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>
|
|
@@ -305,6 +305,62 @@ export_magicavoxel_vox(voxcity_grid, output_path, base_filename=base_filename)
|
|
|
305
305
|
|
|
306
306
|
### 6. Additional Use Cases
|
|
307
307
|
|
|
308
|
+
#### Compute Solar Irradiance:
|
|
309
|
+
|
|
310
|
+
```python
|
|
311
|
+
from voxcity.sim.solar import get_global_solar_irradiance_using_epw
|
|
312
|
+
|
|
313
|
+
solar_kwargs = {
|
|
314
|
+
"download_nearest_epw": True, # Whether to automatically download nearest EPW weather file based on location from Climate.OneBuilding.Org
|
|
315
|
+
"rectangle_vertices": rectangle_vertices, # Coordinates defining the area of interest for calculation
|
|
316
|
+
# "epw_file_path": "./output/new.york-downtown.manhattan.heli_ny_usa_1.epw", # Path to EnergyPlus Weather (EPW) file containing climate data. Set if you already have an EPW file.
|
|
317
|
+
"calc_time": "01-01 12:00:00", # Time for instantaneous calculation in format "MM-DD HH:MM:SS"
|
|
318
|
+
"view_point_height": 1.5, # Height of view point in meters for calculating solar access. Default: 1.5 m
|
|
319
|
+
"tree_k": 0.6, # Static extinction coefficient - controls how much sunlight is blocked by trees (higher = more blocking)
|
|
320
|
+
"tree_lad": 1.0, # Leaf area density of trees - density of leaves/branches that affect shading (higher = denser foliage)
|
|
321
|
+
"dem_grid": dem_grid, # Digital elevation model grid for terrain heights
|
|
322
|
+
"colormap": 'magma', # Matplotlib colormap for visualization. Default: 'viridis'
|
|
323
|
+
"obj_export": True, # Whether to export results as 3D OBJ file
|
|
324
|
+
"output_directory": 'output/test', # Directory for saving output files
|
|
325
|
+
"output_file_name": 'instantaneous_solar_irradiance', # Base filename for outputs (without extension)
|
|
326
|
+
"alpha": 1.0, # Transparency of visualization (0.0-1.0)
|
|
327
|
+
"vmin": 0, # Minimum value for colormap scaling in visualization
|
|
328
|
+
# "vmax": 900, # Maximum value for colormap scaling in visualization
|
|
329
|
+
}
|
|
330
|
+
|
|
331
|
+
# Compute global solar irradiance map (direct + diffuse radiation)
|
|
332
|
+
global_map = get_global_solar_irradiance_using_epw(
|
|
333
|
+
voxcity_grid, # 3D voxel grid representing the urban environment
|
|
334
|
+
meshsize, # Size of each voxel in meters
|
|
335
|
+
calc_type='instantaneous', # Calculate instantaneous irradiance at specified time
|
|
336
|
+
direct_normal_irradiance_scaling=1.0, # Scaling factor for direct solar radiation (1.0 = no scaling)
|
|
337
|
+
diffuse_irradiance_scaling=1.0, # Scaling factor for diffuse solar radiation (1.0 = no scaling)
|
|
338
|
+
**solar_kwargs # Pass all the parameters defined above
|
|
339
|
+
)
|
|
340
|
+
|
|
341
|
+
# Adjust parameters for cumulative calculation
|
|
342
|
+
solar_kwargs["start_time"] = "01-01 01:00:00" # Start time for cumulative calculation
|
|
343
|
+
solar_kwargs["end_time"] = "01-31 23:00:00" # End time for cumulative calculation
|
|
344
|
+
solar_kwargs["output_file_name"] = 'cummulative_solar_irradiance', # Base filename for outputs (without extension)
|
|
345
|
+
|
|
346
|
+
# Calculate cumulative solar irradiance over the specified time period
|
|
347
|
+
global_map = get_global_solar_irradiance_using_epw(
|
|
348
|
+
voxcity_grid, # 3D voxel grid representing the urban environment
|
|
349
|
+
meshsize, # Size of each voxel in meters
|
|
350
|
+
calc_type='cumulative', # Calculate cumulative irradiance over time period instead of instantaneous
|
|
351
|
+
direct_normal_irradiance_scaling=1.0, # Scaling factor for direct solar radiation (1.0 = no scaling)
|
|
352
|
+
diffuse_irradiance_scaling=1.0, # Scaling factor for diffuse solar radiation (1.0 = no scaling)
|
|
353
|
+
**solar_kwargs # Pass all the parameters defined above
|
|
354
|
+
)
|
|
355
|
+
```
|
|
356
|
+
|
|
357
|
+
<p align="center">
|
|
358
|
+
<img src="https://raw.githubusercontent.com/kunifujiwara/VoxCity/main/images/solar.png" alt="Solar Irradiance Maps Rendered in Rhino" width="800">
|
|
359
|
+
</p>
|
|
360
|
+
<p align="center">
|
|
361
|
+
<em>Example Results Saved as OBJ and Rendered in Rhino</em>
|
|
362
|
+
</p>
|
|
363
|
+
|
|
308
364
|
#### Compute Green View Index (GVI) and Sky View Index (SVI):
|
|
309
365
|
|
|
310
366
|
```python
|
|
@@ -249,6 +249,62 @@ export_magicavoxel_vox(voxcity_grid, output_path, base_filename=base_filename)
|
|
|
249
249
|
|
|
250
250
|
### 6. Additional Use Cases
|
|
251
251
|
|
|
252
|
+
#### Compute Solar Irradiance:
|
|
253
|
+
|
|
254
|
+
```python
|
|
255
|
+
from voxcity.sim.solar import get_global_solar_irradiance_using_epw
|
|
256
|
+
|
|
257
|
+
solar_kwargs = {
|
|
258
|
+
"download_nearest_epw": True, # Whether to automatically download nearest EPW weather file based on location from Climate.OneBuilding.Org
|
|
259
|
+
"rectangle_vertices": rectangle_vertices, # Coordinates defining the area of interest for calculation
|
|
260
|
+
# "epw_file_path": "./output/new.york-downtown.manhattan.heli_ny_usa_1.epw", # Path to EnergyPlus Weather (EPW) file containing climate data. Set if you already have an EPW file.
|
|
261
|
+
"calc_time": "01-01 12:00:00", # Time for instantaneous calculation in format "MM-DD HH:MM:SS"
|
|
262
|
+
"view_point_height": 1.5, # Height of view point in meters for calculating solar access. Default: 1.5 m
|
|
263
|
+
"tree_k": 0.6, # Static extinction coefficient - controls how much sunlight is blocked by trees (higher = more blocking)
|
|
264
|
+
"tree_lad": 1.0, # Leaf area density of trees - density of leaves/branches that affect shading (higher = denser foliage)
|
|
265
|
+
"dem_grid": dem_grid, # Digital elevation model grid for terrain heights
|
|
266
|
+
"colormap": 'magma', # Matplotlib colormap for visualization. Default: 'viridis'
|
|
267
|
+
"obj_export": True, # Whether to export results as 3D OBJ file
|
|
268
|
+
"output_directory": 'output/test', # Directory for saving output files
|
|
269
|
+
"output_file_name": 'instantaneous_solar_irradiance', # Base filename for outputs (without extension)
|
|
270
|
+
"alpha": 1.0, # Transparency of visualization (0.0-1.0)
|
|
271
|
+
"vmin": 0, # Minimum value for colormap scaling in visualization
|
|
272
|
+
# "vmax": 900, # Maximum value for colormap scaling in visualization
|
|
273
|
+
}
|
|
274
|
+
|
|
275
|
+
# Compute global solar irradiance map (direct + diffuse radiation)
|
|
276
|
+
global_map = get_global_solar_irradiance_using_epw(
|
|
277
|
+
voxcity_grid, # 3D voxel grid representing the urban environment
|
|
278
|
+
meshsize, # Size of each voxel in meters
|
|
279
|
+
calc_type='instantaneous', # Calculate instantaneous irradiance at specified time
|
|
280
|
+
direct_normal_irradiance_scaling=1.0, # Scaling factor for direct solar radiation (1.0 = no scaling)
|
|
281
|
+
diffuse_irradiance_scaling=1.0, # Scaling factor for diffuse solar radiation (1.0 = no scaling)
|
|
282
|
+
**solar_kwargs # Pass all the parameters defined above
|
|
283
|
+
)
|
|
284
|
+
|
|
285
|
+
# Adjust parameters for cumulative calculation
|
|
286
|
+
solar_kwargs["start_time"] = "01-01 01:00:00" # Start time for cumulative calculation
|
|
287
|
+
solar_kwargs["end_time"] = "01-31 23:00:00" # End time for cumulative calculation
|
|
288
|
+
solar_kwargs["output_file_name"] = 'cummulative_solar_irradiance', # Base filename for outputs (without extension)
|
|
289
|
+
|
|
290
|
+
# Calculate cumulative solar irradiance over the specified time period
|
|
291
|
+
global_map = get_global_solar_irradiance_using_epw(
|
|
292
|
+
voxcity_grid, # 3D voxel grid representing the urban environment
|
|
293
|
+
meshsize, # Size of each voxel in meters
|
|
294
|
+
calc_type='cumulative', # Calculate cumulative irradiance over time period instead of instantaneous
|
|
295
|
+
direct_normal_irradiance_scaling=1.0, # Scaling factor for direct solar radiation (1.0 = no scaling)
|
|
296
|
+
diffuse_irradiance_scaling=1.0, # Scaling factor for diffuse solar radiation (1.0 = no scaling)
|
|
297
|
+
**solar_kwargs # Pass all the parameters defined above
|
|
298
|
+
)
|
|
299
|
+
```
|
|
300
|
+
|
|
301
|
+
<p align="center">
|
|
302
|
+
<img src="https://raw.githubusercontent.com/kunifujiwara/VoxCity/main/images/solar.png" alt="Solar Irradiance Maps Rendered in Rhino" width="800">
|
|
303
|
+
</p>
|
|
304
|
+
<p align="center">
|
|
305
|
+
<em>Example Results Saved as OBJ and Rendered in Rhino</em>
|
|
306
|
+
</p>
|
|
307
|
+
|
|
252
308
|
#### Compute Green View Index (GVI) and Sky View Index (SVI):
|
|
253
309
|
|
|
254
310
|
```python
|
|
@@ -525,4 +525,75 @@ def find_building_containing_point(features, target_point):
|
|
|
525
525
|
if polygon.contains(point):
|
|
526
526
|
id_list.append(feature['properties']['id'])
|
|
527
527
|
|
|
528
|
-
return id_list
|
|
528
|
+
return id_list
|
|
529
|
+
|
|
530
|
+
def get_buildings_in_drawn_polygon(building_geojson, drawn_polygon_vertices,
|
|
531
|
+
operation='within'):
|
|
532
|
+
"""
|
|
533
|
+
Given a list of building footprints (in Lat-Lon) and a set of drawn polygon
|
|
534
|
+
vertices (also in Lat-Lon), return the building IDs that fall within or
|
|
535
|
+
intersect the drawn polygon.
|
|
536
|
+
|
|
537
|
+
Args:
|
|
538
|
+
building_geojson (list):
|
|
539
|
+
A list of GeoJSON features, each feature is a dict with:
|
|
540
|
+
{
|
|
541
|
+
"type": "Feature",
|
|
542
|
+
"geometry": {
|
|
543
|
+
"type": "Polygon",
|
|
544
|
+
"coordinates": [
|
|
545
|
+
[
|
|
546
|
+
[lat1, lon1], [lat2, lon2], ...
|
|
547
|
+
]
|
|
548
|
+
]
|
|
549
|
+
},
|
|
550
|
+
"properties": {
|
|
551
|
+
"id": ...
|
|
552
|
+
...
|
|
553
|
+
}
|
|
554
|
+
}
|
|
555
|
+
Note: These coordinates are in (lat, lon) order, not standard (lon, lat).
|
|
556
|
+
|
|
557
|
+
drawn_polygon_vertices (list):
|
|
558
|
+
A list of (lat, lon) tuples representing the polygon drawn by the user.
|
|
559
|
+
|
|
560
|
+
operation (str):
|
|
561
|
+
Determines how to include buildings.
|
|
562
|
+
Use "intersect" to include buildings that intersect the drawn polygon.
|
|
563
|
+
Use "within" to include buildings that lie entirely within the drawn polygon.
|
|
564
|
+
|
|
565
|
+
Returns:
|
|
566
|
+
list:
|
|
567
|
+
A list of building IDs (strings or ints) that satisfy the condition.
|
|
568
|
+
"""
|
|
569
|
+
# 1. Convert the user-drawn polygon vertices (lat, lon) into a Shapely Polygon.
|
|
570
|
+
# Shapely expects (x, y) = (longitude, latitude).
|
|
571
|
+
# So we'll do (lon, lat) for each vertex.
|
|
572
|
+
drawn_polygon_shapely = Polygon([(lon, lat) for (lat, lon) in drawn_polygon_vertices])
|
|
573
|
+
|
|
574
|
+
included_building_ids = []
|
|
575
|
+
|
|
576
|
+
# 2. Check each building in the GeoJSON
|
|
577
|
+
for feature in building_geojson:
|
|
578
|
+
# Skip any feature that is not Polygon
|
|
579
|
+
if feature['geometry']['type'] != 'Polygon':
|
|
580
|
+
continue
|
|
581
|
+
|
|
582
|
+
# Extract coordinates, which are in [ [lat, lon], [lat, lon], ... ]
|
|
583
|
+
coords = feature['geometry']['coordinates'][0]
|
|
584
|
+
|
|
585
|
+
# Create a Shapely polygon for the building
|
|
586
|
+
# Convert from (lat, lon) to (lon, lat)
|
|
587
|
+
building_polygon = Polygon([(lon, lat) for (lat, lon) in coords])
|
|
588
|
+
|
|
589
|
+
# 3. Depending on the operation, check the relationship
|
|
590
|
+
if operation == 'intersect':
|
|
591
|
+
if building_polygon.intersects(drawn_polygon_shapely):
|
|
592
|
+
included_building_ids.append(feature['properties'].get('id', None))
|
|
593
|
+
elif operation == 'within':
|
|
594
|
+
if building_polygon.within(drawn_polygon_shapely):
|
|
595
|
+
included_building_ids.append(feature['properties'].get('id', None))
|
|
596
|
+
else:
|
|
597
|
+
raise ValueError("operation must be 'intersect' or 'within'")
|
|
598
|
+
|
|
599
|
+
return included_building_ids
|
|
@@ -4,10 +4,11 @@ This module provides functions for drawing and manipulating rectangles on maps.
|
|
|
4
4
|
|
|
5
5
|
import math
|
|
6
6
|
from pyproj import Proj, transform
|
|
7
|
-
from ipyleaflet import Map, DrawControl, Rectangle
|
|
7
|
+
from ipyleaflet import Map, DrawControl, Rectangle, Polygon as LeafletPolygon
|
|
8
8
|
import ipyleaflet
|
|
9
9
|
from geopy import distance
|
|
10
10
|
from .utils import get_coordinates_from_cityname
|
|
11
|
+
import shapely.geometry as geom
|
|
11
12
|
|
|
12
13
|
def rotate_rectangle(m, rectangle_vertices, angle):
|
|
13
14
|
"""
|
|
@@ -222,4 +223,113 @@ def center_location_map_cityname(cityname, east_west_length, north_south_length,
|
|
|
222
223
|
# Register event handler for drawing actions
|
|
223
224
|
draw_control.on_draw(handle_draw)
|
|
224
225
|
|
|
225
|
-
return m, rectangle_vertices
|
|
226
|
+
return m, rectangle_vertices
|
|
227
|
+
|
|
228
|
+
def display_buildings_and_draw_polygon(building_geojson, zoom=17):
|
|
229
|
+
"""
|
|
230
|
+
Displays building footprints (in Lat-Lon order) on an ipyleaflet map,
|
|
231
|
+
and allows the user to draw a polygon whose vertices are returned
|
|
232
|
+
in a Python list (also in Lat-Lon).
|
|
233
|
+
|
|
234
|
+
Args:
|
|
235
|
+
building_geojson (list): A list of GeoJSON features (Polygons),
|
|
236
|
+
BUT with coordinates in [lat, lon] order.
|
|
237
|
+
zoom (int): Initial zoom level for the map. Default=17.
|
|
238
|
+
|
|
239
|
+
Returns:
|
|
240
|
+
(map_object, drawn_polygon_vertices)
|
|
241
|
+
- map_object: ipyleaflet Map instance
|
|
242
|
+
- drawn_polygon_vertices: a Python list that gets updated whenever
|
|
243
|
+
a new polygon is created. The list is in (lat, lon) order.
|
|
244
|
+
"""
|
|
245
|
+
# ---------------------------------------------------------
|
|
246
|
+
# 1. Determine a suitable map center via bounding box logic
|
|
247
|
+
# ---------------------------------------------------------
|
|
248
|
+
all_lats = []
|
|
249
|
+
all_lons = []
|
|
250
|
+
for feature in building_geojson:
|
|
251
|
+
# Handle only Polygons here; skip MultiPolygon if present
|
|
252
|
+
if feature['geometry']['type'] == 'Polygon':
|
|
253
|
+
# Coordinates in this data are [ [lat, lon], [lat, lon], ... ]
|
|
254
|
+
coords = feature['geometry']['coordinates'][0] # outer ring
|
|
255
|
+
all_lats.extend(pt[0] for pt in coords)
|
|
256
|
+
all_lons.extend(pt[1] for pt in coords)
|
|
257
|
+
|
|
258
|
+
if not all_lats or not all_lons:
|
|
259
|
+
# Fallback: If no footprints or invalid data, pick a default
|
|
260
|
+
center_lat, center_lon = 40.0, -100.0
|
|
261
|
+
else:
|
|
262
|
+
min_lat, max_lat = min(all_lats), max(all_lats)
|
|
263
|
+
min_lon, max_lon = min(all_lons), max(all_lons)
|
|
264
|
+
center_lat = (min_lat + max_lat) / 2
|
|
265
|
+
center_lon = (min_lon + max_lon) / 2
|
|
266
|
+
|
|
267
|
+
# Create the ipyleaflet map
|
|
268
|
+
m = Map(center=(center_lat, center_lon), zoom=zoom, scroll_wheel_zoom=True)
|
|
269
|
+
|
|
270
|
+
# -----------------------------------------
|
|
271
|
+
# 2. Add each building footprint to the map
|
|
272
|
+
# -----------------------------------------
|
|
273
|
+
for feature in building_geojson:
|
|
274
|
+
# Only handle simple Polygons
|
|
275
|
+
if feature['geometry']['type'] == 'Polygon':
|
|
276
|
+
coords = feature['geometry']['coordinates'][0]
|
|
277
|
+
# Because your data is already lat-lon, we do NOT swap:
|
|
278
|
+
lat_lon_coords = [(c[0], c[1]) for c in coords]
|
|
279
|
+
|
|
280
|
+
# Create the polygon layer
|
|
281
|
+
bldg_layer = LeafletPolygon(
|
|
282
|
+
locations=lat_lon_coords,
|
|
283
|
+
color="blue",
|
|
284
|
+
fill_color="blue",
|
|
285
|
+
fill_opacity=0.2,
|
|
286
|
+
weight=2
|
|
287
|
+
)
|
|
288
|
+
m.add_layer(bldg_layer)
|
|
289
|
+
|
|
290
|
+
# -----------------------------------------------------------------
|
|
291
|
+
# 3. Enable drawing of polygons, capturing the vertices in Lat-Lon
|
|
292
|
+
# -----------------------------------------------------------------
|
|
293
|
+
drawn_polygon_vertices = [] # We'll store the newly drawn polygon's vertices here (lat, lon).
|
|
294
|
+
|
|
295
|
+
draw_control = DrawControl(
|
|
296
|
+
polygon={
|
|
297
|
+
"shapeOptions": {
|
|
298
|
+
"color": "#6bc2e5",
|
|
299
|
+
"fillColor": "#6bc2e5",
|
|
300
|
+
"fillOpacity": 0.2
|
|
301
|
+
}
|
|
302
|
+
},
|
|
303
|
+
rectangle={}, # Disable rectangles (or enable if needed)
|
|
304
|
+
circle={}, # Disable circles
|
|
305
|
+
circlemarker={}, # Disable circlemarkers
|
|
306
|
+
polyline={}, # Disable polylines
|
|
307
|
+
marker={} # Disable markers
|
|
308
|
+
)
|
|
309
|
+
|
|
310
|
+
def handle_draw(self, action, geo_json):
|
|
311
|
+
"""
|
|
312
|
+
Callback for whenever a shape is created or edited.
|
|
313
|
+
ipyleaflet's DrawControl returns standard GeoJSON (lon, lat).
|
|
314
|
+
We'll convert them to (lat, lon).
|
|
315
|
+
"""
|
|
316
|
+
# Clear any previously stored vertices
|
|
317
|
+
drawn_polygon_vertices.clear()
|
|
318
|
+
|
|
319
|
+
if action == 'created' and geo_json['geometry']['type'] == 'Polygon':
|
|
320
|
+
# The polygon’s first ring
|
|
321
|
+
coordinates = geo_json['geometry']['coordinates'][0]
|
|
322
|
+
print("Vertices of the drawn polygon (Lat-Lon):")
|
|
323
|
+
|
|
324
|
+
# By default, drawn polygon coords are [ [lon, lat], [lon, lat], ... ]
|
|
325
|
+
# The last coordinate repeats the first -> skip it with [:-1]
|
|
326
|
+
for coord in coordinates[:-1]:
|
|
327
|
+
lon = coord[0]
|
|
328
|
+
lat = coord[1]
|
|
329
|
+
drawn_polygon_vertices.append((lat, lon))
|
|
330
|
+
print(f" - (lat, lon) = ({lat}, {lon})")
|
|
331
|
+
|
|
332
|
+
draw_control.on_draw(handle_draw)
|
|
333
|
+
m.add_control(draw_control)
|
|
334
|
+
|
|
335
|
+
return m, drawn_polygon_vertices
|