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/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 and process all available Global Extreme Sea Level Analysis
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
- Can optionally filter by gauge quality and append detailed gauge metadata.
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.2.0
3
+ Version: 0.3.1
4
4
  Summary: Tide modelling tools for large-scale satellite earth observation analysis
5
- Author-email: Robbi Bishop-Taylor <Robbi.BishopTaylor@ga.gov.au>
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.7
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
  [![Release](https://img.shields.io/github/v/release/GeoscienceAustralia/eo-tides)](https://pypi.org/project/eo-tides/)
50
53
  [![Build status](https://img.shields.io/github/actions/workflow/status/GeoscienceAustralia/eo-tides/main.yml?branch=main)](https://github.com/GeoscienceAustralia/eo-tides/actions/workflows/main.yml?query=branch%3Amain)
51
- ![Python Version from PEP 621 TOML](https://img.shields.io/pypi/pyversions/eo-tides)
54
+ [![Python Version from PEP 621 TOML](https://img.shields.io/pypi/pyversions/eo-tides)](https://github.com/GeoscienceAustralia/eo-tides/blob/main/pyproject.toml)
52
55
  [![codecov](https://codecov.io/gh/GeoscienceAustralia/eo-tides/branch/main/graph/badge.svg)](https://codecov.io/gh/GeoscienceAustralia/eo-tides)
53
56
  [![License](https://img.shields.io/github/license/GeoscienceAustralia/eo-tides)](https://img.shields.io/github/license/GeoscienceAustralia/eo-tides)
54
57
 
55
- - **Github repository**: <https://github.com/GeoscienceAustralia/eo-tides/>
56
- - **Documentation** <https://GeoscienceAustralia.github.io/eo-tides/>
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
- ![eo-tides abstract showing satellite data, tide data array and tide animation](docs/assets/eo-tides-abstract.gif)
71
+ ![eo-tides abstract showing satellite data, tide data array and tide animation](https://github.com/GeoscienceAustralia/eo-tides/blob/main/docs/assets/eo-tides-abstract.gif?raw=true)
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 height and stage based on the exact moment of image acquisition
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,,
@@ -1,5 +1,5 @@
1
1
  Wheel-Version: 1.0
2
- Generator: setuptools (75.3.0)
2
+ Generator: setuptools (75.5.0)
3
3
  Root-Is-Purelib: true
4
4
  Tag: py3-none-any
5
5
 
@@ -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,,