eo-tides 0.0.21__py3-none-any.whl → 0.0.23__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- eo_tides/eo.py +36 -57
- eo_tides/model.py +103 -80
- eo_tides/stats.py +388 -100
- eo_tides/validation.py +20 -13
- {eo_tides-0.0.21.dist-info → eo_tides-0.0.23.dist-info}/METADATA +28 -17
- eo_tides-0.0.23.dist-info/RECORD +11 -0
- {eo_tides-0.0.21.dist-info → eo_tides-0.0.23.dist-info}/WHEEL +1 -1
- eo_tides-0.0.21.dist-info/RECORD +0 -11
- {eo_tides-0.0.21.dist-info → eo_tides-0.0.23.dist-info}/LICENSE +0 -0
- {eo_tides-0.0.21.dist-info → eo_tides-0.0.23.dist-info}/top_level.txt +0 -0
eo_tides/eo.py
CHANGED
@@ -2,6 +2,7 @@
|
|
2
2
|
from __future__ import annotations
|
3
3
|
|
4
4
|
import os
|
5
|
+
import warnings
|
5
6
|
from typing import TYPE_CHECKING
|
6
7
|
|
7
8
|
import odc.geo.xr
|
@@ -93,19 +94,17 @@ def _pixel_tides_resample(
|
|
93
94
|
|
94
95
|
|
95
96
|
def tag_tides(
|
96
|
-
ds: xr.Dataset,
|
97
|
+
ds: xr.Dataset | xr.DataArray,
|
97
98
|
model: str | list[str] = "EOT20",
|
98
99
|
directory: str | os.PathLike | None = None,
|
99
100
|
tidepost_lat: float | None = None,
|
100
101
|
tidepost_lon: float | None = None,
|
101
|
-
ebb_flow: bool = False,
|
102
|
-
swap_dims: bool = False,
|
103
102
|
**model_tides_kwargs,
|
104
|
-
) -> xr.
|
103
|
+
) -> xr.DataArray:
|
105
104
|
"""
|
106
105
|
Model tide heights for every timestep in a multi-dimensional
|
107
|
-
dataset, and
|
108
|
-
|
106
|
+
dataset, and return a new `tide_height` array that can
|
107
|
+
be used to "tag" each observation with tide data.
|
109
108
|
|
110
109
|
The function models tides at the centroid of the dataset
|
111
110
|
by default, but a custom tidal modelling location can
|
@@ -123,7 +122,7 @@ def tag_tides(
|
|
123
122
|
|
124
123
|
Parameters
|
125
124
|
----------
|
126
|
-
ds : xarray.Dataset
|
125
|
+
ds : xarray.Dataset or xarray.DataArray
|
127
126
|
A multi-dimensional dataset (e.g. "x", "y", "time") to
|
128
127
|
tag with tide heights. This dataset must contain a "time"
|
129
128
|
dimension.
|
@@ -143,16 +142,6 @@ def tag_tides(
|
|
143
142
|
Optional coordinates used to model tides. The default is None,
|
144
143
|
which uses the centroid of the dataset as the tide modelling
|
145
144
|
location.
|
146
|
-
ebb_flow : bool, optional
|
147
|
-
An optional boolean indicating whether to compute if the
|
148
|
-
tide phase was ebbing (falling) or flowing (rising) for each
|
149
|
-
observation. The default is False; if set to True, a new
|
150
|
-
"ebb_flow" variable will be added to the dataset with each
|
151
|
-
observation labelled with "Ebb" or "Flow".
|
152
|
-
swap_dims : bool, optional
|
153
|
-
An optional boolean indicating whether to swap the `time`
|
154
|
-
dimension in the original `ds` to the new "tide_height"
|
155
|
-
variable. Defaults to False.
|
156
145
|
**model_tides_kwargs :
|
157
146
|
Optional parameters passed to the `eo_tides.model.model_tides`
|
158
147
|
function. Important parameters include `cutoff` (used to
|
@@ -174,8 +163,6 @@ def tag_tides(
|
|
174
163
|
|
175
164
|
# Standardise model into a list for easy handling. and verify only one
|
176
165
|
model = [model] if isinstance(model, str) else model
|
177
|
-
if (len(model) > 1) & swap_dims:
|
178
|
-
raise ValueError("Can only swap dimensions when a single tide model is passed to `model`.")
|
179
166
|
|
180
167
|
# If custom tide modelling locations are not provided, use the
|
181
168
|
# dataset centroid
|
@@ -208,48 +195,38 @@ def tag_tides(
|
|
208
195
|
f"`tidepost_lat` and `tidepost_lon` parameters."
|
209
196
|
)
|
210
197
|
|
211
|
-
# Optionally calculate the tide phase for each observation
|
212
|
-
if ebb_flow:
|
213
|
-
|
214
|
-
|
215
|
-
|
216
|
-
|
217
|
-
|
218
|
-
|
219
|
-
|
220
|
-
|
221
|
-
|
222
|
-
|
223
|
-
|
224
|
-
|
225
|
-
|
226
|
-
|
227
|
-
|
228
|
-
|
229
|
-
|
230
|
-
|
231
|
-
|
232
|
-
|
233
|
-
|
198
|
+
# # Optionally calculate the tide phase for each observation
|
199
|
+
# if ebb_flow:
|
200
|
+
# # Model tides for a time 15 minutes prior to each previously
|
201
|
+
# # modelled satellite acquisition time. This allows us to compare
|
202
|
+
# # tide heights to see if they are rising or falling.
|
203
|
+
# print("Modelling tidal phase (e.g. ebb or flow)")
|
204
|
+
# tide_pre_df = model_tides(
|
205
|
+
# x=lon, # type: ignore
|
206
|
+
# y=lat, # type: ignore
|
207
|
+
# time=(ds.time - pd.Timedelta("15 min")),
|
208
|
+
# model=model,
|
209
|
+
# directory=directory,
|
210
|
+
# crs="EPSG:4326",
|
211
|
+
# **model_tides_kwargs,
|
212
|
+
# )
|
213
|
+
|
214
|
+
# # Compare tides computed for each timestep. If the previous tide
|
215
|
+
# # was higher than the current tide, the tide is 'ebbing'. If the
|
216
|
+
# # previous tide was lower, the tide is 'flowing'
|
217
|
+
# tide_df["ebb_flow"] = (tide_df.tide_height < tide_pre_df.tide_height.values).replace({
|
218
|
+
# True: "Ebb",
|
219
|
+
# False: "Flow",
|
220
|
+
# })
|
234
221
|
|
235
222
|
# Convert to xarray format
|
236
|
-
tide_xr = tide_df.reset_index().set_index(["time", "tide_model"]).drop(["x", "y"], axis=1).to_xarray()
|
223
|
+
tide_xr = tide_df.reset_index().set_index(["time", "tide_model"]).drop(["x", "y"], axis=1).tide_height.to_xarray()
|
237
224
|
|
238
225
|
# If only one tidal model exists, squeeze out "tide_model" dim
|
239
226
|
if len(tide_xr.tide_model) == 1:
|
240
|
-
tide_xr = tide_xr.squeeze("tide_model"
|
241
|
-
|
242
|
-
# Add each array into original dataset
|
243
|
-
for var in tide_xr.data_vars:
|
244
|
-
ds[var] = tide_xr[var]
|
245
|
-
|
246
|
-
# Swap dimensions and sort by tide height
|
247
|
-
if swap_dims:
|
248
|
-
ds = ds.swap_dims({"time": "tide_height"})
|
249
|
-
ds = ds.sortby("tide_height")
|
250
|
-
ds = ds.drop_vars("time")
|
227
|
+
tide_xr = tide_xr.squeeze("tide_model")
|
251
228
|
|
252
|
-
return
|
229
|
+
return tide_xr
|
253
230
|
|
254
231
|
|
255
232
|
def pixel_tides(
|
@@ -493,8 +470,10 @@ def pixel_tides(
|
|
493
470
|
# Set dtype to dtype of the input data as quantile always returns
|
494
471
|
# float64 (memory intensive)
|
495
472
|
if calculate_quantiles is not None:
|
496
|
-
|
497
|
-
|
473
|
+
with warnings.catch_warnings():
|
474
|
+
warnings.simplefilter("ignore")
|
475
|
+
print("Computing tide quantiles")
|
476
|
+
tides_lowres = tides_lowres.quantile(q=calculate_quantiles, dim="time").astype(tides_lowres.dtype)
|
498
477
|
|
499
478
|
# If only one tidal model exists, squeeze out "tide_model" dim
|
500
479
|
if len(tides_lowres.tide_model) == 1:
|
eo_tides/model.py
CHANGED
@@ -5,6 +5,7 @@ import os
|
|
5
5
|
import pathlib
|
6
6
|
import warnings
|
7
7
|
from concurrent.futures import ProcessPoolExecutor
|
8
|
+
from concurrent.futures.process import BrokenProcessPool
|
8
9
|
from functools import partial
|
9
10
|
from typing import TYPE_CHECKING
|
10
11
|
|
@@ -130,7 +131,7 @@ def list_models(
|
|
130
131
|
# Mark available models with a green tick
|
131
132
|
status = "✅"
|
132
133
|
print(f"{status:^{status_width}}│ {m:<{name_width}} │ {expected_paths[m]:<{path_width}}")
|
133
|
-
except:
|
134
|
+
except FileNotFoundError:
|
134
135
|
if show_supported:
|
135
136
|
# Mark unavailable models with a red cross
|
136
137
|
status = "❌"
|
@@ -199,84 +200,99 @@ def _model_tides(
|
|
199
200
|
lat.max() + buffer,
|
200
201
|
]
|
201
202
|
|
202
|
-
|
203
|
-
|
204
|
-
|
205
|
-
|
206
|
-
|
207
|
-
|
208
|
-
|
209
|
-
|
210
|
-
|
211
|
-
|
212
|
-
|
213
|
-
|
214
|
-
|
215
|
-
|
216
|
-
|
217
|
-
|
203
|
+
try:
|
204
|
+
# Read tidal constants and interpolate to grid points
|
205
|
+
if pytmd_model.format in ("OTIS", "ATLAS-compact", "TMD3"):
|
206
|
+
amp, ph, D, c = pyTMD.io.OTIS.extract_constants(
|
207
|
+
lon,
|
208
|
+
lat,
|
209
|
+
pytmd_model.grid_file,
|
210
|
+
pytmd_model.model_file,
|
211
|
+
pytmd_model.projection,
|
212
|
+
type=pytmd_model.type,
|
213
|
+
grid=pytmd_model.file_format,
|
214
|
+
crop=crop,
|
215
|
+
bounds=bounds,
|
216
|
+
method=method,
|
217
|
+
extrapolate=extrapolate,
|
218
|
+
cutoff=cutoff,
|
219
|
+
)
|
218
220
|
|
219
|
-
|
220
|
-
|
221
|
-
|
222
|
-
|
223
|
-
|
224
|
-
|
225
|
-
|
226
|
-
|
227
|
-
|
228
|
-
|
229
|
-
|
230
|
-
|
231
|
-
|
232
|
-
|
233
|
-
|
234
|
-
|
235
|
-
|
236
|
-
|
221
|
+
# Use delta time at 2000.0 to match TMD outputs
|
222
|
+
deltat = np.zeros((len(timescale)), dtype=np.float64)
|
223
|
+
|
224
|
+
elif pytmd_model.format in ("ATLAS-netcdf",):
|
225
|
+
amp, ph, D, c = pyTMD.io.ATLAS.extract_constants(
|
226
|
+
lon,
|
227
|
+
lat,
|
228
|
+
pytmd_model.grid_file,
|
229
|
+
pytmd_model.model_file,
|
230
|
+
type=pytmd_model.type,
|
231
|
+
crop=crop,
|
232
|
+
bounds=bounds,
|
233
|
+
method=method,
|
234
|
+
extrapolate=extrapolate,
|
235
|
+
cutoff=cutoff,
|
236
|
+
scale=pytmd_model.scale,
|
237
|
+
compressed=pytmd_model.compressed,
|
238
|
+
)
|
237
239
|
|
238
|
-
|
239
|
-
|
240
|
-
|
241
|
-
|
242
|
-
|
243
|
-
|
244
|
-
|
245
|
-
|
246
|
-
|
247
|
-
|
248
|
-
|
249
|
-
|
250
|
-
|
251
|
-
|
252
|
-
|
253
|
-
|
254
|
-
|
240
|
+
# Use delta time at 2000.0 to match TMD outputs
|
241
|
+
deltat = np.zeros((len(timescale)), dtype=np.float64)
|
242
|
+
|
243
|
+
elif pytmd_model.format in ("GOT-ascii", "GOT-netcdf"):
|
244
|
+
amp, ph, c = pyTMD.io.GOT.extract_constants(
|
245
|
+
lon,
|
246
|
+
lat,
|
247
|
+
pytmd_model.model_file,
|
248
|
+
grid=pytmd_model.file_format,
|
249
|
+
crop=crop,
|
250
|
+
bounds=bounds,
|
251
|
+
method=method,
|
252
|
+
extrapolate=extrapolate,
|
253
|
+
cutoff=cutoff,
|
254
|
+
scale=pytmd_model.scale,
|
255
|
+
compressed=pytmd_model.compressed,
|
256
|
+
)
|
255
257
|
|
256
|
-
|
257
|
-
|
258
|
-
|
259
|
-
|
260
|
-
|
261
|
-
|
262
|
-
|
263
|
-
|
264
|
-
|
265
|
-
|
266
|
-
|
267
|
-
|
268
|
-
|
269
|
-
|
270
|
-
|
271
|
-
|
272
|
-
|
273
|
-
|
258
|
+
# Delta time (TT - UT1)
|
259
|
+
deltat = timescale.tt_ut1
|
260
|
+
|
261
|
+
elif pytmd_model.format in ("FES-ascii", "FES-netcdf"):
|
262
|
+
amp, ph = pyTMD.io.FES.extract_constants(
|
263
|
+
lon,
|
264
|
+
lat,
|
265
|
+
pytmd_model.model_file,
|
266
|
+
type=pytmd_model.type,
|
267
|
+
version=pytmd_model.version,
|
268
|
+
crop=crop,
|
269
|
+
bounds=bounds,
|
270
|
+
method=method,
|
271
|
+
extrapolate=extrapolate,
|
272
|
+
cutoff=cutoff,
|
273
|
+
scale=pytmd_model.scale,
|
274
|
+
compressed=pytmd_model.compressed,
|
275
|
+
)
|
274
276
|
|
275
|
-
|
276
|
-
|
277
|
+
# Available model constituents
|
278
|
+
c = pytmd_model.constituents
|
277
279
|
|
278
|
-
|
279
|
-
|
280
|
+
# Delta time (TT - UT1)
|
281
|
+
deltat = timescale.tt_ut1
|
282
|
+
else:
|
283
|
+
raise Exception(
|
284
|
+
f"Unsupported model format ({pytmd_model.format}). This may be due to an incompatible version of `pyTMD`."
|
285
|
+
)
|
286
|
+
|
287
|
+
# Raise error if constituent files no not cover analysis extent
|
288
|
+
except IndexError:
|
289
|
+
error_msg = (
|
290
|
+
f"The {model} tide model constituent files do not cover the requested analysis extent. "
|
291
|
+
"This can occur if you are using clipped model files to improve run times. "
|
292
|
+
"Consider using model files that cover your analysis area, or set `crop=False` "
|
293
|
+
"to reduce the extent of tide model constituent files that is loaded."
|
294
|
+
)
|
295
|
+
raise Exception(error_msg)
|
280
296
|
|
281
297
|
# Calculate complex phase in radians for Euler's
|
282
298
|
cph = -1j * ph * np.pi / 180.0
|
@@ -783,12 +799,19 @@ def model_tides(
|
|
783
799
|
)
|
784
800
|
|
785
801
|
# Apply func in parallel, iterating through each input param
|
786
|
-
|
787
|
-
|
788
|
-
|
789
|
-
|
790
|
-
|
791
|
-
|
802
|
+
try:
|
803
|
+
model_outputs = list(
|
804
|
+
tqdm(
|
805
|
+
executor.map(iter_func, model_iters, x_iters, y_iters, time_iters),
|
806
|
+
total=len(model_iters),
|
807
|
+
),
|
808
|
+
)
|
809
|
+
except BrokenProcessPool:
|
810
|
+
error_msg = (
|
811
|
+
"Parallelised tide modelling failed, likely to to an out-of-memory error. "
|
812
|
+
"Try reducing the size of your analysis, or set `parallel=False`."
|
813
|
+
)
|
814
|
+
raise RuntimeError(error_msg)
|
792
815
|
|
793
816
|
# Model tides in series if parallelisation is off
|
794
817
|
else:
|
eo_tides/stats.py
CHANGED
@@ -8,16 +8,133 @@ import matplotlib.pyplot as plt
|
|
8
8
|
import numpy as np
|
9
9
|
import odc.geo.xr
|
10
10
|
import pandas as pd
|
11
|
+
import xarray as xr
|
11
12
|
from scipy import stats
|
12
13
|
|
13
14
|
# Only import if running type checking
|
14
15
|
if TYPE_CHECKING:
|
15
16
|
import xarray as xr
|
16
17
|
|
17
|
-
from .eo import tag_tides
|
18
|
+
from .eo import pixel_tides, tag_tides
|
18
19
|
from .model import model_tides
|
19
20
|
|
20
21
|
|
22
|
+
def _plot_biases(
|
23
|
+
all_tides_df,
|
24
|
+
obs_tides_da,
|
25
|
+
lat,
|
26
|
+
lot,
|
27
|
+
hat,
|
28
|
+
hot,
|
29
|
+
offset_low,
|
30
|
+
offset_high,
|
31
|
+
spread,
|
32
|
+
plot_col,
|
33
|
+
obs_linreg,
|
34
|
+
obs_x,
|
35
|
+
all_timerange,
|
36
|
+
):
|
37
|
+
"""
|
38
|
+
Plot tide bias statistics as a figure, including both
|
39
|
+
satellite observations and all modelled tides.
|
40
|
+
"""
|
41
|
+
|
42
|
+
# Create plot and add all time and observed tide data
|
43
|
+
fig, ax = plt.subplots(figsize=(10, 6))
|
44
|
+
all_tides_df.reset_index(["x", "y"]).tide_height.plot(ax=ax, alpha=0.4, label="Modelled tides")
|
45
|
+
|
46
|
+
# Look through custom column values if provided
|
47
|
+
if plot_col is not None:
|
48
|
+
# Create a list of marker styles
|
49
|
+
markers = [
|
50
|
+
"o",
|
51
|
+
"^",
|
52
|
+
"s",
|
53
|
+
"D",
|
54
|
+
"v",
|
55
|
+
"<",
|
56
|
+
">",
|
57
|
+
"p",
|
58
|
+
"*",
|
59
|
+
"h",
|
60
|
+
"H",
|
61
|
+
"+",
|
62
|
+
"x",
|
63
|
+
"d",
|
64
|
+
"|",
|
65
|
+
"_",
|
66
|
+
]
|
67
|
+
for i, value in enumerate(np.unique(plot_col)):
|
68
|
+
obs_tides_da.sel(time=plot_col == value).plot.line(
|
69
|
+
ax=ax,
|
70
|
+
linewidth=0.0,
|
71
|
+
color="black",
|
72
|
+
marker=markers[i % len(markers)],
|
73
|
+
markersize=4,
|
74
|
+
label=value,
|
75
|
+
)
|
76
|
+
# Otherwise, plot all data at once
|
77
|
+
else:
|
78
|
+
obs_tides_da.plot.line(
|
79
|
+
ax=ax,
|
80
|
+
marker="o",
|
81
|
+
linewidth=0.0,
|
82
|
+
color="black",
|
83
|
+
markersize=3.5,
|
84
|
+
label="Satellite observations",
|
85
|
+
)
|
86
|
+
|
87
|
+
# Add legend and remove title
|
88
|
+
ax.legend(
|
89
|
+
loc="upper center",
|
90
|
+
bbox_to_anchor=(0.5, 1.04),
|
91
|
+
ncol=20,
|
92
|
+
borderaxespad=0,
|
93
|
+
frameon=False,
|
94
|
+
)
|
95
|
+
ax.set_title("")
|
96
|
+
|
97
|
+
# Add linear regression line
|
98
|
+
if obs_linreg is not None:
|
99
|
+
ax.plot(
|
100
|
+
obs_tides_da.time.isel(time=[0, -1]),
|
101
|
+
obs_linreg.intercept + obs_linreg.slope * obs_x[[0, -1]],
|
102
|
+
"r",
|
103
|
+
label="fitted line",
|
104
|
+
)
|
105
|
+
|
106
|
+
# Add horizontal lines for spread/offsets
|
107
|
+
ax.axhline(lot, color="black", linestyle=":", linewidth=1)
|
108
|
+
ax.axhline(hot, color="black", linestyle=":", linewidth=1)
|
109
|
+
ax.axhline(lat, color="black", linestyle=":", linewidth=1)
|
110
|
+
ax.axhline(hat, color="black", linestyle=":", linewidth=1)
|
111
|
+
|
112
|
+
# Add text annotations for spread/offsets
|
113
|
+
ax.annotate(
|
114
|
+
f" High tide\n offset ({offset_high:.0%})",
|
115
|
+
xy=(all_timerange.max(), np.mean([hat, hot])),
|
116
|
+
va="center",
|
117
|
+
)
|
118
|
+
ax.annotate(
|
119
|
+
f" Spread\n ({spread:.0%})",
|
120
|
+
xy=(all_timerange.max(), np.mean([lot, hot])),
|
121
|
+
va="center",
|
122
|
+
)
|
123
|
+
ax.annotate(
|
124
|
+
f" Low tide\n offset ({offset_low:.0%})",
|
125
|
+
xy=(all_timerange.max(), np.mean([lat, lot])),
|
126
|
+
)
|
127
|
+
|
128
|
+
# Remove top right axes and add labels
|
129
|
+
ax.spines["right"].set_visible(False)
|
130
|
+
ax.spines["top"].set_visible(False)
|
131
|
+
ax.set_ylabel("Tide height (m)")
|
132
|
+
ax.set_xlabel("")
|
133
|
+
ax.margins(x=0.015)
|
134
|
+
|
135
|
+
return fig
|
136
|
+
|
137
|
+
|
21
138
|
def tide_stats(
|
22
139
|
ds: xr.Dataset,
|
23
140
|
model: str = "EOT20",
|
@@ -26,15 +143,18 @@ def tide_stats(
|
|
26
143
|
tidepost_lon: float | None = None,
|
27
144
|
plain_english: bool = True,
|
28
145
|
plot: bool = True,
|
29
|
-
|
146
|
+
plot_col: str | None = None,
|
147
|
+
modelled_freq: str = "3h",
|
30
148
|
linear_reg: bool = False,
|
149
|
+
min_max_q: tuple = (0.0, 1.0),
|
31
150
|
round_stats: int = 3,
|
32
151
|
**model_tides_kwargs,
|
33
152
|
) -> pd.Series:
|
34
153
|
"""
|
35
|
-
Takes a multi-dimensional dataset and generate statistics
|
36
|
-
|
37
|
-
|
154
|
+
Takes a multi-dimensional dataset and generate tide statistics
|
155
|
+
and satellite-observed tide bias metrics, calculated based on
|
156
|
+
every timestep in the satellte data and the geographic centroid
|
157
|
+
of the imagery.
|
38
158
|
|
39
159
|
By comparing the subset of tides observed by satellites
|
40
160
|
against the full astronomical tidal range, we can evaluate
|
@@ -48,8 +168,8 @@ def tide_stats(
|
|
48
168
|
Parameters
|
49
169
|
----------
|
50
170
|
ds : xarray.Dataset
|
51
|
-
A multi-dimensional dataset (e.g. "x", "y", "time")
|
52
|
-
|
171
|
+
A multi-dimensional dataset (e.g. "x", "y", "time") used
|
172
|
+
to calculate tide statistics. This dataset must contain
|
53
173
|
a "time" dimension.
|
54
174
|
model : string, optional
|
55
175
|
The tide model to use to model tides. Defaults to "EOT20";
|
@@ -73,10 +193,14 @@ def tide_stats(
|
|
73
193
|
An optional boolean indicating whether to plot how satellite-
|
74
194
|
observed tide heights compare against the full tidal range.
|
75
195
|
Defaults to True.
|
196
|
+
plot_col : str, optional
|
197
|
+
Optional name of a coordinate, dimension or variable in the array
|
198
|
+
that will be used to plot observations with unique symbols.
|
199
|
+
Defaults to None, which will plot all observations as circles.
|
76
200
|
modelled_freq : str, optional
|
77
201
|
An optional string giving the frequency at which to model tides
|
78
|
-
when computing the full modelled tidal range. Defaults to '
|
79
|
-
which computes a tide height for every
|
202
|
+
when computing the full modelled tidal range. Defaults to '3h',
|
203
|
+
which computes a tide height for every three hours across the
|
80
204
|
temporal extent of `ds`.
|
81
205
|
linear_reg: bool, optional
|
82
206
|
Whether to return linear regression statistics that assess
|
@@ -84,6 +208,11 @@ def tide_stats(
|
|
84
208
|
increasing trends over time. This may indicate whether your
|
85
209
|
satellite data may produce misleading trends based on uneven
|
86
210
|
sampling of the local tide regime.
|
211
|
+
min_max_q : tuple, optional
|
212
|
+
Quantiles used to calculate max and min observed and modelled
|
213
|
+
astronomical tides. By default `(0.0, 1.0)` which is equivalent
|
214
|
+
to minimum and maximum; to use a softer threshold that is more
|
215
|
+
robust to outliers, use e.g. `(0.1, 0.9)`.
|
87
216
|
round_stats : int, optional
|
88
217
|
The number of decimal places used to round the output statistics.
|
89
218
|
Defaults to 3.
|
@@ -96,25 +225,27 @@ def tide_stats(
|
|
96
225
|
|
97
226
|
Returns
|
98
227
|
-------
|
99
|
-
|
100
|
-
|
101
|
-
|
102
|
-
- `
|
103
|
-
- `
|
104
|
-
- `
|
105
|
-
- `
|
106
|
-
- `
|
107
|
-
- `
|
108
|
-
- `
|
109
|
-
- `
|
110
|
-
- `
|
111
|
-
- `
|
112
|
-
|
113
|
-
|
228
|
+
stats_df : pandas.Series
|
229
|
+
A `pandas.Series` containing the following statistics:
|
230
|
+
|
231
|
+
- `y`: latitude used for modelling tide heights
|
232
|
+
- `x`: longitude used for modelling tide heights
|
233
|
+
- `mot`: mean tide height observed by the satellite (metres)
|
234
|
+
- `mat`: mean modelled astronomical tide height (metres)
|
235
|
+
- `lot`: minimum tide height observed by the satellite (metres)
|
236
|
+
- `lat`: minimum tide height from modelled astronomical tidal range (metres)
|
237
|
+
- `hot`: maximum tide height observed by the satellite (metres)
|
238
|
+
- `hat`: maximum tide height from modelled astronomical tidal range (metres)
|
239
|
+
- `otr`: tidal range observed by the satellite (metres)
|
240
|
+
- `tr`: modelled astronomical tide range (metres)
|
241
|
+
- `spread`: proportion of the full modelled tidal range observed by the satellite
|
242
|
+
- `offset_low`: proportion of the lowest tides never observed by the satellite
|
243
|
+
- `offset_high`: proportion of the highest tides never observed by the satellite
|
244
|
+
|
245
|
+
If `linear_reg = True`, the output will also contain:
|
114
246
|
|
115
247
|
- `observed_slope`: slope of any relationship between observed tide heights and time
|
116
248
|
- `observed_pval`: significance/p-value of any relationship between observed tide heights and time
|
117
|
-
|
118
249
|
"""
|
119
250
|
# Verify that only one tide model is provided
|
120
251
|
if isinstance(model, list):
|
@@ -126,7 +257,7 @@ def tide_stats(
|
|
126
257
|
tidepost_lon, tidepost_lat = ds.odc.geobox.geographic_extent.centroid.coords[0]
|
127
258
|
|
128
259
|
# Model tides for each observation in the supplied xarray object
|
129
|
-
|
260
|
+
obs_tides_da = tag_tides(
|
130
261
|
ds,
|
131
262
|
model=model,
|
132
263
|
directory=directory,
|
@@ -135,14 +266,12 @@ def tide_stats(
|
|
135
266
|
return_tideposts=True,
|
136
267
|
**model_tides_kwargs,
|
137
268
|
)
|
138
|
-
|
139
|
-
# Drop spatial ref for nicer plotting
|
140
|
-
ds_tides = ds_tides.drop_vars("spatial_ref")
|
269
|
+
obs_tides_da = obs_tides_da.sortby("time")
|
141
270
|
|
142
271
|
# Generate range of times covering entire period of satellite record
|
143
272
|
all_timerange = pd.date_range(
|
144
|
-
start=
|
145
|
-
end=
|
273
|
+
start=obs_tides_da.time.min().item(),
|
274
|
+
end=obs_tides_da.time.max().item(),
|
146
275
|
freq=modelled_freq,
|
147
276
|
)
|
148
277
|
|
@@ -158,10 +287,10 @@ def tide_stats(
|
|
158
287
|
)
|
159
288
|
|
160
289
|
# Get coarse statistics on all and observed tidal ranges
|
161
|
-
obs_mean =
|
290
|
+
obs_mean = obs_tides_da.mean().item()
|
162
291
|
all_mean = all_tides_df.tide_height.mean()
|
163
|
-
obs_min, obs_max =
|
164
|
-
all_min, all_max = all_tides_df.tide_height.quantile(
|
292
|
+
obs_min, obs_max = obs_tides_da.quantile(min_max_q).values
|
293
|
+
all_min, all_max = all_tides_df.tide_height.quantile(min_max_q).values
|
165
294
|
|
166
295
|
# Calculate tidal range
|
167
296
|
obs_range = obs_max - obs_min
|
@@ -169,92 +298,84 @@ def tide_stats(
|
|
169
298
|
|
170
299
|
# Calculate Bishop-Taylor et al. 2018 tidal metrics
|
171
300
|
spread = obs_range / all_range
|
172
|
-
|
173
|
-
|
174
|
-
|
175
|
-
|
176
|
-
|
177
|
-
|
178
|
-
|
301
|
+
low_tide_offset_m = abs(all_min - obs_min)
|
302
|
+
high_tide_offset_m = abs(all_max - obs_max)
|
303
|
+
low_tide_offset = low_tide_offset_m / all_range
|
304
|
+
high_tide_offset = high_tide_offset_m / all_range
|
305
|
+
|
306
|
+
# Plain text descriptors
|
307
|
+
mean_diff = "higher" if obs_mean > all_mean else "lower"
|
308
|
+
mean_diff_icon = "⬆️" if obs_mean > all_mean else "⬇️"
|
309
|
+
spread_icon = "🟢" if spread >= 0.9 else "🟡" if 0.7 < spread <= 0.9 else "🔴"
|
310
|
+
low_tide_icon = "🟢" if low_tide_offset <= 0.1 else "🟡" if 0.1 <= low_tide_offset < 0.2 else "🔴"
|
311
|
+
high_tide_icon = "🟢" if high_tide_offset <= 0.1 else "🟡" if 0.1 <= high_tide_offset < 0.2 else "🔴"
|
179
312
|
|
180
313
|
# Extract x (time in decimal years) and y (distance) values
|
181
|
-
obs_x =
|
182
|
-
|
314
|
+
obs_x = (
|
315
|
+
obs_tides_da.time.dt.year + ((obs_tides_da.time.dt.dayofyear - 1) / 365) + ((obs_tides_da.time.dt.hour) / 24)
|
316
|
+
)
|
317
|
+
obs_y = obs_tides_da.values.astype(np.float32)
|
183
318
|
|
184
319
|
# Compute linear regression
|
185
320
|
obs_linreg = stats.linregress(x=obs_x, y=obs_y)
|
186
321
|
|
187
322
|
if plain_english:
|
323
|
+
print(f"\n\n🌊 Modelled astronomical tide range: {all_range:.2f} metres.")
|
324
|
+
print(f"🛰️ Observed tide range: {obs_range:.2f} metres.\n")
|
325
|
+
print(f"{spread_icon} {spread:.0%} of the modelled astronomical tide range was observed at this location.")
|
188
326
|
print(
|
189
|
-
f"
|
190
|
-
|
191
|
-
|
192
|
-
f"of
|
327
|
+
f"{high_tide_icon} The highest {high_tide_offset:.0%} ({high_tide_offset_m:.2f} metres) of the tide range was never observed."
|
328
|
+
)
|
329
|
+
print(
|
330
|
+
f"{low_tide_icon} The lowest {low_tide_offset:.0%} ({low_tide_offset_m:.2f} metres) of the tide range was never observed.\n"
|
331
|
+
)
|
332
|
+
print(f"🌊 Mean modelled astronomical tide height: {all_mean:.2f} metres.")
|
333
|
+
print(f"🛰️ Mean observed tide height: {obs_mean:.2f} metres.\n")
|
334
|
+
print(
|
335
|
+
f"{mean_diff_icon} The mean observed tide height was {obs_mean - all_mean:.2f} metres {mean_diff} than the mean modelled astronomical tide height."
|
193
336
|
)
|
194
337
|
|
195
338
|
if linear_reg:
|
196
|
-
if obs_linreg.pvalue > 0.
|
197
|
-
print(
|
339
|
+
if obs_linreg.pvalue > 0.01:
|
340
|
+
print("➖ Observed tides showed no significant trends over time.")
|
198
341
|
else:
|
199
|
-
obs_slope_desc = "
|
342
|
+
obs_slope_desc = "decreasing" if obs_linreg.slope < 0 else "increasing"
|
200
343
|
print(
|
201
|
-
f"Observed tides {obs_slope_desc}
|
202
|
-
f"(p={obs_linreg.pvalue:.3f}) over time by "
|
203
|
-
f"{obs_linreg.slope:.03f} m per year (i.e. a "
|
204
|
-
f"~{time_period * obs_linreg.slope:.2f} m "
|
205
|
-
f"{obs_slope_desc} over the ~{time_period:.0f} year period)."
|
344
|
+
f"⚠️ Observed tides showed a significant {obs_slope_desc} trend over time (p={obs_linreg.pvalue:.3f}, {obs_linreg.slope:.2f} metres per year)"
|
206
345
|
)
|
207
346
|
|
208
347
|
if plot:
|
209
|
-
|
210
|
-
|
211
|
-
|
212
|
-
|
213
|
-
|
214
|
-
|
215
|
-
|
216
|
-
|
217
|
-
|
218
|
-
|
219
|
-
|
220
|
-
|
221
|
-
|
222
|
-
|
223
|
-
xy=(all_timerange.max(), np.mean([all_max, obs_max])),
|
224
|
-
va="center",
|
225
|
-
)
|
226
|
-
ax.annotate(
|
227
|
-
f" Spread\n ({spread:.0%})",
|
228
|
-
xy=(all_timerange.max(), np.mean([obs_min, obs_max])),
|
229
|
-
va="center",
|
230
|
-
)
|
231
|
-
ax.annotate(
|
232
|
-
f" Low tide\n offset ({low_tide_offset:.0%})",
|
233
|
-
xy=(all_timerange.max(), np.mean([all_min, obs_min])),
|
348
|
+
_plot_biases(
|
349
|
+
all_tides_df=all_tides_df,
|
350
|
+
obs_tides_da=obs_tides_da,
|
351
|
+
lat=all_min,
|
352
|
+
lot=obs_min,
|
353
|
+
hat=all_max,
|
354
|
+
hot=obs_max,
|
355
|
+
offset_low=low_tide_offset,
|
356
|
+
offset_high=high_tide_offset,
|
357
|
+
spread=spread,
|
358
|
+
plot_col=ds[plot_col] if plot_col else None,
|
359
|
+
obs_linreg=obs_linreg if linear_reg else None,
|
360
|
+
obs_x=obs_x,
|
361
|
+
all_timerange=all_timerange,
|
234
362
|
)
|
235
363
|
|
236
|
-
# Remove top right axes and add labels
|
237
|
-
ax.spines["right"].set_visible(False)
|
238
|
-
ax.spines["top"].set_visible(False)
|
239
|
-
ax.set_ylabel("Tide height (m)")
|
240
|
-
ax.set_xlabel("")
|
241
|
-
ax.margins(x=0.015)
|
242
|
-
|
243
364
|
# Export pandas.Series containing tidal stats
|
244
365
|
output_stats = {
|
245
|
-
"
|
246
|
-
"
|
247
|
-
"
|
248
|
-
"
|
249
|
-
"
|
250
|
-
"
|
251
|
-
"
|
252
|
-
"
|
253
|
-
"
|
254
|
-
"
|
366
|
+
"y": tidepost_lat,
|
367
|
+
"x": tidepost_lon,
|
368
|
+
"mot": obs_mean,
|
369
|
+
"mat": all_mean,
|
370
|
+
"lot": obs_min,
|
371
|
+
"lat": all_min,
|
372
|
+
"hot": obs_max,
|
373
|
+
"hat": all_max,
|
374
|
+
"otr": obs_range,
|
375
|
+
"tr": all_range,
|
255
376
|
"spread": spread,
|
256
|
-
"
|
257
|
-
"
|
377
|
+
"offset_low": low_tide_offset,
|
378
|
+
"offset_high": high_tide_offset,
|
258
379
|
}
|
259
380
|
|
260
381
|
if linear_reg:
|
@@ -263,4 +384,171 @@ def tide_stats(
|
|
263
384
|
"observed_pval": obs_linreg.pvalue,
|
264
385
|
})
|
265
386
|
|
266
|
-
|
387
|
+
# Return pandas data
|
388
|
+
stats_df = pd.Series(output_stats).round(round_stats)
|
389
|
+
return stats_df
|
390
|
+
|
391
|
+
|
392
|
+
def pixel_stats(
|
393
|
+
ds: xr.Dataset | xr.DataArray,
|
394
|
+
model: str | list[str] = "EOT20",
|
395
|
+
directory: str | os.PathLike | None = None,
|
396
|
+
resample: bool = False,
|
397
|
+
modelled_freq="3h",
|
398
|
+
min_max_q=(0.0, 1.0),
|
399
|
+
extrapolate: bool = True,
|
400
|
+
cutoff: float = 10,
|
401
|
+
**pixel_tides_kwargs,
|
402
|
+
) -> xr.Dataset:
|
403
|
+
"""
|
404
|
+
Takes a multi-dimensional dataset and generate two-dimensional
|
405
|
+
tide statistics and satellite-observed tide bias metrics,
|
406
|
+
calculated based on every timestep in the satellte data and
|
407
|
+
modelled into the spatial extent of the imagery.
|
408
|
+
|
409
|
+
By comparing the subset of tides observed by satellites
|
410
|
+
against the full astronomical tidal range, we can evaluate
|
411
|
+
whether the tides observed by satellites are biased
|
412
|
+
(e.g. fail to observe either the highest or lowest tides).
|
413
|
+
|
414
|
+
Compared to `tide_stats`, this function models tide metrics
|
415
|
+
spatially to produce a two-dimensional output.
|
416
|
+
|
417
|
+
For more information about the tidal statistics computed by this
|
418
|
+
function, refer to Figure 8 in Bishop-Taylor et al. 2018:
|
419
|
+
<https://www.sciencedirect.com/science/article/pii/S0272771418308783#fig8>
|
420
|
+
|
421
|
+
Parameters
|
422
|
+
----------
|
423
|
+
ds : xarray.Dataset or xarray.DataArray
|
424
|
+
A multi-dimensional dataset (e.g. "x", "y", "time") used
|
425
|
+
to calculate 2D tide statistics. This dataset must contain
|
426
|
+
a "time" dimension.
|
427
|
+
model : str or list of str, optional
|
428
|
+
The tide model (or models) to use to model tides. If a list is
|
429
|
+
provided, a new "tide_model" dimension will be added to `ds`.
|
430
|
+
Defaults to "EOT20"; for a full list of available/supported
|
431
|
+
models, run `eo_tides.model.list_models`.
|
432
|
+
directory : str, optional
|
433
|
+
The directory containing tide model data files. If no path is
|
434
|
+
provided, this will default to the environment variable
|
435
|
+
`EO_TIDES_TIDE_MODELS` if set, or raise an error if not.
|
436
|
+
Tide modelling files should be stored in sub-folders for each
|
437
|
+
model that match the structure required by `pyTMD`
|
438
|
+
(<https://geoscienceaustralia.github.io/eo-tides/setup/>).
|
439
|
+
resample : bool, optional
|
440
|
+
Whether to resample tide statistics back into `ds`'s original
|
441
|
+
higher resolution grid. Defaults to False, which will return
|
442
|
+
lower-resolution statistics that are typically sufficient for
|
443
|
+
most purposes.
|
444
|
+
modelled_freq : str, optional
|
445
|
+
An optional string giving the frequency at which to model tides
|
446
|
+
when computing the full modelled tidal range. Defaults to '3h',
|
447
|
+
which computes a tide height for every three hours across the
|
448
|
+
temporal extent of `ds`.
|
449
|
+
min_max_q : tuple, optional
|
450
|
+
Quantiles used to calculate max and min observed and modelled
|
451
|
+
astronomical tides. By default `(0.0, 1.0)` which is equivalent
|
452
|
+
to minimum and maximum; to use a softer threshold that is more
|
453
|
+
robust to outliers, use e.g. `(0.1, 0.9)`.
|
454
|
+
extrapolate : bool, optional
|
455
|
+
Whether to extrapolate tides for x and y coordinates outside of
|
456
|
+
the valid tide modelling domain using nearest-neighbor. Defaults
|
457
|
+
to True.
|
458
|
+
cutoff : float, optional
|
459
|
+
Extrapolation cutoff in kilometers. To avoid producing tide
|
460
|
+
statistics too far inland, the default is 10 km.
|
461
|
+
**pixel_tides_kwargs :
|
462
|
+
Optional parameters passed to the `eo_tides.eo.pixel_tides`
|
463
|
+
function.
|
464
|
+
|
465
|
+
Returns
|
466
|
+
-------
|
467
|
+
stats_ds : xarray.Dataset
|
468
|
+
An `xarray.Dataset` containing the following statistics as two-dimensional data variables:
|
469
|
+
|
470
|
+
- `lot`: minimum tide height observed by the satellite (metres)
|
471
|
+
- `lat`: minimum tide height from modelled astronomical tidal range (metres)
|
472
|
+
- `hot`: maximum tide height observed by the satellite (metres)
|
473
|
+
- `hat`: maximum tide height from modelled astronomical tidal range (metres)
|
474
|
+
- `otr`: tidal range observed by the satellite (metres)
|
475
|
+
- `tr`: modelled astronomical tide range (metres)
|
476
|
+
- `spread`: proportion of the full modelled tidal range observed by the satellite
|
477
|
+
- `offset_low`: proportion of the lowest tides never observed by the satellite
|
478
|
+
- `offset_high`: proportion of the highest tides never observed by the satellite
|
479
|
+
|
480
|
+
"""
|
481
|
+
# Model observed tides
|
482
|
+
obs_tides = pixel_tides(
|
483
|
+
ds,
|
484
|
+
resample=False,
|
485
|
+
model=model,
|
486
|
+
directory=directory,
|
487
|
+
calculate_quantiles=min_max_q,
|
488
|
+
extrapolate=extrapolate,
|
489
|
+
cutoff=cutoff,
|
490
|
+
**pixel_tides_kwargs,
|
491
|
+
)
|
492
|
+
|
493
|
+
# Generate times covering entire period of satellite record
|
494
|
+
all_timerange = pd.date_range(
|
495
|
+
start=ds.time.min().item(),
|
496
|
+
end=ds.time.max().item(),
|
497
|
+
freq=modelled_freq,
|
498
|
+
)
|
499
|
+
|
500
|
+
# Model all tides
|
501
|
+
all_tides = pixel_tides(
|
502
|
+
ds,
|
503
|
+
times=all_timerange,
|
504
|
+
model=model,
|
505
|
+
directory=directory,
|
506
|
+
calculate_quantiles=min_max_q,
|
507
|
+
resample=False,
|
508
|
+
extrapolate=extrapolate,
|
509
|
+
cutoff=cutoff,
|
510
|
+
**pixel_tides_kwargs,
|
511
|
+
)
|
512
|
+
|
513
|
+
# Calculate min and max tides
|
514
|
+
lot = obs_tides.isel(quantile=0)
|
515
|
+
hot = obs_tides.isel(quantile=-1)
|
516
|
+
lat = all_tides.isel(quantile=0)
|
517
|
+
hat = all_tides.isel(quantile=-1)
|
518
|
+
|
519
|
+
# Calculate tidal range
|
520
|
+
otr = hot - lot
|
521
|
+
tr = hat - lat
|
522
|
+
|
523
|
+
# Calculate Bishop-Taylor et al. 2018 tidal metrics
|
524
|
+
spread = otr / tr
|
525
|
+
offset_low_m = abs(lat - lot)
|
526
|
+
offset_high_m = abs(hat - hot)
|
527
|
+
offset_low = offset_low_m / tr
|
528
|
+
offset_high = offset_high_m / tr
|
529
|
+
|
530
|
+
# Combine into a single dataset
|
531
|
+
stats_ds = (
|
532
|
+
xr.merge(
|
533
|
+
[
|
534
|
+
hat.rename("hat"),
|
535
|
+
hot.rename("hot"),
|
536
|
+
lat.rename("lat"),
|
537
|
+
lot.rename("lot"),
|
538
|
+
otr.rename("otr"),
|
539
|
+
tr.rename("tr"),
|
540
|
+
spread.rename("spread"),
|
541
|
+
offset_low.rename("offset_low"),
|
542
|
+
offset_high.rename("offset_high"),
|
543
|
+
],
|
544
|
+
compat="override",
|
545
|
+
)
|
546
|
+
.drop_vars("quantile")
|
547
|
+
.odc.assign_crs(crs=ds.odc.crs)
|
548
|
+
)
|
549
|
+
|
550
|
+
# Optionally resample into the original pixel grid of `ds`
|
551
|
+
if resample:
|
552
|
+
stats_ds = stats_ds.odc.reproject(how=ds.odc.geobox, resample_method="bilinear")
|
553
|
+
|
554
|
+
return stats_ds
|
eo_tides/validation.py
CHANGED
@@ -153,19 +153,21 @@ def _load_gauge_metadata(metadata_path):
|
|
153
153
|
|
154
154
|
|
155
155
|
def _load_gesla_dataset(site, path, na_value):
|
156
|
-
|
157
|
-
|
158
|
-
|
159
|
-
|
160
|
-
|
161
|
-
|
162
|
-
|
163
|
-
|
164
|
-
|
156
|
+
with warnings.catch_warnings():
|
157
|
+
warnings.simplefilter("ignore", FutureWarning)
|
158
|
+
gesla_df = (
|
159
|
+
pd.read_csv(
|
160
|
+
path,
|
161
|
+
skiprows=41,
|
162
|
+
names=["date", "time", "sea_level", "qc_flag", "use_flag"],
|
163
|
+
sep=r"\s+", # sep="\s+",
|
164
|
+
parse_dates=[[0, 1]],
|
165
|
+
index_col=0,
|
166
|
+
na_values=na_value,
|
167
|
+
)
|
168
|
+
.rename_axis("time")
|
169
|
+
.assign(site_code=site)
|
165
170
|
)
|
166
|
-
.rename_axis("time")
|
167
|
-
.assign(site_code=site)
|
168
|
-
)
|
169
171
|
|
170
172
|
return gesla_df
|
171
173
|
|
@@ -267,7 +269,12 @@ def load_gauge_gesla(
|
|
267
269
|
|
268
270
|
# If x and y are single numbers, select nearest row
|
269
271
|
elif isinstance(x, Number) & isinstance(y, Number):
|
270
|
-
|
272
|
+
with warnings.catch_warnings():
|
273
|
+
warnings.simplefilter("ignore")
|
274
|
+
site_code = (
|
275
|
+
_nearest_row(metadata_gdf, x, y, max_distance).rename({"index_right": "site_code"}, axis=1).site_code
|
276
|
+
)
|
277
|
+
# site_code = _nearest_row(metadata_gdf, x, y, max_distance).site_code
|
271
278
|
|
272
279
|
# Raise exception if no valid tide gauges are found
|
273
280
|
if site_code.isnull().all():
|
@@ -1,6 +1,6 @@
|
|
1
1
|
Metadata-Version: 2.1
|
2
2
|
Name: eo-tides
|
3
|
-
Version: 0.0.
|
3
|
+
Version: 0.0.23
|
4
4
|
Summary: Tide modelling tools for large-scale satellite earth observation analysis
|
5
5
|
Author-email: Robbi Bishop-Taylor <Robbi.BishopTaylor@ga.gov.au>
|
6
6
|
Project-URL: Homepage, https://GeoscienceAustralia.github.io/eo-tides/
|
@@ -22,23 +22,25 @@ Classifier: Programming Language :: Python :: 3.12
|
|
22
22
|
Requires-Python: <4.0,>=3.9
|
23
23
|
Description-Content-Type: text/markdown
|
24
24
|
License-File: LICENSE
|
25
|
-
Requires-Dist: colorama
|
26
|
-
Requires-Dist: geopandas >=
|
27
|
-
Requires-Dist:
|
28
|
-
Requires-Dist:
|
29
|
-
Requires-Dist:
|
30
|
-
Requires-Dist:
|
25
|
+
Requires-Dist: colorama >=0.4.3
|
26
|
+
Requires-Dist: geopandas >=0.10.0
|
27
|
+
Requires-Dist: matplotlib >=3.8.0
|
28
|
+
Requires-Dist: numpy >=1.26.0
|
29
|
+
Requires-Dist: odc-geo >=0.4.7
|
30
|
+
Requires-Dist: pandas >=2.2.0
|
31
|
+
Requires-Dist: pyproj >=3.6.1
|
31
32
|
Requires-Dist: pyTMD ==2.1.6
|
32
|
-
Requires-Dist: scikit-learn
|
33
|
-
Requires-Dist: scipy
|
34
|
-
Requires-Dist: shapely
|
35
|
-
Requires-Dist: tqdm
|
36
|
-
Requires-Dist: xarray
|
33
|
+
Requires-Dist: scikit-learn >=1.4.0
|
34
|
+
Requires-Dist: scipy >=1.11.2
|
35
|
+
Requires-Dist: shapely >=2.0.6
|
36
|
+
Requires-Dist: tqdm >=4.55.0
|
37
|
+
Requires-Dist: xarray >=2022.3.0
|
37
38
|
Provides-Extra: notebooks
|
38
39
|
Requires-Dist: odc-stac >=0.3.10 ; extra == 'notebooks'
|
39
|
-
Requires-Dist:
|
40
|
-
Requires-Dist:
|
41
|
-
Requires-Dist:
|
40
|
+
Requires-Dist: odc-geo[tiff,warp] >=0.4.7 ; extra == 'notebooks'
|
41
|
+
Requires-Dist: pystac-client >=0.8.3 ; extra == 'notebooks'
|
42
|
+
Requires-Dist: folium >=0.16.0 ; extra == 'notebooks'
|
43
|
+
Requires-Dist: planetary-computer >=1.0.0 ; extra == 'notebooks'
|
42
44
|
|
43
45
|
# `eo-tides`: Tide modelling tools for large-scale satellite earth observation analysis
|
44
46
|
|
@@ -46,7 +48,7 @@ Requires-Dist: matplotlib ; extra == 'notebooks'
|
|
46
48
|
|
47
49
|
[](https://pypi.org/project/eo-tides/)
|
48
50
|
[](https://github.com/GeoscienceAustralia/eo-tides/actions/workflows/main.yml?query=branch%3Amain)
|
49
|
-

|
50
52
|
[](https://codecov.io/gh/GeoscienceAustralia/eo-tides)
|
51
53
|
[](https://img.shields.io/github/license/GeoscienceAustralia/eo-tides)
|
52
54
|
|
@@ -69,9 +71,9 @@ These tools can be applied to petabytes of freely available satellite data (e.g.
|
|
69
71
|
- 🌊 Model tides from multiple global ocean tide models in parallel, and return tide heights in standardised `pandas.DataFrame` format for further analysis
|
70
72
|
- 🛰️ "Tag" satellite data with tide height and stage based on the exact moment of image acquisition
|
71
73
|
- 🌐 Model tides for every individual satellite pixel, producing three-dimensional "tide height" `xarray`-format datacubes that can be integrated with satellite data
|
72
|
-
<!-- - 🎯 Combine multiple tide models into a single locally-optimised "ensemble" model informed by satellite altimetry and satellite-observed patterns of tidal inundation -->
|
73
74
|
- 📈 Calculate statistics describing local tide dynamics, as well as biases caused by interactions between tidal processes and satellite orbits
|
74
75
|
- 🛠️ Validate modelled tides using measured sea levels from coastal tide gauges (e.g. [GESLA Global Extreme Sea Level Analysis](https://gesla.org/))
|
76
|
+
<!-- - 🎯 Combine multiple tide models into a single locally-optimised "ensemble" model informed by satellite altimetry and satellite-observed patterns of tidal inundation -->
|
75
77
|
|
76
78
|
## Supported tide models
|
77
79
|
|
@@ -85,6 +87,10 @@ These tools can be applied to petabytes of freely available satellite data (e.g.
|
|
85
87
|
|
86
88
|
For instructions on how to set up these models for use in `eo-tides`, refer to [Setting up tide models](setup.md).
|
87
89
|
|
90
|
+
## Installing and setting up `eo-tides`
|
91
|
+
|
92
|
+
To get started with `eo-tides`, follow the [Installation](https://geoscienceaustralia.github.io/eo-tides/install/) and [Setting up tide models](https://geoscienceaustralia.github.io/eo-tides/setup/) guides.
|
93
|
+
|
88
94
|
## Citing `eo-tides`
|
89
95
|
|
90
96
|
To cite `eo-tides` in your work, please use the following citation:
|
@@ -92,3 +98,8 @@ To cite `eo-tides` in your work, please use the following citation:
|
|
92
98
|
```
|
93
99
|
Bishop-Taylor, R., Sagar, S., Phillips, C., & Newey, V. (2024). eo-tides: Tide modelling tools for large-scale satellite earth observation analysis. https://github.com/GeoscienceAustralia/eo-tides
|
94
100
|
```
|
101
|
+
|
102
|
+
## Acknowledgements
|
103
|
+
|
104
|
+
For a full list of acknowledgements, refer to [Citations and Credits](https://geoscienceaustralia.github.io/eo-tides/credits/).
|
105
|
+
This repository was initialised using the [`cookiecutter-uv`](https://github.com/fpgmaas/cookiecutter-uv) package.
|
@@ -0,0 +1,11 @@
|
|
1
|
+
eo_tides/__init__.py,sha256=TWmQNplePCcNAlua5WI_H7SShkWNk-Gd3X70EDEknSo,1557
|
2
|
+
eo_tides/eo.py,sha256=OTFjchQWpSfgi2Q62mFBj6ckmy_1B_ExCAmex4UT4js,21616
|
3
|
+
eo_tides/model.py,sha256=XoJoIa6SmcAR8v1IDisqV8nW44XfY4YU4HWCoTgokx0,34390
|
4
|
+
eo_tides/stats.py,sha256=9nfa7_obkS4tiHrQ1WTutuy2jlHtFg-cmOqP3l_Q7b8,20644
|
5
|
+
eo_tides/utils.py,sha256=l9VXJawQzaRBYaFMsP8VBeaN5VA3rFDdzcvF7Rk04Vc,5620
|
6
|
+
eo_tides/validation.py,sha256=JjTUqDfbR189m_6W1bpaSolQIHNTLicTHN7z9O_nr3s,11828
|
7
|
+
eo_tides-0.0.23.dist-info/LICENSE,sha256=owxWsXViCL2J6Ks3XYhot7t4Y93nstmXAT95Zf030Cc,11350
|
8
|
+
eo_tides-0.0.23.dist-info/METADATA,sha256=P01mYqtSyfTHIVAduGhTPyRbQe-ZNuCJXhYqKa4sq_s,6902
|
9
|
+
eo_tides-0.0.23.dist-info/WHEEL,sha256=OVMc5UfuAQiSplgO0_WdW7vXVGAt9Hdd6qtN4HotdyA,91
|
10
|
+
eo_tides-0.0.23.dist-info/top_level.txt,sha256=lXZDUUM1DlLdKWHRn8zdmtW8Rx-eQOIWVvt0b8VGiyQ,9
|
11
|
+
eo_tides-0.0.23.dist-info/RECORD,,
|
eo_tides-0.0.21.dist-info/RECORD
DELETED
@@ -1,11 +0,0 @@
|
|
1
|
-
eo_tides/__init__.py,sha256=TWmQNplePCcNAlua5WI_H7SShkWNk-Gd3X70EDEknSo,1557
|
2
|
-
eo_tides/eo.py,sha256=K2Ubxvp5yYxVwAOaUZDHCIV_AppRuqMiQRtArhe7YOM,22480
|
3
|
-
eo_tides/model.py,sha256=WC-3gBH-ToREl1RS6CxYJLfXlpy03vtwZKd_464QYcU,32958
|
4
|
-
eo_tides/stats.py,sha256=gpkewfgM7ixOr_XXepkMHpezmvjlt5-Qo2xbUmzyKhs,10898
|
5
|
-
eo_tides/utils.py,sha256=l9VXJawQzaRBYaFMsP8VBeaN5VA3rFDdzcvF7Rk04Vc,5620
|
6
|
-
eo_tides/validation.py,sha256=yFuIjAxS9qf097_n4DHWG4AOa6n4nt1HGibUkOkJW6o,11437
|
7
|
-
eo_tides-0.0.21.dist-info/LICENSE,sha256=owxWsXViCL2J6Ks3XYhot7t4Y93nstmXAT95Zf030Cc,11350
|
8
|
-
eo_tides-0.0.21.dist-info/METADATA,sha256=t60AoRbSg9K6GEXlk3TCd9nrEQbaFrHp7rgAmwAda_g,6298
|
9
|
-
eo_tides-0.0.21.dist-info/WHEEL,sha256=GV9aMThwP_4oNCtvEC2ec3qUYutgWeAzklro_0m4WJQ,91
|
10
|
-
eo_tides-0.0.21.dist-info/top_level.txt,sha256=lXZDUUM1DlLdKWHRn8zdmtW8Rx-eQOIWVvt0b8VGiyQ,9
|
11
|
-
eo_tides-0.0.21.dist-info/RECORD,,
|
File without changes
|
File without changes
|