pyTMD 2.1.2__tar.gz → 2.1.4__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.
- {pytmd-2.1.2 → pytmd-2.1.4}/PKG-INFO +1 -1
- {pytmd-2.1.2 → pytmd-2.1.4}/pyTMD/compute.py +53 -26
- {pytmd-2.1.2 → pytmd-2.1.4}/pyTMD/crs.py +54 -13
- {pytmd-2.1.2 → pytmd-2.1.4}/pyTMD/interpolate.py +9 -5
- {pytmd-2.1.2 → pytmd-2.1.4}/pyTMD/io/ATLAS.py +177 -12
- {pytmd-2.1.2 → pytmd-2.1.4}/pyTMD/io/FES.py +164 -15
- {pytmd-2.1.2 → pytmd-2.1.4}/pyTMD/io/GOT.py +170 -22
- {pytmd-2.1.2 → pytmd-2.1.4}/pyTMD/io/OTIS.py +200 -23
- {pytmd-2.1.2 → pytmd-2.1.4}/pyTMD/io/model.py +193 -5
- {pytmd-2.1.2 → pytmd-2.1.4}/pyTMD.egg-info/PKG-INFO +1 -1
- {pytmd-2.1.2 → pytmd-2.1.4}/scripts/compute_tidal_currents.py +20 -5
- {pytmd-2.1.2 → pytmd-2.1.4}/scripts/compute_tidal_elevations.py +20 -5
- pytmd-2.1.4/version.txt +1 -0
- pytmd-2.1.2/version.txt +0 -1
- {pytmd-2.1.2 → pytmd-2.1.4}/.dockerignore +0 -0
- {pytmd-2.1.2 → pytmd-2.1.4}/.gitignore +0 -0
- {pytmd-2.1.2 → pytmd-2.1.4}/CODE_OF_CONDUCT.rst +0 -0
- {pytmd-2.1.2 → pytmd-2.1.4}/CONTRIBUTORS.rst +0 -0
- {pytmd-2.1.2 → pytmd-2.1.4}/LICENSE +0 -0
- {pytmd-2.1.2 → pytmd-2.1.4}/MANIFEST.in +0 -0
- {pytmd-2.1.2 → pytmd-2.1.4}/README.rst +0 -0
- {pytmd-2.1.2 → pytmd-2.1.4}/postBuild +0 -0
- {pytmd-2.1.2 → pytmd-2.1.4}/pyTMD/__init__.py +0 -0
- {pytmd-2.1.2 → pytmd-2.1.4}/pyTMD/arguments.py +0 -0
- {pytmd-2.1.2 → pytmd-2.1.4}/pyTMD/astro.py +0 -0
- {pytmd-2.1.2 → pytmd-2.1.4}/pyTMD/check_points.py +0 -0
- {pytmd-2.1.2 → pytmd-2.1.4}/pyTMD/compute_tide_corrections.py +0 -0
- {pytmd-2.1.2 → pytmd-2.1.4}/pyTMD/data/opoleloadcoefcmcor.txt.gz +0 -0
- {pytmd-2.1.2 → pytmd-2.1.4}/pyTMD/data/tab5.2e.txt +0 -0
- {pytmd-2.1.2 → pytmd-2.1.4}/pyTMD/data/tab5.3a.txt +0 -0
- {pytmd-2.1.2 → pytmd-2.1.4}/pyTMD/data/tab5.3b.txt +0 -0
- {pytmd-2.1.2 → pytmd-2.1.4}/pyTMD/ellipse.py +0 -0
- {pytmd-2.1.2 → pytmd-2.1.4}/pyTMD/eop.py +0 -0
- {pytmd-2.1.2 → pytmd-2.1.4}/pyTMD/io/__init__.py +0 -0
- {pytmd-2.1.2 → pytmd-2.1.4}/pyTMD/io/constituents.py +0 -0
- {pytmd-2.1.2 → pytmd-2.1.4}/pyTMD/io/ocean_pole_tide.py +0 -0
- {pytmd-2.1.2 → pytmd-2.1.4}/pyTMD/predict.py +0 -0
- {pytmd-2.1.2 → pytmd-2.1.4}/pyTMD/solve/__init__.py +0 -0
- {pytmd-2.1.2 → pytmd-2.1.4}/pyTMD/solve/constants.py +0 -0
- {pytmd-2.1.2 → pytmd-2.1.4}/pyTMD/spatial.py +0 -0
- {pytmd-2.1.2 → pytmd-2.1.4}/pyTMD/time.py +0 -0
- {pytmd-2.1.2 → pytmd-2.1.4}/pyTMD/tools.py +0 -0
- {pytmd-2.1.2 → pytmd-2.1.4}/pyTMD/utilities.py +0 -0
- {pytmd-2.1.2 → pytmd-2.1.4}/pyTMD/version.py +0 -0
- {pytmd-2.1.2 → pytmd-2.1.4}/pyTMD.egg-info/SOURCES.txt +0 -0
- {pytmd-2.1.2 → pytmd-2.1.4}/pyTMD.egg-info/dependency_links.txt +0 -0
- {pytmd-2.1.2 → pytmd-2.1.4}/pyTMD.egg-info/requires.txt +0 -0
- {pytmd-2.1.2 → pytmd-2.1.4}/pyTMD.egg-info/top_level.txt +0 -0
- {pytmd-2.1.2 → pytmd-2.1.4}/requirements-dev.txt +0 -0
- {pytmd-2.1.2 → pytmd-2.1.4}/requirements.txt +0 -0
- {pytmd-2.1.2 → pytmd-2.1.4}/scripts/arcticdata_tides.py +0 -0
- {pytmd-2.1.2 → pytmd-2.1.4}/scripts/aviso_fes_tides.py +0 -0
- {pytmd-2.1.2 → pytmd-2.1.4}/scripts/compute_LPET_elevations.py +0 -0
- {pytmd-2.1.2 → pytmd-2.1.4}/scripts/compute_LPT_displacements.py +0 -0
- {pytmd-2.1.2 → pytmd-2.1.4}/scripts/compute_OPT_displacements.py +0 -0
- {pytmd-2.1.2 → pytmd-2.1.4}/scripts/compute_SET_displacements.py +0 -0
- {pytmd-2.1.2 → pytmd-2.1.4}/scripts/reduce_OTIS_files.py +0 -0
- {pytmd-2.1.2 → pytmd-2.1.4}/scripts/usap_cats_tides.py +0 -0
- {pytmd-2.1.2 → pytmd-2.1.4}/scripts/verify_box_tpxo.py +0 -0
- {pytmd-2.1.2 → pytmd-2.1.4}/setup.cfg +0 -0
- {pytmd-2.1.2 → pytmd-2.1.4}/setup.py +0 -0
|
@@ -62,6 +62,8 @@ PROGRAM DEPENDENCIES:
|
|
|
62
62
|
UPDATE HISTORY:
|
|
63
63
|
Updated 07/2024: assert that data type is a known value
|
|
64
64
|
make number of days to convert JD to MJD a variable
|
|
65
|
+
added option to crop tide models to the domain of the input data
|
|
66
|
+
added option to use JSON format definition files
|
|
65
67
|
Updated 06/2024: use np.clongdouble instead of np.longcomplex
|
|
66
68
|
Updated 04/2024: use wrapper to importlib for optional dependencies
|
|
67
69
|
Updated 02/2024: changed class name for ellipsoid parameters to datum
|
|
@@ -114,6 +116,7 @@ from __future__ import print_function, annotations
|
|
|
114
116
|
import logging
|
|
115
117
|
import pathlib
|
|
116
118
|
import numpy as np
|
|
119
|
+
from io import IOBase
|
|
117
120
|
import scipy.interpolate
|
|
118
121
|
import pyTMD.crs
|
|
119
122
|
import pyTMD.io
|
|
@@ -183,7 +186,10 @@ def tide_elevations(
|
|
|
183
186
|
MODEL: str | None = None,
|
|
184
187
|
ATLAS_FORMAT: str = 'netcdf',
|
|
185
188
|
GZIP: bool = False,
|
|
186
|
-
DEFINITION_FILE: str | pathlib.Path | None = None,
|
|
189
|
+
DEFINITION_FILE: str | pathlib.Path | IOBase | None = None,
|
|
190
|
+
DEFINITION_FORMAT: str = 'ascii',
|
|
191
|
+
CROP: bool = False,
|
|
192
|
+
BOUNDS: list | np.ndarray | None = None,
|
|
187
193
|
EPSG: str | int = 3031,
|
|
188
194
|
EPOCH: list | tuple = (2000, 1, 1, 0, 0, 0),
|
|
189
195
|
TYPE: str | None = 'drift',
|
|
@@ -219,8 +225,17 @@ def tide_elevations(
|
|
|
219
225
|
- ``'netcdf'``
|
|
220
226
|
GZIP: bool, default False
|
|
221
227
|
Tide model files are gzip compressed
|
|
222
|
-
DEFINITION_FILE: str or NoneType, default None
|
|
228
|
+
DEFINITION_FILE: str, pathlib.Path, io.IOBase or NoneType, default None
|
|
223
229
|
Tide model definition file for use
|
|
230
|
+
DEFINITION_FORMAT: str, default 'ascii'
|
|
231
|
+
Format for model definition file
|
|
232
|
+
|
|
233
|
+
- ``'ascii'``: tab-delimited definition file
|
|
234
|
+
- ``'json'``: JSON formatted definition file
|
|
235
|
+
CROP: bool, default False
|
|
236
|
+
Crop tide model data to (buffered) bounds
|
|
237
|
+
BOUNDS: list, np.ndarray or NoneType, default None
|
|
238
|
+
Boundaries for cropping tide model data
|
|
224
239
|
EPSG: int, default: 3031 (Polar Stereographic South, WGS84)
|
|
225
240
|
Input coordinate system
|
|
226
241
|
EPOCH: tuple, default (2000,1,1,0,0,0)
|
|
@@ -280,8 +295,8 @@ def tide_elevations(
|
|
|
280
295
|
|
|
281
296
|
# get parameters for tide model
|
|
282
297
|
if DEFINITION_FILE is not None:
|
|
283
|
-
model = pyTMD.io.model(DIRECTORY).from_file(
|
|
284
|
-
|
|
298
|
+
model = pyTMD.io.model(DIRECTORY).from_file(DEFINITION_FILE,
|
|
299
|
+
format=DEFINITION_FORMAT)
|
|
285
300
|
else:
|
|
286
301
|
model = pyTMD.io.model(DIRECTORY, format=ATLAS_FORMAT,
|
|
287
302
|
compressed=GZIP).elevation(MODEL)
|
|
@@ -323,28 +338,28 @@ def tide_elevations(
|
|
|
323
338
|
if model.format in ('OTIS', 'ATLAS', 'TMD3'):
|
|
324
339
|
amp,ph,D,c = pyTMD.io.OTIS.extract_constants(lon, lat, model.grid_file,
|
|
325
340
|
model.model_file, model.projection, type=model.type,
|
|
326
|
-
method=METHOD, extrapolate=EXTRAPOLATE,
|
|
327
|
-
grid=model.format, apply_flexure=APPLY_FLEXURE)
|
|
341
|
+
crop=CROP, bounds=BOUNDS, method=METHOD, extrapolate=EXTRAPOLATE,
|
|
342
|
+
cutoff=CUTOFF, grid=model.format, apply_flexure=APPLY_FLEXURE)
|
|
328
343
|
# use delta time at 2000.0 to match TMD outputs
|
|
329
344
|
deltat = np.zeros((nt), dtype=np.float64)
|
|
330
345
|
elif (model.format == 'netcdf'):
|
|
331
346
|
amp,ph,D,c = pyTMD.io.ATLAS.extract_constants(lon, lat, model.grid_file,
|
|
332
|
-
model.model_file, type=model.type,
|
|
333
|
-
extrapolate=EXTRAPOLATE, cutoff=CUTOFF,
|
|
334
|
-
compressed=model.compressed)
|
|
347
|
+
model.model_file, type=model.type, crop=CROP, bounds=BOUNDS,
|
|
348
|
+
method=METHOD, extrapolate=EXTRAPOLATE, cutoff=CUTOFF,
|
|
349
|
+
scale=model.scale, compressed=model.compressed)
|
|
335
350
|
# use delta time at 2000.0 to match TMD outputs
|
|
336
351
|
deltat = np.zeros((nt), dtype=np.float64)
|
|
337
352
|
elif (model.format == 'GOT'):
|
|
338
353
|
amp,ph,c = pyTMD.io.GOT.extract_constants(lon, lat, model.model_file,
|
|
339
|
-
method=METHOD, extrapolate=EXTRAPOLATE,
|
|
340
|
-
scale=model.scale, compressed=model.compressed)
|
|
354
|
+
crop=CROP, bounds=BOUNDS, method=METHOD, extrapolate=EXTRAPOLATE,
|
|
355
|
+
cutoff=CUTOFF, scale=model.scale, compressed=model.compressed)
|
|
341
356
|
# delta time (TT - UT1)
|
|
342
357
|
deltat = ts.tt_ut1
|
|
343
358
|
elif (model.format == 'FES'):
|
|
344
359
|
amp,ph = pyTMD.io.FES.extract_constants(lon, lat, model.model_file,
|
|
345
|
-
type=model.type, version=model.version,
|
|
346
|
-
extrapolate=EXTRAPOLATE, cutoff=CUTOFF,
|
|
347
|
-
compressed=model.compressed)
|
|
360
|
+
type=model.type, version=model.version, crop=CROP, bounds=BOUNDS,
|
|
361
|
+
method=METHOD, extrapolate=EXTRAPOLATE, cutoff=CUTOFF,
|
|
362
|
+
scale=model.scale, compressed=model.compressed)
|
|
348
363
|
# available model constituents
|
|
349
364
|
c = model.constituents
|
|
350
365
|
# delta time (TT - UT1)
|
|
@@ -412,7 +427,10 @@ def tide_currents(
|
|
|
412
427
|
MODEL: str | None = None,
|
|
413
428
|
ATLAS_FORMAT: str = 'netcdf',
|
|
414
429
|
GZIP: bool = False,
|
|
415
|
-
DEFINITION_FILE: str | pathlib.Path | None = None,
|
|
430
|
+
DEFINITION_FILE: str | pathlib.Path | IOBase | None = None,
|
|
431
|
+
DEFINITION_FORMAT: str = 'ascii',
|
|
432
|
+
CROP: bool = False,
|
|
433
|
+
BOUNDS: list | np.ndarray | None = None,
|
|
416
434
|
EPSG: str | int = 3031,
|
|
417
435
|
EPOCH: list | tuple = (2000, 1, 1, 0, 0, 0),
|
|
418
436
|
TYPE: str | None = 'drift',
|
|
@@ -447,8 +465,17 @@ def tide_currents(
|
|
|
447
465
|
- ``'netcdf'``
|
|
448
466
|
GZIP: bool, default False
|
|
449
467
|
Tide model files are gzip compressed
|
|
450
|
-
DEFINITION_FILE: str or NoneType, default None
|
|
468
|
+
DEFINITION_FILE: str, pathlib.Path, io.IOBase or NoneType, default None
|
|
451
469
|
Tide model definition file for use
|
|
470
|
+
DEFINITION_FORMAT: str, default 'ascii'
|
|
471
|
+
Format for model definition file
|
|
472
|
+
|
|
473
|
+
- ``'ascii'``: tab-delimited definition file
|
|
474
|
+
- ``'json'``: JSON formatted definition file
|
|
475
|
+
CROP: bool, default False
|
|
476
|
+
Crop tide model data to (buffered) bounds
|
|
477
|
+
BOUNDS: list, np.ndarray or NoneType, default None
|
|
478
|
+
Boundaries for cropping tide model data
|
|
452
479
|
EPSG: int, default: 3031 (Polar Stereographic South, WGS84)
|
|
453
480
|
Input coordinate system
|
|
454
481
|
EPOCH: tuple, default (2000,1,1,0,0,0)
|
|
@@ -504,8 +531,8 @@ def tide_currents(
|
|
|
504
531
|
|
|
505
532
|
# get parameters for tide model
|
|
506
533
|
if DEFINITION_FILE is not None:
|
|
507
|
-
model = pyTMD.io.model(DIRECTORY).from_file(
|
|
508
|
-
|
|
534
|
+
model = pyTMD.io.model(DIRECTORY).from_file(DEFINITION_FILE,
|
|
535
|
+
format=DEFINITION_FORMAT)
|
|
509
536
|
else:
|
|
510
537
|
model = pyTMD.io.model(DIRECTORY, format=ATLAS_FORMAT,
|
|
511
538
|
compressed=GZIP).current(MODEL)
|
|
@@ -551,22 +578,22 @@ def tide_currents(
|
|
|
551
578
|
if model.format in ('OTIS', 'ATLAS', 'TMD3'):
|
|
552
579
|
amp,ph,D,c = pyTMD.io.OTIS.extract_constants(lon, lat, model.grid_file,
|
|
553
580
|
model.model_file['u'], model.projection, type=t,
|
|
554
|
-
method=METHOD, extrapolate=EXTRAPOLATE,
|
|
555
|
-
grid=model.format)
|
|
581
|
+
crop=CROP, bounds=BOUNDS, method=METHOD, extrapolate=EXTRAPOLATE,
|
|
582
|
+
cutoff=CUTOFF, grid=model.format)
|
|
556
583
|
# use delta time at 2000.0 to match TMD outputs
|
|
557
584
|
deltat = np.zeros((nt), dtype=np.float64)
|
|
558
585
|
elif (model.format == 'netcdf'):
|
|
559
586
|
amp,ph,D,c = pyTMD.io.ATLAS.extract_constants(lon, lat, model.grid_file,
|
|
560
|
-
model.model_file[t], type=t,
|
|
561
|
-
extrapolate=EXTRAPOLATE, cutoff=CUTOFF,
|
|
562
|
-
compressed=model.compressed)
|
|
587
|
+
model.model_file[t], type=t, crop=CROP, bounds=BOUNDS,
|
|
588
|
+
method=METHOD, extrapolate=EXTRAPOLATE, cutoff=CUTOFF,
|
|
589
|
+
scale=model.scale, compressed=model.compressed)
|
|
563
590
|
# use delta time at 2000.0 to match TMD outputs
|
|
564
591
|
deltat = np.zeros((nt), dtype=np.float64)
|
|
565
592
|
elif (model.format == 'FES'):
|
|
566
593
|
amp,ph = pyTMD.io.FES.extract_constants(lon, lat, model.model_file[t],
|
|
567
|
-
type=t, version=model.version,
|
|
568
|
-
extrapolate=EXTRAPOLATE, cutoff=CUTOFF,
|
|
569
|
-
compressed=model.compressed)
|
|
594
|
+
type=t, version=model.version, crop=CROP, bounds=BOUNDS,
|
|
595
|
+
method=METHOD, extrapolate=EXTRAPOLATE, cutoff=CUTOFF,
|
|
596
|
+
scale=model.scale, compressed=model.compressed)
|
|
570
597
|
# available model constituents
|
|
571
598
|
c = model.constituents
|
|
572
599
|
# delta time (TT - UT1)
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
#!/usr/bin/env python
|
|
2
2
|
u"""
|
|
3
3
|
crs.py
|
|
4
|
-
Written by Tyler Sutterley (
|
|
4
|
+
Written by Tyler Sutterley (07/2024)
|
|
5
5
|
Coordinates Reference System (CRS) routines
|
|
6
6
|
|
|
7
7
|
CALLING SEQUENCE:
|
|
@@ -30,6 +30,7 @@ PYTHON DEPENDENCIES:
|
|
|
30
30
|
https://pyproj4.github.io/pyproj/
|
|
31
31
|
|
|
32
32
|
UPDATE HISTORY:
|
|
33
|
+
Updated 07/2024: added function to get the CRS transform
|
|
33
34
|
Updated 05/2024: make subscriptable and allow item assignment
|
|
34
35
|
Updated 04/2024: use wrapper to importlib for optional dependencies
|
|
35
36
|
Updated 02/2024: changed class name for ellipsoid parameters to datum
|
|
@@ -106,6 +107,34 @@ class crs:
|
|
|
106
107
|
o2: np.ndarray
|
|
107
108
|
Output transformed y-coordinates
|
|
108
109
|
"""
|
|
110
|
+
# name of the projection
|
|
111
|
+
self.name = PROJ
|
|
112
|
+
# set the transform and transform direction
|
|
113
|
+
self.get(PROJ, EPSG=EPSG)
|
|
114
|
+
self._direction = BF[0].upper()
|
|
115
|
+
# run conversion program and return values
|
|
116
|
+
return self.transform(i1, i2)
|
|
117
|
+
|
|
118
|
+
# PURPOSE: try to get the projection information
|
|
119
|
+
def get(self, PROJ: str, EPSG: int | str = 4326):
|
|
120
|
+
"""
|
|
121
|
+
Tries to get the CRS transformer for given values
|
|
122
|
+
|
|
123
|
+
Parameters
|
|
124
|
+
----------
|
|
125
|
+
PROJ: str
|
|
126
|
+
Spatial reference system code for coordinate transformations
|
|
127
|
+
EPSG: int or str, default 4326 (WGS84 Latitude/Longitude)
|
|
128
|
+
input (``'F'``) or output (``'B'``) coordinate system
|
|
129
|
+
|
|
130
|
+
Returns
|
|
131
|
+
-------
|
|
132
|
+
o1: np.ndarray
|
|
133
|
+
Output transformed x-coordinates
|
|
134
|
+
o2: np.ndarray
|
|
135
|
+
Output transformed y-coordinates
|
|
136
|
+
"""
|
|
137
|
+
# name of the projection
|
|
109
138
|
self.name = PROJ
|
|
110
139
|
# python dictionary with named conversion functions
|
|
111
140
|
transforms = {}
|
|
@@ -115,8 +144,6 @@ class crs:
|
|
|
115
144
|
transforms['3976'] = self._EPSG3976
|
|
116
145
|
transforms['PSNorth'] = self._PSNorth
|
|
117
146
|
transforms['4326'] = self._EPSG4326
|
|
118
|
-
# set the direction of the transform
|
|
119
|
-
self._direction = BF.upper()
|
|
120
147
|
# check that PROJ for conversion was entered correctly
|
|
121
148
|
# run named conversion program and return values
|
|
122
149
|
try:
|
|
@@ -125,7 +152,7 @@ class crs:
|
|
|
125
152
|
pass
|
|
126
153
|
else:
|
|
127
154
|
# return the output variables
|
|
128
|
-
return self
|
|
155
|
+
return self
|
|
129
156
|
# try changing the projection using a custom projection
|
|
130
157
|
# run custom conversion program and return values
|
|
131
158
|
try:
|
|
@@ -133,11 +160,14 @@ class crs:
|
|
|
133
160
|
except Exception as exc:
|
|
134
161
|
pass
|
|
135
162
|
else:
|
|
136
|
-
return self
|
|
163
|
+
return self
|
|
137
164
|
# projection not found or available
|
|
138
165
|
raise Exception(f'PROJ: {PROJ} conversion function not found')
|
|
139
166
|
|
|
140
|
-
def transform(self,
|
|
167
|
+
def transform(self,
|
|
168
|
+
i1: np.ndarray,
|
|
169
|
+
i2: np.ndarray,
|
|
170
|
+
**kwargs):
|
|
141
171
|
"""
|
|
142
172
|
Performs Coordinates Reference System (CRS) transformations
|
|
143
173
|
|
|
@@ -147,6 +177,8 @@ class crs:
|
|
|
147
177
|
Input x-coordinates
|
|
148
178
|
i2: np.ndarray
|
|
149
179
|
Input y-coordinates
|
|
180
|
+
kwargs: dict
|
|
181
|
+
Keyword arguments for the transformation
|
|
150
182
|
|
|
151
183
|
Returns
|
|
152
184
|
-------
|
|
@@ -155,10 +187,11 @@ class crs:
|
|
|
155
187
|
o2: np.ndarray
|
|
156
188
|
Output transformed y-coordinates
|
|
157
189
|
"""
|
|
190
|
+
# set the direction of the transformation
|
|
191
|
+
kwargs.setdefault('direction', self.direction)
|
|
158
192
|
if (self.name == 'PSNorth') and (self.direction.name == 'FORWARD'):
|
|
159
193
|
# convert input coordinate reference system to lat/lon
|
|
160
|
-
lon, lat = self.transformer.transform(i1, i2,
|
|
161
|
-
direction=self.direction)
|
|
194
|
+
lon, lat = self.transformer.transform(i1, i2, **kwargs)
|
|
162
195
|
# convert lat/lon to (idealized) Polar-Stereographic x/y
|
|
163
196
|
o1 = (90.0 - lat)*111.7*np.cos(lon/180.0*np.pi)
|
|
164
197
|
o2 = (90.0 - lat)*111.7*np.sin(lon/180.0*np.pi)
|
|
@@ -170,12 +203,10 @@ class crs:
|
|
|
170
203
|
ii, = np.nonzero(lon < 0)
|
|
171
204
|
lon[ii] += 360.0
|
|
172
205
|
# convert to output coordinate reference system
|
|
173
|
-
o1, o2 = self.transformer.transform(lon, lat,
|
|
174
|
-
direction=self.direction)
|
|
206
|
+
o1, o2 = self.transformer.transform(lon, lat, **kwargs)
|
|
175
207
|
else:
|
|
176
208
|
# convert coordinate reference system
|
|
177
|
-
o1, o2 = self.transformer.transform(i1, i2,
|
|
178
|
-
direction=self.direction)
|
|
209
|
+
o1, o2 = self.transformer.transform(i1, i2, **kwargs)
|
|
179
210
|
# return the transformed coordinates
|
|
180
211
|
return (o1, o2)
|
|
181
212
|
|
|
@@ -340,12 +371,22 @@ class crs:
|
|
|
340
371
|
``pyproj`` direction of the coordinate transform
|
|
341
372
|
"""
|
|
342
373
|
# convert from input coordinates to model coordinates
|
|
343
|
-
if (self._direction.upper() == 'F'):
|
|
374
|
+
if (self._direction is None) or (self._direction.upper() == 'F'):
|
|
344
375
|
return pyproj.enums.TransformDirection.FORWARD
|
|
345
376
|
# convert from model coordinates to coordinates
|
|
346
377
|
elif (self._direction.upper() == 'B'):
|
|
347
378
|
return pyproj.enums.TransformDirection.INVERSE
|
|
348
379
|
|
|
380
|
+
@property
|
|
381
|
+
def is_geographic(self):
|
|
382
|
+
"""
|
|
383
|
+
Check if the coordinate reference system is geographic
|
|
384
|
+
"""
|
|
385
|
+
if (self.name == 'PSNorth'):
|
|
386
|
+
return False
|
|
387
|
+
else:
|
|
388
|
+
return self.transformer.target_crs.is_geographic
|
|
389
|
+
|
|
349
390
|
def __str__(self):
|
|
350
391
|
"""String representation of the ``crs`` object
|
|
351
392
|
"""
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
#!/usr/bin/env python
|
|
2
2
|
u"""
|
|
3
3
|
interpolate.py
|
|
4
|
-
Written by Tyler Sutterley (
|
|
4
|
+
Written by Tyler Sutterley (07/2024)
|
|
5
5
|
Interpolators for spatial data
|
|
6
6
|
|
|
7
7
|
PYTHON DEPENDENCIES:
|
|
@@ -12,6 +12,7 @@ PYTHON DEPENDENCIES:
|
|
|
12
12
|
https://docs.scipy.org/doc/
|
|
13
13
|
|
|
14
14
|
UPDATE HISTORY:
|
|
15
|
+
Updated 07/2024: changed projection flag in extrapolation to is_geographic
|
|
15
16
|
Written 12/2022
|
|
16
17
|
"""
|
|
17
18
|
from __future__ import annotations
|
|
@@ -275,7 +276,7 @@ def extrapolate(
|
|
|
275
276
|
fill_value: float = None,
|
|
276
277
|
dtype: str | np.dtype = np.float64,
|
|
277
278
|
cutoff: int | float = np.inf,
|
|
278
|
-
|
|
279
|
+
is_geographic: bool = True,
|
|
279
280
|
**kwargs
|
|
280
281
|
):
|
|
281
282
|
"""
|
|
@@ -303,14 +304,17 @@ def extrapolate(
|
|
|
303
304
|
return only neighbors within distance [km]
|
|
304
305
|
|
|
305
306
|
Set to ``np.inf`` to extrapolate for all points
|
|
306
|
-
|
|
307
|
-
|
|
307
|
+
is_geographic: bool, default True
|
|
308
|
+
input grid is in geographic coordinates
|
|
308
309
|
|
|
309
310
|
Returns
|
|
310
311
|
-------
|
|
311
312
|
DATA: np.ndarray
|
|
312
313
|
interpolated data
|
|
313
314
|
"""
|
|
315
|
+
# set geographic flag if using old EPSG projection keyword
|
|
316
|
+
if hasattr(kwargs, 'EPSG') and (kwargs['EPSG'] == '4326'):
|
|
317
|
+
is_geographic = True
|
|
314
318
|
# verify output dimensions
|
|
315
319
|
lon = np.atleast_1d(lon)
|
|
316
320
|
lat = np.atleast_1d(lat)
|
|
@@ -332,7 +336,7 @@ def extrapolate(
|
|
|
332
336
|
valid_bounds = np.ones_like(idata.mask, dtype=bool)
|
|
333
337
|
|
|
334
338
|
# calculate coordinates for nearest-neighbors
|
|
335
|
-
if
|
|
339
|
+
if is_geographic:
|
|
336
340
|
# global or regional equirectangular model
|
|
337
341
|
# calculate meshgrid of model coordinates
|
|
338
342
|
gridlon, gridlat = np.meshgrid(ilon, ilat)
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
#!/usr/bin/env python
|
|
2
2
|
u"""
|
|
3
3
|
ATLAS.py
|
|
4
|
-
Written by Tyler Sutterley (
|
|
4
|
+
Written by Tyler Sutterley (07/2024)
|
|
5
5
|
|
|
6
6
|
Reads files for a tidal model and makes initial calculations to run tide program
|
|
7
7
|
Includes functions to extract tidal harmonic constants from OTIS tide models for
|
|
@@ -55,6 +55,7 @@ PROGRAM DEPENDENCIES:
|
|
|
55
55
|
interpolate.py: interpolation routines for spatial data
|
|
56
56
|
|
|
57
57
|
UPDATE HISTORY:
|
|
58
|
+
Updated 07/2024: added crop and bounds keywords for trimming model data
|
|
58
59
|
Updated 02/2024: changed variable for setting global grid flag to is_global
|
|
59
60
|
Updated 10/2023: add generic wrapper function for reading constituents
|
|
60
61
|
Updated 04/2023: using pathlib to define and expand tide model paths
|
|
@@ -141,6 +142,10 @@ def extract_constants(
|
|
|
141
142
|
- ``'U'``: horizontal depth-averaged transport
|
|
142
143
|
- ``'v'``: vertical transport velocities
|
|
143
144
|
- ``'V'``: vertical depth-averaged transport
|
|
145
|
+
crop: bool, default False
|
|
146
|
+
Crop tide model data to (buffered) bounds
|
|
147
|
+
bounds: list or NoneType, default None
|
|
148
|
+
Boundaries for cropping tide model data
|
|
144
149
|
method: str, default 'spline'
|
|
145
150
|
Interpolation method
|
|
146
151
|
|
|
@@ -171,6 +176,7 @@ def extract_constants(
|
|
|
171
176
|
"""
|
|
172
177
|
# set default keyword arguments
|
|
173
178
|
kwargs.setdefault('type', 'z')
|
|
179
|
+
kwargs.setdefault('crop', False)
|
|
174
180
|
kwargs.setdefault('method', 'spline')
|
|
175
181
|
kwargs.setdefault('extrapolate', False)
|
|
176
182
|
kwargs.setdefault('cutoff', 10.0)
|
|
@@ -204,9 +210,24 @@ def extract_constants(
|
|
|
204
210
|
# adjust dimensions of input coordinates to be iterable
|
|
205
211
|
ilon = np.atleast_1d(np.copy(ilon))
|
|
206
212
|
ilat = np.atleast_1d(np.copy(ilat))
|
|
207
|
-
#
|
|
208
|
-
|
|
209
|
-
|
|
213
|
+
# set default bounds if cropping
|
|
214
|
+
xmin, xmax = np.min(ilon), np.max(ilon)
|
|
215
|
+
ymin, ymax = np.min(ilat), np.max(ilat)
|
|
216
|
+
kwargs.setdefault('bounds', [xmin, xmax, ymin, ymax])
|
|
217
|
+
# grid step size of tide model
|
|
218
|
+
dlon = lon[1] - lon[0]
|
|
219
|
+
# if global: extend limits
|
|
220
|
+
is_global = False
|
|
221
|
+
|
|
222
|
+
# crop bathymetry data to (buffered) bounds
|
|
223
|
+
# or adjust longitudinal convention to fit tide model
|
|
224
|
+
if kwargs['crop'] and np.any(kwargs['bounds']):
|
|
225
|
+
mlon, mlat = np.copy(lon), np.copy(lat)
|
|
226
|
+
bathymetry, lon, lat = _crop(bathymetry, mlon, mlat,
|
|
227
|
+
bounds=kwargs['bounds'],
|
|
228
|
+
buffer=4*dlon
|
|
229
|
+
)
|
|
230
|
+
elif (np.min(ilon) < 0.0) & (np.max(lon) > 180.0):
|
|
210
231
|
# input points convention (-180:180)
|
|
211
232
|
# tide model convention (0:360)
|
|
212
233
|
ilon[ilon < 0.0] += 360.0
|
|
@@ -215,10 +236,6 @@ def extract_constants(
|
|
|
215
236
|
# tide model convention (-180:180)
|
|
216
237
|
ilon[ilon > 180.0] -= 360.0
|
|
217
238
|
|
|
218
|
-
# grid step size of tide model
|
|
219
|
-
dlon = lon[1] - lon[0]
|
|
220
|
-
# if global: extend limits
|
|
221
|
-
is_global = False
|
|
222
239
|
# replace original values with extend arrays/matrices
|
|
223
240
|
if np.isclose(lon[-1] - lon[0], 360.0 - dlon):
|
|
224
241
|
lon = _extend_array(lon, dlon)
|
|
@@ -280,6 +297,12 @@ def extract_constants(
|
|
|
280
297
|
compressed=kwargs['compressed'])
|
|
281
298
|
# append constituent to list
|
|
282
299
|
constituents.append(cons)
|
|
300
|
+
# crop tide model data to (buffered) bounds
|
|
301
|
+
if kwargs['crop'] and np.any(kwargs['bounds']):
|
|
302
|
+
hc, _, _ = _crop(hc, mlon, mlat,
|
|
303
|
+
bounds=kwargs['bounds'],
|
|
304
|
+
buffer=4*dlon
|
|
305
|
+
)
|
|
283
306
|
# replace original values with extend matrices
|
|
284
307
|
if is_global:
|
|
285
308
|
hc = _extend_matrix(hc)
|
|
@@ -366,6 +389,10 @@ def read_constants(
|
|
|
366
389
|
- ``'V'``: vertical depth-averaged transport
|
|
367
390
|
compressed: bool, default False
|
|
368
391
|
Input files are gzip compressed
|
|
392
|
+
crop: bool, default False
|
|
393
|
+
Crop tide model data to (buffered) bounds
|
|
394
|
+
bounds: list or NoneType, default None
|
|
395
|
+
Boundaries for cropping tide model data
|
|
369
396
|
|
|
370
397
|
Returns
|
|
371
398
|
-------
|
|
@@ -375,6 +402,8 @@ def read_constants(
|
|
|
375
402
|
# set default keyword arguments
|
|
376
403
|
kwargs.setdefault('type', 'z')
|
|
377
404
|
kwargs.setdefault('compressed', True)
|
|
405
|
+
kwargs.setdefault('crop', False)
|
|
406
|
+
kwargs.setdefault('bounds', None)
|
|
378
407
|
|
|
379
408
|
# raise warning if model files are entered as a string or path
|
|
380
409
|
if isinstance(model_files, (str, pathlib.Path)):
|
|
@@ -389,12 +418,21 @@ def read_constants(
|
|
|
389
418
|
# read the tide grid file for bathymetry and spatial coordinates
|
|
390
419
|
lon, lat, bathymetry = read_netcdf_grid(grid_file, kwargs['type'],
|
|
391
420
|
compressed=kwargs['compressed'])
|
|
421
|
+
is_global = False
|
|
392
422
|
|
|
423
|
+
# crop bathymetry data to (buffered) bounds
|
|
424
|
+
if kwargs['crop'] and np.any(kwargs['bounds']):
|
|
425
|
+
mlon, mlat = np.copy(lon), np.copy(lat)
|
|
426
|
+
bathymetry, lon, lat = _crop(bathymetry, mlon, mlat,
|
|
427
|
+
bounds=kwargs['bounds'],
|
|
428
|
+
)
|
|
393
429
|
# grid step size of tide model
|
|
394
430
|
dlon = lon[1] - lon[0]
|
|
395
431
|
# replace original values with extend arrays/matrices
|
|
396
|
-
lon
|
|
397
|
-
|
|
432
|
+
if np.isclose(lon[-1] - lon[0], 360.0 - dlon):
|
|
433
|
+
lon = _extend_array(lon, dlon)
|
|
434
|
+
bathymetry = _extend_matrix(bathymetry)
|
|
435
|
+
is_global = True
|
|
398
436
|
# save output constituents
|
|
399
437
|
constituents = pyTMD.io.constituents(
|
|
400
438
|
longitude=lon,
|
|
@@ -412,8 +450,15 @@ def read_constants(
|
|
|
412
450
|
# read constituent from netCDF4 file
|
|
413
451
|
hc, cons = read_netcdf_file(model_file, kwargs['type'],
|
|
414
452
|
compressed=kwargs['compressed'])
|
|
453
|
+
# crop tide model data to (buffered) bounds
|
|
454
|
+
if kwargs['crop'] and np.any(kwargs['bounds']):
|
|
455
|
+
hc, lon, lat = _crop(hc, mlon, mlat,
|
|
456
|
+
bounds=kwargs['bounds'],
|
|
457
|
+
)
|
|
415
458
|
# replace original values with extend matrices
|
|
416
|
-
|
|
459
|
+
if is_global:
|
|
460
|
+
hc = _extend_matrix(hc)
|
|
461
|
+
# set constituent masks
|
|
417
462
|
hc.mask[:] |= bathymetry.mask[:]
|
|
418
463
|
# append extended constituent
|
|
419
464
|
constituents.append(cons, hc)
|
|
@@ -1171,8 +1216,128 @@ def _extend_matrix(input_matrix: np.ndarray):
|
|
|
1171
1216
|
extended matrix
|
|
1172
1217
|
"""
|
|
1173
1218
|
ny, nx = np.shape(input_matrix)
|
|
1174
|
-
|
|
1219
|
+
# allocate for extended matrix
|
|
1220
|
+
if np.ma.isMA(input_matrix):
|
|
1221
|
+
temp = np.ma.zeros((ny,nx+2), dtype=input_matrix.dtype)
|
|
1222
|
+
else:
|
|
1223
|
+
temp = np.zeros((ny,nx+2), dtype=input_matrix.dtype)
|
|
1224
|
+
# extend matrix
|
|
1175
1225
|
temp[:,0] = input_matrix[:,-1]
|
|
1176
1226
|
temp[:,1:-1] = input_matrix[:,:]
|
|
1177
1227
|
temp[:,-1] = input_matrix[:,0]
|
|
1178
1228
|
return temp
|
|
1229
|
+
|
|
1230
|
+
# PURPOSE: crop tide model data to bounds
|
|
1231
|
+
def _crop(
|
|
1232
|
+
input_matrix: np.ndarray,
|
|
1233
|
+
ilon: np.ndarray,
|
|
1234
|
+
ilat: np.ndarray,
|
|
1235
|
+
bounds: list | tuple,
|
|
1236
|
+
buffer: int | float = 0
|
|
1237
|
+
):
|
|
1238
|
+
"""
|
|
1239
|
+
Crop tide model data to bounds
|
|
1240
|
+
|
|
1241
|
+
Parameters
|
|
1242
|
+
----------
|
|
1243
|
+
input_matrix: np.ndarray
|
|
1244
|
+
matrix to crop
|
|
1245
|
+
ilon: np.ndarray
|
|
1246
|
+
longitude of tidal model
|
|
1247
|
+
ilat: np.ndarray
|
|
1248
|
+
latitude of tidal model
|
|
1249
|
+
bounds: list, tuple
|
|
1250
|
+
bounding box: ``[xmin, xmax, ymin, ymax]``
|
|
1251
|
+
buffer: int or float, default 0
|
|
1252
|
+
buffer to add to bounds for cropping
|
|
1253
|
+
|
|
1254
|
+
Returns
|
|
1255
|
+
-------
|
|
1256
|
+
temp: np.ndarray
|
|
1257
|
+
cropped matrix
|
|
1258
|
+
lon: np.ndarray
|
|
1259
|
+
cropped longitude
|
|
1260
|
+
lat: np.ndarray
|
|
1261
|
+
cropped latitude
|
|
1262
|
+
"""
|
|
1263
|
+
# adjust longitudinal convention of tide model
|
|
1264
|
+
if (np.min(bounds[:2]) < 0.0) & (np.max(ilon) > 180.0):
|
|
1265
|
+
input_matrix, ilon = _shift(input_matrix, ilon,
|
|
1266
|
+
lon0=180.0, cyclic=360.0, direction='west')
|
|
1267
|
+
elif (np.max(bounds[:2]) > 180.0) & (np.min(ilon) < 0.0):
|
|
1268
|
+
input_matrix, ilon = _shift(input_matrix, ilon,
|
|
1269
|
+
lon0=0.0, cyclic=360.0, direction='east')
|
|
1270
|
+
# unpack bounds and buffer
|
|
1271
|
+
xmin = bounds[0] - buffer
|
|
1272
|
+
xmax = bounds[1] + buffer
|
|
1273
|
+
ymin = bounds[2] - buffer
|
|
1274
|
+
ymax = bounds[3] + buffer
|
|
1275
|
+
# find indices for cropping
|
|
1276
|
+
yind = np.flatnonzero((ilat >= ymin) & (ilat <= ymax))
|
|
1277
|
+
xind = np.flatnonzero((ilon >= xmin) & (ilon <= xmax))
|
|
1278
|
+
# slices for cropping axes
|
|
1279
|
+
rows = slice(yind[0], yind[-1]+1)
|
|
1280
|
+
cols = slice(xind[0], xind[-1]+1)
|
|
1281
|
+
# crop matrix
|
|
1282
|
+
temp = input_matrix[rows, cols]
|
|
1283
|
+
lon = ilon[cols]
|
|
1284
|
+
lat = ilat[rows]
|
|
1285
|
+
# return cropped data
|
|
1286
|
+
return (temp, lon, lat)
|
|
1287
|
+
|
|
1288
|
+
# PURPOSE: shift a grid east or west
|
|
1289
|
+
def _shift(
|
|
1290
|
+
input_matrix: np.ndarray,
|
|
1291
|
+
ilon: np.ndarray,
|
|
1292
|
+
lon0: int | float = 180,
|
|
1293
|
+
cyclic: int | float = 360,
|
|
1294
|
+
direction: str = 'west'
|
|
1295
|
+
):
|
|
1296
|
+
"""
|
|
1297
|
+
Shift global grid east or west to a new base longitude
|
|
1298
|
+
|
|
1299
|
+
Parameters
|
|
1300
|
+
----------
|
|
1301
|
+
input_matrix: np.ndarray
|
|
1302
|
+
input matrix to shift
|
|
1303
|
+
ilon: np.ndarray
|
|
1304
|
+
longitude of tidal model
|
|
1305
|
+
lon0: int or float, default 180
|
|
1306
|
+
Starting longitude for shifted grid
|
|
1307
|
+
cyclic: int or float, default 360
|
|
1308
|
+
width of periodic domain
|
|
1309
|
+
direction: str, default 'west'
|
|
1310
|
+
Direction to shift grid
|
|
1311
|
+
|
|
1312
|
+
- ``'west'``
|
|
1313
|
+
- ``'east'``
|
|
1314
|
+
|
|
1315
|
+
Returns
|
|
1316
|
+
-------
|
|
1317
|
+
temp: np.ndarray
|
|
1318
|
+
shifted matrix
|
|
1319
|
+
lon: np.ndarray
|
|
1320
|
+
shifted longitude
|
|
1321
|
+
"""
|
|
1322
|
+
# find the starting index if cyclic
|
|
1323
|
+
offset = 0 if (np.fabs(ilon[-1]-ilon[0]-cyclic) > 1e-4) else 1
|
|
1324
|
+
i0 = np.argmin(np.fabs(ilon - lon0))
|
|
1325
|
+
# shift longitudinal values
|
|
1326
|
+
lon = np.zeros(ilon.shape, ilon.dtype)
|
|
1327
|
+
lon[0:-i0] = ilon[i0:]
|
|
1328
|
+
lon[-i0:] = ilon[offset: i0+offset]
|
|
1329
|
+
# add or remove the cyclic
|
|
1330
|
+
if (direction == 'east'):
|
|
1331
|
+
lon[-i0:] += cyclic
|
|
1332
|
+
elif (direction == 'west'):
|
|
1333
|
+
lon[0:-i0] -= cyclic
|
|
1334
|
+
# allocate for shifted data
|
|
1335
|
+
if np.ma.isMA(input_matrix):
|
|
1336
|
+
temp = np.ma.zeros(input_matrix.shape,input_matrix.dtype)
|
|
1337
|
+
else:
|
|
1338
|
+
temp = np.zeros(input_matrix.shape, input_matrix.dtype)
|
|
1339
|
+
# shift data values
|
|
1340
|
+
temp[:,:-i0] = input_matrix[:,i0:]
|
|
1341
|
+
temp[:,-i0:] = input_matrix[:,offset: i0+offset]
|
|
1342
|
+
# return the shifted values
|
|
1343
|
+
return (temp, lon)
|