Skip to content

Commit b094885

Browse files
authored
Merge pull request #204 from CU-ESIIL/codex/fix-prism-ppt-units-and-plot-rotation
Fix PRISM ppt units and cube viewer rotation target
2 parents a8f7666 + e8453d6 commit b094885

7 files changed

Lines changed: 110 additions & 14 deletions

File tree

docs/changelog.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,7 @@
99
- Added an `allow_synthetic` safety switch to gridMET/PRISM loaders with clearer empty-time/all-NaN error messages.
1010
- `load_prism_cube` now returns a DataArray when a single variable is requested, matching docs examples.
1111
- Fixed cube viewer rotation so drag/zoom updates the cube as well as axis labels.
12+
- Standardized PRISM precipitation units to millimeters and aligned cube viewer rotation variables with the wrapper element.
1213

1314
## Earlier work
1415

docs/concepts/cubes.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -30,7 +30,7 @@ CubeDynamics focuses on **streaming cubes** instead of requiring large local dow
3030

3131
## Correctness & cube shapes
3232

33-
- **PRISM/gridMET loaders** (`load_prism_cube`, `load_gridmet_cube`) return `xarray.Dataset` objects with dims `(time, y, x)` per variable. Pass exactly one AOI description: `lat`/`lon`, `bbox=[min_lon, min_lat, max_lon, max_lat]`, or `aoi_geojson` (GeoJSON Feature/FeatureCollection).
33+
- **PRISM/gridMET loaders** (`load_prism_cube`, `load_gridmet_cube`) return `xarray.Dataset` objects with dims `(time, y, x)` per variable, or a single-variable `xarray.DataArray` when `variable="ppt"` (or another single variable) is requested. Pass exactly one AOI description: `lat`/`lon`, `bbox=[min_lon, min_lat, max_lon, max_lat]`, or `aoi_geojson` (GeoJSON Feature/FeatureCollection).
3434
- **Sentinel-2 loaders**:
3535
- `load_sentinel2_cube` and `load_sentinel2_bands_cube` return multispectral stacks with dims `(time, y, x, band)`.
3636
- `load_sentinel2_ndvi_cube` returns raw NDVI reflectance with dims `(time, y, x)` and optionally the underlying bands when `return_raw=True`.

docs/datasets/prism.md

Lines changed: 5 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -12,19 +12,19 @@ import cubedynamics as cd
1212
from cubedynamics import pipe, verbs as v
1313

1414
cube = cd.load_prism_cube(
15-
variables=["ppt"],
15+
variable="ppt",
1616
start="2020-01-01",
1717
end="2020-02-01",
18-
aoi={"min_lat": 39.8, "max_lat": 40.2, "min_lon": -105.4, "max_lon": -105.0},
18+
bbox=[-105.4, 39.8, -105.0, 40.2],
1919
)
2020

21-
pipe(cube["ppt"]) | v.mean(dim="time") | v.plot()
21+
pipe(cube) | v.mean(dim="time") | v.plot()
2222
```
2323

2424
### Customizing the view
2525

2626
```python
27-
pipe(cube["ppt"]) | v.plot(camera={"eye": {"x": 2.2, "y": 1.6, "z": 1.3}})
27+
pipe(cube) | v.plot(camera={"eye": {"x": 2.2, "y": 1.6, "z": 1.3}})
2828
```
2929

3030
### Preview plot
@@ -40,7 +40,7 @@ pipe(cube["ppt"]) | v.plot(camera={"eye": {"x": 2.2, "y": 1.6, "z": 1.3}})
4040
2. Capture the CubePlot viewer for export:
4141

4242
```python
43-
viewer = (pipe(cube["ppt"]) | v.mean(dim="time") | v.plot()).unwrap()
43+
viewer = (pipe(cube) | v.mean(dim="time") | v.plot()).unwrap()
4444
viewer.save("docs/assets/datasets/prism-preview.html")
4545
```
4646

src/cubedynamics/data/prism.py

Lines changed: 28 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,32 @@
1818

1919

2020
_POINT_BUFFER_DEGREES = 0.05
21+
_PRISM_VARIABLE_METADATA = {
22+
"ppt": {
23+
"units": "mm",
24+
"long_name": "Total precipitation",
25+
},
26+
}
27+
28+
29+
def _apply_prism_variable_metadata(
30+
ds: xr.Dataset, variables: Sequence[str]
31+
) -> xr.Dataset:
32+
for name in variables:
33+
if name not in ds.data_vars:
34+
continue
35+
meta = _PRISM_VARIABLE_METADATA.get(name.lower())
36+
if not meta:
37+
continue
38+
da = ds[name]
39+
units = da.attrs.get("units")
40+
if not units or str(units).strip().lower() == "synthetic":
41+
da.attrs["units"] = meta["units"]
42+
for key, value in meta.items():
43+
if key == "units":
44+
continue
45+
da.attrs.setdefault(key, value)
46+
return ds
2147

2248

2349
def load_prism_cube(
@@ -571,6 +597,8 @@ def _finalize_prism_cube(
571597
)
572598
source = "synthetic"
573599

600+
ds = _apply_prism_variable_metadata(ds, variables)
601+
574602
provenance_error = backend_error if source != "prism_streaming" else None
575603
return set_cube_provenance(
576604
ds,

src/cubedynamics/plotting/cube_viewer.py

Lines changed: 0 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -447,9 +447,6 @@ def _render_cube_html(
447447
height: 100%;
448448
perspective: 950px;
449449
transform-style: preserve-3d;
450-
--rot-x: 0rad;
451-
--rot-y: 0rad;
452-
--zoom: 1;
453450
}}
454451
455452
.cube-scene .cube-rotation {{
@@ -739,11 +736,6 @@ def _render_cube_html(
739736
cubeWrapper.style.setProperty("--rot-y", rotationY + "rad");
740737
cubeWrapper.style.setProperty("--zoom", zoom);
741738
}}
742-
if (scene && scene !== cubeWrapper) {{
743-
scene.style.setProperty("--rot-x", rotationX + "rad");
744-
scene.style.setProperty("--rot-y", rotationY + "rad");
745-
scene.style.setProperty("--zoom", zoom);
746-
}}
747739
if (!cubeWrapper && rotationTarget) {{
748740
rotationTarget.style.setProperty("--rot-x", rotationX + "rad");
749741
rotationTarget.style.setProperty("--rot-y", rotationY + "rad");
Lines changed: 37 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,37 @@
1+
from __future__ import annotations
2+
3+
import numpy as np
4+
import pandas as pd
5+
import xarray as xr
6+
7+
from cubedynamics.plotting.cube_viewer import cube_from_dataarray
8+
9+
10+
def test_cube_viewer_rotation_target_uses_wrapper(tmp_path):
11+
data = xr.DataArray(
12+
np.arange(2 * 3 * 3, dtype=float).reshape(2, 3, 3),
13+
dims=("time", "y", "x"),
14+
coords={
15+
"time": pd.date_range("2023-01-01", periods=2, freq="D"),
16+
"y": np.arange(3),
17+
"x": np.arange(3),
18+
},
19+
name="demo",
20+
)
21+
22+
html = cube_from_dataarray(
23+
data,
24+
out_html=str(tmp_path / "viewer.html"),
25+
return_html=True,
26+
show_progress=False,
27+
thin_time_factor=1,
28+
)
29+
30+
assert "const rotationTarget = cubeWrapper || scene" in html
31+
assert "const rotationTarget = scene || cubeWrapper" not in html
32+
assert 'scene.style.setProperty("--rot-x"' not in html
33+
34+
scene_block = html.split(".cube-scene {", 1)[1].split("}", 1)[0]
35+
assert "--rot-x" not in scene_block
36+
assert "--rot-y" not in scene_block
37+
assert "--zoom" not in scene_block

tests/test_prism_ppt_units.py

Lines changed: 38 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,38 @@
1+
from __future__ import annotations
2+
3+
import numpy as np
4+
import pandas as pd
5+
import xarray as xr
6+
7+
import cubedynamics.data.prism as prism
8+
9+
10+
def test_prism_ppt_units_are_mm(monkeypatch):
11+
def fake_stream(variables, start, end, aoi, freq, show_progress=True):
12+
coords = {
13+
"time": pd.date_range("2000-01-01", periods=1, freq="D"),
14+
"y": np.array([40.0]),
15+
"x": np.array([-105.25]),
16+
}
17+
da = xr.DataArray(
18+
np.ones((1, 1, 1)),
19+
coords=coords,
20+
dims=("time", "y", "x"),
21+
name="ppt",
22+
attrs={"units": "synthetic"},
23+
)
24+
return xr.Dataset({"ppt": da})
25+
26+
monkeypatch.setattr(prism, "_open_prism_streaming", fake_stream)
27+
28+
da = prism.load_prism_cube(
29+
lat=40.0,
30+
lon=-105.25,
31+
start="2000-01-01",
32+
end="2000-01-02",
33+
variable="ppt",
34+
)
35+
36+
assert isinstance(da, xr.DataArray)
37+
assert da.name == "ppt"
38+
assert da.attrs.get("units") == "mm"

0 commit comments

Comments
 (0)