voxcity 0.7.0__py3-none-any.whl → 1.0.13__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.
- voxcity/__init__.py +14 -14
- voxcity/downloader/ocean.py +559 -0
- voxcity/exporter/__init__.py +12 -12
- voxcity/exporter/cityles.py +633 -633
- voxcity/exporter/envimet.py +733 -728
- voxcity/exporter/magicavoxel.py +333 -333
- voxcity/exporter/netcdf.py +238 -238
- voxcity/exporter/obj.py +1480 -1480
- voxcity/generator/__init__.py +47 -44
- voxcity/generator/api.py +727 -675
- voxcity/generator/grids.py +394 -379
- voxcity/generator/io.py +94 -94
- voxcity/generator/pipeline.py +582 -282
- voxcity/generator/update.py +429 -0
- voxcity/generator/voxelizer.py +18 -6
- voxcity/geoprocessor/__init__.py +75 -75
- voxcity/geoprocessor/draw.py +1494 -1219
- voxcity/geoprocessor/merge_utils.py +91 -91
- voxcity/geoprocessor/mesh.py +806 -806
- voxcity/geoprocessor/network.py +708 -708
- voxcity/geoprocessor/raster/__init__.py +2 -0
- voxcity/geoprocessor/raster/buildings.py +435 -428
- voxcity/geoprocessor/raster/core.py +31 -0
- voxcity/geoprocessor/raster/export.py +93 -93
- voxcity/geoprocessor/raster/landcover.py +178 -51
- voxcity/geoprocessor/raster/raster.py +1 -1
- voxcity/geoprocessor/utils.py +824 -824
- voxcity/models.py +115 -113
- voxcity/simulator/solar/__init__.py +66 -43
- voxcity/simulator/solar/integration.py +336 -336
- voxcity/simulator/solar/sky.py +668 -0
- voxcity/simulator/solar/temporal.py +792 -434
- voxcity/simulator_gpu/__init__.py +115 -0
- voxcity/simulator_gpu/common/__init__.py +9 -0
- voxcity/simulator_gpu/common/geometry.py +11 -0
- voxcity/simulator_gpu/core.py +322 -0
- voxcity/simulator_gpu/domain.py +262 -0
- voxcity/simulator_gpu/environment.yml +11 -0
- voxcity/simulator_gpu/init_taichi.py +154 -0
- voxcity/simulator_gpu/integration.py +15 -0
- voxcity/simulator_gpu/kernels.py +56 -0
- voxcity/simulator_gpu/radiation.py +28 -0
- voxcity/simulator_gpu/raytracing.py +623 -0
- voxcity/simulator_gpu/sky.py +9 -0
- voxcity/simulator_gpu/solar/__init__.py +178 -0
- voxcity/simulator_gpu/solar/core.py +66 -0
- voxcity/simulator_gpu/solar/csf.py +1249 -0
- voxcity/simulator_gpu/solar/domain.py +561 -0
- voxcity/simulator_gpu/solar/epw.py +421 -0
- voxcity/simulator_gpu/solar/integration.py +2953 -0
- voxcity/simulator_gpu/solar/radiation.py +3019 -0
- voxcity/simulator_gpu/solar/raytracing.py +686 -0
- voxcity/simulator_gpu/solar/reflection.py +533 -0
- voxcity/simulator_gpu/solar/sky.py +907 -0
- voxcity/simulator_gpu/solar/solar.py +337 -0
- voxcity/simulator_gpu/solar/svf.py +446 -0
- voxcity/simulator_gpu/solar/volumetric.py +1151 -0
- voxcity/simulator_gpu/solar/voxcity.py +2953 -0
- voxcity/simulator_gpu/temporal.py +13 -0
- voxcity/simulator_gpu/utils.py +25 -0
- voxcity/simulator_gpu/view.py +32 -0
- voxcity/simulator_gpu/visibility/__init__.py +109 -0
- voxcity/simulator_gpu/visibility/geometry.py +278 -0
- voxcity/simulator_gpu/visibility/integration.py +808 -0
- voxcity/simulator_gpu/visibility/landmark.py +753 -0
- voxcity/simulator_gpu/visibility/view.py +944 -0
- voxcity/utils/__init__.py +11 -0
- voxcity/utils/classes.py +194 -0
- voxcity/utils/lc.py +80 -39
- voxcity/utils/shape.py +230 -0
- voxcity/visualizer/__init__.py +24 -24
- voxcity/visualizer/builder.py +43 -43
- voxcity/visualizer/grids.py +141 -141
- voxcity/visualizer/maps.py +187 -187
- voxcity/visualizer/renderer.py +1146 -928
- {voxcity-0.7.0.dist-info → voxcity-1.0.13.dist-info}/METADATA +56 -52
- voxcity-1.0.13.dist-info/RECORD +116 -0
- voxcity-0.7.0.dist-info/RECORD +0 -77
- {voxcity-0.7.0.dist-info → voxcity-1.0.13.dist-info}/WHEEL +0 -0
- {voxcity-0.7.0.dist-info → voxcity-1.0.13.dist-info}/licenses/AUTHORS.rst +0 -0
- {voxcity-0.7.0.dist-info → voxcity-1.0.13.dist-info}/licenses/LICENSE +0 -0
|
@@ -0,0 +1,808 @@
|
|
|
1
|
+
"""
|
|
2
|
+
VoxCity Integration Module for simulator_gpu.visibility
|
|
3
|
+
|
|
4
|
+
This module provides utilities for loading VoxCity models and using them
|
|
5
|
+
with the GPU-accelerated visibility analysis tools.
|
|
6
|
+
|
|
7
|
+
This module emulates the voxcity.simulator.visibility API using Taichi GPU acceleration.
|
|
8
|
+
|
|
9
|
+
VoxCity models contain:
|
|
10
|
+
- 3D voxel grids with building, tree, and ground information
|
|
11
|
+
- Land cover classification codes
|
|
12
|
+
- Building heights and IDs
|
|
13
|
+
- Tree canopy data
|
|
14
|
+
|
|
15
|
+
API Compatibility:
|
|
16
|
+
The functions in this module match the voxcity.simulator.visibility API:
|
|
17
|
+
- get_view_index() - same signature as voxcity.simulator.visibility.get_view_index
|
|
18
|
+
- get_sky_view_factor_map() - same signature as voxcity.simulator.visibility.get_sky_view_factor_map
|
|
19
|
+
- get_surface_view_factor() - same signature as voxcity.simulator.visibility.get_surface_view_factor
|
|
20
|
+
- get_landmark_visibility_map() - same signature as voxcity.simulator.visibility.get_landmark_visibility_map
|
|
21
|
+
- get_surface_landmark_visibility() - same signature as voxcity.simulator.visibility.get_surface_landmark_visibility
|
|
22
|
+
- mark_building_by_id() - same as voxcity.simulator.visibility.mark_building_by_id
|
|
23
|
+
"""
|
|
24
|
+
|
|
25
|
+
import numpy as np
|
|
26
|
+
from typing import Dict, Optional, Tuple, Union, List
|
|
27
|
+
from dataclasses import dataclass
|
|
28
|
+
import math
|
|
29
|
+
|
|
30
|
+
from ..domain import Domain
|
|
31
|
+
from .view import ViewCalculator, SurfaceViewFactorCalculator
|
|
32
|
+
from .landmark import LandmarkVisibilityCalculator, SurfaceLandmarkVisibilityCalculator
|
|
33
|
+
from .landmark import mark_building_by_id
|
|
34
|
+
|
|
35
|
+
|
|
36
|
+
# VoxCity voxel class codes
|
|
37
|
+
VOXCITY_GROUND_CODE = -1
|
|
38
|
+
VOXCITY_TREE_CODE = -2
|
|
39
|
+
VOXCITY_BUILDING_CODE = -3
|
|
40
|
+
|
|
41
|
+
# Green view target codes
|
|
42
|
+
GREEN_VIEW_CODES = (-2, 2, 5, 6, 7, 8) # Trees and vegetation classes
|
|
43
|
+
|
|
44
|
+
|
|
45
|
+
def create_domain_from_voxcity(voxcity) -> Domain:
|
|
46
|
+
"""
|
|
47
|
+
Create a Domain object from a VoxCity model.
|
|
48
|
+
|
|
49
|
+
Args:
|
|
50
|
+
voxcity: VoxCity object with voxels attribute
|
|
51
|
+
|
|
52
|
+
Returns:
|
|
53
|
+
Domain object configured for view analysis
|
|
54
|
+
"""
|
|
55
|
+
voxel_data = voxcity.voxels.classes
|
|
56
|
+
nx, ny, nz = voxel_data.shape
|
|
57
|
+
meshsize = voxcity.voxels.meta.meshsize
|
|
58
|
+
|
|
59
|
+
domain = Domain(
|
|
60
|
+
nx=nx, ny=ny, nz=nz,
|
|
61
|
+
dx=meshsize, dy=meshsize, dz=meshsize
|
|
62
|
+
)
|
|
63
|
+
|
|
64
|
+
# Set domain from voxel data
|
|
65
|
+
domain.set_from_voxel_data(voxel_data, tree_code=VOXCITY_TREE_CODE)
|
|
66
|
+
|
|
67
|
+
return domain
|
|
68
|
+
|
|
69
|
+
|
|
70
|
+
def get_view_index_gpu(
|
|
71
|
+
voxcity,
|
|
72
|
+
mode: str = None,
|
|
73
|
+
hit_values: Tuple[int, ...] = None,
|
|
74
|
+
inclusion_mode: bool = True,
|
|
75
|
+
view_point_height: float = 1.5,
|
|
76
|
+
n_azimuth: int = 120,
|
|
77
|
+
n_elevation: int = 20,
|
|
78
|
+
elevation_min_degrees: float = -30.0,
|
|
79
|
+
elevation_max_degrees: float = 30.0,
|
|
80
|
+
ray_sampling: str = "grid",
|
|
81
|
+
n_rays: int = None,
|
|
82
|
+
tree_k: float = 0.5,
|
|
83
|
+
tree_lad: float = 1.0,
|
|
84
|
+
show_plot: bool = False,
|
|
85
|
+
**kwargs
|
|
86
|
+
) -> np.ndarray:
|
|
87
|
+
"""
|
|
88
|
+
GPU-accelerated View Index calculation for VoxCity.
|
|
89
|
+
|
|
90
|
+
This function emulates voxcity.simulator.visibility.view.get_view_index
|
|
91
|
+
using Taichi GPU acceleration.
|
|
92
|
+
|
|
93
|
+
Args:
|
|
94
|
+
voxcity: VoxCity object
|
|
95
|
+
mode: Predefined mode ('green', 'sky', or None for custom)
|
|
96
|
+
hit_values: Target voxel values to count as visible
|
|
97
|
+
inclusion_mode: If True, count hits on targets; if False, count non-blocked rays
|
|
98
|
+
view_point_height: Observer height above ground (meters)
|
|
99
|
+
n_azimuth: Number of azimuthal divisions
|
|
100
|
+
n_elevation: Number of elevation divisions
|
|
101
|
+
elevation_min_degrees: Minimum viewing angle
|
|
102
|
+
elevation_max_degrees: Maximum viewing angle
|
|
103
|
+
ray_sampling: 'grid' or 'fibonacci'
|
|
104
|
+
n_rays: Total rays for fibonacci sampling
|
|
105
|
+
tree_k: Tree extinction coefficient
|
|
106
|
+
tree_lad: Leaf area density
|
|
107
|
+
show_plot: Whether to display a matplotlib plot
|
|
108
|
+
**kwargs: Additional parameters
|
|
109
|
+
|
|
110
|
+
Returns:
|
|
111
|
+
2D array of view index values
|
|
112
|
+
"""
|
|
113
|
+
voxel_data = voxcity.voxels.classes
|
|
114
|
+
meshsize = voxcity.voxels.meta.meshsize
|
|
115
|
+
nx, ny, nz = voxel_data.shape
|
|
116
|
+
|
|
117
|
+
# Create domain
|
|
118
|
+
domain = Domain(
|
|
119
|
+
nx=nx, ny=ny, nz=nz,
|
|
120
|
+
dx=meshsize, dy=meshsize, dz=meshsize
|
|
121
|
+
)
|
|
122
|
+
|
|
123
|
+
# Create calculator
|
|
124
|
+
calc = ViewCalculator(
|
|
125
|
+
domain,
|
|
126
|
+
n_azimuth=n_azimuth,
|
|
127
|
+
n_elevation=n_elevation,
|
|
128
|
+
ray_sampling=ray_sampling,
|
|
129
|
+
n_rays=n_rays
|
|
130
|
+
)
|
|
131
|
+
|
|
132
|
+
# Compute view index
|
|
133
|
+
vi_map = calc.compute_view_index(
|
|
134
|
+
voxel_data=voxel_data,
|
|
135
|
+
mode=mode,
|
|
136
|
+
hit_values=hit_values,
|
|
137
|
+
inclusion_mode=inclusion_mode,
|
|
138
|
+
view_point_height=view_point_height,
|
|
139
|
+
elevation_min_degrees=elevation_min_degrees,
|
|
140
|
+
elevation_max_degrees=elevation_max_degrees,
|
|
141
|
+
tree_k=tree_k,
|
|
142
|
+
tree_lad=tree_lad
|
|
143
|
+
)
|
|
144
|
+
|
|
145
|
+
# Note: ViewCalculator.compute_view_index already flips to match VoxCity coordinate system
|
|
146
|
+
|
|
147
|
+
# Plot if requested
|
|
148
|
+
if show_plot:
|
|
149
|
+
try:
|
|
150
|
+
import matplotlib.pyplot as plt
|
|
151
|
+
colormap = kwargs.get('colormap', 'viridis')
|
|
152
|
+
vmin = kwargs.get('vmin', 0.0)
|
|
153
|
+
vmax = kwargs.get('vmax', 1.0)
|
|
154
|
+
|
|
155
|
+
cmap = plt.cm.get_cmap(colormap).copy()
|
|
156
|
+
cmap.set_bad(color='lightgray')
|
|
157
|
+
plt.figure(figsize=(10, 8))
|
|
158
|
+
plt.imshow(vi_map, origin='lower', cmap=cmap, vmin=vmin, vmax=vmax)
|
|
159
|
+
plt.colorbar(label='View Index')
|
|
160
|
+
plt.axis('off')
|
|
161
|
+
plt.show()
|
|
162
|
+
except ImportError:
|
|
163
|
+
pass
|
|
164
|
+
|
|
165
|
+
return vi_map
|
|
166
|
+
|
|
167
|
+
|
|
168
|
+
# VoxCity API-compatible function names (recommended interface)
|
|
169
|
+
def get_view_index(voxcity, mode=None, hit_values=None, inclusion_mode=True, fast_path=True, **kwargs):
|
|
170
|
+
"""
|
|
171
|
+
GPU-accelerated View Index calculation for VoxCity.
|
|
172
|
+
|
|
173
|
+
This function matches the signature of voxcity.simulator.visibility.get_view_index
|
|
174
|
+
using Taichi GPU acceleration.
|
|
175
|
+
|
|
176
|
+
Args:
|
|
177
|
+
voxcity: VoxCity object
|
|
178
|
+
mode: Predefined mode ('green', 'sky', or None for custom)
|
|
179
|
+
hit_values: Target voxel values to count as visible
|
|
180
|
+
inclusion_mode: If True, count hits on targets; if False, count non-blocked rays
|
|
181
|
+
**kwargs: Additional parameters including:
|
|
182
|
+
- view_point_height (float): Observer height above ground (default: 1.5)
|
|
183
|
+
- N_azimuth (int): Number of azimuthal divisions (default: 120)
|
|
184
|
+
- N_elevation (int): Number of elevation divisions (default: 20)
|
|
185
|
+
- elevation_min_degrees (float): Minimum viewing angle (default: -30)
|
|
186
|
+
- elevation_max_degrees (float): Maximum viewing angle (default: 30)
|
|
187
|
+
- ray_sampling (str): 'grid' or 'fibonacci' (default: 'grid')
|
|
188
|
+
- N_rays (int): Total rays for fibonacci sampling
|
|
189
|
+
- tree_k (float): Tree extinction coefficient (default: 0.5)
|
|
190
|
+
- tree_lad (float): Leaf area density (default: 1.0)
|
|
191
|
+
- colormap (str): Matplotlib colormap name (default: 'viridis')
|
|
192
|
+
- vmin, vmax (float): Colormap limits (default: 0.0, 1.0)
|
|
193
|
+
- obj_export (bool): Whether to export OBJ file (default: False)
|
|
194
|
+
|
|
195
|
+
Returns:
|
|
196
|
+
2D array of view index values
|
|
197
|
+
"""
|
|
198
|
+
# Map VoxCity-style kwargs to our internal parameter names
|
|
199
|
+
n_azimuth = kwargs.pop('N_azimuth', kwargs.pop('n_azimuth', 120))
|
|
200
|
+
n_elevation = kwargs.pop('N_elevation', kwargs.pop('n_elevation', 20))
|
|
201
|
+
n_rays = kwargs.pop('N_rays', kwargs.pop('n_rays', None))
|
|
202
|
+
view_point_height = kwargs.pop('view_point_height', 1.5)
|
|
203
|
+
elevation_min_degrees = kwargs.pop('elevation_min_degrees', -30.0)
|
|
204
|
+
elevation_max_degrees = kwargs.pop('elevation_max_degrees', 30.0)
|
|
205
|
+
ray_sampling = kwargs.pop('ray_sampling', 'grid')
|
|
206
|
+
tree_k = kwargs.pop('tree_k', 0.5)
|
|
207
|
+
tree_lad = kwargs.pop('tree_lad', 1.0)
|
|
208
|
+
|
|
209
|
+
return get_view_index_gpu(
|
|
210
|
+
voxcity,
|
|
211
|
+
mode=mode,
|
|
212
|
+
hit_values=hit_values,
|
|
213
|
+
inclusion_mode=inclusion_mode,
|
|
214
|
+
view_point_height=view_point_height,
|
|
215
|
+
n_azimuth=n_azimuth,
|
|
216
|
+
n_elevation=n_elevation,
|
|
217
|
+
elevation_min_degrees=elevation_min_degrees,
|
|
218
|
+
elevation_max_degrees=elevation_max_degrees,
|
|
219
|
+
ray_sampling=ray_sampling,
|
|
220
|
+
n_rays=n_rays,
|
|
221
|
+
tree_k=tree_k,
|
|
222
|
+
tree_lad=tree_lad,
|
|
223
|
+
show_plot=True, # VoxCity default shows plot
|
|
224
|
+
**kwargs
|
|
225
|
+
)
|
|
226
|
+
|
|
227
|
+
|
|
228
|
+
def get_sky_view_factor_map(voxcity, show_plot=False, **kwargs):
|
|
229
|
+
"""
|
|
230
|
+
GPU-accelerated Sky View Factor calculation for VoxCity.
|
|
231
|
+
|
|
232
|
+
This function matches the signature of voxcity.simulator.visibility.get_sky_view_factor_map
|
|
233
|
+
using Taichi GPU acceleration.
|
|
234
|
+
|
|
235
|
+
Args:
|
|
236
|
+
voxcity: VoxCity object
|
|
237
|
+
show_plot: Whether to display a matplotlib plot
|
|
238
|
+
**kwargs: Additional parameters including:
|
|
239
|
+
- view_point_height (float): Observer height above ground (default: 1.5)
|
|
240
|
+
- N_azimuth (int): Number of azimuthal divisions (default: 120)
|
|
241
|
+
- N_elevation (int): Number of elevation divisions (default: 20)
|
|
242
|
+
- tree_k (float): Tree extinction coefficient (default: 0.6)
|
|
243
|
+
- tree_lad (float): Leaf area density (default: 1.0)
|
|
244
|
+
- colormap (str): Matplotlib colormap name (default: 'BuPu_r')
|
|
245
|
+
|
|
246
|
+
Returns:
|
|
247
|
+
2D array of SVF values
|
|
248
|
+
"""
|
|
249
|
+
n_azimuth = kwargs.pop('N_azimuth', kwargs.pop('n_azimuth', 120))
|
|
250
|
+
n_elevation = kwargs.pop('N_elevation', kwargs.pop('n_elevation', 20))
|
|
251
|
+
view_point_height = kwargs.pop('view_point_height', 1.5)
|
|
252
|
+
tree_k = kwargs.pop('tree_k', 0.6)
|
|
253
|
+
tree_lad = kwargs.pop('tree_lad', 1.0)
|
|
254
|
+
colormap = kwargs.pop('colormap', 'BuPu_r')
|
|
255
|
+
|
|
256
|
+
return get_view_index_gpu(
|
|
257
|
+
voxcity,
|
|
258
|
+
mode='sky',
|
|
259
|
+
view_point_height=view_point_height,
|
|
260
|
+
n_azimuth=n_azimuth,
|
|
261
|
+
n_elevation=n_elevation,
|
|
262
|
+
elevation_min_degrees=0.0,
|
|
263
|
+
elevation_max_degrees=90.0,
|
|
264
|
+
tree_k=tree_k,
|
|
265
|
+
tree_lad=tree_lad,
|
|
266
|
+
show_plot=show_plot,
|
|
267
|
+
colormap=colormap,
|
|
268
|
+
**kwargs
|
|
269
|
+
)
|
|
270
|
+
|
|
271
|
+
|
|
272
|
+
def get_surface_view_factor(voxcity, mode=None, **kwargs):
|
|
273
|
+
"""
|
|
274
|
+
GPU-accelerated Surface View Factor calculation for VoxCity.
|
|
275
|
+
|
|
276
|
+
This function matches the signature of voxcity.simulator.visibility.get_surface_view_factor
|
|
277
|
+
using Taichi GPU acceleration.
|
|
278
|
+
|
|
279
|
+
Computes view factors for building surface faces by tracing rays from
|
|
280
|
+
face centers through the voxel domain.
|
|
281
|
+
|
|
282
|
+
Args:
|
|
283
|
+
voxcity: VoxCity object
|
|
284
|
+
**kwargs: Additional parameters including:
|
|
285
|
+
- value_name (str): Name for the metadata field (default: 'view_factor_values')
|
|
286
|
+
- colormap (str): Matplotlib colormap name (default: 'BuPu_r')
|
|
287
|
+
- vmin, vmax (float): Colormap limits (default: 0.0, 1.0)
|
|
288
|
+
- N_azimuth (int): Number of azimuthal divisions (default: 120)
|
|
289
|
+
- N_elevation (int): Number of elevation divisions (default: 20)
|
|
290
|
+
- ray_sampling (str): 'grid' or 'fibonacci' (default: 'grid')
|
|
291
|
+
- tree_k (float): Tree extinction coefficient (default: 0.6)
|
|
292
|
+
- tree_lad (float): Leaf area density (default: 1.0)
|
|
293
|
+
- target_values (tuple): Target voxel values (default: (0,))
|
|
294
|
+
- inclusion_mode (bool): Inclusion vs exclusion mode (default: False)
|
|
295
|
+
- building_class_id (int): Building class ID for mesh extraction (default: -3)
|
|
296
|
+
- progress_report (bool): Show progress (default: False)
|
|
297
|
+
- obj_export (bool): Export mesh to OBJ (default: False)
|
|
298
|
+
|
|
299
|
+
Returns:
|
|
300
|
+
Trimesh object with view factor values in metadata
|
|
301
|
+
"""
|
|
302
|
+
voxel_data = voxcity.voxels.classes
|
|
303
|
+
meshsize = voxcity.voxels.meta.meshsize
|
|
304
|
+
building_id_grid = voxcity.buildings.ids
|
|
305
|
+
nx, ny, nz = voxel_data.shape
|
|
306
|
+
|
|
307
|
+
value_name = kwargs.get('value_name', 'view_factor_values')
|
|
308
|
+
n_azimuth = kwargs.get('N_azimuth', kwargs.get('n_azimuth', 120))
|
|
309
|
+
n_elevation = kwargs.get('N_elevation', kwargs.get('n_elevation', 20))
|
|
310
|
+
ray_sampling = kwargs.get('ray_sampling', 'grid')
|
|
311
|
+
n_rays = kwargs.get('N_rays', kwargs.get('n_rays', None))
|
|
312
|
+
tree_k = kwargs.get('tree_k', 0.6)
|
|
313
|
+
tree_lad = kwargs.get('tree_lad', 1.0)
|
|
314
|
+
building_class_id = kwargs.get('building_class_id', -3)
|
|
315
|
+
progress_report = kwargs.get('progress_report', False)
|
|
316
|
+
|
|
317
|
+
# Handle mode parameter
|
|
318
|
+
if mode == 'sky':
|
|
319
|
+
target_values = (0,)
|
|
320
|
+
inclusion_mode = False
|
|
321
|
+
elif mode == 'green':
|
|
322
|
+
target_values = (-2, 2, 5, 6, 7, 8)
|
|
323
|
+
inclusion_mode = True
|
|
324
|
+
else:
|
|
325
|
+
target_values = kwargs.get('target_values', (0,))
|
|
326
|
+
inclusion_mode = kwargs.get('inclusion_mode', False)
|
|
327
|
+
|
|
328
|
+
# Try to import mesh creation utility
|
|
329
|
+
try:
|
|
330
|
+
from voxcity.geoprocessor.mesh import create_voxel_mesh
|
|
331
|
+
except ImportError:
|
|
332
|
+
raise ImportError("VoxCity geoprocessor.mesh module required for surface view factor calculation")
|
|
333
|
+
|
|
334
|
+
# Create mesh from building voxels
|
|
335
|
+
try:
|
|
336
|
+
building_mesh = create_voxel_mesh(
|
|
337
|
+
voxel_data,
|
|
338
|
+
building_class_id,
|
|
339
|
+
meshsize,
|
|
340
|
+
building_id_grid=building_id_grid,
|
|
341
|
+
mesh_type='open_air'
|
|
342
|
+
)
|
|
343
|
+
if building_mesh is None or len(building_mesh.faces) == 0:
|
|
344
|
+
print("No surfaces found in voxel data for the specified class.")
|
|
345
|
+
return None
|
|
346
|
+
except Exception as e:
|
|
347
|
+
print(f"Error during mesh extraction: {e}")
|
|
348
|
+
return None
|
|
349
|
+
|
|
350
|
+
if progress_report:
|
|
351
|
+
print(f"Processing view factor for {len(building_mesh.faces)} faces...")
|
|
352
|
+
|
|
353
|
+
face_centers = building_mesh.triangles_center.astype(np.float32)
|
|
354
|
+
face_normals = building_mesh.face_normals.astype(np.float32)
|
|
355
|
+
|
|
356
|
+
# Create domain and calculator
|
|
357
|
+
domain = Domain(
|
|
358
|
+
nx=nx, ny=ny, nz=nz,
|
|
359
|
+
dx=meshsize, dy=meshsize, dz=meshsize
|
|
360
|
+
)
|
|
361
|
+
|
|
362
|
+
calc = SurfaceViewFactorCalculator(
|
|
363
|
+
domain,
|
|
364
|
+
n_azimuth=n_azimuth,
|
|
365
|
+
n_elevation=n_elevation,
|
|
366
|
+
ray_sampling=ray_sampling,
|
|
367
|
+
n_rays=n_rays
|
|
368
|
+
)
|
|
369
|
+
|
|
370
|
+
# Compute surface view factors
|
|
371
|
+
face_vf_values = calc.compute_surface_view_factor(
|
|
372
|
+
face_centers=face_centers,
|
|
373
|
+
face_normals=face_normals,
|
|
374
|
+
voxel_data=voxel_data,
|
|
375
|
+
target_values=target_values,
|
|
376
|
+
inclusion_mode=inclusion_mode,
|
|
377
|
+
tree_k=tree_k,
|
|
378
|
+
tree_lad=tree_lad
|
|
379
|
+
)
|
|
380
|
+
|
|
381
|
+
# Add values to mesh metadata
|
|
382
|
+
if not hasattr(building_mesh, 'metadata'):
|
|
383
|
+
building_mesh.metadata = {}
|
|
384
|
+
building_mesh.metadata[value_name] = face_vf_values
|
|
385
|
+
|
|
386
|
+
# Export if requested
|
|
387
|
+
obj_export = kwargs.get('obj_export', False)
|
|
388
|
+
if obj_export:
|
|
389
|
+
import os
|
|
390
|
+
output_dir = kwargs.get('output_directory', 'output')
|
|
391
|
+
output_file_name = kwargs.get('output_file_name', 'surface_view_factor')
|
|
392
|
+
os.makedirs(output_dir, exist_ok=True)
|
|
393
|
+
try:
|
|
394
|
+
building_mesh.export(f"{output_dir}/{output_file_name}.obj")
|
|
395
|
+
if progress_report:
|
|
396
|
+
print(f"Exported surface mesh to {output_dir}/{output_file_name}.obj")
|
|
397
|
+
except Exception as e:
|
|
398
|
+
print(f"Error exporting mesh: {e}")
|
|
399
|
+
|
|
400
|
+
return building_mesh
|
|
401
|
+
|
|
402
|
+
|
|
403
|
+
def get_landmark_visibility_map(voxcity, building_gdf=None, **kwargs):
|
|
404
|
+
"""
|
|
405
|
+
GPU-accelerated Landmark Visibility Map calculation for VoxCity.
|
|
406
|
+
|
|
407
|
+
This function matches the signature of voxcity.simulator.visibility.get_landmark_visibility_map
|
|
408
|
+
using Taichi GPU acceleration.
|
|
409
|
+
|
|
410
|
+
Args:
|
|
411
|
+
voxcity: VoxCity object
|
|
412
|
+
building_gdf: GeoDataFrame of buildings (optional, will use voxcity.extras['building_gdf'])
|
|
413
|
+
**kwargs: Additional parameters including:
|
|
414
|
+
- view_point_height (float): Observer height above ground (default: 1.5)
|
|
415
|
+
- colormap (str): Matplotlib colormap name (default: 'viridis')
|
|
416
|
+
- landmark_building_ids (list): List of building IDs to mark as landmarks
|
|
417
|
+
- landmark_polygon: Polygon to select landmark buildings
|
|
418
|
+
- tree_k (float): Tree extinction coefficient (default: 0.6)
|
|
419
|
+
- tree_lad (float): Leaf area density (default: 1.0)
|
|
420
|
+
- obj_export (bool): Export results to OBJ (default: False)
|
|
421
|
+
|
|
422
|
+
Returns:
|
|
423
|
+
Tuple of (visibility_map, modified_voxel_data)
|
|
424
|
+
"""
|
|
425
|
+
landmark_building_ids = kwargs.pop('landmark_building_ids', None)
|
|
426
|
+
view_point_height = kwargs.pop('view_point_height', 1.5)
|
|
427
|
+
tree_k = kwargs.pop('tree_k', 0.6)
|
|
428
|
+
tree_lad = kwargs.pop('tree_lad', 1.0)
|
|
429
|
+
colormap = kwargs.pop('colormap', 'viridis')
|
|
430
|
+
|
|
431
|
+
# Get landmark IDs from various sources if not provided
|
|
432
|
+
if landmark_building_ids is None:
|
|
433
|
+
landmark_polygon = kwargs.get('landmark_polygon', None)
|
|
434
|
+
if landmark_polygon is not None and building_gdf is not None:
|
|
435
|
+
try:
|
|
436
|
+
from voxcity.geoprocessor.selection import get_buildings_in_drawn_polygon
|
|
437
|
+
# Convert landmark_polygon to VoxCity expected format
|
|
438
|
+
# VoxCity expects: [{'vertices': [(x1,y1), (x2,y2), ...]}]
|
|
439
|
+
if hasattr(landmark_polygon, 'exterior'):
|
|
440
|
+
# Single shapely Polygon - convert to VoxCity format
|
|
441
|
+
polygons = [{'vertices': list(landmark_polygon.exterior.coords)}]
|
|
442
|
+
elif isinstance(landmark_polygon, list) and len(landmark_polygon) > 0:
|
|
443
|
+
if isinstance(landmark_polygon[0], dict) and 'vertices' in landmark_polygon[0]:
|
|
444
|
+
# Already in VoxCity format
|
|
445
|
+
polygons = landmark_polygon
|
|
446
|
+
elif hasattr(landmark_polygon[0], 'exterior'):
|
|
447
|
+
# List of shapely Polygons
|
|
448
|
+
polygons = [{'vertices': list(p.exterior.coords)} for p in landmark_polygon]
|
|
449
|
+
else:
|
|
450
|
+
# Assume list of coordinate tuples - wrap as single polygon
|
|
451
|
+
polygons = [{'vertices': landmark_polygon}]
|
|
452
|
+
else:
|
|
453
|
+
polygons = landmark_polygon
|
|
454
|
+
# Use 'intersect' to find buildings that touch/overlap the polygon
|
|
455
|
+
landmark_building_ids = get_buildings_in_drawn_polygon(building_gdf, polygons, operation='intersect')
|
|
456
|
+
except ImportError:
|
|
457
|
+
pass
|
|
458
|
+
|
|
459
|
+
if landmark_building_ids is None:
|
|
460
|
+
rectangle_vertices = kwargs.get('rectangle_vertices', None)
|
|
461
|
+
if rectangle_vertices is None:
|
|
462
|
+
rectangle_vertices = voxcity.extras.get('rectangle_vertices', None)
|
|
463
|
+
if rectangle_vertices is not None and building_gdf is not None:
|
|
464
|
+
try:
|
|
465
|
+
from voxcity.geoprocessor.selection import find_building_containing_point
|
|
466
|
+
lons = [coord[0] for coord in rectangle_vertices]
|
|
467
|
+
lats = [coord[1] for coord in rectangle_vertices]
|
|
468
|
+
center_lon = (min(lons) + max(lons)) / 2
|
|
469
|
+
center_lat = (min(lats) + max(lats)) / 2
|
|
470
|
+
target_point = (center_lon, center_lat)
|
|
471
|
+
landmark_building_ids = find_building_containing_point(building_gdf, target_point)
|
|
472
|
+
except ImportError:
|
|
473
|
+
pass
|
|
474
|
+
|
|
475
|
+
if landmark_building_ids is None:
|
|
476
|
+
print("Cannot set landmark buildings. You need to input either of rectangle_vertices or landmark_building_ids.")
|
|
477
|
+
return None
|
|
478
|
+
|
|
479
|
+
return get_landmark_visibility_map_gpu(
|
|
480
|
+
voxcity,
|
|
481
|
+
building_gdf=building_gdf,
|
|
482
|
+
landmark_building_ids=landmark_building_ids,
|
|
483
|
+
view_point_height=view_point_height,
|
|
484
|
+
tree_k=tree_k,
|
|
485
|
+
tree_lad=tree_lad,
|
|
486
|
+
show_plot=True, # VoxCity default shows plot
|
|
487
|
+
colormap=colormap,
|
|
488
|
+
**kwargs
|
|
489
|
+
)
|
|
490
|
+
|
|
491
|
+
|
|
492
|
+
def get_surface_landmark_visibility(voxcity, building_gdf=None, **kwargs):
|
|
493
|
+
"""
|
|
494
|
+
GPU-accelerated Surface Landmark Visibility calculation for VoxCity.
|
|
495
|
+
|
|
496
|
+
This function matches the signature of voxcity.simulator.visibility.get_surface_landmark_visibility
|
|
497
|
+
using Taichi GPU acceleration.
|
|
498
|
+
|
|
499
|
+
Computes landmark visibility for building surface faces.
|
|
500
|
+
|
|
501
|
+
Args:
|
|
502
|
+
voxcity: VoxCity object
|
|
503
|
+
building_gdf: GeoDataFrame of buildings
|
|
504
|
+
**kwargs: Additional parameters including:
|
|
505
|
+
- landmark_building_ids (list): List of building IDs to mark as landmarks
|
|
506
|
+
- landmark_polygon: Polygon to select landmark buildings
|
|
507
|
+
- tree_k (float): Tree extinction coefficient (default: 0.6)
|
|
508
|
+
- tree_lad (float): Leaf area density (default: 1.0)
|
|
509
|
+
- building_class_id (int): Building class ID (default: -3)
|
|
510
|
+
- progress_report (bool): Show progress (default: False)
|
|
511
|
+
- colormap (str): Matplotlib colormap name (default: 'RdYlGn')
|
|
512
|
+
- obj_export (bool): Export mesh to OBJ (default: False)
|
|
513
|
+
|
|
514
|
+
Returns:
|
|
515
|
+
Tuple of (building_mesh with visibility, modified_voxel_data)
|
|
516
|
+
"""
|
|
517
|
+
if building_gdf is None:
|
|
518
|
+
building_gdf = voxcity.extras.get('building_gdf', None)
|
|
519
|
+
if building_gdf is None:
|
|
520
|
+
raise ValueError("building_gdf not provided and not found in voxcity.extras['building_gdf']")
|
|
521
|
+
|
|
522
|
+
voxel_data = voxcity.voxels.classes
|
|
523
|
+
building_id_grid = voxcity.buildings.ids
|
|
524
|
+
meshsize = voxcity.voxels.meta.meshsize
|
|
525
|
+
nx, ny, nz = voxel_data.shape
|
|
526
|
+
|
|
527
|
+
progress_report = kwargs.get('progress_report', False)
|
|
528
|
+
landmark_building_ids = kwargs.get('landmark_building_ids', None)
|
|
529
|
+
landmark_polygon = kwargs.get('landmark_polygon', None)
|
|
530
|
+
tree_k = kwargs.get('tree_k', 0.6)
|
|
531
|
+
tree_lad = kwargs.get('tree_lad', 1.0)
|
|
532
|
+
building_class_id = kwargs.get('building_class_id', -3)
|
|
533
|
+
colormap = kwargs.get('colormap', 'RdYlGn')
|
|
534
|
+
landmark_value = -30
|
|
535
|
+
|
|
536
|
+
# Get landmark IDs
|
|
537
|
+
if landmark_building_ids is None:
|
|
538
|
+
if landmark_polygon is not None:
|
|
539
|
+
try:
|
|
540
|
+
from voxcity.geoprocessor.selection import get_buildings_in_drawn_polygon
|
|
541
|
+
# Convert landmark_polygon to VoxCity expected format
|
|
542
|
+
# VoxCity expects: [{'vertices': [(x1,y1), (x2,y2), ...]}]
|
|
543
|
+
if hasattr(landmark_polygon, 'exterior'):
|
|
544
|
+
# Single shapely Polygon - convert to VoxCity format
|
|
545
|
+
polygons = [{'vertices': list(landmark_polygon.exterior.coords)}]
|
|
546
|
+
elif isinstance(landmark_polygon, list) and len(landmark_polygon) > 0:
|
|
547
|
+
if isinstance(landmark_polygon[0], dict) and 'vertices' in landmark_polygon[0]:
|
|
548
|
+
# Already in VoxCity format
|
|
549
|
+
polygons = landmark_polygon
|
|
550
|
+
elif hasattr(landmark_polygon[0], 'exterior'):
|
|
551
|
+
# List of shapely Polygons
|
|
552
|
+
polygons = [{'vertices': list(p.exterior.coords)} for p in landmark_polygon]
|
|
553
|
+
else:
|
|
554
|
+
# Assume list of coordinate tuples - wrap as single polygon
|
|
555
|
+
polygons = [{'vertices': landmark_polygon}]
|
|
556
|
+
else:
|
|
557
|
+
polygons = landmark_polygon
|
|
558
|
+
# Use 'intersect' to find buildings that touch/overlap the polygon
|
|
559
|
+
landmark_building_ids = get_buildings_in_drawn_polygon(building_gdf, polygons, operation='intersect')
|
|
560
|
+
except ImportError:
|
|
561
|
+
pass
|
|
562
|
+
else:
|
|
563
|
+
rectangle_vertices = kwargs.get('rectangle_vertices', None)
|
|
564
|
+
if rectangle_vertices is None:
|
|
565
|
+
rectangle_vertices = voxcity.extras.get('rectangle_vertices', None)
|
|
566
|
+
if rectangle_vertices is None:
|
|
567
|
+
print("Cannot set landmark buildings. You need to input either rectangle_vertices or landmark_building_ids.")
|
|
568
|
+
return None, None
|
|
569
|
+
try:
|
|
570
|
+
from voxcity.geoprocessor.selection import find_building_containing_point
|
|
571
|
+
lons = [coord[0] for coord in rectangle_vertices]
|
|
572
|
+
lats = [coord[1] for coord in rectangle_vertices]
|
|
573
|
+
center_lon = (min(lons) + max(lons)) / 2
|
|
574
|
+
center_lat = (min(lats) + max(lats)) / 2
|
|
575
|
+
target_point = (center_lon, center_lat)
|
|
576
|
+
landmark_building_ids = find_building_containing_point(building_gdf, target_point)
|
|
577
|
+
except ImportError:
|
|
578
|
+
pass
|
|
579
|
+
|
|
580
|
+
if landmark_building_ids is None:
|
|
581
|
+
print("Cannot set landmark buildings. No landmark_building_ids found.")
|
|
582
|
+
return None, None
|
|
583
|
+
|
|
584
|
+
# Prepare voxel data
|
|
585
|
+
voxel_data_for_mesh = voxel_data.copy()
|
|
586
|
+
voxel_data_modified = voxel_data.copy()
|
|
587
|
+
voxel_data_modified = mark_building_by_id(voxel_data_modified, building_id_grid, landmark_building_ids, landmark_value)
|
|
588
|
+
voxel_data_for_mesh = mark_building_by_id(voxel_data_for_mesh, building_id_grid, landmark_building_ids, 0)
|
|
589
|
+
|
|
590
|
+
landmark_positions = np.argwhere(voxel_data_modified == landmark_value).astype(np.float32)
|
|
591
|
+
if landmark_positions.shape[0] == 0:
|
|
592
|
+
print(f"No landmarks found after marking buildings with IDs: {landmark_building_ids}")
|
|
593
|
+
return None, None
|
|
594
|
+
|
|
595
|
+
if progress_report:
|
|
596
|
+
print(f"Found {landmark_positions.shape[0]} landmark voxels")
|
|
597
|
+
print(f"Landmark building IDs: {landmark_building_ids}")
|
|
598
|
+
|
|
599
|
+
# Create mesh
|
|
600
|
+
try:
|
|
601
|
+
from voxcity.geoprocessor.mesh import create_voxel_mesh
|
|
602
|
+
building_mesh = create_voxel_mesh(
|
|
603
|
+
voxel_data_for_mesh,
|
|
604
|
+
building_class_id,
|
|
605
|
+
meshsize,
|
|
606
|
+
building_id_grid=building_id_grid,
|
|
607
|
+
mesh_type='open_air'
|
|
608
|
+
)
|
|
609
|
+
if building_mesh is None or len(building_mesh.faces) == 0:
|
|
610
|
+
print("No non-landmark building surfaces found in voxel data.")
|
|
611
|
+
return None, None
|
|
612
|
+
except ImportError:
|
|
613
|
+
raise ImportError("VoxCity geoprocessor.mesh module required for surface landmark visibility")
|
|
614
|
+
except Exception as e:
|
|
615
|
+
print(f"Error during mesh extraction: {e}")
|
|
616
|
+
return None, None
|
|
617
|
+
|
|
618
|
+
if progress_report:
|
|
619
|
+
print(f"Processing landmark visibility for {len(building_mesh.faces)} faces...")
|
|
620
|
+
|
|
621
|
+
face_centers = building_mesh.triangles_center.astype(np.float32)
|
|
622
|
+
face_normals = building_mesh.face_normals.astype(np.float32)
|
|
623
|
+
|
|
624
|
+
# Create domain and calculator
|
|
625
|
+
domain = Domain(
|
|
626
|
+
nx=nx, ny=ny, nz=nz,
|
|
627
|
+
dx=meshsize, dy=meshsize, dz=meshsize
|
|
628
|
+
)
|
|
629
|
+
|
|
630
|
+
calc = SurfaceLandmarkVisibilityCalculator(domain)
|
|
631
|
+
calc.set_landmarks_from_positions(landmark_positions)
|
|
632
|
+
|
|
633
|
+
# Compute surface landmark visibility
|
|
634
|
+
visibility_values = calc.compute_surface_landmark_visibility(
|
|
635
|
+
face_centers=face_centers,
|
|
636
|
+
face_normals=face_normals,
|
|
637
|
+
voxel_data=voxel_data_modified,
|
|
638
|
+
landmark_value=landmark_value,
|
|
639
|
+
tree_k=tree_k,
|
|
640
|
+
tree_lad=tree_lad
|
|
641
|
+
)
|
|
642
|
+
|
|
643
|
+
# Add to mesh metadata
|
|
644
|
+
building_mesh.metadata = getattr(building_mesh, 'metadata', {})
|
|
645
|
+
building_mesh.metadata['landmark_visibility'] = visibility_values
|
|
646
|
+
|
|
647
|
+
if progress_report:
|
|
648
|
+
valid_mask = ~np.isnan(visibility_values)
|
|
649
|
+
n_valid = np.sum(valid_mask)
|
|
650
|
+
n_visible = np.sum(visibility_values[valid_mask] > 0.5)
|
|
651
|
+
print(f"Landmark visibility statistics:")
|
|
652
|
+
print(f" Total faces: {len(visibility_values)}")
|
|
653
|
+
print(f" Valid faces: {n_valid}")
|
|
654
|
+
print(f" Faces with landmark visibility: {n_visible} ({n_visible/n_valid*100:.1f}%)")
|
|
655
|
+
|
|
656
|
+
# Export if requested
|
|
657
|
+
obj_export = kwargs.get('obj_export', False)
|
|
658
|
+
if obj_export:
|
|
659
|
+
import os
|
|
660
|
+
try:
|
|
661
|
+
import matplotlib.pyplot as plt
|
|
662
|
+
output_dir = kwargs.get('output_directory', 'output')
|
|
663
|
+
output_file_name = kwargs.get('output_file_name', 'surface_landmark_visibility')
|
|
664
|
+
os.makedirs(output_dir, exist_ok=True)
|
|
665
|
+
|
|
666
|
+
cmap = plt.cm.get_cmap(colormap)
|
|
667
|
+
face_colors = np.zeros((len(visibility_values), 4))
|
|
668
|
+
for i, val in enumerate(visibility_values):
|
|
669
|
+
if np.isnan(val):
|
|
670
|
+
face_colors[i] = [0.7, 0.7, 0.7, 1.0]
|
|
671
|
+
else:
|
|
672
|
+
face_colors[i] = cmap(val)
|
|
673
|
+
building_mesh.visual.face_colors = face_colors
|
|
674
|
+
building_mesh.export(f"{output_dir}/{output_file_name}.obj")
|
|
675
|
+
if progress_report:
|
|
676
|
+
print(f"Exported surface mesh to {output_dir}/{output_file_name}.obj")
|
|
677
|
+
except Exception as e:
|
|
678
|
+
print(f"Error exporting mesh: {e}")
|
|
679
|
+
|
|
680
|
+
return building_mesh, voxel_data_modified
|
|
681
|
+
|
|
682
|
+
|
|
683
|
+
def get_sky_view_factor_map_gpu(
|
|
684
|
+
voxcity,
|
|
685
|
+
view_point_height: float = 1.5,
|
|
686
|
+
n_azimuth: int = 120,
|
|
687
|
+
n_elevation: int = 20,
|
|
688
|
+
tree_k: float = 0.6,
|
|
689
|
+
tree_lad: float = 1.0,
|
|
690
|
+
show_plot: bool = False,
|
|
691
|
+
**kwargs
|
|
692
|
+
) -> np.ndarray:
|
|
693
|
+
"""
|
|
694
|
+
GPU-accelerated Sky View Factor calculation for VoxCity.
|
|
695
|
+
|
|
696
|
+
Legacy function - use get_sky_view_factor_map() for VoxCity API compatibility.
|
|
697
|
+
|
|
698
|
+
Args:
|
|
699
|
+
voxcity: VoxCity object
|
|
700
|
+
view_point_height: Observer height above ground (meters)
|
|
701
|
+
n_azimuth: Number of azimuthal divisions
|
|
702
|
+
n_elevation: Number of elevation divisions
|
|
703
|
+
tree_k: Tree extinction coefficient
|
|
704
|
+
tree_lad: Leaf area density
|
|
705
|
+
show_plot: Whether to display a matplotlib plot
|
|
706
|
+
**kwargs: Additional parameters
|
|
707
|
+
|
|
708
|
+
Returns:
|
|
709
|
+
2D array of SVF values
|
|
710
|
+
"""
|
|
711
|
+
return get_view_index_gpu(
|
|
712
|
+
voxcity,
|
|
713
|
+
mode='sky',
|
|
714
|
+
view_point_height=view_point_height,
|
|
715
|
+
n_azimuth=n_azimuth,
|
|
716
|
+
n_elevation=n_elevation,
|
|
717
|
+
elevation_min_degrees=0.0,
|
|
718
|
+
elevation_max_degrees=90.0,
|
|
719
|
+
tree_k=tree_k,
|
|
720
|
+
tree_lad=tree_lad,
|
|
721
|
+
show_plot=show_plot,
|
|
722
|
+
**kwargs
|
|
723
|
+
)
|
|
724
|
+
|
|
725
|
+
|
|
726
|
+
def get_landmark_visibility_map_gpu(
|
|
727
|
+
voxcity,
|
|
728
|
+
building_gdf=None,
|
|
729
|
+
landmark_building_ids: List[int] = None,
|
|
730
|
+
view_point_height: float = 1.5,
|
|
731
|
+
tree_k: float = 0.6,
|
|
732
|
+
tree_lad: float = 1.0,
|
|
733
|
+
show_plot: bool = False,
|
|
734
|
+
**kwargs
|
|
735
|
+
) -> Tuple[np.ndarray, np.ndarray]:
|
|
736
|
+
"""
|
|
737
|
+
GPU-accelerated Landmark Visibility calculation for VoxCity.
|
|
738
|
+
|
|
739
|
+
Legacy function - use get_landmark_visibility_map() for VoxCity API compatibility.
|
|
740
|
+
|
|
741
|
+
Args:
|
|
742
|
+
voxcity: VoxCity object
|
|
743
|
+
building_gdf: GeoDataFrame of buildings (optional)
|
|
744
|
+
landmark_building_ids: List of building IDs to mark as landmarks
|
|
745
|
+
view_point_height: Observer height above ground (meters)
|
|
746
|
+
tree_k: Tree extinction coefficient
|
|
747
|
+
tree_lad: Leaf area density
|
|
748
|
+
show_plot: Whether to display a matplotlib plot
|
|
749
|
+
**kwargs: Additional parameters
|
|
750
|
+
|
|
751
|
+
Returns:
|
|
752
|
+
Tuple of (visibility_map, modified_voxel_data)
|
|
753
|
+
"""
|
|
754
|
+
if landmark_building_ids is None:
|
|
755
|
+
raise ValueError("landmark_building_ids must be provided")
|
|
756
|
+
|
|
757
|
+
voxel_data = voxcity.voxels.classes
|
|
758
|
+
building_id_grid = voxcity.buildings.ids
|
|
759
|
+
meshsize = voxcity.voxels.meta.meshsize
|
|
760
|
+
nx, ny, nz = voxel_data.shape
|
|
761
|
+
|
|
762
|
+
view_height_voxel = int(view_point_height / meshsize)
|
|
763
|
+
|
|
764
|
+
# Mark landmark buildings
|
|
765
|
+
target_value = -30
|
|
766
|
+
voxel_data_modified = mark_building_by_id(
|
|
767
|
+
voxel_data, building_id_grid, landmark_building_ids, target_value
|
|
768
|
+
)
|
|
769
|
+
|
|
770
|
+
# Create domain
|
|
771
|
+
domain = Domain(
|
|
772
|
+
nx=nx, ny=ny, nz=nz,
|
|
773
|
+
dx=meshsize, dy=meshsize, dz=meshsize
|
|
774
|
+
)
|
|
775
|
+
|
|
776
|
+
# Create calculator
|
|
777
|
+
calc = LandmarkVisibilityCalculator(domain)
|
|
778
|
+
calc.set_landmarks_from_voxel_value(voxel_data_modified, target_value)
|
|
779
|
+
|
|
780
|
+
# Compute visibility
|
|
781
|
+
visibility_map = calc.compute_visibility_map(
|
|
782
|
+
voxel_data=voxel_data_modified,
|
|
783
|
+
view_height_voxel=view_height_voxel,
|
|
784
|
+
tree_k=tree_k,
|
|
785
|
+
tree_lad=tree_lad
|
|
786
|
+
)
|
|
787
|
+
|
|
788
|
+
# Plot if requested
|
|
789
|
+
if show_plot:
|
|
790
|
+
try:
|
|
791
|
+
import matplotlib.pyplot as plt
|
|
792
|
+
import matplotlib.patches as mpatches
|
|
793
|
+
colormap = kwargs.get('colormap', 'viridis')
|
|
794
|
+
|
|
795
|
+
cmap = plt.cm.get_cmap(colormap, 2).copy()
|
|
796
|
+
cmap.set_bad(color='lightgray')
|
|
797
|
+
plt.figure(figsize=(10, 8))
|
|
798
|
+
plt.imshow(visibility_map, origin='lower', cmap=cmap, vmin=0, vmax=1)
|
|
799
|
+
visible_patch = mpatches.Patch(color=cmap(1.0), label='Visible (1)')
|
|
800
|
+
not_visible_patch = mpatches.Patch(color=cmap(0.0), label='Not Visible (0)')
|
|
801
|
+
plt.legend(handles=[visible_patch, not_visible_patch],
|
|
802
|
+
loc='center left', bbox_to_anchor=(1.0, 0.5))
|
|
803
|
+
plt.axis('off')
|
|
804
|
+
plt.show()
|
|
805
|
+
except ImportError:
|
|
806
|
+
pass
|
|
807
|
+
|
|
808
|
+
return visibility_map, voxel_data_modified
|