Skip to content

Skip longitude normalisation for projected datasets in harmonise_dims (fixes #1476)#1479

Open
gaoflow wants to merge 2 commits into
Deltares:mainfrom
gaoflow:fix/1476-harmonise-dims-projected-crs
Open

Skip longitude normalisation for projected datasets in harmonise_dims (fixes #1476)#1479
gaoflow wants to merge 2 commits into
Deltares:mainfrom
gaoflow:fix/1476-harmonise-dims-projected-crs

Conversation

@gaoflow

@gaoflow gaoflow commented Jun 1, 2026

Copy link
Copy Markdown
Contributor

Problem

harmonise_dims unconditionally wraps 0-360 longitudes to -180-180 for any dataset whose x-coordinates exceed 180:

if np.any(lons > 180):
    ds[x_dim] = xr.Variable(x_dim, np.where(lons > 180, lons - 360, lons))
    ds = ds.sortby(x_dim)

For a projected dataset whose x is in metres (e.g. SVY21 / EPSG:3414, where every x value is > 180), this subtracts 360 from every coordinate, silently shifting the grid 360 m to the west. As the source CRS is still unset, the displaced grid is then reprojected incorrectly, producing spatially shifted output. No warning is raised. (Reported in #1476.)

Fix

Normalise longitudes only when the dataset has a geographic CRS:

  • geographic CRS → wrap to -180-180 (unchanged behaviour);
  • projected CRS → leave untouched (x is metres, not longitude);
  • no CRS → leave unchanged and emit a warning, rather than assuming geographic — the caller should set crs in the data catalog entry to opt in.

This follows the direction in the issue discussion (thanks @alimeshgi, @michaelohanrahan): an unknown CRS should not be assumed geographic, since that is the exact scenario that corrupts projected data.

Reproduction (from #1476)

import numpy as np, pandas as pd, xarray as xr
from hydromt.data_catalog.drivers.preprocessing import harmonise_dims

x = np.arange(10490, 10490 + 30*30, 30, dtype=float)   # 30 m resolution, all > 180
y = np.arange(39010, 39010 + 30*30, 30, dtype=float)[::-1]
t = pd.date_range("2020-01-01", periods=3, freq="D")
ds = xr.Dataset({"precip": (["time","y","x"], np.random.rand(3, len(y), len(x)))},
                coords={"x": x, "y": y, "time": t})

print(ds.x.values[0])                    # 10490.0
print(harmonise_dims(ds.copy()).x.values[0])
# before fix: 10130.0  (shifted -360)
# after fix : 10490.0  (unchanged, with a warning that no CRS is set)

Verification

  • New regression tests in tests/data_catalog/drivers/test_preprocessing.py cover all three cases (geographic normalises, no-CRS left unchanged + warns, projected-CRS left unchanged). The two bug-targeting tests fail on main and pass with this change; the geographic case is unchanged.
  • Full tests/data_catalog/drivers/ suite passes (112 passed, 15 skipped).
  • ruff check and ruff format --check clean.

harmonise_dims unconditionally wrapped 0-360 longitudes to -180-180 for any
dataset with x-coordinates greater than 180. For a projected dataset whose x
is in metres (all values > 180), this silently subtracted 360 from every x,
shifting the grid 360 m to the west. Because the source CRS was still unset,
the displaced grid was then reprojected incorrectly, producing spatially
shifted output.

Only normalise longitudes when the dataset has a geographic CRS. Datasets
with a projected CRS are left untouched, and datasets without any CRS are
left unchanged with a warning rather than assuming geographic coordinates.

Fixes Deltares#1476
@michaelohanrahan

Copy link
Copy Markdown

Thanks for your attention on this issue @gaoflow

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants