diff --git a/pyTMD/compute.py b/pyTMD/compute.py index 79a80a1f..c8b91f6f 100644 --- a/pyTMD/compute.py +++ b/pyTMD/compute.py @@ -62,6 +62,8 @@ UPDATE HISTORY: Updated 07/2024: assert that data type is a known value make number of days to convert JD to MJD a variable + added option to crop tide models to the domain of the input data + added option to use JSON format definition files Updated 06/2024: use np.clongdouble instead of np.longcomplex Updated 04/2024: use wrapper to importlib for optional dependencies Updated 02/2024: changed class name for ellipsoid parameters to datum @@ -114,6 +116,7 @@ import logging import pathlib import numpy as np +from io import IOBase import scipy.interpolate import pyTMD.crs import pyTMD.io @@ -183,7 +186,10 @@ def tide_elevations( MODEL: str | None = None, ATLAS_FORMAT: str = 'netcdf', GZIP: bool = False, - DEFINITION_FILE: str | pathlib.Path | None = None, + DEFINITION_FILE: str | pathlib.Path | IOBase | None = None, + DEFINITION_FORMAT: str = 'ascii', + CROP: bool = False, + BOUNDS: list | np.ndarray | None = None, EPSG: str | int = 3031, EPOCH: list | tuple = (2000, 1, 1, 0, 0, 0), TYPE: str | None = 'drift', @@ -219,8 +225,17 @@ def tide_elevations( - ``'netcdf'`` GZIP: bool, default False Tide model files are gzip compressed - DEFINITION_FILE: str or NoneType, default None + DEFINITION_FILE: str, pathlib.Path, io.IOBase or NoneType, default None Tide model definition file for use + DEFINITION_FORMAT: str, default 'ascii' + Format for model definition file + + - ``'ascii'``: tab-delimited definition file + - ``'json'``: JSON formatted definition file + CROP: bool, default False + Crop tide model data to (buffered) bounds + BOUNDS: list, np.ndarray or NoneType, default None + Boundaries for cropping tide model data EPSG: int, default: 3031 (Polar Stereographic South, WGS84) Input coordinate system EPOCH: tuple, default (2000,1,1,0,0,0) @@ -280,8 +295,8 @@ def tide_elevations( # get parameters for tide model if DEFINITION_FILE is not None: - model = pyTMD.io.model(DIRECTORY).from_file( - pathlib.Path(DEFINITION_FILE).expanduser()) + model = pyTMD.io.model(DIRECTORY).from_file(DEFINITION_FILE, + format=DEFINITION_FORMAT) else: model = pyTMD.io.model(DIRECTORY, format=ATLAS_FORMAT, compressed=GZIP).elevation(MODEL) @@ -323,28 +338,28 @@ def tide_elevations( if model.format in ('OTIS', 'ATLAS', 'TMD3'): amp,ph,D,c = pyTMD.io.OTIS.extract_constants(lon, lat, model.grid_file, model.model_file, model.projection, type=model.type, - method=METHOD, extrapolate=EXTRAPOLATE, cutoff=CUTOFF, - grid=model.format, apply_flexure=APPLY_FLEXURE) + crop=CROP, bounds=BOUNDS, method=METHOD, extrapolate=EXTRAPOLATE, + cutoff=CUTOFF, grid=model.format, apply_flexure=APPLY_FLEXURE) # use delta time at 2000.0 to match TMD outputs deltat = np.zeros((nt), dtype=np.float64) elif (model.format == 'netcdf'): amp,ph,D,c = pyTMD.io.ATLAS.extract_constants(lon, lat, model.grid_file, - model.model_file, type=model.type, method=METHOD, - extrapolate=EXTRAPOLATE, cutoff=CUTOFF, scale=model.scale, - compressed=model.compressed) + model.model_file, type=model.type, crop=CROP, bounds=BOUNDS, + method=METHOD, extrapolate=EXTRAPOLATE, cutoff=CUTOFF, + scale=model.scale, compressed=model.compressed) # use delta time at 2000.0 to match TMD outputs deltat = np.zeros((nt), dtype=np.float64) elif (model.format == 'GOT'): amp,ph,c = pyTMD.io.GOT.extract_constants(lon, lat, model.model_file, - method=METHOD, extrapolate=EXTRAPOLATE, cutoff=CUTOFF, - scale=model.scale, compressed=model.compressed) + crop=CROP, bounds=BOUNDS, method=METHOD, extrapolate=EXTRAPOLATE, + cutoff=CUTOFF, scale=model.scale, compressed=model.compressed) # delta time (TT - UT1) deltat = ts.tt_ut1 elif (model.format == 'FES'): amp,ph = pyTMD.io.FES.extract_constants(lon, lat, model.model_file, - type=model.type, version=model.version, method=METHOD, - extrapolate=EXTRAPOLATE, cutoff=CUTOFF, scale=model.scale, - compressed=model.compressed) + type=model.type, version=model.version, crop=CROP, bounds=BOUNDS, + method=METHOD, extrapolate=EXTRAPOLATE, cutoff=CUTOFF, + scale=model.scale, compressed=model.compressed) # available model constituents c = model.constituents # delta time (TT - UT1) @@ -412,7 +427,10 @@ def tide_currents( MODEL: str | None = None, ATLAS_FORMAT: str = 'netcdf', GZIP: bool = False, - DEFINITION_FILE: str | pathlib.Path | None = None, + DEFINITION_FILE: str | pathlib.Path | IOBase | None = None, + DEFINITION_FORMAT: str = 'ascii', + CROP: bool = False, + BOUNDS: list | np.ndarray | None = None, EPSG: str | int = 3031, EPOCH: list | tuple = (2000, 1, 1, 0, 0, 0), TYPE: str | None = 'drift', @@ -447,8 +465,17 @@ def tide_currents( - ``'netcdf'`` GZIP: bool, default False Tide model files are gzip compressed - DEFINITION_FILE: str or NoneType, default None + DEFINITION_FILE: str, pathlib.Path, io.IOBase or NoneType, default None Tide model definition file for use + DEFINITION_FORMAT: str, default 'ascii' + Format for model definition file + + - ``'ascii'``: tab-delimited definition file + - ``'json'``: JSON formatted definition file + CROP: bool, default False + Crop tide model data to (buffered) bounds + BOUNDS: list, np.ndarray or NoneType, default None + Boundaries for cropping tide model data EPSG: int, default: 3031 (Polar Stereographic South, WGS84) Input coordinate system EPOCH: tuple, default (2000,1,1,0,0,0) @@ -504,8 +531,8 @@ def tide_currents( # get parameters for tide model if DEFINITION_FILE is not None: - model = pyTMD.io.model(DIRECTORY).from_file( - pathlib.Path(DEFINITION_FILE).expanduser()) + model = pyTMD.io.model(DIRECTORY).from_file(DEFINITION_FILE, + format=DEFINITION_FORMAT) else: model = pyTMD.io.model(DIRECTORY, format=ATLAS_FORMAT, compressed=GZIP).current(MODEL) @@ -551,22 +578,22 @@ def tide_currents( if model.format in ('OTIS', 'ATLAS', 'TMD3'): amp,ph,D,c = pyTMD.io.OTIS.extract_constants(lon, lat, model.grid_file, model.model_file['u'], model.projection, type=t, - method=METHOD, extrapolate=EXTRAPOLATE, cutoff=CUTOFF, - grid=model.format) + crop=CROP, bounds=BOUNDS, method=METHOD, extrapolate=EXTRAPOLATE, + cutoff=CUTOFF, grid=model.format) # use delta time at 2000.0 to match TMD outputs deltat = np.zeros((nt), dtype=np.float64) elif (model.format == 'netcdf'): amp,ph,D,c = pyTMD.io.ATLAS.extract_constants(lon, lat, model.grid_file, - model.model_file[t], type=t, method=METHOD, - extrapolate=EXTRAPOLATE, cutoff=CUTOFF, scale=model.scale, - compressed=model.compressed) + model.model_file[t], type=t, crop=CROP, bounds=BOUNDS, + method=METHOD, extrapolate=EXTRAPOLATE, cutoff=CUTOFF, + scale=model.scale, compressed=model.compressed) # use delta time at 2000.0 to match TMD outputs deltat = np.zeros((nt), dtype=np.float64) elif (model.format == 'FES'): amp,ph = pyTMD.io.FES.extract_constants(lon, lat, model.model_file[t], - type=t, version=model.version, method=METHOD, - extrapolate=EXTRAPOLATE, cutoff=CUTOFF, scale=model.scale, - compressed=model.compressed) + type=t, version=model.version, crop=CROP, bounds=BOUNDS, + method=METHOD, extrapolate=EXTRAPOLATE, cutoff=CUTOFF, + scale=model.scale, compressed=model.compressed) # available model constituents c = model.constituents # delta time (TT - UT1) diff --git a/pyTMD/io/FES.py b/pyTMD/io/FES.py index 365c73ab..5115abb2 100644 --- a/pyTMD/io/FES.py +++ b/pyTMD/io/FES.py @@ -59,6 +59,7 @@ UPDATE HISTORY: Updated 07/2024: added new FES2022 to available known model versions FES2022 have masked longitudes, only extract longitude data + FES2022 extrapolated data have zeroed out inland water bodies added crop and bounds keywords for trimming model data Updated 01/2024: attempt to extract constituent IDs from filenames Updated 06/2023: extract ocean tide model variables for FES2012 @@ -725,6 +726,7 @@ def read_netcdf_file( # calculate complex form of constituent oscillation mask = (amp.data == amp.fill_value) | \ (ph.data == ph.fill_value) | \ + ((amp.data == 0) & (ph.data == 0)) | \ np.isnan(amp.data) | np.isnan(ph.data) hc = np.ma.array(amp*np.exp(-1j*ph*np.pi/180.0), mask=mask, fill_value=np.ma.default_fill_value(np.dtype(complex))) diff --git a/pyTMD/io/model.py b/pyTMD/io/model.py index 3d6d49f5..7d4472ea 100644 --- a/pyTMD/io/model.py +++ b/pyTMD/io/model.py @@ -1353,8 +1353,8 @@ def from_file(self, format: str format of the input definition file - - ``'ascii'`` for tab-delimited definition file - - ``'json'`` for JSON formatted definition file + - ``'ascii'``: tab-delimited definition file + - ``'json'``: JSON formatted definition file """ # Opening definition file and assigning file ID number if isinstance(definition_file, io.IOBase): diff --git a/scripts/compute_tidal_currents.py b/scripts/compute_tidal_currents.py index 8a56ebdf..1069b629 100755 --- a/scripts/compute_tidal_currents.py +++ b/scripts/compute_tidal_currents.py @@ -99,6 +99,7 @@ UPDATE HISTORY: Updated 07/2024: assert that data type is a known value added option to crop to the domain of the input data + added option to use JSON format definition files Updated 06/2024: include attributes in output parquet files Updated 05/2024: use function to reading parquet files to allow reading and parsing of geometry column from geopandas datasets @@ -200,6 +201,7 @@ def compute_tidal_currents(tide_dir, input_file, output_file, ATLAS_FORMAT='netcdf', GZIP=True, DEFINITION_FILE=None, + DEFINITION_FORMAT='ascii', CROP=False, FORMAT='csv', VARIABLES=[], @@ -219,7 +221,8 @@ def compute_tidal_currents(tide_dir, input_file, output_file, # get parameters for tide model if DEFINITION_FILE is not None: - model = pyTMD.io.model(tide_dir).from_file(DEFINITION_FILE) + model = pyTMD.io.model(tide_dir).from_file(DEFINITION_FILE, + format=DEFINITION_FORMAT) else: model = pyTMD.io.model(tide_dir, format=ATLAS_FORMAT, compressed=GZIP).current(TIDE_MODEL) @@ -471,6 +474,9 @@ def arguments(): group.add_argument('--definition-file', type=pathlib.Path, help='Tide model definition file') + parser.add_argument('--definition-format', + type=str, default='ascii', choices=('ascii', 'json'), + help='Format for model definition file') # crop tide model to (buffered) bounds of data parser.add_argument('--crop', '-C', default=False, action='store_true', @@ -574,6 +580,7 @@ def main(): ATLAS_FORMAT=args.atlas_format, GZIP=args.gzip, DEFINITION_FILE=args.definition_file, + DEFINITION_FORMAT=args.definition_format, CROP=args.crop, FORMAT=args.format, VARIABLES=args.variables, diff --git a/scripts/compute_tidal_elevations.py b/scripts/compute_tidal_elevations.py index 49dcb617..abe55875 100755 --- a/scripts/compute_tidal_elevations.py +++ b/scripts/compute_tidal_elevations.py @@ -102,6 +102,7 @@ UPDATE HISTORY: Updated 07/2024: assert that data type is a known value added option to crop to the domain of the input data + added option to use JSON format definition files Updated 06/2024: include attributes in output parquet files Updated 05/2024: use function to reading parquet files to allow reading and parsing of geometry column from geopandas datasets @@ -204,6 +205,7 @@ def compute_tidal_elevations(tide_dir, input_file, output_file, ATLAS_FORMAT='netcdf', GZIP=True, DEFINITION_FILE=None, + DEFINITION_FORMAT='ascii', CROP=False, FORMAT='csv', VARIABLES=[], @@ -224,7 +226,8 @@ def compute_tidal_elevations(tide_dir, input_file, output_file, # get parameters for tide model if DEFINITION_FILE is not None: - model = pyTMD.io.model(tide_dir).from_file(DEFINITION_FILE) + model = pyTMD.io.model(tide_dir).from_file(DEFINITION_FILE, + format=DEFINITION_FORMAT) else: model = pyTMD.io.model(tide_dir, format=ATLAS_FORMAT, compressed=GZIP).elevation(TIDE_MODEL) @@ -467,6 +470,9 @@ def arguments(): group.add_argument('--definition-file', type=pathlib.Path, help='Tide model definition file') + parser.add_argument('--definition-format', + type=str, default='ascii', choices=('ascii', 'json'), + help='Format for model definition file') # crop tide model to (buffered) bounds of data parser.add_argument('--crop', '-C', default=False, action='store_true', @@ -575,6 +581,7 @@ def main(): ATLAS_FORMAT=args.atlas_format, GZIP=args.gzip, DEFINITION_FILE=args.definition_file, + DEFINITION_FORMAT=args.definition_format, CROP=args.crop, FORMAT=args.format, VARIABLES=args.variables, diff --git a/test/model_FES2022b_extrapolated.def b/test/model_FES2022b_extrapolated.def new file mode 100644 index 00000000..141563c9 --- /dev/null +++ b/test/model_FES2022b_extrapolated.def @@ -0,0 +1,9 @@ +format FES +name FES2022 +model_file fes2022b/ocean_tide_extrapolated/*fes2022.nc* +type z +version FES2022 +variable tide_ocean +scale 0.01 +compressed True +reference https://doi.org/10.24400/527896/A01-2024.004 \ No newline at end of file diff --git a/test/model_FES2022b_extrapolated.json b/test/model_FES2022b_extrapolated.json new file mode 100644 index 00000000..30049e5a --- /dev/null +++ b/test/model_FES2022b_extrapolated.json @@ -0,0 +1 @@ +{"format": "FES", "name": "FES2022", "model_file": "fes2022b/ocean_tide_extrapolated/*fes2022.nc*", "type": "z", "version": "FES2022", "variable": "tide_ocean", "scale": 0.01, "compressed": true, "reference": "https://doi.org/10.24400/527896/A01-2024.004"} \ No newline at end of file