eo-tides 0.7.6.dev2__py3-none-any.whl → 0.8.0__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/model.py CHANGED
@@ -1,3 +1,9 @@
1
+ """Core tide modelling functionality.
2
+
3
+ This module provides tools for modelling ocean tide heights and phases
4
+ for any location or time period using one or more global tide models.
5
+ """
6
+
1
7
  # Used to postpone evaluation of type annotations
2
8
  from __future__ import annotations
3
9
 
@@ -23,7 +29,13 @@ import pyTMD
23
29
  import timescale.time
24
30
  from tqdm import tqdm
25
31
 
26
- from .utils import DatetimeLike, _set_directory, _standardise_models, _standardise_time, idw
32
+ from .utils import (
33
+ DatetimeLike,
34
+ _set_directory,
35
+ _standardise_models,
36
+ _standardise_time,
37
+ idw,
38
+ )
27
39
 
28
40
 
29
41
  def _parallel_splits(
@@ -32,12 +44,13 @@ def _parallel_splits(
32
44
  parallel_max: int | None = None,
33
45
  min_points_per_split: int = 1000,
34
46
  ) -> int:
35
- """
36
- Calculates the optimal number of parallel splits for data
37
- processing based on system resources and processing constraints.
47
+ """Calculate the optimal number of parallel splits for data processing.
48
+
49
+ Optimal parallelisation is estimated based on system resources
50
+ and processing constraints.
38
51
 
39
- Parameters:
40
- -----------
52
+ Parameters
53
+ ----------
41
54
  total_points : int
42
55
  Total number of data points to process
43
56
  model_count : int
@@ -46,6 +59,7 @@ def _parallel_splits(
46
59
  Maximum number of parallel processes to use. If None, uses CPU core count
47
60
  min_points_per_split : int, default=1000
48
61
  Minimum number of points that should be processed in each split
62
+
49
63
  """
50
64
  # Get available CPUs. First see if `CPU_GUARANTEE` exists in
51
65
  # environment (if running in JupyterHub); if not use psutil
@@ -81,15 +95,20 @@ def _model_tides(
81
95
  crop,
82
96
  crop_buffer,
83
97
  append_node,
98
+ constituents,
84
99
  extra_databases,
85
100
  ):
86
- """Worker function applied in parallel by `model_tides`. Handles the
87
- extraction of tide modelling constituents and tide modelling using
88
- `pyTMD`.
101
+ """Worker function applied in parallel by `model_tides`.
102
+
103
+ Handles the extraction of tide modelling constituents and tide
104
+ modelling using `pyTMD`.
89
105
  """
90
106
  # Load models from pyTMD database
91
107
  extra_databases = [] if extra_databases is None else extra_databases
92
- pytmd_model = pyTMD.io.model(directory=directory, extra_databases=extra_databases).elevation(model)
108
+ pytmd_model = pyTMD.io.model(
109
+ directory=directory,
110
+ extra_databases=extra_databases,
111
+ ).elevation(model)
93
112
 
94
113
  # Reproject x, y to latitude/longitude
95
114
  transformer = pyproj.Transformer.from_crs(crs, "EPSG:4326", always_xy=True)
@@ -110,6 +129,8 @@ def _model_tides(
110
129
  extrapolate=extrapolate,
111
130
  cutoff=cutoff,
112
131
  append_node=append_node,
132
+ constituents=constituents,
133
+ extra_databases=extra_databases,
113
134
  )
114
135
 
115
136
  # TODO: Return constituents
@@ -126,7 +147,8 @@ def _model_tides(
126
147
  "affect your results but may lead to a minor slowdown. "
127
148
  "This can occur when analysing clipped model files restricted "
128
149
  "to the western hemisphere. To suppress this warning, manually "
129
- "set `crop=False`."
150
+ "set `crop=False`.",
151
+ stacklevel=2,
130
152
  )
131
153
 
132
154
  # Read tidal constants and interpolate to grid points
@@ -140,6 +162,8 @@ def _model_tides(
140
162
  extrapolate=extrapolate,
141
163
  cutoff=cutoff,
142
164
  append_node=append_node,
165
+ constituents=constituents,
166
+ extra_databases=extra_databases,
143
167
  )
144
168
 
145
169
  # Otherwise, raise error if cropping if set to True
@@ -210,13 +234,15 @@ def _model_tides(
210
234
 
211
235
  # Convert data to pandas.DataFrame, and set index to our input
212
236
  # time/x/y values
213
- tide_df = pd.DataFrame({
214
- "time": np.tile(time, points_repeat),
215
- "x": np.repeat(x, time_repeat),
216
- "y": np.repeat(y, time_repeat),
217
- "tide_model": model,
218
- "tide_height": tide,
219
- }).set_index(["time", "x", "y"])
237
+ tide_df = pd.DataFrame(
238
+ {
239
+ "time": np.tile(time, points_repeat),
240
+ "x": np.repeat(x, time_repeat),
241
+ "y": np.repeat(y, time_repeat),
242
+ "tide_model": model,
243
+ "tide_height": tide,
244
+ },
245
+ ).set_index(["time", "x", "y"])
220
246
 
221
247
  # Optionally convert outputs to integer units (can save memory)
222
248
  if output_units == "m":
@@ -239,10 +265,11 @@ def ensemble_tides(
239
265
  ranking_valid_perc=0.02,
240
266
  **idw_kwargs,
241
267
  ):
242
- """Combine multiple tide models into a single locally optimised
243
- ensemble tide model using external model ranking data (e.g.
244
- satellite altimetry or NDWI-tide correlations along the coastline)
245
- to inform the selection of the best local models.
268
+ """Combine multiple tide models into a single locally optimised ensemble tide model.
269
+
270
+ Uses external model ranking data (e.g. satellite altimetry or
271
+ NDWI-tide correlations along the coastline) to inform the
272
+ selection of the best local models.
246
273
 
247
274
  This function performs the following steps:
248
275
 
@@ -313,11 +340,12 @@ def ensemble_tides(
313
340
  """
314
341
  # Raise data if `tide_df` provided in wide format
315
342
  if "tide_model" not in tide_df:
316
- raise Exception(
343
+ err_msg = (
317
344
  "`tide_df` does not contain the expected 'tide_model' and "
318
345
  "'tide_height' columns. Ensure that tides were modelled in "
319
- "long format (i.e. `output_format='long'` in `model_tides`)."
346
+ "long format (i.e. `output_format='long'` in `model_tides`).",
320
347
  )
348
+ raise Exception(err_msg)
321
349
 
322
350
  # Extract x and y coords from dataframe
323
351
  x = tide_df.index.get_level_values(level="x")
@@ -333,7 +361,8 @@ def ensemble_tides(
333
361
  gpd.read_file(ranking_points, engine="pyogrio")
334
362
  .to_crs(crs)
335
363
  .query(f"valid_perc > {ranking_valid_perc}")
336
- .dropna(how="all")[model_ranking_cols + ["geometry"]]
364
+ .dropna(how="all")
365
+ .filter(model_ranking_cols + ["geometry"]) # noqa: RUF005
337
366
  )
338
367
  except KeyError:
339
368
  error_msg = f"""
@@ -435,6 +464,7 @@ def model_tides(
435
464
  crop: bool | str = "auto",
436
465
  crop_buffer: float | None = 5,
437
466
  append_node: bool = False,
467
+ constituents: list[str] | None = None,
438
468
  parallel: bool = True,
439
469
  parallel_splits: int | str = "auto",
440
470
  parallel_max: int | None = None,
@@ -442,9 +472,7 @@ def model_tides(
442
472
  extra_databases: str | os.PathLike | list | None = None,
443
473
  **ensemble_kwargs,
444
474
  ) -> pd.DataFrame:
445
- """
446
- Model tide heights at multiple coordinates and/or timesteps
447
- using using one or more ocean tide models.
475
+ """Model tide heights at multiple coordinates or timesteps using using multiple ocean tide models.
448
476
 
449
477
  This function is parallelised to improve performance, and
450
478
  supports all tidal models supported by `pyTMD`, including:
@@ -457,33 +485,30 @@ def model_tides(
457
485
  - Technical University of Denmark tide models (DTU23)
458
486
 
459
487
  This function requires access to tide model data files.
460
- These should be placed in a folder with subfolders matching
461
- the structure required by `pyTMD`. For more details:
462
- <https://geoscienceaustralia.github.io/eo-tides/setup/>
463
- <https://pytmd.readthedocs.io/en/latest/getting_started/Getting-Started.html#directories>
488
+ For tide model setup instructions, refer to the guide:
489
+ https://geoscienceaustralia.github.io/eo-tides/setup/
464
490
 
465
491
  This function is a modification of the `pyTMD` package's
466
492
  `pyTMD.compute.tide_elevations` function. For more info:
467
- <https://pytmd.readthedocs.io/en/latest/api_reference/compute.html#pyTMD.compute.tide_elevations>
493
+ https://pytmd.readthedocs.io/en/latest/api_reference/compute.html#pyTMD.compute.tide_elevations
494
+ https://pytmd.readthedocs.io/en/latest/getting_started/Getting-Started.html#directories
468
495
 
469
496
  Parameters
470
497
  ----------
471
- x : float or list of float
472
- One or more x coordinates used to define the location at
473
- which to model tides. By default these coordinates should
474
- be in "EPSG:4326" WGS84 degrees longitude; use "crs" if they
475
- are in a custom coordinate reference system.
476
- y : float or list of float
477
- One or more y coordinates used to define the location at
478
- which to model tides. By default these coordinates should
479
- be in "EPSG:4326" WGS84 degrees latitude; use "crs" if they
480
- are in a custom coordinate reference system.
498
+ x : float or list of floats
499
+ One or more x coordinates at which to model tides. Assumes
500
+ degrees longitude (EPSG:4326) by default; use `crs` to specify
501
+ a different coordinate reference system.
502
+ y : float or list of floats
503
+ One or more y coordinates at which to model tides. Assumes
504
+ degrees latitude (EPSG:4326) by default; use `crs` to specify
505
+ a different coordinate reference system.
481
506
  time : DatetimeLike
482
- Times at which to model tide heights (in UTC). Accepts
483
- any format that can be converted by `pandas.to_datetime()`;
484
- e.g. np.ndarray[datetime64], pd.DatetimeIndex, pd.Timestamp,
485
- datetime.datetime and strings (e.g. "2020-01-01 23:00").
486
- For example: `time=pd.date_range(start="2000", end="2001", freq="5h")`
507
+ One or more UTC times at which to model tide heights. Accepts
508
+ any time format compatible with `pandas.to_datetime()`, e.g.
509
+ datetime.datetime, pd.Timestamp, pd.DatetimeIndex, numpy.datetime64,
510
+ or date/time strings (e.g. "2020-01-01 23:00"). For example:
511
+ `time = pd.date_range(start="2000", end="2001", freq="5h")`.
487
512
  model : str or list of str, optional
488
513
  The tide model (or list of models) to use to model tides.
489
514
  Defaults to "EOT20"; specify "all" to use all models available
@@ -497,65 +522,67 @@ def model_tides(
497
522
  model that match the structure required by `pyTMD`
498
523
  (<https://geoscienceaustralia.github.io/eo-tides/setup/>).
499
524
  crs : str, optional
500
- Input coordinate reference system for x and y coordinates.
501
- Defaults to "EPSG:4326" (WGS84; degrees latitude, longitude).
525
+ Input coordinate reference system for x/y coordinates.
526
+ Defaults to "EPSG:4326" (degrees latitude, longitude).
502
527
  mode : str, optional
503
- The analysis mode to use for tide modelling. Supports two options:
504
-
505
- - "one-to-many": Models tides for every timestep in "time" at
506
- every input x and y coordinate point. This is useful if you
507
- want to model tides for a specific list of timesteps across
508
- multiple spatial points (e.g. for the same set of satellite
509
- acquisition times at various locations across your study area).
510
- - "one-to-one": Model tides using a unique timestep for each
511
- x and y coordinate pair. In this mode, the number of x and
512
- y points must equal the number of timesteps provided in "time".
528
+ Tide modelling analysis mode. Supports two options:
529
+
530
+ - `"one-to-many"`: Models tides at every x/y coordinate for
531
+ every timestep in `time`. This is useful for Earth observation
532
+ workflows where you want to model tides at many spatial points
533
+ for a common set of acquisition times (e.g. satellite overpasses).
534
+
535
+ - `"one-to-one"`: Model tides using one timestep for each x/y
536
+ coordinate. In this mode, the number of x/y coordinates must
537
+ match the number of timesteps in `time`.
513
538
  output_format : str, optional
514
539
  Whether to return the output dataframe in long format (with
515
540
  results stacked vertically along "tide_model" and "tide_height"
516
541
  columns), or wide format (with a column for each tide model).
517
542
  Defaults to "long".
518
543
  output_units : str, optional
519
- Whether to return modelled tides in floating point metre units,
520
- or integer centimetre units (i.e. scaled by 100) or integer
521
- millimetre units (i.e. scaled by 1000. Returning outputs in
522
- integer units can be useful for reducing memory usage.
523
- Defaults to "m" for metres; set to "cm" for centimetres or "mm"
524
- for millimetres.
544
+ Units for the returned tide heights. Options are:
545
+
546
+ - `"m"` (default): floating point values in metres
547
+ - `"cm"`: integer values in centimetres (x100)
548
+ - `"mm"`: integer values in millimetres (x1000)
549
+
550
+ Using integer units can help reduce memory usage.
525
551
  method : str, optional
526
- Method used to interpolate tidal constituents
527
- from model files. Defaults to "linear"; options include:
552
+ Method used to interpolate tide model constituent files.
553
+ Defaults to "linear"; options include:
528
554
 
529
- - "linear", "nearest": scipy regular grid interpolations
530
- - "spline": scipy bivariate spline interpolation
531
- - "bilinear": quick bilinear interpolation
555
+ - `"linear"`, `"nearest"`: scipy regular grid interpolations
556
+ - `"spline"`: scipy bivariate spline interpolation
557
+ - `"bilinear"`: quick bilinear interpolation
532
558
  extrapolate : bool, optional
533
- Whether to extrapolate tides into x and y coordinates outside
534
- of the valid tide modelling domain using nearest-neighbor
535
- interpolation. The default of True ensures that modelled tides
536
- will be returned even if there is no underlying tide model
537
- data for a location (e.g. inside estuaries far from the
538
- coastline). However, this can also produce unreliable results.
559
+ If True (default), extrapolate tides inland of the valid tide
560
+ model extent using nearest-neighbor interpolation. This can
561
+ ensure tide are returned everywhere, but accuracy may degrade
562
+ with distance from the valid model extent (e.g. inland or along
563
+ complex estuaries or rivers). Set `cutoff` to define the
564
+ maximum extrapolation distance.
539
565
  cutoff : float, optional
540
- Extrapolation cutoff in kilometers. The default is None, which
541
- will extrapolate for all points regardless of distance from the
542
- valid tide modelling domain.
566
+ Maximum distance in kilometres to extrapolate tides inland of the
567
+ valid tide model extent. The default of None allows extrapolation
568
+ at any (i.e. infinite) distance.
543
569
  crop : bool or str, optional
544
- Whether to crop tide model constituent files on-the-fly to
545
- improve performance. Defaults to "auto", which will attempt to
546
- apply on-the-fly cropping where possible (some clipped model
547
- files restricted entirely to the western hemisphere are not
548
- suitable for on-the-fly cropping). Set `crop_buffer` to
549
- customise the buffer distance used to crop the files.
570
+ Whether to crop tide model files on-the-fly to improve performance.
571
+ Defaults to "auto", which enables cropping when supported (some
572
+ clipped model files limited to the western hemisphere may not support
573
+ on-the-fly cropping). Use `crop_buffer` to adjust the buffer
574
+ distance used for cropping.
550
575
  crop_buffer : int or float, optional
551
- The buffer distance in degrees used to crop tide model
552
- constituent files around the modelling area. Defaults to 5,
553
- which will crop constituents using a five degree buffer on
554
- either side of the analysis extent.
555
- append_node: bool, optional
576
+ The buffer distance in degrees to crop tide model files around the
577
+ requested x/y coordinates. Defaults to 5, which will crop model
578
+ files using a five degree buffer.
579
+ append_node : bool, optional
556
580
  Apply adjustments to harmonic constituents to allow for periodic
557
581
  modulations over the 18.6-year nodal period (lunar nodal tide).
558
582
  Default is False.
583
+ constituents : list, optional
584
+ Optional list of tide constituents to use for tide prediction.
585
+ Default is None, which will use all available constituents.
559
586
  parallel : bool, optional
560
587
  Whether to parallelise tide modelling. If multiple tide models
561
588
  are requested, these will be run in parallel. If enough workers
@@ -585,7 +612,7 @@ def model_tides(
585
612
  See: https://pytmd.readthedocs.io/en/latest/getting_started/Getting-Started.html#model-database
586
613
  **ensemble_kwargs :
587
614
  Keyword arguments used to customise the generation of optional
588
- ensemble tide models if "ensemble" modelling are requested.
615
+ ensemble tide models if "ensemble" tide modelling is requested.
589
616
  These are passed to the underlying `_ensemble_model` function.
590
617
  Useful parameters include `ranking_points` (path to model
591
618
  rankings data), `k` (for controlling how model rankings are
@@ -604,26 +631,43 @@ def model_tides(
604
631
  time = _standardise_time(time)
605
632
 
606
633
  # Validate input arguments
607
- assert time is not None, "Times for modelling tides must be provided via `time`."
608
- assert method in ("bilinear", "spline", "linear", "nearest")
609
- assert output_units in (
610
- "m",
611
- "cm",
612
- "mm",
613
- ), "Output units must be either 'm', 'cm', or 'mm'."
614
- assert output_format in (
615
- "long",
616
- "wide",
617
- ), "Output format must be either 'long' or 'wide'."
618
- assert np.issubdtype(x.dtype, np.number), "`x` must contain only valid numeric values, and must not be None."
619
- assert np.issubdtype(y.dtype, np.number), "`y` must contain only valid numeric values, and must not be None.."
620
- assert len(x) == len(y), "x and y must be the same length."
621
- if mode == "one-to-one":
622
- assert len(x) == len(time), (
623
- "The number of supplied x and y points and times must be "
624
- "identical in 'one-to-one' mode. Use 'one-to-many' mode if "
625
- "you intended to model multiple timesteps at each point."
634
+ if time is None:
635
+ err_msg = "Times for modelling tides must be provided via `time`."
636
+ raise ValueError(err_msg)
637
+
638
+ if method not in ("bilinear", "spline", "linear", "nearest"):
639
+ err_msg = (
640
+ f"Invalid interpolation method '{method}'. Must be one of 'bilinear', 'spline', 'linear', or 'nearest'."
641
+ )
642
+ raise ValueError(err_msg)
643
+
644
+ if output_units not in ("m", "cm", "mm"):
645
+ err_msg = "Output units must be either 'm', 'cm', or 'mm'."
646
+ raise ValueError(err_msg)
647
+
648
+ if output_format not in ("long", "wide"):
649
+ err_msg = "Output format must be either 'long' or 'wide'."
650
+ raise ValueError(err_msg)
651
+
652
+ if not np.issubdtype(x.dtype, np.number):
653
+ err_msg = "`x` must contain only valid numeric values, and must not be None."
654
+ raise TypeError(err_msg)
655
+
656
+ if not np.issubdtype(y.dtype, np.number):
657
+ err_msg = "`y` must contain only valid numeric values, and must not be None."
658
+ raise TypeError(err_msg)
659
+
660
+ if len(x) != len(y):
661
+ err_msg = "`x` and `y` must be the same length."
662
+ raise ValueError(err_msg)
663
+
664
+ if mode == "one-to-one" and len(x) != len(time):
665
+ err_msg = (
666
+ "The number of supplied `x` and `y` points and `time` values must be "
667
+ "identical in 'one-to-one' mode. Use 'one-to-many' mode if you intended "
668
+ "to model multiple timesteps at each point."
626
669
  )
670
+ raise ValueError(err_msg)
627
671
 
628
672
  # Set tide modelling files directory. If no custom path is
629
673
  # provided, try global environment variable.
@@ -652,6 +696,7 @@ def model_tides(
652
696
  crop=crop,
653
697
  crop_buffer=crop_buffer,
654
698
  append_node=append_node,
699
+ constituents=constituents,
655
700
  extra_databases=extra_databases,
656
701
  )
657
702
 
@@ -666,15 +711,16 @@ def model_tides(
666
711
  )
667
712
 
668
713
  # Verify that parallel splits are not larger than number of points
669
- assert isinstance(parallel_splits, int)
714
+ assert isinstance(parallel_splits, int) # noqa: S101
670
715
  if parallel_splits > len(x):
671
- raise ValueError(f"Parallel splits ({parallel_splits}) cannot be larger than the number of points ({len(x)}).")
716
+ err_msg = f"Parallel splits ({parallel_splits}) cannot be larger than the number of points ({len(x)})."
717
+ raise ValueError(err_msg)
672
718
 
673
719
  # Parallelise if either multiple models or multiple splits requested
674
720
  if parallel & ((len(models_to_process) > 1) | (parallel_splits > 1)):
675
721
  with ProcessPoolExecutor(max_workers=parallel_max) as executor:
676
722
  print(
677
- f"Modelling tides with {', '.join(models_to_process)} in parallel (models: {len(models_to_process)}, splits: {parallel_splits})"
723
+ f"Modelling tides with {', '.join(models_to_process)} in parallel (models: {len(models_to_process)}, splits: {parallel_splits})",
678
724
  )
679
725
 
680
726
  # Optionally split lon/lat points into `splits_n` chunks
@@ -691,6 +737,7 @@ def model_tides(
691
737
  if mode == "one-to-many":
692
738
  model_iters, x_iters, y_iters = zip(
693
739
  *[(m, x_split[i], y_split[i]) for m in models_to_process for i in range(parallel_splits)],
740
+ strict=False,
694
741
  )
695
742
  time_iters = [time] * len(model_iters)
696
743
  elif mode == "one-to-one":
@@ -701,22 +748,29 @@ def model_tides(
701
748
  for m in models_to_process
702
749
  for i in range(parallel_splits)
703
750
  ],
751
+ strict=False,
704
752
  )
705
753
 
706
754
  # Apply func in parallel, iterating through each input param
707
755
  try:
708
756
  model_outputs = list(
709
757
  tqdm(
710
- executor.map(iter_func, model_iters, x_iters, y_iters, time_iters),
758
+ executor.map(
759
+ iter_func,
760
+ model_iters,
761
+ x_iters,
762
+ y_iters,
763
+ time_iters,
764
+ ),
711
765
  total=len(model_iters),
712
766
  ),
713
767
  )
714
768
  except BrokenProcessPool:
715
- error_msg = (
769
+ err_msg = (
716
770
  "Parallelised tide modelling failed, likely to to an out-of-memory error. "
717
771
  "Try reducing the size of your analysis, or set `parallel=False`."
718
772
  )
719
- raise RuntimeError(error_msg)
773
+ raise RuntimeError(err_msg) from None
720
774
 
721
775
  # Model tides in series if parallelisation is off
722
776
  else:
@@ -736,20 +790,27 @@ def model_tides(
736
790
 
737
791
  # Update requested models with any custom ensemble models, then
738
792
  # filter the dataframe to keep only models originally requested
739
- models_requested = list(np.union1d(models_requested, ensemble_df.tide_model.unique()))
740
- tide_df = pd.concat([tide_df, ensemble_df]).query("tide_model in @models_requested")
793
+ models_requested = list(
794
+ np.union1d(models_requested, ensemble_df.tide_model.unique()),
795
+ )
796
+ tide_df = pd.concat([tide_df, ensemble_df]).query(
797
+ "tide_model in @models_requested",
798
+ )
741
799
 
742
800
  # Optionally convert to a wide format dataframe with a tide model in
743
801
  # each dataframe column
744
802
  if output_format == "wide":
745
803
  # Pivot into wide format with each time model as a column
746
804
  print("Converting to a wide format dataframe")
747
- tide_df = tide_df.pivot(columns="tide_model", values="tide_height")
805
+ tide_df = tide_df.pivot(columns="tide_model", values="tide_height") # noqa: PD010
748
806
 
749
807
  # If in 'one-to-one' mode, reindex using our input time/x/y
750
808
  # values to ensure the output is sorted the same as our inputs
751
809
  if mode == "one-to-one":
752
- output_indices = pd.MultiIndex.from_arrays([time, x, y], names=["time", "x", "y"])
810
+ output_indices = pd.MultiIndex.from_arrays(
811
+ [time, x, y],
812
+ names=["time", "x", "y"],
813
+ )
753
814
  tide_df = tide_df.reindex(output_indices)
754
815
 
755
816
  return tide_df
@@ -765,41 +826,39 @@ def model_phases(
765
826
  return_tides: bool = False,
766
827
  **model_tides_kwargs,
767
828
  ) -> pd.DataFrame:
768
- """
769
- Model tide phases (low-flow, high-flow, high-ebb, low-ebb)
770
- at multiple coordinates and/or timesteps using using one
771
- or more ocean tide models.
772
-
773
- Ebb and low phases are calculated by running the
774
- `eo_tides.model.model_tides` function twice, once for
775
- the requested timesteps, and again after subtracting a
776
- small time offset (by default, 15 minutes). If tides
777
- increased over this period, they are assigned as "flow";
778
- if they decreased, they are assigned as "ebb".
829
+ """Model tide phases at multiple coordinates or timesteps using multiple ocean tide models.
830
+
831
+ Ebb and low phases (low-flow, high-flow, high-ebb, low-ebb)
832
+ are calculated by running the `eo_tides.model.model_tides`
833
+ function twice, once for the requested timesteps, and again
834
+ after subtracting a small time offset (15 mins by default).
835
+ If tides increased over this period, they are assigned as
836
+ "flow"; if they decreased, they are assigned as "ebb".
779
837
  Tides are considered "high" if equal or greater than 0
780
838
  metres tide height, otherwise "low".
781
839
 
782
840
  This function supports all parameters that are supported
783
841
  by `model_tides`.
784
842
 
843
+ For tide model setup instructions, refer to the guide:
844
+ https://geoscienceaustralia.github.io/eo-tides/setup/
845
+
785
846
  Parameters
786
847
  ----------
787
- x : float or list of float
788
- One or more x coordinates used to define the location at
789
- which to model tide phases. By default these coordinates
790
- should be in "EPSG:4326" WGS84 degrees longitude; use "crs"
791
- if they are in a custom coordinate reference system.
792
- y : float or list of float
793
- One or more y coordinates used to define the location at
794
- which to model tide phases. By default these coordinates
795
- should be in "EPSG:4326" WGS84 degrees latitude; use "crs"
796
- if they are in a custom coordinate reference system.
848
+ x : float or list of floats
849
+ One or more x coordinates at which to model tides. Assumes
850
+ degrees longitude (EPSG:4326) by default; use `crs` to specify
851
+ a different coordinate reference system.
852
+ y : float or list of floats
853
+ One or more y coordinates at which to model tides. Assumes
854
+ degrees latitude (EPSG:4326) by default; use `crs` to specify
855
+ a different coordinate reference system.
797
856
  time : DatetimeLike
798
- Times at which to model tide phases (in UTC). Accepts
799
- any format that can be converted by `pandas.to_datetime()`;
800
- e.g. np.ndarray[datetime64], pd.DatetimeIndex, pd.Timestamp,
801
- datetime.datetime and strings (e.g. "2020-01-01 23:00").
802
- For example: `time=pd.date_range(start="2000", end="2001", freq="5h")`
857
+ One or more UTC times at which to model tide heights. Accepts
858
+ any time format compatible with `pandas.to_datetime()`, e.g.
859
+ datetime.datetime, pd.Timestamp, pd.DatetimeIndex, numpy.datetime64,
860
+ or date/time strings (e.g. "2020-01-01 23:00"). For example:
861
+ `time = pd.date_range(start="2000", end="2001", freq="5h")`.
803
862
  model : str or list of str, optional
804
863
  The tide model (or list of models) to use to model tides.
805
864
  Defaults to "EOT20"; specify "all" to use all models available
@@ -832,7 +891,6 @@ def model_phases(
832
891
  A dataframe containing modelled tide phases.
833
892
 
834
893
  """
835
-
836
894
  # Pop output format and mode for special handling
837
895
  output_format = model_tides_kwargs.pop("output_format", "long")
838
896
  mode = model_tides_kwargs.pop("mode", "one-to-many")
@@ -862,7 +920,9 @@ def model_phases(
862
920
  # Compare tides computed for each timestep. If the previous tide
863
921
  # was higher than the current tide, the tide is 'ebbing'. If the
864
922
  # previous tide was lower, the tide is 'flowing'
865
- ebb_flow = (tide_df.tide_height < pre_df.tide_height.values).replace({True: "ebb", False: "flow"})
923
+ ebb_flow = (tide_df.tide_height < pre_df.tide_height.to_numpy()).replace(
924
+ {True: "ebb", False: "flow"},
925
+ )
866
926
 
867
927
  # If tides are greater than 0, then "high", otherwise "low"
868
928
  high_low = (tide_df.tide_height >= 0).replace({True: "high", False: "low"})
@@ -875,12 +935,15 @@ def model_phases(
875
935
  if output_format == "wide":
876
936
  # Pivot into wide format with each time model as a column
877
937
  print("Converting to a wide format dataframe")
878
- tide_df = tide_df.pivot(columns="tide_model")
938
+ tide_df = tide_df.pivot(columns="tide_model") # noqa: PD010
879
939
 
880
940
  # If in 'one-to-one' mode, reindex using our input time/x/y
881
941
  # values to ensure the output is sorted the same as our inputs
882
942
  if mode == "one-to-one":
883
- output_indices = pd.MultiIndex.from_arrays([time, x, y], names=["time", "x", "y"])
943
+ output_indices = pd.MultiIndex.from_arrays(
944
+ [time, x, y],
945
+ names=["time", "x", "y"],
946
+ )
884
947
  tide_df = tide_df.reindex(output_indices)
885
948
 
886
949
  # Optionally drop tides