voxcity 0.3.2__py3-none-any.whl → 0.3.4__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/sim/view.py CHANGED
@@ -2,15 +2,43 @@
2
2
 
3
3
  This module provides functionality to compute and visualize:
4
4
  - Green View Index (GVI): Measures visibility of green elements like trees and vegetation
5
- - Sky View Index (SVI): Measures visibility of open sky from street level
5
+ - Sky View Index (SVI): Measures visibility of open sky from street level
6
+ - Sky View Factor (SVF): Measures the ratio of visible sky hemisphere to total hemisphere
6
7
  - Landmark Visibility: Measures visibility of specified landmark buildings from different locations
7
8
 
8
9
  The module uses optimized ray tracing techniques with Numba JIT compilation for efficient computation.
9
10
  Key features:
10
11
  - Generic ray tracing framework that can be customized for different view indices
11
12
  - Parallel processing for fast computation of view maps
13
+ - Tree transmittance modeling using Beer-Lambert law
12
14
  - Visualization tools including matplotlib plots and OBJ exports
13
15
  - Support for both inclusion and exclusion based visibility checks
16
+
17
+ The module provides several key functions:
18
+ - trace_ray_generic(): Core ray tracing function that handles tree transmittance
19
+ - compute_vi_generic(): Computes view indices by casting rays in specified directions
20
+ - compute_vi_map_generic(): Generates 2D maps of view indices
21
+ - get_view_index(): High-level function to compute various view indices
22
+ - compute_landmark_visibility(): Computes visibility of landmark buildings
23
+ - get_sky_view_factor_map(): Computes sky view factor maps
24
+
25
+ The module uses a voxel-based representation where:
26
+ - Empty space is represented by 0
27
+ - Trees are represented by -2
28
+ - Buildings are represented by -3
29
+ - Other values can be used for different features
30
+
31
+ Tree transmittance is modeled using the Beer-Lambert law with configurable parameters:
32
+ - tree_k: Static extinction coefficient (default 0.6)
33
+ - tree_lad: Leaf area density in m^-1 (default 1.0)
34
+
35
+ Additional implementation details:
36
+ - Uses DDA (Digital Differential Analyzer) algorithm for efficient ray traversal
37
+ - Handles edge cases like zero-length rays and division by zero
38
+ - Supports early exit optimizations for performance
39
+ - Provides flexible observer placement rules
40
+ - Includes comprehensive error checking and validation
41
+ - Allows customization of visualization parameters
14
42
  """
15
43
 
16
44
  import numpy as np
@@ -18,20 +46,31 @@ import matplotlib.pyplot as plt
18
46
  import matplotlib.patches as mpatches
19
47
  from numba import njit, prange
20
48
 
21
- from ..file.geojson import find_building_containing_point
49
+ from ..file.geojson import find_building_containing_point, get_buildings_in_drawn_polygon
22
50
  from ..file.obj import grid_to_obj, export_obj
23
51
 
24
52
  @njit
25
53
  def calculate_transmittance(length, tree_k=0.6, tree_lad=1.0):
26
54
  """Calculate tree transmittance using the Beer-Lambert law.
27
55
 
56
+ Uses the Beer-Lambert law to model light attenuation through tree canopy:
57
+ transmittance = exp(-k * LAD * L)
58
+ where:
59
+ - k is the extinction coefficient
60
+ - LAD is the leaf area density
61
+ - L is the path length through the canopy
62
+
28
63
  Args:
29
64
  length (float): Path length through tree voxel in meters
30
- tree_k (float): Static extinction coefficient (default: 0.5)
65
+ tree_k (float): Static extinction coefficient (default: 0.6)
66
+ Controls overall light attenuation strength
31
67
  tree_lad (float): Leaf area density in m^-1 (default: 1.0)
68
+ Higher values = denser foliage = more attenuation
32
69
 
33
70
  Returns:
34
71
  float: Transmittance value between 0 and 1
72
+ 1.0 = fully transparent
73
+ 0.0 = fully opaque
35
74
  """
36
75
  return np.exp(-tree_k * tree_lad * length)
37
76
 
@@ -39,9 +78,34 @@ def calculate_transmittance(length, tree_k=0.6, tree_lad=1.0):
39
78
  def trace_ray_generic(voxel_data, origin, direction, hit_values, meshsize, tree_k, tree_lad, inclusion_mode=True):
40
79
  """Trace a ray through a voxel grid and check for hits with specified values.
41
80
 
42
- For tree voxels (-2):
43
- - If -2 in hit_values: counts obstruction (1 - transmittance) as hit contribution
44
- - If -2 not in hit_values: applies transmittance normally
81
+ Uses DDA algorithm to efficiently traverse voxels along ray path.
82
+ Handles tree transmittance using Beer-Lambert law.
83
+
84
+ The DDA algorithm:
85
+ 1. Initializes ray at origin voxel
86
+ 2. Calculates distances to next voxel boundaries in each direction
87
+ 3. Steps to next voxel by choosing smallest distance
88
+ 4. Repeats until hit or out of bounds
89
+
90
+ Tree transmittance:
91
+ - When ray passes through tree voxels (-2), transmittance is accumulated
92
+ - Uses Beer-Lambert law with configurable extinction coefficient and leaf area density
93
+ - Ray is considered blocked if cumulative transmittance falls below 0.01
94
+
95
+ Args:
96
+ voxel_data (ndarray): 3D array of voxel values
97
+ origin (ndarray): Starting point (x,y,z) of ray in voxel coordinates
98
+ direction (ndarray): Direction vector of ray (will be normalized)
99
+ hit_values (tuple): Values to check for hits
100
+ meshsize (float): Size of each voxel in meters
101
+ tree_k (float): Tree extinction coefficient
102
+ tree_lad (float): Leaf area density in m^-1
103
+ inclusion_mode (bool): If True, hit_values are hits. If False, hit_values are allowed values.
104
+
105
+ Returns:
106
+ tuple: (hit_detected, transmittance_value)
107
+ hit_detected (bool): Whether ray hit a target voxel
108
+ transmittance_value (float): Cumulative transmittance through trees
45
109
  """
46
110
  nx, ny, nz = voxel_data.shape
47
111
  x0, y0, z0 = origin
@@ -151,9 +215,28 @@ def trace_ray_generic(voxel_data, origin, direction, hit_values, meshsize, tree_
151
215
  def compute_vi_generic(observer_location, voxel_data, ray_directions, hit_values, meshsize, tree_k, tree_lad, inclusion_mode=True):
152
216
  """Compute view index accounting for tree transmittance.
153
217
 
154
- For tree voxels (-2):
155
- - If -2 in hit_values: counts obstruction (1 - transmittance) as hit contribution
156
- - If -2 not in hit_values: applies transmittance normally
218
+ Casts rays in specified directions and computes visibility index based on hits and transmittance.
219
+ The view index is the ratio of visible rays to total rays cast, where:
220
+ - For inclusion mode: Counts hits with target values
221
+ - For exclusion mode: Counts rays that don't hit obstacles
222
+ Tree transmittance is handled specially:
223
+ - In inclusion mode with trees as targets: Uses (1 - transmittance) as contribution
224
+ - In exclusion mode: Uses transmittance value directly
225
+
226
+ Args:
227
+ observer_location (ndarray): Observer position (x,y,z) in voxel coordinates
228
+ voxel_data (ndarray): 3D array of voxel values
229
+ ray_directions (ndarray): Array of direction vectors for rays
230
+ hit_values (tuple): Values to check for hits
231
+ meshsize (float): Size of each voxel in meters
232
+ tree_k (float): Tree extinction coefficient
233
+ tree_lad (float): Leaf area density in m^-1
234
+ inclusion_mode (bool): If True, hit_values are hits. If False, hit_values are allowed values.
235
+
236
+ Returns:
237
+ float: View index value between 0 and 1
238
+ 0.0 = no visibility in any direction
239
+ 1.0 = full visibility in all directions
157
240
  """
158
241
  total_rays = ray_directions.shape[0]
159
242
  visibility_sum = 0.0
@@ -180,7 +263,31 @@ def compute_vi_generic(observer_location, voxel_data, ray_directions, hit_values
180
263
  @njit(parallel=True)
181
264
  def compute_vi_map_generic(voxel_data, ray_directions, view_height_voxel, hit_values,
182
265
  meshsize, tree_k, tree_lad, inclusion_mode=True):
183
- """Compute view index map incorporating tree transmittance."""
266
+ """Compute view index map incorporating tree transmittance.
267
+
268
+ Places observers at valid locations and computes view index for each position.
269
+ Valid observer locations are:
270
+ - Empty voxels (0) or tree voxels (-2)
271
+ - Above non-empty, non-tree voxels
272
+ - Not above water (7,8,9) or negative values
273
+
274
+ The function processes each x,y position in parallel for efficiency.
275
+
276
+ Args:
277
+ voxel_data (ndarray): 3D array of voxel values
278
+ ray_directions (ndarray): Array of direction vectors for rays
279
+ view_height_voxel (int): Observer height in voxel units
280
+ hit_values (tuple): Values to check for hits
281
+ meshsize (float): Size of each voxel in meters
282
+ tree_k (float): Tree extinction coefficient
283
+ tree_lad (float): Leaf area density in m^-1
284
+ inclusion_mode (bool): If True, hit_values are hits. If False, hit_values are allowed values.
285
+
286
+ Returns:
287
+ ndarray: 2D array of view index values
288
+ NaN = invalid observer location
289
+ 0.0-1.0 = view index value
290
+ """
184
291
  nx, ny, nz = voxel_data.shape
185
292
  vi_map = np.full((nx, ny), np.nan)
186
293
 
@@ -188,12 +295,15 @@ def compute_vi_map_generic(voxel_data, ray_directions, view_height_voxel, hit_va
188
295
  for y in range(ny):
189
296
  found_observer = False
190
297
  for z in range(1, nz):
298
+ # Check for valid observer location
191
299
  if voxel_data[x, y, z] in (0, -2) and voxel_data[x, y, z - 1] not in (0, -2):
300
+ # Skip invalid ground types
192
301
  if (voxel_data[x, y, z - 1] in (7, 8, 9)) or (voxel_data[x, y, z - 1] < 0):
193
302
  vi_map[x, y] = np.nan
194
303
  found_observer = True
195
304
  break
196
305
  else:
306
+ # Place observer and compute view index
197
307
  observer_location = np.array([x, y, z + view_height_voxel], dtype=np.float64)
198
308
  vi_value = compute_vi_generic(observer_location, voxel_data, ray_directions,
199
309
  hit_values, meshsize, tree_k, tree_lad, inclusion_mode)
@@ -208,6 +318,14 @@ def compute_vi_map_generic(voxel_data, ray_directions, view_height_voxel, hit_va
208
318
  def get_view_index(voxel_data, meshsize, mode=None, hit_values=None, inclusion_mode=True, **kwargs):
209
319
  """Calculate and visualize a generic view index for a voxel city model.
210
320
 
321
+ This is a high-level function that provides a flexible interface for computing
322
+ various view indices. It handles:
323
+ - Mode presets for common indices (green, sky)
324
+ - Ray direction generation
325
+ - Tree transmittance parameters
326
+ - Visualization
327
+ - Optional OBJ export
328
+
211
329
  Args:
212
330
  voxel_data (ndarray): 3D array of voxel values.
213
331
  meshsize (float): Size of each voxel in meters.
@@ -321,15 +439,21 @@ def get_view_index(voxel_data, meshsize, mode=None, hit_values=None, inclusion_m
321
439
 
322
440
  return vi_map
323
441
 
324
- def mark_building_by_id(voxcity_grid, building_id_grid_ori, ids, mark):
442
+ def mark_building_by_id(voxcity_grid_ori, building_id_grid_ori, ids, mark):
325
443
  """Mark specific buildings in the voxel grid with a given value.
326
444
 
445
+ Used to identify landmark buildings for visibility analysis.
446
+ Flips building ID grid vertically to match voxel grid orientation.
447
+
327
448
  Args:
328
449
  voxcity_grid (ndarray): 3D array of voxel values
329
450
  building_id_grid_ori (ndarray): 2D array of building IDs
330
451
  ids (list): List of building IDs to mark
331
452
  mark (int): Value to mark the buildings with
332
453
  """
454
+
455
+ voxcity_grid = voxcity_grid_ori.copy()
456
+
333
457
  # Flip building ID grid vertically to match voxel grid orientation
334
458
  building_id_grid = np.flipud(building_id_grid_ori.copy())
335
459
 
@@ -342,17 +466,20 @@ def mark_building_by_id(voxcity_grid, building_id_grid_ori, ids, mark):
342
466
  # Replace building voxels (-3) with mark value at this x,y position
343
467
  z_mask = voxcity_grid[x, y, :] == -3
344
468
  voxcity_grid[x, y, z_mask] = mark
469
+
470
+ return voxcity_grid
345
471
 
346
472
  @njit
347
473
  def trace_ray_to_target(voxel_data, origin, target, opaque_values):
348
474
  """Trace a ray from origin to target through voxel data.
349
475
 
350
476
  Uses DDA algorithm to efficiently traverse voxels along ray path.
477
+ Checks for any opaque voxels blocking the line of sight.
351
478
 
352
479
  Args:
353
480
  voxel_data (ndarray): 3D array of voxel values
354
- origin (tuple): Starting point (x,y,z) of ray
355
- target (tuple): End point (x,y,z) of ray
481
+ origin (tuple): Starting point (x,y,z) in voxel coordinates
482
+ target (tuple): End point (x,y,z) in voxel coordinates
356
483
  opaque_values (ndarray): Array of voxel values that block the ray
357
484
 
358
485
  Returns:
@@ -443,8 +570,11 @@ def trace_ray_to_target(voxel_data, origin, target, opaque_values):
443
570
  def compute_visibility_to_all_landmarks(observer_location, landmark_positions, voxel_data, opaque_values):
444
571
  """Check if any landmark is visible from the observer location.
445
572
 
573
+ Traces rays to each landmark position until finding one that's visible.
574
+ Uses optimized ray tracing with early exit on first visible landmark.
575
+
446
576
  Args:
447
- observer_location (ndarray): Observer position (x,y,z)
577
+ observer_location (ndarray): Observer position (x,y,z) in voxel coordinates
448
578
  landmark_positions (ndarray): Array of landmark positions
449
579
  voxel_data (ndarray): 3D array of voxel values
450
580
  opaque_values (ndarray): Array of voxel values that block visibility
@@ -467,6 +597,12 @@ def compute_visibility_map(voxel_data, landmark_positions, opaque_values, view_h
467
597
  Places observers at valid locations (empty voxels above ground, excluding building
468
598
  roofs and vegetation) and checks visibility to any landmark.
469
599
 
600
+ The function processes each x,y position in parallel for efficiency.
601
+ Valid observer locations are:
602
+ - Empty voxels (0) or tree voxels (-2)
603
+ - Above non-empty, non-tree voxels
604
+ - Not above water (7,8,9) or negative values
605
+
470
606
  Args:
471
607
  voxel_data (ndarray): 3D array of voxel values
472
608
  landmark_positions (ndarray): Array of landmark positions
@@ -474,7 +610,10 @@ def compute_visibility_map(voxel_data, landmark_positions, opaque_values, view_h
474
610
  view_height_voxel (int): Height offset for observer in voxels
475
611
 
476
612
  Returns:
477
- ndarray: 2D array of visibility values (0 or 1)
613
+ ndarray: 2D array of visibility values
614
+ NaN = invalid observer location
615
+ 0 = no landmarks visible
616
+ 1 = at least one landmark visible
478
617
  """
479
618
  nx, ny, nz = voxel_data.shape
480
619
  visibility_map = np.full((nx, ny), np.nan)
@@ -509,6 +648,12 @@ def compute_landmark_visibility(voxel_data, target_value=-30, view_height_voxel=
509
648
  Places observers at valid locations and checks visibility to any landmark voxel.
510
649
  Generates a binary visibility map and visualization.
511
650
 
651
+ The function:
652
+ 1. Identifies all landmark voxels (target_value)
653
+ 2. Determines which voxel values block visibility
654
+ 3. Computes visibility from each valid observer location
655
+ 4. Generates visualization with legend
656
+
512
657
  Args:
513
658
  voxel_data (ndarray): 3D array of voxel values
514
659
  target_value (int, optional): Value used to identify landmark voxels. Defaults to -30.
@@ -517,6 +662,9 @@ def compute_landmark_visibility(voxel_data, target_value=-30, view_height_voxel=
517
662
 
518
663
  Returns:
519
664
  ndarray: 2D array of visibility values (0 or 1) with y-axis flipped
665
+ NaN = invalid observer location
666
+ 0 = no landmarks visible
667
+ 1 = at least one landmark visible
520
668
 
521
669
  Raises:
522
670
  ValueError: If no landmark voxels are found with the specified target_value
@@ -553,7 +701,7 @@ def compute_landmark_visibility(voxel_data, target_value=-30, view_height_voxel=
553
701
 
554
702
  return np.flipud(visibility_map)
555
703
 
556
- def get_landmark_visibility_map(voxcity_grid, building_id_grid, building_geojson, meshsize, **kwargs):
704
+ def get_landmark_visibility_map(voxcity_grid_ori, building_id_grid, building_geojson, meshsize, **kwargs):
557
705
  """Generate a visibility map for landmark buildings in a voxel city.
558
706
 
559
707
  Places observers at valid locations and checks visibility to any part of the
@@ -590,25 +738,29 @@ def get_landmark_visibility_map(voxcity_grid, building_id_grid, building_geojson
590
738
  # Get landmark building IDs either directly or by finding buildings in rectangle
591
739
  features = building_geojson
592
740
  landmark_ids = kwargs.get('landmark_building_ids', None)
741
+ landmark_polygon = kwargs.get('landmark_polygon', None)
593
742
  if landmark_ids is None:
594
- rectangle_vertices = kwargs.get("rectangle_vertices", None)
595
- if rectangle_vertices is None:
596
- print("Cannot set landmark buildings. You need to input either of rectangle_vertices or landmark_ids.")
597
- return None
743
+ if landmark_polygon is not None:
744
+ landmark_ids = get_buildings_in_drawn_polygon(building_geojson, landmark_polygon, operation='within')
745
+ else:
746
+ rectangle_vertices = kwargs.get("rectangle_vertices", None)
747
+ if rectangle_vertices is None:
748
+ print("Cannot set landmark buildings. You need to input either of rectangle_vertices or landmark_ids.")
749
+ return None
750
+
751
+ # Calculate center point of rectangle
752
+ lons = [coord[0] for coord in rectangle_vertices]
753
+ lats = [coord[1] for coord in rectangle_vertices]
754
+ center_lon = (min(lons) + max(lons)) / 2
755
+ center_lat = (min(lats) + max(lats)) / 2
756
+ target_point = (center_lon, center_lat)
598
757
 
599
- # Calculate center point of rectangle
600
- lats = [coord[0] for coord in rectangle_vertices]
601
- lons = [coord[1] for coord in rectangle_vertices]
602
- center_lat = (min(lats) + max(lats)) / 2
603
- center_lon = (min(lons) + max(lons)) / 2
604
- target_point = (center_lat, center_lon)
605
-
606
- # Find buildings at center point
607
- landmark_ids = find_building_containing_point(features, target_point)
758
+ # Find buildings at center point
759
+ landmark_ids = find_building_containing_point(features, target_point)
608
760
 
609
761
  # Mark landmark buildings in voxel grid with special value
610
762
  target_value = -30
611
- mark_building_by_id(voxcity_grid, building_id_grid, landmark_ids, target_value)
763
+ voxcity_grid = mark_building_by_id(voxcity_grid_ori, building_id_grid, landmark_ids, target_value)
612
764
 
613
765
  # Compute visibility map
614
766
  landmark_vis_map = compute_landmark_visibility(voxcity_grid, target_value=target_value, view_height_voxel=view_height_voxel, colormap=colormap)
@@ -641,7 +793,7 @@ def get_landmark_visibility_map(voxcity_grid, building_id_grid, building_geojson
641
793
  output_file_name_vox = 'voxcity_' + output_file_name
642
794
  export_obj(voxcity_grid, output_dir, output_file_name_vox, meshsize)
643
795
 
644
- return landmark_vis_map
796
+ return landmark_vis_map, voxcity_grid
645
797
 
646
798
  def get_sky_view_factor_map(voxel_data, meshsize, show_plot=False, **kwargs):
647
799
  """
voxcity/utils/weather.py CHANGED
@@ -163,14 +163,14 @@ def process_epw(epw_path: Union[str, Path]) -> Tuple[pd.DataFrame, Dict]:
163
163
 
164
164
  return df, headers
165
165
 
166
- def get_nearest_epw_from_climate_onebuilding(latitude: float, longitude: float, output_dir: str = "./", max_distance: Optional[float] = None,
166
+ def get_nearest_epw_from_climate_onebuilding(longitude: float, latitude: float, output_dir: str = "./", max_distance: Optional[float] = None,
167
167
  extract_zip: bool = True, load_data: bool = True) -> Tuple[Optional[str], Optional[pd.DataFrame], Optional[Dict]]:
168
168
  """
169
169
  Download and process EPW weather file from Climate.OneBuilding.Org based on coordinates.
170
170
 
171
171
  Args:
172
- latitude (float): Latitude of the location
173
172
  longitude (float): Longitude of the location
173
+ latitude (float): Latitude of the location
174
174
  output_dir (str): Directory to save the EPW file (defaults to current directory)
175
175
  max_distance (float, optional): Maximum distance in kilometers to search for stations
176
176
  extract_zip (bool): Whether to extract the ZIP file (default True)
@@ -222,7 +222,7 @@ def get_nearest_epw_from_climate_onebuilding(latitude: float, longitude: float,
222
222
  content = re.sub(r'[^\x09\x0A\x0D\x20-\x7E\x85\xA0-\xFF]', '', content)
223
223
  return content
224
224
 
225
- def haversine_distance(lat1: float, lon1: float, lat2: float, lon2: float) -> float:
225
+ def haversine_distance(lon1: float, lat1: float, lon2: float, lat2: float) -> float:
226
226
  """Calculate the great circle distance between two points on Earth."""
227
227
  R = 6371 # Earth's radius in kilometers
228
228
 
@@ -281,8 +281,8 @@ def get_nearest_epw_from_climate_onebuilding(latitude: float, longitude: float,
281
281
 
282
282
  metadata = {
283
283
  'url': url,
284
- 'latitude': lat,
285
284
  'longitude': lon,
285
+ 'latitude': lat,
286
286
  'elevation': int(extract_value(r'Elevation <b>(-?\d+)</b>', '0')),
287
287
  'name': extract_value(r'<b>(.*?)</b>'),
288
288
  'wmo': extract_value(r'WMO <b>(\d+)</b>'),
@@ -370,7 +370,7 @@ def get_nearest_epw_from_climate_onebuilding(latitude: float, longitude: float,
370
370
 
371
371
  # Calculate distances and find nearest station
372
372
  stations_with_distances = [
373
- (station, haversine_distance(latitude, longitude, station['latitude'], station['longitude']))
373
+ (station, haversine_distance(longitude, latitude, station['longitude'], station['latitude']))
374
374
  for station in all_stations
375
375
  ]
376
376
 
@@ -445,7 +445,7 @@ def get_nearest_epw_from_climate_onebuilding(latitude: float, longitude: float,
445
445
  # Print station information
446
446
  print(f"\nDownloaded EPW file for {nearest_station['name']}")
447
447
  print(f"Distance: {distance:.2f} km")
448
- print(f"Station coordinates: {nearest_station['latitude']}, {nearest_station['longitude']}")
448
+ print(f"Station coordinates: {nearest_station['longitude']}, {nearest_station['latitude']}")
449
449
  if nearest_station['wmo']:
450
450
  print(f"WMO: {nearest_station['wmo']}")
451
451
  if nearest_station['climate_zone']:
@@ -520,4 +520,4 @@ def read_epw_for_solar_simulation(epw_file_path):
520
520
  df = pd.DataFrame(data, columns=['time', 'DNI', 'DHI']).set_index('time')
521
521
  df = df.sort_index()
522
522
 
523
- return df, lat, lon, tz, elevation_m
523
+ return df, lon, lat, tz, elevation_m
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.1
2
2
  Name: voxcity
3
- Version: 0.3.2
3
+ Version: 0.3.4
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>
@@ -164,10 +164,10 @@ Define the target area by directly specifying the coordinates of the rectangle v
164
164
 
165
165
  ```python
166
166
  rectangle_vertices = [
167
- (47.59830044521263, -122.33587348582083), # Southwest corner (latitude, longitude)
168
- (47.60279755390168, -122.33587348582083), # Northwest corner (latitude, longitude)
169
- (47.60279755390168, -122.32922451417917), # Northeast corner (latitude, longitude)
170
- (47.59830044521263, -122.32922451417917) # Southeast corner (latitude, longitude)
167
+ (-122.33587348582083, 47.59830044521263), # Southwest corner (longitude, latitude)
168
+ (-122.33587348582083, 47.60279755390168), # Northwest corner (longitude, latitude)
169
+ (-122.32922451417917, 47.60279755390168), # Northeast corner (longitude, latitude)
170
+ (-122.32922451417917, 47.59830044521263) # Southeast corner (longitude, latitude)
171
171
  ]
172
172
  ```
173
173
 
@@ -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
@@ -0,0 +1,34 @@
1
+ voxcity/__init__.py,sha256=HJM0D2Mv9qpk4JdVzt2SRAAk-hA1D_pCO0ezZH9F7KA,248
2
+ voxcity/voxcity.py,sha256=ewwSxA_lMIkQ5yiLZutq4UCLfnUm0r5f2Jiy-q6cFm0,32256
3
+ voxcity/download/__init__.py,sha256=OgGcGxOXF4tjcEL6DhOnt13DYPTvOigUelp5xIpTqM0,171
4
+ voxcity/download/eubucco.py,sha256=e1JXBuUfBptSDvNznSGckRs5Xgrj_SAFxk445J_o4KY,14854
5
+ voxcity/download/gee.py,sha256=j7jmzp44T3M6j_4DwhU9Y8Y6gqbZo1zFIlduQPc0jvk,14339
6
+ voxcity/download/mbfp.py,sha256=aQOGKP0pV6J6MCBXG9J6kQX04_S31rMjJEVvgrgOPg4,3942
7
+ voxcity/download/oemj.py,sha256=YlCuWBQfi40gfmwQcGDeHiPOs4Pk_jLZq65d5R3IGMU,7886
8
+ voxcity/download/omt.py,sha256=EjzimZMFXcjWNRlUEwPIjeTmE4rPh_9bjsgZyro8_mo,8819
9
+ voxcity/download/osm.py,sha256=HHSuj6jiQrThrfyJMWHE2nQ0Rqkx4UsXopk8AoNZS6Q,26536
10
+ voxcity/download/overture.py,sha256=daOvsySC2KIcTcMJUSA7XdbMELJuyLAIM2vr1DRLGp0,7714
11
+ voxcity/download/utils.py,sha256=z6MdPxM96FWQVqvZW2Eg5pMewVHVysUP7F6ueeCwMfI,1375
12
+ voxcity/file/__init_.py,sha256=cVyNyE6axEpSd3CT5hGuMOAlOyU1p8lVP4jkF1-0Ad8,94
13
+ voxcity/file/envimet.py,sha256=SPVoSyYTMNyDRDFWsI0YAsIsb6yt_SXZeDUlhyqlEqY,24282
14
+ voxcity/file/geojson.py,sha256=G8jG5Ffh86uhNZBLmr_hgyU9FwGab_tJBePET5DUQYk,24188
15
+ voxcity/file/magicavoxel.py,sha256=Fsv7yGRXeKmp82xcG3rOb0t_HtoqltNq2tHl08xVlqY,7500
16
+ voxcity/file/obj.py,sha256=oW-kPoZj53nfmO9tXP3Wvizq6Kkjh-QQR8UBexRuMiI,21609
17
+ voxcity/geo/__init_.py,sha256=rsj0OMzrTNACccdvEfmf632mb03BRUtKLuecppsxX40,62
18
+ voxcity/geo/draw.py,sha256=roljWXyqYdsWYkmb-5_WNxrJrfV5lnAt8uZblCCo_3Q,13555
19
+ voxcity/geo/grid.py,sha256=YgAityV3KaBsng9R_aDQKRFkdEssv5Yzn5wKCIOJQOQ,34243
20
+ voxcity/geo/utils.py,sha256=1BRHp-DDeOA8HG8jplY7Eo75G3oXkVGL6DGONL4BA8A,19815
21
+ voxcity/sim/__init_.py,sha256=APdkcdaovj0v_RPOaA4SBvFUKT2RM7Hxuuz3Sux4gCo,65
22
+ voxcity/sim/solar.py,sha256=7waUoUMzDBf_Van3qghSG019TrgHgNj-TcVRVf0StuU,31306
23
+ voxcity/sim/utils.py,sha256=sEYBB2-hLJxTiXQps1_-Fi7t1HN3-1OPOvBCWtgIisA,130
24
+ voxcity/sim/view.py,sha256=oq6G-f0Tn-KT0vjYNJfucmOIrv1GNjljhA-zvU4nNoA,36668
25
+ voxcity/utils/__init_.py,sha256=xjEadXQ9wXTw0lsx0JTbyTqASWw0GJLfT6eRr0CyQzw,71
26
+ voxcity/utils/lc.py,sha256=RwPd-VY3POV3gTrBhM7TubgGb9MCd3nVah_G8iUEF7k,11562
27
+ voxcity/utils/visualization.py,sha256=GVERj0noHAvJtDT0fV3K6w7pTfuAUfwKez-UMuEakEg,42214
28
+ voxcity/utils/weather.py,sha256=fJ2p5susoMgYSBlrmlTlZVUDe9kpQwmLuyv1TgcOnDM,21482
29
+ voxcity-0.3.4.dist-info/AUTHORS.rst,sha256=m82vkI5QokEGdcHof2OxK39lf81w1P58kG9ZNNAKS9U,175
30
+ voxcity-0.3.4.dist-info/LICENSE,sha256=-hGliOFiwUrUSoZiB5WF90xXGqinKyqiDI2t6hrnam8,1087
31
+ voxcity-0.3.4.dist-info/METADATA,sha256=wi1ziMnMN8UpySzEQZpGN6SrUW2ZSkY-gOd4kQ9de0U,23608
32
+ voxcity-0.3.4.dist-info/WHEEL,sha256=A3WOREP4zgxI0fKrHUG8DC8013e3dK3n7a6HDbcEIwE,91
33
+ voxcity-0.3.4.dist-info/top_level.txt,sha256=00b2U-LKfDllt6RL1R33MXie5MvxzUFye0NGD96t_8I,8
34
+ voxcity-0.3.4.dist-info/RECORD,,
@@ -1,5 +1,5 @@
1
1
  Wheel-Version: 1.0
2
- Generator: setuptools (75.6.0)
2
+ Generator: setuptools (75.7.0)
3
3
  Root-Is-Purelib: true
4
4
  Tag: py3-none-any
5
5
 
@@ -1,34 +0,0 @@
1
- voxcity/__init__.py,sha256=HJM0D2Mv9qpk4JdVzt2SRAAk-hA1D_pCO0ezZH9F7KA,248
2
- voxcity/voxcity.py,sha256=ewwSxA_lMIkQ5yiLZutq4UCLfnUm0r5f2Jiy-q6cFm0,32256
3
- voxcity/download/__init__.py,sha256=OgGcGxOXF4tjcEL6DhOnt13DYPTvOigUelp5xIpTqM0,171
4
- voxcity/download/eubucco.py,sha256=vd-LoWwUk1A1WC1cSeJTVRFPlAiU04NyQj3RMjohx4M,15149
5
- voxcity/download/gee.py,sha256=mHrG8mMhhOAvA6wASurZvUPpCKCcg75GriD6VN8VbCM,14297
6
- voxcity/download/mbfp.py,sha256=pa5eCw1ANzNIzr1bTcrfzttRtNUjUDS808nDAXEHAag,3942
7
- voxcity/download/oemj.py,sha256=sJ32-hTIo68Vov7Jqxc-n-6oGOF5LcWc8amwZhgZagc,7886
8
- voxcity/download/omt.py,sha256=x_oKPLWA0YhhC5BRsiGl5sHPFVG5io9w_-0Uafhihm8,8898
9
- voxcity/download/osm.py,sha256=h5K2ZWeVBpbN_BeWgujWOiyO6gYysylXvzHE3Kk0zEw,26272
10
- voxcity/download/overture.py,sha256=R6XtC2iP6Xp6e2Otop4FXs97gCW_bAuFQ_RCOPiHbjo,8079
11
- voxcity/download/utils.py,sha256=z6MdPxM96FWQVqvZW2Eg5pMewVHVysUP7F6ueeCwMfI,1375
12
- voxcity/file/__init_.py,sha256=cVyNyE6axEpSd3CT5hGuMOAlOyU1p8lVP4jkF1-0Ad8,94
13
- voxcity/file/envimet.py,sha256=s3qw3kI8sO5996xdnB0MgPCCL0PvICoY1NfrtCz51Sw,24182
14
- voxcity/file/geojson.py,sha256=Wm_ABjG7lRLOWLPxt0vjP0jycomB898wNte3FEtYT_M,22301
15
- voxcity/file/magicavoxel.py,sha256=Fsv7yGRXeKmp82xcG3rOb0t_HtoqltNq2tHl08xVlqY,7500
16
- voxcity/file/obj.py,sha256=oW-kPoZj53nfmO9tXP3Wvizq6Kkjh-QQR8UBexRuMiI,21609
17
- voxcity/geo/__init_.py,sha256=rsj0OMzrTNACccdvEfmf632mb03BRUtKLuecppsxX40,62
18
- voxcity/geo/draw.py,sha256=yRaJHFAztLuFRO6gJtTGqLQPQkLvGrvw3E0fucnbKPQ,9090
19
- voxcity/geo/grid.py,sha256=l9iqi2OCmtJixCc3Y3RthF403pdrx6sB0565wZ1uHgM,40042
20
- voxcity/geo/utils.py,sha256=sR9InBHxV76XjlGPLD7blg_6EjbM0MG5DOyJffhBjWk,19372
21
- voxcity/sim/__init_.py,sha256=APdkcdaovj0v_RPOaA4SBvFUKT2RM7Hxuuz3Sux4gCo,65
22
- voxcity/sim/solar.py,sha256=8_qyA3BLiWWr72GtLo490xLYKOnFxi2XXyKQ0sijI3s,24284
23
- voxcity/sim/utils.py,sha256=sEYBB2-hLJxTiXQps1_-Fi7t1HN3-1OPOvBCWtgIisA,130
24
- voxcity/sim/view.py,sha256=IrCJJ7A4nili6SdUWcnhQ_tRHI4Q_eLSLeetdMy8Og0,29451
25
- voxcity/utils/__init_.py,sha256=xjEadXQ9wXTw0lsx0JTbyTqASWw0GJLfT6eRr0CyQzw,71
26
- voxcity/utils/lc.py,sha256=RwPd-VY3POV3gTrBhM7TubgGb9MCd3nVah_G8iUEF7k,11562
27
- voxcity/utils/visualization.py,sha256=GVERj0noHAvJtDT0fV3K6w7pTfuAUfwKez-UMuEakEg,42214
28
- voxcity/utils/weather.py,sha256=Qwnr0paGdRQstwD0A9q2QfJIV-aQUyxH-6viRwXOuwM,21482
29
- voxcity-0.3.2.dist-info/AUTHORS.rst,sha256=m82vkI5QokEGdcHof2OxK39lf81w1P58kG9ZNNAKS9U,175
30
- voxcity-0.3.2.dist-info/LICENSE,sha256=-hGliOFiwUrUSoZiB5WF90xXGqinKyqiDI2t6hrnam8,1087
31
- voxcity-0.3.2.dist-info/METADATA,sha256=8cVLI71VkZ74Xk0N5-bnrn1wYRpK8MbnQTLCsD-7Jl8,19876
32
- voxcity-0.3.2.dist-info/WHEEL,sha256=PZUExdf71Ui_so67QXpySuHtCi3-J3wvF4ORK6k_S8U,91
33
- voxcity-0.3.2.dist-info/top_level.txt,sha256=00b2U-LKfDllt6RL1R33MXie5MvxzUFye0NGD96t_8I,8
34
- voxcity-0.3.2.dist-info/RECORD,,