Skip to content

Commit 8822107

Browse files
committed
Add extensive tests for solar and raytracing functionalities
- Implement tests for the `trace_ray_generic` function, covering various scenarios including empty voxel grids, hits on buildings, and transmittance reduction by trees. - Introduce tests for the `_build_face_basis` function to ensure correct basis generation for different normals. - Add tests for the `rotate_vector_axis_angle` function to validate vector rotation around specified axes. - Create a new test suite for the `voxcity.simulator.solar.sky` module, including tests for Tregenza, Reinhart, and Fibonacci sky discretizations. - Implement tests for the `voxcity.simulator.solar.temporal` module, focusing on solar position calculations and weather aggregation to sky patches. - Extend utility tests for land cover functions, including nearest class determination and dominant class identification. - Introduce tests for setting building materials from GeoDataFrames, ensuring correct application and handling of materials.
1 parent a73e084 commit 8822107

9 files changed

Lines changed: 1713 additions & 2 deletions

pyproject.toml

Lines changed: 31 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -112,14 +112,44 @@ source = ["src/voxcity"]
112112
omit = [
113113
# Visualization and I/O heavy modules (non-deterministic / GUI)
114114
"src/voxcity/utils/visualization.py",
115-
# External data/network heavy downloaders
115+
# External data/network heavy downloaders (require network/external services)
116116
"src/voxcity/downloader/gee.py",
117117
"src/voxcity/downloader/mbfp.py",
118118
"src/voxcity/downloader/oemj.py",
119119
"src/voxcity/downloader/overture.py",
120120
"src/voxcity/downloader/citygml.py",
121+
"src/voxcity/downloader/osm.py",
122+
"src/voxcity/downloader/gba.py",
123+
"src/voxcity/downloader/eubucco.py",
124+
"src/voxcity/downloader/plateau.py",
125+
"src/voxcity/downloader/cog.py",
126+
"src/voxcity/downloader/las.py",
127+
"src/voxcity/downloader/ocean.py",
121128
# Pure exporters (file writers) not critical for algorithmic coverage
122129
"src/voxcity/exporter/*",
130+
# GPU-specific code requiring Taichi/CUDA hardware (tested via integration tests)
131+
"src/voxcity/simulator_gpu/*",
132+
"src/voxcity/visualizer/renderer_gpu.py",
133+
# Visualization/rendering code (requires GUI/display)
134+
"src/voxcity/visualizer/renderer.py",
135+
"src/voxcity/visualizer/maps.py",
136+
"src/voxcity/visualizer/palette.py",
137+
# Drawing code (interactive/visualization)
138+
"src/voxcity/geoprocessor/draw.py",
139+
# Weather data downloading (requires external API)
140+
"src/voxcity/utils/weather/onebuilding.py",
141+
# Raster I/O heavy modules (require GeoTIFF files)
142+
"src/voxcity/geoprocessor/raster/buildings.py",
143+
"src/voxcity/geoprocessor/raster/canopy.py",
144+
"src/voxcity/geoprocessor/raster/landcover.py",
145+
"src/voxcity/geoprocessor/raster/raster.py",
146+
"src/voxcity/geoprocessor/raster/export.py",
147+
# Grid generation (requires external data sources)
148+
"src/voxcity/generator/grids.py",
149+
"src/voxcity/generator/api.py",
150+
# Visibility simulation (requires full voxel grid)
151+
"src/voxcity/simulator/visibility/view.py",
152+
"src/voxcity/simulator/visibility/landmark.py",
123153
]
124154

125155
[tool.coverage.report]

tests/test_generator_voxelizer.py

Lines changed: 92 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@
55
from voxcity.generator.voxelizer import (
66
Voxelizer,
77
_flatten_building_segments,
8+
replace_nan_in_nested,
89
GROUND_CODE,
910
TREE_CODE,
1011
BUILDING_CODE,
@@ -209,3 +210,94 @@ def test_generate_combined_has_ground(self, simple_inputs):
209210
# Land cover values are positive (e.g., 11 for developed space from Urbanwatch)
210211
# Check that there are positive values at z=0 (land cover layer)
211212
assert np.any(result[:, :, 0] > 0)
213+
214+
215+
class TestReplaceNanInNested:
216+
"""Tests for replace_nan_in_nested function."""
217+
218+
def test_non_array_returns_unchanged(self):
219+
"""Non-array input should be returned unchanged."""
220+
result = replace_nan_in_nested("not an array")
221+
assert result == "not an array"
222+
223+
result = replace_nan_in_nested(42)
224+
assert result == 42
225+
226+
result = replace_nan_in_nested(None)
227+
assert result is None
228+
229+
def test_empty_cells_stay_empty(self):
230+
"""Empty list cells should remain empty lists."""
231+
arr = np.empty((2, 2), dtype=object)
232+
arr[0, 0] = []
233+
arr[0, 1] = []
234+
arr[1, 0] = []
235+
arr[1, 1] = []
236+
237+
result = replace_nan_in_nested(arr)
238+
239+
assert result[0, 0] == []
240+
assert result[0, 1] == []
241+
242+
def test_none_cells_become_empty(self):
243+
"""None cells should become empty lists."""
244+
arr = np.empty((1, 1), dtype=object)
245+
arr[0, 0] = None
246+
247+
result = replace_nan_in_nested(arr)
248+
249+
assert result[0, 0] == []
250+
251+
def test_replaces_nan_in_list_segments(self):
252+
"""NaN values in list segments should be replaced."""
253+
arr = np.empty((1, 1), dtype=object)
254+
arr[0, 0] = [[np.nan, 10.0], [5.0, np.nan]]
255+
256+
result = replace_nan_in_nested(arr, replace_value=99.0)
257+
258+
assert result[0, 0][0][0] == 99.0
259+
assert result[0, 0][0][1] == 10.0
260+
assert result[0, 0][1][0] == 5.0
261+
assert result[0, 0][1][1] == 99.0
262+
263+
def test_replaces_nan_in_numpy_segments(self):
264+
"""NaN values in numpy array segments should be replaced."""
265+
arr = np.empty((1, 1), dtype=object)
266+
arr[0, 0] = [np.array([np.nan, 10.0])]
267+
268+
result = replace_nan_in_nested(arr, replace_value=7.0)
269+
270+
assert result[0, 0][0][0] == 7.0
271+
assert result[0, 0][0][1] == 10.0
272+
273+
def test_preserves_non_nan_values(self):
274+
"""Non-NaN values should be preserved."""
275+
arr = np.empty((1, 1), dtype=object)
276+
arr[0, 0] = [[1.0, 2.0], [3.0, 4.0]]
277+
278+
result = replace_nan_in_nested(arr, replace_value=99.0)
279+
280+
assert result[0, 0][0][0] == 1.0
281+
assert result[0, 0][0][1] == 2.0
282+
assert result[0, 0][1][0] == 3.0
283+
assert result[0, 0][1][1] == 4.0
284+
285+
def test_default_replace_value(self):
286+
"""Default replace value should be 10.0."""
287+
arr = np.empty((1, 1), dtype=object)
288+
arr[0, 0] = [[np.nan]]
289+
290+
result = replace_nan_in_nested(arr)
291+
292+
assert result[0, 0][0][0] == 10.0
293+
294+
def test_preserves_shape(self):
295+
"""Output should have same shape as input."""
296+
arr = np.empty((3, 4), dtype=object)
297+
for i in range(3):
298+
for j in range(4):
299+
arr[i, j] = []
300+
301+
result = replace_nan_in_nested(arr)
302+
303+
assert result.shape == (3, 4)

0 commit comments

Comments
 (0)