Skip to content

Commit

Permalink
Update notebooks, fix toc issue
Browse files Browse the repository at this point in the history
  • Loading branch information
robbibt committed Oct 15, 2024
1 parent 55f3616 commit ac1c260
Show file tree
Hide file tree
Showing 11 changed files with 415 additions and 401 deletions.
8 changes: 5 additions & 3 deletions docs/changelog.md
Original file line number Diff line number Diff line change
@@ -1,11 +1,13 @@
# Changelog

## v0.0.1 (2024-09-01)
## v0.1.0 (2024-10-18)

### New features

- Initial creation of repo
- Initial creation of `eo-tides` repo

### Breaking changes

### Bug fixes
See [Migrating from DEA Tools](#migration) for a guide to updating your code from the original [`Digital Earth Australia Notebooks and Tools` repository](https://github.com/GeoscienceAustralia/dea-notebooks/).

<!-- ### Bug fixes -->
2 changes: 2 additions & 0 deletions docs/credits.md
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,8 @@ Functions from `eo-tides` were originally developed in the [`Digital Earth Austr

FES Finite Element Solution tide models were developed, validated by the CTOH/LEGOS, France and distributed by Aviso+: <https://www.aviso.altimetry.fr/en/data/products/sea-surface-height-products/regional/x-track-sla/x-track-l2p-sla-version-2022.html>

This repository was initialised using the [`cookiecutter-uv`](https://github.com/fpgmaas/cookiecutter-uv) package.

## References

<small>
Expand Down
5 changes: 3 additions & 2 deletions docs/notebooks/Model_tides.ipynb
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,8 @@
"source": [
"# Modelling tides\n",
"\n",
"This guide demonstrates how to use the [`model_tides`](../../api/#eo_tides.model.model_tides) function from the [`eo_tides.model`](../../api/#eo_tides.model) module. \n",
"**This guide demonstrates how to use the [`model_tides`](../../api/#eo_tides.model.model_tides) function from the [`eo_tides.model`](../../api/#eo_tides.model) module.** \n",
"\n",
"This function allows you to model tide heights at multiple coordinates and/or timestep, using using one or more ocean tide models.\n",
"\n",
"The `model_tides` function can be used independently of Earth observation (EO) data, e.g. for any application where you need to generate a time series of tide heights.\n",
Expand Down Expand Up @@ -1099,7 +1100,7 @@
"name": "python",
"nbconvert_exporter": "python",
"pygments_lexer": "ipython3",
"version": "3.11.10"
"version": "3.12.1"
}
},
"nbformat": 4,
Expand Down
364 changes: 95 additions & 269 deletions docs/notebooks/Satellite_data.ipynb

Large diffs are not rendered by default.

2 changes: 1 addition & 1 deletion docs/notebooks/Tide_statistics.ipynb
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@
"source": [
"# Calculating tide statistics and satellite biases\n",
"\n",
"This guide demonstrates how to use the `tidal_stats` function from the `eo_tides.stats` module to calculate statistics describing local tide dynamics, and identify biases caused by interactions between tidal processes and satellite orbits.\n",
"**This guide demonstrates how to use the [`tide_stats`](../../api/#eo_tides.stats.tide_stats) function from [`eo_tides.stats`](../../api/#eo_tides.stats) to calculate local tide statistics and identify biases caused by interactions between tidal processes and satellite orbits.**\n",
"\n",
"Complex interactions between temporal tide dynamics and the regular mid-morning overpass timing of sun-synchronous sensors like Landsat or Sentinel-2 mean that satellites often does not observe the entire tidal cycle. \n",
"Biases in satellite coverage of the tidal cycle can mean that tidal extremes (e.g. the lowest or highest tides at a location) may either never be captured by satellites, or be over-represented in the satellite EO record. \n",
Expand Down
84 changes: 30 additions & 54 deletions eo_tides/eo.py
Original file line number Diff line number Diff line change
Expand Up @@ -93,19 +93,17 @@ def _pixel_tides_resample(


def tag_tides(
ds: xr.Dataset,
ds: xr.Dataset | xr.DataArray,
model: str | list[str] = "EOT20",
directory: str | os.PathLike | None = None,
tidepost_lat: float | None = None,
tidepost_lon: float | None = None,
ebb_flow: bool = False,
swap_dims: bool = False,
**model_tides_kwargs,
) -> xr.Dataset:
) -> xr.DataArray:
"""
Model tide heights for every timestep in a multi-dimensional
dataset, and add them as a new `tide_height` (and optionally,
`ebb_flow`) variable that "tags" each observation with tide data.
dataset, and return a new `tide_height` array that can
be used to "tag" each observation with tide data.
The function models tides at the centroid of the dataset
by default, but a custom tidal modelling location can
Expand All @@ -123,7 +121,7 @@ def tag_tides(
Parameters
----------
ds : xarray.Dataset
ds : xarray.Dataset or xarray.DataArray
A multi-dimensional dataset (e.g. "x", "y", "time") to
tag with tide heights. This dataset must contain a "time"
dimension.
Expand All @@ -143,16 +141,6 @@ def tag_tides(
Optional coordinates used to model tides. The default is None,
which uses the centroid of the dataset as the tide modelling
location.
ebb_flow : bool, optional
An optional boolean indicating whether to compute if the
tide phase was ebbing (falling) or flowing (rising) for each
observation. The default is False; if set to True, a new
"ebb_flow" variable will be added to the dataset with each
observation labelled with "Ebb" or "Flow".
swap_dims : bool, optional
An optional boolean indicating whether to swap the `time`
dimension in the original `ds` to the new "tide_height"
variable. Defaults to False.
**model_tides_kwargs :
Optional parameters passed to the `eo_tides.model.model_tides`
function. Important parameters include `cutoff` (used to
Expand All @@ -174,8 +162,6 @@ def tag_tides(

# Standardise model into a list for easy handling. and verify only one
model = [model] if isinstance(model, str) else model
if (len(model) > 1) & swap_dims:
raise ValueError("Can only swap dimensions when a single tide model is passed to `model`.")

# If custom tide modelling locations are not provided, use the
# dataset centroid
Expand Down Expand Up @@ -208,48 +194,38 @@ def tag_tides(
f"`tidepost_lat` and `tidepost_lon` parameters."
)

# Optionally calculate the tide phase for each observation
if ebb_flow:
# Model tides for a time 15 minutes prior to each previously
# modelled satellite acquisition time. This allows us to compare
# tide heights to see if they are rising or falling.
print("Modelling tidal phase (e.g. ebb or flow)")
tide_pre_df = model_tides(
x=lon, # type: ignore
y=lat, # type: ignore
time=(ds.time - pd.Timedelta("15 min")),
model=model,
directory=directory,
crs="EPSG:4326",
**model_tides_kwargs,
)

# Compare tides computed for each timestep. If the previous tide
# was higher than the current tide, the tide is 'ebbing'. If the
# previous tide was lower, the tide is 'flowing'
tide_df["ebb_flow"] = (tide_df.tide_height < tide_pre_df.tide_height.values).replace({
True: "Ebb",
False: "Flow",
})
# # Optionally calculate the tide phase for each observation
# if ebb_flow:
# # Model tides for a time 15 minutes prior to each previously
# # modelled satellite acquisition time. This allows us to compare
# # tide heights to see if they are rising or falling.
# print("Modelling tidal phase (e.g. ebb or flow)")
# tide_pre_df = model_tides(
# x=lon, # type: ignore
# y=lat, # type: ignore
# time=(ds.time - pd.Timedelta("15 min")),
# model=model,
# directory=directory,
# crs="EPSG:4326",
# **model_tides_kwargs,
# )

# # Compare tides computed for each timestep. If the previous tide
# # was higher than the current tide, the tide is 'ebbing'. If the
# # previous tide was lower, the tide is 'flowing'
# tide_df["ebb_flow"] = (tide_df.tide_height < tide_pre_df.tide_height.values).replace({
# True: "Ebb",
# False: "Flow",
# })

# Convert to xarray format
tide_xr = tide_df.reset_index().set_index(["time", "tide_model"]).drop(["x", "y"], axis=1).to_xarray()
tide_xr = tide_df.reset_index().set_index(["time", "tide_model"]).drop(["x", "y"], axis=1).tide_height.to_xarray()

# If only one tidal model exists, squeeze out "tide_model" dim
if len(tide_xr.tide_model) == 1:
tide_xr = tide_xr.squeeze("tide_model", drop=True)

# Add each array into original dataset
for var in tide_xr.data_vars:
ds[var] = tide_xr[var]

# Swap dimensions and sort by tide height
if swap_dims:
ds = ds.swap_dims({"time": "tide_height"})
ds = ds.sortby("tide_height")
ds = ds.drop_vars("time")

return ds
return tide_xr


def pixel_tides(
Expand Down
1 change: 1 addition & 0 deletions mkdocs.yml
Original file line number Diff line number Diff line change
Expand Up @@ -105,6 +105,7 @@ extra:
markdown_extensions:
- toc:
permalink: true
toc_depth: 3
- pymdownx.arithmatex:
generic: true
- admonition
Expand Down
1 change: 1 addition & 0 deletions pyproject.toml
Original file line number Diff line number Diff line change
Expand Up @@ -70,6 +70,7 @@ dev-dependencies = [
"mkdocs-material>=9.5.0",
"mkdocs-jupyter>=0.24.0",
"mkdocstrings[python]>=0.20.0",
"black>=22.1.0",
"nbval>=0.10.0",
"odc-stac>=0.3.8",
"pystac-client>=0.8.3",
Expand Down
68 changes: 24 additions & 44 deletions tests/test_eo.py
Original file line number Diff line number Diff line change
Expand Up @@ -14,71 +14,51 @@


@pytest.mark.parametrize(
"ebb_flow, swap_dims, tidepost_lat, tidepost_lon",
"tidepost_lat, tidepost_lon",
[
(False, False, None, None), # Run with default settings
(True, False, None, None), # Run with ebb_flow on
(False, True, None, None), # Run with swap_dims on
(False, False, GAUGE_Y, GAUGE_X), # Run with custom tide posts
(None, None), # Run with default settings
(GAUGE_Y, GAUGE_X), # Run with custom tide posts
],
)
def test_tag_tides(satellite_ds, measured_tides_ds, ebb_flow, swap_dims, tidepost_lat, tidepost_lon):
def test_tag_tides(satellite_ds, measured_tides_ds, tidepost_lat, tidepost_lon):
# Use tag_tides to assign a "tide_height" variable to each observation
tagged_tides_ds = tag_tides(
tagged_tides_da = tag_tides(
satellite_ds,
ebb_flow=ebb_flow,
swap_dims=swap_dims,
tidepost_lat=tidepost_lat,
tidepost_lon=tidepost_lon,
)

# Verify tide_height variable was added
assert "tide_height" in tagged_tides_ds
# Verify tide_height variable has correct name
assert tagged_tides_da.name == "tide_height"

# Verify ebb_flow variable was added if requested
if ebb_flow:
assert "ebb_flow" in tagged_tides_ds
# Test that tagged tides have same timesteps as satellite data
assert len(tagged_tides_da.time) == len(satellite_ds.time)

if swap_dims:
# Verify "tide_height" is now a dimension
assert "tide_height" in tagged_tides_ds.dims

# Test that "tide_height" dim is same length as satellite "time" dim
assert len(tagged_tides_ds.tide_height) == len(satellite_ds.time)

# Test that first value on "tide_height" dim is lower than last
# (data should be sorted in increasing tide height order)
assert tagged_tides_ds.isel(tide_height=0).tide_height < tagged_tides_ds.isel(tide_height=-1).tide_height

else:
# Test that tagged tides have same timesteps as satellite data
assert len(tagged_tides_ds.tide_height.time) == len(satellite_ds.time)

# Interpolate measured tide data to same timesteps
measured_tides_ds = measured_tides_ds.interp(time=satellite_ds.time, method="linear")
# Interpolate measured tide data to same timesteps
measured_tides_ds = measured_tides_ds.interp(time=satellite_ds.time, method="linear")

# Compare measured and modelled tides
val_stats = eval_metrics(x=measured_tides_ds.tide_height, y=tagged_tides_ds.tide_height)
# Compare measured and modelled tides
val_stats = eval_metrics(x=measured_tides_ds.tide_height, y=tagged_tides_da)

# Test that modelled tides meet expected accuracy
assert val_stats["Correlation"] > 0.99
assert val_stats["RMSE"] < 0.26
assert val_stats["R-squared"] > 0.96
assert abs(val_stats["Bias"]) < 0.20
# Test that modelled tides meet expected accuracy
assert val_stats["Correlation"] > 0.99
assert val_stats["RMSE"] < 0.26
assert val_stats["R-squared"] > 0.96
assert abs(val_stats["Bias"]) < 0.20


def test_tag_tides_multiple(satellite_ds):
# Model multiple models at once
tagged_tides_ds = tag_tides(satellite_ds, model=["EOT20", "HAMTIDE11"], ebb_flow=True)
tagged_tides_da = tag_tides(satellite_ds, model=["EOT20", "HAMTIDE11"], ebb_flow=True)

assert "tide_model" in tagged_tides_ds.dims
assert tagged_tides_ds.tide_height.dims == ("time", "tide_model")
assert tagged_tides_ds.ebb_flow.dims == ("time", "tide_model")
assert tagged_tides_da.name == "tide_height"
assert "tide_model" in tagged_tides_da.dims
assert tagged_tides_da.dims == ("time", "tide_model")

# Test that multiple tide models are correlated
val_stats = eval_metrics(
x=tagged_tides_ds.sel(tide_model="EOT20").tide_height,
y=tagged_tides_ds.sel(tide_model="HAMTIDE11").tide_height,
x=tagged_tides_da.sel(tide_model="EOT20"),
y=tagged_tides_da.sel(tide_model="HAMTIDE11"),
)
assert val_stats["Correlation"] >= 0.99

Expand Down
Loading

0 comments on commit ac1c260

Please sign in to comment.