Skip to content

Commit

Permalink
Merge 3735714 into 16a615a
Browse files Browse the repository at this point in the history
  • Loading branch information
rhugonnet committed May 7, 2024
2 parents 16a615a + 3735714 commit 6e3792b
Show file tree
Hide file tree
Showing 3 changed files with 93 additions and 17 deletions.
15 changes: 10 additions & 5 deletions doc/source/vertical_ref.md
Original file line number Diff line number Diff line change
Expand Up @@ -224,17 +224,22 @@ xDEM uses only the best available (i.e. best accuracy) transformation returned b
# Open a DEM and set its CRS
filename_dem = xdem.examples.get_path("longyearbyen_ref_dem")
dem = xdem.DEM(filename_dem, vcrs="Ellipsoid")
dem.to_vcrs("EGM96")
dem.vcrs
trans_dem = dem.to_vcrs("EGM96")
trans_dem.vcrs
```

The operation updates the DEM array **in-place**, shifting each pixel by the transformation at their coordinates:
The operation returns a new {class}`~xdem.DEM` by default, but can also be done in-place. It vertically shifts
each pixel value by the transformation at their coordinates:

```{code-cell} ipython3
import numpy as np
diff = trans_dem - dem
# Mean difference after transformation (about 30 m in Svalbard)
dem_orig = xdem.DEM(filename_dem)
diff = dem_orig - dem
np.nanmean(diff)
```

```{code-cell} ipython3
# Plot the elevation differences due to the vertical transformation
diff.plot(cmap="RdYlBu", cbar_title="Elevation differences (m)")
```
21 changes: 16 additions & 5 deletions tests/test_dem.py
Original file line number Diff line number Diff line change
Expand Up @@ -273,9 +273,20 @@ def test_to_vcrs(self) -> None:
dem.set_vcrs(new_vcrs="Ellipsoid")
ccrs_init = dem.ccrs
median_before = np.nanmean(dem)
# Transform to EGM96 geoid
dem.to_vcrs(vcrs="EGM96")
median_after = np.nanmean(dem)
# Transform to EGM96 geoid not inplace (default)
trans_dem = dem.to_vcrs(vcrs="EGM96")

# The output should be a DEM, input shouldn't have changed
assert isinstance(trans_dem, DEM)
assert dem.raster_equal(dem_before_trans)

# Compare to inplace
should_be_none = dem.to_vcrs(vcrs="EGM96", inplace=True)
assert should_be_none is None
assert dem.raster_equal(trans_dem)

# Save the median of after
median_after = np.nanmean(trans_dem)

# About 32 meters of difference in Svalbard between EGM96 geoid and ellipsoid
assert median_after - median_before == pytest.approx(-32, rel=0.1)
Expand Down Expand Up @@ -337,10 +348,10 @@ def test_to_vcrs__grids(self, grid_shifts: dict[str, Any]) -> None:
dem.set_vcrs("Ellipsoid")

# Transform to the vertical CRS of the grid
dem.to_vcrs(grid_shifts["grid"])
trans_dem = dem.to_vcrs(grid_shifts["grid"])

# Compare the elevation difference
z_diff = 100 - dem.data[0, 0]
z_diff = 100 - trans_dem.data[0, 0]

# Check the shift is the one expect within 10%
assert z_diff == pytest.approx(grid_shifts["shift"], rel=0.1)
Expand Down
74 changes: 67 additions & 7 deletions xdem/dem.py
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@

import pathlib
import warnings
from typing import Any, Literal
from typing import Any, Literal, overload

import geopandas as gpd
import numpy as np
Expand Down Expand Up @@ -254,6 +254,7 @@ def ccrs(self) -> CompoundCRS | CRS | None:
else:
return None

@overload
def to_vcrs(
self,
vcrs: Literal["Ellipsoid", "EGM08", "EGM96"] | str | pathlib.Path | VerticalCRS | int,
Expand All @@ -263,15 +264,61 @@ def to_vcrs(
| VerticalCRS
| int
| None = None,
*,
inplace: Literal[False] = False,
) -> DEM:
...

@overload
def to_vcrs(
self,
vcrs: Literal["Ellipsoid", "EGM08", "EGM96"] | str | pathlib.Path | VerticalCRS | int,
force_source_vcrs: Literal["Ellipsoid", "EGM08", "EGM96"]
| str
| pathlib.Path
| VerticalCRS
| int
| None = None,
*,
inplace: Literal[True],
) -> None:
...

@overload
def to_vcrs(
self,
vcrs: Literal["Ellipsoid", "EGM08", "EGM96"] | str | pathlib.Path | VerticalCRS | int,
force_source_vcrs: Literal["Ellipsoid", "EGM08", "EGM96"]
| str
| pathlib.Path
| VerticalCRS
| int
| None = None,
*,
inplace: bool = False,
) -> DEM | None:
...

def to_vcrs(
self,
vcrs: Literal["Ellipsoid", "EGM08", "EGM96"] | str | pathlib.Path | VerticalCRS | int,
force_source_vcrs: Literal["Ellipsoid", "EGM08", "EGM96"]
| str
| pathlib.Path
| VerticalCRS
| int
| None = None,
inplace: bool = False,
) -> DEM | None:
"""
Convert the DEM to another vertical coordinate reference system.
:param vcrs: Destination vertical CRS. Either as a name ("WGS84", "EGM08", "EGM96"),
an EPSG code or pyproj.crs.VerticalCRS, or a path to a PROJ grid file (https://github.com/OSGeo/PROJ-data)
:param force_source_vcrs: Force a source vertical CRS (uses metadata by default). Same formats as for `vcrs`.
:param inplace: Whether to return a new DEM (default) or the same DEM updated in-place.
:return:
:return: DEM with vertical reference transformed, or None.
"""

if self.vcrs is None and force_source_vcrs is None:
Expand Down Expand Up @@ -307,12 +354,25 @@ def to_vcrs(
zz = self.data
xx, yy = self.coords()
zz_trans = _transform_zz(crs_from=src_ccrs, crs_to=dst_ccrs, xx=xx, yy=yy, zz=zz)
new_data = zz_trans.astype(self.dtype) # type: ignore

# Update DEM
self._data = zz_trans.astype(self.dtype) # type: ignore

# Update vcrs (which will update ccrs if called)
self.set_vcrs(new_vcrs=vcrs)
# If inplace, update DEM and vcrs
if inplace:
self._data = new_data
self.set_vcrs(new_vcrs=vcrs)
return None
# Otherwise, return new DEM
else:
return DEM.from_array(
data=new_data,
transform=self.transform,
crs=self.crs,
nodata=self.nodata,
area_or_point=self.area_or_point,
tags=self.tags,
vcrs=vcrs,
cast_nodata=False,
)

@copy_doc(terrain, remove_dem_res_params=True)
def slope(
Expand Down

0 comments on commit 6e3792b

Please sign in to comment.