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.

Files changed (50) hide show
  1. {voxcity-0.3.1 → voxcity-0.3.3}/PKG-INFO +57 -1
  2. {voxcity-0.3.1 → voxcity-0.3.3}/README.md +56 -0
  3. {voxcity-0.3.1 → voxcity-0.3.3}/pyproject.toml +1 -1
  4. {voxcity-0.3.1 → voxcity-0.3.3}/src/voxcity/file/geojson.py +72 -1
  5. {voxcity-0.3.1 → voxcity-0.3.3}/src/voxcity/geo/draw.py +112 -2
  6. {voxcity-0.3.1 → voxcity-0.3.3}/src/voxcity/sim/solar.py +293 -69
  7. {voxcity-0.3.1 → voxcity-0.3.3}/src/voxcity/sim/view.py +184 -32
  8. {voxcity-0.3.1 → voxcity-0.3.3}/src/voxcity.egg-info/PKG-INFO +57 -1
  9. {voxcity-0.3.1 → voxcity-0.3.3}/AUTHORS.rst +0 -0
  10. {voxcity-0.3.1 → voxcity-0.3.3}/CONTRIBUTING.rst +0 -0
  11. {voxcity-0.3.1 → voxcity-0.3.3}/HISTORY.rst +0 -0
  12. {voxcity-0.3.1 → voxcity-0.3.3}/LICENSE +0 -0
  13. {voxcity-0.3.1 → voxcity-0.3.3}/MANIFEST.in +0 -0
  14. {voxcity-0.3.1 → voxcity-0.3.3}/docs/Makefile +0 -0
  15. {voxcity-0.3.1 → voxcity-0.3.3}/docs/archive/README.rst +0 -0
  16. {voxcity-0.3.1 → voxcity-0.3.3}/docs/authors.rst +0 -0
  17. {voxcity-0.3.1 → voxcity-0.3.3}/docs/conf.py +0 -0
  18. {voxcity-0.3.1 → voxcity-0.3.3}/docs/index.rst +0 -0
  19. {voxcity-0.3.1 → voxcity-0.3.3}/docs/make.bat +0 -0
  20. {voxcity-0.3.1 → voxcity-0.3.3}/setup.cfg +0 -0
  21. {voxcity-0.3.1 → voxcity-0.3.3}/src/voxcity/__init__.py +0 -0
  22. {voxcity-0.3.1 → voxcity-0.3.3}/src/voxcity/download/__init__.py +0 -0
  23. {voxcity-0.3.1 → voxcity-0.3.3}/src/voxcity/download/eubucco.py +0 -0
  24. {voxcity-0.3.1 → voxcity-0.3.3}/src/voxcity/download/gee.py +0 -0
  25. {voxcity-0.3.1 → voxcity-0.3.3}/src/voxcity/download/mbfp.py +0 -0
  26. {voxcity-0.3.1 → voxcity-0.3.3}/src/voxcity/download/oemj.py +0 -0
  27. {voxcity-0.3.1 → voxcity-0.3.3}/src/voxcity/download/omt.py +0 -0
  28. {voxcity-0.3.1 → voxcity-0.3.3}/src/voxcity/download/osm.py +0 -0
  29. {voxcity-0.3.1 → voxcity-0.3.3}/src/voxcity/download/overture.py +0 -0
  30. {voxcity-0.3.1 → voxcity-0.3.3}/src/voxcity/download/utils.py +0 -0
  31. {voxcity-0.3.1 → voxcity-0.3.3}/src/voxcity/file/__init_.py +0 -0
  32. {voxcity-0.3.1 → voxcity-0.3.3}/src/voxcity/file/envimet.py +0 -0
  33. {voxcity-0.3.1 → voxcity-0.3.3}/src/voxcity/file/magicavoxel.py +0 -0
  34. {voxcity-0.3.1 → voxcity-0.3.3}/src/voxcity/file/obj.py +0 -0
  35. {voxcity-0.3.1 → voxcity-0.3.3}/src/voxcity/geo/__init_.py +0 -0
  36. {voxcity-0.3.1 → voxcity-0.3.3}/src/voxcity/geo/grid.py +0 -0
  37. {voxcity-0.3.1 → voxcity-0.3.3}/src/voxcity/geo/utils.py +0 -0
  38. {voxcity-0.3.1 → voxcity-0.3.3}/src/voxcity/sim/__init_.py +0 -0
  39. {voxcity-0.3.1 → voxcity-0.3.3}/src/voxcity/sim/utils.py +0 -0
  40. {voxcity-0.3.1 → voxcity-0.3.3}/src/voxcity/utils/__init_.py +0 -0
  41. {voxcity-0.3.1 → voxcity-0.3.3}/src/voxcity/utils/lc.py +0 -0
  42. {voxcity-0.3.1 → voxcity-0.3.3}/src/voxcity/utils/visualization.py +0 -0
  43. {voxcity-0.3.1 → voxcity-0.3.3}/src/voxcity/utils/weather.py +0 -0
  44. {voxcity-0.3.1 → voxcity-0.3.3}/src/voxcity/voxcity.py +0 -0
  45. {voxcity-0.3.1 → voxcity-0.3.3}/src/voxcity.egg-info/SOURCES.txt +0 -0
  46. {voxcity-0.3.1 → voxcity-0.3.3}/src/voxcity.egg-info/dependency_links.txt +0 -0
  47. {voxcity-0.3.1 → voxcity-0.3.3}/src/voxcity.egg-info/requires.txt +0 -0
  48. {voxcity-0.3.1 → voxcity-0.3.3}/src/voxcity.egg-info/top_level.txt +0 -0
  49. {voxcity-0.3.1 → voxcity-0.3.3}/tests/__init__.py +0 -0
  50. {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.1
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
@@ -1,6 +1,6 @@
1
1
  [project]
2
2
  name = "voxcity"
3
- version = "0.3.1"
3
+ version = "0.3.3"
4
4
  requires-python = ">=3.10,<3.13"
5
5
  classifiers = [
6
6
  "Programming Language :: Python :: 3.10",
@@ -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