pyTMD 3.0.2__tar.gz → 3.0.3__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.
Files changed (56) hide show
  1. {pytmd-3.0.2/pyTMD.egg-info → pytmd-3.0.3}/PKG-INFO +2 -2
  2. {pytmd-3.0.2 → pytmd-3.0.3}/pyTMD/datasets/reduce_otis.py +7 -7
  3. {pytmd-3.0.2 → pytmd-3.0.3}/pyTMD/io/ATLAS.py +15 -9
  4. {pytmd-3.0.2 → pytmd-3.0.3}/pyTMD/io/FES.py +9 -7
  5. {pytmd-3.0.2 → pytmd-3.0.3}/pyTMD/io/GOT.py +16 -5
  6. {pytmd-3.0.2 → pytmd-3.0.3}/pyTMD/io/OTIS.py +20 -15
  7. {pytmd-3.0.2 → pytmd-3.0.3}/pyTMD/io/dataset.py +45 -2
  8. {pytmd-3.0.2 → pytmd-3.0.3}/pyTMD/io/model.py +38 -8
  9. {pytmd-3.0.2 → pytmd-3.0.3/pyTMD.egg-info}/PKG-INFO +2 -2
  10. {pytmd-3.0.2 → pytmd-3.0.3}/pyTMD.egg-info/requires.txt +1 -1
  11. {pytmd-3.0.2 → pytmd-3.0.3}/pyproject.toml +3 -3
  12. {pytmd-3.0.2 → pytmd-3.0.3}/LICENSE +0 -0
  13. {pytmd-3.0.2 → pytmd-3.0.3}/MANIFEST.in +0 -0
  14. {pytmd-3.0.2 → pytmd-3.0.3}/README.md +0 -0
  15. {pytmd-3.0.2 → pytmd-3.0.3}/pyTMD/__init__.py +0 -0
  16. {pytmd-3.0.2 → pytmd-3.0.3}/pyTMD/astro.py +0 -0
  17. {pytmd-3.0.2 → pytmd-3.0.3}/pyTMD/compute.py +0 -0
  18. {pytmd-3.0.2 → pytmd-3.0.3}/pyTMD/constituents.py +0 -0
  19. {pytmd-3.0.2 → pytmd-3.0.3}/pyTMD/data/ct1971_tab6.txt +0 -0
  20. {pytmd-3.0.2 → pytmd-3.0.3}/pyTMD/data/cte1973_tab.txt +0 -0
  21. {pytmd-3.0.2 → pytmd-3.0.3}/pyTMD/data/d1921_tab.txt +0 -0
  22. {pytmd-3.0.2 → pytmd-3.0.3}/pyTMD/data/database.json +0 -0
  23. {pytmd-3.0.2 → pytmd-3.0.3}/pyTMD/data/doodson.json +0 -0
  24. {pytmd-3.0.2 → pytmd-3.0.3}/pyTMD/data/hw1995_tab.txt +0 -0
  25. {pytmd-3.0.2 → pytmd-3.0.3}/pyTMD/data/re14_tab3.txt +0 -0
  26. {pytmd-3.0.2 → pytmd-3.0.3}/pyTMD/data/t1987_tab.txt +0 -0
  27. {pytmd-3.0.2 → pytmd-3.0.3}/pyTMD/data/tab5.2e.txt +0 -0
  28. {pytmd-3.0.2 → pytmd-3.0.3}/pyTMD/data/tab5.3a.txt +0 -0
  29. {pytmd-3.0.2 → pytmd-3.0.3}/pyTMD/data/tab5.3b.txt +0 -0
  30. {pytmd-3.0.2 → pytmd-3.0.3}/pyTMD/data/w1990_tab.txt +0 -0
  31. {pytmd-3.0.2 → pytmd-3.0.3}/pyTMD/datasets/__init__.py +0 -0
  32. {pytmd-3.0.2 → pytmd-3.0.3}/pyTMD/datasets/fetch_arcticdata.py +0 -0
  33. {pytmd-3.0.2 → pytmd-3.0.3}/pyTMD/datasets/fetch_aviso_fes.py +0 -0
  34. {pytmd-3.0.2 → pytmd-3.0.3}/pyTMD/datasets/fetch_box_tpxo.py +0 -0
  35. {pytmd-3.0.2 → pytmd-3.0.3}/pyTMD/datasets/fetch_gsfc_got.py +0 -0
  36. {pytmd-3.0.2 → pytmd-3.0.3}/pyTMD/datasets/fetch_iers_opole.py +0 -0
  37. {pytmd-3.0.2 → pytmd-3.0.3}/pyTMD/datasets/fetch_jpl_ssd.py +0 -0
  38. {pytmd-3.0.2 → pytmd-3.0.3}/pyTMD/datasets/fetch_test_data.py +0 -0
  39. {pytmd-3.0.2 → pytmd-3.0.3}/pyTMD/ellipse.py +0 -0
  40. {pytmd-3.0.2 → pytmd-3.0.3}/pyTMD/interpolate.py +0 -0
  41. {pytmd-3.0.2 → pytmd-3.0.3}/pyTMD/io/IERS.py +0 -0
  42. {pytmd-3.0.2 → pytmd-3.0.3}/pyTMD/io/NOAA.py +0 -0
  43. {pytmd-3.0.2 → pytmd-3.0.3}/pyTMD/io/__init__.py +0 -0
  44. {pytmd-3.0.2 → pytmd-3.0.3}/pyTMD/math.py +0 -0
  45. {pytmd-3.0.2 → pytmd-3.0.3}/pyTMD/predict.py +0 -0
  46. {pytmd-3.0.2 → pytmd-3.0.3}/pyTMD/solve/__init__.py +0 -0
  47. {pytmd-3.0.2 → pytmd-3.0.3}/pyTMD/solve/constants.py +0 -0
  48. {pytmd-3.0.2 → pytmd-3.0.3}/pyTMD/spatial.py +0 -0
  49. {pytmd-3.0.2 → pytmd-3.0.3}/pyTMD/tools.py +0 -0
  50. {pytmd-3.0.2 → pytmd-3.0.3}/pyTMD/utilities.py +0 -0
  51. {pytmd-3.0.2 → pytmd-3.0.3}/pyTMD/version.py +0 -0
  52. {pytmd-3.0.2 → pytmd-3.0.3}/pyTMD.egg-info/SOURCES.txt +0 -0
  53. {pytmd-3.0.2 → pytmd-3.0.3}/pyTMD.egg-info/dependency_links.txt +0 -0
  54. {pytmd-3.0.2 → pytmd-3.0.3}/pyTMD.egg-info/entry_points.txt +0 -0
  55. {pytmd-3.0.2 → pytmd-3.0.3}/pyTMD.egg-info/top_level.txt +0 -0
  56. {pytmd-3.0.2 → pytmd-3.0.3}/setup.cfg +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: pyTMD
3
- Version: 3.0.2
3
+ Version: 3.0.3
4
4
  Summary: Python-based tidal prediction software for estimating ocean, load, solid Earth and pole tides
5
5
  Author: Tyler Sutterley
6
6
  Author-email: tsutterl@uw.edu
@@ -58,7 +58,7 @@ Requires-Dist: scipy>=1.10.1
58
58
  Requires-Dist: timescale>=0.0.8
59
59
  Requires-Dist: xarray
60
60
  Provides-Extra: doc
61
- Requires-Dist: docutils; extra == "doc"
61
+ Requires-Dist: docutils>=0.17; extra == "doc"
62
62
  Requires-Dist: graphviz; extra == "doc"
63
63
  Requires-Dist: ipympl; extra == "doc"
64
64
  Requires-Dist: myst-nb; extra == "doc"
@@ -114,10 +114,10 @@ def reduce_otis(
114
114
  m["u"].model_file
115
115
  )
116
116
  # combine local solutions with global solution
117
- dsg = dsg.compact.combine_local(dtg)
118
- dsz = dsz.compact.combine_local(dtz)
119
- dsu = dsu.compact.combine_local(dtu)
120
- dsv = dsv.compact.combine_local(dtv)
117
+ dsg = dsg.tmd.compact.combine_local(dtg)
118
+ dsz = dsz.tmd.compact.combine_local(dtz)
119
+ dsu = dsu.tmd.compact.combine_local(dtu)
120
+ dsv = dsv.tmd.compact.combine_local(dtv)
121
121
  else:
122
122
  # if reading a pure global solution
123
123
  dsg = pyTMD.io.OTIS.open_otis_grid(m["z"].grid_file)
@@ -140,9 +140,9 @@ def reduce_otis(
140
140
  new_elevation_file = _unique_filename(m["z"].model_file)
141
141
  new_transport_file = _unique_filename(m["u"].model_file)
142
142
  # output reduced datasets to file
143
- dtree.otis.to_grid(new_grid_file)
144
- dtree.otis.to_elevation(new_elevation_file)
145
- dtree.otis.to_transport(new_transport_file)
143
+ dtree.tmd.otis.to_grid(new_grid_file)
144
+ dtree.tmd.otis.to_elevation(new_elevation_file)
145
+ dtree.tmd.otis.to_transport(new_transport_file)
146
146
  # change the permissions level to mode
147
147
  new_grid_file.chmod(mode=mode)
148
148
  new_elevation_file.chmod(mode=mode)
@@ -1,7 +1,7 @@
1
1
  #!/usr/bin/env python
2
2
  """
3
3
  ATLAS.py
4
- Written by Tyler Sutterley (12/2025)
4
+ Written by Tyler Sutterley (02/2026)
5
5
 
6
6
  Reads netCDF4 ATLAS tidal solutions provided by Oregon State University
7
7
 
@@ -10,6 +10,8 @@ PYTHON DEPENDENCIES:
10
10
  https://docs.xarray.dev/en/stable/
11
11
 
12
12
  UPDATE HISTORY:
13
+ Updated 02/2026: make dataset and datatree accessors for ATLAS
14
+ be subaccessors from dataset module
13
15
  Updated 12/2025: no longer subclassing pathlib.Path for working directories
14
16
  added option to change the output datatype when writing netCDF files
15
17
  Updated 11/2025: near-complete rewrite of program to use xarray
@@ -63,6 +65,10 @@ import datetime
63
65
  import xarray as xr
64
66
  import pyTMD.version
65
67
  import pyTMD.utilities
68
+ from .dataset import (
69
+ register_dataset_subaccessor,
70
+ register_datatree_subaccessor,
71
+ )
66
72
 
67
73
  # attempt imports
68
74
  dask = pyTMD.utilities.import_dependency("dask")
@@ -295,11 +301,10 @@ def open_atlas_dataset(
295
301
 
296
302
 
297
303
  # PURPOSE: ATLAS-netcdf utilities for xarray Datasets
298
- @xr.register_dataset_accessor("atlas")
304
+ @register_dataset_subaccessor("atlas")
299
305
  class ATLASDataset:
300
306
  """
301
- Accessor for extending an ``xarray.Dataset`` for ATLAS-netcdf
302
- tidal models
307
+ ``xarray.Dataset`` utilities for ATLAS-netcdf tidal models
303
308
  """
304
309
 
305
310
  def __init__(self, ds):
@@ -462,11 +467,10 @@ class ATLASDataset:
462
467
 
463
468
 
464
469
  # PURPOSE: ATLAS-netcdf utilities for xarray DataTrees
465
- @xr.register_datatree_accessor("atlas")
470
+ @register_datatree_subaccessor("atlas")
466
471
  class ATLASDataTree:
467
472
  """
468
- Accessor for extending an ``xarray.DataTree`` for ATLAS-netcdf
469
- tidal models
473
+ ``xarray.DataTree`` utilities for ATLAS-netcdf tidal models
470
474
  """
471
475
 
472
476
  def __init__(self, dtree):
@@ -500,6 +504,8 @@ class ATLASDataTree:
500
504
  ds = self._dtree[group].to_dataset()
501
505
  # write in append mode to add group to same grid and directory
502
506
  # output grid file
503
- ds.atlasnc.to_grid(grid_file, group=group, mode="a", **kwargs)
507
+ ATLASDataset(ds).to_grid(grid_file, group=group, mode="a", **kwargs)
504
508
  # output constituent files
505
- ds.atlasnc.to_netcdf(directory, group=group, mode="a", **kwargs)
509
+ ATLASDataset(ds).to_netcdf(
510
+ directory, group=group, mode="a", **kwargs
511
+ )
@@ -1,7 +1,7 @@
1
1
  #!/usr/bin/env python
2
2
  """
3
3
  FES.py
4
- Written by Tyler Sutterley (12/2025)
4
+ Written by Tyler Sutterley (02/2026)
5
5
 
6
6
  Reads ascii and netCDF4 files for FES tidal solutions provided by AVISO
7
7
  https://www.aviso.altimetry.fr/data/products/auxiliary-products/
@@ -15,6 +15,7 @@ PYTHON DEPENDENCIES:
15
15
  https://docs.xarray.dev/en/stable/
16
16
 
17
17
  UPDATE HISTORY:
18
+ Updated 02/2026: make dataset accessor for FES be a subaccessor from dataset
18
19
  Updated 12/2025: no longer subclassing pathlib.Path for working directories
19
20
  Updated 11/2025: near-complete rewrite of program to use xarray
20
21
  Updated 10/2025: simplify ascii read function to use masked_equal
@@ -71,6 +72,7 @@ import numpy as np
71
72
  import xarray as xr
72
73
  import pyTMD.constituents
73
74
  import pyTMD.utilities
75
+ from .dataset import register_dataset_subaccessor
74
76
 
75
77
  # attempt imports
76
78
  dask = pyTMD.utilities.import_dependency("dask")
@@ -311,14 +313,14 @@ def open_fes_netcdf(
311
313
  if "Ha" in tmp.variables:
312
314
  # FES2012 variable names
313
315
  mapping_coords = dict(lon="x", lat="y")
314
- mapping_amp = dict(z="Ha", u="Ua", v="Va")
315
- mapping_ph = dict(z="Hg", u="Ug", v="Vg")
316
- elif "amplitude" in tmp.variables:
316
+ mapping_amp = dict(z="Ha")
317
+ mapping_ph = dict(z="Hg")
318
+ elif any([v in tmp.variables for v in ["amplitude", "Ua", "Va"]]):
317
319
  # FES2014/2022 variable names
318
320
  mapping_coords = dict(lon="x", lat="y")
319
321
  mapping_amp = dict(z="amplitude", u="Ua", v="Va")
320
322
  mapping_ph = dict(z="phase", u="Ug", v="Vg")
321
- elif "AMPL" in tmp.variables:
323
+ elif any([v in tmp.variables for v in ["AMPL", "UAMP", "VAMP"]]):
322
324
  # HAMTIDE11 variable names
323
325
  mapping_coords = dict(LON="x", LAT="y")
324
326
  mapping_amp = dict(z="AMPL", u="UAMP", v="VAMP")
@@ -344,9 +346,9 @@ def open_fes_netcdf(
344
346
 
345
347
 
346
348
  # PURPOSE: FES utilities for xarray Datasets
347
- @xr.register_dataset_accessor("fes")
349
+ @register_dataset_subaccessor("fes")
348
350
  class FESDataset:
349
- """Accessor for extending an ``xarray.Dataset`` for FES tidal models"""
351
+ """``xarray.Dataset`` utilities for FES tidal models"""
350
352
 
351
353
  def __init__(self, ds):
352
354
  self._ds = ds
@@ -1,7 +1,7 @@
1
1
  #!/usr/bin/env python
2
2
  """
3
3
  GOT.py
4
- Written by Tyler Sutterley (12/2025)
4
+ Written by Tyler Sutterley (02/2026)
5
5
 
6
6
  Reads ascii and netCDF4 files from Richard Ray's Goddard Ocean Tide (GOT) model
7
7
  https://earth.gsfc.nasa.gov/geo/data/ocean-tide-models
@@ -14,6 +14,8 @@ PYTHON DEPENDENCIES:
14
14
  https://docs.xarray.dev/en/stable/
15
15
 
16
16
  UPDATE HISTORY:
17
+ Updated 02/2026: make dataset accessor for GOT be a subaccessor from dataset
18
+ some models have units in the second line of the header text
17
19
  Updated 12/2025: no longer subclassing pathlib.Path for working directories
18
20
  added function to write to output GOT-formatted ascii files
19
21
  fixed writing of output constituents to match GOT attribute format
@@ -74,6 +76,7 @@ import xarray as xr
74
76
  import pyTMD.version
75
77
  import pyTMD.constituents
76
78
  import pyTMD.utilities
79
+ from .dataset import register_dataset_subaccessor
77
80
 
78
81
  # attempt imports
79
82
  dask = pyTMD.utilities.import_dependency("dask")
@@ -208,8 +211,16 @@ def open_got_ascii(
208
211
  # parse header text
209
212
  # constituent identifier
210
213
  cons = pyTMD.constituents._parse_name(file_contents[0])
211
- # get units
212
- units = re.findall(r"\((\w+m)\)", file_contents[0], re.IGNORECASE)
214
+ # get units from header if available
215
+ rx = re.compile(r"\((\w+m)\)", re.IGNORECASE)
216
+ # GOT headers from Richard Ray have units on the first line
217
+ # some other models have units on the second line
218
+ if rx.search(file_contents[0]):
219
+ units = rx.findall(file_contents[0], re.IGNORECASE)
220
+ elif rx.search(file_contents[1]):
221
+ units = rx.findall(file_contents[1], re.IGNORECASE)
222
+ else:
223
+ units = None
213
224
  # grid dimensions
214
225
  nlat, nlon = np.array(file_contents[2].split(), dtype=int)
215
226
  # longitude range
@@ -331,9 +342,9 @@ def open_got_netcdf(
331
342
 
332
343
 
333
344
  # PURPOSE: GOT utilities for xarray Datasets
334
- @xr.register_dataset_accessor("got")
345
+ @register_dataset_subaccessor("got")
335
346
  class GOTDataset:
336
- """Accessor for extending an ``xarray.Dataset`` for GOT tidal models"""
347
+ """``xarray.Dataset`` utilities for GOT tidal models"""
337
348
 
338
349
  def __init__(self, ds):
339
350
  self._ds = ds
@@ -1,7 +1,7 @@
1
1
  #!/usr/bin/env python
2
2
  """
3
3
  OTIS.py
4
- Written by Tyler Sutterley (01/2026)
4
+ Written by Tyler Sutterley (02/2026)
5
5
 
6
6
  Reads OTIS format tidal solutions provided by Oregon State University and ESR
7
7
  http://volkov.oce.orst.edu/tides/region.html
@@ -19,6 +19,8 @@ PYTHON DEPENDENCIES:
19
19
  https://docs.xarray.dev/en/stable/
20
20
 
21
21
  UPDATE HISTORY:
22
+ Updated 02/2026: make dataset and datatree accessors for OTIS
23
+ be subaccessors from dataset module
22
24
  Updated 01/2026: check if flexure variable exists in TMD3 files
23
25
  Updated 12/2025: no longer subclassing pathlib.Path for working directories
24
26
  Updated 11/2025: near-complete rewrite of program to use xarray
@@ -102,6 +104,10 @@ import warnings
102
104
  import numpy as np
103
105
  import xarray as xr
104
106
  import pyTMD.utilities
107
+ from .dataset import (
108
+ register_dataset_subaccessor,
109
+ register_datatree_subaccessor,
110
+ )
105
111
 
106
112
  # attempt imports
107
113
  dask = pyTMD.utilities.import_dependency("dask")
@@ -126,7 +132,7 @@ __all__ = [
126
132
  "write_raw_binary",
127
133
  "OTISDataset",
128
134
  "OTISDataTree",
129
- "ATLASDataset",
135
+ "CompactDataset",
130
136
  ]
131
137
 
132
138
  # variable attributes
@@ -373,7 +379,7 @@ def open_otis_dataset(
373
379
  # transports are returned as (u,v)
374
380
  ds2 = open_otis_transport(model_file, **kwargs)[1]
375
381
  # merge datasets
376
- ds = ds1.otis.merge(ds2, group=group)
382
+ ds = OTISDataset(ds1).merge(ds2, group=group)
377
383
  # add attributes
378
384
  ds.attrs["group"] = group.upper() if group in ("u", "v") else group
379
385
  # return xarray dataset
@@ -421,22 +427,22 @@ def open_atlas_dataset(
421
427
  crs = kwargs.get("crs", 4326)
422
428
  # open grid file
423
429
  dsg, dtg = open_atlas_grid(grid_file, use_mmap=use_mmap)
424
- ds1 = dsg.compact.combine_local(dtg, chunks=chunks)
430
+ ds1 = CompactDataset(dsg).combine_local(dtg, chunks=chunks)
425
431
  # add attributes
426
432
  ds1.attrs["crs"] = pyproj.CRS.from_user_input(crs).to_dict()
427
433
  # open model file(s)
428
434
  if group == "z":
429
435
  # elevations are returned as (z, localz)
430
436
  dsh, dth = open_atlas_elevation(model_file, use_mmap=use_mmap)
431
- ds2 = dsh.compact.combine_local(dth, chunks=chunks)
437
+ ds2 = CompactDataset(dsh).combine_local(dth, chunks=chunks)
432
438
  elif group in ("u", "U"):
433
439
  # transports are returned as (u, v, localu, localv)
434
440
  dsu, dtu, dsv, dtv = open_atlas_transport(model_file, use_mmap=use_mmap)
435
- ds2 = dsu.compact.combine_local(dtu, chunks=chunks)
441
+ ds2 = CompactDataset(dsu).combine_local(dtu, chunks=chunks)
436
442
  elif group in ("v", "V"):
437
443
  # transports are returned as (u, v, localu, localv)
438
444
  dsu, dtu, dsv, dtv = open_atlas_transport(model_file, use_mmap=use_mmap)
439
- ds2 = dsv.compact.combine_local(dtv, chunks=chunks)
445
+ ds2 = CompactDataset(dsv).combine_local(dtv, chunks=chunks)
440
446
  # merge datasets
441
447
  ds = xr.merge([ds1, ds2], compat="override")
442
448
  # add attributes
@@ -1836,9 +1842,9 @@ def write_raw_binary(
1836
1842
 
1837
1843
 
1838
1844
  # PURPOSE: OTIS utilities for xarray Datasets
1839
- @xr.register_dataset_accessor("otis")
1845
+ @register_dataset_subaccessor("otis")
1840
1846
  class OTISDataset:
1841
- """Accessor for extending an ``xarray.Dataset`` for OTIS tidal models"""
1847
+ """``xarray.Dataset`` utilities for OTIS tidal models"""
1842
1848
 
1843
1849
  def __init__(self, ds):
1844
1850
  # initialize dataset
@@ -1913,9 +1919,9 @@ class OTISDataset:
1913
1919
 
1914
1920
 
1915
1921
  # PURPOSE: OTIS utilities for xarray datatrees
1916
- @xr.register_datatree_accessor("otis")
1922
+ @register_datatree_subaccessor("otis")
1917
1923
  class OTISDataTree:
1918
- """Accessor for extending an ``xarray.DataTree`` for OTIS tidal models"""
1924
+ """``xarray.DataTree`` utilities for OTIS tidal models"""
1919
1925
 
1920
1926
  def __init__(self, dtree):
1921
1927
  # initialize datatree
@@ -2144,11 +2150,10 @@ class OTISDataTree:
2144
2150
 
2145
2151
 
2146
2152
  # PURPOSE: ATLAS-compact utilities for xarray Datasets
2147
- @xr.register_dataset_accessor("compact")
2148
- class ATLASDataset:
2153
+ @register_datatree_subaccessor("compact")
2154
+ class CompactDataset:
2149
2155
  """
2150
- Accessor for extending an ``xarray.Dataset`` for ATLAS-compact
2151
- tidal models
2156
+ ``xarray.Dataset`` utilities for ATLAS-compact tidal models
2152
2157
  """
2153
2158
 
2154
2159
  def __init__(self, ds, spacing: float | list[float] = 1.0 / 30.0):
@@ -1,7 +1,7 @@
1
1
  #!/usr/bin/env python
2
2
  """
3
3
  dataset.py
4
- Written by Tyler Sutterley (01/2026)
4
+ Written by Tyler Sutterley (02/2026)
5
5
  An xarray.Dataset extension for tidal model data
6
6
 
7
7
  PYTHON DEPENDENCIES:
@@ -17,6 +17,7 @@ PYTHON DEPENDENCIES:
17
17
  https://docs.xarray.dev/en/stable/
18
18
 
19
19
  UPDATE HISTORY:
20
+ Updated 02/2026: create subaccessor registration functions
20
21
  Updated 01/2026: handle scalar inputs for coordinate transformations
21
22
  Updated 12/2025: add coords functions to transform coordinates
22
23
  set units attribute for amplitude and phase data arrays
@@ -40,7 +41,16 @@ import xarray as xr
40
41
  # suppress warnings
41
42
  warnings.filterwarnings("ignore", category=UserWarning)
42
43
 
43
- __all__ = ["DataTree", "Dataset", "DataArray", "_transform", "_coords"]
44
+ __all__ = [
45
+ "DataTree",
46
+ "Dataset",
47
+ "DataArray",
48
+ "register_datatree_subaccessor",
49
+ "register_dataset_subaccessor",
50
+ "register_dataarray_subaccessor",
51
+ "_transform",
52
+ "_coords",
53
+ ]
44
54
 
45
55
  # pint unit registry
46
56
  __ureg__ = pint.UnitRegistry()
@@ -916,6 +926,39 @@ class DataArray:
916
926
  raise ValueError(f"Unknown unit group: {self.units}")
917
927
 
918
928
 
929
+ def register_datatree_subaccessor(name):
930
+ """Register a subaccessor on ``DataTree`` objects
931
+
932
+ Parameters
933
+ ----------
934
+ name: str
935
+ subaccessor name
936
+ """
937
+ return xr.core.extensions._register_accessor(name, DataTree)
938
+
939
+
940
+ def register_dataset_subaccessor(name):
941
+ """Register a subaccessor on ``Dataset`` objects
942
+
943
+ Parameters
944
+ ----------
945
+ name: str
946
+ subaccessor name
947
+ """
948
+ return xr.core.extensions._register_accessor(name, Dataset)
949
+
950
+
951
+ def register_dataarray_subaccessor(name):
952
+ """Register a subaccessor on ``DataArray`` objects
953
+
954
+ Parameters
955
+ ----------
956
+ name: str
957
+ subaccessor name
958
+ """
959
+ return xr.core.extensions._register_accessor(name, DataArray)
960
+
961
+
919
962
  def _transform(
920
963
  i1: np.ndarray,
921
964
  i2: np.ndarray,
@@ -1,7 +1,7 @@
1
1
  #!/usr/bin/env python
2
2
  """
3
3
  model.py
4
- Written by Tyler Sutterley (11/2025)
4
+ Written by Tyler Sutterley (02/2026)
5
5
  Retrieves tide model parameters for named tide models and
6
6
  from model definition files
7
7
 
@@ -13,6 +13,8 @@ PYTHON DEPENDENCIES:
13
13
  https://docs.xarray.dev/en/stable/
14
14
 
15
15
  UPDATE HISTORY:
16
+ Updated 02/2026: add HTML representation for model objects using xarray
17
+ set tidal constituent units (if unset) in a loop
16
18
  Updated 11/2025: use default cache directory if directory is None
17
19
  added crs property for model coordinate reference system
18
20
  refactor to use new simpler (flattened) database format
@@ -121,14 +123,14 @@ class DataBase:
121
123
  """Returns the items of the model database"""
122
124
  return self.__dict__.items()
123
125
 
124
- def __repr__(self):
125
- """Representation of the ``DataBase`` object"""
126
- return str(self.__dict__)
127
-
128
126
  def __str__(self):
129
127
  """String representation of the ``DataBase`` object"""
130
128
  return str(self.__dict__)
131
129
 
130
+ def __repr__(self):
131
+ """Representation of the ``DataBase`` object"""
132
+ return self.__str__()
133
+
132
134
  def get(self, key, default=None):
133
135
  if not hasattr(self, key) or getattr(self, key) is None:
134
136
  return default
@@ -218,6 +220,7 @@ class model:
218
220
  self.format = None
219
221
  self.name = None
220
222
  self.verify = copy.copy(kwargs["verify"])
223
+ self.__parameters__ = {}
221
224
 
222
225
  def from_database(self, m: str, group: tuple = ("z", "u", "v")):
223
226
  """
@@ -833,11 +836,10 @@ class model:
833
836
  ds.attrs["source"] = self.name
834
837
  # add coordinate reference system to Dataset
835
838
  ds.attrs["crs"] = self.crs.to_dict()
836
- # list of constituents
837
- c = ds.tmd.constituents
838
839
  # set units attribute if not already set
839
840
  # (uses value defined in the model database)
840
- ds[c].attrs["units"] = ds[c].attrs.get("units", self[group].units)
841
+ for c in ds.tmd.constituents:
842
+ ds[c].attrs["units"] = ds[c].attrs.get("units", self[group].units)
841
843
  # convert to default units
842
844
  if kwargs["use_default_units"]:
843
845
  ds = ds.tmd.to_default_units()
@@ -873,6 +875,34 @@ class model:
873
875
  properties.append(f" name: {self.name}")
874
876
  return "\n".join(properties)
875
877
 
878
+ def __repr__(self):
879
+ """Representation of the ``io.model`` object"""
880
+ return self.__str__()
881
+
882
+ def _repr_html_(self):
883
+ """HTML representation of the ``io.model`` object"""
884
+ header = "pyTMD.io.model"
885
+ header_components = [f"<div class='xr-obj-type'>{header}</div>"]
886
+ sections = []
887
+ data_vars = [k for k in ("z", "u", "v") if k in self.__parameters__]
888
+ parameters = {
889
+ k: v for k, v in self.__parameters__.items() if k not in data_vars
890
+ }
891
+ sections.append(xr.core.formatting_html.attr_section(parameters))
892
+ for v in data_vars:
893
+ sections.append(
894
+ xr.core.formatting_html._mapping_section(
895
+ mapping=self.__parameters__[v],
896
+ name=f"{v}-Attributes",
897
+ details_func=xr.core.formatting_html.summarize_attrs,
898
+ max_items_collapse=0,
899
+ expand_option_name="display_expand_attrs",
900
+ )
901
+ )
902
+ return xr.core.formatting_html._obj_repr(
903
+ self, header_components, sections
904
+ )
905
+
876
906
  def get(self, key, default=None):
877
907
  return getattr(self, key, default) or default
878
908
 
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: pyTMD
3
- Version: 3.0.2
3
+ Version: 3.0.3
4
4
  Summary: Python-based tidal prediction software for estimating ocean, load, solid Earth and pole tides
5
5
  Author: Tyler Sutterley
6
6
  Author-email: tsutterl@uw.edu
@@ -58,7 +58,7 @@ Requires-Dist: scipy>=1.10.1
58
58
  Requires-Dist: timescale>=0.0.8
59
59
  Requires-Dist: xarray
60
60
  Provides-Extra: doc
61
- Requires-Dist: docutils; extra == "doc"
61
+ Requires-Dist: docutils>=0.17; extra == "doc"
62
62
  Requires-Dist: graphviz; extra == "doc"
63
63
  Requires-Dist: ipympl; extra == "doc"
64
64
  Requires-Dist: myst-nb; extra == "doc"
@@ -36,7 +36,7 @@ pytest-cov
36
36
  pytest-xdist
37
37
 
38
38
  [doc]
39
- docutils
39
+ docutils>=0.17
40
40
  graphviz
41
41
  ipympl
42
42
  myst-nb
@@ -4,7 +4,7 @@ build-backend = "setuptools.build_meta"
4
4
 
5
5
  [project]
6
6
  name = "pyTMD"
7
- version = "3.0.2"
7
+ version = "3.0.3"
8
8
  description = "Python-based tidal prediction software for estimating ocean, load, solid Earth and pole tides"
9
9
  keywords = [
10
10
  "Ocean Tides",
@@ -59,7 +59,7 @@ Repository = "https://github.com/pyTMD/pyTMD"
59
59
  Issues = "https://github.com/pyTMD/pyTMD/issues"
60
60
 
61
61
  [project.optional-dependencies]
62
- doc = ["docutils", "graphviz", "ipympl", "myst-nb", "numpydoc", "sphinx", "sphinx-argparse>=0.4", "sphinxcontrib-bibtex", "sphinx-design", "sphinx_rtd_theme"]
62
+ doc = ["docutils>=0.17", "graphviz", "ipympl", "myst-nb", "numpydoc", "sphinx", "sphinx-argparse>=0.4", "sphinxcontrib-bibtex", "sphinx-design", "sphinx_rtd_theme"]
63
63
  all = ["cartopy", "ipyleaflet", "ipywidgets", "jplephem", "jupyterlab", "matplotlib", "notebook", "pandas"]
64
64
  aws = ["dask", "obstore", "pandas", "pyarrow", "s3fs", "zarr>=3"]
65
65
  dev = ["dask", "flake8", "gh", "oct2py", "pytest>=4.6", "pytest-cov", "pytest-xdist"]
@@ -230,7 +230,7 @@ pytest-cov = "*"
230
230
  pytest-xdist = "*"
231
231
 
232
232
  [tool.pixi.feature.doc.dependencies]
233
- docutils = "*"
233
+ docutils = ">=0.17"
234
234
  graphviz = "*"
235
235
  ipympl = "*"
236
236
  myst-nb = "*"
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes