diff --git a/doc/source/api_reference/geoid/interp_EGM2008_ICESat_GLA12.rst b/doc/source/api_reference/geoid/interp_EGM2008_ICESat_GLA12.rst new file mode 100644 index 0000000..4520321 --- /dev/null +++ b/doc/source/api_reference/geoid/interp_EGM2008_ICESat_GLA12.rst @@ -0,0 +1,19 @@ +============================== +interp_EGM2008_ICESat_GLA12.py +============================== + +- Reads EGM2008 geoid height spatial grids from unformatted binary files provided by the National Geospatial-Intelligence Agency and interpolates to ICESat/GLAS L2 GLA12 Antarctic and Greenland Ice Sheet elevation data + +`Source code`__ + +.. __: https://github.com/tsutterley/Grounding-Zones/blob/main/geoid/interp_EGM2008_ICESat_GLA12.py + +Calling Sequence +################ + +.. argparse:: + :filename: interp_EGM2008_ICESat_GLA12.py + :func: arguments + :prog: interp_EGM2008_ICESat_GLA12.py + :nodescription: + :nodefault: diff --git a/doc/source/api_reference/geoid/interp_EGM2008_icebridge_data.rst b/doc/source/api_reference/geoid/interp_EGM2008_icebridge_data.rst new file mode 100644 index 0000000..ca6a2cc --- /dev/null +++ b/doc/source/api_reference/geoid/interp_EGM2008_icebridge_data.rst @@ -0,0 +1,19 @@ +================================ +interp_EGM2008_icebridge_data.py +================================ + +- Reads EGM2008 geoid height spatial grids from unformatted binary files provided by the National Geospatial-Intelligence Agency and interpolates to Operation IceBridge elevation data + +`Source code`__ + +.. __: https://github.com/tsutterley/Grounding-Zones/blob/main/geoid/interp_EGM2008_icebridge_data.py + +Calling Sequence +################ + +.. argparse:: + :filename: interp_EGM2008_icebridge_data.py + :func: arguments + :prog: interp_EGM2008_icebridge_data.py + :nodescription: + :nodefault: diff --git a/doc/source/index.rst b/doc/source/index.rst index 439981a..4662051 100644 --- a/doc/source/index.rst +++ b/doc/source/index.rst @@ -88,6 +88,8 @@ Python Tools for Estimating Ice Sheet Grounding Zone Locations with data from NA api_reference/geoid/compute_geoid_ICESat2_ATL10.rst api_reference/geoid/compute_geoid_ICESat2_ATL11.rst api_reference/geoid/compute_geoid_ICESat2_ATL12.rst + api_reference/geoid/interp_EGM2008_icebridge_data.rst + api_reference/geoid/interp_EGM2008_ICESat_GLA12.rst .. toctree:: :maxdepth: 1 diff --git a/geoid/compute_geoid_ICESat_GLA12.py b/geoid/compute_geoid_ICESat_GLA12.py index 87d8f83..5d3de38 100644 --- a/geoid/compute_geoid_ICESat_GLA12.py +++ b/geoid/compute_geoid_ICESat_GLA12.py @@ -386,7 +386,7 @@ def HDF5_GLA12_geoid_write(IS_gla12_geoid, IS_gla12_attrs, # PURPOSE: create argument parser def arguments(): parser = argparse.ArgumentParser( - description="""Calculates geoid undunations for correcting ICESat/GLAS + description="""Calculates geoid undulations for correcting ICESat/GLAS L2 GLA12 Antarctic and Greenland Ice Sheet elevation data """, fromfile_prefix_chars="@" diff --git a/geoid/compute_geoid_icebridge_data.py b/geoid/compute_geoid_icebridge_data.py index 7973ced..d4bf15b 100644 --- a/geoid/compute_geoid_icebridge_data.py +++ b/geoid/compute_geoid_icebridge_data.py @@ -160,10 +160,8 @@ def compute_geoid_icebridge_data(model_file, arg, LMAX=None, LOVE=None, attrib['geoid_free2mean'] = {} attrib['geoid_free2mean']['units'] = 'm' attrib['geoid_free2mean']['long_name'] = 'Geoid_Free-to-Mean_conversion' - args = (Ylms['modelname'],Ylms['max_degree']) - attrib['geoid_free2mean']['description'] = ('Additive value to convert ' - 'geoid heights from the tide-free system to the mean-tide system') - attrib['geoid_free2mean']['tide_system'] = Ylms['tide_system'] + attrib['geoid_free2mean']['description'] = ('Additive_value_to_convert_' + 'geoid_heights_from_the_tide-free_system_to_the_mean-tide_system') attrib['geoid_free2mean']['earth_gravity_constant'] = GM attrib['geoid_free2mean']['radius'] = R attrib['geoid_free2mean']['coordinates'] = 'lat lon' @@ -203,7 +201,7 @@ def compute_geoid_icebridge_data(model_file, arg, LMAX=None, LOVE=None, dinput, file_lines, HEM = gz.io.icebridge.read_LVIS_HDF5_file( input_file, input_subsetter) - # output tidal HDF5 file + # output geoid HDF5 file # form: rg_NASA_model_GEOID_WGS84_fl1yyyymmddjjjjj.H5 # where rg is the hemisphere flag (GR or AN) for the region # model is the geoid model name flag @@ -243,8 +241,8 @@ def compute_geoid_icebridge_data(model_file, arg, LMAX=None, LOVE=None, data=dinput[key][:], dtype=dinput[key].dtype, compression='gzip') # add HDF5 variable attributes - for att_name,att_val in attributes: - h5.attrs[att_name] = att_val + for att_name,att_val in attributes.items(): + h5[key].attrs[att_name] = att_val # attach dimensions if key not in ('time',): for i,dim in enumerate(['time']): @@ -254,8 +252,7 @@ def compute_geoid_icebridge_data(model_file, arg, LMAX=None, LOVE=None, # HDF5 file attributes fid.attrs['featureType'] = 'trajectory' fid.attrs['title'] = 'Geoid_height_for_elevation_measurements' - fid.attrs['summary'] = ('Geoid_undulation_computed_at_elevation_' - 'measurements_using_a_tidal_model_driver.') + fid.attrs['summary'] = 'Geoid_undulation_computed_at_elevation_measurements' fid.attrs['project'] = 'NASA_Operation_IceBridge' fid.attrs['processing_level'] = '4' fid.attrs['date_created'] = time.strftime('%Y-%m-%d',time.localtime()) diff --git a/geoid/interp_EGM2008_ICESat_GLA12.py b/geoid/interp_EGM2008_ICESat_GLA12.py new file mode 100644 index 0000000..04bdbff --- /dev/null +++ b/geoid/interp_EGM2008_ICESat_GLA12.py @@ -0,0 +1,441 @@ +#!/usr/bin/env python +u""" +interp_EGM2008_ICESat_GLA12.py +Written by Tyler Sutterley (05/2024) +Reads EGM2008 geoid height spatial grids from unformatted binary files +provided by the National Geospatial-Intelligence Agency and interpolates +to ICESat/GLAS L2 GLA12 Antarctic and Greenland Ice Sheet elevation data + +NGA Office of Geomatics + https://earth-info.nga.mil/ + +INPUTS: + input_file: ICESat GLA12 data file + +COMMAND LINE OPTIONS: + -O X, --output-directory X: input/output data directory + -G X, --gravity X: 2.5x2.5 arcminute geoid height spatial grid + -n X, --love X: Degree 2 load Love number (default EGM2008 value) + -M X, --mode X: Permission mode of directories and files created + -V, --verbose: Output information about each created file + +PYTHON DEPENDENCIES: + numpy: Scientific Computing Tools For Python + https://numpy.org + https://numpy.org/doc/stable/user/numpy-for-matlab-users.html + scipy: Scientific Tools for Python + https://docs.scipy.org/doc/ + h5py: Python interface for Hierarchal Data Format 5 (HDF5) + https://www.h5py.org/ + pyproj: Python interface to PROJ library + https://pypi.org/project/pyproj/ + timescale: Python tools for time and astronomical calculations + https://pypi.org/project/timescale/ + +UPDATE HISTORY: + Written 05/2024 +""" +from __future__ import print_function + +import re +import logging +import pathlib +import argparse +import numpy as np +import scipy.interpolate +import grounding_zones as gz + +# attempt imports +geoidtk = gz.utilities.import_dependency('geoid_toolkit') +h5py = gz.utilities.import_dependency('h5py') +timescale = gz.utilities.import_dependency('timescale') + +# PURPOSE: read ICESat ice sheet HDF5 elevation data (GLAH12) from NSIDC +# and interpolates EGM2008 geoid undulation at points +def interp_EGM2008_ICESat(model_file, INPUT_FILE, + OUTPUT_DIRECTORY=None, + LOVE=0.3, + VERBOSE=False, + MODE=0o775): + + # create logger for verbosity level + loglevel = logging.INFO if VERBOSE else logging.CRITICAL + logging.basicConfig(level=loglevel) + + # log input file + logging.info(f'{str(INPUT_FILE)} -->') + # input granule basename + GRANULE = INPUT_FILE.name + model = 'EGM2008' + + # compile regular expression operator for extracting information from file + rx = re.compile((r'GLAH(\d{2})_(\d{3})_(\d{1})(\d{1})(\d{2})_(\d{3})_' + r'(\d{4})_(\d{1})_(\d{2})_(\d{4})\.H5'), re.VERBOSE) + # extract parameters from ICESat/GLAS HDF5 file name + # PRD: Product number (01, 05, 06, 12, 13, 14, or 15) + # RL: Release number for process that created the product = 634 + # RGTP: Repeat ground-track phase (1=8-day, 2=91-day, 3=transfer orbit) + # ORB: Reference orbit number (starts at 1 and increments each time a + # new reference orbit ground track file is obtained.) + # INST: Instance number (increments every time the satellite enters a + # different reference orbit) + # CYCL: Cycle of reference orbit for this phase + # TRK: Track within reference orbit + # SEG: Segment of orbit + # GRAN: Granule version number + # TYPE: File type + try: + PRD,RL,RGTP,ORB,INST,CYCL,TRK,SEG,GRAN,TYPE = rx.findall(GRANULE).pop() + except: + # output geoid HDF5 file (generic) + FILENAME = f'{INPUT_FILE.stem}_{model}_GEOID{INPUT_FILE.suffix}' + else: + # output geoid HDF5 file for NSIDC granules + args = (PRD,RL,model,RGTP,ORB,INST,CYCL,TRK,SEG,GRAN,TYPE) + file_format = 'GLAH{0}_{1}_{2}_GEOID_{3}{4}{5}_{6}_{7}_{8}_{9}_{10}.h5' + FILENAME = file_format.format(*args) + # get output directory from input file + if OUTPUT_DIRECTORY is None: + OUTPUT_DIRECTORY = INPUT_FILE.parent + # full path to output file + OUTPUT_FILE = OUTPUT_DIRECTORY.joinpath(FILENAME) + + # check if data is an s3 presigned url + if str(INPUT_FILE).startswith('s3:'): + client = gz.utilities.attempt_login('urs.earthdata.nasa.gov', + authorization_header=True) + session = gz.utilities.s3_filesystem() + INPUT_FILE = session.open(INPUT_FILE, mode='rb') + else: + INPUT_FILE = pathlib.Path(INPUT_FILE).expanduser().absolute() + + # read GLAH12 HDF5 file + fileID = h5py.File(INPUT_FILE, mode='r') + # get variables and attributes + rec_ndx_40HZ = fileID['Data_40HZ']['Time']['i_rec_ndx'][:].copy() + # seconds since 2000-01-01 12:00:00 UTC (J2000) + DS_UTCTime_40HZ = fileID['Data_40HZ']['DS_UTCTime_40'][:].copy() + # Latitude (degrees North) + lat_TPX = fileID['Data_40HZ']['Geolocation']['d_lat'][:].copy() + # Longitude (degrees East) + lon_40HZ = fileID['Data_40HZ']['Geolocation']['d_lon'][:].copy() + # Elevation (height above TOPEX/Poseidon ellipsoid in meters) + elev_TPX = fileID['Data_40HZ']['Elevation_Surfaces']['d_elev'][:].copy() + fv = fileID['Data_40HZ']['Elevation_Surfaces']['d_elev'].attrs['_FillValue'] + + # semimajor axis (a) and flattening (f) for TP and WGS84 ellipsoids + # parameters for Topex/Poseidon and WGS84 ellipsoids + topex = geoidtk.ref_ellipsoid('TOPEX') + wgs84 = geoidtk.ref_ellipsoid('WGS84') + # convert from Topex/Poseidon to WGS84 Ellipsoids + lat_40HZ,elev_40HZ = geoidtk.spatial.convert_ellipsoid(lat_TPX, elev_TPX, + topex['a'], topex['f'], wgs84['a'], wgs84['f'], eps=1e-12, itmax=10) + # colatitude in radians + theta_40HZ = (90.0 - lat_40HZ)*np.pi/180.0 + + # set grid parameters + dlon,dlat = (2.5/60.0), (2.5/60.0) + latlimit_north, latlimit_south = (90.0, -90.0) + longlimit_west, longlimit_east = (0.0, 360.0) + # boundary parameters + nlat = np.abs((latlimit_north - latlimit_south)/dlat).astype('i') + 1 + nlon = np.abs((longlimit_west - longlimit_east)/dlon).astype('i') + 1 + # grid coordinates (degrees) + lon = longlimit_west + np.arange(nlon)*dlon + lat = latlimit_south + np.arange(nlat)*dlat + + # check that EGM2008 data file is present in file system + model_file = pathlib.Path(model_file).expanduser().absolute() + if not model_file.exists(): + raise FileNotFoundError(f'{str(model_file)} not found') + # open input file and read contents + GRAVITY = np.fromfile(model_file, dtype='= 90): + YY1 = f'{1900.0+year_two_digit:4.0f}' + else: + YY1 = f'{2000.0+year_two_digit:4.0f}' + elif (len(YYMMDD1) == 8): + YY1,MM1,DD1 = YYMMDD1[:4],YYMMDD1[4:6],YYMMDD1[6:] + elif OIB in ('LVIS','LVGH'): + M1,RG1,YY1,MMDD1,RLD1,SS1 = re.findall(regex[OIB], input_file.name).pop() + MM1,DD1 = MMDD1[:2],MMDD1[2:] + + # read data from input_file + logging.info(f'{input_file} -->') + if (OIB == 'ATM'): + # load IceBridge ATM data from input_file + dinput, file_lines, HEM = gz.io.icebridge.read_ATM_icessn_file( + input_file, input_subsetter) + elif (OIB == 'ATM1b'): + # load IceBridge Level-1b ATM data from input_file + dinput, file_lines, HEM = gz.io.icebridge.read_ATM_qfit_file( + input_file, input_subsetter) + elif OIB in ('LVIS','LVGH'): + # load IceBridge LVIS data from input_file + dinput, file_lines, HEM = gz.io.icebridge.read_LVIS_HDF5_file( + input_file, input_subsetter) + + # set grid parameters + dlon,dlat = (2.5/60.0), (2.5/60.0) + latlimit_north, latlimit_south = (90.0, -90.0) + longlimit_west, longlimit_east = (0.0, 360.0) + # boundary parameters + nlat = np.abs((latlimit_north - latlimit_south)/dlat).astype('i') + 1 + nlon = np.abs((longlimit_west - longlimit_east)/dlon).astype('i') + 1 + # grid coordinates (degrees) + lon = longlimit_west + np.arange(nlon)*dlon + lat = latlimit_south + np.arange(nlat)*dlat + + # check that EGM2008 data file is present in file system + model_file = pathlib.Path(model_file).expanduser().absolute() + if not model_file.exists(): + raise FileNotFoundError(f'{str(model_file)} not found') + # open input file and read contents + GRAVITY = np.fromfile(model_file, dtype='