|
11 | 11 | TESTDATADIR = join(dirname(abspath(__file__)), "data") |
12 | 12 |
|
13 | 13 |
|
| 14 | +def _make_grid(crs, x0, y0, res, nx=4, ny=3): |
| 15 | + x = x0 + (np.arange(nx) + 0.5) * res |
| 16 | + y = y0 - (np.arange(ny) + 0.5) * res # N -> S |
| 17 | + da = xr.DataArray( |
| 18 | + np.ones((ny, nx), dtype="float32"), |
| 19 | + coords={"y": y, "x": x}, |
| 20 | + dims=("y", "x"), |
| 21 | + name="dummy", |
| 22 | + ) |
| 23 | + da.raster.set_crs(crs) |
| 24 | + return da |
| 25 | + |
| 26 | + |
| 27 | +def test_gridarea_projected_returns_uniform_positive_area(): |
| 28 | + """Projected CRS (e.g. SVY21 / EPSG:3414) must produce uniform positive areas. |
| 29 | +
|
| 30 | + Regression test for the gridarea bug that fed SVY21 Easting metres into |
| 31 | + ``_reggrid_area`` (which interprets them as longitude degrees). At |
| 32 | + Easting=10500-18000 m, ``sin(radians(Easting))`` is a periodic function |
| 33 | + of large angles and produces partly-negative garbage, which propagated |
| 34 | + through the mm -> m3/s unit conversion and yielded negative precip values |
| 35 | + in dynamicdata.nc. The patched gridarea delegates to |
| 36 | + ``ds.raster.area_grid()`` for projected CRS, which uses res*res*ucf**2. |
| 37 | + """ |
| 38 | + res = 30.0 |
| 39 | + da = _make_grid("EPSG:3414", x0=10490.0, y0=39280.0, res=res) |
| 40 | + |
| 41 | + area = gridarea(da) |
| 42 | + |
| 43 | + assert area.shape == da.shape |
| 44 | + assert float(area.min()) > 0, "projected cell area must be strictly positive" |
| 45 | + expected = res * res # SVY21 linear unit factor is 1.0 (metres) |
| 46 | + np.testing.assert_allclose(area.values, expected, rtol=1e-6) |
| 47 | + |
| 48 | + |
| 49 | +def test_gridarea_geographic_path_unchanged(): |
| 50 | + """Geographic CRS path must still go through _reggrid_area (spherical cap).""" |
| 51 | + da = _make_grid("EPSG:4326", x0=4.0, y0=52.0, res=0.01) |
| 52 | + |
| 53 | + area = gridarea(da) |
| 54 | + |
| 55 | + assert float(area.min()) > 0 |
| 56 | + # At ~52N, 0.01deg cells are ~700-800 m on a side -> ~5-7e5 m2. |
| 57 | + assert 4e5 < float(area.mean()) < 9e5 |
| 58 | + |
| 59 | + |
14 | 60 | def test_setup_grid(example_demission_model): |
15 | 61 | # Initialize model and read results |
16 | 62 | mod = example_demission_model |
|
0 commit comments