voxcity 1.0.2__py3-none-any.whl → 1.0.15__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.
Files changed (41) hide show
  1. voxcity/downloader/ocean.py +559 -0
  2. voxcity/generator/api.py +6 -0
  3. voxcity/generator/grids.py +45 -32
  4. voxcity/generator/pipeline.py +327 -27
  5. voxcity/geoprocessor/draw.py +14 -8
  6. voxcity/geoprocessor/raster/__init__.py +2 -0
  7. voxcity/geoprocessor/raster/core.py +31 -0
  8. voxcity/geoprocessor/raster/landcover.py +173 -49
  9. voxcity/geoprocessor/raster/raster.py +1 -1
  10. voxcity/models.py +2 -0
  11. voxcity/simulator/solar/__init__.py +13 -0
  12. voxcity/simulator_gpu/__init__.py +90 -0
  13. voxcity/simulator_gpu/core.py +322 -0
  14. voxcity/simulator_gpu/domain.py +36 -0
  15. voxcity/simulator_gpu/init_taichi.py +154 -0
  16. voxcity/simulator_gpu/raytracing.py +776 -0
  17. voxcity/simulator_gpu/solar/__init__.py +222 -0
  18. voxcity/simulator_gpu/solar/core.py +66 -0
  19. voxcity/simulator_gpu/solar/csf.py +1249 -0
  20. voxcity/simulator_gpu/solar/domain.py +618 -0
  21. voxcity/simulator_gpu/solar/epw.py +421 -0
  22. voxcity/simulator_gpu/solar/integration.py +4322 -0
  23. voxcity/simulator_gpu/solar/mask.py +459 -0
  24. voxcity/simulator_gpu/solar/radiation.py +3019 -0
  25. voxcity/simulator_gpu/solar/raytracing.py +182 -0
  26. voxcity/simulator_gpu/solar/reflection.py +533 -0
  27. voxcity/simulator_gpu/solar/sky.py +907 -0
  28. voxcity/simulator_gpu/solar/solar.py +337 -0
  29. voxcity/simulator_gpu/solar/svf.py +446 -0
  30. voxcity/simulator_gpu/solar/volumetric.py +2099 -0
  31. voxcity/simulator_gpu/visibility/__init__.py +109 -0
  32. voxcity/simulator_gpu/visibility/geometry.py +278 -0
  33. voxcity/simulator_gpu/visibility/integration.py +808 -0
  34. voxcity/simulator_gpu/visibility/landmark.py +753 -0
  35. voxcity/simulator_gpu/visibility/view.py +944 -0
  36. voxcity/visualizer/renderer.py +2 -1
  37. {voxcity-1.0.2.dist-info → voxcity-1.0.15.dist-info}/METADATA +16 -53
  38. {voxcity-1.0.2.dist-info → voxcity-1.0.15.dist-info}/RECORD +41 -16
  39. {voxcity-1.0.2.dist-info → voxcity-1.0.15.dist-info}/WHEEL +0 -0
  40. {voxcity-1.0.2.dist-info → voxcity-1.0.15.dist-info}/licenses/AUTHORS.rst +0 -0
  41. {voxcity-1.0.2.dist-info → voxcity-1.0.15.dist-info}/licenses/LICENSE +0 -0
@@ -0,0 +1,337 @@
1
+ """
2
+ Solar position calculation for palm-solar.
3
+
4
+ Based on PALM's calc_zenith subroutine (radiation_model_mod.f90 lines 7965-8012).
5
+ Computes solar declination, hour angle, zenith angle, and direction vector.
6
+
7
+ PALM Alignment:
8
+ - Declination formula: ASIN(decl_1 * SIN(decl_2 * day_of_year - decl_3))
9
+ - Hour angle formula: 2π * (second_of_day / 86400) + longitude - π
10
+ - cos_zenith: sin(lat)*sin(decl) + cos(lat)*cos(decl)*cos(hour_angle)
11
+ - Sun direction: Computed from declination and hour angle
12
+
13
+ All constants match PALM exactly:
14
+ - decl_1 = sin(23.45°) = 0.39794968147687266
15
+ - decl_2 = 2π/365 = 0.017214206321039962
16
+ - decl_3 = decl_2 * 81 = 1.3943507120042368 (vernal equinox offset)
17
+ """
18
+
19
+ import taichi as ti
20
+ import math
21
+ from datetime import datetime, timezone
22
+ from typing import Tuple, Optional
23
+ from dataclasses import dataclass
24
+
25
+ from .core import Vector3, Point3, DEG_TO_RAD, RAD_TO_DEG, PI, TWO_PI
26
+
27
+
28
+ # Constants for solar declination calculation (matching PALM exactly)
29
+ # PALM: decl_1 = SIN( 23.45_wp * pi / 180.0_wp )
30
+ # PALM: decl_2 = 2.0_wp * pi / 365.0_wp
31
+ # PALM: decl_3 = decl_2 * 81.0_wp (offset for vernal equinox ~March 21)
32
+ DECL_1 = 0.39794968147687266 # sin(23.45 * pi / 180)
33
+ DECL_2 = 0.017214206321039962 # 2 * pi / 365
34
+ DECL_3 = 1.3943507120042368 # DECL_2 * 81
35
+
36
+ # Seconds per day
37
+ SECONDS_PER_DAY = 86400.0
38
+
39
+
40
+ @dataclass
41
+ class SolarPosition:
42
+ """
43
+ Solar position data.
44
+
45
+ Attributes:
46
+ cos_zenith: Cosine of solar zenith angle (0 at horizon, 1 at zenith)
47
+ zenith_angle: Solar zenith angle in degrees
48
+ azimuth_angle: Solar azimuth angle in degrees (0 = North, 90 = East)
49
+ elevation_angle: Solar elevation angle in degrees (0 = horizon, 90 = zenith)
50
+ direction: Unit vector pointing towards the sun (x, y, z)
51
+ sun_up: True if sun is above horizon
52
+ """
53
+ cos_zenith: float
54
+ zenith_angle: float
55
+ azimuth_angle: float
56
+ elevation_angle: float
57
+ direction: Tuple[float, float, float]
58
+ sun_up: bool
59
+
60
+
61
+ def calc_zenith(
62
+ day_of_year: int,
63
+ second_of_day: float,
64
+ latitude: float,
65
+ longitude: float
66
+ ) -> SolarPosition:
67
+ """
68
+ Calculate solar position.
69
+
70
+ Based on PALM's calc_zenith subroutine.
71
+
72
+ Args:
73
+ day_of_year: Day number (1-365)
74
+ second_of_day: Seconds since midnight UTC
75
+ latitude: Latitude in degrees (-90 to 90)
76
+ longitude: Longitude in degrees (-180 to 180)
77
+
78
+ Returns:
79
+ SolarPosition with all solar geometry data
80
+ """
81
+ # Convert to radians
82
+ lat = latitude * DEG_TO_RAD
83
+ lon = longitude * DEG_TO_RAD
84
+
85
+ # Solar declination angle
86
+ declination = math.asin(DECL_1 * math.sin(DECL_2 * day_of_year - DECL_3))
87
+
88
+ # Hour angle (solar noon at lon=0 is at 12:00 UTC)
89
+ hour_angle = TWO_PI * (second_of_day / SECONDS_PER_DAY) + lon - PI
90
+
91
+ # Cosine of zenith angle
92
+ cos_zenith = (math.sin(lat) * math.sin(declination) +
93
+ math.cos(lat) * math.cos(declination) * math.cos(hour_angle))
94
+ cos_zenith = max(0.0, cos_zenith)
95
+
96
+ # Zenith and elevation angles
97
+ zenith_angle = math.acos(min(1.0, cos_zenith)) * RAD_TO_DEG
98
+ elevation_angle = 90.0 - zenith_angle
99
+
100
+ # Solar direction vector (x=east, y=north, z=up)
101
+ # Direction in longitudes = sin(solar_azimuth) * sin(zenith)
102
+ sun_dir_lon = -math.sin(hour_angle) * math.cos(declination)
103
+
104
+ # Direction in latitudes = cos(solar_azimuth) * sin(zenith)
105
+ sun_dir_lat = (math.sin(declination) * math.cos(lat) -
106
+ math.cos(hour_angle) * math.cos(declination) * math.sin(lat))
107
+
108
+ # Normalize to get unit vector pointing toward sun
109
+ sin_zenith = math.sqrt(1.0 - cos_zenith**2) if cos_zenith < 1.0 else 0.0
110
+
111
+ if sin_zenith > 1e-10:
112
+ # Horizontal components
113
+ sun_x = sun_dir_lon # East component
114
+ sun_y = sun_dir_lat # North component
115
+ sun_z = cos_zenith # Up component
116
+
117
+ # Normalize
118
+ length = math.sqrt(sun_x**2 + sun_y**2 + sun_z**2)
119
+ if length > 1e-10:
120
+ sun_x /= length
121
+ sun_y /= length
122
+ sun_z /= length
123
+ else:
124
+ # Sun at zenith
125
+ sun_x = 0.0
126
+ sun_y = 0.0
127
+ sun_z = 1.0
128
+
129
+ # Azimuth angle (0 = North, 90 = East)
130
+ azimuth_angle = math.atan2(sun_x, sun_y) * RAD_TO_DEG
131
+ if azimuth_angle < 0:
132
+ azimuth_angle += 360.0
133
+
134
+ sun_up = cos_zenith > 0.0
135
+
136
+ return SolarPosition(
137
+ cos_zenith=cos_zenith,
138
+ zenith_angle=zenith_angle,
139
+ azimuth_angle=azimuth_angle,
140
+ elevation_angle=elevation_angle,
141
+ direction=(sun_x, sun_y, sun_z),
142
+ sun_up=sun_up
143
+ )
144
+
145
+
146
+ def calc_solar_position_datetime(
147
+ dt: datetime,
148
+ latitude: float,
149
+ longitude: float
150
+ ) -> SolarPosition:
151
+ """
152
+ Calculate solar position from datetime.
153
+
154
+ Args:
155
+ dt: Datetime (should be in UTC or timezone-aware)
156
+ latitude: Latitude in degrees
157
+ longitude: Longitude in degrees
158
+
159
+ Returns:
160
+ SolarPosition
161
+ """
162
+ # Convert to UTC if timezone-aware
163
+ if dt.tzinfo is not None:
164
+ dt = dt.astimezone(timezone.utc)
165
+
166
+ # Day of year (1-365)
167
+ day_of_year = dt.timetuple().tm_yday
168
+
169
+ # Seconds since midnight UTC
170
+ second_of_day = dt.hour * 3600.0 + dt.minute * 60.0 + dt.second + dt.microsecond / 1e6
171
+
172
+ return calc_zenith(day_of_year, second_of_day, latitude, longitude)
173
+
174
+
175
+ def get_day_of_year(year: int, month: int, day: int) -> int:
176
+ """Get day of year from date."""
177
+ return datetime(year, month, day).timetuple().tm_yday
178
+
179
+
180
+ @ti.func
181
+ def calc_zenith_ti(
182
+ day_of_year: ti.i32,
183
+ second_of_day: ti.f32,
184
+ lat_rad: ti.f32,
185
+ lon_rad: ti.f32
186
+ ) -> ti.math.vec4:
187
+ """
188
+ Taichi function to calculate solar position.
189
+
190
+ Args:
191
+ day_of_year: Day number (1-365)
192
+ second_of_day: Seconds since midnight UTC
193
+ lat_rad: Latitude in radians
194
+ lon_rad: Longitude in radians
195
+
196
+ Returns:
197
+ vec4(cos_zenith, sun_x, sun_y, sun_z)
198
+ """
199
+ # Solar declination
200
+ declination = ti.asin(0.409093 * ti.sin(0.0172028 * day_of_year - 1.39012))
201
+
202
+ # Hour angle
203
+ hour_angle = 6.283185 * (second_of_day / 86400.0) + lon_rad - 3.141593
204
+
205
+ # Cosine of zenith
206
+ cos_zenith = (ti.sin(lat_rad) * ti.sin(declination) +
207
+ ti.cos(lat_rad) * ti.cos(declination) * ti.cos(hour_angle))
208
+ cos_zenith = ti.max(0.0, cos_zenith)
209
+
210
+ # Direction components
211
+ sun_dir_lon = -ti.sin(hour_angle) * ti.cos(declination)
212
+ sun_dir_lat = (ti.sin(declination) * ti.cos(lat_rad) -
213
+ ti.cos(hour_angle) * ti.cos(declination) * ti.sin(lat_rad))
214
+
215
+ # Normalize
216
+ sun_x = sun_dir_lon
217
+ sun_y = sun_dir_lat
218
+ sun_z = cos_zenith
219
+ length = ti.sqrt(sun_x**2 + sun_y**2 + sun_z**2)
220
+
221
+ if length > 1e-10:
222
+ sun_x /= length
223
+ sun_y /= length
224
+ sun_z /= length
225
+ else:
226
+ sun_x = 0.0
227
+ sun_y = 0.0
228
+ sun_z = 1.0
229
+
230
+ return ti.math.vec4(cos_zenith, sun_x, sun_y, sun_z)
231
+
232
+
233
+ @ti.data_oriented
234
+ class SolarCalculator:
235
+ """
236
+ GPU-accelerated solar position calculator.
237
+
238
+ Pre-computes solar positions for all time steps.
239
+ """
240
+
241
+ def __init__(self, latitude: float, longitude: float):
242
+ """
243
+ Initialize solar calculator.
244
+
245
+ Args:
246
+ latitude: Site latitude in degrees
247
+ longitude: Site longitude in degrees
248
+ """
249
+ self.latitude = latitude
250
+ self.longitude = longitude
251
+ self.lat_rad = latitude * DEG_TO_RAD
252
+ self.lon_rad = longitude * DEG_TO_RAD
253
+
254
+ # Current solar position (stored as fields for GPU access)
255
+ self.cos_zenith = ti.field(dtype=ti.f32, shape=())
256
+ self.sun_direction = ti.Vector.field(3, dtype=ti.f32, shape=())
257
+ self.sun_up = ti.field(dtype=ti.i32, shape=())
258
+
259
+ # Initialize
260
+ self.cos_zenith[None] = 0.0
261
+ self.sun_direction[None] = Vector3(0.0, 0.0, 1.0)
262
+ self.sun_up[None] = 0
263
+
264
+ def update(self, day_of_year: int, second_of_day: float):
265
+ """Update solar position for given time."""
266
+ pos = calc_zenith(day_of_year, second_of_day, self.latitude, self.longitude)
267
+ self.cos_zenith[None] = pos.cos_zenith
268
+ self.sun_direction[None] = Vector3(*pos.direction)
269
+ self.sun_up[None] = 1 if pos.sun_up else 0
270
+
271
+ def update_datetime(self, dt: datetime):
272
+ """Update solar position for given datetime."""
273
+ pos = calc_solar_position_datetime(dt, self.latitude, self.longitude)
274
+ self.cos_zenith[None] = pos.cos_zenith
275
+ self.sun_direction[None] = Vector3(*pos.direction)
276
+ self.sun_up[None] = 1 if pos.sun_up else 0
277
+
278
+ @ti.func
279
+ def get_cos_zenith(self) -> ti.f32:
280
+ """Get current cosine of zenith angle."""
281
+ return self.cos_zenith[None]
282
+
283
+ @ti.func
284
+ def get_sun_direction(self) -> Vector3:
285
+ """Get current sun direction unit vector."""
286
+ return self.sun_direction[None]
287
+
288
+ @ti.func
289
+ def is_sun_up(self) -> ti.i32:
290
+ """Check if sun is above horizon."""
291
+ return self.sun_up[None]
292
+
293
+
294
+ def discretize_sky_directions(
295
+ n_azimuth: int = 80,
296
+ n_elevation: int = 40
297
+ ) -> Tuple[list, list]:
298
+ """
299
+ Generate discretized sky directions for SVF/ray tracing.
300
+
301
+ Args:
302
+ n_azimuth: Number of azimuthal divisions
303
+ n_elevation: Number of elevation divisions (full hemisphere)
304
+
305
+ Returns:
306
+ Tuple of (directions, solid_angles) where:
307
+ - directions: List of (x, y, z) unit vectors
308
+ - solid_angles: List of solid angle weights
309
+ """
310
+ directions = []
311
+ solid_angles = []
312
+
313
+ # Only upper hemisphere (elevation from 0 to 90)
314
+ for i_elev in range(n_elevation // 2):
315
+ # Center of elevation band
316
+ elev_low = (i_elev / n_elevation) * PI
317
+ elev_high = ((i_elev + 1) / n_elevation) * PI
318
+ elevation = (elev_low + elev_high) / 2
319
+
320
+ # Solid angle for this band
321
+ d_omega = (2 * PI / n_azimuth) * (math.cos(elev_low) - math.cos(elev_high))
322
+
323
+ for i_azim in range(n_azimuth):
324
+ # Center of azimuth band
325
+ azimuth = (i_azim + 0.5) * (2 * PI / n_azimuth)
326
+
327
+ # Convert to Cartesian
328
+ cos_elev = math.cos(elevation)
329
+ sin_elev = math.sin(elevation)
330
+ x = sin_elev * math.sin(azimuth)
331
+ y = sin_elev * math.cos(azimuth)
332
+ z = cos_elev
333
+
334
+ directions.append((x, y, z))
335
+ solid_angles.append(d_omega)
336
+
337
+ return directions, solid_angles