diff --git a/doc/source/api_reference/compute_LPET_elevations.rst b/doc/source/api_reference/compute_LPET_elevations.rst index 89058d50..6aad0cec 100644 --- a/doc/source/api_reference/compute_LPET_elevations.rst +++ b/doc/source/api_reference/compute_LPET_elevations.rst @@ -39,6 +39,7 @@ Calling Sequence * ``'GPS'``: GPS Time * ``'LORAN'``: Long Range Navigator Time * ``'TAI'``: International Atomic Time + * ``'datetime'``: formatted datetime string in UTC --projection : @after * ``4326``: latitude and longitude coordinates on WGS84 reference ellipsoid diff --git a/doc/source/api_reference/compute_LPT_displacements.rst b/doc/source/api_reference/compute_LPT_displacements.rst index 3d7b41dc..341e8ec9 100644 --- a/doc/source/api_reference/compute_LPT_displacements.rst +++ b/doc/source/api_reference/compute_LPT_displacements.rst @@ -37,6 +37,7 @@ compute_LPT_displacements.py * ``'GPS'``: GPS Time * ``'LORAN'``: Long Range Navigator Time * ``'TAI'``: International Atomic Time + * ``'datetime'``: formatted datetime string in UTC --projection : @after * ``4326``: latitude and longitude coordinates on WGS84 reference ellipsoid diff --git a/doc/source/api_reference/compute_OPT_displacements.rst b/doc/source/api_reference/compute_OPT_displacements.rst index dfe4c2f0..3ef05790 100644 --- a/doc/source/api_reference/compute_OPT_displacements.rst +++ b/doc/source/api_reference/compute_OPT_displacements.rst @@ -38,6 +38,7 @@ compute_OPT_displacements.py * ``'GPS'``: GPS Time * ``'LORAN'``: Long Range Navigator Time * ``'TAI'``: International Atomic Time + * ``'datetime'``: formatted datetime string in UTC --projection : @after * ``4326``: latitude and longitude coordinates on WGS84 reference ellipsoid diff --git a/doc/source/api_reference/compute_tidal_currents.rst b/doc/source/api_reference/compute_tidal_currents.rst index a38017a1..f4c40fda 100644 --- a/doc/source/api_reference/compute_tidal_currents.rst +++ b/doc/source/api_reference/compute_tidal_currents.rst @@ -37,6 +37,7 @@ compute_tidal_currents.py * ``'GPS'``: GPS Time * ``'LORAN'``: Long Range Navigator Time * ``'TAI'``: International Atomic Time + * ``'datetime'``: formatted datetime string in UTC --projection : @after * ``4326``: latitude and longitude coordinates on WGS84 reference ellipsoid diff --git a/doc/source/api_reference/compute_tidal_elevations.rst b/doc/source/api_reference/compute_tidal_elevations.rst index 9ef71c23..1479706a 100644 --- a/doc/source/api_reference/compute_tidal_elevations.rst +++ b/doc/source/api_reference/compute_tidal_elevations.rst @@ -38,6 +38,7 @@ compute_tidal_elevations.py * ``'GPS'``: GPS Time * ``'LORAN'``: Long Range Navigator Time * ``'TAI'``: International Atomic Time + * ``'datetime'``: formatted datetime string in UTC --projection : @after * ``4326``: latitude and longitude coordinates on WGS84 reference ellipsoid diff --git a/pyTMD/spatial.py b/pyTMD/spatial.py index 9f0a3a7d..4c410ecc 100644 --- a/pyTMD/spatial.py +++ b/pyTMD/spatial.py @@ -1,7 +1,7 @@ #!/usr/bin/env python u""" spatial.py -Written by Tyler Sutterley (06/2022) +Written by Tyler Sutterley (10/2022) Utilities for reading, writing and operating on spatial data @@ -19,6 +19,7 @@ https://github.com/yaml/pyyaml UPDATE HISTORY: + Updated 10/2022: added datetime parser for ascii time columns Updated 06/2022: added field_mapping options to netCDF4 and HDF5 reads added from_file wrapper function to read from particular formats Updated 04/2022: add option to reduce input GDAL raster datasets @@ -56,6 +57,7 @@ import datetime import warnings import numpy as np +import dateutil.parser # attempt imports try: import osgeo.gdal, osgeo.osr, osgeo.gdalconst @@ -165,13 +167,19 @@ def from_ascii(filename, **kwargs): file compression type columns: list, default ['time','y','x','data'] column names of ascii file + delimiter: str, + Delimiter for csv or ascii files header: int, default 0 header lines to skip from start of file + parse_dates: bool, default False + Try parsing the time column """ # set default keyword arguments kwargs.setdefault('compression',None) kwargs.setdefault('columns',['time','y','x','data']) + kwargs.setdefault('delimiter',',') kwargs.setdefault('header',0) + kwargs.setdefault('parse_dates',False) # print filename logging.info(filename) # get column names @@ -218,30 +226,43 @@ def from_ascii(filename, **kwargs): dinput['attributes'] = YAML_HEADER['header']['global_attributes'] # allocate for each variable and copy variable attributes for c in columns: - dinput[c] = np.zeros((file_lines-count)) + if (c == 'time') and kwargs['parse_dates']: + dinput[c] = np.zeros((file_lines-count),dtype='datetime64[ms]') + else: + dinput[c] = np.zeros((file_lines-count)) dinput['attributes'][c] = YAML_HEADER['header']['variables'][c] # update number of file lines to skip for reading data header = int(count) else: - # output spatial data and attributes - dinput = {c:np.zeros((file_lines-kwargs['header'])) for c in columns} - dinput['attributes'] = {c:dict() for c in columns} + # allocate for each variable and variable attributes + dinput = {} header = int(kwargs['header']) + for c in columns: + if (c == 'time') and kwargs['parse_dates']: + dinput[c] = np.zeros((file_lines-header),dtype='datetime64[ms]') + else: + dinput[c] = np.zeros((file_lines-header)) + dinput['attributes'] = {c:dict() for c in columns} # extract spatial data array # for each line in the file for i,line in enumerate(file_contents[header:]): # extract columns of interest and assign to dict # convert fortran exponentials if applicable - column = {c:r.replace('D','E') for c,r in zip(columns,rx.findall(line))} + if kwargs['delimiter']: + column = {c:l.replace('D','E') for c,l in zip(columns,line.split(kwargs['delimiter']))} + else: + column = {c:r.replace('D','E') for c,r in zip(columns,rx.findall(line))} # copy variables from column dict to output dictionary for c in columns: - dinput[c][i] = np.float64(column[c]) + if (c == 'time') and kwargs['parse_dates']: + dinput[c][i] = dateutil.parser.parse(column[c]) + else: + dinput[c][i] = np.float64(column[c]) # convert to masked array if fill values - dinput['data'] = np.ma.asarray(dinput['data']) - dinput['data'].mask = np.zeros_like(dinput['data'],dtype=bool) - if '_FillValue' in dinput['attributes']['data'].keys(): + if 'data' in dinput.keys() and '_FillValue' in dinput['attributes']['data'].keys(): + dinput['data'] = np.ma.asarray(dinput['data']) dinput['data'].fill_value = dinput['attributes']['data']['_FillValue'] - dinput['data'].mask[:] = (dinput['data'].data == dinput['data'].fill_value) + dinput['data'].mask = (dinput['data'].data == dinput['data'].fill_value) # return the spatial variables return dinput @@ -340,7 +361,7 @@ def from_netCDF4(filename, **kwargs): srs.ImportFromWkt(dinput['attributes']['crs']['crs_wkt']) dinput['attributes']['projection'] = srs.ExportToProj4() # convert to masked array if fill values - if '_FillValue' in dinput['attributes']['data'].keys(): + if 'data' in dinput.keys() and '_FillValue' in dinput['attributes']['data'].keys(): dinput['data'] = np.ma.asarray(dinput['data']) dinput['data'].fill_value = dinput['attributes']['data']['_FillValue'] dinput['data'].mask = (dinput['data'].data == dinput['data'].fill_value) @@ -445,7 +466,7 @@ def from_HDF5(filename, **kwargs): srs.ImportFromWkt(dinput['attributes']['crs']['crs_wkt']) dinput['attributes']['projection'] = srs.ExportToProj4() # convert to masked array if fill values - if '_FillValue' in dinput['attributes']['data'].keys(): + if 'data' in dinput.keys() and '_FillValue' in dinput['attributes']['data'].keys(): dinput['data'] = np.ma.asarray(dinput['data']) dinput['data'].fill_value = dinput['attributes']['data']['_FillValue'] dinput['data'].mask = (dinput['data'].data == dinput['data'].fill_value) diff --git a/scripts/compute_LPET_elevations.py b/scripts/compute_LPET_elevations.py index 38612ea9..b15dee89 100644 --- a/scripts/compute_LPET_elevations.py +++ b/scripts/compute_LPET_elevations.py @@ -1,7 +1,7 @@ #!/usr/bin/env python u""" compute_LPET_elevations.py -Written by Tyler Sutterley (04/2022) +Written by Tyler Sutterley (10/2022) Calculates long-period equilibrium tidal elevations for an input file INPUTS: @@ -20,6 +20,7 @@ for csv files: the order of the columns within the file for HDF5 and netCDF4 files: time, y, x and data variable names -H X, --header X: number of header lines for csv files + --delimiter X: Delimiter for csv or ascii files -t X, --type X: input data type drift: drift buoys or satellite/airborne altimetry (time per data point) grid: spatial grids or images (single time for all data points) @@ -27,11 +28,12 @@ days since 1858-11-17T00:00:00 -d X, --deltatime X: Input delta time for files without date information can be set to 0 to use exact calendar date from epoch - -s X, --standard X: Input time standard for delta times + -s X, --standard X: Input time standard for delta times or input time type UTC: Coordinate Universal Time GPS: GPS Time LORAN: Long Range Navigator Time TAI: International Atomic Time + datetime: formatted datetime string in UTC -P X, --projection X: spatial projection as EPSG code or PROJ4 string 4326: latitude and longitude coordinates on WGS84 reference ellipsoid -V, --verbose: Verbose output of processing run @@ -62,6 +64,7 @@ compute_equilibrium_tide.py: calculates long-period equilibrium ocean tides UPDATE HISTORY: + Updated 10/2022: added delimiter option and datetime parsing for ascii files Updated 04/2022: use argparse descriptions within documentation Updated 01/2022: added option for changing the time standard Updated 11/2021: add function for attempting to extract projection @@ -114,9 +117,17 @@ def get_projection(attributes, PROJECTION): # PURPOSE: read csv, netCDF or HDF5 data # compute long-period equilibrium tides at points and times def compute_LPET_elevations(input_file, output_file, - FORMAT='csv', VARIABLES=['time','lat','lon','data'], HEADER=0, TYPE='drift', - TIME_UNITS='days since 1858-11-17T00:00:00', TIME_STANDARD='UTC', - TIME=None, PROJECTION='4326', VERBOSE=False, MODE=0o775): + FORMAT='csv', + VARIABLES=['time','lat','lon','data'], + HEADER=0, + DELIMITER=',', + TYPE='drift', + TIME_UNITS='days since 1858-11-17T00:00:00', + TIME_STANDARD='UTC', + TIME=None, + PROJECTION='4326', + VERBOSE=False, + MODE=0o775): # create logger for verbosity level loglevel = logging.INFO if VERBOSE else logging.CRITICAL @@ -149,8 +160,9 @@ def compute_LPET_elevations(input_file, output_file, # read input file to extract time, spatial coordinates and data if (FORMAT == 'csv'): + parse_dates = (TIME_STANDARD.lower() == 'datetime') dinput = pyTMD.spatial.from_ascii(input_file, columns=VARIABLES, - header=HEADER) + delimiter=DELIMITER, header=HEADER, parse_dates=parse_dates) elif (FORMAT == 'netCDF4'): dinput = pyTMD.spatial.from_netCDF4(input_file, timename=VARIABLES[0], xname=VARIABLES[2], yname=VARIABLES[1], varname=VARIABLES[3]) @@ -217,9 +229,15 @@ def compute_LPET_elevations(input_file, output_file, else: leap_seconds = 0.0 - # convert time from units to days since 1992-01-01T00:00:00 (UTC) - tide_time = pyTMD.time.convert_delta_time(delta_time-leap_seconds, - epoch1=epoch1, epoch2=(1992,1,1,0,0,0), scale=1.0/86400.0) + if (TIME_STANDARD.lower() == 'datetime'): + # convert delta time array from datetime object + # to days relative to 1992-01-01T00:00:00 + tide_time = pyTMD.time.convert_datetime(delta_time, + epoch=(1992,1,1,0,0,0))/86400.0 + else: + # convert time from units to days since 1992-01-01T00:00:00 (UTC) + tide_time = pyTMD.time.convert_delta_time(delta_time-leap_seconds, + epoch1=epoch1, epoch2=(1992,1,1,0,0,0), scale=1.0/86400.0) # interpolate delta times from calendar dates to tide time delta_file = pyTMD.utilities.get_data_path(['data','merged_deltat.data']) deltat = calc_delta_time(delta_file, tide_time) @@ -238,7 +256,8 @@ def compute_LPET_elevations(input_file, output_file, # output to file output = dict(time=tide_time,lon=lon,lat=lat,tide_lpe=tide_lpe) if (FORMAT == 'csv'): - pyTMD.spatial.to_ascii(output, attrib, output_file, delimiter=',', + pyTMD.spatial.to_ascii(output, attrib, output_file, + delimiter=DELIMITER, header=False, columns=['time','lat','lon','tide_lpe']) elif (FORMAT == 'netCDF4'): pyTMD.spatial.to_netCDF4(output, attrib, output_file) @@ -279,6 +298,10 @@ def arguments(): parser.add_argument('--header','-H', type=int, default=0, help='Number of header lines for csv files') + # delimiter for csv or ascii files + parser.add_argument('--delimiter', + type=str, default=',', + help='Delimiter for csv or ascii files') # input data type # drift: drift buoys or satellite/airborne altimetry (time per data point) # grid: spatial grids or images (single time for all data points) @@ -297,7 +320,7 @@ def arguments(): help='Input delta time for files without date variables') # input time standard definition parser.add_argument('--standard','-s', - type=str, choices=('UTC','GPS','TAI','LORAN'), default='UTC', + type=str, choices=('UTC','GPS','TAI','LORAN','datetime'), default='UTC', help='Input time standard for delta times') # spatial projection (EPSG code or PROJ4 string) parser.add_argument('--projection','-P', @@ -328,11 +351,18 @@ def main(): args.outfile = '{0}_{1}{2}'.format(*vars) # run long period equilibrium tide program for input file - compute_LPET_elevations(args.infile, args.outfile, FORMAT=args.format, - VARIABLES=args.variables, HEADER=args.header, TYPE=args.type, - TIME_UNITS=args.epoch, TIME=args.deltatime, - TIME_STANDARD=args.standard, PROJECTION=args.projection, - VERBOSE=args.verbose, MODE=args.mode) + compute_LPET_elevations(args.infile, args.outfile, + FORMAT=args.format, + VARIABLES=args.variables, + HEADER=args.header, + DELIMITER=args.delimiter, + TYPE=args.type, + TIME_UNITS=args.epoch, + TIME=args.deltatime, + TIME_STANDARD=args.standard, + PROJECTION=args.projection, + VERBOSE=args.verbose, + MODE=args.mode) # run main program if __name__ == '__main__': diff --git a/scripts/compute_LPT_displacements.py b/scripts/compute_LPT_displacements.py index 4cfd809a..b67a053c 100644 --- a/scripts/compute_LPT_displacements.py +++ b/scripts/compute_LPT_displacements.py @@ -1,7 +1,7 @@ #!/usr/bin/env python u""" compute_LPT_displacements.py -Written by Tyler Sutterley (04/2022) +Written by Tyler Sutterley (10/2022) Calculates radial pole load tide displacements for an input file following IERS Convention (2010) guidelines http://maia.usno.navy.mil/conventions/2010officialinfo.php @@ -23,6 +23,7 @@ for csv files: the order of the columns within the file for HDF5 and netCDF4 files: time, y, x and data variable names -H X, --header X: number of header lines for csv files + --delimiter X: Delimiter for csv or ascii files -t X, --type X: input data type drift: drift buoys or satellite/airborne altimetry (time per data point) grid: spatial grids or images (single time for all data points) @@ -30,11 +31,12 @@ days since 1858-11-17T00:00:00 -d X, --deltatime X: Input delta time for files without date information can be set to 0 to use exact calendar date from epoch - -s X, --standard X: Input time standard for delta times + -s X, --standard X: Input time standard for delta times or input time type UTC: Coordinate Universal Time GPS: GPS Time LORAN: Long Range Navigator Time TAI: International Atomic Time + datetime: formatted datetime string in UTC -P X, --projection X: spatial projection as EPSG code or PROJ4 string 4326: latitude and longitude coordinates on WGS84 reference ellipsoid -V, --verbose: Verbose output of processing run @@ -65,6 +67,7 @@ read_iers_EOP.py: read daily earth orientation parameters from IERS UPDATE HISTORY: + Updated 10/2022: added delimiter option and datetime parsing for ascii files Updated 04/2022: use argparse descriptions within documentation Updated 01/2022: added option for changing the time standard Updated 11/2021: add function for attempting to extract projection @@ -123,10 +126,18 @@ def get_projection(attributes, PROJECTION): # PURPOSE: compute the pole load tide radial displacements following # IERS conventions (2010) -def compute_LPT_displacements(input_file, output_file, FORMAT='csv', - VARIABLES=['time','lat','lon','data'], HEADER=0, TYPE='drift', - TIME_UNITS='days since 1858-11-17T00:00:00', TIME=None, - TIME_STANDARD='UTC', PROJECTION='4326', VERBOSE=False, MODE=0o775): +def compute_LPT_displacements(input_file, output_file, + FORMAT='csv', + VARIABLES=['time','lat','lon','data'], + HEADER=0, + DELIMITER=',', + TYPE='drift', + TIME_UNITS='days since 1858-11-17T00:00:00', + TIME=None, + TIME_STANDARD='UTC', + PROJECTION='4326', + VERBOSE=False, + MODE=0o775): # create logger for verbosity level loglevel = logging.INFO if VERBOSE else logging.CRITICAL @@ -163,8 +174,9 @@ def compute_LPT_displacements(input_file, output_file, FORMAT='csv', # read input file to extract time, spatial coordinates and data if (FORMAT == 'csv'): + parse_dates = (TIME_STANDARD.lower() == 'datetime') dinput = pyTMD.spatial.from_ascii(input_file, columns=VARIABLES, - header=HEADER) + delimiter=DELIMITER, header=HEADER, parse_dates=parse_dates) elif (FORMAT == 'netCDF4'): dinput = pyTMD.spatial.from_netCDF4(input_file, timename=VARIABLES[0], xname=VARIABLES[2], yname=VARIABLES[1], varname=VARIABLES[3]) @@ -231,9 +243,15 @@ def compute_LPT_displacements(input_file, output_file, FORMAT='csv', else: leap_seconds = 0.0 - # convert dates to Modified Julian days (days since 1858-11-17T00:00:00) - MJD = pyTMD.time.convert_delta_time(delta_time-leap_seconds, - epoch1=epoch1, epoch2=(1858,11,17,0,0,0), scale=1.0/86400.0) + if (TIME_STANDARD.lower() == 'datetime'): + # convert delta time array from datetime object + # to Modified Julian days (days since 1858-11-17T00:00:00) + MJD = pyTMD.time.convert_datetime(delta_time, + epoch=(1858,11,17,0,0,0))/86400.0 + else: + # convert dates to Modified Julian days (days since 1858-11-17T00:00:00) + MJD = pyTMD.time.convert_delta_time(delta_time-leap_seconds, + epoch1=epoch1, epoch2=(1858,11,17,0,0,0), scale=1.0/86400.0) # add offset to convert to Julian days and then convert to calendar dates Y,M,D,h,m,s = pyTMD.time.convert_julian(2400000.5 + MJD, format='tuple') # calculate time in year-decimal format @@ -330,7 +348,8 @@ def compute_LPT_displacements(input_file, output_file, FORMAT='csv', # output to file output = dict(time=MJD,lon=lon,lat=lat,tide_pole=Srad) if (FORMAT == 'csv'): - pyTMD.spatial.to_ascii(output, attrib, output_file, delimiter=',', + pyTMD.spatial.to_ascii(output, attrib, output_file, + delimiter=DELIMITER, header=False, columns=['time','lat','lon','tide_pole']) elif (FORMAT == 'netCDF4'): pyTMD.spatial.to_netCDF4(output, attrib, output_file) @@ -371,6 +390,10 @@ def arguments(): parser.add_argument('--header','-H', type=int, default=0, help='Number of header lines for csv files') + # delimiter for csv or ascii files + parser.add_argument('--delimiter', + type=str, default=',', + help='Delimiter for csv or ascii files') # input data type # drift: drift buoys or satellite/airborne altimetry (time per data point) # grid: spatial grids or images (single time for all data points) @@ -389,7 +412,7 @@ def arguments(): help='Input delta time for files without date variables') # input time standard definition parser.add_argument('--standard','-s', - type=str, choices=('UTC','GPS','TAI','LORAN'), default='UTC', + type=str, choices=('UTC','GPS','TAI','LORAN','datetime'), default='UTC', help='Input time standard for delta times') # spatial projection (EPSG code or PROJ4 string) parser.add_argument('--projection','-P', @@ -420,11 +443,18 @@ def main(): args.outfile = '{0}_{1}{2}'.format(*vars) # run load pole tide program for input file - compute_LPT_displacements(args.infile, args.outfile, FORMAT=args.format, - VARIABLES=args.variables, HEADER=args.header, TYPE=args.type, - TIME_UNITS=args.epoch, TIME=args.deltatime, - TIME_STANDARD=args.standard, PROJECTION=args.projection, - VERBOSE=args.verbose, MODE=args.mode) + compute_LPT_displacements(args.infile, args.outfile, + FORMAT=args.format, + VARIABLES=args.variables, + HEADER=args.header, + DELIMITER=args.delimiter, + TYPE=args.type, + TIME_UNITS=args.epoch, + TIME=args.deltatime, + TIME_STANDARD=args.standard, + PROJECTION=args.projection, + VERBOSE=args.verbose, + MODE=args.mode) # run main program if __name__ == '__main__': diff --git a/scripts/compute_OPT_displacements.py b/scripts/compute_OPT_displacements.py index cbf3228a..a3e7e5d1 100644 --- a/scripts/compute_OPT_displacements.py +++ b/scripts/compute_OPT_displacements.py @@ -1,7 +1,7 @@ #!/usr/bin/env python u""" compute_OPT_displacements.py -Written by Tyler Sutterley (04/2022) +Written by Tyler Sutterley (10/2022) Calculates radial ocean pole load tide displacements for an input file following IERS Convention (2010) guidelines http://maia.usno.navy.mil/conventions/2010officialinfo.php @@ -23,6 +23,7 @@ for csv files: the order of the columns within the file for HDF5 and netCDF4 files: time, y, x and data variable names -H X, --header X: number of header lines for csv files + --delimiter X: Delimiter for csv or ascii files -t X, --type X: input data type drift: drift buoys or satellite/airborne altimetry (time per data point) grid: spatial grids or images (single time for all data points) @@ -30,11 +31,12 @@ days since 1858-11-17T00:00:00 -d X, --deltatime X: Input delta time for files without date information can be set to 0 to use exact calendar date from epoch - -s X, --standard X: Input time standard for delta times + -s X, --standard X: Input time standard for delta times or input time type UTC: Coordinate Universal Time GPS: GPS Time LORAN: Long Range Navigator Time TAI: International Atomic Time + datetime: formatted datetime string in UTC -P X, --projection X: spatial projection as EPSG code or PROJ4 string 4326: latitude and longitude coordinates on WGS84 reference ellipsoid -I X, --interpolate X: Interpolation method @@ -77,6 +79,7 @@ doi: 10.1007/s00190-015-0848-7 UPDATE HISTORY: + Updated 10/2022: added delimiter option and datetime parsing for ascii files Updated 04/2022: use longcomplex data format to be windows compliant use argparse descriptions within sphinx documentation Updated 01/2022: added option for changing the time standard @@ -141,11 +144,19 @@ def get_projection(attributes, PROJECTION): # PURPOSE: compute the ocean pole load tide radial displacements following # IERS conventions (2010) and using data from Desai (2002) -def compute_OPT_displacements(input_file, output_file, FORMAT='csv', - VARIABLES=['time','lat','lon','data'], HEADER=0, TYPE='drift', - TIME_UNITS='days since 1858-11-17T00:00:00', TIME=None, - TIME_STANDARD='UTC', PROJECTION='4326', METHOD='spline', - VERBOSE=False, MODE=0o775): +def compute_OPT_displacements(input_file, output_file, + FORMAT='csv', + VARIABLES=['time','lat','lon','data'], + HEADER=0, + DELIMITER=',', + TYPE='drift', + TIME_UNITS='days since 1858-11-17T00:00:00', + TIME=None, + TIME_STANDARD='UTC', + PROJECTION='4326', + METHOD='spline', + VERBOSE=False, + MODE=0o775): # create logger for verbosity level loglevel = logging.INFO if VERBOSE else logging.CRITICAL @@ -182,8 +193,9 @@ def compute_OPT_displacements(input_file, output_file, FORMAT='csv', # read input file to extract time, spatial coordinates and data if (FORMAT == 'csv'): + parse_dates = (TIME_STANDARD.lower() == 'datetime') dinput = pyTMD.spatial.from_ascii(input_file, columns=VARIABLES, - header=HEADER) + delimiter=DELIMITER, header=HEADER, parse_dates=parse_dates) elif (FORMAT == 'netCDF4'): dinput = pyTMD.spatial.from_netCDF4(input_file, timename=VARIABLES[0], xname=VARIABLES[2], yname=VARIABLES[1], varname=VARIABLES[3]) @@ -250,9 +262,15 @@ def compute_OPT_displacements(input_file, output_file, FORMAT='csv', else: leap_seconds = 0.0 - # convert dates to Modified Julian days (days since 1858-11-17T00:00:00) - MJD = pyTMD.time.convert_delta_time(delta_time-leap_seconds, - epoch1=epoch1, epoch2=(1858,11,17,0,0,0), scale=1.0/86400.0) + if (TIME_STANDARD.lower() == 'datetime'): + # convert delta time array from datetime object + # to Modified Julian days (days since 1858-11-17T00:00:00) + MJD = pyTMD.time.convert_datetime(delta_time, + epoch=(1858,11,17,0,0,0))/86400.0 + else: + # convert dates to Modified Julian days (days since 1858-11-17T00:00:00) + MJD = pyTMD.time.convert_delta_time(delta_time-leap_seconds, + epoch1=epoch1, epoch2=(1858,11,17,0,0,0), scale=1.0/86400.0) # add offset to convert to Julian days and then convert to calendar dates Y,M,D,h,m,s = pyTMD.time.convert_julian(2400000.5 + MJD, format='tuple') # calculate time in year-decimal format @@ -348,7 +366,8 @@ def compute_OPT_displacements(input_file, output_file, FORMAT='csv', # output to file output = dict(time=MJD,lon=lon,lat=lat,tide_oc_pole=Urad) if (FORMAT == 'csv'): - pyTMD.spatial.to_ascii(output, attrib, output_file, delimiter=',', + pyTMD.spatial.to_ascii(output, attrib, output_file, + delimiter=DELIMITER, header=False, columns=['time','lat','lon','tide_oc_pole']) elif (FORMAT == 'netCDF4'): pyTMD.spatial.to_netCDF4(output, attrib, output_file) @@ -389,6 +408,10 @@ def arguments(): parser.add_argument('--header','-H', type=int, default=0, help='Number of header lines for csv files') + # delimiter for csv or ascii files + parser.add_argument('--delimiter', + type=str, default=',', + help='Delimiter for csv or ascii files') # input data type # drift: drift buoys or satellite/airborne altimetry (time per data point) # grid: spatial grids or images (single time for all data points) @@ -407,7 +430,7 @@ def arguments(): help='Input delta time for files without date variables') # input time standard definition parser.add_argument('--standard','-s', - type=str, choices=('UTC','GPS','TAI','LORAN'), default='UTC', + type=str, choices=('UTC','GPS','TAI','LORAN','datetime'), default='UTC', help='Input time standard for delta times') # spatial projection (EPSG code or PROJ4 string) parser.add_argument('--projection','-P', @@ -443,11 +466,19 @@ def main(): args.outfile = '{0}_{1}{2}'.format(*vars) # run ocean pole tide program for input file - compute_OPT_displacements(args.infile, args.outfile, FORMAT=args.format, - VARIABLES=args.variables, HEADER=args.header, TYPE=args.type, - TIME_UNITS=args.epoch, TIME=args.deltatime, - TIME_STANDARD=args.standard, PROJECTION=args.projection, - METHOD=args.interpolate, VERBOSE=args.verbose, MODE=args.mode) + compute_OPT_displacements(args.infile, args.outfile, + FORMAT=args.format, + VARIABLES=args.variables, + HEADER=args.header, + DELIMITER=args.delimiter, + TYPE=args.type, + TIME_UNITS=args.epoch, + TIME=args.deltatime, + TIME_STANDARD=args.standard, + PROJECTION=args.projection, + METHOD=args.interpolate, + VERBOSE=args.verbose, + MODE=args.mode) # run main program if __name__ == '__main__': diff --git a/scripts/compute_tidal_currents.py b/scripts/compute_tidal_currents.py index 89bda634..390df962 100755 --- a/scripts/compute_tidal_currents.py +++ b/scripts/compute_tidal_currents.py @@ -1,7 +1,7 @@ #!/usr/bin/env python u""" compute_tidal_currents.py -Written by Tyler Sutterley (05/2022) +Written by Tyler Sutterley (10/2022) Calculates zonal and meridional tidal currents for an input file Uses OTIS format tidal solutions provided by Ohio State University and ESR @@ -31,6 +31,7 @@ for csv files: the order of the columns within the file for HDF5 and netCDF4 files: time, y, x and data variable names -H X, --header X: number of header lines for csv files + --delimiter X: Delimiter for csv or ascii files -t X, --type X: input data type drift: drift buoys or satellite/airborne altimetry (time per data point) grid: spatial grids or images (single time for all data points) @@ -38,11 +39,12 @@ days since 1858-11-17T00:00:00 -d X, --deltatime X: Input delta time for files without date information can be set to 0 to use exact calendar date from epoch - -s X, --standard X: Input time standard for delta times + -s X, --standard X: Input time standard for delta times or input time type UTC: Coordinate Universal Time GPS: GPS Time LORAN: Long Range Navigator Time TAI: International Atomic Time + datetime: formatted datetime string in UTC -P X, --projection X: spatial projection as EPSG code or PROJ4 string 4326: latitude and longitude coordinates on WGS84 reference ellipsoid -I X, --interpolate X: Interpolation method @@ -92,6 +94,7 @@ predict_tide_drift.py: predict tidal elevations using harmonic constants UPDATE HISTORY: + Updated 10/2022: added delimiter option and datetime parsing for ascii files Updated 05/2022: added ESR netCDF4 formats to list of model types updated keyword arguments to read tide model programs Updated 04/2022: use argparse descriptions within documentation @@ -172,11 +175,24 @@ def get_projection(attributes, PROJECTION): # PURPOSE: read csv, netCDF or HDF5 data # compute tides at points and times using tidal model driver algorithms def compute_tidal_currents(tide_dir, input_file, output_file, - TIDE_MODEL=None, ATLAS_FORMAT='netcdf', GZIP=True, - DEFINITION_FILE=None, FORMAT='csv', VARIABLES=[], HEADER=0, - TYPE='drift', TIME_UNITS='days since 1858-11-17T00:00:00', - TIME=None, TIME_STANDARD='UTC', PROJECTION='4326', METHOD='spline', - EXTRAPOLATE=False, CUTOFF=None, VERBOSE=False, MODE=0o775): + TIDE_MODEL=None, + ATLAS_FORMAT='netcdf', + GZIP=True, + DEFINITION_FILE=None, + FORMAT='csv', + VARIABLES=[], + HEADER=0, + DELIMITER=',', + TYPE='drift', + TIME_UNITS='days since 1858-11-17T00:00:00', + TIME=None, + TIME_STANDARD='UTC', + PROJECTION='4326', + METHOD='spline', + EXTRAPOLATE=False, + CUTOFF=None, + VERBOSE=False, + MODE=0o775): # create logger for verbosity level loglevel = logging.INFO if VERBOSE else logging.CRITICAL @@ -228,8 +244,9 @@ def compute_tidal_currents(tide_dir, input_file, output_file, # read input file to extract time, spatial coordinates and data if (FORMAT == 'csv'): + parse_dates = (TIME_STANDARD.lower() == 'datetime') dinput = pyTMD.spatial.from_ascii(input_file, columns=VARIABLES, - header=HEADER) + delimiter=DELIMITER, header=HEADER, parse_dates=parse_dates) elif (FORMAT == 'netCDF4'): dinput = pyTMD.spatial.from_netCDF4(input_file, timename=VARIABLES[0], xname=VARIABLES[2], yname=VARIABLES[1], varname=VARIABLES[3]) @@ -263,7 +280,8 @@ def compute_tidal_currents(tide_dir, input_file, output_file, except (TypeError, KeyError, ValueError): epoch1,to_secs = pyTMD.time.parse_date_string(TIME_UNITS) # convert time to seconds - delta_time = to_secs*dinput['time'].flatten() + if (TIME_STANDARD.lower() != 'datetime'): + delta_time = to_secs*dinput['time'].flatten() # calculate leap seconds if specified if (TIME_STANDARD.upper() == 'GPS'): @@ -295,9 +313,16 @@ def compute_tidal_currents(tide_dir, input_file, output_file, else: leap_seconds = 0.0 - # convert time from units to days since 1992-01-01T00:00:00 - tide_time = pyTMD.time.convert_delta_time(delta_time-leap_seconds, - epoch1=epoch1, epoch2=(1992,1,1,0,0,0), scale=1.0/86400.0) + # convert delta times or datetimes objects + if (TIME_STANDARD.lower() == 'datetime'): + # convert delta time array from datetime object + # to days relative to 1992-01-01T00:00:00 + tide_time = pyTMD.time.convert_datetime(dinput['time'].flatten(), + epoch=(1992,1,1,0,0,0))/86400.0 + else: + # convert time from units to days since 1992-01-01T00:00:00 + tide_time = pyTMD.time.convert_delta_time(delta_time-leap_seconds, + epoch1=epoch1, epoch2=(1992,1,1,0,0,0), scale=1.0/86400.0) # number of time points nt = len(tide_time) # delta time (TT - UT1) file @@ -361,7 +386,8 @@ def compute_tidal_currents(tide_dir, input_file, output_file, # output to file if (FORMAT == 'csv'): - pyTMD.spatial.to_ascii(output, attrib, output_file, delimiter=',', + pyTMD.spatial.to_ascii(output, attrib, output_file, + delimiter=DELIMITER, header=False, columns=['time','lat','lon','u','v']) elif (FORMAT == 'netCDF4'): pyTMD.spatial.to_netCDF4(output, attrib, output_file) @@ -427,6 +453,10 @@ def arguments(): parser.add_argument('--header','-H', type=int, default=0, help='Number of header lines for csv files') + # delimiter for csv or ascii files + parser.add_argument('--delimiter', + type=str, default=',', + help='Delimiter for csv or ascii files') # input data type # drift: drift buoys or satellite/airborne altimetry (time per data point) # grid: spatial grids or images (single time for all data points) @@ -445,7 +475,7 @@ def arguments(): help='Input delta time for files without date variables') # input time standard definition parser.add_argument('--standard','-s', - type=str, choices=('UTC','GPS','TAI','LORAN'), default='UTC', + type=str, choices=('UTC','GPS','TAI','LORAN','datetime'), default='UTC', help='Input time standard definition') # spatial projection (EPSG code or PROJ4 string) parser.add_argument('--projection','-P', @@ -491,14 +521,24 @@ def main(): # run tidal current program for input file compute_tidal_currents(args.directory, args.infile, args.outfile, - TIDE_MODEL=args.tide, ATLAS_FORMAT=args.atlas_format, - GZIP=args.gzip, DEFINITION_FILE=args.definition_file, - FORMAT=args.format, VARIABLES=args.variables, - HEADER=args.header, TYPE=args.type, TIME_UNITS=args.epoch, - TIME=args.deltatime, TIME_STANDARD=args.standard, - PROJECTION=args.projection, METHOD=args.interpolate, - EXTRAPOLATE=args.extrapolate, CUTOFF=args.cutoff, - VERBOSE=args.verbose, MODE=args.mode) + TIDE_MODEL=args.tide, + ATLAS_FORMAT=args.atlas_format, + GZIP=args.gzip, + DEFINITION_FILE=args.definition_file, + FORMAT=args.format, + VARIABLES=args.variables, + HEADER=args.header, + DELIMITER=args.delimiter, + TYPE=args.type, + TIME_UNITS=args.epoch, + TIME=args.deltatime, + TIME_STANDARD=args.standard, + PROJECTION=args.projection, + METHOD=args.interpolate, + EXTRAPOLATE=args.extrapolate, + CUTOFF=args.cutoff, + VERBOSE=args.verbose, + MODE=args.mode) # run main program if __name__ == '__main__': diff --git a/scripts/compute_tidal_elevations.py b/scripts/compute_tidal_elevations.py index 7ce0a027..dbf139d3 100755 --- a/scripts/compute_tidal_elevations.py +++ b/scripts/compute_tidal_elevations.py @@ -1,7 +1,7 @@ #!/usr/bin/env python u""" compute_tidal_elevations.py -Written by Tyler Sutterley (05/2022) +Written by Tyler Sutterley (10/2022) Calculates tidal elevations for an input file Uses OTIS format tidal solutions provided by Ohio State University and ESR @@ -32,6 +32,7 @@ for csv files: the order of the columns within the file for HDF5 and netCDF4 files: time, y, x and data variable names -H X, --header X: number of header lines for csv files + --delimiter X: Delimiter for csv or ascii files -t X, --type X: input data type drift: drift buoys or satellite/airborne altimetry (time per data point) grid: spatial grids or images (single time for all data points) @@ -39,11 +40,12 @@ days since 1858-11-17T00:00:00 -d X, --deltatime X: Input delta time for files without date information can be set to 0 to use exact calendar date from epoch - -s X, --standard X: Input time standard for delta times + -s X, --standard X: Input time standard for delta times or input time type UTC: Coordinate Universal Time GPS: GPS Time LORAN: Long Range Navigator Time TAI: International Atomic Time + datetime: formatted datetime string in UTC -P X, --projection X: spatial projection as EPSG code or PROJ4 string 4326: latitude and longitude coordinates on WGS84 reference ellipsoid -I X, --interpolate X: Interpolation method @@ -96,6 +98,7 @@ predict_tide_drift.py: predict tidal elevations using harmonic constants UPDATE HISTORY: + Updated 10/2022: added delimiter option and datetime parsing for ascii files Updated 05/2022: added ESR netCDF4 formats to list of model types updated keyword arguments to read tide model programs added command line option to apply flexure for applicable models @@ -186,6 +189,7 @@ def compute_tidal_elevations(tide_dir, input_file, output_file, FORMAT='csv', VARIABLES=[], HEADER=0, + DELIMITER=',', TYPE='drift', TIME_UNITS='days since 1858-11-17T00:00:00', TIME_STANDARD='UTC', @@ -239,8 +243,9 @@ def compute_tidal_elevations(tide_dir, input_file, output_file, # read input file to extract time, spatial coordinates and data if (FORMAT == 'csv'): + parse_dates = (TIME_STANDARD.lower() == 'datetime') dinput = pyTMD.spatial.from_ascii(input_file, columns=VARIABLES, - header=HEADER) + delimiter=DELIMITER, header=HEADER, parse_dates=parse_dates) elif (FORMAT == 'netCDF4'): dinput = pyTMD.spatial.from_netCDF4(input_file, timename=VARIABLES[0], xname=VARIABLES[2], yname=VARIABLES[1], varname=VARIABLES[3]) @@ -306,9 +311,15 @@ def compute_tidal_elevations(tide_dir, input_file, output_file, else: leap_seconds = 0.0 - # convert time from units to days since 1992-01-01T00:00:00 - tide_time = pyTMD.time.convert_delta_time(delta_time-leap_seconds, - epoch1=epoch1, epoch2=(1992,1,1,0,0,0), scale=1.0/86400.0) + if (TIME_STANDARD.lower() == 'datetime'): + # convert delta time array from datetime object + # to days relative to 1992-01-01T00:00:00 + tide_time = pyTMD.time.convert_datetime(delta_time, + epoch=(1992,1,1,0,0,0))/86400.0 + else: + # convert time from units to days since 1992-01-01T00:00:00 + tide_time = pyTMD.time.convert_delta_time(delta_time-leap_seconds, + epoch1=epoch1, epoch2=(1992,1,1,0,0,0), scale=1.0/86400.0) # number of time points nt = len(tide_time) # delta time (TT - UT1) file @@ -374,7 +385,8 @@ def compute_tidal_elevations(tide_dir, input_file, output_file, # output to file output = {'time':tide_time,'lon':lon,'lat':lat,output_variable:tide} if (FORMAT == 'csv'): - pyTMD.spatial.to_ascii(output, attrib, output_file, delimiter=',', + pyTMD.spatial.to_ascii(output, attrib, output_file, + delimiter=DELIMITER, header=False, columns=['time','lat','lon',output_variable]) elif (FORMAT == 'netCDF4'): pyTMD.spatial.to_netCDF4(output, attrib, output_file) @@ -436,6 +448,10 @@ def arguments(): parser.add_argument('--header','-H', type=int, default=0, help='Number of header lines for csv files') + # delimiter for csv or ascii files + parser.add_argument('--delimiter', + type=str, default=',', + help='Delimiter for csv or ascii files') # input data type # drift: drift buoys or satellite/airborne altimetry (time per data point) # grid: spatial grids or images (single time for all data points) @@ -454,7 +470,7 @@ def arguments(): help='Input delta time for files without date variables') # input time standard definition parser.add_argument('--standard','-s', - type=str, choices=('UTC','GPS','TAI','LORAN'), default='UTC', + type=str, choices=('UTC','GPS','TAI','LORAN','datetime'), default='UTC', help='Input time standard for delta times') # spatial projection (EPSG code or PROJ4 string) parser.add_argument('--projection','-P', @@ -512,6 +528,7 @@ def main(): FORMAT=args.format, VARIABLES=args.variables, HEADER=args.header, + DELIMITER=args.delimiter, TYPE=args.type, TIME_UNITS=args.epoch, TIME=args.deltatime, diff --git a/version.txt b/version.txt index 66c4c226..9084fa2f 100644 --- a/version.txt +++ b/version.txt @@ -1 +1 @@ -1.0.9 +1.1.0