eo-tides 0.0.19__tar.gz → 0.0.20__tar.gz

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.
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.1
2
2
  Name: eo-tides
3
- Version: 0.0.19
3
+ Version: 0.0.20
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/
@@ -1,5 +1,6 @@
1
1
  import os
2
2
  import pathlib
3
+ import warnings
3
4
  from concurrent.futures import ProcessPoolExecutor
4
5
  from functools import partial
5
6
 
@@ -15,65 +16,103 @@ from tqdm import tqdm
15
16
  from eo_tides.utils import idw
16
17
 
17
18
 
18
- def available_models(directory=None, show_supported=True):
19
+ def _set_directory(directory):
19
20
  """
20
- Prints a list of all tide models available for tide
21
- modelling using `eo-tides`.
21
+ Set tide modelling files directory. If no custom
22
+ path is provided, try global environmental variable
23
+ instead.
24
+ """
25
+ if directory is None:
26
+ if "EO_TIDES_TIDE_MODELS" in os.environ:
27
+ directory = os.environ["EO_TIDES_TIDE_MODELS"]
28
+ else:
29
+ raise Exception(
30
+ "No tide model directory provided via `directory`, and/or no "
31
+ "`EO_TIDES_TIDE_MODELS` environment variable found. "
32
+ "Please provide a valid path to your tide model directory."
33
+ )
34
+
35
+ # Verify path exists
36
+ directory = pathlib.Path(directory).expanduser()
37
+ if not directory.exists():
38
+ raise FileNotFoundError(f"No valid tide model directory found at path `{directory}`")
39
+ else:
40
+ return directory
41
+
42
+
43
+ def list_models(directory=None, show_available=True, show_supported=True, raise_error=False):
44
+ """
45
+ List all tide models available for tide modelling, and
46
+ all models supported by `eo-tides` and `pyTMD`.
22
47
 
23
48
  This function scans the specified tide model directory
24
- for tide models supported by the `pyTMD` package, and
25
- prints a list of models that are available in the
26
- directory as well as the full list of supported models.
49
+ and returns a list of models that are available in the
50
+ directory as well as the full list of all supported models.
27
51
 
28
52
  For instructions on setting up tide models, see:
29
53
  <https://geoscienceaustralia.github.io/eo-tides/setup/>
30
54
 
31
55
  Parameters
32
56
  ----------
33
- directory : str
34
- Path to the directory containing tide model files.
57
+ directory : string, optional
58
+ The directory containing tide model data files. If no path is
59
+ provided, this will default to the environment variable
60
+ `EO_TIDES_TIDE_MODELS` if set, or raise an error if not.
61
+ Tide modelling files should be stored in sub-folders for each
62
+ model that match the structure required by `pyTMD`
63
+ (<https://geoscienceaustralia.github.io/eo-tides/setup/>).
64
+ show_available : bool, optional
65
+ Whether to print a list of locally available models.
66
+ show_supported : bool, optional
67
+ Whether to print a list of all supported models, in
68
+ addition to models available locally.
35
69
 
36
70
  Returns
37
71
  -------
38
- available_m : list
39
- A list of all available tide models within
40
- `directory`.
72
+ available_models : list
73
+ A list of alltide models available within `directory`.
74
+ supported_models : list
75
+ A list of all tide models supported by `eo-tides`.
41
76
  """
42
- # TODO: Pull directory code into re-usable function
43
-
44
- # Set tide modelling files directory. If no custom path is provided,
45
- # first try global environmental var, then "/var/share/tide_models"
46
- if directory is None:
47
- if "EO_TIDES_TIDE_MODELS" in os.environ:
48
- directory = os.environ["EO_TIDES_TIDE_MODELS"]
49
- else:
50
- directory = "/var/share/tide_models"
77
+ # Set tide modelling files directory. If no custom path is
78
+ # provided, try global environment variable.
79
+ directory = _set_directory(directory)
51
80
 
52
- # Verify path exists
53
- directory = pathlib.Path(directory).expanduser()
54
- if not directory.exists():
55
- raise FileNotFoundError("Invalid tide directory")
56
-
57
- # Get full list of supported models from the database
58
- supported_models = load_database()["elevation"].keys()
81
+ # Get full list of supported models from pyTMD database
82
+ supported_models = list(load_database()["elevation"].keys())
59
83
 
60
84
  # Print list of supported models, marking available and
61
85
  # unavailable models and appending available to list
62
- print(f"Tide models available in `{directory}`:")
63
- available_m = []
86
+ if show_available or show_supported:
87
+ print(f"Tide models available in `{directory}`:")
88
+ available_models = []
64
89
  for m in supported_models:
65
90
  try:
66
91
  model(directory=directory).elevation(m=m)
67
- # Mark available models with a green tick
68
- print(f" {m}")
69
- available_m.append(m)
92
+ if show_available:
93
+ # Mark available models with a green tick
94
+ print(f" ✅ {m}")
95
+ available_models.append(m)
70
96
  except:
71
97
  if show_supported:
72
98
  # Mark unavailable models with a red cross
73
99
  print(f" ❌ {m}")
74
100
 
75
- # Return list of available models
76
- return available_m
101
+ # Raise error or warning if no models are available
102
+ if not available_models:
103
+ warning_text = (
104
+ f"No valid tide models are available in `{directory}`. "
105
+ "Verify that you have provided the correct `directory` path, "
106
+ "or set the `EO_TIDES_TIDE_MODELS` environment variable "
107
+ "to point to the location of your tide model directory."
108
+ )
109
+ if raise_error:
110
+ raise Exception(warning_text)
111
+ else:
112
+ warnings.warn(warning_text, UserWarning)
113
+
114
+ # Return list of available and supported models
115
+ return available_models, supported_models
77
116
 
78
117
 
79
118
  def _model_tides(
@@ -94,34 +133,7 @@ def _model_tides(
94
133
  extraction of tide modelling constituents and tide modelling using
95
134
  `pyTMD`.
96
135
  """
97
- # import pyTMD.eop
98
- # import pyTMD.io
99
- # import pyTMD.io.model
100
- # import pyTMD.predict
101
- # import pyTMD.spatial
102
- # import pyTMD.time
103
- # import pyTMD.utilities
104
-
105
- # Get parameters for tide model; use custom definition file for
106
- # FES2012 (leave this as an undocumented feature for now)
107
- # if model == "FES2012":
108
- # pytmd_model = pyTMD.io.model(directory).from_file(
109
- # directory / "model_FES2012.def"
110
- # )
111
- # elif model == "TPXO8-atlas-v1":
112
- # pytmd_model = pyTMD.io.model(directory).from_file(directory / "model_TPXO8.def")
113
- # else:
114
- # pytmd_model = pyTMD.io.model(
115
- # directory, format="netcdf", compressed=False
116
- # ).elevation(model)
117
-
118
- # if model in NONSTANDARD_MODELS:
119
- # model_params = NONSTANDARD_MODELS[model]
120
- # model_params_bytes = io.BytesIO(json.dumps(model_params).encode("utf-8"))
121
- # pytmd_model = pyTMD.io.model(directory).from_file(definition_file=model_params_bytes)
122
-
123
- # else:
124
-
136
+ # Obtain model details
125
137
  pytmd_model = pyTMD.io.model(directory).elevation(model)
126
138
 
127
139
  # Convert x, y to latitude/longitude
@@ -474,11 +486,11 @@ def model_tides(
474
486
  This function is parallelised to improve performance, and
475
487
  supports all tidal models supported by `pyTMD`, including:
476
488
 
477
- - Empirical Ocean Tide model (`EOT20`)
478
- - Finite Element Solution tide models (`FES2022`, `FES2014`, `FES2012`)
479
- - TOPEX/POSEIDON global tide models (`TPXO10`, `TPXO9`, `TPXO8`)
480
- - Global Ocean Tide models (`GOT5.6`, `GOT5.5`, `GOT4.10`, `GOT4.8`, `GOT4.7`)
481
- - Hamburg direct data Assimilation Methods for Tides models (`HAMTIDE11`)
489
+ - Empirical Ocean Tide model (`EOT20`)
490
+ - Finite Element Solution tide models (`FES2022`, `FES2014`, `FES2012`)
491
+ - TOPEX/POSEIDON global tide models (`TPXO10`, `TPXO9`, `TPXO8`)
492
+ - Global Ocean Tide models (`GOT5.6`, `GOT5.5`, `GOT4.10`, `GOT4.8`, `GOT4.7`)
493
+ - Hamburg direct data Assimilation Methods for Tides models (`HAMTIDE11`)
482
494
 
483
495
  This function requires access to tide model data files.
484
496
  These should be placed in a folder with subfolders matching
@@ -504,11 +516,11 @@ def model_tides(
504
516
  model : string, optional
505
517
  The tide model used to model tides. Options include:
506
518
 
507
- - "FES2014" (pre-configured on DEA Sandbox)
519
+ - "EOT20"
520
+ - "FES2014"
508
521
  - "FES2022"
509
522
  - "TPXO9-atlas-v5"
510
523
  - "TPXO8-atlas"
511
- - "EOT20"
512
524
  - "HAMTIDE11"
513
525
  - "GOT4.10"
514
526
  - "ensemble" (advanced ensemble tide model functionality;
@@ -516,7 +528,7 @@ def model_tides(
516
528
  directory : string, optional
517
529
  The directory containing tide model data files. If no path is
518
530
  provided, this will default to the environment variable
519
- `EO_TIDES_TIDE_MODELS` if set, otherwise "/var/share/tide_models".
531
+ `EO_TIDES_TIDE_MODELS` if set, or raise an error if not.
520
532
  Tide modelling files should be stored in sub-folders for each
521
533
  model that match the structure provided by `pyTMD`.
522
534
 
@@ -602,23 +614,6 @@ def model_tides(
602
614
  A dataframe containing modelled tide heights.
603
615
 
604
616
  """
605
- # Set tide modelling files directory. If no custom path is provided,
606
- # first try global environmental var, then "/var/share/tide_models"
607
- if directory is None:
608
- if "EO_TIDES_TIDE_MODELS" in os.environ:
609
- directory = os.environ["EO_TIDES_TIDE_MODELS"]
610
- else:
611
- directory = "/var/share/tide_models"
612
-
613
- # Verify path exists
614
- directory = pathlib.Path(directory).expanduser()
615
- if not directory.exists():
616
- raise FileNotFoundError("Invalid tide directory")
617
-
618
- # If time passed as a single Timestamp, convert to datetime64
619
- if isinstance(time, pd.Timestamp):
620
- time = time.to_datetime64()
621
-
622
617
  # Turn inputs into arrays for consistent handling
623
618
  models_requested = np.atleast_1d(model)
624
619
  x = np.atleast_1d(x)
@@ -644,32 +639,41 @@ def model_tides(
644
639
  "you intended to model multiple timesteps at each point."
645
640
  )
646
641
 
647
- # Verify that all provided models are supported
648
- valid_models = [
649
- # Standard built-in pyTMD models
650
- "EOT20",
651
- "FES2014",
652
- "FES2022",
653
- "GOT4.10",
654
- "HAMTIDE11",
655
- "TPXO8-atlas", # binary version, not suitable for clipping
656
- "TPXO9-atlas-v5",
657
- # Non-standard models, defined internally
658
- "FES2012",
659
- "FES2014_extrapolated",
660
- "FES2022_extrapolated",
661
- "GOT5.6",
662
- "GOT5.6_extrapolated",
663
- "TPXO8-atlas-v1", # netCDF version
664
- # Advanced ensemble model functionality
665
- "ensemble",
666
- ]
642
+ # If time passed as a single Timestamp, convert to datetime64
643
+ if isinstance(time, pd.Timestamp):
644
+ time = time.to_datetime64()
645
+
646
+ # Set tide modelling files directory. If no custom path is
647
+ # provided, try global environment variable.
648
+ directory = _set_directory(directory)
649
+
650
+ # Get full list of supported models from pyTMD database;
651
+ # add ensemble option to list of models
652
+ available_models, valid_models = list_models(
653
+ directory, show_available=False, show_supported=False, raise_error=True
654
+ )
655
+ available_models = available_models + ["ensemble"]
656
+ valid_models = valid_models + ["ensemble"]
657
+
658
+ # Error if any models are not supported
667
659
  if not all(m in valid_models for m in models_requested):
668
- raise ValueError(
669
- f"One or more of the models requested {models_requested} is "
670
- f"not valid. The following models are currently supported: "
671
- f"{valid_models}",
660
+ error_text = (
661
+ f"One or more of the requested models are not valid:\n"
662
+ f"{models_requested}\n\n"
663
+ "The following models are supported:\n"
664
+ f"{valid_models}"
665
+ )
666
+ raise ValueError(error_text)
667
+
668
+ # Error if any models are not available in `directory`
669
+ if not all(m in available_models for m in models_requested):
670
+ error_text = (
671
+ f"One or more of the requested models are valid, but not available in `{directory}`:\n"
672
+ f"{models_requested}\n\n"
673
+ f"The following models are available in `{directory}`:\n"
674
+ f"{available_models}"
672
675
  )
676
+ raise ValueError(error_text)
673
677
 
674
678
  # If ensemble modelling is requested, use a custom list of models
675
679
  # for subsequent processing
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.1
2
2
  Name: eo-tides
3
- Version: 0.0.19
3
+ Version: 0.0.20
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/
@@ -12,4 +12,5 @@ eo_tides.egg-info/dependency_links.txt
12
12
  eo_tides.egg-info/requires.txt
13
13
  eo_tides.egg-info/top_level.txt
14
14
  tests/test_model.py
15
+ tests/test_utils.py
15
16
  tests/test_validation.py
@@ -1,6 +1,6 @@
1
1
  [project]
2
2
  name = "eo-tides"
3
- version = "0.0.19"
3
+ version = "0.0.20"
4
4
  description = "Tide modelling tools for large-scale satellite earth observation analysis"
5
5
  authors = [{ name = "Robbi Bishop-Taylor", email = "Robbi.BishopTaylor@ga.gov.au" }]
6
6
  readme = "README.md"
@@ -6,7 +6,7 @@ import pystac_client
6
6
  import pytest
7
7
  import xarray as xr
8
8
 
9
- from eo_tides.model import available_models, model_tides, pixel_tides
9
+ from eo_tides.model import list_models, model_tides, pixel_tides
10
10
  from eo_tides.validation import eval_metrics
11
11
 
12
12
  GAUGE_X = 122.2183
@@ -91,9 +91,12 @@ def satellite_ds(request):
91
91
 
92
92
 
93
93
  # Test available tide models
94
- def test_available_models():
95
- available_m = available_models()
96
- assert available_m == ["FES2014", "HAMTIDE11"]
94
+ def test_list_models():
95
+ available_models, supported_models = list_models()
96
+ assert available_models == ["FES2014", "HAMTIDE11"]
97
+
98
+ available_models, supported_models = list_models(show_available=False, show_supported=False)
99
+ assert available_models == ["FES2014", "HAMTIDE11"]
97
100
 
98
101
 
99
102
  # Run test for multiple input coordinates, CRSs and interpolation methods
@@ -0,0 +1,84 @@
1
+ import numpy as np
2
+ import pytest
3
+
4
+ from eo_tides.utils import idw
5
+
6
+
7
+ # Test Inverse Distance Weighted function
8
+ def test_idw():
9
+ # Basic psuedo-1D example
10
+ input_z = [1, 2, 3, 4, 5]
11
+ input_x = [0, 1, 2, 3, 4]
12
+ input_y = [0, 0, 0, 0, 0]
13
+ output_x = [0.5, 1.5, 2.5, 3.5]
14
+ output_y = [0.0, 0.0, 0.0, 0.0]
15
+ out = idw(input_z, input_x, input_y, output_x, output_y, k=2)
16
+ assert np.allclose(out, [1.5, 2.5, 3.5, 4.5])
17
+
18
+ # Verify that k > input points gives error
19
+ with pytest.raises(ValueError):
20
+ idw(input_z, input_x, input_y, output_x, output_y, k=6)
21
+
22
+ # 2D nearest neighbour case
23
+ input_z = [1, 2, 3, 4]
24
+ input_x = [0, 4, 0, 4]
25
+ input_y = [0, 0, 4, 4]
26
+ output_x = [1, 4, 0, 3]
27
+ output_y = [0, 1, 3, 4]
28
+ out = idw(input_z, input_x, input_y, output_x, output_y, k=1)
29
+ assert np.allclose(out, [1, 2, 3, 4])
30
+
31
+ # Two neighbours
32
+ input_z = [1, 2, 3, 4]
33
+ input_x = [0, 4, 0, 4]
34
+ input_y = [0, 0, 4, 4]
35
+ output_x = [2, 0, 4, 2]
36
+ output_y = [0, 2, 2, 4]
37
+ out = idw(input_z, input_x, input_y, output_x, output_y, k=2)
38
+ assert np.allclose(out, [1.5, 2, 3, 3.5])
39
+
40
+ # Four neighbours
41
+ out = idw(input_z, input_x, input_y, output_x, output_y, k=4)
42
+ assert np.allclose(out, [2.11, 2.30, 2.69, 2.88], rtol=0.01)
43
+
44
+ # Four neighbours; max distance of 2
45
+ out = idw(input_z, input_x, input_y, output_x, output_y, k=4, max_dist=2)
46
+ assert np.allclose(out, [1.5, 2, 3, 3.5])
47
+
48
+ # Four neighbours; max distance of 2, k_min of 4 (should return NaN)
49
+ out = idw(input_z, input_x, input_y, output_x, output_y, k=4, max_dist=2, k_min=4)
50
+ assert np.isnan(out).all()
51
+
52
+ # Four neighbours; power function p=0
53
+ out = idw(input_z, input_x, input_y, output_x, output_y, k=4, p=0)
54
+ assert np.allclose(out, [2.5, 2.5, 2.5, 2.5])
55
+
56
+ # Four neighbours; power function p=2
57
+ out = idw(input_z, input_x, input_y, output_x, output_y, k=4, p=2)
58
+ assert np.allclose(out, [1.83, 2.17, 2.83, 3.17], rtol=0.01)
59
+
60
+ # Different units, nearest neighbour case
61
+ input_z = [10, 20, 30, 40]
62
+ input_x = [1125296, 1155530, 1125296, 1155530]
63
+ input_y = [-4169722, -4169722, -4214782, -4214782]
64
+ output_x = [1124952, 1159593, 1120439, 1155284]
65
+ output_y = [-4169749, -4172892, -4211108, -4214332]
66
+ out = idw(input_z, input_x, input_y, output_x, output_y, k=1)
67
+ assert np.allclose(out, [10, 20, 30, 40])
68
+
69
+ # Verify distance works on different units
70
+ output_x = [1142134, 1138930]
71
+ output_y = [-4171232, -4213451]
72
+ out = idw(input_z, input_x, input_y, output_x, output_y, k=4, max_dist=20000)
73
+ assert np.allclose(out, [15, 35], rtol=0.1)
74
+
75
+ # Test multidimensional input
76
+ input_z = np.column_stack(([1, 2, 3, 4], [10, 20, 30, 40]))
77
+ input_x = [0, 4, 0, 4]
78
+ input_y = [0, 0, 4, 4]
79
+ output_x = [1, 4, 0, 3]
80
+ output_y = [0, 1, 3, 4]
81
+ out = idw(input_z, input_x, input_y, output_x, output_y, k=1)
82
+ assert input_z.shape == out.shape
83
+ assert np.allclose(out[:, 0], [1, 2, 3, 4])
84
+ assert np.allclose(out[:, 1], [10, 20, 30, 40])
File without changes
File without changes
File without changes
File without changes
File without changes