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/download/eubucco.py +9 -17
- voxcity/download/gee.py +4 -3
- voxcity/download/mbfp.py +7 -7
- voxcity/download/oemj.py +22 -22
- voxcity/download/omt.py +10 -10
- voxcity/download/osm.py +23 -21
- voxcity/download/overture.py +7 -15
- voxcity/file/envimet.py +4 -4
- voxcity/file/geojson.py +83 -26
- voxcity/geo/draw.py +128 -22
- voxcity/geo/grid.py +9 -143
- voxcity/geo/utils.py +79 -66
- voxcity/sim/solar.py +187 -53
- voxcity/sim/view.py +183 -31
- voxcity/utils/weather.py +7 -7
- {voxcity-0.3.2.dist-info → voxcity-0.3.4.dist-info}/METADATA +61 -5
- voxcity-0.3.4.dist-info/RECORD +34 -0
- {voxcity-0.3.2.dist-info → voxcity-0.3.4.dist-info}/WHEEL +1 -1
- voxcity-0.3.2.dist-info/RECORD +0 -34
- {voxcity-0.3.2.dist-info → voxcity-0.3.4.dist-info}/AUTHORS.rst +0 -0
- {voxcity-0.3.2.dist-info → voxcity-0.3.4.dist-info}/LICENSE +0 -0
- {voxcity-0.3.2.dist-info → voxcity-0.3.4.dist-info}/top_level.txt +0 -0
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.
|
|
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
|
-
|
|
43
|
-
|
|
44
|
-
|
|
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
|
-
|
|
155
|
-
|
|
156
|
-
-
|
|
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(
|
|
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)
|
|
355
|
-
target (tuple): End point (x,y,z)
|
|
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
|
|
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(
|
|
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
|
-
|
|
595
|
-
|
|
596
|
-
|
|
597
|
-
|
|
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
|
-
|
|
600
|
-
|
|
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(
|
|
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(
|
|
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(
|
|
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(
|
|
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['
|
|
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,
|
|
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.
|
|
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
|
-
(
|
|
168
|
-
(
|
|
169
|
-
(
|
|
170
|
-
(
|
|
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,,
|
voxcity-0.3.2.dist-info/RECORD
DELETED
|
@@ -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,,
|
|
File without changes
|
|
File without changes
|
|
File without changes
|