eo-tides 0.1.1__py3-none-any.whl → 0.3.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/__init__.py CHANGED
@@ -28,19 +28,22 @@ validation : Load observed tide gauge data to validate modelled tides
28
28
 
29
29
  # Import commonly used functions for convenience
30
30
  from .eo import pixel_tides, tag_tides
31
- from .model import list_models, model_tides
32
- from .stats import tide_stats
33
- from .utils import idw
31
+ from .model import model_phases, model_tides
32
+ from .stats import pixel_stats, tide_stats
33
+ from .utils import clip_models, idw, list_models
34
34
  from .validation import eval_metrics, load_gauge_gesla
35
35
 
36
36
  # Define what should be imported with "from eo_tides import *"
37
37
  __all__ = [
38
38
  "list_models",
39
39
  "model_tides",
40
+ "model_phases",
40
41
  "tag_tides",
41
42
  "pixel_tides",
42
43
  "tide_stats",
43
- "idw",
44
+ "pixel_stats",
44
45
  "eval_metrics",
45
46
  "load_gauge_gesla",
47
+ "clip_models",
48
+ "idw",
46
49
  ]
eo_tides/eo.py CHANGED
@@ -2,26 +2,104 @@
2
2
  from __future__ import annotations
3
3
 
4
4
  import os
5
+ import textwrap
5
6
  import warnings
6
7
  from typing import TYPE_CHECKING
7
8
 
9
+ import numpy as np
8
10
  import odc.geo.xr
9
- import pandas as pd
10
11
  import xarray as xr
11
12
  from odc.geo.geobox import GeoBox
12
13
 
13
14
  # Only import if running type checking
14
15
  if TYPE_CHECKING:
15
- import numpy as np
16
+ from odc.geo import Shape2d
16
17
 
17
18
  from .model import model_tides
19
+ from .utils import DatetimeLike, _standardise_time
20
+
21
+
22
+ def _resample_chunks(
23
+ data: xr.DataArray | xr.Dataset | GeoBox,
24
+ dask_chunks: tuple | None = None,
25
+ ) -> tuple | Shape2d:
26
+ """
27
+ Automatically return optimised dask chunks
28
+ for reprojection with `_pixel_tides_resample`.
29
+ Use entire image if GeoBox or if no default
30
+ chunks; use existing chunks if they exist.
31
+ """
32
+
33
+ # If dask_chunks is provided, return directly
34
+ if dask_chunks is not None:
35
+ return dask_chunks
36
+
37
+ # If data is a GeoBox, return its shape
38
+ if isinstance(data, GeoBox):
39
+ return data.shape
40
+
41
+ # if data has chunks, then return just spatial chunks
42
+ if data.chunks is not None:
43
+ y_dim, x_dim = data.odc.spatial_dims
44
+ return data.chunks[y_dim], data.chunks[x_dim]
45
+
46
+ # if data has no chunks, then return entire image shape
47
+ return data.odc.geobox.shape
48
+
49
+
50
+ def _standardise_inputs(
51
+ data: xr.DataArray | xr.Dataset | GeoBox,
52
+ time: DatetimeLike | None,
53
+ ) -> tuple[GeoBox, np.ndarray | None]:
54
+ """
55
+ Takes an xarray or GeoBox input and an optional custom times,
56
+ and returns a standardised GeoBox and times (usually an
57
+ array, but possibly None).
58
+ """
59
+
60
+ # If `data` is an xarray object, extract its GeoBox and time
61
+ if isinstance(data, (xr.DataArray, xr.Dataset)):
62
+ # Try to extract GeoBox
63
+ try:
64
+ gbox: GeoBox = data.odc.geobox
65
+ except AttributeError:
66
+ error_msg = """
67
+ Cannot extract a valid GeoBox for `data`. This is required for
68
+ extracting details about `data`'s CRS and spatial location.
69
+
70
+ Import `odc.geo.xr` then run `data = data.odc.assign_crs(crs=...)`
71
+ to prepare your data before passing it to this function.
72
+ """
73
+ raise Exception(textwrap.dedent(error_msg).strip())
74
+
75
+ # Use custom time by default if provided; otherwise try and extract from `data`
76
+ if time is not None:
77
+ time = _standardise_time(time)
78
+ elif "time" in data.dims:
79
+ time = np.asarray(data.coords["time"].values)
80
+ else:
81
+ raise ValueError("`data` does not have a 'time' dimension, and no custom times were provided via `time`.")
82
+
83
+ # If `data` is a GeoBox, use it directly; raise an error if no time was provided
84
+ elif isinstance(data, GeoBox):
85
+ gbox = data
86
+ if time is not None:
87
+ time = _standardise_time(time)
88
+ else:
89
+ raise ValueError("If `data` is a GeoBox, custom times must be provided via `time`.")
90
+
91
+ # Raise error if no valid inputs were provided
92
+ else:
93
+ raise TypeError("`data` must be an xarray.DataArray, xarray.Dataset, or odc.geo.geobox.GeoBox.")
94
+
95
+ return gbox, time
18
96
 
19
97
 
20
98
  def _pixel_tides_resample(
21
99
  tides_lowres,
22
- ds,
100
+ gbox,
23
101
  resample_method="bilinear",
24
- dask_chunks="auto",
102
+ dask_chunks=None,
25
103
  dask_compute=True,
26
104
  ):
27
105
  """Resamples low resolution tides modelled by `pixel_tides` into the
@@ -32,56 +110,39 @@ def _pixel_tides_resample(
32
110
  ----------
33
111
  tides_lowres : xarray.DataArray
34
112
  The low resolution tide modelling data array to be resampled.
35
- ds : xarray.Dataset
36
- The dataset whose geobox will be used as the template for the
37
- resampling operation. This is typically the same satellite
38
- dataset originally passed to `pixel_tides`.
113
+ gbox : GeoBox
114
+ The GeoBox to use as the template for the resampling operation.
115
+ This is typically comes from the same satellite dataset originally
116
+ passed to `pixel_tides` (e.g. `data.odc.geobox`).
39
117
  resample_method : string, optional
40
118
  The resampling method to use. Defaults to "bilinear"; valid
41
119
  options include "nearest", "cubic", "min", "max", "average" etc.
42
- dask_chunks : str or tuple, optional
120
+ dask_chunks : tuple of float, optional
43
121
  Can be used to configure custom Dask chunking for the final
44
- resampling step. The default of "auto" will automatically set
45
- x/y chunks to match those in `ds` if they exist, otherwise will
46
- set x/y chunks that cover the entire extent of the dataset.
47
- For custom chunks, provide a tuple in the form `(y, x)`, e.g.
48
- `(2048, 2048)`.
122
+ resampling step. For custom chunks, provide a tuple in the form
123
+ (y, x), e.g. (2048, 2048).
49
124
  dask_compute : bool, optional
50
125
  Whether to compute results of the resampling step using Dask.
51
- If False, this will return `tides_highres` as a Dask array.
126
+ If False, this will return `tides_highres` as a lazy loaded
127
+ Dask-enabled array.
52
128
 
53
129
  Returns
54
130
  -------
55
- tides_highres, tides_lowres : tuple of xr.DataArrays
56
- In addition to `tides_lowres` (see above), a high resolution
57
- array of tide heights will be generated matching the
58
- exact spatial resolution and extent of `ds`.
131
+ tides_highres : xr.DataArray
132
+ A high resolution array of tide heights matching the exact
133
+ spatial resolution and extent of `gbox`.
59
134
 
60
135
  """
61
136
  # Determine spatial dimensions
62
- y_dim, x_dim = ds.odc.spatial_dims
137
+ y_dim, x_dim = gbox.dimensions
63
138
 
64
139
  # Convert array to Dask, using no chunking along y and x dims,
65
140
  # and a single chunk for each timestep/quantile and tide model
66
141
  tides_lowres_dask = tides_lowres.chunk({d: None if d in [y_dim, x_dim] else 1 for d in tides_lowres.dims})
67
142
 
68
- # Automatically set Dask chunks for reprojection if set to "auto".
69
- # This will either use x/y chunks if they exist in `ds`, else
70
- # will cover the entire x and y dims) so we don't end up with
71
- # hundreds of tiny x and y chunks due to the small size of
72
- # `tides_lowres` (possible odc.geo bug?)
73
- if dask_chunks == "auto":
74
- if ds.chunks is not None:
75
- if (y_dim in ds.chunks) & (x_dim in ds.chunks):
76
- dask_chunks = (ds.chunks[y_dim], ds.chunks[x_dim])
77
- else:
78
- dask_chunks = ds.odc.geobox.shape
79
- else:
80
- dask_chunks = ds.odc.geobox.shape
81
-
82
- # Reproject into the GeoBox of `ds` using odc.geo and Dask
143
+ # Reproject into the pixel grid of `gbox` using odc.geo and Dask
83
144
  tides_highres = tides_lowres_dask.odc.reproject(
84
- how=ds.odc.geobox,
145
+ how=gbox,
85
146
  chunks=dask_chunks,
86
147
  resampling=resample_method,
87
148
  ).rename("tide_height")
@@ -94,7 +155,8 @@ def _pixel_tides_resample(
94
155
 
95
156
 
96
157
  def tag_tides(
97
- ds: xr.Dataset | xr.DataArray,
158
+ data: xr.Dataset | xr.DataArray | GeoBox,
159
+ time: DatetimeLike | None = None,
98
160
  model: str | list[str] = "EOT20",
99
161
  directory: str | os.PathLike | None = None,
100
162
  tidepost_lat: float | None = None,
@@ -104,7 +166,7 @@ def tag_tides(
104
166
  """
105
167
  Model tide heights for every timestep in a multi-dimensional
106
168
  dataset, and return a new `tide_height` array that can
107
- be used to "tag" each observation with tide data.
169
+ be used to "tag" each observation with tide heights.
108
170
 
109
171
  The function models tides at the centroid of the dataset
110
172
  by default, but a custom tidal modelling location can
@@ -122,15 +184,23 @@ def tag_tides(
122
184
 
123
185
  Parameters
124
186
  ----------
125
- ds : xarray.Dataset or xarray.DataArray
126
- A multi-dimensional dataset (e.g. "x", "y", "time") to
127
- tag with tide heights. This dataset must contain a "time"
128
- dimension.
187
+ data : xarray.Dataset or xarray.DataArray or odc.geo.geobox.GeoBox
188
+ A multi-dimensional dataset or GeoBox pixel grid that will
189
+ be used to define the tide modelling location. If `data`
190
+ is an xarray object, it should include a "time" dimension.
191
+ If no "time" dimension exists or if `data` is a GeoBox,
192
+ then times must be passed using the `time` parameter.
193
+ time : DatetimeLike, optional
194
+ By default, tides will be modelled using times from the
195
+ "time" dimension of `data`. Alternatively, this param can
196
+ be used to provide a custom set of times. Accepts any format
197
+ that can be converted by `pandas.to_datetime()`. For example:
198
+ `time=pd.date_range(start="2000", end="2001", freq="5h")`
129
199
  model : str or list of str, optional
130
- The tide model (or models) to use to model tides. If a list is
131
- provided, a new "tide_model" dimension will be added to `ds`.
132
- Defaults to "EOT20"; for a full list of available/supported
133
- models, run `eo_tides.model.list_models`.
200
+ The tide model (or models) used to model tides. If a list is
201
+ provided, a new "tide_model" dimension will be added to the
202
+ `xarray.DataArray` outputs. Defaults to "EOT20"; for a full
203
+ list of available/supported models, run `eo_tides.model.list_models`.
134
204
  directory : str, optional
135
205
  The directory containing tide model data files. If no path is
136
206
  provided, this will default to the environment variable
@@ -151,23 +221,18 @@ def tag_tides(
151
221
 
152
222
  Returns
153
223
  -------
154
- ds : xr.Dataset
155
- The original `xarray.Dataset` with a new `tide_height` variable
156
- giving the height of the tide (and optionally, its ebb-flow phase)
157
- for each timestep in the data.
158
-
224
+ tides_da : xr.DataArray
225
+ A one-dimensional tide height array. This will contain either
226
+ tide heights for every timestep in `data`, or for every time in
227
+ `times` if provided.
159
228
  """
160
- # Only datasets are supported
161
- if not isinstance(ds, xr.Dataset):
162
- raise TypeError("Input must be an xarray.Dataset, not an xarray.DataArray or other data type.")
163
-
164
- # Standardise model into a list for easy handling. and verify only one
229
+ # Standardise data inputs, time and models
230
+ gbox, time_coords = _standardise_inputs(data, time)
165
231
  model = [model] if isinstance(model, str) else model
166
232
 
167
- # If custom tide modelling locations are not provided, use the
168
- # dataset centroid
233
+ # If custom tide posts are not provided, use dataset centroid
169
234
  if tidepost_lat is None or tidepost_lon is None:
170
- lon, lat = ds.odc.geobox.geographic_extent.centroid.coords[0]
235
+ lon, lat = gbox.geographic_extent.centroid.coords[0]
171
236
  print(f"Setting tide modelling location from dataset centroid: {lon:.2f}, {lat:.2f}")
172
237
  else:
173
238
  lon, lat = tidepost_lon, tidepost_lat
@@ -177,7 +242,7 @@ def tag_tides(
177
242
  tide_df = model_tides(
178
243
  x=lon, # type: ignore
179
244
  y=lat, # type: ignore
180
- time=ds.time,
245
+ time=time_coords,
181
246
  model=model,
182
247
  directory=directory,
183
248
  crs="EPSG:4326",
@@ -195,43 +260,19 @@ def tag_tides(
195
260
  f"`tidepost_lat` and `tidepost_lon` parameters."
196
261
  )
197
262
 
198
- # # Optionally calculate the tide phase for each observation
199
- # if ebb_flow:
200
- # # Model tides for a time 15 minutes prior to each previously
201
- # # modelled satellite acquisition time. This allows us to compare
202
- # # tide heights to see if they are rising or falling.
203
- # print("Modelling tidal phase (e.g. ebb or flow)")
204
- # tide_pre_df = model_tides(
205
- # x=lon, # type: ignore
206
- # y=lat, # type: ignore
207
- # time=(ds.time - pd.Timedelta("15 min")),
208
- # model=model,
209
- # directory=directory,
210
- # crs="EPSG:4326",
211
- # **model_tides_kwargs,
212
- # )
213
-
214
- # # Compare tides computed for each timestep. If the previous tide
215
- # # was higher than the current tide, the tide is 'ebbing'. If the
216
- # # previous tide was lower, the tide is 'flowing'
217
- # tide_df["ebb_flow"] = (tide_df.tide_height < tide_pre_df.tide_height.values).replace({
218
- # True: "Ebb",
219
- # False: "Flow",
220
- # })
221
-
222
263
  # Convert to xarray format
223
- tide_xr = tide_df.reset_index().set_index(["time", "tide_model"]).drop(["x", "y"], axis=1).tide_height.to_xarray()
264
+ tides_da = tide_df.reset_index().set_index(["time", "tide_model"]).drop(["x", "y"], axis=1).tide_height.to_xarray()
224
265
 
225
266
  # If only one tidal model exists, squeeze out "tide_model" dim
226
- if len(tide_xr.tide_model) == 1:
227
- tide_xr = tide_xr.squeeze("tide_model")
267
+ if len(tides_da.tide_model) == 1:
268
+ tides_da = tides_da.squeeze("tide_model")
228
269
 
229
- return tide_xr
270
+ return tides_da
230
271
 
231
272
 
232
273
  def pixel_tides(
233
- ds: xr.Dataset | xr.DataArray,
234
- times=None,
274
+ data: xr.Dataset | xr.DataArray | GeoBox,
275
+ time: DatetimeLike | None = None,
235
276
  model: str | list[str] = "EOT20",
236
277
  directory: str | os.PathLike | None = None,
237
278
  resample: bool = True,
@@ -239,7 +280,7 @@ def pixel_tides(
239
280
  resolution: float | None = None,
240
281
  buffer: float | None = None,
241
282
  resample_method: str = "bilinear",
242
- dask_chunks: str | tuple[float, float] = "auto",
283
+ dask_chunks: tuple[float, float] | None = None,
243
284
  dask_compute: bool = True,
244
285
  **model_tides_kwargs,
245
286
  ) -> xr.DataArray:
@@ -250,10 +291,9 @@ def pixel_tides(
250
291
  This function models tides into a low-resolution tide
251
292
  modelling grid covering the spatial extent of the input
252
293
  data (buffered to reduce potential edge effects). These
253
- modelled tides are then (optionally) resampled back into
254
- the original higher resolution dataset's extent and
255
- resolution - resulting in a modelled tide height for every
256
- pixel through time.
294
+ modelled tides can then be resampled back into the original
295
+ higher resolution dataset's extent and resolution to
296
+ produce a modelled tide height for every pixel through time.
257
297
 
258
298
  This function uses the parallelised `model_tides` function
259
299
  under the hood. It supports all tidal models supported by
@@ -273,15 +313,18 @@ def pixel_tides(
273
313
 
274
314
  Parameters
275
315
  ----------
276
- ds : xarray.Dataset or xarray.DataArray
277
- A multi-dimensional dataset (e.g. "x", "y", "time") that will
278
- be used to define the tide modelling grid.
279
- times : pd.DatetimeIndex or list of pd.Timestamp, optional
280
- By default, the function will model tides using the times
281
- contained in the `time` dimension of `ds`. Alternatively, this
282
- param can be used to model tides for a custom set of times
283
- instead. For example:
284
- `times=pd.date_range(start="2000", end="2001", freq="5h")`
316
+ data : xarray.Dataset or xarray.DataArray or odc.geo.geobox.GeoBox
317
+ A multi-dimensional dataset or GeoBox pixel grid that will
318
+ be used to define the spatial tide modelling grid. If `data`
319
+ is an xarray object, it should include a "time" dimension.
320
+ If no "time" dimension exists or if `data` is a GeoBox,
321
+ then times must be passed using the `time` parameter.
322
+ time : DatetimeLike, optional
323
+ By default, tides will be modelled using times from the
324
+ "time" dimension of `data`. Alternatively, this param can
325
+ be used to provide a custom set of times. Accepts any format
326
+ that can be converted by `pandas.to_datetime()`. For example:
327
+ `time=pd.date_range(start="2000", end="2001", freq="5h")`
285
328
  model : str or list of str, optional
286
329
  The tide model (or models) used to model tides. If a list is
287
330
  provided, a new "tide_model" dimension will be added to the
@@ -295,7 +338,7 @@ def pixel_tides(
295
338
  model that match the structure required by `pyTMD`
296
339
  (<https://geoscienceaustralia.github.io/eo-tides/setup/>).
297
340
  resample : bool, optional
298
- Whether to resample low resolution tides back into `ds`'s original
341
+ Whether to resample low resolution tides back into `data`'s original
299
342
  higher resolution grid. Set this to `False` if you do not want
300
343
  low resolution tides to be re-projected back to higher resolution.
301
344
  calculate_quantiles : tuple of float or numpy.ndarray, optional
@@ -307,38 +350,38 @@ def pixel_tides(
307
350
  resolution : float, optional
308
351
  The desired resolution of the low-resolution grid used for tide
309
352
  modelling. The default None will create a 5000 m resolution grid
310
- if `ds` has a projected CRS (i.e. metre units), or a 0.05 degree
311
- resolution grid if `ds` has a geographic CRS (e.g. degree units).
353
+ if `data` has a projected CRS (i.e. metre units), or a 0.05 degree
354
+ resolution grid if `data` has a geographic CRS (e.g. degree units).
312
355
  Note: higher resolutions do not necessarily provide better
313
356
  tide modelling performance, as results will be limited by the
314
357
  resolution of the underlying global tide model (e.g. 1/16th
315
358
  degree / ~5 km resolution grid for FES2014).
316
359
  buffer : float, optional
317
360
  The amount by which to buffer the higher resolution grid extent
318
- when creating the new low resolution grid. This buffering is
319
- important as it ensures that ensure pixel-based tides are seamless
320
- across dataset boundaries. This buffer will eventually be clipped
321
- away when the low-resolution data is re-projected back to the
322
- resolution and extent of the higher resolution dataset. To
323
- ensure that at least two pixels occur outside of the dataset
324
- bounds, the default None applies a 12000 m buffer if `ds` has a
361
+ when creating the new low resolution grid. This buffering
362
+ ensures that modelled tides are seamless across analysis
363
+ boundaries. This buffer is eventually be clipped away when
364
+ the low-resolution modelled tides are re-projected back to the
365
+ original resolution and extent of `data`. To ensure that at least
366
+ two low-resolution grid pixels occur outside of the dataset
367
+ bounds, the default None applies a 12000 m buffer if `data` has a
325
368
  projected CRS (i.e. metre units), or a 0.12 degree buffer if
326
- `ds` has a geographic CRS (e.g. degree units).
369
+ `data` has a geographic CRS (e.g. degree units).
327
370
  resample_method : str, optional
328
371
  If resampling is requested (see `resample` above), use this
329
372
  resampling method when converting from low resolution to high
330
373
  resolution pixels. Defaults to "bilinear"; valid options include
331
374
  "nearest", "cubic", "min", "max", "average" etc.
332
- dask_chunks : str or tuple of float, optional
375
+ dask_chunks : tuple of float, optional
333
376
  Can be used to configure custom Dask chunking for the final
334
- resampling step. The default of "auto" will automatically set
335
- x/y chunks to match those in `ds` if they exist, otherwise will
336
- set x/y chunks that cover the entire extent of the dataset.
377
+ resampling step. By default, chunks will be automatically set
378
+ to match y/x chunks from `data` if they exist; otherwise chunks
379
+ will be chosen to cover the entire y/x extent of the dataset.
337
380
  For custom chunks, provide a tuple in the form `(y, x)`, e.g.
338
381
  `(2048, 2048)`.
339
382
  dask_compute : bool, optional
340
383
  Whether to compute results of the resampling step using Dask.
341
- If False, this will return `tides_highres` as a Dask array.
384
+ If False, `tides_highres` will be returned as a Dask array.
342
385
  **model_tides_kwargs :
343
386
  Optional parameters passed to the `eo_tides.model.model_tides`
344
387
  function. Important parameters include `cutoff` (used to
@@ -348,54 +391,34 @@ def pixel_tides(
348
391
  Returns
349
392
  -------
350
393
  tides_da : xr.DataArray
351
- If `resample=True` (default), a high-resolution array
352
- of tide heights matching the exact spatial resolution and
353
- extents of `ds`. This will contain either tide heights every
354
- timestep in `ds` (if `times` is None), tide heights at every
355
- time in `times` (if `times` is not None), or tide height
394
+ A three-dimensional tide height array.
395
+ If `resample=True` (default), a high-resolution array of tide
396
+ heights will be returned that matches the exact spatial resolution
397
+ and extents of `data`. This will contain either tide heights for
398
+ every timestep in `data` (or in `times` if provided), or tide height
356
399
  quantiles for every quantile provided by `calculate_quantiles`.
357
400
  If `resample=False`, results for the intermediate low-resolution
358
401
  tide modelling grid will be returned instead.
359
402
  """
360
- # First test if no time dimension and nothing passed to `times`
361
- if ("time" not in ds.dims) & (times is None):
362
- raise ValueError(
363
- "`ds` does not contain a 'time' dimension. Times are required "
364
- "for modelling tides: please pass in a set of custom tides "
365
- "using the `times` parameter. For example: "
366
- "`times=pd.date_range(start='2000', end='2001', freq='5h')`",
367
- )
368
-
369
- # If custom times are provided, convert them to a consistent
370
- # pandas.DatatimeIndex format
371
- if times is not None:
372
- if isinstance(times, list):
373
- time_coords = pd.DatetimeIndex(times)
374
- elif isinstance(times, pd.Timestamp):
375
- time_coords = pd.DatetimeIndex([times])
376
- else:
377
- time_coords = times
378
-
379
- # Otherwise, use times from `ds` directly
380
- else:
381
- time_coords = ds.coords["time"]
382
-
383
- # Standardise model into a list for easy handling
403
+ # Standardise data inputs, time and models
404
+ gbox, time_coords = _standardise_inputs(data, time)
405
+ dask_chunks = _resample_chunks(data, dask_chunks)
384
406
  model = [model] if isinstance(model, str) else model
385
407
 
386
408
  # Determine spatial dimensions
387
- y_dim, x_dim = ds.odc.spatial_dims
409
+ y_dim, x_dim = gbox.dimensions
388
410
 
389
411
  # Determine resolution and buffer, using different defaults for
390
412
  # geographic (i.e. degrees) and projected (i.e. metres) CRSs:
391
- crs_units = ds.odc.geobox.crs.units[0][0:6]
392
- if ds.odc.geobox.crs.geographic:
413
+ assert gbox.crs is not None
414
+ crs_units = gbox.crs.units[0][0:6]
415
+ if gbox.crs.geographic:
393
416
  if resolution is None:
394
417
  resolution = 0.05
395
418
  elif resolution > 360:
396
419
  raise ValueError(
397
420
  f"A resolution of greater than 360 was "
398
- f"provided, but `ds` has a geographic CRS "
421
+ f"provided, but `data` has a geographic CRS "
399
422
  f"in {crs_units} units. Did you accidently "
400
423
  f"provide a resolution in projected "
401
424
  f"(i.e. metre) units?",
@@ -408,7 +431,7 @@ def pixel_tides(
408
431
  elif resolution < 1:
409
432
  raise ValueError(
410
433
  f"A resolution of less than 1 was provided, "
411
- f"but `ds` has a projected CRS in "
434
+ f"but `data` has a projected CRS in "
412
435
  f"{crs_units} units. Did you accidently "
413
436
  f"provide a resolution in geographic "
414
437
  f"(degree) units?",
@@ -417,12 +440,12 @@ def pixel_tides(
417
440
  buffer = 12000
418
441
 
419
442
  # Raise error if resolution is less than dataset resolution
420
- dataset_res = ds.odc.geobox.resolution.x
443
+ dataset_res = gbox.resolution.x
421
444
  if resolution < dataset_res:
422
445
  raise ValueError(
423
446
  f"The resolution of the low-resolution tide "
424
447
  f"modelling grid ({resolution:.2f}) is less "
425
- f"than `ds`'s pixel resolution ({dataset_res:.2f}). "
448
+ f"than `data`'s pixel resolution ({dataset_res:.2f}). "
426
449
  f"This can cause extremely slow tide modelling "
427
450
  f"performance. Please select provide a resolution "
428
451
  f"greater than {dataset_res:.2f} using "
@@ -432,20 +455,20 @@ def pixel_tides(
432
455
  # Create a new reduced resolution tide modelling grid after
433
456
  # first buffering the grid
434
457
  print(f"Creating reduced resolution {resolution} x {resolution} {crs_units} tide modelling array")
435
- buffered_geobox = ds.odc.geobox.buffered(buffer)
458
+ buffered_geobox = gbox.buffered(buffer)
436
459
  rescaled_geobox = GeoBox.from_bbox(bbox=buffered_geobox.boundingbox, resolution=resolution)
437
460
  rescaled_ds = odc.geo.xr.xr_zeros(rescaled_geobox)
438
461
 
439
462
  # Flatten grid to 1D, then add time dimension
440
463
  flattened_ds = rescaled_ds.stack(z=(x_dim, y_dim))
441
- flattened_ds = flattened_ds.expand_dims(dim={"time": time_coords.values})
464
+ flattened_ds = flattened_ds.expand_dims(dim={"time": time_coords})
442
465
 
443
466
  # Model tides in parallel, returning a pandas.DataFrame
444
467
  tide_df = model_tides(
445
468
  x=flattened_ds[x_dim],
446
469
  y=flattened_ds[y_dim],
447
470
  time=flattened_ds.time,
448
- crs=f"EPSG:{ds.odc.geobox.crs.epsg}",
471
+ crs=f"EPSG:{gbox.crs.epsg}",
449
472
  model=model,
450
473
  directory=directory,
451
474
  **model_tides_kwargs,
@@ -480,14 +503,14 @@ def pixel_tides(
480
503
  tides_lowres = tides_lowres.squeeze("tide_model")
481
504
 
482
505
  # Ensure CRS is present before we apply any resampling
483
- tides_lowres = tides_lowres.odc.assign_crs(ds.odc.geobox.crs)
506
+ tides_lowres = tides_lowres.odc.assign_crs(gbox.crs)
484
507
 
485
508
  # Reproject into original high resolution grid
486
509
  if resample:
487
510
  print("Reprojecting tides into original resolution")
488
511
  tides_highres = _pixel_tides_resample(
489
512
  tides_lowres,
490
- ds,
513
+ gbox,
491
514
  resample_method,
492
515
  dask_chunks,
493
516
  dask_compute,