Combining satellite data with tidal modelling 86981216eb9643bd934e4e5980eb2841

Background

Ocean tides are the periodic rise and fall of the ocean caused by the gravitational pull of the moon and sun and the earth’s rotation. Tides in coastal areas can greatly influence how these environments appear in satellite imagery as water levels vary by up to 12 metres (e.g. in north-western Australia). To be able to study coastal environments and processes along Australia’s coastline, it is vital to obtain data on tidal conditions at the exact moment each satellite image was acquired.

Description

This notebook demonstrates how to combine remotely sensed imagery with information about ocean tides using functions from the `dea_tools.coastal module <../Tools/dea_tools/coastal.py>`__, allowing us to analyse satellite imagery by tidal stage (e.g. low, high, ebb, flow). These functions use the Finite Element Solution 2014 (FES2014) tidal model to calculate the height (relative to mean sea level, i.e. approximately equivalent to the Australian Height Datum or AHD) and stage of the tide at the exact moment each satellite image was acquired.

These tide modelling tools and tide models underpin DEA products including DEA Coastlines and DEA Intertidal.

The notebook demonstrates how to:

  1. Model tide heights for specific coordinates and times using the model_tides function

  2. Model tide heights for each satellite observation using the tidal_tag function

  3. Use tide height data to produce median composites of the coast at low and high tide

  4. Swap a dataset’s dimensions to make it easier to select imagery from low to high tide.

  5. Compute ebb or flow tide phase data to determine whether water levels were rising or falling in each satellite observation

Advanced tools:

  1. Spatially model tides into each pixel of a satellite dataset using pixel_tides

  2. Evaluate potential biases in the tides observed by a satellite using tidal_stats


Getting started

To run this analysis, run all the cells in the notebook, starting with the “Load packages” cell.

Load packages

[1]:
import datacube
import xarray as xr
import pandas as pd
import matplotlib.pyplot as plt
from datacube.utils.masking import mask_invalid_data

import sys

sys.path.insert(1, "../Tools/")
from dea_tools.datahandling import load_ard
from dea_tools.plotting import rgb, display_map
from dea_tools.coastal import model_tides, tidal_tag, pixel_tides, tidal_stats

Connect to the datacube

[2]:
dc = datacube.Datacube(app="Tidal_modelling")

Modelling tide heights using model_tides

To simply model tide heights for a specific location and set of times, we can use the dea_tools.coastal.model_tides function. For example, we can model hourly tides across a one month period (September 2022):

[3]:
# Set of times to model tides for
date_list = pd.date_range(start="2022-09-01", end="2022-09-10", freq="1h")

# Run the FES2014 tidal model
tide_df = model_tides(
    x=[122.21],
    y=[-18.20],
    time=date_list,
)

# Print outputs
tide_df.head()
Modelling tides using FES2014
[3]:
tide_model tide_m
time x y
2022-09-01 00:00:00 122.21 -18.2 FES2014 -3.606788
2022-09-01 01:00:00 122.21 -18.2 FES2014 -2.470168
2022-09-01 02:00:00 122.21 -18.2 FES2014 -0.675034
2022-09-01 03:00:00 122.21 -18.2 FES2014 1.140802
2022-09-01 04:00:00 122.21 -18.2 FES2014 2.591887

Tide heights for each time and coordinate are included in the tide_m column above (representing tide height in metres relative to Mean Sea Level).

We can also plot out resulting tides to view how tides changed across this month. By looking at the y-axis, we can see that tides ranged from a minimum of ~-4 metres up to a maximum of +4 metres relative to Mean Sea Level:

[4]:
tide_df.reset_index(["x", "y"]).tide_m.plot();
../../../_images/notebooks_How_to_guides_Tidal_modelling_10_0.png

“One-to-many” and “one-to-one” modes

By default, the model_tides function operates in “one-to-many” mode, which will model tides for every requested timestep, at every requested location. For example, if we provided five x, y coordinates and five timesteps, the function would return:

5 locations * 5 timesteps = 25 modelled tides

However, often you may have a list of locations and matching timesteps. Using “one-to-one” mode, we can model tides for only these exact pairs of locations and times:

5 timesteps at 5 locations = 5 modelled tides

To demonstrate “one-to-one” mode, imagine we have a pandas.Dataframe where each row contains unique site locations and times:

[5]:
sites_df = pd.DataFrame({
    "time": pd.date_range(start="2022-09-01", end="2022-09-30", periods=5),
    "x": [122.21, 122.22, 122.23, 122.24, 122.25],
    "y": [-18.20, -18.20, -18.20, -18.20, -18.20]
})

sites_df
[5]:
time x y
0 2022-09-01 00:00:00 122.21 -18.2
1 2022-09-08 06:00:00 122.22 -18.2
2 2022-09-15 12:00:00 122.23 -18.2
3 2022-09-22 18:00:00 122.24 -18.2
4 2022-09-30 00:00:00 122.25 -18.2

Using “one-to-one” mode, we can model a tide height for each row in our dataframe, and add it as a new dataframe column:

[6]:
# Model tides in "one-to-one" mode
tide_df = model_tides(
    x=sites_df.x,
    y=sites_df.y,
    time=sites_df.time,
    mode="one-to-one",
)

# Add results as a new datframe column
sites_df["tide_height"] = tide_df.tide_m.values
sites_df.style.set_properties(**{"background-color": "#FFFF8F"}, subset=["tide_height"])
Modelling tides using FES2014 in parallel
100%|██████████| 5/5 [00:12<00:00,  2.54s/it]
[6]:
  time x y tide_height
0 2022-09-01 00:00:00 122.210000 -18.200000 -3.606788
1 2022-09-08 06:00:00 122.220000 -18.200000 -1.738778
2 2022-09-15 12:00:00 122.230000 -18.200000 -3.303217
3 2022-09-22 18:00:00 122.240000 -18.200000 -0.859731
4 2022-09-30 00:00:00 122.250000 -18.200000 -3.687130

Modelling tide heights for each satellite observation using tidal_tag

However, often it is valuable to estimate the height of the tide at the exact moment satellite images were taken over a coastline. This can help sort and filter images by tide height, allowing us to learn more about how coastal environments respond to the effect of changing tides.

To demonstrate how this can be done, we first need to load in an example time series of satellite imagery.

Set up data query

First we set up a query to define the area, time period and other parameters required for loading data. In this example, we will load several years of Landsat 8 data for intertidal mud flats south of Broome in Western Australia. We load the 'nbart_red', 'nbart_green', 'nbart_blue' bands so that we can plot the data as true colour imagery.

The dask_chunks parameter allows us to use Dask to lazily load data rather than load data directly into memory, which can take a long time and large amounts of memory. Lazy loading can be a very useful approach for when you need to load large amounts of data without crashing your analysis. In coastal applications, it allows us to load (using either .compute() or by plotting our data) only a small subset of observations from our entire time series (e.g. only low or high tide observations) without having to load the entire dataset into memory first, which can greatly decrease processing times.

For more information about using Dask, refer to the Parallel processing with Dask notebook.

[7]:
# Set up data load query
query = {
    "x": (122.15, 122.26),
    "y": (-18.14, -18.24),
    "time": ("2020-01", "2020-06"),
    "measurements": ["nbart_red", "nbart_green", "nbart_blue"],
    "group_by": "solar_day",
    "dask_chunks": {},
}

We can preview the area that we will load data for:

[8]:
display_map(x=query['x'], y=query['y'])
[8]:
Make this Notebook Trusted to load map: File -> Trust Notebook

Load satellite time-series

To obtain some satellite data to analyse, we use the load_ard function to import a time series of Landsat 8 observations as an xarray.Dataset. The input data does not need to be from Landsat: any remotely-sensed imagery with timestamps and spatial coordinates provide enough data to run the tidal model.

[9]:
# Load available data from Landsat 8
ds = load_ard(
    dc=dc,
    products=["ga_ls8c_ard_3"],
    mask_pixel_quality=False,
    cloud_cover=(0, 10),  # load only scenes with <10% cloud
    **query
)

# Set nodata to NaN and print output
ds = mask_invalid_data(ds)
ds
Finding datasets
    ga_ls8c_ard_3
Returning 13 time steps as a dask array
[9]:
<xarray.Dataset> Size: 26MB
Dimensions:      (time: 13, y: 398, x: 415)
Coordinates:
  * time         (time) datetime64[ns] 104B 2020-01-13T01:49:41.792637 ... 20...
  * y            (y) float64 3kB -1.977e+06 -1.977e+06 ... -1.989e+06 -1.989e+06
  * x            (x) float64 3kB -1.041e+06 -1.041e+06 ... -1.029e+06 -1.029e+06
    spatial_ref  int32 4B 3577
Data variables:
    nbart_red    (time, y, x) float32 9MB dask.array<chunksize=(1, 398, 415), meta=np.ndarray>
    nbart_green  (time, y, x) float32 9MB dask.array<chunksize=(1, 398, 415), meta=np.ndarray>
    nbart_blue   (time, y, x) float32 9MB dask.array<chunksize=(1, 398, 415), meta=np.ndarray>
Attributes:
    crs:           EPSG:3577
    grid_mapping:  spatial_ref

“Tagging” each satellite image with tide height metadata

We can now use the tidal_tag function from dea_tools.coastal to “tag” or associate each satellite observation in our time series with a tide height relative to mean sea level (i.e. approximately equivalent to the Australian Height Datum or AHD). This function uses the time and date of acquisition and the geographic location of each satellite observation as inputs to the Finite Element Solution 2014 (FES2014) tidal model:

FES2014 is the last version of the FES (Finite Element Solution) tide model developed in 2014-2016. It is an improved version of the FES2012 model. This new FES2014 model has been developed, implemented and validated by the LEGOS, NOVELTIS and CLS, within a CNES funded project. FES2014 takes advantage of longer altimeter time series and better altimeter standards, improved modelling and data assimilation techniques, a more accurate ocean bathymetry and a refined mesh in most of the shallow water regions. Special efforts have been dedicated to address the major non-linear tides issue and to the determination of accurate tidal currents.

The function will automatically select a tide modelling location based on the dataset centroid. It will then output modelled tide heights as a new tide_m variable in the xarray.Dataset (the variable should appear under Data variables below):

[10]:
# Model tide heights
ds_tidal = tidal_tag(ds)

# Print output data
ds_tidal
Setting tide modelling location from dataset centroid: 122.20, -18.19
Modelling tides using FES2014
[10]:
<xarray.Dataset> Size: 26MB
Dimensions:      (time: 13, y: 398, x: 415)
Coordinates:
  * time         (time) datetime64[ns] 104B 2020-01-13T01:49:41.792637 ... 20...
  * y            (y) float64 3kB -1.977e+06 -1.977e+06 ... -1.989e+06 -1.989e+06
  * x            (x) float64 3kB -1.041e+06 -1.041e+06 ... -1.029e+06 -1.029e+06
    spatial_ref  int32 4B 3577
Data variables:
    nbart_red    (time, y, x) float32 9MB dask.array<chunksize=(1, 398, 415), meta=np.ndarray>
    nbart_green  (time, y, x) float32 9MB dask.array<chunksize=(1, 398, 415), meta=np.ndarray>
    nbart_blue   (time, y, x) float32 9MB dask.array<chunksize=(1, 398, 415), meta=np.ndarray>
    tide_m       (time) float32 52B 0.5622 -0.9301 1.639 ... -1.419 3.05 -2.026
Attributes:
    crs:           EPSG:3577
    grid_mapping:  spatial_ref

We can easily plot this new variable to inspect the range of tide heights observed by the satellites in our time series. In this example, our observed tide heights range from approximately -2.0 to 4.0 m relative to Mean Sea Level:

[11]:
ds_tidal.tide_m.plot();
../../../_images/notebooks_How_to_guides_Tidal_modelling_24_0.png

Example tide height analysis

To demonstrate how tidally tagged images can be used to produce composites of high and low tide imagery, we can compute the lowest 20% and highest 20% of tide heights, and use these to filter our observations. We can then combine and plot these filtered observations to visualise how the landscape appears at low and high tide:

[12]:
# Calculate the lowest and highest 20% of tides
lowest_20, highest_20 = ds_tidal.tide_m.quantile([0.2, 0.8]).values

# Filter our data to low and high tide observations
filtered_low = ds_tidal.where(ds_tidal.tide_m <= lowest_20, drop=True)
filtered_high = ds_tidal.where(ds_tidal.tide_m >= highest_20, drop=True)

# Take the simple median of each set of low and high tide observations to
# produce a composite (alternatively, observations could be combined
# using a geomedian to keep band relationships consistent)
median_low = filtered_low.median(dim="time", keep_attrs=True)
median_high = filtered_high.median(dim="time", keep_attrs=True)

# Combine low and high tide medians into a single dataset and give
# each layer a meaningful name
ds_highlow = xr.concat([median_low, median_high], dim="tide_m")
ds_highlow["tide_m"] = ["Low tide", "High tide"]

# Plot low and high tide medians side-by-side
rgb(ds_highlow, col="tide_m")
../../../_images/notebooks_How_to_guides_Tidal_modelling_26_0.png
/env/lib/python3.10/site-packages/rasterio/warp.py:344: NotGeoreferencedWarning: Dataset has no geotransform, gcps, or rpcs. The identity matrix will be returned.
  _reproject(

Swapping dimensions

The tidal_tag function allows you to use tide heights as the primary dimension in the dataset, rather than time. Setting swap_dims=True will swap the time dimension in the original xarray.Dataset to the new tide_m variable.

[13]:
# Model tide heights
ds_tidal = tidal_tag(ds, swap_dims=True)

# Print output data
ds_tidal
Setting tide modelling location from dataset centroid: 122.20, -18.19
Modelling tides using FES2014
[13]:
<xarray.Dataset> Size: 26MB
Dimensions:      (y: 398, x: 415, tide_m: 13)
Coordinates:
  * y            (y) float64 3kB -1.977e+06 -1.977e+06 ... -1.989e+06 -1.989e+06
  * x            (x) float64 3kB -1.041e+06 -1.041e+06 ... -1.029e+06 -1.029e+06
    spatial_ref  int32 4B 3577
  * tide_m       (tide_m) float32 52B -2.026 -1.811 -1.419 ... 3.05 3.272 3.786
Data variables:
    nbart_red    (tide_m, y, x) float32 9MB dask.array<chunksize=(1, 398, 415), meta=np.ndarray>
    nbart_green  (tide_m, y, x) float32 9MB dask.array<chunksize=(1, 398, 415), meta=np.ndarray>
    nbart_blue   (tide_m, y, x) float32 9MB dask.array<chunksize=(1, 398, 415), meta=np.ndarray>
Attributes:
    crs:           EPSG:3577
    grid_mapping:  spatial_ref

The dataset now contains three dimensions: tide_m, x and y. This can make it easier to analyse the data with respect to tide, e.g. plotting the lowest and highest tide image in our dataset:

[14]:
# Plot first (lowest tide) and last (highest tide) images
rgb(ds_tidal, index_dim="tide_m", index=[0, -1])
../../../_images/notebooks_How_to_guides_Tidal_modelling_30_0.png

Note: The white dots in the images above are false positives in the automated cloud mask caused by bright sandy shorelines being mistaken for clouds.

Modelling ebb and flow tidal phases

The tidal_tag function also allows us to determine whether each satellite observation was taken while the tide was rising/incoming (flow tide) or falling/outgoing (ebb tide) by setting ebb_flow=True. This is achieved by comparing tide heights 15 minutes before and after the observed satellite observation.

Ebb and flow data can provide valuable contextual information for interpreting satellite imagery, particularly in tidal flat or mangrove forest environments where water may remain in the landscape for considerable time after the tidal peak.

[15]:
# Model tide heights
ds_tidal = tidal_tag(ds, ebb_flow=True)

# Print output data
ds_tidal
Setting tide modelling location from dataset centroid: 122.20, -18.19
Modelling tides using FES2014
Modelling tidal phase (e.g. ebb or flow)
Modelling tides using FES2014
[15]:
<xarray.Dataset> Size: 26MB
Dimensions:      (time: 13, y: 398, x: 415)
Coordinates:
  * time         (time) datetime64[ns] 104B 2020-01-13T01:49:41.792637 ... 20...
  * y            (y) float64 3kB -1.977e+06 -1.977e+06 ... -1.989e+06 -1.989e+06
  * x            (x) float64 3kB -1.041e+06 -1.041e+06 ... -1.029e+06 -1.029e+06
    spatial_ref  int32 4B 3577
Data variables:
    nbart_red    (time, y, x) float32 9MB dask.array<chunksize=(1, 398, 415), meta=np.ndarray>
    nbart_green  (time, y, x) float32 9MB dask.array<chunksize=(1, 398, 415), meta=np.ndarray>
    nbart_blue   (time, y, x) float32 9MB dask.array<chunksize=(1, 398, 415), meta=np.ndarray>
    tide_m       (time) float32 52B 0.5622 -0.9301 1.639 ... -1.419 3.05 -2.026
    ebb_flow     (time) <U4 208B 'Flow' 'Flow' 'Flow' ... 'Flow' 'Flow' 'Flow'
Attributes:
    crs:           EPSG:3577
    grid_mapping:  spatial_ref

We now have data giving us the both the tide height and tidal phase (‘ebb’ or ‘flow’) for every satellite image:

[16]:
ds_tidal[["time", "tide_m", "ebb_flow"]].to_dataframe().drop("spatial_ref", axis=1)
[16]:
tide_m ebb_flow
time
2020-01-13 01:49:41.792637 0.562249 Flow
2020-01-29 01:49:37.148233 -0.930141 Flow
2020-02-21 01:55:18.216005 1.639398 Flow
2020-03-08 01:55:12.515274 2.301430 Flow
2020-03-17 01:49:21.317513 -1.811296 Flow
2020-03-24 01:55:04.353944 2.813786 Flow
2020-04-09 01:54:56.077227 3.272171 Flow
2020-04-25 01:54:48.941936 2.010019 Flow
2020-05-04 01:48:56.906589 1.238869 Ebb
2020-06-05 01:49:03.396612 3.785858 Flow
2020-06-12 01:54:54.641568 -1.419148 Flow
2020-06-21 01:49:12.757339 3.050224 Flow
2020-06-28 01:55:03.091663 -2.025774 Flow

We could for example use this data to filter our observations to keep ebbing phase observations only:

[17]:
ds_tidal.where(ds_tidal.ebb_flow == "Ebb", drop=True)
[17]:
<xarray.Dataset> Size: 2MB
Dimensions:      (time: 1, y: 398, x: 415)
Coordinates:
  * time         (time) datetime64[ns] 8B 2020-05-04T01:48:56.906589
  * y            (y) float64 3kB -1.977e+06 -1.977e+06 ... -1.989e+06 -1.989e+06
  * x            (x) float64 3kB -1.041e+06 -1.041e+06 ... -1.029e+06 -1.029e+06
    spatial_ref  int32 4B 3577
Data variables:
    nbart_red    (time, y, x) float32 661kB dask.array<chunksize=(1, 398, 415), meta=np.ndarray>
    nbart_green  (time, y, x) float32 661kB dask.array<chunksize=(1, 398, 415), meta=np.ndarray>
    nbart_blue   (time, y, x) float32 661kB dask.array<chunksize=(1, 398, 415), meta=np.ndarray>
    tide_m       (time) float32 4B 1.239
    ebb_flow     (time) object 8B 'Ebb'
Attributes:
    crs:           EPSG:3577
    grid_mapping:  spatial_ref

Advanced

Modelling tides for each pixel in a satellite time series using pixel_tides

The previous examples show how to model a single tide height for each satellite image using the centroid of the image as a tide modelling location. However, in reality tides vary spatially, potentially up to several metres across a distance of kilometres in areas of complex tidal dynamics. This means that an individual satellite image can contain a range of tide height conditions.

To capture this spatial variability in tide heights, we can use the pixel_tides function from dea_tools.coastal. For efficient processing, this function first models tides into a low resolution 5000 m grid surrounding each satellite image in our time series. This lower resolution data will also include a buffer around the extent of our data tides can be modelled seamlessly across analysis boundaries:

[18]:
# Extract a subset of our satellite data above to make plotting easier
ds_subset = ds.isel(time=slice(0, 3))

# Model tides spatially using `pixel_tides`
tides_lowres = pixel_tides(ds_subset, resample=False)
Creating reduced resolution 5000 x 5000 metre tide modelling array
Modelling tides using FES2014 in parallel
100%|██████████| 5/5 [00:12<00:00,  2.49s/it]
Returning low resolution tide array

If we plot the resulting data, we can see that we now have 2D tide surfaces for each timestep in our data (instead of the single tide height per timestamp returned by the tidal_tag function).

Blue values below indicate low tide pixels, while red indicates high tide pixels. If you look closely at the second and third timestep below, you may also be able to see some spatial variability in tide heights within each timestep, with slight variations in tide heights along the left (west) side of the study area:

[19]:
tides_lowres.plot.imshow(col="time", vmin=-1, vmax=1, cmap='RdBu')
[19]:
<xarray.plot.facetgrid.FacetGrid at 0x7f971049b250>
../../../_images/notebooks_How_to_guides_Tidal_modelling_41_1.png

Reprojecting tide heights back into original high resolution spatial grid

By setting resample=True, we can use interpolation to re-project our low resolution tide data back into the resolution of our satellite image, resulting in an individual tide height value for every single pixel in our dataset through time and space:

[20]:
# Model tides spatially using `pixel_tides`
tides_highres, tides_lowres = pixel_tides(ds_subset, resample=True)

tides_highres.plot.imshow(col="time", vmin=-1, vmax=1, cmap="RdBu")
Creating reduced resolution 5000 x 5000 metre tide modelling array
Modelling tides using FES2014 in parallel
100%|██████████| 5/5 [00:12<00:00,  2.59s/it]
Reprojecting tides into original array
[20]:
<xarray.plot.facetgrid.FacetGrid at 0x7f97108df190>
../../../_images/notebooks_How_to_guides_Tidal_modelling_43_4.png

tides_highres will have exactly the same dimensions as ds_subset, with a unique tide height for every satellite pixel:

[21]:
ds_subset.sizes
[21]:
Frozen({'time': 3, 'y': 398, 'x': 415})
[22]:
tides_highres.sizes
[22]:
Frozen({'time': 3, 'y': 398, 'x': 415})

Because of this, our stack of tides can be added as an additional 3D variable in our dataset:

[23]:
ds_subset["tide_m"] = tides_highres
ds_subset
[23]:
<xarray.Dataset> Size: 8MB
Dimensions:      (time: 3, y: 398, x: 415)
Coordinates:
  * time         (time) datetime64[ns] 24B 2020-01-13T01:49:41.792637 ... 202...
  * y            (y) float64 3kB -1.977e+06 -1.977e+06 ... -1.989e+06 -1.989e+06
  * x            (x) float64 3kB -1.041e+06 -1.041e+06 ... -1.029e+06 -1.029e+06
    spatial_ref  int32 4B 3577
    tide_model   <U7 28B 'FES2014'
Data variables:
    nbart_red    (time, y, x) float32 2MB dask.array<chunksize=(1, 398, 415), meta=np.ndarray>
    nbart_green  (time, y, x) float32 2MB dask.array<chunksize=(1, 398, 415), meta=np.ndarray>
    nbart_blue   (time, y, x) float32 2MB dask.array<chunksize=(1, 398, 415), meta=np.ndarray>
    tide_m       (time, y, x) float32 2MB 0.5856 0.5854 0.5853 ... 1.638 1.638
    ebb_flow     (time) <U4 48B 'Flow' 'Flow' 'Flow'
Attributes:
    crs:           EPSG:3577
    grid_mapping:  spatial_ref

Calculating min/max/median/quantiles of tide heights for each pixel

Min, max or any specific quantile of all tide heights observed over a region can be calculated for each pixel by passing in a list of quantiles/percentiles.

This calculation is performed on the low resolution modelled tide data before reprojecting to higher resolution, so should be faster than calculating min/max/median tide at high resolution:

[24]:
tides_highres_quantiles, tides_lowres_quantiles = pixel_tides(
    ds_subset, resample=True, calculate_quantiles=(0, 0.5, 1)
)
Creating reduced resolution 5000 x 5000 metre tide modelling array
Modelling tides using FES2014 in parallel
100%|██████████| 5/5 [00:12<00:00,  2.54s/it]
Computing tide quantiles
Reprojecting tides into original array
[25]:
tides_highres_quantiles.plot.imshow(col="quantile")
[25]:
<xarray.plot.facetgrid.FacetGrid at 0x7f9711d4bac0>
../../../_images/notebooks_How_to_guides_Tidal_modelling_51_1.png

Pixel-based tides for custom times

Instead of using times contained in the time dimension of our dataset, we can also calculate pixel-based tides for a custom set of times:

[26]:
custom_times = pd.date_range(start="2022-01-01", end="2022-01-02", freq="6H")

tides_highres, tides_lowres = pixel_tides(ds_subset, resample=True, times=custom_times)
Creating reduced resolution 5000 x 5000 metre tide modelling array
Modelling tides using FES2014 in parallel
100%|██████████| 5/5 [00:12<00:00,  2.58s/it]
Reprojecting tides into original array
[27]:
tides_highres.plot.imshow(col="time")
[27]:
<xarray.plot.facetgrid.FacetGrid at 0x7f970ba373a0>
../../../_images/notebooks_How_to_guides_Tidal_modelling_54_1.png

Evaluating tidal biases using tidal_stats

The complex temporal behaviour of tides mean that a sun synchronous sensor like Landsat does not observe the full range of the tidal cycle at all locations. Biases in the proportion of the tidal range observed by satellites can prevent us from obtaining data on areas of the coastline exposed or inundated at the extremes of the tidal range. This can risk gaining misleading insights into the true extent of the area of the coastline affected by tides, and make it difficult to compare high or low tide images fairly in different locations.

The tidal_stats function can assist in evaluating how the range of tides observed by satellites compare to the full tidal range. It works by using our tidal model to to model all available tide heights at a regular interval (every two hours by default) across the entire time period covered by the input satellite time series dataset. This is then compared against the tide heights in observed by the satellite and used to calculate a range of statistics and a plot that summarises potential biases in the data.

For a more detailed discussion of the issue of tidal bias in sun-synchronous satellite observations of the coastline, refer to the ‘Limitations and future work’ section in Bishop-Taylor et al. 2018.

[28]:
out_stats = tidal_stats(ds)
Setting tide modelling location from dataset centroid: 122.20, -18.19
Modelling tides using FES2014
Modelling tides using FES2014
                                                 tide_model    tide_m
time                       x          y
2020-01-13 01:49:41.792637 122.204971 -18.190029    FES2014  0.562249
2020-01-13 03:49:41.792637 122.204971 -18.190029    FES2014  3.454227
2020-01-13 05:49:41.792637 122.204971 -18.190029    FES2014  2.493470
2020-01-13 07:49:41.792637 122.204971 -18.190029    FES2014 -0.528646
2020-01-13 09:49:41.792637 122.204971 -18.190029    FES2014 -3.204748
...                                                     ...       ...
2020-06-27 17:49:41.792637 122.204971 -18.190029    FES2014  1.922974
2020-06-27 19:49:41.792637 122.204971 -18.190029    FES2014  2.180105
2020-06-27 21:49:41.792637 122.204971 -18.190029    FES2014  0.217506
2020-06-27 23:49:41.792637 122.204971 -18.190029    FES2014 -1.491524
2020-06-28 01:49:41.792637 122.204971 -18.190029    FES2014 -2.060531

[2005 rows x 2 columns]

59% of the 9.81 m modelled astronomical tidal range is observed at this location.
The lowest 28% and highest 12% of astronomical tides are never observed.

../../../_images/notebooks_How_to_guides_Tidal_modelling_56_1.png

The function also outputs a pandas.Series object containing a set of statistics that compare the observed vs. full modelled tidal ranges. These statistics include:

  • tidepost_lat: latitude used for modelling tide heights

  • tidepost_lon: longitude used for modelling tide heights

  • observed_min_m: minimum tide height observed by the satellite (in metre units)

  • all_min_m: minimum tide height from full modelled tidal range (in metre units)

  • observed_max_m: maximum tide height observed by the satellite (in metre units)

  • all_max_m: maximum tide height from full modelled tidal range (in metre units)

  • observed_range_m: tidal range observed by the satellite (in metre units)

  • all_range_m: full modelled tidal range (in metre units)

  • spread_m: proportion of the full modelled tidal range observed by the satellite (see Bishop-Taylor et al. 2018)

  • low_tide_offset: proportion of the lowest tides never observed by the satellite (see Bishop-Taylor et al. 2018)

  • high_tide_offset: proportion of the highest tides never observed by the satellite (see Bishop-Taylor et al. 2018)

[29]:
out_stats
[29]:
tidepost_lat        -18.190
tidepost_lon        122.205
observed_mean_m       1.114
all_mean_m            0.001
observed_min_m       -2.026
all_min_m            -4.803
observed_max_m        3.786
all_max_m             5.008
observed_range_m      5.812
all_range_m           9.811
spread                0.592
low_tide_offset       0.283
high_tide_offset      0.125
dtype: float64

Additional information

FES2014: Tidal modelling is provided by the FES2014 global tidal model, implemented using functions from the pyTMD Python package. FES2014 was produced by NOVELTIS, LEGOS, CLS Space Oceanography Division and CNES. It is distributed by AVISO, with support from CNES (https://www.aviso.altimetry.fr/en/data/products/auxiliary-products/global-tide-fes/description-fes2014.html).

License: The code in this notebook is licensed under the Apache License, Version 2.0. Digital Earth Australia data is licensed under the Creative Commons by Attribution 4.0 license.

Contact: If you need assistance, please post a question on the Open Data Cube Discord chat or on the GIS Stack Exchange using the open-data-cube tag (you can view previously asked questions here). If you would like to report an issue with this notebook, you can file one on GitHub.

Last modified: July 2024

Compatible datacube version:

[30]:
print(datacube.__version__)
1.8.18

Tags

Tags: sandbox compatible, NCI compatible, landsat 8, display_map, load_ard, rgb, tidal_tag, tidal_stats, model_tides, pixel_tides, tide modelling, intertidal, Dask, lazy loading, rolling window