1919
2020from emsarray import utils
2121from emsarray .compat .shapely import SpatialIndex
22+ from emsarray .exceptions import NoSuchCoordinateError
2223from emsarray .operations import depth
2324from emsarray .plot import (
2425 _requires_plot , animate_on_figure , plot_on_figure , polygons_to_collection
@@ -282,22 +283,122 @@ def _get_data_array(self, data_array: DataArrayOrName) -> xr.DataArray:
282283 else :
283284 return self .dataset [data_array ]
284285
286+ @cached_property
287+ def time_coordinate (self ) -> xr .DataArray :
288+ """The time coordinate for this dataset.
289+
290+ Returns
291+ -------
292+ xarray.DataArray
293+ The variable for the time coordinate for this dataset.
294+
295+ Raises
296+ ------
297+ exceptions.NoSuchCoordinateError
298+ If no time coordinate was found
299+
300+ See Also
301+ --------
302+ get_time_name
303+ """
304+ return self .dataset [self .get_time_name ()]
305+
306+ @cached_property
307+ def depth_coordinate (self ) -> xr .DataArray :
308+ """The depth coordinate for this dataset.
309+
310+ Returns
311+ -------
312+ xarray.DataArray
313+ The variable for the depth coordinate for this dataset.
314+
315+ Raises
316+ ------
317+ exceptions.NoSuchCoordinateError
318+ If no depth coordinate was found
319+
320+ See Also
321+ --------
322+ get_depth_name
323+ """
324+ return self .dataset [self .get_depth_name ()]
325+
285326 def get_time_name (self ) -> Hashable :
286- """Get the name of the time variable in this dataset."""
327+ """Get the name of the time variable in this dataset.
328+
329+ Returns
330+ -------
331+ Hashable
332+ The name of the time coordinate.
333+
334+ Raises
335+ ------
336+ exceptions.NoSuchCoordinateError
337+ If no time coordinate was found
338+
339+ Notes
340+ -----
341+ The CF Conventions state that
342+ a time variable is defined by having a `units` attribute
343+ formatted according to the UDUNITS package [1]_.
344+
345+ xarray will find all time variables and convert them to numpy datetimes.
346+
347+ References
348+ ----------
349+ .. [1] `CF Conventions v1.10, 4.4 Time Coordinate <https://cfconventions.org/Data/cf-conventions/cf-conventions-1.10/cf-conventions.html#time-coordinate>`_
350+ """
287351 for name , variable in self .dataset .variables .items ():
288- if variable .attrs .get ('standard_name' ) == 'time' :
289- return name
290- raise KeyError ("Dataset does not have a time dimension" )
352+ # xarray will automatically decode all time variables
353+ # and move the 'units' attribute over to encoding to store this change.
354+ if 'units' in variable .encoding :
355+ units = variable .encoding ['units' ]
356+ # A time variable must have units of the form '<units> since <epoc>'
357+ if 'since' in units :
358+ # The variable must now be a numpy datetime
359+ if variable .dtype .type == np .datetime64 :
360+ return name
361+ raise NoSuchCoordinateError ("Could not find time coordinate in dataset" )
291362
292363 def get_depth_name (self ) -> Hashable :
293364 """Get the name of the layer depth coordinate variable.
365+
294366 For datasets with multiple depth variables, this should be the one that
295367 represents the centre of the layer, not the bounds.
296368
297369 Note that this is the name of the coordinate variable,
298370 not the name of the dimension, for datasets where these differ.
371+
372+ Returns
373+ -------
374+ Hashable
375+ The name of the depth coordinate.
376+
377+ Raises
378+ ------
379+ exceptions.NoSuchCoordinateError
380+ If no time coordinate was found
381+
382+ Notes
383+ -----
384+ The CF Conventions state that
385+ a depth variable is identifiable by units of pressure; or
386+ the presence of the ``positive`` attribute with value of ``up`` or ``down``
387+ [2]_.
388+
389+ In practice, many datasets do not follow this convention.
390+ In addition to checking for the ``positive`` attribute,
391+ all coordinates are checked for a ``standard_name: "depth"``,
392+ ``coordinate_type: "Z"``, or ``axiz: "Z"``.
393+
394+ References
395+ ----------
396+ .. [2] `CF Conventions v1.10, 4.3 Vertical (Height or Depth) Coordinate <https://cfconventions.org/Data/cf-conventions/cf-conventions-1.10/cf-conventions.html#vertical-coordinate>`_
299397 """
300- return self .get_all_depth_names ()[0 ]
398+ try :
399+ return self .get_all_depth_names ()[0 ]
400+ except IndexError :
401+ raise NoSuchCoordinateError ("Could not find depth coordinate in dataset" )
301402
302403 def get_all_depth_names (self ) -> List [Hashable ]:
303404 """Get the names of all depth layers.
@@ -312,7 +413,8 @@ def get_all_depth_names(self) -> List[Hashable]:
312413 data_array = self .dataset [name ]
313414
314415 if not (
315- data_array .attrs .get ('axis' ) == 'Z'
416+ data_array .attrs .get ('positive' , '' ).lower () in {'up' , 'down' }
417+ or data_array .attrs .get ('axis' ) == 'Z'
316418 or data_array .attrs .get ('cartesian_axis' ) == 'Z'
317419 or data_array .attrs .get ('coordinate_type' ) == 'Z'
318420 or data_array .attrs .get ('standard_name' ) == 'depth'
@@ -333,27 +435,49 @@ def get_all_depth_names(self) -> List[Hashable]:
333435
334436 return depth_names
335437
438+ @utils .deprecated (
439+ (
440+ "Convention.get_depths() is deprecated. "
441+ "Use Convention.depth_coordinate.values instead."
442+ ),
443+ DeprecationWarning ,
444+ )
336445 def get_depths (self ) -> np .ndarray :
337446 """Get the depth of each vertical layer in this dataset.
338447
448+ .. deprecated:: 0.5.0
449+ This method is replaced by
450+ :attr:`Convention.depth_coordinate.values <Convention.depth_coordinate>`.
451+
339452 Returns
340453 -------
341454 :class:`numpy.ndarray`
342455 An array of depths, one per vertical layer in the dataset.
343456 """
344- return cast (np .ndarray , self .dataset .variables [self .get_depth_name ()].values )
345-
457+ return cast (np .ndarray , self .depth_coordinate .values )
458+
459+ @utils .deprecated (
460+ (
461+ "Convention.get_times() is deprecated. "
462+ "Use Convention.time_coordinate.values instead."
463+ ),
464+ DeprecationWarning ,
465+ )
346466 def get_times (self ) -> np .ndarray :
347467 """Get all timesteps in this dataset.
348468
469+ .. deprecated:: 0.5.0
470+ This method is replaced by
471+ :attr:`Convention.time_coordinate.values <Convention.time_coordinate>`.
472+
349473 Returns
350474 -------
351475 :class:`numpy.ndarray`
352476 An array of datetimes.
353477 The datetimes will be whatever native format the dataset uses,
354478 likely :class:`numpy.datetime64`.
355479 """
356- return cast (np .ndarray , self .dataset . variables [ self . get_time_name ()] .values )
480+ return cast (np .ndarray , self .time_coordinate .values )
357481
358482 @abc .abstractmethod
359483 def ravel_index (self , index : Index ) -> int :
0 commit comments