eo-tides 0.2.0__py3-none-any.whl → 0.3.1__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/__init__.py +4 -3
- eo_tides/eo.py +2 -4
- eo_tides/model.py +218 -371
- eo_tides/stats.py +2 -4
- eo_tides/utils.py +546 -1
- eo_tides/validation.py +5 -5
- {eo_tides-0.2.0.dist-info → eo_tides-0.3.1.dist-info}/METADATA +13 -9
- eo_tides-0.3.1.dist-info/RECORD +11 -0
- {eo_tides-0.2.0.dist-info → eo_tides-0.3.1.dist-info}/WHEEL +1 -1
- eo_tides-0.2.0.dist-info/RECORD +0 -11
- {eo_tides-0.2.0.dist-info → eo_tides-0.3.1.dist-info}/LICENSE +0 -0
- {eo_tides-0.2.0.dist-info → eo_tides-0.3.1.dist-info}/top_level.txt +0 -0
eo_tides/utils.py
CHANGED
@@ -1,5 +1,550 @@
|
|
1
|
+
# Used to postpone evaluation of type annotations
|
2
|
+
from __future__ import annotations
|
3
|
+
|
4
|
+
import datetime
|
5
|
+
import os
|
6
|
+
import pathlib
|
7
|
+
import textwrap
|
8
|
+
import warnings
|
9
|
+
from typing import List, Union
|
10
|
+
|
1
11
|
import numpy as np
|
12
|
+
import odc.geo
|
13
|
+
import pandas as pd
|
14
|
+
import xarray as xr
|
15
|
+
from colorama import Style, init
|
16
|
+
from odc.geo.geom import BoundingBox
|
17
|
+
from pyTMD.io.model import load_database
|
18
|
+
from pyTMD.io.model import model as pytmd_model
|
2
19
|
from scipy.spatial import cKDTree as KDTree
|
20
|
+
from tqdm import tqdm
|
21
|
+
|
22
|
+
# Type alias for all possible inputs to "time" params
|
23
|
+
DatetimeLike = Union[np.ndarray, pd.DatetimeIndex, pd.Timestamp, datetime.datetime, str, List[str]]
|
24
|
+
|
25
|
+
|
26
|
+
def _set_directory(
|
27
|
+
directory: str | os.PathLike | None = None,
|
28
|
+
) -> os.PathLike:
|
29
|
+
"""
|
30
|
+
Set tide modelling files directory. If no custom
|
31
|
+
path is provided, try global `EO_TIDES_TIDE_MODELS`
|
32
|
+
environmental variable instead.
|
33
|
+
"""
|
34
|
+
if directory is None:
|
35
|
+
if "EO_TIDES_TIDE_MODELS" in os.environ:
|
36
|
+
directory = os.environ["EO_TIDES_TIDE_MODELS"]
|
37
|
+
else:
|
38
|
+
raise Exception(
|
39
|
+
"No tide model directory provided via `directory`, and/or no "
|
40
|
+
"`EO_TIDES_TIDE_MODELS` environment variable found. "
|
41
|
+
"Please provide a valid path to your tide model directory."
|
42
|
+
)
|
43
|
+
|
44
|
+
# Verify path exists
|
45
|
+
directory = pathlib.Path(directory).expanduser()
|
46
|
+
if not directory.exists():
|
47
|
+
raise FileNotFoundError(f"No valid tide model directory found at path `{directory}`")
|
48
|
+
else:
|
49
|
+
return directory
|
50
|
+
|
51
|
+
|
52
|
+
def _standardise_time(
|
53
|
+
time: DatetimeLike | None,
|
54
|
+
) -> np.ndarray | None:
|
55
|
+
"""
|
56
|
+
Accept any time format accepted by `pd.to_datetime`,
|
57
|
+
and return a datetime64 ndarray. Return None if None
|
58
|
+
passed.
|
59
|
+
"""
|
60
|
+
# Return time as-is if None
|
61
|
+
if time is None:
|
62
|
+
return None
|
63
|
+
|
64
|
+
# Use pd.to_datetime for conversion, then convert to numpy array
|
65
|
+
time = pd.to_datetime(time).to_numpy().astype("datetime64[ns]")
|
66
|
+
|
67
|
+
# Ensure that data has at least one dimension
|
68
|
+
return np.atleast_1d(time)
|
69
|
+
|
70
|
+
|
71
|
+
def _standardise_models(
|
72
|
+
model: str | list[str],
|
73
|
+
directory: str | os.PathLike,
|
74
|
+
ensemble_models: list[str] | None = None,
|
75
|
+
) -> tuple[list[str], list[str], list[str] | None]:
|
76
|
+
"""
|
77
|
+
Take an input model name or list of names, and return a list
|
78
|
+
of models to process, requested models, and ensemble models,
|
79
|
+
as required by the `model_tides` function.
|
80
|
+
|
81
|
+
Handles two special values passed to `model`: "all", which
|
82
|
+
will model tides for all models available in `directory`, and
|
83
|
+
"ensemble", which will model tides for all models in a list
|
84
|
+
of custom ensemble models.
|
85
|
+
"""
|
86
|
+
|
87
|
+
# Turn inputs into arrays for consistent handling
|
88
|
+
models_requested = list(np.atleast_1d(model))
|
89
|
+
|
90
|
+
# Get full list of supported models from pyTMD database
|
91
|
+
available_models, valid_models = list_models(
|
92
|
+
directory, show_available=False, show_supported=False, raise_error=True
|
93
|
+
)
|
94
|
+
custom_options = ["ensemble", "all"]
|
95
|
+
|
96
|
+
# Error if any models are not supported
|
97
|
+
if not all(m in valid_models + custom_options for m in models_requested):
|
98
|
+
error_text = (
|
99
|
+
f"One or more of the requested models are not valid:\n"
|
100
|
+
f"{models_requested}\n\n"
|
101
|
+
"The following models are supported:\n"
|
102
|
+
f"{valid_models}"
|
103
|
+
)
|
104
|
+
raise ValueError(error_text)
|
105
|
+
|
106
|
+
# Error if any models are not available in `directory`
|
107
|
+
if not all(m in available_models + custom_options for m in models_requested):
|
108
|
+
error_text = (
|
109
|
+
f"One or more of the requested models are valid, but not available in `{directory}`:\n"
|
110
|
+
f"{models_requested}\n\n"
|
111
|
+
f"The following models are available in `{directory}`:\n"
|
112
|
+
f"{available_models}"
|
113
|
+
)
|
114
|
+
raise ValueError(error_text)
|
115
|
+
|
116
|
+
# If "all" models are requested, update requested list to include available models
|
117
|
+
if "all" in models_requested:
|
118
|
+
models_requested = available_models + [m for m in models_requested if m != "all"]
|
119
|
+
|
120
|
+
# If "ensemble" modeling is requested, use custom list of ensemble models
|
121
|
+
if "ensemble" in models_requested:
|
122
|
+
print("Running ensemble tide modelling")
|
123
|
+
ensemble_models = (
|
124
|
+
ensemble_models
|
125
|
+
if ensemble_models is not None
|
126
|
+
else [
|
127
|
+
"FES2014",
|
128
|
+
"TPXO9-atlas-v5",
|
129
|
+
"EOT20",
|
130
|
+
"HAMTIDE11",
|
131
|
+
"GOT4.10",
|
132
|
+
"FES2012",
|
133
|
+
"TPXO8-atlas-v1",
|
134
|
+
]
|
135
|
+
)
|
136
|
+
|
137
|
+
# Error if any ensemble models are not available in `directory`
|
138
|
+
if not all(m in available_models for m in ensemble_models):
|
139
|
+
error_text = (
|
140
|
+
f"One or more of the requested ensemble models are not available in `{directory}`:\n"
|
141
|
+
f"{ensemble_models}\n\n"
|
142
|
+
f"The following models are available in `{directory}`:\n"
|
143
|
+
f"{available_models}"
|
144
|
+
)
|
145
|
+
raise ValueError(error_text)
|
146
|
+
|
147
|
+
# Return set of all ensemble plus any other requested models
|
148
|
+
models_to_process = sorted(list(set(ensemble_models + [m for m in models_requested if m != "ensemble"])))
|
149
|
+
|
150
|
+
# Otherwise, models to process are the same as those requested
|
151
|
+
else:
|
152
|
+
models_to_process = models_requested
|
153
|
+
|
154
|
+
return models_to_process, models_requested, ensemble_models
|
155
|
+
|
156
|
+
|
157
|
+
def _clip_model_file(
|
158
|
+
nc: xr.Dataset,
|
159
|
+
bbox: BoundingBox,
|
160
|
+
ydim: str,
|
161
|
+
xdim: str,
|
162
|
+
ycoord: str,
|
163
|
+
xcoord: str,
|
164
|
+
) -> xr.Dataset:
|
165
|
+
"""
|
166
|
+
Clips tide model netCDF datasets to a bounding box.
|
167
|
+
|
168
|
+
If the bounding box crosses 0 degrees longitude (e.g. Greenwich),
|
169
|
+
the function will clip the dataset into two parts and concatenate
|
170
|
+
them along the x-dimension to create a continuous result.
|
171
|
+
|
172
|
+
Parameters
|
173
|
+
----------
|
174
|
+
nc : xr.Dataset
|
175
|
+
Input tide model xarray dataset.
|
176
|
+
bbox : odc.geo.geom.BoundingBox
|
177
|
+
A BoundingBox object for clipping the dataset in EPSG:4326
|
178
|
+
degrees coordinates. For example:
|
179
|
+
`BoundingBox(left=108, bottom=-48, right=158, top=-6, crs='EPSG:4326')`
|
180
|
+
ydim : str
|
181
|
+
The name of the xarray dimension representing the y-axis.
|
182
|
+
Depending on the tide model, this may or may not contain
|
183
|
+
actual latitude values.
|
184
|
+
xdim : str
|
185
|
+
The name of the xarray dimension representing the x-axis.
|
186
|
+
Depending on the tide model, this may or may not contain
|
187
|
+
actual longitude values.
|
188
|
+
ycoord : str
|
189
|
+
The name of the coordinate, variable or dimension containing
|
190
|
+
actual latitude values used for clipping the data.
|
191
|
+
xcoord : str
|
192
|
+
The name of the coordinate, variable or dimension containing
|
193
|
+
actual longitude values used for clipping the data.
|
194
|
+
|
195
|
+
Returns
|
196
|
+
-------
|
197
|
+
xr.Dataset
|
198
|
+
A dataset clipped to the specified bounding box, with
|
199
|
+
appropriate adjustments if the bounding box crosses 0
|
200
|
+
degrees longitude.
|
201
|
+
|
202
|
+
Examples
|
203
|
+
--------
|
204
|
+
>>> nc = xr.open_dataset("GOT5.5/ocean_tides/2n2.nc")
|
205
|
+
>>> bbox = BoundingBox(left=108, bottom=-48, right=158, top=-6, crs='EPSG:4326')
|
206
|
+
>>> clipped_nc = _clip_model_file(nc, bbox, xdim="lon", ydim="lat", ycoord="latitude", xcoord="longitude")
|
207
|
+
"""
|
208
|
+
|
209
|
+
# Extract x and y coords from xarray and load into memory
|
210
|
+
xcoords = nc[xcoord].compute()
|
211
|
+
ycoords = nc[ycoord].compute()
|
212
|
+
|
213
|
+
# If data falls within 0-360 degree bounds, then clip directly
|
214
|
+
if (bbox.left >= 0) & (bbox.right <= 360):
|
215
|
+
nc_clipped = nc.sel({
|
216
|
+
ydim: (ycoords >= bbox.bottom) & (ycoords <= bbox.top),
|
217
|
+
xdim: (xcoords >= bbox.left) & (xcoords <= bbox.right),
|
218
|
+
})
|
219
|
+
|
220
|
+
# If bbox crosses zero longitude, extract left and right
|
221
|
+
# separately and then combine into one concatenated dataset
|
222
|
+
elif (bbox.left < 0) & (bbox.right > 0):
|
223
|
+
# Convert longitudes to 0-360 range
|
224
|
+
left = bbox.left % 360
|
225
|
+
right = bbox.right % 360
|
226
|
+
|
227
|
+
# Extract data from left of 0 longitude, and convert lon
|
228
|
+
# coords to -180 to 0 range to enable continuous interpolation
|
229
|
+
# across 0 boundary
|
230
|
+
nc_left = nc.sel({
|
231
|
+
ydim: (ycoords >= bbox.bottom) & (ycoords <= bbox.top),
|
232
|
+
xdim: (xcoords >= left) & (xcoords <= 360),
|
233
|
+
}).assign({xcoord: lambda x: x[xcoord] - 360})
|
234
|
+
|
235
|
+
# Convert additional lon variables for TXPO
|
236
|
+
if "lon_v" in nc_left:
|
237
|
+
nc_left = nc_left.assign({
|
238
|
+
"lon_v": lambda x: x["lon_v"] - 360,
|
239
|
+
"lon_u": lambda x: x["lon_u"] - 360,
|
240
|
+
})
|
241
|
+
|
242
|
+
# Extract data to right of 0 longitude
|
243
|
+
nc_right = nc.sel({
|
244
|
+
ydim: (ycoords >= bbox.bottom) & (ycoords <= bbox.top),
|
245
|
+
xdim: (xcoords > 0) & (xcoords <= right),
|
246
|
+
})
|
247
|
+
|
248
|
+
# Combine left and right data along x dimension
|
249
|
+
nc_clipped = xr.concat([nc_left, nc_right], dim=xdim)
|
250
|
+
|
251
|
+
# Hack fix to remove expanded x dim on lat variables issue
|
252
|
+
# for TPXO data; remove x dim by selecting the first obs
|
253
|
+
for i in ["lat_z", "lat_v", "lat_u", "con"]:
|
254
|
+
try:
|
255
|
+
nc_clipped[i] = nc_clipped[i].isel(nx=0)
|
256
|
+
except:
|
257
|
+
pass
|
258
|
+
|
259
|
+
return nc_clipped
|
260
|
+
|
261
|
+
|
262
|
+
def clip_models(
|
263
|
+
input_directory: str | os.PathLike,
|
264
|
+
output_directory: str | os.PathLike,
|
265
|
+
bbox: tuple[float, float, float, float],
|
266
|
+
model: list | None = None,
|
267
|
+
buffer: float = 1,
|
268
|
+
overwrite: bool = False,
|
269
|
+
):
|
270
|
+
"""
|
271
|
+
Clip NetCDF-format ocean tide models to a bounding box.
|
272
|
+
|
273
|
+
This function identifies all NetCDF-format tide models in a
|
274
|
+
given input directory, including "ATLAS-netcdf" (e.g. TPXO9-atlas-nc),
|
275
|
+
"FES-netcdf" (e.g. FES2022, EOT20), and "GOT-netcdf" (e.g. GOT5.5)
|
276
|
+
format files. Files for each model are then clipped to the extent of
|
277
|
+
the provided bounding box, handling model-specific file structures.
|
278
|
+
After each model is clipped, the result is exported to the output
|
279
|
+
directory and verified with `pyTMD` to ensure the clipped data is
|
280
|
+
suitable for tide modelling.
|
281
|
+
|
282
|
+
For instructions on accessing and downloading tide models, see:
|
283
|
+
<https://geoscienceaustralia.github.io/eo-tides/setup/>
|
284
|
+
|
285
|
+
Parameters
|
286
|
+
----------
|
287
|
+
input_directory : str or os.PathLike
|
288
|
+
Path to directory containing input NetCDF-format tide model files.
|
289
|
+
output_directory : str or os.PathLike
|
290
|
+
Path to directory where clipped NetCDF files will be exported.
|
291
|
+
bbox : tuple of float
|
292
|
+
Bounding box for clipping the tide models in EPSG:4326 degrees
|
293
|
+
coordinates, specified as `(left, bottom, right, top)`.
|
294
|
+
model : str or list of str, optional
|
295
|
+
The tide model (or models) to clip. Defaults to None, which
|
296
|
+
will automatically identify and clip all NetCDF-format models
|
297
|
+
in the input directly.
|
298
|
+
buffer : float, optional
|
299
|
+
Buffer distance (in degrees) added to the bounding box to provide
|
300
|
+
sufficient data on edges of study area. Defaults to 1 degree.
|
301
|
+
overwrite : bool, optional
|
302
|
+
If True, overwrite existing files in the output directory.
|
303
|
+
Defaults to False.
|
304
|
+
|
305
|
+
Examples
|
306
|
+
--------
|
307
|
+
>>> clip_models(
|
308
|
+
... input_directory="tide_models/",
|
309
|
+
... output_directory="tide_models_clipped/",
|
310
|
+
... bbox=(-8.968392, 50.070574, 2.447160, 59.367122),
|
311
|
+
... )
|
312
|
+
"""
|
313
|
+
|
314
|
+
# Get input and output paths
|
315
|
+
input_directory = _set_directory(input_directory)
|
316
|
+
output_directory = pathlib.Path(output_directory)
|
317
|
+
|
318
|
+
# Prepare bounding box
|
319
|
+
bbox = odc.geo.geom.BoundingBox(*bbox, crs="EPSG:4326").buffered(buffer)
|
320
|
+
|
321
|
+
# Identify NetCDF models
|
322
|
+
model_database = load_database()["elevation"]
|
323
|
+
netcdf_formats = ["ATLAS-netcdf", "FES-netcdf", "GOT-netcdf"]
|
324
|
+
netcdf_models = {k for k, v in model_database.items() if v["format"] in netcdf_formats}
|
325
|
+
|
326
|
+
# Identify subset of available and requested NetCDF models
|
327
|
+
available_models, _ = list_models(directory=input_directory, show_available=False, show_supported=False)
|
328
|
+
requested_models = list(np.atleast_1d(model)) if model is not None else available_models
|
329
|
+
available_netcdf_models = list(set(available_models) & set(requested_models) & set(netcdf_models))
|
330
|
+
|
331
|
+
# Raise error if no valid models found
|
332
|
+
if len(available_netcdf_models) == 0:
|
333
|
+
raise ValueError(f"No valid NetCDF models found in {input_directory}.")
|
334
|
+
|
335
|
+
# If model list is provided,
|
336
|
+
print(f"Preparing to clip suitable NetCDF models: {available_netcdf_models}\n")
|
337
|
+
|
338
|
+
# Loop through suitable models and export
|
339
|
+
for m in available_netcdf_models:
|
340
|
+
# Get model file and grid file list if they exist
|
341
|
+
model_files = model_database[m].get("model_file", [])
|
342
|
+
grid_file = model_database[m].get("grid_file", [])
|
343
|
+
|
344
|
+
# Convert to list if strings and combine
|
345
|
+
model_files = model_files if isinstance(model_files, list) else [model_files]
|
346
|
+
grid_file = grid_file if isinstance(grid_file, list) else [grid_file]
|
347
|
+
all_files = model_files + grid_file
|
348
|
+
|
349
|
+
# Loop through each model file and clip
|
350
|
+
for file in tqdm(all_files, desc=f"Clipping {m}"):
|
351
|
+
# Skip if it exists in output directory
|
352
|
+
if (output_directory / file).exists() and not overwrite:
|
353
|
+
continue
|
354
|
+
|
355
|
+
# Load model file
|
356
|
+
nc = xr.open_mfdataset(input_directory / file)
|
357
|
+
|
358
|
+
# Open file and clip according to model
|
359
|
+
if m in (
|
360
|
+
"GOT5.5",
|
361
|
+
"GOT5.5_load",
|
362
|
+
"GOT5.5_extrapolated",
|
363
|
+
"GOT5.5D",
|
364
|
+
"GOT5.5D_extrapolated",
|
365
|
+
"GOT5.6",
|
366
|
+
"GOT5.6_extrapolated",
|
367
|
+
):
|
368
|
+
nc_clipped = _clip_model_file(
|
369
|
+
nc,
|
370
|
+
bbox,
|
371
|
+
xdim="lon",
|
372
|
+
ydim="lat",
|
373
|
+
ycoord="latitude",
|
374
|
+
xcoord="longitude",
|
375
|
+
)
|
376
|
+
|
377
|
+
elif m in ("HAMTIDE11",):
|
378
|
+
nc_clipped = _clip_model_file(nc, bbox, xdim="LON", ydim="LAT", ycoord="LAT", xcoord="LON")
|
379
|
+
|
380
|
+
elif m in (
|
381
|
+
"EOT20",
|
382
|
+
"EOT20_load",
|
383
|
+
"FES2012",
|
384
|
+
"FES2014",
|
385
|
+
"FES2014_extrapolated",
|
386
|
+
"FES2014_load",
|
387
|
+
"FES2022",
|
388
|
+
"FES2022_extrapolated",
|
389
|
+
"FES2022_load",
|
390
|
+
):
|
391
|
+
nc_clipped = _clip_model_file(nc, bbox, xdim="lon", ydim="lat", ycoord="lat", xcoord="lon")
|
392
|
+
|
393
|
+
elif m in (
|
394
|
+
"TPXO8-atlas-nc",
|
395
|
+
"TPXO9-atlas-nc",
|
396
|
+
"TPXO9-atlas-v2-nc",
|
397
|
+
"TPXO9-atlas-v3-nc",
|
398
|
+
"TPXO9-atlas-v4-nc",
|
399
|
+
"TPXO9-atlas-v5-nc",
|
400
|
+
"TPXO10-atlas-v2-nc",
|
401
|
+
):
|
402
|
+
nc_clipped = _clip_model_file(
|
403
|
+
nc,
|
404
|
+
bbox,
|
405
|
+
xdim="nx",
|
406
|
+
ydim="ny",
|
407
|
+
ycoord="lat_z",
|
408
|
+
xcoord="lon_z",
|
409
|
+
)
|
410
|
+
|
411
|
+
else:
|
412
|
+
raise Exception(f"Model {m} not supported")
|
413
|
+
|
414
|
+
# Create directory and export
|
415
|
+
(output_directory / file).parent.mkdir(parents=True, exist_ok=True)
|
416
|
+
nc_clipped.to_netcdf(output_directory / file, mode="w")
|
417
|
+
|
418
|
+
# Verify that models are ready
|
419
|
+
pytmd_model(directory=output_directory).elevation(m=m).verify
|
420
|
+
print(" ✅ Clipped model exported and verified")
|
421
|
+
|
422
|
+
print(f"\nOutputs exported to {output_directory}")
|
423
|
+
list_models(directory=output_directory, show_available=True, show_supported=False)
|
424
|
+
|
425
|
+
|
426
|
+
def list_models(
|
427
|
+
directory: str | os.PathLike | None = None,
|
428
|
+
show_available: bool = True,
|
429
|
+
show_supported: bool = True,
|
430
|
+
raise_error: bool = False,
|
431
|
+
) -> tuple[list[str], list[str]]:
|
432
|
+
"""
|
433
|
+
List all tide models available for tide modelling.
|
434
|
+
|
435
|
+
This function scans the specified tide model directory
|
436
|
+
and returns a list of models that are available in the
|
437
|
+
directory as well as the full list of all models supported
|
438
|
+
by `eo-tides` and `pyTMD`.
|
439
|
+
|
440
|
+
For instructions on setting up tide models, see:
|
441
|
+
<https://geoscienceaustralia.github.io/eo-tides/setup/>
|
442
|
+
|
443
|
+
Parameters
|
444
|
+
----------
|
445
|
+
directory : str, optional
|
446
|
+
The directory containing tide model data files. If no path is
|
447
|
+
provided, this will default to the environment variable
|
448
|
+
`EO_TIDES_TIDE_MODELS` if set, or raise an error if not.
|
449
|
+
Tide modelling files should be stored in sub-folders for each
|
450
|
+
model that match the structure required by `pyTMD`
|
451
|
+
(<https://geoscienceaustralia.github.io/eo-tides/setup/>).
|
452
|
+
show_available : bool, optional
|
453
|
+
Whether to print a list of locally available models.
|
454
|
+
show_supported : bool, optional
|
455
|
+
Whether to print a list of all supported models, in
|
456
|
+
addition to models available locally.
|
457
|
+
raise_error : bool, optional
|
458
|
+
If True, raise an error if no available models are found.
|
459
|
+
If False, raise a warning.
|
460
|
+
|
461
|
+
Returns
|
462
|
+
-------
|
463
|
+
available_models : list of str
|
464
|
+
A list of all tide models available within `directory`.
|
465
|
+
supported_models : list of str
|
466
|
+
A list of all tide models supported by `eo-tides`.
|
467
|
+
"""
|
468
|
+
init() # Initialize colorama
|
469
|
+
|
470
|
+
# Set tide modelling files directory. If no custom path is
|
471
|
+
# provided, try global environment variable.
|
472
|
+
directory = _set_directory(directory)
|
473
|
+
|
474
|
+
# Get full list of supported models from pyTMD database
|
475
|
+
model_database = load_database()["elevation"]
|
476
|
+
supported_models = list(model_database.keys())
|
477
|
+
|
478
|
+
# Extract expected model paths
|
479
|
+
expected_paths = {}
|
480
|
+
for m in supported_models:
|
481
|
+
model_file = model_database[m]["model_file"]
|
482
|
+
|
483
|
+
# Handle GOT5.6 differently to ensure we test for presence of GOT5.6 constituents
|
484
|
+
if m in ("GOT5.6", "GOT5.6_extrapolated"):
|
485
|
+
model_file = [file for file in model_file if "GOT5.6" in file][0]
|
486
|
+
else:
|
487
|
+
model_file = model_file[0] if isinstance(model_file, list) else model_file
|
488
|
+
|
489
|
+
# Add path to dict
|
490
|
+
expected_paths[m] = str(directory / pathlib.Path(model_file).expanduser().parent)
|
491
|
+
|
492
|
+
# Define column widths
|
493
|
+
status_width = 4 # Width for emoji
|
494
|
+
name_width = max(len(name) for name in supported_models)
|
495
|
+
path_width = max(len(path) for path in expected_paths.values())
|
496
|
+
|
497
|
+
# Print list of supported models, marking available and
|
498
|
+
# unavailable models and appending available to list
|
499
|
+
if show_available or show_supported:
|
500
|
+
total_width = min(status_width + name_width + path_width + 6, 80)
|
501
|
+
print("─" * total_width)
|
502
|
+
print(f"{'🌊':^{status_width}} | {'Model':<{name_width}} | {'Expected path':<{path_width}}")
|
503
|
+
print("─" * total_width)
|
504
|
+
|
505
|
+
available_models = []
|
506
|
+
for m in supported_models:
|
507
|
+
try:
|
508
|
+
model_file = pytmd_model(directory=directory).elevation(m=m)
|
509
|
+
available_models.append(m)
|
510
|
+
|
511
|
+
if show_available:
|
512
|
+
# Mark available models with a green tick
|
513
|
+
status = "✅"
|
514
|
+
print(f"{status:^{status_width}}│ {m:<{name_width}} │ {expected_paths[m]:<{path_width}}")
|
515
|
+
except FileNotFoundError:
|
516
|
+
if show_supported:
|
517
|
+
# Mark unavailable models with a red cross
|
518
|
+
status = "❌"
|
519
|
+
print(
|
520
|
+
f"{status:^{status_width}}│ {Style.DIM}{m:<{name_width}} │ {expected_paths[m]:<{path_width}}{Style.RESET_ALL}"
|
521
|
+
)
|
522
|
+
|
523
|
+
if show_available or show_supported:
|
524
|
+
print("─" * total_width)
|
525
|
+
|
526
|
+
# Print summary
|
527
|
+
print(f"\n{Style.BRIGHT}Summary:{Style.RESET_ALL}")
|
528
|
+
print(f"Available models: {len(available_models)}/{len(supported_models)}")
|
529
|
+
|
530
|
+
# Raise error or warning if no models are available
|
531
|
+
if not available_models:
|
532
|
+
warning_msg = textwrap.dedent(
|
533
|
+
f"""
|
534
|
+
No valid tide models are available in `{directory}`.
|
535
|
+
Are you sure you have provided the correct `directory` path, or set the
|
536
|
+
`EO_TIDES_TIDE_MODELS` environment variable to point to the location of your
|
537
|
+
tide model directory?
|
538
|
+
"""
|
539
|
+
).strip()
|
540
|
+
|
541
|
+
if raise_error:
|
542
|
+
raise Exception(warning_msg)
|
543
|
+
else:
|
544
|
+
warnings.warn(warning_msg, UserWarning)
|
545
|
+
|
546
|
+
# Return list of available and supported models
|
547
|
+
return available_models, supported_models
|
3
548
|
|
4
549
|
|
5
550
|
def idw(
|
@@ -22,7 +567,7 @@ def idw(
|
|
22
567
|
inverse distance to each neighbor, with weights descreasing with
|
23
568
|
increasing distance.
|
24
569
|
|
25
|
-
Code inspired by: https://github.com/DahnJ/REM-xarray
|
570
|
+
Code inspired by: <https://github.com/DahnJ/REM-xarray>
|
26
571
|
|
27
572
|
Parameters
|
28
573
|
----------
|
eo_tides/validation.py
CHANGED
@@ -1,5 +1,4 @@
|
|
1
1
|
import datetime
|
2
|
-
import glob
|
3
2
|
import warnings
|
4
3
|
from math import sqrt
|
5
4
|
from numbers import Number
|
@@ -193,11 +192,12 @@ def load_gauge_gesla(
|
|
193
192
|
metadata_path="/gdata1/data/sea_level/GESLA3_ALL 2.csv",
|
194
193
|
):
|
195
194
|
"""
|
196
|
-
Load
|
197
|
-
(GESLA) tide gauge data with an `x, y, time` spatiotemporal query,
|
198
|
-
or from a list of specific tide gauges.
|
195
|
+
Load Global Extreme Sea Level Analysis (GESLA) tide gauge data.
|
199
196
|
|
200
|
-
|
197
|
+
Load and process all available GESLA measured sea-level data
|
198
|
+
with an `x, y, time` spatio-temporal query, or from a list of
|
199
|
+
specific tide gauges. Can optionally filter by gauge quality
|
200
|
+
and append detailed gauge metadata.
|
201
201
|
|
202
202
|
Modified from original code in <https://github.com/philiprt/GeslaDataset>.
|
203
203
|
|
@@ -1,8 +1,9 @@
|
|
1
1
|
Metadata-Version: 2.1
|
2
2
|
Name: eo-tides
|
3
|
-
Version: 0.
|
3
|
+
Version: 0.3.1
|
4
4
|
Summary: Tide modelling tools for large-scale satellite earth observation analysis
|
5
|
-
Author
|
5
|
+
Author: Robbi Bishop-Taylor, Stephen Sagar, Claire Phillips, Vanessa Newey
|
6
|
+
Author-email: Robbi.BishopTaylor@ga.gov.au
|
6
7
|
Project-URL: Homepage, https://GeoscienceAustralia.github.io/eo-tides/
|
7
8
|
Project-URL: Repository, https://github.com/GeoscienceAustralia/eo-tides
|
8
9
|
Project-URL: Documentation, https://GeoscienceAustralia.github.io/eo-tides/
|
@@ -19,6 +20,7 @@ Classifier: Programming Language :: Python :: 3.9
|
|
19
20
|
Classifier: Programming Language :: Python :: 3.10
|
20
21
|
Classifier: Programming Language :: Python :: 3.11
|
21
22
|
Classifier: Programming Language :: Python :: 3.12
|
23
|
+
Classifier: Programming Language :: Python :: 3.13
|
22
24
|
Requires-Python: <4.0,>=3.9
|
23
25
|
Description-Content-Type: text/markdown
|
24
26
|
License-File: LICENSE
|
@@ -28,8 +30,9 @@ Requires-Dist: matplotlib >=3.8.0
|
|
28
30
|
Requires-Dist: numpy >=1.26.0
|
29
31
|
Requires-Dist: odc-geo >=0.4.7
|
30
32
|
Requires-Dist: pandas >=2.2.0
|
33
|
+
Requires-Dist: psutil >=5.8.0
|
31
34
|
Requires-Dist: pyproj >=3.6.1
|
32
|
-
Requires-Dist: pyTMD ==2.1.
|
35
|
+
Requires-Dist: pyTMD ==2.1.8
|
33
36
|
Requires-Dist: scikit-learn >=1.4.0
|
34
37
|
Requires-Dist: scipy >=1.11.2
|
35
38
|
Requires-Dist: shapely >=2.0.6
|
@@ -44,16 +47,17 @@ Requires-Dist: planetary-computer >=1.0.0 ; extra == 'notebooks'
|
|
44
47
|
|
45
48
|
# `eo-tides`: Tide modelling tools for large-scale satellite earth observation analysis
|
46
49
|
|
47
|
-
<img align="right" width="200" src="docs/assets/eo-tides-logo.gif" alt="eo-tides logo" style="margin-right: 40px;">
|
50
|
+
<img align="right" width="200" src="https://github.com/GeoscienceAustralia/eo-tides/blob/main/docs/assets/eo-tides-logo.gif?raw=true" alt="eo-tides logo" style="margin-right: 40px;">
|
48
51
|
|
49
52
|
[](https://pypi.org/project/eo-tides/)
|
50
53
|
[](https://github.com/GeoscienceAustralia/eo-tides/actions/workflows/main.yml?query=branch%3Amain)
|
51
|
-

|
54
|
+
[](https://github.com/GeoscienceAustralia/eo-tides/blob/main/pyproject.toml)
|
52
55
|
[](https://codecov.io/gh/GeoscienceAustralia/eo-tides)
|
53
56
|
[](https://img.shields.io/github/license/GeoscienceAustralia/eo-tides)
|
54
57
|
|
55
|
-
- **Github repository**: <https://github.com/GeoscienceAustralia/eo-tides/>
|
56
|
-
- **Documentation
|
58
|
+
- ⚙️ **Github repository**: <https://github.com/GeoscienceAustralia/eo-tides/>
|
59
|
+
- 📘 **Documentation**: <https://GeoscienceAustralia.github.io/eo-tides/>
|
60
|
+
- 🐍 **PyPI**: <https://pypi.org/project/eo-tides/>
|
57
61
|
|
58
62
|
> [!CAUTION]
|
59
63
|
> This package is a work in progress, and not currently ready for operational use.
|
@@ -64,12 +68,12 @@ Requires-Dist: planetary-computer >=1.0.0 ; extra == 'notebooks'
|
|
64
68
|
|
65
69
|
These tools can be applied to petabytes of freely available satellite data (e.g. from [Digital Earth Australia](https://knowledge.dea.ga.gov.au/) or [Microsoft Planetary Computer](https://planetarycomputer.microsoft.com/)) loaded via Open Data Cube's [`odc-stac`](https://odc-stac.readthedocs.io/en/latest/) or [`datacube`](https://opendatacube.readthedocs.io/en/latest/) packages, supporting coastal and ocean earth observation analysis for any time period or location globally.
|
66
70
|
|
67
|
-

|
71
|
+

|
68
72
|
|
69
73
|
## Highlights
|
70
74
|
|
71
75
|
- 🌊 Model tide heights and phases (e.g. high, low, ebb, flow) from multiple global ocean tide models in parallel, and return a `pandas.DataFrame` for further analysis
|
72
|
-
- 🛰️ "Tag" satellite data with tide
|
76
|
+
- 🛰️ "Tag" satellite data with tide heights based on the exact moment of image acquisition
|
73
77
|
- 🌐 Model tides for every individual satellite pixel through time, producing three-dimensional "tide height" `xarray`-format datacubes that can be integrated with satellite data
|
74
78
|
- 📈 Calculate statistics describing local tide dynamics, as well as biases caused by interactions between tidal processes and satellite orbits
|
75
79
|
- 🛠️ Validate modelled tides using measured sea levels from coastal tide gauges (e.g. [GESLA Global Extreme Sea Level Analysis](https://gesla.org/))
|
@@ -0,0 +1,11 @@
|
|
1
|
+
eo_tides/__init__.py,sha256=rn6slQP0bAhqM9cL2W8omiEM8f0b7libt6WsQ5DvYTE,1655
|
2
|
+
eo_tides/eo.py,sha256=Vc_AHqT0_IDqdwbOpNcONjHhiCtbCe8Osk2gvzUeNmU,22377
|
3
|
+
eo_tides/model.py,sha256=abfwswxEgSCBveK0-kTN5L-jw-AceXz3-NXSCbYnalk,32747
|
4
|
+
eo_tides/stats.py,sha256=lchWWJ5gBDuZWvaD8TF-12Xlo2qCWiNI2IcgAaKWy2U,22668
|
5
|
+
eo_tides/utils.py,sha256=i7JoNAH41hWZ0-lIOa87i-zCPndzJl3ylXecMb1jHoQ,26056
|
6
|
+
eo_tides/validation.py,sha256=UREsc0yWRO4x0PJXvyoIx8gYiBZiRSim4z6TmAz_VDM,11857
|
7
|
+
eo_tides-0.3.1.dist-info/LICENSE,sha256=owxWsXViCL2J6Ks3XYhot7t4Y93nstmXAT95Zf030Cc,11350
|
8
|
+
eo_tides-0.3.1.dist-info/METADATA,sha256=06d3l-KrXyKSvqGD-ku2CKORtWA1AHgA841i-iDw7_Q,8039
|
9
|
+
eo_tides-0.3.1.dist-info/WHEEL,sha256=R06PA3UVYHThwHvxuRWMqaGcr-PuniXahwjmQRFMEkY,91
|
10
|
+
eo_tides-0.3.1.dist-info/top_level.txt,sha256=lXZDUUM1DlLdKWHRn8zdmtW8Rx-eQOIWVvt0b8VGiyQ,9
|
11
|
+
eo_tides-0.3.1.dist-info/RECORD,,
|
eo_tides-0.2.0.dist-info/RECORD
DELETED
@@ -1,11 +0,0 @@
|
|
1
|
-
eo_tides/__init__.py,sha256=MKL_HjRECVHHQVnMVg-TSz3J-vjxPZgbnyWu0RAXZ0U,1623
|
2
|
-
eo_tides/eo.py,sha256=98llXpF2lSDfsVQBao-dpr8Z4zH5q0C2Rfu4X-qmPiE,22400
|
3
|
-
eo_tides/model.py,sha256=kaYXU_jmv91ONam_CpoVN137aJsmqHvKHEPWe-DRJnI,38280
|
4
|
-
eo_tides/stats.py,sha256=sOwXEh8RPb8muh2o9-z1c0GDnV5FJa_TgsiGb4rMkiU,22689
|
5
|
-
eo_tides/utils.py,sha256=l9VXJawQzaRBYaFMsP8VBeaN5VA3rFDdzcvF7Rk04Vc,5620
|
6
|
-
eo_tides/validation.py,sha256=JjTUqDfbR189m_6W1bpaSolQIHNTLicTHN7z9O_nr3s,11828
|
7
|
-
eo_tides-0.2.0.dist-info/LICENSE,sha256=owxWsXViCL2J6Ks3XYhot7t4Y93nstmXAT95Zf030Cc,11350
|
8
|
-
eo_tides-0.2.0.dist-info/METADATA,sha256=msiUYdlCm5pTix7LXA7RzM8Hg-9r4JHpWTSUyxdPpg4,7637
|
9
|
-
eo_tides-0.2.0.dist-info/WHEEL,sha256=P9jw-gEje8ByB7_hXoICnHtVCrEwMQh-630tKvQWehc,91
|
10
|
-
eo_tides-0.2.0.dist-info/top_level.txt,sha256=lXZDUUM1DlLdKWHRn8zdmtW8Rx-eQOIWVvt0b8VGiyQ,9
|
11
|
-
eo_tides-0.2.0.dist-info/RECORD,,
|
File without changes
|