diff --git a/.github/workflows/python-request.yml b/.github/workflows/python-request.yml
index fd0c89d6..ac94770d 100644
--- a/.github/workflows/python-request.yml
+++ b/.github/workflows/python-request.yml
@@ -1,13 +1,18 @@
# This workflow will install Python dependencies, run tests and lint with a variety of Python versions
# For more information see: https://help.github.com/actions/language-and-framework-guides/using-python-with-github-actions
-name: Python on pull request
+name: pytest Build
on:
pull_request:
paths:
- gravity_toolkit/**
+ - access/**
+ - dealiasing/**
+ - geocenter/**
+ - mapping/**
- scripts/**
+ - utilities/**
- test/**
- .github/workflows/python-request.yml
schedule:
@@ -32,7 +37,7 @@ jobs:
with:
lfs: true
- name: Set up pixi environment
- uses: prefix-dev/setup-pixi@v0.9.1
+ uses: prefix-dev/setup-pixi@v0.9.6
- name: Lint with flake8
run: |
# stop the build if there are Python syntax errors or undefined names
diff --git a/.github/workflows/ruff-format.yml b/.github/workflows/ruff-format.yml
new file mode 100644
index 00000000..609db62b
--- /dev/null
+++ b/.github/workflows/ruff-format.yml
@@ -0,0 +1,18 @@
+name: Ruff Format
+
+on:
+ pull_request:
+ types: [opened, synchronize, reopened, ready_for_review]
+ branches:
+ - main
+
+jobs:
+ ruff-format:
+ runs-on: ubuntu-slim
+ steps:
+ - uses: actions/checkout@v6
+ - name: Format and annotate PR
+ uses: astral-sh/ruff-action@v3
+ with:
+ version: "latest"
+ args: "format --check --diff"
diff --git a/.github/workflows/sphinx-build.yml b/.github/workflows/sphinx-build.yml
new file mode 100644
index 00000000..966a94a8
--- /dev/null
+++ b/.github/workflows/sphinx-build.yml
@@ -0,0 +1,39 @@
+name: "Sphinx Build"
+
+on:
+ pull_request:
+ paths:
+ - gravity_toolkit/**
+ - doc/**
+ - .github/workflows/sphinx-build.yml
+ types: [opened, synchronize, reopened, ready_for_review]
+
+# set permissions for the workflow
+permissions:
+ contents: read
+ pull-requests: write
+
+jobs:
+ docs:
+ if: github.event.pull_request.draft == false
+ runs-on: ubuntu-latest
+ env:
+ SPHINXOPTS: "--fail-on-warning --fresh-env --keep-going --warning-file=sphinx.log"
+ defaults:
+ run:
+ shell: bash -l {0}
+
+ steps:
+ - uses: actions/checkout@v6
+ - name: Set up pixi environment
+ uses: prefix-dev/setup-pixi@v0.9.6
+ - name: Compile Sphinx Documentation
+ run: |
+ # run a build of the documentation
+ pixi run --environment=dev docs
+ - name: Archive Sphinx Warnings
+ if: github.event.pull_request.head.repo.full_name == github.repository
+ uses: actions/upload-artifact@v6
+ with:
+ name: sphinx-log
+ path: doc/sphinx.log
diff --git a/README.md b/README.md
new file mode 100644
index 00000000..457488f0
--- /dev/null
+++ b/README.md
@@ -0,0 +1,185 @@
+# gravity-toolkit
+
+Python tools for obtaining and working with Level-2 spherical harmonic coefficients from the NASA/DLR Gravity Recovery and Climate Experiment (GRACE) and the NASA/GFZ Gravity Recovery and Climate Experiment Follow-On (GRACE-FO) missions
+
+## About
+
+
+
+ | Version: |
+
+
+
+
+ |
+
+
+ | Citation: |
+
+
+ |
+
+
+ | Tests: |
+
+
+
+
+ |
+
+
+ | Data: |
+
+
+
+ |
+
+
+ | License: |
+
+
+ |
+
+
+
+For more information: see the documentation at [gravity-toolkit.readthedocs.io](https://gravity-toolkit.readthedocs.io/)
+
+## Installation
+
+From PyPI:
+
+```bash
+python3 -m pip install gravity-toolkit
+```
+
+To include all optional dependencies:
+
+```bash
+python3 -m pip install gravity-toolkit[all]
+```
+
+Using `conda` or `mamba` from conda-forge:
+
+```bash
+conda install -c conda-forge gravity-toolkit
+```
+
+```bash
+mamba install -c conda-forge gravity-toolkit
+```
+
+Development version from GitHub:
+
+```bash
+python3 -m pip install git+https://github.com/tsutterley/gravity-toolkit.git
+```
+
+### Running with Pixi
+
+Alternatively, you can use [Pixi](https://pixi.sh/) for a streamlined workspace environment:
+
+1. Install Pixi following the [installation instructions](https://pixi.sh/latest/#installation)
+2. Clone the project repository:
+
+```bash
+git clone https://github.com/tsutterley/gravity-toolkit.git
+```
+
+3. Move into the `gravity-toolkit` directory
+
+```bash
+cd gravity-toolkit
+```
+
+4. Install dependencies and start JupyterLab:
+
+```bash
+pixi run start
+```
+
+This will automatically create the environment, install all dependencies, and launch JupyterLab in the [notebooks](./doc/source/notebooks/) directory.
+
+## Resources
+
+- [NASA GRACE mission site](https://www.nasa.gov/mission_pages/Grace/index.html)
+- [NASA GRACE-FO mission site](https://www.nasa.gov/missions/grace-fo)
+- [JPL GRACE Tellus site](https://grace.jpl.nasa.gov/)
+- [JPL GRACE-FO site](https://gracefo.jpl.nasa.gov/)
+- [UTCSR GRACE site](http://www.csr.utexas.edu/grace/)
+- [GRACE at the NASA Physical Oceanography Distributed Active Archive Center (PO.DAAC)](https://podaac.jpl.nasa.gov/grace)
+- [GRACE at the GFZ Information System and Data Center](http://isdc.gfz-potsdam.de/grace-isdc/)
+
+## Dependencies
+
+- [boto3: Amazon Web Services (AWS) SDK for Python](https://boto3.amazonaws.com/v1/documentation/api/latest/index.html)
+- [future: Compatibility layer between Python 2 and Python 3](https://python-future.org/)
+- [lxml: processing XML and HTML in Python](https://pypi.python.org/pypi/lxml)
+- [matplotlib: Python 2D plotting library](https://matplotlib.org/)
+- [netCDF4: Python interface to the netCDF C library](https://unidata.github.io/netcdf4-python/)
+- [numpy: Scientific Computing Tools For Python](https://www.numpy.org)
+- [platformdirs: Python module for determining platform-specific directories](https://pypi.org/project/platformdirs/)
+- [python-dateutil: powerful extensions to datetime](https://dateutil.readthedocs.io/en/stable/)
+- [PyYAML: YAML parser and emitter for Python](https://github.com/yaml/pyyaml)
+- [scipy: Scientific Tools for Python](https://docs.scipy.org/doc/)
+
+## Download
+
+The program homepage is:
+
+
+A zip archive of the latest version is available directly at:
+
+
+## Disclaimer
+
+This package includes software developed at the University of California at Irvine (UCI), the NASA Jet Propulsion Laboratory (JPL), NASA Goddard Space Flight Center (GSFC) and the University of Washington Applied Physics Laboratory (UW-APL).
+This program is not sponsored or maintained by the Universities Space Research Association (USRA),
+the Center for Space Research at the University of Texas (UTCSR), the Jet Propulsion Laboratory (JPL),
+the German Research Centre for Geosciences (GeoForschungsZentrum, GFZ) or NASA.
+The software is provided here for your convenience but *with no guarantees whatsoever*.
+
+## Contributing
+
+This project contains work and contributions from the [scientific community](./CONTRIBUTORS.md).
+If you would like to contribute to the project, please have a look at the [contribution guidelines](./doc/source/getting_started/Contributing.rst), [open issues](https://github.com/tsutterley/gravity-toolkit/issues) and [discussions board](https://github.com/tsutterley/gravity-toolkit/discussions).
+
+## References
+
+> T. C. Sutterley, I. Velicogna, and C.-W. Hsu,
+> "Self-Consistent Ice Mass Balance and Regional Sea Level From Time-Variable Gravity",
+> *Earth and Space Science*, 7, (2020).
+> [doi: 10.1029/2019EA000860](https://doi.org/10.1029/2019EA000860)
+>
+> T. C. Sutterley and I. Velicogna,
+> "Improved estimates of geocenter variability from time-variable gravity and ocean model outputs",
+> *Remote Sensing*, 11(18), 2108, (2019).
+> [doi: 10.3390/rs11182108](https://doi.org/10.3390/rs11182108)
+>
+> J. Wahr, S. C. Swenson, and I. Velicogna,
+> "Accuracy of GRACE mass estimates",
+> *Geophysical Research Letters*, 33(6), L06401, (2006).
+> [doi: 10.1029/2005GL025305](https://doi.org/10.1029/2005GL025305)
+>
+> J. Wahr, M. Molenaar, and F. Bryan,
+> "Time variability of the Earth's gravity field: Hydrological and oceanic effects and their possible > detection using GRACE",
+> *Journal of Geophysical Research: Solid Earth*, 103(B12), (1998).
+> [doi: 10.1029/98JB02844](https://doi.org/10.1029/98JB02844)
+>
+> D. Han and J. Wahr,
+> "The viscoelastic relaxation of a realistically stratified earth, and a further analysis of postglacial rebound",
+> *Geophysical Journal International*, 120(2), (1995).
+> [doi: 10.1111/j.1365-246X.1995.tb01819.x](https://doi.org/10.1111/j.1365-246X.1995.tb01819.x)
+
+## Data Repositories
+
+> T. C. Sutterley, I. Velicogna, and C.-W. Hsu,
+> "Ice Mass and Regional Sea Level Estimates from Time-Variable Gravity", (2020).
+> [doi: 10.6084/m9.figshare.9702338](https://doi.org/10.6084/m9.figshare.9702338)
+>
+> T. C. Sutterley and I. Velicogna,
+> "Geocenter Estimates from Time-Variable Gravity and Ocean Model Outputs", (2019).
+> [doi: 10.6084/m9.figshare.7388540](https://doi.org/10.6084/m9.figshare.7388540)
+
+## License
+
+The content of this project is licensed under the [Creative Commons Attribution 4.0 Attribution license](https://creativecommons.org/licenses/by/4.0/) and the source code is licensed under the [MIT license](LICENSE).
diff --git a/README.rst b/README.rst
deleted file mode 100644
index 558ea905..00000000
--- a/README.rst
+++ /dev/null
@@ -1,114 +0,0 @@
-===============
-gravity-toolkit
-===============
-
-|License|
-|Documentation Status|
-|PyPI|
-|conda-forge|
-|commits-since|
-|zenodo|
-
-.. |License| image:: https://img.shields.io/github/license/tsutterley/gravity-toolkit
- :target: https://github.com/tsutterley/gravity-toolkit/blob/main/LICENSE
-
-.. |Documentation Status| image:: https://readthedocs.org/projects/gravity-toolkit/badge/?version=latest
- :target: https://gravity-toolkit.readthedocs.io/en/latest/?badge=latest
-
-.. |PyPI| image:: https://img.shields.io/pypi/v/gravity-toolkit.svg
- :target: https://pypi.python.org/pypi/gravity-toolkit/
-
-.. |conda-forge| image:: https://img.shields.io/conda/vn/conda-forge/gravity-toolkit
- :target: https://anaconda.org/conda-forge/gravity-toolkit
-
-.. |commits-since| image:: https://img.shields.io/github/commits-since/tsutterley/gravity-toolkit/latest
- :target: https://github.com/tsutterley/gravity-toolkit/releases/latest
-
-.. |zenodo| image:: https://zenodo.org/badge/DOI/10.5281/zenodo.5156864.svg
- :target: https://doi.org/10.5281/zenodo.5156864
-
-Python tools for obtaining and working with Level-2 spherical harmonic coefficients from the NASA/DLR Gravity Recovery and Climate Experiment (GRACE) and the NASA/GFZ Gravity Recovery and Climate Experiment Follow-On (GRACE-FO) missions
-
-Resources
-#########
-
-- `NASA GRACE mission site `_
-- `NASA GRACE-FO mission site `_
-- `JPL GRACE Tellus site `_
-- `JPL GRACE-FO site `_
-- `UTCSR GRACE site `_
-- `GRACE at the NASA Physical Oceanography Distributed Active Archive Center (PO.DAAC) `_
-- `GRACE at the GFZ Information System and Data Center `_
-
-Dependencies
-############
-
-- `numpy: Scientific Computing Tools For Python `_
-- `scipy: Scientific Tools for Python `_
-- `dateutil: powerful extensions to datetime `_
-- `PyYAML: YAML parser and emitter for Python `_
-- `lxml: processing XML and HTML in Python `_
-- `future: Compatibility layer between Python 2 and Python 3 `_
-- `matplotlib: Python 2D plotting library `_
-- `cartopy: Python package designed for geospatial data processing `_
-- `netCDF4: Python interface to the netCDF C library `_
-- `h5py: Python interface for Hierarchal Data Format 5 (HDF5) `_
-- `geoid-toolkit: Python utilities for calculating geoid heights from static gravity field coefficients `_
-
-References
-##########
-
- T. C. Sutterley, I. Velicogna, and C.-W. Hsu, "Self-Consistent Ice Mass Balance
- and Regional Sea Level From Time-Variable Gravity", *Earth and Space Science*, 7,
- (2020). `doi: 10.1029/2019EA000860 `_
-
- T. C. Sutterley and I. Velicogna, "Improved estimates of geocenter variability
- from time-variable gravity and ocean model outputs", *Remote Sensing*, 11(18),
- 2108, (2019). `doi: 10.3390/rs11182108 `_
-
- J. Wahr, S. C. Swenson, and I. Velicogna, "Accuracy of GRACE mass estimates",
- *Geophysical Research Letters*, 33(6), L06401, (2006).
- `doi: 10.1029/2005GL025305 `_
-
- J. Wahr, M. Molenaar, and F. Bryan, "Time variability of the Earth's gravity
- field: Hydrological and oceanic effects and their possible detection using
- GRACE", *Journal of Geophysical Research: Solid Earth*, 103(B12), (1998).
- `doi: 10.1029/98JB02844 `_
-
- D. Han and J. Wahr, "The viscoelastic relaxation of a realistically stratified
- earth, and a further analysis of postglacial rebound", *Geophysical Journal
- International*, 120(2), (1995).
- `doi: 10.1111/j.1365-246X.1995.tb01819.x `_
-
-Data Repositories
-#################
-
- T. C. Sutterley, I. Velicogna, and C.-W. Hsu, "Ice Mass and Regional Sea Level
- Estimates from Time-Variable Gravity", (2020).
- `doi: 10.6084/m9.figshare.9702338 `_
-
- T. C. Sutterley and I. Velicogna, "Geocenter Estimates from Time-Variable
- Gravity and Ocean Model Outputs", (2019).
- `doi: 10.6084/m9.figshare.7388540 `_
-
-Download
-########
-
-| The program homepage is:
-| https://github.com/tsutterley/gravity-toolkit
-| A zip archive of the latest version is available directly at:
-| https://github.com/tsutterley/gravity-toolkit/archive/main.zip
-
-Disclaimer
-##########
-
-This project contains work and contributions from the `scientific community <./CONTRIBUTORS.md>`_.
-This program is not sponsored or maintained by the Universities Space Research Association (USRA),
-the Center for Space Research at the University of Texas (UTCSR), the Jet Propulsion Laboratory (JPL),
-the German Research Centre for Geosciences (GeoForschungsZentrum, GFZ) or NASA.
-It is provided here for your convenience but *with no guarantees whatsoever*.
-
-License
-#######
-
-The content of this project is licensed under the `Creative Commons Attribution 4.0 Attribution license `_ and the source code is licensed under the `MIT license `_.
diff --git a/access/cnes_grace_sync.py b/access/cnes_grace_sync.py
index 00232e12..21c44de8 100755
--- a/access/cnes_grace_sync.py
+++ b/access/cnes_grace_sync.py
@@ -1,5 +1,5 @@
#!/usr/bin/env python
-u"""
+"""
cnes_grace_sync.py
Written by Tyler Sutterley (12/2022)
@@ -91,6 +91,7 @@
added functionality for RL01 and RL03 (future release)
Written 07/2012
"""
+
from __future__ import print_function
import sys
@@ -108,11 +109,13 @@
import posixpath
import gravity_toolkit as gravtk
+
# PURPOSE: sync local GRACE/GRACE-FO files with CNES server
-def cnes_grace_sync(DIRECTORY, DREL=[], TIMEOUT=None, LOG=False,
- CLOBBER=False, MODE=None):
+def cnes_grace_sync(
+ DIRECTORY, DREL=[], TIMEOUT=None, LOG=False, CLOBBER=False, MODE=None
+):
# remote CNES/GRGS host directory
- HOST = ['http://gravitegrace.get.obs-mip.fr','grgs.obs-mip.fr','data']
+ HOST = ['http://gravitegrace.get.obs-mip.fr', 'grgs.obs-mip.fr', 'data']
# check if directory exists and recursively create if not
DIRECTORY = pathlib.Path(DIRECTORY).expanduser().absolute()
@@ -127,27 +130,27 @@ def cnes_grace_sync(DIRECTORY, DREL=[], TIMEOUT=None, LOG=False,
DSET['RL05'] = ['GSM', 'GAA', 'GAB']
# remote path to tar files on CNES servers
- REMOTE = dict(RL01={},RL02={},RL03={},RL04={},RL05={})
+ REMOTE = dict(RL01={}, RL02={}, RL03={}, RL04={}, RL05={})
# RL01: GSM and GAC
- REMOTE['RL01']['GSM'] = ['RL01','variable','archives']
- REMOTE['RL01']['GAC'] = ['RL01','variable','archives']
+ REMOTE['RL01']['GSM'] = ['RL01', 'variable', 'archives']
+ REMOTE['RL01']['GAC'] = ['RL01', 'variable', 'archives']
# RL02: GSM, GAA and GAB
- REMOTE['RL02']['GSM'] = ['RL02','variable','archives']
- REMOTE['RL02']['GAA'] = ['RL02','variable','archives']
- REMOTE['RL02']['GAB'] = ['RL02','variable','archives']
+ REMOTE['RL02']['GSM'] = ['RL02', 'variable', 'archives']
+ REMOTE['RL02']['GAA'] = ['RL02', 'variable', 'archives']
+ REMOTE['RL02']['GAB'] = ['RL02', 'variable', 'archives']
# RL03: GSM, GAA and GAB
- REMOTE['RL03']['GSM'] = ['RL03-v3','archives']
- REMOTE['RL03']['GAA'] = ['RL03','variable','archives']
- REMOTE['RL03']['GAB'] = ['RL03','variable','archives']
+ REMOTE['RL03']['GSM'] = ['RL03-v3', 'archives']
+ REMOTE['RL03']['GAA'] = ['RL03', 'variable', 'archives']
+ REMOTE['RL03']['GAB'] = ['RL03', 'variable', 'archives']
# RL04: GSM
- REMOTE['RL04']['GSM'] = ['RL04-v1','archives']
+ REMOTE['RL04']['GSM'] = ['RL04-v1', 'archives']
# RL05: GSM, GAA, GAB for GRACE/GRACE-FO
- REMOTE['RL05']['GSM'] = ['RL05','archives']
- REMOTE['RL05']['GAA'] = ['RL05','archives']
- REMOTE['RL05']['GAB'] = ['RL05','archives']
+ REMOTE['RL05']['GSM'] = ['RL05', 'archives']
+ REMOTE['RL05']['GAA'] = ['RL05', 'archives']
+ REMOTE['RL05']['GAB'] = ['RL05', 'archives']
# tar file names for each dataset
- TAR = dict(RL01={},RL02={},RL03={},RL04={},RL05={})
+ TAR = dict(RL01={}, RL02={}, RL03={}, RL04={}, RL05={})
# RL01: GSM and GAC
TAR['RL01']['GSM'] = ['GRGS.SH_models.GRACEFORMAT.RL01.tar.gz']
TAR['RL01']['GAC'] = ['GRGS.dealiasing.RL01.tar.gz']
@@ -161,10 +164,14 @@ def cnes_grace_sync(DIRECTORY, DREL=[], TIMEOUT=None, LOG=False,
TAR['RL03']['GAB'] = ['GRGS.RL03.dealiasing.monthly.tar.gz']
# RL04: GSM
# TAR['RL04']['GSM'] = ['CNES.RL04-v1.monthly.OLD_IERS2010_MEAN_POLE_CONVENTION.tar.gz']
- TAR['RL04']['GSM'] = ['CNES.RL04-v1.monthly.NEW_IERS2010_MEAN_POLE_CONVENTION.tar.gz']
+ TAR['RL04']['GSM'] = [
+ 'CNES.RL04-v1.monthly.NEW_IERS2010_MEAN_POLE_CONVENTION.tar.gz'
+ ]
# RL05: GSM, GAA and GAB
- TAR['RL05']['GSM'] = ['CNES-GRGS.RL05.GRACE.monthly.tar.gz',
- 'CNES-GRGS.RL05.GRACE-FO.monthly.tar.gz']
+ TAR['RL05']['GSM'] = [
+ 'CNES-GRGS.RL05.GRACE.monthly.tar.gz',
+ 'CNES-GRGS.RL05.GRACE-FO.monthly.tar.gz',
+ ]
TAR['RL05']['GAA'] = ['CNES-GRGS.RL05.monthly.dealiasing.tar.gz']
TAR['RL05']['GAB'] = ['CNES-GRGS.RL05.monthly.dealiasing.tar.gz']
@@ -172,7 +179,7 @@ def cnes_grace_sync(DIRECTORY, DREL=[], TIMEOUT=None, LOG=False,
if LOG:
# output to log file
# format: CNES_sync_2002-04-01.log
- today = time.strftime('%Y-%m-%d',time.localtime())
+ today = time.strftime('%Y-%m-%d', time.localtime())
LOGFILE = DIRECTORY.joinpath(f'CNES_sync_{today}.log')
fid1 = LOGFILE.open(mode='w', encoding='utf8')
logging.basicConfig(stream=fid1, level=logging.INFO)
@@ -200,18 +207,27 @@ def cnes_grace_sync(DIRECTORY, DREL=[], TIMEOUT=None, LOG=False,
local_file = DIRECTORY.joinpath('CNES', rl, t)
MD5 = gravtk.utilities.get_hash(local_file)
# copy remote tar file to local if new or updated
- gravtk.utilities.from_http(remote_tar_path,
- local=local_file, timeout=TIMEOUT, hash=MD5, chunk=16384,
- verbose=True, fid=fid1, mode=MODE)
+ gravtk.utilities.from_http(
+ remote_tar_path,
+ local=local_file,
+ timeout=TIMEOUT,
+ hash=MD5,
+ chunk=16384,
+ verbose=True,
+ fid=fid1,
+ mode=MODE,
+ )
# Create and submit request to get modification time of file
remote_file = posixpath.join(*remote_tar_path)
request = gravtk.utilities.urllib2.Request(remote_file)
- response = gravtk.utilities.urllib2.urlopen(request,
- timeout=TIMEOUT)
+ response = gravtk.utilities.urllib2.urlopen(
+ request, timeout=TIMEOUT
+ )
# change modification time to remote
time_string = response.headers['last-modified']
- remote_mtime = gravtk.utilities.get_unix_time(time_string,
- format='%a, %d %b %Y %H:%M:%S %Z')
+ remote_mtime = gravtk.utilities.get_unix_time(
+ time_string, format='%a, %d %b %Y %H:%M:%S %Z'
+ )
# keep remote modification time of file and local access time
os.utime(local_file, (local_file.stat().st_atime, remote_mtime))
@@ -219,7 +235,9 @@ def cnes_grace_sync(DIRECTORY, DREL=[], TIMEOUT=None, LOG=False,
tar = tarfile.open(name=local_file, mode='r:gz')
# copy files from the tar file into the data directory
- member_list=[m for m in tar.getmembers() if re.search(ds,m.name)]
+ member_list = [
+ m for m in tar.getmembers() if re.search(ds, m.name)
+ ]
# for each member of the dataset within the tar file
for member in member_list:
# local gzipped version of the file
@@ -230,8 +248,9 @@ def cnes_grace_sync(DIRECTORY, DREL=[], TIMEOUT=None, LOG=False,
tar.close()
# find GRACE files and sort by date
- grace_files = [f.name for f in local_dir.iterdir()
- if re.search(ds, f.name)]
+ grace_files = [
+ f.name for f in local_dir.iterdir() if re.search(ds, f.name)
+ ]
# outputting GRACE filenames to index
index_file = local_dir.joinpath('index.txt')
with index_file.open(mode='w', encoding='utf8') as fid:
@@ -245,6 +264,7 @@ def cnes_grace_sync(DIRECTORY, DREL=[], TIMEOUT=None, LOG=False,
fid1.close()
LOGFILE.chmod(mode=MODE)
+
# PURPOSE: copy file from tar file checking if file exists locally
# and if the original file is newer than the local file
def gzip_copy_file(tar, member, local_file, CLOBBER, MODE):
@@ -261,9 +281,9 @@ def gzip_copy_file(tar, member, local_file, CLOBBER, MODE):
fileobj = fileID.fileobj
fileobj.seek(4)
# extract little endian 4 bit unsigned integer
- file2_mtime, = struct.unpack(" file2_mtime):
+ if file1_mtime > file2_mtime:
TEST = True
OVERWRITE = ' (overwrite)'
else:
@@ -283,6 +303,7 @@ def gzip_copy_file(tar, member, local_file, CLOBBER, MODE):
os.utime(local_file, (local_file.stat().st_atime, file1_mtime))
local_file.chmod(mode=MODE)
+
# PURPOSE: create argument parser
def arguments():
parser = argparse.ArgumentParser(
@@ -292,45 +313,78 @@ def arguments():
)
# command line parameters
# working data directory
- parser.add_argument('--directory','-D',
- type=pathlib.Path, default=pathlib.Path.cwd(),
- help='Working data directory')
+ parser.add_argument(
+ '--directory',
+ '-D',
+ type=pathlib.Path,
+ default=gravtk.utilities.get_cache_path(ensure_exists=False),
+ help='Working data directory',
+ )
# GRACE/GRACE-FO data release
- parser.add_argument('--release','-r',
- metavar='DREL', type=str, nargs='+',
- default=['RL05'], choices=['RL01','RL02','RL03','RL04','RL05'],
- help='GRACE/GRACE-FO data release')
+ parser.add_argument(
+ '--release',
+ '-r',
+ metavar='DREL',
+ type=str,
+ nargs='+',
+ default=['RL05'],
+ choices=['RL01', 'RL02', 'RL03', 'RL04', 'RL05'],
+ help='GRACE/GRACE-FO data release',
+ )
# connection timeout
- parser.add_argument('--timeout','-t',
- type=int, default=360,
- help='Timeout in seconds for blocking operations')
+ parser.add_argument(
+ '--timeout',
+ '-t',
+ type=int,
+ default=360,
+ help='Timeout in seconds for blocking operations',
+ )
# Output log file in form
# CNES_sync_2002-04-01.log
- parser.add_argument('--log','-l',
- default=False, action='store_true',
- help='Output log file')
- parser.add_argument('--clobber','-C',
- default=False, action='store_true',
- help='Overwrite existing data in transfer')
+ parser.add_argument(
+ '--log',
+ '-l',
+ default=False,
+ action='store_true',
+ help='Output log file',
+ )
+ parser.add_argument(
+ '--clobber',
+ '-C',
+ default=False,
+ action='store_true',
+ help='Overwrite existing data in transfer',
+ )
# permissions mode of the directories and files synced (number in octal)
- parser.add_argument('--mode','-M',
- type=lambda x: int(x,base=8), default=0o775,
- help='Permission mode of directories and files synced')
+ parser.add_argument(
+ '--mode',
+ '-M',
+ type=lambda x: int(x, base=8),
+ default=0o775,
+ help='Permission mode of directories and files synced',
+ )
# return the parser
return parser
+
# This is the main part of the program that calls the individual functions
def main():
# Read the system arguments listed after the program
parser = arguments()
- args,_ = parser.parse_known_args()
+ args, _ = parser.parse_known_args()
# check internet connection before attempting to run program
HOST = 'http://gravitegrace.get.obs-mip.fr'
if gravtk.utilities.check_connection(HOST):
- cnes_grace_sync(args.directory, DREL=args.release,
- TIMEOUT=args.timeout, LOG=args.log,
- CLOBBER=args.clobber, MODE=args.mode)
+ cnes_grace_sync(
+ args.directory,
+ DREL=args.release,
+ TIMEOUT=args.timeout,
+ LOG=args.log,
+ CLOBBER=args.clobber,
+ MODE=args.mode,
+ )
+
# run main program
if __name__ == '__main__':
diff --git a/access/esa_costg_swarm_sync.py b/access/esa_costg_swarm_sync.py
index 2f9a696e..080baf35 100644
--- a/access/esa_costg_swarm_sync.py
+++ b/access/esa_costg_swarm_sync.py
@@ -1,5 +1,5 @@
#!/usr/bin/env python
-u"""
+"""
esa_costg_swarm_sync.py
Written by Tyler Sutterley (05/2023)
Syncs Swarm gravity field products from the ESA Swarm Science Server
@@ -36,6 +36,7 @@
Updated 10/2021: using python logging for handling verbose output
Written 09/2021
"""
+
from __future__ import print_function
import sys
@@ -52,21 +53,29 @@
import lxml.etree
import gravity_toolkit as gravtk
-# PURPOSE: sync local Swarm files with ESA server
-def esa_costg_swarm_sync(DIRECTORY, RELEASE=None, TIMEOUT=None, LOG=False,
- LIST=False, CLOBBER=False, CHECKSUM=False, MODE=0o775):
+# PURPOSE: sync local Swarm files with ESA server
+def esa_costg_swarm_sync(
+ DIRECTORY,
+ RELEASE=None,
+ TIMEOUT=None,
+ LOG=False,
+ LIST=False,
+ CLOBBER=False,
+ CHECKSUM=False,
+ MODE=0o775,
+):
# check if directory exists and recursively create if not
DIRECTORY = pathlib.Path(DIRECTORY).expanduser().absolute()
# local directory for exact data product
- local_dir = DIRECTORY.joinpath('Swarm',RELEASE,'GSM')
+ local_dir = DIRECTORY.joinpath('Swarm', RELEASE, 'GSM')
local_dir.mkdir(mode=MODE, parents=True, exist_ok=True)
# create log file with list of synchronized files (or print to terminal)
if LOG:
# output to log file
# format: ESA_Swarm_sync_2002-04-01.log
- today = time.strftime('%Y-%m-%d',time.localtime())
+ today = time.strftime('%Y-%m-%d', time.localtime())
LOGFILE = DIRECTORY.joinpath(f'ESA_Swarm_sync_{today}.log')
logging.basicConfig(filename=LOGFILE, level=logging.INFO)
logging.info(f'ESA Swarm Sync Log ({today})')
@@ -81,8 +90,9 @@ def esa_costg_swarm_sync(DIRECTORY, RELEASE=None, TIMEOUT=None, LOG=False,
# compile xml parsers for lxml
XMLparser = lxml.etree.XMLParser()
# create "opener" (OpenerDirector instance)
- gravtk.utilities.build_opener(None, None,
- authorization_header=False, urs=HOST)
+ gravtk.utilities.build_opener(
+ None, None, authorization_header=False, urs=HOST
+ )
# All calls to urllib2.urlopen will now use handler
# Make sure not to include the protocol in with the URL, or
# HTTPPasswordMgrWithDefaultRealm will be confused.
@@ -95,20 +105,24 @@ def esa_costg_swarm_sync(DIRECTORY, RELEASE=None, TIMEOUT=None, LOG=False,
colnames = []
collastmod = []
# position, maximum number of files to list, flag to check if done
- pos,maxfiles,prevmax = (0,500,500)
+ pos, maxfiles, prevmax = (0, 500, 500)
# iterate to get a compiled list of files
# will iterate until there are no more files to add to the lists
- while (maxfiles == prevmax):
+ while maxfiles == prevmax:
# set previous flag to maximum
prevmax = maxfiles
# open connection with Swarm science server at remote directory
# to list maxfiles number of files at position
- parameters = gravtk.utilities.urlencode({'maxfiles':prevmax,
- 'pos':pos,'file':posixpath.join('swarm','Level2longterm','EGF')})
- url=posixpath.join(HOST,f'?do=list&{parameters}')
+ parameters = gravtk.utilities.urlencode(
+ {
+ 'maxfiles': prevmax,
+ 'pos': pos,
+ 'file': posixpath.join('swarm', 'Level2longterm', 'EGF'),
+ }
+ )
+ url = posixpath.join(HOST, f'?do=list&{parameters}')
request = gravtk.utilities.urllib2.Request(url=url)
- response = gravtk.utilities.urllib2.urlopen(request,
- timeout=TIMEOUT)
+ response = gravtk.utilities.urllib2.urlopen(request, timeout=TIMEOUT)
table = json.loads(response.read().decode())
# extend lists with new files
colnames.extend([t['name'] for t in table['results']])
@@ -119,22 +133,33 @@ def esa_costg_swarm_sync(DIRECTORY, RELEASE=None, TIMEOUT=None, LOG=False,
pos += maxfiles
# find lines of valid files
- valid_lines = [i for i,f in enumerate(colnames) if R1.match(f)]
+ valid_lines = [i for i, f in enumerate(colnames) if R1.match(f)]
# write each file to an index
- index_file = local_dir.joinpath(local_dir,'index.txt')
+ index_file = local_dir.joinpath(local_dir, 'index.txt')
fid = index_file.open(mode='w', encoding='utf8')
# for each data and header file
for i in valid_lines:
# remote and local versions of the file
- parameters = gravtk.utilities.urlencode({'file':
- posixpath.join('swarm','Level2longterm','EGF',colnames[i])})
- remote_file = posixpath.join(HOST,
- f'?do=download&{parameters}')
+ parameters = gravtk.utilities.urlencode(
+ {
+ 'file': posixpath.join(
+ 'swarm', 'Level2longterm', 'EGF', colnames[i]
+ )
+ }
+ )
+ remote_file = posixpath.join(HOST, f'?do=download&{parameters}')
local_file = local_dir.joinpath(colnames[i])
# check that file is not in file system unless overwriting
- http_pull_file(remote_file, collastmod[i], local_file,
- TIMEOUT=TIMEOUT, LIST=LIST, CLOBBER=CLOBBER,
- CHECKSUM=CHECKSUM, MODE=MODE)
+ http_pull_file(
+ remote_file,
+ collastmod[i],
+ local_file,
+ TIMEOUT=TIMEOUT,
+ LIST=LIST,
+ CLOBBER=CLOBBER,
+ CHECKSUM=CHECKSUM,
+ MODE=MODE,
+ )
# output Swarm filenames to index
print(colnames[i], file=fid)
# change permissions of index file
@@ -144,10 +169,19 @@ def esa_costg_swarm_sync(DIRECTORY, RELEASE=None, TIMEOUT=None, LOG=False,
if LOG:
LOGFILE.chmod(mode=MODE)
+
# PURPOSE: pull file from a remote host checking if file exists locally
# and if the remote file is newer than the local file
-def http_pull_file(remote_file, remote_mtime, local_file, TIMEOUT=120,
- LIST=False, CLOBBER=False, CHECKSUM=False, MODE=0o775):
+def http_pull_file(
+ remote_file,
+ remote_mtime,
+ local_file,
+ TIMEOUT=120,
+ LIST=False,
+ CLOBBER=False,
+ CHECKSUM=False,
+ MODE=0o775,
+):
# if file exists in file system: check if remote file is newer
TEST = False
OVERWRITE = ' (clobber)'
@@ -161,22 +195,23 @@ def http_pull_file(remote_file, remote_mtime, local_file, TIMEOUT=120,
# There are a wide range of exceptions that can be thrown here
# including HTTPError and URLError.
req = gravtk.utilities.urllib2.Request(remote_file)
- resp = gravtk.utilities.urllib2.urlopen(req,timeout=TIMEOUT)
+ resp = gravtk.utilities.urllib2.urlopen(req, timeout=TIMEOUT)
# copy remote file contents to bytesIO object
remote_buffer = io.BytesIO(resp.read())
remote_buffer.seek(0)
# generate checksum hash for remote file
remote_hash = gravtk.utilities.get_hash(remote_buffer)
# compare checksums
- if (local_hash != remote_hash):
+ if local_hash != remote_hash:
TEST = True
OVERWRITE = f' (checksums: {local_hash} {remote_hash})'
elif local_file.exists():
# check last modification time of local file
local_mtime = local_file.stat().st_mtime
# if remote file is newer: overwrite the local file
- if (gravtk.utilities.even(remote_mtime) >
- gravtk.utilities.even(local_mtime)):
+ if gravtk.utilities.even(remote_mtime) > gravtk.utilities.even(
+ local_mtime
+ ):
TEST = True
OVERWRITE = ' (overwrite)'
else:
@@ -202,8 +237,9 @@ def http_pull_file(remote_file, remote_mtime, local_file, TIMEOUT=120,
# There are a range of exceptions that can be thrown here
# including HTTPError and URLError.
request = gravtk.utilities.urllib2.Request(remote_file)
- response = gravtk.utilities.urllib2.urlopen(request,
- timeout=TIMEOUT)
+ response = gravtk.utilities.urllib2.urlopen(
+ request, timeout=TIMEOUT
+ )
# copy remote file contents to local file
with local_file.open(mode='wb') as f:
shutil.copyfileobj(response, f, CHUNK)
@@ -211,6 +247,7 @@ def http_pull_file(remote_file, remote_mtime, local_file, TIMEOUT=120,
os.utime(local_file, (local_file.stat().st_atime, remote_mtime))
local_file.chmod(mode=MODE)
+
# PURPOSE: create argument parser
def arguments():
parser = argparse.ArgumentParser(
@@ -220,39 +257,72 @@ def arguments():
)
# command line parameters
# working data directory
- parser.add_argument('--directory','-D',
- type=pathlib.Path, default=pathlib.Path.cwd(),
- help='Working data directory')
+ parser.add_argument(
+ '--directory',
+ '-D',
+ type=pathlib.Path,
+ default=gravtk.utilities.get_cache_path(ensure_exists=False),
+ help='Working data directory',
+ )
# data release
- parser.add_argument('--release','-r',
- type=str, default='RL01', choices=['RL01'],
- help='Data release to sync')
+ parser.add_argument(
+ '--release',
+ '-r',
+ type=str,
+ default='RL01',
+ choices=['RL01'],
+ help='Data release to sync',
+ )
# connection timeout
- parser.add_argument('--timeout','-t',
- type=int, default=360,
- help='Timeout in seconds for blocking operations')
+ parser.add_argument(
+ '--timeout',
+ '-t',
+ type=int,
+ default=360,
+ help='Timeout in seconds for blocking operations',
+ )
# Output log file in form
# ESA_Swarm_sync_2002-04-01.log
- parser.add_argument('--log','-l',
- default=False, action='store_true',
- help='Output log file')
+ parser.add_argument(
+ '--log',
+ '-l',
+ default=False,
+ action='store_true',
+ help='Output log file',
+ )
# sync options
- parser.add_argument('--list','-L',
- default=False, action='store_true',
- help='Only print files that could be transferred')
- parser.add_argument('--clobber','-C',
- default=False, action='store_true',
- help='Overwrite existing data in transfer')
- parser.add_argument('--checksum',
- default=False, action='store_true',
- help='Compare hashes to check for overwriting existing data')
+ parser.add_argument(
+ '--list',
+ '-L',
+ default=False,
+ action='store_true',
+ help='Only print files that could be transferred',
+ )
+ parser.add_argument(
+ '--clobber',
+ '-C',
+ default=False,
+ action='store_true',
+ help='Overwrite existing data in transfer',
+ )
+ parser.add_argument(
+ '--checksum',
+ default=False,
+ action='store_true',
+ help='Compare hashes to check for overwriting existing data',
+ )
# permissions mode of the directories and files synced (number in octal)
- parser.add_argument('--mode','-M',
- type=lambda x: int(x,base=8), default=0o775,
- help='Permission mode of directories and files synced')
+ parser.add_argument(
+ '--mode',
+ '-M',
+ type=lambda x: int(x, base=8),
+ default=0o775,
+ help='Permission mode of directories and files synced',
+ )
# return the parser
return parser
+
# This is the main part of the program that calls the individual functions
def main():
# Read the system arguments listed after the program
@@ -262,9 +332,17 @@ def main():
# check internet connection before attempting to run program
HOST = 'https://swarm-diss.eo.esa.int'
if gravtk.utilities.check_connection(HOST):
- esa_costg_swarm_sync(args.directory, RELEASE=args.release,
- TIMEOUT=args.timeout, LOG=args.log, LIST=args.list,
- CLOBBER=args.clobber, CHECKSUM=args.checksum, MODE=args.mode)
+ esa_costg_swarm_sync(
+ args.directory,
+ RELEASE=args.release,
+ TIMEOUT=args.timeout,
+ LOG=args.log,
+ LIST=args.list,
+ CLOBBER=args.clobber,
+ CHECKSUM=args.checksum,
+ MODE=args.mode,
+ )
+
# run main program
if __name__ == '__main__':
diff --git a/access/gfz_icgem_costg_ftp.py b/access/gfz_icgem_costg_ftp.py
index 9b7b0e89..33b1aec1 100644
--- a/access/gfz_icgem_costg_ftp.py
+++ b/access/gfz_icgem_costg_ftp.py
@@ -1,5 +1,5 @@
#!/usr/bin/env python
-u"""
+"""
gfz_icgem_costg_ftp.py
Written by Tyler Sutterley (05/2023)
Syncs GRACE/GRACE-FO/Swarm COST-G data from the GFZ International
@@ -45,6 +45,7 @@
Updated 10/2021: using python logging for handling verbose output
Written 09/2021
"""
+
from __future__ import print_function
import sys
@@ -60,31 +61,41 @@
import posixpath
import gravity_toolkit as gravtk
+
# PURPOSE: create and compile regular expression operator to find files
def compile_regex_pattern(MISSION, DSET):
- if ((DSET == 'GSM') and (MISSION == 'Swarm')):
+ if (DSET == 'GSM') and (MISSION == 'Swarm'):
# regular expression operators for Swarm data
- regex=r'(SW)_(.*?)_(EGF_SHA_2)__(.*?)_(.*?)_(.*?)(\.gfc|\.ZIP)'
- elif ((DSET != 'GSM') and (MISSION == 'Swarm')):
- regex=r'(GAA|GAB|GAC|GAD)_Swarm_(\d+)_(\d{2})_(\d{4})(\.gfc|\.ZIP)'
+ regex = r'(SW)_(.*?)_(EGF_SHA_2)__(.*?)_(.*?)_(.*?)(\.gfc|\.ZIP)'
+ elif (DSET != 'GSM') and (MISSION == 'Swarm'):
+ regex = r'(GAA|GAB|GAC|GAD)_Swarm_(\d+)_(\d{2})_(\d{4})(\.gfc|\.ZIP)'
else:
- regex=rf'{DSET}-2_(.*?)\.gfc$'
+ regex = rf'{DSET}-2_(.*?)\.gfc$'
# return the compiled regular expression operator used to find files
return re.compile(regex, re.VERBOSE)
-# PURPOSE: sync local GRACE/GRACE-FO/Swarm files with GFZ ICGEM server
-def gfz_icgem_costg_ftp(DIRECTORY, MISSION=[], RELEASE=None, TIMEOUT=None,
- LOG=False, LIST=False, CLOBBER=False, CHECKSUM=False, MODE=None):
+# PURPOSE: sync local GRACE/GRACE-FO/Swarm files with GFZ ICGEM server
+def gfz_icgem_costg_ftp(
+ DIRECTORY,
+ MISSION=[],
+ RELEASE=None,
+ TIMEOUT=None,
+ LOG=False,
+ LIST=False,
+ CLOBBER=False,
+ CHECKSUM=False,
+ MODE=None,
+):
# check if directory exists and recursively create if not
DIRECTORY = pathlib.Path(DIRECTORY).expanduser().absolute()
DIRECTORY.mkdir(mode=MODE, parents=True, exist_ok=True)
# dealiasing datasets for each mission
DSET = {}
- DSET['Grace'] = ['GAC','GSM']
+ DSET['Grace'] = ['GAC', 'GSM']
DSET['Grace-FO'] = ['GSM']
- DSET['Swarm'] = ['GAA','GAB','GAC','GAD','GSM']
+ DSET['Swarm'] = ['GAA', 'GAB', 'GAC', 'GAD', 'GSM']
# local subdirectory for data
LOCAL = {}
LOCAL['Grace'] = 'COSTG'
@@ -95,7 +106,7 @@ def gfz_icgem_costg_ftp(DIRECTORY, MISSION=[], RELEASE=None, TIMEOUT=None,
if LOG:
# output to log file
# format: GFZ_ICGEM_COST-G_sync_2002-04-01.log
- today = time.strftime('%Y-%m-%d',time.localtime())
+ today = time.strftime('%Y-%m-%d', time.localtime())
LOGFILE = DIRECTORY.joinpath(f'GFZ_ICGEM_COST-G_sync_{today}.log')
logging.basicConfig(filename=LOGFILE, level=logging.INFO)
logging.info(f'GFZ ICGEM COST-G Sync Log ({today})')
@@ -121,33 +132,42 @@ def gfz_icgem_costg_ftp(DIRECTORY, MISSION=[], RELEASE=None, TIMEOUT=None,
# compile the regular expression operator to find files
R1 = compile_regex_pattern(MISSION, ds)
# set the remote path to download files
- if ds in ('GAA','GAB','GAC','GAD') and (MISSION == 'Swarm'):
- remote_path = [ftp.host,'02_COST-G',MISSION,'GAX_products',ds]
- elif ds in ('GAA','GAB','GAC','GAD') and (MISSION != 'Swarm'):
- remote_path = [ftp.host,'02_COST-G',MISSION,'GAX_products']
- elif (MISSION == 'Swarm'):
- remote_path = [ftp.host,'02_COST-G',MISSION,'40x40']
- elif (MISSION == 'Grace'):
- remote_path = [ftp.host,'02_COST-G',MISSION,'unfiltered']
- elif (MISSION == 'Grace-FO'):
- remote_path = [ftp.host,'02_COST-G',MISSION]
+ if ds in ('GAA', 'GAB', 'GAC', 'GAD') and (MISSION == 'Swarm'):
+ remote_path = [ftp.host, '02_COST-G', MISSION, 'GAX_products', ds]
+ elif ds in ('GAA', 'GAB', 'GAC', 'GAD') and (MISSION != 'Swarm'):
+ remote_path = [ftp.host, '02_COST-G', MISSION, 'GAX_products']
+ elif MISSION == 'Swarm':
+ remote_path = [ftp.host, '02_COST-G', MISSION, '40x40']
+ elif MISSION == 'Grace':
+ remote_path = [ftp.host, '02_COST-G', MISSION, 'unfiltered']
+ elif MISSION == 'Grace-FO':
+ remote_path = [ftp.host, '02_COST-G', MISSION]
# get filenames from remote directory
- remote_files,remote_mtimes = gravtk.utilities.ftp_list(
- remote_path, timeout=TIMEOUT, basename=True, pattern=R1,
- sort=True)
+ remote_files, remote_mtimes = gravtk.utilities.ftp_list(
+ remote_path, timeout=TIMEOUT, basename=True, pattern=R1, sort=True
+ )
# download the file from the ftp server
- for fi,remote_mtime in zip(remote_files,remote_mtimes):
+ for fi, remote_mtime in zip(remote_files, remote_mtimes):
# remote and local versions of the file
remote_path.append(fi)
local_file = local_dir.joinpath(fi)
- ftp_mirror_file(ftp, remote_path, remote_mtime,
- local_file, TIMEOUT=TIMEOUT, LIST=LIST,
- CLOBBER=CLOBBER, CHECKSUM=CHECKSUM, MODE=MODE)
+ ftp_mirror_file(
+ ftp,
+ remote_path,
+ remote_mtime,
+ local_file,
+ TIMEOUT=TIMEOUT,
+ LIST=LIST,
+ CLOBBER=CLOBBER,
+ CHECKSUM=CHECKSUM,
+ MODE=MODE,
+ )
# remove the file from the remote path list
remote_path.remove(fi)
# find local GRACE/GRACE-FO/Swarm files to create index
- grace_files = sorted([f.name for f in local_dir.iterdir()
- if R1.match(f.name)])
+ grace_files = sorted(
+ [f.name for f in local_dir.iterdir() if R1.match(f.name)]
+ )
# write each file to an index
index_file = local_dir.joinpath('index.txt')
with index_file.open(mode='w', encoding='utf8') as fid:
@@ -163,10 +183,20 @@ def gfz_icgem_costg_ftp(DIRECTORY, MISSION=[], RELEASE=None, TIMEOUT=None,
if LOG:
LOGFILE.chmod(mode=MODE)
+
# PURPOSE: pull file from a remote host checking if file exists locally
# and if the remote file is newer than the local file
-def ftp_mirror_file(ftp,remote_path,remote_mtime,local_file,
- TIMEOUT=None,LIST=False,CLOBBER=False,CHECKSUM=False,MODE=0o775):
+def ftp_mirror_file(
+ ftp,
+ remote_path,
+ remote_mtime,
+ local_file,
+ TIMEOUT=None,
+ LIST=False,
+ CLOBBER=False,
+ CHECKSUM=False,
+ MODE=0o775,
+):
# if file exists in file system: check if remote file is newer
TEST = False
OVERWRITE = ' (clobber)'
@@ -177,20 +207,20 @@ def ftp_mirror_file(ftp,remote_path,remote_mtime,local_file,
# open the local_file in binary read mode
local_hash = gravtk.utilities.get_hash(local_file)
# copy remote file contents to bytesIO object
- remote_buffer = gravtk.utilities.from_ftp(remote_path,
- timeout=TIMEOUT)
+ remote_buffer = gravtk.utilities.from_ftp(remote_path, timeout=TIMEOUT)
# generate checksum hash for remote file
remote_hash = hashlib.md5(remote_buffer.getvalue()).hexdigest()
# compare checksums
- if (local_hash != remote_hash):
+ if local_hash != remote_hash:
TEST = True
OVERWRITE = f' (checksums: {local_hash} {remote_hash})'
elif local_file.exists():
# check last modification time of local file
local_mtime = local_file.stat().st_mtime
# if remote file is newer: overwrite the local file
- if (gravtk.utilities.even(remote_mtime) >
- gravtk.utilities.even(local_mtime)):
+ if gravtk.utilities.even(remote_mtime) > gravtk.utilities.even(
+ local_mtime
+ ):
TEST = True
OVERWRITE = ' (overwrite)'
else:
@@ -199,7 +229,7 @@ def ftp_mirror_file(ftp,remote_path,remote_mtime,local_file,
# if file does not exist locally, is to be overwritten, or CLOBBER is set
if TEST or CLOBBER:
# Printing files transferred
- remote_ftp_url = posixpath.join('ftp://',*remote_path)
+ remote_ftp_url = posixpath.join('ftp://', *remote_path)
logging.info(f'{remote_ftp_url} -->')
logging.info(f'\t{str(local_file)}{OVERWRITE}\n')
# if executing copy command (not only printing the files)
@@ -220,6 +250,7 @@ def ftp_mirror_file(ftp,remote_path,remote_mtime,local_file,
os.utime(local_file, (local_file.stat().st_atime, remote_mtime))
local_file.chmod(mode=MODE)
+
# PURPOSE: create argument parser
def arguments():
parser = argparse.ArgumentParser(
@@ -229,62 +260,108 @@ def arguments():
)
# command line parameters
# working data directory
- parser.add_argument('--directory','-D',
- type=pathlib.Path, default=pathlib.Path.cwd(),
- help='Working data directory')
+ parser.add_argument(
+ '--directory',
+ '-D',
+ type=pathlib.Path,
+ default=gravtk.utilities.get_cache_path(ensure_exists=False),
+ help='Working data directory',
+ )
# mission (GRACE, GRACE Follow-On or Swarm)
- choices = ['Grace','Grace-FO','Swarm']
- parser.add_argument('--mission','-m',
- type=str, nargs='+',
- default=['Grace','Grace-FO','Swarm'], choices=choices,
- help='Mission to sync between GRACE, GRACE-FO and Swarm')
+ choices = ['Grace', 'Grace-FO', 'Swarm']
+ parser.add_argument(
+ '--mission',
+ '-m',
+ type=str,
+ nargs='+',
+ default=['Grace', 'Grace-FO', 'Swarm'],
+ choices=choices,
+ help='Mission to sync between GRACE, GRACE-FO and Swarm',
+ )
# data release
- parser.add_argument('--release','-r',
- type=str, default='RL01', choices=['RL01'],
- help='Data release to sync')
+ parser.add_argument(
+ '--release',
+ '-r',
+ type=str,
+ default='RL01',
+ choices=['RL01'],
+ help='Data release to sync',
+ )
# connection timeout
- parser.add_argument('--timeout','-t',
- type=int, default=360,
- help='Timeout in seconds for blocking operations')
+ parser.add_argument(
+ '--timeout',
+ '-t',
+ type=int,
+ default=360,
+ help='Timeout in seconds for blocking operations',
+ )
# Output log file in form
# GFZ_ICGEM_COST-G_sync_2002-04-01.log
- parser.add_argument('--log','-l',
- default=False, action='store_true',
- help='Output log file')
+ parser.add_argument(
+ '--log',
+ '-l',
+ default=False,
+ action='store_true',
+ help='Output log file',
+ )
# sync options
- parser.add_argument('--list','-L',
- default=False, action='store_true',
- help='Only print files that could be transferred')
- parser.add_argument('--checksum',
- default=False, action='store_true',
- help='Compare hashes to check for overwriting existing data')
- parser.add_argument('--clobber','-C',
- default=False, action='store_true',
- help='Overwrite existing data in transfer')
+ parser.add_argument(
+ '--list',
+ '-L',
+ default=False,
+ action='store_true',
+ help='Only print files that could be transferred',
+ )
+ parser.add_argument(
+ '--checksum',
+ default=False,
+ action='store_true',
+ help='Compare hashes to check for overwriting existing data',
+ )
+ parser.add_argument(
+ '--clobber',
+ '-C',
+ default=False,
+ action='store_true',
+ help='Overwrite existing data in transfer',
+ )
# permissions mode of the directories and files synced (number in octal)
- parser.add_argument('--mode','-M',
- type=lambda x: int(x,base=8), default=0o775,
- help='Permission mode of directories and files synced')
+ parser.add_argument(
+ '--mode',
+ '-M',
+ type=lambda x: int(x, base=8),
+ default=0o775,
+ help='Permission mode of directories and files synced',
+ )
# return the parser
return parser
+
# This is the main part of the program that calls the individual functions
def main():
# Read the system arguments listed after the program
parser = arguments()
- args,_ = parser.parse_known_args()
+ args, _ = parser.parse_known_args()
# check internet connection before attempting to run program
HOST = 'icgem.gfz-potsdam.de'
if gravtk.utilities.check_ftp_connection(HOST):
for m in args.mission:
- gfz_icgem_costg_ftp(args.directory, MISSION=m,
- RELEASE=args.release, TIMEOUT=args.timeout,
- LIST=args.list, LOG=args.log, CLOBBER=args.clobber,
- CHECKSUM=args.checksum, MODE=args.mode)
+ gfz_icgem_costg_ftp(
+ args.directory,
+ MISSION=m,
+ RELEASE=args.release,
+ TIMEOUT=args.timeout,
+ LIST=args.list,
+ LOG=args.log,
+ CLOBBER=args.clobber,
+ CHECKSUM=args.checksum,
+ MODE=args.mode,
+ )
else:
raise RuntimeError('Check internet connection')
+
# run main program
if __name__ == '__main__':
main()
diff --git a/access/gfz_isdc_dealiasing_sync.py b/access/gfz_isdc_dealiasing_sync.py
index f51f9b78..e84bb695 100644
--- a/access/gfz_isdc_dealiasing_sync.py
+++ b/access/gfz_isdc_dealiasing_sync.py
@@ -1,5 +1,5 @@
#!/usr/bin/env python
-u"""
+"""
gfz_isdc_dealiasing_sync.py
Written by Tyler Sutterley (10/2025)
Syncs GRACE Level-1b dealiasing products from the GFZ Information
@@ -48,6 +48,7 @@
Updated 03/2018: made tar file creation optional with --tar
Written 03/2018
"""
+
from __future__ import print_function
import sys
@@ -63,20 +64,30 @@
import posixpath
import gravity_toolkit as gravtk
+
# PURPOSE: syncs GRACE Level-1b dealiasing products from the GFZ data server
# and optionally outputs as monthly tar files
-def gfz_isdc_dealiasing_sync(base_dir, DREL, YEAR=None, MONTHS=None, TAR=False,
- TIMEOUT=None, LOG=False, CLOBBER=False, MODE=None):
+def gfz_isdc_dealiasing_sync(
+ base_dir,
+ DREL,
+ YEAR=None,
+ MONTHS=None,
+ TAR=False,
+ TIMEOUT=None,
+ LOG=False,
+ CLOBBER=False,
+ MODE=None,
+):
# check if directory exists and recursively create if not
base_dir = pathlib.Path(base_dir).expanduser().absolute()
- grace_dir = base_dir.joinpath('AOD1B',DREL)
+ grace_dir = base_dir.joinpath('AOD1B', DREL)
grace_dir.mkdir(mode=MODE, parents=True, exist_ok=True)
# create log file with list of synchronized files (or print to terminal)
if LOG:
# output to log file
# format: GFZ_AOD1B_sync_2002-04-01.log
- today = time.strftime('%Y-%m-%d',time.localtime())
+ today = time.strftime('%Y-%m-%d', time.localtime())
LOGFILE = base_dir.joinpath(f'GFZ_AOD1B_sync_{today}.log')
logging.basicConfig(filename=LOGFILE, level=logging.INFO)
logging.info(f'GFZ AOD1b Sync Log ({today})')
@@ -98,14 +109,19 @@ def gfz_isdc_dealiasing_sync(base_dir, DREL, YEAR=None, MONTHS=None, TAR=False,
SUFFIX = dict(RL04='tar.gz', RL05='tar.gz', RL06='tgz')
# find remote yearly directories for DREL
- YRS,_ = http_list([HOST,'grace','Level-1B', 'GFZ','AOD',DREL],
- timeout=TIMEOUT, basename=True, pattern=R1, sort=True)
+ YRS, _ = http_list(
+ [HOST, 'grace', 'Level-1B', 'GFZ', 'AOD', DREL],
+ timeout=TIMEOUT,
+ basename=True,
+ pattern=R1,
+ sort=True,
+ )
# for each year
for Y in YRS:
# for each month of interest
for M in MONTHS:
# output tar file for year and month
- args = (Y, M, DREL.replace('RL',''), SUFFIX[DREL])
+ args = (Y, M, DREL.replace('RL', ''), SUFFIX[DREL])
FILE = 'AOD1B_{0}-{1:02d}_{2}.{3}'.format(*args)
# check if output tar file exists (if TAR)
local_tar_file = grace_dir.joinpath(FILE)
@@ -113,22 +129,36 @@ def gfz_isdc_dealiasing_sync(base_dir, DREL, YEAR=None, MONTHS=None, TAR=False,
# compile regular expressions operators for file dates
# will extract year and month and calendar day from the ascii file
regex_pattern = r'AOD1B_({0})-({1:02d})-(\d+)_X_\d+.asc.gz$'
- R2 = re.compile(regex_pattern.format(Y,M), re.VERBOSE)
- remote_files,remote_mtimes = http_list(
- [HOST,'grace','Level-1B','GFZ','AOD',DREL,Y],
- timeout=TIMEOUT, basename=True, pattern=R2, sort=True)
+ R2 = re.compile(regex_pattern.format(Y, M), re.VERBOSE)
+ remote_files, remote_mtimes = http_list(
+ [HOST, 'grace', 'Level-1B', 'GFZ', 'AOD', DREL, Y],
+ timeout=TIMEOUT,
+ basename=True,
+ pattern=R2,
+ sort=True,
+ )
file_count = len(remote_files)
# if compressing into monthly tar files
if TAR and (file_count > 0) and (TEST or CLOBBER):
# copy each gzip file and store within monthly tar files
tar = tarfile.open(name=local_tar_file, mode='w:gz')
- for fi,remote_mtime in zip(remote_files,remote_mtimes):
+ for fi, remote_mtime in zip(remote_files, remote_mtimes):
# remote version of each input file
- remote = [HOST,'grace','Level-1B','GFZ','AOD',DREL,Y,fi]
+ remote = [
+ HOST,
+ 'grace',
+ 'Level-1B',
+ 'GFZ',
+ 'AOD',
+ DREL,
+ Y,
+ fi,
+ ]
logging.info(posixpath.join(*remote))
# retrieve bytes from remote file
- remote_buffer = gravtk.utilities.from_sync(remote,
- timeout=TIMEOUT)
+ remote_buffer = gravtk.utilities.from_sync(
+ remote, timeout=TIMEOUT
+ )
# add file to tar
tar_info = tarfile.TarInfo(name=fi)
tar_info.mtime = remote_mtime
@@ -140,25 +170,40 @@ def gfz_isdc_dealiasing_sync(base_dir, DREL, YEAR=None, MONTHS=None, TAR=False,
local_tar_file.chmod(mode=MODE)
elif (file_count > 0) and not TAR:
# copy each gzip file and keep as individual daily files
- for fi,remote_mtime in zip(remote_files,remote_mtimes):
+ for fi, remote_mtime in zip(remote_files, remote_mtimes):
# remote and local version of each input file
- remote = [HOST,'grace','Level-1B','GFZ','AOD',DREL,Y,fi]
+ remote = [
+ HOST,
+ 'grace',
+ 'Level-1B',
+ 'GFZ',
+ 'AOD',
+ DREL,
+ Y,
+ fi,
+ ]
local_file = grace_dir.joinpath(fi)
- http_pull_file(remote,remote_mtime,local_file,
- CLOBBER=CLOBBER, MODE=MODE)
+ http_pull_file(
+ remote,
+ remote_mtime,
+ local_file,
+ CLOBBER=CLOBBER,
+ MODE=MODE,
+ )
# close log file and set permissions level to MODE
if LOG:
LOGFILE.chmod(mode=MODE)
+
# PURPOSE: list a directory on the GFZ https server
def http_list(
- HOST: str | list,
- timeout: int | None = None,
- context: ssl.SSLContext = gravtk.utilities._default_ssl_context,
- pattern: str | re.Pattern = '',
- sort: bool = False
- ):
+ HOST: str | list,
+ timeout: int | None = None,
+ context: ssl.SSLContext = gravtk.utilities._default_ssl_context,
+ pattern: str | re.Pattern = '',
+ sort: bool = False,
+):
"""
List a directory on the GFZ https Server
@@ -192,8 +237,9 @@ def http_list(
try:
# Create and submit request.
request = gravtk.utilities.urllib2.Request(posixpath.join(*HOST))
- response = gravtk.utilities.urllib2.urlopen(request,
- timeout=timeout, context=context)
+ response = gravtk.utilities.urllib2.urlopen(
+ request, timeout=timeout, context=context
+ )
except Exception as exc:
raise Exception('List error from {0}'.format(posixpath.join(*HOST)))
# read the directory listing
@@ -201,32 +247,41 @@ def http_list(
# read and parse request for files (column names and modified times)
lines = [l for l in contents if rx.search(l.decode('utf-8'))]
# column names and last modified times
- colnames = [None]*len(lines)
- collastmod = [None]*len(lines)
+ colnames = [None] * len(lines)
+ collastmod = [None] * len(lines)
for i, l in enumerate(lines):
colnames[i], lastmod = rx.findall(l.decode('utf-8')).pop()
# get the Unix timestamp value for a modification time
- collastmod[i] = gravtk.utilities.get_unix_time(lastmod,
- format='%Y-%m-%d %H:%M')
+ collastmod[i] = gravtk.utilities.get_unix_time(
+ lastmod, format='%Y-%m-%d %H:%M'
+ )
# reduce using regular expression pattern
if pattern:
- i = [i for i,f in enumerate(colnames) if re.search(pattern, f)]
+ i = [i for i, f in enumerate(colnames) if re.search(pattern, f)]
# reduce list of column names and last modified times
colnames = [colnames[indice] for indice in i]
collastmod = [collastmod[indice] for indice in i]
# sort the list
if sort:
- i = [i for i,j in sorted(enumerate(colnames), key=lambda i: i[1])]
+ i = [i for i, j in sorted(enumerate(colnames), key=lambda i: i[1])]
# sort list of column names and last modified times
colnames = [colnames[indice] for indice in i]
collastmod = [collastmod[indice] for indice in i]
# return the list of column names and last modified times
return (colnames, collastmod)
+
# PURPOSE: pull file from a remote host checking if file exists locally
# and if the remote file is newer than the local file
-def http_pull_file(remote_path, remote_mtime, local_file,
- TIMEOUT=0, LIST=False, CLOBBER=False, MODE=0o775):
+def http_pull_file(
+ remote_path,
+ remote_mtime,
+ local_file,
+ TIMEOUT=0,
+ LIST=False,
+ CLOBBER=False,
+ MODE=0o775,
+):
# verify inputs for remote http host
if isinstance(remote_path, str):
remote_path = gravtk.utilities.url_split(remote_path)
@@ -241,8 +296,9 @@ def http_pull_file(remote_path, remote_mtime, local_file,
# check last modification time of local file
local_mtime = local_file.stat().st_mtime
# if remote file is newer: overwrite the local file
- if (gravtk.utilities.even(remote_mtime) >
- gravtk.utilities.even(local_mtime)):
+ if gravtk.utilities.even(remote_mtime) > gravtk.utilities.even(
+ local_mtime
+ ):
TEST = True
OVERWRITE = ' (overwrite)'
else:
@@ -258,8 +314,9 @@ def http_pull_file(remote_path, remote_mtime, local_file,
# Create and submit request. There are a wide range of exceptions
# that can be thrown here, including HTTPError and URLError.
request = gravtk.utilities.urllib2.Request(remote_file)
- response = gravtk.utilities.urllib2.urlopen(request,
- timeout=TIMEOUT)
+ response = gravtk.utilities.urllib2.urlopen(
+ request, timeout=TIMEOUT
+ )
# chunked transfer encoding size
CHUNK = 16 * 1024
# copy contents to local file using chunked transfer encoding
@@ -270,6 +327,7 @@ def http_pull_file(remote_path, remote_mtime, local_file,
os.utime(local_file, (local_file.stat().st_atime, remote_mtime))
local_file.chmod(mode=MODE)
+
# PURPOSE: create argument parser
def arguments():
parser = argparse.ArgumentParser(
@@ -279,64 +337,113 @@ def arguments():
)
# command line parameters
# working data directory
- parser.add_argument('--directory','-D',
- type=pathlib.Path, default=pathlib.Path.cwd(),
- help='Working data directory')
+ parser.add_argument(
+ '--directory',
+ '-D',
+ type=pathlib.Path,
+ default=gravtk.utilities.get_cache_path(ensure_exists=False),
+ help='Working data directory',
+ )
# GRACE/GRACE-FO data release
- parser.add_argument('--release','-r',
- metavar='DREL', type=str, nargs='+',
- default=['RL06'], choices=['RL04','RL05','RL06'],
- help='GRACE/GRACE-FO data release')
+ parser.add_argument(
+ '--release',
+ '-r',
+ metavar='DREL',
+ type=str,
+ nargs='+',
+ default=['RL06'],
+ choices=['RL04', 'RL05', 'RL06'],
+ help='GRACE/GRACE-FO data release',
+ )
# years to download
- parser.add_argument('--year','-Y',
- type=int, nargs='+', default=range(2000,2023),
- help='Years of data to sync')
+ parser.add_argument(
+ '--year',
+ '-Y',
+ type=int,
+ nargs='+',
+ default=range(2000, 2023),
+ help='Years of data to sync',
+ )
# months to download
- parser.add_argument('--month','-m',
- type=int, nargs='+', default=range(1,13),
- help='Months of data to sync')
+ parser.add_argument(
+ '--month',
+ '-m',
+ type=int,
+ nargs='+',
+ default=range(1, 13),
+ help='Months of data to sync',
+ )
# output dealiasing files as monthly tar files
- parser.add_argument('--tar','-T',
- default=False, action='store_true',
- help='Output data as monthly tar files')
+ parser.add_argument(
+ '--tar',
+ '-T',
+ default=False,
+ action='store_true',
+ help='Output data as monthly tar files',
+ )
# connection timeout
- parser.add_argument('--timeout','-t',
- type=int, default=360,
- help='Timeout in seconds for blocking operations')
+ parser.add_argument(
+ '--timeout',
+ '-t',
+ type=int,
+ default=360,
+ help='Timeout in seconds for blocking operations',
+ )
# Output log file in form
# GFZ_AOD1B_sync_2002-04-01.log
- parser.add_argument('--log','-l',
- default=False, action='store_true',
- help='Output log file')
+ parser.add_argument(
+ '--log',
+ '-l',
+ default=False,
+ action='store_true',
+ help='Output log file',
+ )
# sync options
- parser.add_argument('--clobber','-C',
- default=False, action='store_true',
- help='Overwrite existing data in transfer')
+ parser.add_argument(
+ '--clobber',
+ '-C',
+ default=False,
+ action='store_true',
+ help='Overwrite existing data in transfer',
+ )
# permissions mode of the directories and files synced (number in octal)
- parser.add_argument('--mode','-M',
- type=lambda x: int(x,base=8), default=0o775,
- help='Permission mode of directories and files synced')
+ parser.add_argument(
+ '--mode',
+ '-M',
+ type=lambda x: int(x, base=8),
+ default=0o775,
+ help='Permission mode of directories and files synced',
+ )
# return the parser
return parser
+
# This is the main part of the program that calls the individual functions
def main():
# Read the system arguments listed after the program
parser = arguments()
- args,_ = parser.parse_known_args()
+ args, _ = parser.parse_known_args()
# GFZ ISDC https host
HOST = 'https://isdc-data.gfz.de/'
# check internet connection before attempting to run program
if gravtk.utilities.check_connection(HOST):
for DREL in args.release:
- gfz_isdc_dealiasing_sync(args.directory, DREL=DREL,
- YEAR=args.year, MONTHS=args.month, TAR=args.tar,
- TIMEOUT=args.timeout, LOG=args.log,
- CLOBBER=args.clobber, MODE=args.mode)
+ gfz_isdc_dealiasing_sync(
+ args.directory,
+ DREL=DREL,
+ YEAR=args.year,
+ MONTHS=args.month,
+ TAR=args.tar,
+ TIMEOUT=args.timeout,
+ LOG=args.log,
+ CLOBBER=args.clobber,
+ MODE=args.mode,
+ )
else:
raise RuntimeError('Check internet connection')
+
# run main program
if __name__ == '__main__':
main()
diff --git a/access/gfz_isdc_grace_sync.py b/access/gfz_isdc_grace_sync.py
index b641e5aa..1106da52 100644
--- a/access/gfz_isdc_grace_sync.py
+++ b/access/gfz_isdc_grace_sync.py
@@ -1,5 +1,5 @@
#!/usr/bin/env python
-u"""
+"""
gfz_isdc_grace_sync.py
Written by Tyler Sutterley (10/2025)
Syncs GRACE/GRACE-FO data from the GFZ Information System and Data Center (ISDC)
@@ -59,6 +59,7 @@
added GRACE Follow-On data sync
Written 08/2018
"""
+
from __future__ import print_function
import sys
@@ -74,11 +75,20 @@
import posixpath
import gravity_toolkit as gravtk
-# PURPOSE: sync local GRACE/GRACE-FO files with GFZ ISDC server
-def gfz_isdc_grace_sync(DIRECTORY, PROC=[], DREL=[], VERSION=[],
- NEWSLETTERS=False, TIMEOUT=None, LOG=False, LIST=False,
- CLOBBER=False, MODE=None):
+# PURPOSE: sync local GRACE/GRACE-FO files with GFZ ISDC server
+def gfz_isdc_grace_sync(
+ DIRECTORY,
+ PROC=[],
+ DREL=[],
+ VERSION=[],
+ NEWSLETTERS=False,
+ TIMEOUT=None,
+ LOG=False,
+ LIST=False,
+ CLOBBER=False,
+ MODE=None,
+):
# check if directory exists and recursively create if not
DIRECTORY = pathlib.Path(DIRECTORY).expanduser().absolute()
DIRECTORY.mkdir(mode=MODE, parents=True, exist_ok=True)
@@ -86,7 +96,7 @@ def gfz_isdc_grace_sync(DIRECTORY, PROC=[], DREL=[], VERSION=[],
# GFZ ISDC https host
HOST = 'https://isdc-data.gfz.de/'
# mission shortnames
- shortname = {'grace':'GRAC', 'grace-fo':'GRFO'}
+ shortname = {'grace': 'GRAC', 'grace-fo': 'GRFO'}
# datasets for each processing center
DSET = {}
DSET['CSR'] = ['GAC', 'GAD', 'GSM']
@@ -97,7 +107,7 @@ def gfz_isdc_grace_sync(DIRECTORY, PROC=[], DREL=[], VERSION=[],
if LOG:
# output to log file
# format: GFZ_ISDC_sync_2002-04-01.log
- today = time.strftime('%Y-%m-%d',time.localtime())
+ today = time.strftime('%Y-%m-%d', time.localtime())
LOGFILE = DIRECTORY.joinpath(f'GFZ_ISDC_sync_{today}.log')
logging.basicConfig(filename=LOGFILE, level=logging.INFO)
logging.info(f'GFZ ISDC Sync Log ({today})')
@@ -116,51 +126,78 @@ def gfz_isdc_grace_sync(DIRECTORY, PROC=[], DREL=[], VERSION=[],
# compile regular expression operator for remote files
R1 = re.compile(r'TN-13_GEOC_(CSR|GFZ|JPL)_(.*?).txt$', re.VERBOSE)
# get filenames from remote directory
- remote_files,remote_mtimes = http_list(
- [HOST,'grace-fo','DOCUMENTS','TECHNICAL_NOTES'],
- timeout=TIMEOUT, pattern=R1, sort=True)
+ remote_files, remote_mtimes = http_list(
+ [HOST, 'grace-fo', 'DOCUMENTS', 'TECHNICAL_NOTES'],
+ timeout=TIMEOUT,
+ pattern=R1,
+ sort=True,
+ )
# for each file on the remote server
- for fi,remote_mtime in zip(remote_files,remote_mtimes):
+ for fi, remote_mtime in zip(remote_files, remote_mtimes):
# extract filename from regex object
- remote_path = [HOST,'grace-fo','DOCUMENTS','TECHNICAL_NOTES',fi]
+ remote_path = [HOST, 'grace-fo', 'DOCUMENTS', 'TECHNICAL_NOTES', fi]
local_file = local_dir.joinpath(fi)
- http_pull_file(remote_path, remote_mtime,
- local_file, TIMEOUT=TIMEOUT, LIST=LIST,
- CLOBBER=CLOBBER, MODE=MODE)
+ http_pull_file(
+ remote_path,
+ remote_mtime,
+ local_file,
+ TIMEOUT=TIMEOUT,
+ LIST=LIST,
+ CLOBBER=CLOBBER,
+ MODE=MODE,
+ )
# SLR C2,0 coefficients
logging.info('C2,0 Coefficients:')
# compile regular expression operator for remote files
R1 = re.compile(r'TN-(05|07|11)_C20_SLR_RL(.*?).txt$', re.VERBOSE)
# get filenames from remote directory
- remote_files,remote_mtimes = http_list(
- [HOST,'grace','DOCUMENTS','TECHNICAL_NOTES'],
- timeout=TIMEOUT, pattern=R1, sort=True)
+ remote_files, remote_mtimes = http_list(
+ [HOST, 'grace', 'DOCUMENTS', 'TECHNICAL_NOTES'],
+ timeout=TIMEOUT,
+ pattern=R1,
+ sort=True,
+ )
# for each file on the remote server
- for fi,remote_mtime in zip(remote_files,remote_mtimes):
+ for fi, remote_mtime in zip(remote_files, remote_mtimes):
# extract filename from regex object
- remote_path = [HOST,'grace','DOCUMENTS','TECHNICAL_NOTES',fi]
- local_file = DIRECTORY.joinpath(re.sub(r'(_RL.*?).txt','.txt',fi))
- http_pull_file(remote_path, remote_mtime,
- local_file, TIMEOUT=TIMEOUT, LIST=LIST,
- CLOBBER=CLOBBER, MODE=MODE)
+ remote_path = [HOST, 'grace', 'DOCUMENTS', 'TECHNICAL_NOTES', fi]
+ local_file = DIRECTORY.joinpath(re.sub(r'(_RL.*?).txt', '.txt', fi))
+ http_pull_file(
+ remote_path,
+ remote_mtime,
+ local_file,
+ TIMEOUT=TIMEOUT,
+ LIST=LIST,
+ CLOBBER=CLOBBER,
+ MODE=MODE,
+ )
# SLR C3,0 coefficients
logging.info('C3,0 Coefficients:')
# compile regular expression operator for remote files
R1 = re.compile(r'TN-(14)_C30_C20_SLR_GSFC.txt$', re.VERBOSE)
# get filenames from remote directory
- remote_files,remote_mtimes = http_list(
- [HOST,'grace-fo','DOCUMENTS','TECHNICAL_NOTES'],
- timeout=TIMEOUT, pattern=R1, sort=True)
+ remote_files, remote_mtimes = http_list(
+ [HOST, 'grace-fo', 'DOCUMENTS', 'TECHNICAL_NOTES'],
+ timeout=TIMEOUT,
+ pattern=R1,
+ sort=True,
+ )
# for each file on the remote server
- for fi,remote_mtime in zip(remote_files,remote_mtimes):
+ for fi, remote_mtime in zip(remote_files, remote_mtimes):
# extract filename from regex object
- remote_path = [HOST,'grace-fo','DOCUMENTS','TECHNICAL_NOTES',fi]
- local_file = DIRECTORY.joinpath(re.sub(r'(SLR_GSFC)','GSFC_SLR',fi))
- http_pull_file(remote_path, remote_mtime,
- local_file, TIMEOUT=TIMEOUT, LIST=LIST,
- CLOBBER=CLOBBER, MODE=MODE)
+ remote_path = [HOST, 'grace-fo', 'DOCUMENTS', 'TECHNICAL_NOTES', fi]
+ local_file = DIRECTORY.joinpath(re.sub(r'(SLR_GSFC)', 'GSFC_SLR', fi))
+ http_pull_file(
+ remote_path,
+ remote_mtime,
+ local_file,
+ TIMEOUT=TIMEOUT,
+ LIST=LIST,
+ CLOBBER=CLOBBER,
+ MODE=MODE,
+ )
# TN-08 GAE, TN-09 GAF and TN-10 GAG ECMWF atmosphere correction products
logging.info('TN-08 GAE, TN-09 GAF and TN-10 GAG products:')
@@ -171,17 +208,26 @@ def gfz_isdc_grace_sync(DIRECTORY, PROC=[], DREL=[], VERSION=[],
# compile regular expression operator for remote files
R1 = re.compile(r'({0}|{1}|{2})'.format(*ECMWF_files), re.VERBOSE)
# get filenames from remote directory
- remote_files,remote_mtimes = http_list(
- [HOST,'grace','DOCUMENTS','TECHNICAL_NOTES'],
- timeout=TIMEOUT, pattern=R1, sort=True)
+ remote_files, remote_mtimes = http_list(
+ [HOST, 'grace', 'DOCUMENTS', 'TECHNICAL_NOTES'],
+ timeout=TIMEOUT,
+ pattern=R1,
+ sort=True,
+ )
# for each file on the remote server
- for fi,remote_mtime in zip(remote_files,remote_mtimes):
+ for fi, remote_mtime in zip(remote_files, remote_mtimes):
# extract filename from regex object
- remote_path = [HOST,'grace','DOCUMENTS','TECHNICAL_NOTES',fi]
+ remote_path = [HOST, 'grace', 'DOCUMENTS', 'TECHNICAL_NOTES', fi]
local_file = DIRECTORY.joinpath(fi)
- http_pull_file(remote_path, remote_mtime,
- local_file, TIMEOUT=TIMEOUT, LIST=LIST,
- CLOBBER=CLOBBER, MODE=MODE)
+ http_pull_file(
+ remote_path,
+ remote_mtime,
+ local_file,
+ TIMEOUT=TIMEOUT,
+ LIST=LIST,
+ CLOBBER=CLOBBER,
+ MODE=MODE,
+ )
# GRACE and GRACE-FO newsletters
if NEWSLETTERS:
@@ -190,30 +236,41 @@ def gfz_isdc_grace_sync(DIRECTORY, PROC=[], DREL=[], VERSION=[],
# check if newsletters directory exists and recursively create if not
local_dir.mkdir(mode=MODE, parents=True, exist_ok=True)
# for each satellite mission (grace, grace-fo)
- for i,mi in enumerate(['grace','grace-fo']):
+ for i, mi in enumerate(['grace', 'grace-fo']):
logging.info(f'{mi} Newsletters:')
# compile regular expression operator for remote files
- NAME = mi.upper().replace('-','_')
+ NAME = mi.upper().replace('-', '_')
R1 = re.compile(rf'{NAME}_SDS_NL_(\d+).pdf', re.VERBOSE)
# find years for GRACE/GRACE-FO newsletters
- years,_ = http_list([HOST,mi,'DOCUMENTS','NEWSLETTER'],
- timeout=TIMEOUT, pattern=r'\d+',
- sort=True)
+ years, _ = http_list(
+ [HOST, mi, 'DOCUMENTS', 'NEWSLETTER'],
+ timeout=TIMEOUT,
+ pattern=r'\d+',
+ sort=True,
+ )
# for each year of GRACE/GRACE-FO newsletters
for Y in years:
# find GRACE/GRACE-FO newsletters
- remote_files,remote_mtimes = http_list(
- [HOST,mi,'DOCUMENTS','NEWSLETTER',Y],
- timeout=TIMEOUT, pattern=R1,
- sort=True)
+ remote_files, remote_mtimes = http_list(
+ [HOST, mi, 'DOCUMENTS', 'NEWSLETTER', Y],
+ timeout=TIMEOUT,
+ pattern=R1,
+ sort=True,
+ )
# for each file on the remote server
- for fi,remote_mtime in zip(remote_files,remote_mtimes):
+ for fi, remote_mtime in zip(remote_files, remote_mtimes):
# extract filename from regex object
- remote_path = [HOST,mi,'DOCUMENTS','NEWSLETTER',Y,fi]
+ remote_path = [HOST, mi, 'DOCUMENTS', 'NEWSLETTER', Y, fi]
local_file = local_dir.joinpath(fi)
- http_pull_file(remote_path, remote_mtime,
- local_file, TIMEOUT=TIMEOUT, LIST=LIST,
- CLOBBER=CLOBBER, MODE=MODE)
+ http_pull_file(
+ remote_path,
+ remote_mtime,
+ local_file,
+ TIMEOUT=TIMEOUT,
+ LIST=LIST,
+ CLOBBER=CLOBBER,
+ MODE=MODE,
+ )
# GRACE/GRACE-FO level-2 spherical harmonic products
logging.info('GRACE/GRACE-FO L2 Global Spherical Harmonics:')
@@ -230,9 +287,9 @@ def gfz_isdc_grace_sync(DIRECTORY, PROC=[], DREL=[], VERSION=[],
# list of GRACE/GRACE-FO files for index
grace_files = []
# for each satellite mission (grace, grace-fo)
- for i,mi in enumerate(['grace','grace-fo']):
+ for i, mi in enumerate(['grace', 'grace-fo']):
# modifiers for intermediate data releases
- if (int(VERSION[i]) > 0):
+ if int(VERSION[i]) > 0:
drel_str = f'{rl}.{VERSION[i]}'
else:
drel_str = copy.copy(rl)
@@ -241,22 +298,37 @@ def gfz_isdc_grace_sync(DIRECTORY, PROC=[], DREL=[], VERSION=[],
# compile the regular expression operator to find files
R1 = re.compile(rf'({ds}-(.*?)(gz|txt|dif))')
# get filenames from remote directory
- remote_files,remote_mtimes = http_list(
- [HOST,mi,'Level-2',pr,drel_str], timeout=TIMEOUT,
- pattern=R1, sort=True)
- for fi,remote_mtime in zip(remote_files,remote_mtimes):
+ remote_files, remote_mtimes = http_list(
+ [HOST, mi, 'Level-2', pr, drel_str],
+ timeout=TIMEOUT,
+ pattern=R1,
+ sort=True,
+ )
+ for fi, remote_mtime in zip(remote_files, remote_mtimes):
# extract filename from regex object
- remote_path = [HOST,mi,'Level-2',pr,drel_str,fi]
+ remote_path = [HOST, mi, 'Level-2', pr, drel_str, fi]
local_file = local_dir.joinpath(fi)
- http_pull_file(remote_path, remote_mtime,
- local_file, TIMEOUT=TIMEOUT, LIST=LIST,
- CLOBBER=CLOBBER, MODE=MODE)
+ http_pull_file(
+ remote_path,
+ remote_mtime,
+ local_file,
+ TIMEOUT=TIMEOUT,
+ LIST=LIST,
+ CLOBBER=CLOBBER,
+ MODE=MODE,
+ )
# regular expression operator for data product
rx = gravtk.utilities.compile_regex_pattern(
- pr, rl, ds, mission=shortname[mi])
+ pr, rl, ds, mission=shortname[mi]
+ )
# find local GRACE/GRACE-FO files to create index
- granules = sorted([f.name for f in local_dir.iterdir()
- if rx.match(f.name)])
+ granules = sorted(
+ [
+ f.name
+ for f in local_dir.iterdir()
+ if rx.match(f.name)
+ ]
+ )
# reduce list of GRACE/GRACE-FO files to unique dates
granules = gravtk.time.reduce_by_date(granules)
# extend list of GRACE/GRACE-FO files with granules
@@ -274,14 +346,15 @@ def gfz_isdc_grace_sync(DIRECTORY, PROC=[], DREL=[], VERSION=[],
if LOG:
LOGFILE.chmod(mode=MODE)
+
# PURPOSE: list a directory on the GFZ https server
def http_list(
- HOST: str | list,
- timeout: int | None = None,
- context: ssl.SSLContext = gravtk.utilities._default_ssl_context,
- pattern: str | re.Pattern = '',
- sort: bool = False
- ):
+ HOST: str | list,
+ timeout: int | None = None,
+ context: ssl.SSLContext = gravtk.utilities._default_ssl_context,
+ pattern: str | re.Pattern = '',
+ sort: bool = False,
+):
"""
List a directory on the GFZ https Server
@@ -315,8 +388,9 @@ def http_list(
try:
# Create and submit request.
request = gravtk.utilities.urllib2.Request(posixpath.join(*HOST))
- response = gravtk.utilities.urllib2.urlopen(request,
- timeout=timeout, context=context)
+ response = gravtk.utilities.urllib2.urlopen(
+ request, timeout=timeout, context=context
+ )
except Exception as exc:
raise Exception('List error from {0}'.format(posixpath.join(*HOST)))
# read the directory listing
@@ -324,32 +398,41 @@ def http_list(
# read and parse request for files (column names and modified times)
lines = [l for l in contents if rx.search(l.decode('utf-8'))]
# column names and last modified times
- colnames = [None]*len(lines)
- collastmod = [None]*len(lines)
+ colnames = [None] * len(lines)
+ collastmod = [None] * len(lines)
for i, l in enumerate(lines):
colnames[i], lastmod = rx.findall(l.decode('utf-8')).pop()
# get the Unix timestamp value for a modification time
- collastmod[i] = gravtk.utilities.get_unix_time(lastmod,
- format='%Y-%m-%d %H:%M')
+ collastmod[i] = gravtk.utilities.get_unix_time(
+ lastmod, format='%Y-%m-%d %H:%M'
+ )
# reduce using regular expression pattern
if pattern:
- i = [i for i,f in enumerate(colnames) if re.search(pattern, f)]
+ i = [i for i, f in enumerate(colnames) if re.search(pattern, f)]
# reduce list of column names and last modified times
colnames = [colnames[indice] for indice in i]
collastmod = [collastmod[indice] for indice in i]
# sort the list
if sort:
- i = [i for i,j in sorted(enumerate(colnames), key=lambda i: i[1])]
+ i = [i for i, j in sorted(enumerate(colnames), key=lambda i: i[1])]
# sort list of column names and last modified times
colnames = [colnames[indice] for indice in i]
collastmod = [collastmod[indice] for indice in i]
# return the list of column names and last modified times
return (colnames, collastmod)
+
# PURPOSE: pull file from a remote host checking if file exists locally
# and if the remote file is newer than the local file
-def http_pull_file(remote_path, remote_mtime, local_file,
- TIMEOUT=0, LIST=False, CLOBBER=False, MODE=0o775):
+def http_pull_file(
+ remote_path,
+ remote_mtime,
+ local_file,
+ TIMEOUT=0,
+ LIST=False,
+ CLOBBER=False,
+ MODE=0o775,
+):
# verify inputs for remote http host
if isinstance(remote_path, str):
remote_path = gravtk.utilities.url_split(remote_path)
@@ -364,8 +447,9 @@ def http_pull_file(remote_path, remote_mtime, local_file,
# check last modification time of local file
local_mtime = local_file.stat().st_mtime
# if remote file is newer: overwrite the local file
- if (gravtk.utilities.even(remote_mtime) >
- gravtk.utilities.even(local_mtime)):
+ if gravtk.utilities.even(remote_mtime) > gravtk.utilities.even(
+ local_mtime
+ ):
TEST = True
OVERWRITE = ' (overwrite)'
else:
@@ -381,8 +465,9 @@ def http_pull_file(remote_path, remote_mtime, local_file,
# Create and submit request. There are a wide range of exceptions
# that can be thrown here, including HTTPError and URLError.
request = gravtk.utilities.urllib2.Request(remote_file)
- response = gravtk.utilities.urllib2.urlopen(request,
- timeout=TIMEOUT)
+ response = gravtk.utilities.urllib2.urlopen(
+ request, timeout=TIMEOUT
+ )
# chunked transfer encoding size
CHUNK = 16 * 1024
# copy contents to local file using chunked transfer encoding
@@ -393,6 +478,7 @@ def http_pull_file(remote_path, remote_mtime, local_file,
os.utime(local_file, (local_file.stat().st_atime, remote_mtime))
local_file.chmod(mode=MODE)
+
# PURPOSE: create argument parser
def arguments():
parser = argparse.ArgumentParser(
@@ -402,69 +488,123 @@ def arguments():
)
# command line parameters
# working data directory
- parser.add_argument('--directory','-D',
- type=pathlib.Path, default=pathlib.Path.cwd(),
- help='Working data directory')
+ parser.add_argument(
+ '--directory',
+ '-D',
+ type=pathlib.Path,
+ default=gravtk.utilities.get_cache_path(ensure_exists=False),
+ help='Working data directory',
+ )
# GRACE/GRACE-FO processing center
- parser.add_argument('--center','-c',
- metavar='PROC', type=str, nargs='+',
- default=['CSR','GFZ','JPL'], choices=['CSR','GFZ','JPL'],
- help='GRACE/GRACE-FO processing center')
+ parser.add_argument(
+ '--center',
+ '-c',
+ metavar='PROC',
+ type=str,
+ nargs='+',
+ default=['CSR', 'GFZ', 'JPL'],
+ choices=['CSR', 'GFZ', 'JPL'],
+ help='GRACE/GRACE-FO processing center',
+ )
# GRACE/GRACE-FO data release
- parser.add_argument('--release','-r',
- metavar='DREL', type=str, nargs='+',
- default=['RL06'], choices=['RL04','RL05','RL06'],
- help='GRACE/GRACE-FO data release')
+ parser.add_argument(
+ '--release',
+ '-r',
+ metavar='DREL',
+ type=str,
+ nargs='+',
+ default=['RL06'],
+ choices=['RL04', 'RL05', 'RL06'],
+ help='GRACE/GRACE-FO data release',
+ )
# GRACE/GRACE-FO data version
- parser.add_argument('--version','-v',
- metavar='VERSION', type=str, nargs=2,
- default=['0','1'],
- help='GRACE/GRACE-FO Level-2 data version')
+ parser.add_argument(
+ '--version',
+ '-v',
+ metavar='VERSION',
+ type=str,
+ nargs=2,
+ default=['0', '1'],
+ help='GRACE/GRACE-FO Level-2 data version',
+ )
# GRACE/GRACE-FO newsletters
- parser.add_argument('--newsletters','-n',
- default=False, action='store_true',
- help='Sync GRACE/GRACE-FO Newsletters')
+ parser.add_argument(
+ '--newsletters',
+ '-n',
+ default=False,
+ action='store_true',
+ help='Sync GRACE/GRACE-FO Newsletters',
+ )
# connection timeout
- parser.add_argument('--timeout','-t',
- type=int, default=360,
- help='Timeout in seconds for blocking operations')
+ parser.add_argument(
+ '--timeout',
+ '-t',
+ type=int,
+ default=360,
+ help='Timeout in seconds for blocking operations',
+ )
# Output log file in form
# GFZ_ISDC_sync_2002-04-01.log
- parser.add_argument('--log','-l',
- default=False, action='store_true',
- help='Output log file')
+ parser.add_argument(
+ '--log',
+ '-l',
+ default=False,
+ action='store_true',
+ help='Output log file',
+ )
# sync options
- parser.add_argument('--list','-L',
- default=False, action='store_true',
- help='Only print files that could be transferred')
- parser.add_argument('--clobber','-C',
- default=False, action='store_true',
- help='Overwrite existing data in transfer')
+ parser.add_argument(
+ '--list',
+ '-L',
+ default=False,
+ action='store_true',
+ help='Only print files that could be transferred',
+ )
+ parser.add_argument(
+ '--clobber',
+ '-C',
+ default=False,
+ action='store_true',
+ help='Overwrite existing data in transfer',
+ )
# permissions mode of the directories and files synced (number in octal)
- parser.add_argument('--mode','-M',
- type=lambda x: int(x,base=8), default=0o775,
- help='Permission mode of directories and files synced')
+ parser.add_argument(
+ '--mode',
+ '-M',
+ type=lambda x: int(x, base=8),
+ default=0o775,
+ help='Permission mode of directories and files synced',
+ )
# return the parser
return parser
+
# This is the main part of the program that calls the individual functions
def main():
# Read the system arguments listed after the program
parser = arguments()
- args,_ = parser.parse_known_args()
+ args, _ = parser.parse_known_args()
# GFZ ISDC https host
HOST = 'https://isdc-data.gfz.de/'
# check internet connection before attempting to run program
if gravtk.utilities.check_connection(HOST):
- gfz_isdc_grace_sync(args.directory, PROC=args.center,
- DREL=args.release, VERSION=args.version,
- NEWSLETTERS=args.newsletters, TIMEOUT=args.timeout,
- LIST=args.list, LOG=args.log, CLOBBER=args.clobber,
- MODE=args.mode)
+ gfz_isdc_grace_sync(
+ args.directory,
+ PROC=args.center,
+ DREL=args.release,
+ VERSION=args.version,
+ NEWSLETTERS=args.newsletters,
+ TIMEOUT=args.timeout,
+ LIST=args.list,
+ LOG=args.log,
+ CLOBBER=args.clobber,
+ MODE=args.mode,
+ )
else:
raise RuntimeError('Check internet connection')
+
# run main program
if __name__ == '__main__':
main()
diff --git a/access/itsg_graz_grace_sync.py b/access/itsg_graz_grace_sync.py
index c765a4cd..ec3a9423 100755
--- a/access/itsg_graz_grace_sync.py
+++ b/access/itsg_graz_grace_sync.py
@@ -1,7 +1,7 @@
#!/usr/bin/env python
-u"""
+"""
itsg_graz_grace_sync.py
-Written by Tyler Sutterley (05/2023)
+Written by Tyler Sutterley (07/2026)
Syncs GRACE/GRACE-FO and auxiliary data from the ITSG GRAZ server
CALLING SEQUENCE:
@@ -39,6 +39,7 @@
utilities.py: download and management utilities for syncing files
UPDATE HISTORY:
+ Updated 07/2026: ITSG GRACE server moved from outgoing to pub
Updated 05/2023: use pathlib to define and operate on paths
Updated 12/2022: single implicit import of gravity toolkit
Updated 11/2022: use f-strings for formatting verbose or ascii output
@@ -46,6 +47,7 @@
Updated 10/2021: using python logging for handling verbose output
Written 09/2021
"""
+
from __future__ import print_function
import sys
@@ -59,10 +61,18 @@
import posixpath
import gravity_toolkit as gravtk
-# PURPOSE: sync local GRACE/GRACE-FO files with ITSG GRAZ server
-def itsg_graz_grace_sync(DIRECTORY, RELEASE=None, LMAX=None, TIMEOUT=0,
- LOG=False, LIST=False, MODE=0o775, CLOBBER=False):
+# PURPOSE: sync local GRACE/GRACE-FO files with ITSG GRAZ server
+def itsg_graz_grace_sync(
+ DIRECTORY,
+ RELEASE=None,
+ LMAX=None,
+ TIMEOUT=0,
+ LOG=False,
+ LIST=False,
+ MODE=0o775,
+ CLOBBER=False,
+):
# check if directory exists and recursively create if not
DIRECTORY = pathlib.Path(DIRECTORY).expanduser().absolute()
DIRECTORY.mkdir(mode=MODE, parents=True, exist_ok=True)
@@ -71,7 +81,7 @@ def itsg_graz_grace_sync(DIRECTORY, RELEASE=None, LMAX=None, TIMEOUT=0,
if LOG:
# output to log file
# format: ITSG_GRAZ_GRACE_sync_2002-04-01.log
- today = time.strftime('%Y-%m-%d',time.localtime())
+ today = time.strftime('%Y-%m-%d', time.localtime())
LOGFILE = DIRECTORY.joinpath(f'ITSG_GRAZ_GRACE_sync_{today}.log')
logging.basicConfig(filename=LOGFILE, level=logging.INFO)
logging.info(f'ITSG GRAZ GRACE Sync Log ({today})')
@@ -82,7 +92,7 @@ def itsg_graz_grace_sync(DIRECTORY, RELEASE=None, LMAX=None, TIMEOUT=0,
logging.basicConfig(level=logging.INFO)
# ITSG GRAZ server
- HOST = ['http://ftp.tugraz.at','outgoing','ITSG','GRACE']
+ HOST = ['http://ftp.tugraz.at', 'pub', 'ITSG', 'GRACE']
# open connection with ITSG GRAZ server at remote directory
release_directory = f'ITSG-{RELEASE}'
# regular expression operators for ITSG data and models
@@ -95,8 +105,10 @@ def itsg_graz_grace_sync(DIRECTORY, RELEASE=None, LMAX=None, TIMEOUT=0,
itsg_products.append(r'Grace2016')
itsg_products.append(r'Grace2018')
itsg_products.append(r'Grace_operational')
- itsg_pattern = (r'(AOD1B_RL\d+|model|ITSG)[-_]({0})(_n\d+)?_'
- r'(\d+)-(\d+)(\.gfc)').format(r'|'.join(itsg_products))
+ itsg_pattern = (
+ r'(AOD1B_RL\d+|model|ITSG)[-_]({0})(_n\d+)?_'
+ r'(\d+)-(\d+)(\.gfc)'
+ ).format(r'|'.join(itsg_products))
R1 = re.compile(itsg_pattern, re.VERBOSE | re.IGNORECASE)
# local directory for release
DREL = {}
@@ -113,52 +125,69 @@ def itsg_graz_grace_sync(DIRECTORY, RELEASE=None, LMAX=None, TIMEOUT=0,
# sync ITSG GRAZ dealiasing products
subdir = 'background' if (RELEASE == 'Grace2014') else 'monthly_background'
- REMOTE = [*HOST,release_directory,'monthly',subdir]
- files,mtimes = gravtk.utilities.http_list(REMOTE,
- timeout=TIMEOUT,pattern=R1,sort=True)
+ REMOTE = [*HOST, release_directory, 'monthly', subdir]
+ files, mtimes = gravtk.utilities.http_list(
+ REMOTE, timeout=TIMEOUT, pattern=R1, sort=True
+ )
# for each file on the remote directory
- for colname,remote_mtime in zip(files,mtimes):
+ for colname, remote_mtime in zip(files, mtimes):
# extract parameters from input filename
- PFX,PRD,trunc,year,month,SFX = R1.findall(colname).pop()
+ PFX, PRD, trunc, year, month, SFX = R1.findall(colname).pop()
# local directory for output GRAZ data
- local_dir = DIRECTORY.joinpath('GRAZ',DREL[RELEASE],DEALIASING[PRD])
+ local_dir = DIRECTORY.joinpath('GRAZ', DREL[RELEASE], DEALIASING[PRD])
# check if local directory exists and recursively create if not
local_dir.mkdir(mode=MODE, parents=True, exist_ok=True)
# local and remote versions of the file
local_file = local_dir.joinpath(colname)
- remote_file = posixpath.join(*REMOTE,colname)
+ remote_file = posixpath.join(*REMOTE, colname)
# copy file from remote directory comparing modified dates
- http_pull_file(remote_file, remote_mtime, local_file,
- TIMEOUT=TIMEOUT, LIST=LIST, CLOBBER=CLOBBER, MODE=MODE)
+ http_pull_file(
+ remote_file,
+ remote_mtime,
+ local_file,
+ TIMEOUT=TIMEOUT,
+ LIST=LIST,
+ CLOBBER=CLOBBER,
+ MODE=MODE,
+ )
# sync ITSG GRAZ data for truncation
subdir = f'monthly_n{LMAX:d}'
- REMOTE = [*HOST,release_directory,'monthly',subdir]
- files,mtimes = gravtk.utilities.http_list(REMOTE,
- timeout=TIMEOUT,pattern=R1,sort=True)
+ REMOTE = [*HOST, release_directory, 'monthly', subdir]
+ files, mtimes = gravtk.utilities.http_list(
+ REMOTE, timeout=TIMEOUT, pattern=R1, sort=True
+ )
# local directory for output GRAZ data
- local_dir = DIRECTORY.joinpath('GRAZ',DREL[RELEASE],'GSM')
+ local_dir = DIRECTORY.joinpath('GRAZ', DREL[RELEASE], 'GSM')
# check if local directory exists and recursively create if not
local_dir.mkdir(mode=MODE, parents=True, exist_ok=True)
# for each file on the remote directory
- for colname,remote_mtime in zip(files,mtimes):
+ for colname, remote_mtime in zip(files, mtimes):
# local and remote versions of the file
local_file = local_dir.joinpath(colname)
- remote_file = posixpath.join(*REMOTE,colname)
+ remote_file = posixpath.join(*REMOTE, colname)
# copy file from remote directory comparing modified dates
- http_pull_file(remote_file, remote_mtime, local_file,
- TIMEOUT=TIMEOUT, LIST=LIST, CLOBBER=CLOBBER, MODE=MODE)
+ http_pull_file(
+ remote_file,
+ remote_mtime,
+ local_file,
+ TIMEOUT=TIMEOUT,
+ LIST=LIST,
+ CLOBBER=CLOBBER,
+ MODE=MODE,
+ )
# create index file for GRACE/GRACE-FO L2 Spherical Harmonic Data
# DATA PRODUCTS (GAC, GAD, GSM, GAA, GAB)
- for ds in ['GAA','GAB','GAC','GAD','GSM']:
+ for ds in ['GAA', 'GAB', 'GAC', 'GAD', 'GSM']:
# local directory for exact data product
- local_dir = DIRECTORY.joinpath('GRAZ',DREL[RELEASE],ds)
+ local_dir = DIRECTORY.joinpath('GRAZ', DREL[RELEASE], ds)
if not local_dir.exists():
continue
# find local GRACE files to create index
- grace_files = sorted([f.name for f in local_dir.iterdir()
- if R1.match(f.name)])
+ grace_files = sorted(
+ [f.name for f in local_dir.iterdir() if R1.match(f.name)]
+ )
# outputting GRACE filenames to index
index_file = local_dir.joinpath('index.txt')
with index_file.open(mode='w', encoding='utf8') as fid:
@@ -171,10 +200,18 @@ def itsg_graz_grace_sync(DIRECTORY, RELEASE=None, LMAX=None, TIMEOUT=0,
if LOG:
LOGFILE.chmod(mode=MODE)
+
# PURPOSE: pull file from a remote host checking if file exists locally
# and if the remote file is newer than the local file
-def http_pull_file(remote_file,remote_mtime,local_file,
- TIMEOUT=0,LIST=False,CLOBBER=False,MODE=0o775):
+def http_pull_file(
+ remote_file,
+ remote_mtime,
+ local_file,
+ TIMEOUT=0,
+ LIST=False,
+ CLOBBER=False,
+ MODE=0o775,
+):
# if file exists in file system: check if remote file is newer
TEST = False
OVERWRITE = ' (clobber)'
@@ -184,8 +221,9 @@ def http_pull_file(remote_file,remote_mtime,local_file,
# check last modification time of local file
local_mtime = local_file.stat().st_mtime
# if remote file is newer: overwrite the local file
- if (gravtk.utilities.even(remote_mtime) >
- gravtk.utilities.even(local_mtime)):
+ if gravtk.utilities.even(remote_mtime) > gravtk.utilities.even(
+ local_mtime
+ ):
TEST = True
OVERWRITE = ' (overwrite)'
else:
@@ -201,8 +239,9 @@ def http_pull_file(remote_file,remote_mtime,local_file,
# Create and submit request. There are a wide range of exceptions
# that can be thrown here, including HTTPError and URLError.
request = gravtk.utilities.urllib2.Request(remote_file)
- response = gravtk.utilities.urllib2.urlopen(request,
- timeout=TIMEOUT)
+ response = gravtk.utilities.urllib2.urlopen(
+ request, timeout=TIMEOUT
+ )
# chunked transfer encoding size
CHUNK = 16 * 1024
# copy contents to local file using chunked transfer encoding
@@ -213,6 +252,7 @@ def http_pull_file(remote_file,remote_mtime,local_file,
os.utime(local_file, (local_file.stat().st_atime, remote_mtime))
local_file.chmod(mode=MODE)
+
# PURPOSE: create argument parser
def arguments():
parser = argparse.ArgumentParser(
@@ -222,55 +262,98 @@ def arguments():
)
# command line parameters
# working data directory
- parser.add_argument('--directory','-D',
- type=pathlib.Path, default=pathlib.Path.cwd(),
- help='Working data directory')
+ parser.add_argument(
+ '--directory',
+ '-D',
+ type=pathlib.Path,
+ default=gravtk.utilities.get_cache_path(ensure_exists=False),
+ help='Working data directory',
+ )
# ITSG GRAZ releases
- choices = ['Grace2014','Grace2016','Grace2018','Grace_operational']
- parser.add_argument('--release','-r',
- type=str, nargs='+', metavar='DREL',
- default=['Grace2018','Grace_operational'],choices=choices,
- help='GRAZ Data Releases to sync')
- parser.add_argument('--lmax',
- type=int, default=60, choices=[60,96,120],
- help='Maximum degree and order of GRAZ products')
+ choices = ['Grace2014', 'Grace2016', 'Grace2018', 'Grace_operational']
+ parser.add_argument(
+ '--release',
+ '-r',
+ type=str,
+ nargs='+',
+ metavar='DREL',
+ default=['Grace2018', 'Grace_operational'],
+ choices=choices,
+ help='GRAZ Data Releases to sync',
+ )
+ parser.add_argument(
+ '--lmax',
+ type=int,
+ default=60,
+ choices=[60, 96, 120],
+ help='Maximum degree and order of GRAZ products',
+ )
# connection timeout
- parser.add_argument('--timeout','-t',
- type=int, default=360,
- help='Timeout in seconds for blocking operations')
+ parser.add_argument(
+ '--timeout',
+ '-t',
+ type=int,
+ default=360,
+ help='Timeout in seconds for blocking operations',
+ )
# Output log file in form
# ITSG_GRAZ_GRACE_sync_2002-04-01.log
- parser.add_argument('--log','-l',
- default=False, action='store_true',
- help='Output log file')
+ parser.add_argument(
+ '--log',
+ '-l',
+ default=False,
+ action='store_true',
+ help='Output log file',
+ )
# sync options
- parser.add_argument('--list','-L',
- default=False, action='store_true',
- help='Only print files that could be transferred')
- parser.add_argument('--clobber','-C',
- default=False, action='store_true',
- help='Overwrite existing data in transfer')
+ parser.add_argument(
+ '--list',
+ '-L',
+ default=False,
+ action='store_true',
+ help='Only print files that could be transferred',
+ )
+ parser.add_argument(
+ '--clobber',
+ '-C',
+ default=False,
+ action='store_true',
+ help='Overwrite existing data in transfer',
+ )
# permissions mode of the directories and files synced (number in octal)
- parser.add_argument('--mode','-M',
- type=lambda x: int(x,base=8), default=0o775,
- help='Permission mode of directories and files synced')
+ parser.add_argument(
+ '--mode',
+ '-M',
+ type=lambda x: int(x, base=8),
+ default=0o775,
+ help='Permission mode of directories and files synced',
+ )
# return the parser
return parser
+
# This is the main part of the program that calls the individual functions
def main():
# Read the system arguments listed after the program
parser = arguments()
- args,_ = parser.parse_known_args()
+ args, _ = parser.parse_known_args()
# check internet connection before attempting to run program
HOST = posixpath.join('http://ftp.tugraz.at')
if gravtk.utilities.check_connection(HOST):
# for each ITSG GRAZ release
for RELEASE in args.release:
- itsg_graz_grace_sync(args.directory, RELEASE=RELEASE,
- LMAX=args.lmax, TIMEOUT=args.timeout, LOG=args.log,
- LIST=args.list, CLOBBER=args.clobber, MODE=args.mode)
+ itsg_graz_grace_sync(
+ args.directory,
+ RELEASE=RELEASE,
+ LMAX=args.lmax,
+ TIMEOUT=args.timeout,
+ LOG=args.log,
+ LIST=args.list,
+ CLOBBER=args.clobber,
+ MODE=args.mode,
+ )
+
# run main program
if __name__ == '__main__':
diff --git a/access/podaac_cumulus.py b/access/podaac_cumulus.py
index dcae0784..a49f445c 100644
--- a/access/podaac_cumulus.py
+++ b/access/podaac_cumulus.py
@@ -1,5 +1,5 @@
#!/usr/bin/env python
-u"""
+"""
podaac_cumulus.py
Written by Tyler Sutterley (11/2024)
@@ -70,6 +70,7 @@
use argparse descriptions within sphinx documentation
Written 03/2022 with release of PO.DAAC Cumulus
"""
+
from __future__ import print_function
import sys
@@ -83,17 +84,28 @@
import argparse
import gravity_toolkit as gravtk
-# PURPOSE: sync local GRACE/GRACE-FO files with JPL PO.DAAC AWS S3 bucket
-def podaac_cumulus(client, DIRECTORY, PROC=[], DREL=[], VERSION=[],
- AOD1B=False, ENDPOINT='s3', TIMEOUT=None, GZIP=False, LOG=False,
- CLOBBER=False, MODE=None):
+# PURPOSE: sync local GRACE/GRACE-FO files with JPL PO.DAAC AWS S3 bucket
+def podaac_cumulus(
+ client,
+ DIRECTORY,
+ PROC=[],
+ DREL=[],
+ VERSION=[],
+ AOD1B=False,
+ ENDPOINT='s3',
+ TIMEOUT=None,
+ GZIP=False,
+ LOG=False,
+ CLOBBER=False,
+ MODE=None,
+):
# check if directory exists and recursively create if not
DIRECTORY = pathlib.Path(DIRECTORY).expanduser().absolute()
DIRECTORY.mkdir(mode=MODE, parents=True, exist_ok=True)
# mission shortnames
- shortname = {'grace':'GRAC', 'grace-fo':'GRFO'}
+ shortname = {'grace': 'GRAC', 'grace-fo': 'GRFO'}
# default bucket for GRACE/GRACE-FO bucket
bucket = gravtk.utilities._s3_buckets['podaac']
# datasets for each processing center
@@ -135,13 +147,17 @@ def podaac_cumulus(client, DIRECTORY, PROC=[], DREL=[], VERSION=[],
for version in set(VERSION):
# query CMR for product metadata
urls = gravtk.utilities.cmr_metadata(
- mission='grace-fo', center=pr, release=rl,
- version=version, provider='POCLOUD',
- endpoint='documentation')
+ mission='grace-fo',
+ center=pr,
+ release=rl,
+ version=version,
+ provider='POCLOUD',
+ endpoint='documentation',
+ )
# TN-13 JPL degree 1 files
try:
- url, = [url for url in urls if R1.search(url)]
+ (url,) = [url for url in urls if R1.search(url)]
except ValueError as exc:
logging.info('No TN-13 Files Available')
url = None
@@ -150,18 +166,25 @@ def podaac_cumulus(client, DIRECTORY, PROC=[], DREL=[], VERSION=[],
local_file = local_dir.joinpath(granule)
# access auxiliary data from endpoint
if (ENDPOINT == 'data') and (url is not None):
- http_pull_file(url, mtime, local_file,
- TIMEOUT=TIMEOUT, CLOBBER=CLOBBER, MODE=MODE)
+ http_pull_file(
+ url,
+ mtime,
+ local_file,
+ TIMEOUT=TIMEOUT,
+ CLOBBER=CLOBBER,
+ MODE=MODE,
+ )
elif (ENDPOINT == 's3') and (url is not None):
bucket = gravtk.utilities.s3_bucket(url)
key = gravtk.utilities.s3_key(url)
response = client.get_object(Bucket=bucket, Key=key)
- s3_pull_file(response, mtime, local_file,
- CLOBBER=CLOBBER, MODE=MODE)
+ s3_pull_file(
+ response, mtime, local_file, CLOBBER=CLOBBER, MODE=MODE
+ )
# TN-14 SLR C2,0 and C3,0 files
try:
- url, = [url for url in urls if R2.search(url)]
+ (url,) = [url for url in urls if R2.search(url)]
except ValueError as exc:
logging.info('No TN-14 Files Available')
url = None
@@ -170,14 +193,21 @@ def podaac_cumulus(client, DIRECTORY, PROC=[], DREL=[], VERSION=[],
local_file = DIRECTORY.joinpath(granule)
# access auxiliary data from endpoint
if (ENDPOINT == 'data') and (url is not None):
- http_pull_file(url, mtime, local_file,
- TIMEOUT=TIMEOUT, CLOBBER=CLOBBER, MODE=MODE)
+ http_pull_file(
+ url,
+ mtime,
+ local_file,
+ TIMEOUT=TIMEOUT,
+ CLOBBER=CLOBBER,
+ MODE=MODE,
+ )
elif (ENDPOINT == 's3') and (url is not None):
bucket = gravtk.utilities.s3_bucket(url)
key = gravtk.utilities.s3_key(url)
response = client.get_object(Bucket=bucket, Key=key)
- s3_pull_file(response, mtime, local_file,
- CLOBBER=CLOBBER, MODE=MODE)
+ s3_pull_file(
+ response, mtime, local_file, CLOBBER=CLOBBER, MODE=MODE
+ )
# GRACE/GRACE-FO AOD1B dealiasing products
if AOD1B:
@@ -187,41 +217,56 @@ def podaac_cumulus(client, DIRECTORY, PROC=[], DREL=[], VERSION=[],
# print string of exact data product
logging.info(f'GFZ/AOD1B/{rl}')
# local directory for exact data product
- local_dir = DIRECTORY.joinpath('AOD1B',rl)
+ local_dir = DIRECTORY.joinpath('AOD1B', rl)
# check if directory exists and recursively create if not
local_dir.mkdir(mode=MODE, parents=True, exist_ok=True)
# test connection to s3 bucket
- if (ENDPOINT == 's3'):
+ if ENDPOINT == 's3':
# get shortname for CMR query
- cmr_shortname, = gravtk.utilities.cmr_product_shortname(
- mission='grace', center='GFZ', release=rl, level='L1B')
+ (cmr_shortname,) = gravtk.utilities.cmr_product_shortname(
+ mission='grace', center='GFZ', release=rl, level='L1B'
+ )
# attempt to list objects in s3 bucket
try:
- objects = client.list_objects(Bucket=bucket,
- Prefix=cmr_shortname)
+ objects = client.list_objects(
+ Bucket=bucket, Prefix=cmr_shortname
+ )
except Exception as exc:
message = f'Error accessing S3 bucket {bucket}'
raise Exception(message) from exc
# query CMR for dataset
- ids,urls,mtimes = gravtk.utilities.cmr(
- mission='grace', level='L1B', center='GFZ', release=rl,
- product='AOD1B', start_date='2002-01-01T00:00:00',
- provider='POCLOUD', endpoint=ENDPOINT)
+ ids, urls, mtimes = gravtk.utilities.cmr(
+ mission='grace',
+ level='L1B',
+ center='GFZ',
+ release=rl,
+ product='AOD1B',
+ start_date='2002-01-01T00:00:00',
+ provider='POCLOUD',
+ endpoint=ENDPOINT,
+ )
# for each model id and url
- for id,url,mtime in zip(ids,urls,mtimes):
+ for id, url, mtime in zip(ids, urls, mtimes):
# retrieve GRACE/GRACE-FO files
granule = gravtk.utilities.url_split(url)[-1]
local_file = local_dir.joinpath(granule)
# access data from endpoint
- if (ENDPOINT == 'data'):
- http_pull_file(url, mtime, local_file,
- TIMEOUT=TIMEOUT, CLOBBER=CLOBBER, MODE=MODE)
- elif (ENDPOINT == 's3'):
+ if ENDPOINT == 'data':
+ http_pull_file(
+ url,
+ mtime,
+ local_file,
+ TIMEOUT=TIMEOUT,
+ CLOBBER=CLOBBER,
+ MODE=MODE,
+ )
+ elif ENDPOINT == 's3':
bucket = gravtk.utilities.s3_bucket(url)
key = gravtk.utilities.s3_key(url)
response = client.get_object(Bucket=bucket, Key=key)
- s3_pull_file(response, mtime, local_file,
- CLOBBER=CLOBBER, MODE=MODE)
+ s3_pull_file(
+ response, mtime, local_file, CLOBBER=CLOBBER, MODE=MODE
+ )
# GRACE/GRACE-FO level-2 spherical harmonic products
logging.info('GRACE/GRACE-FO L2 Global Spherical Harmonics:')
@@ -238,49 +283,76 @@ def podaac_cumulus(client, DIRECTORY, PROC=[], DREL=[], VERSION=[],
# list of GRACE/GRACE-FO files for index
grace_files = []
# for each satellite mission (grace, grace-fo)
- for i,mi in enumerate(['grace','grace-fo']):
+ for i, mi in enumerate(['grace', 'grace-fo']):
# print string of exact data product
logging.info(f'{mi} {pr}/{rl}/{ds}')
# test connection to s3 bucket
- if (ENDPOINT == 's3'):
+ if ENDPOINT == 's3':
# get shortname for CMR query
- cmr_shortname, = gravtk.utilities.cmr_product_shortname(
- mission=mi, center=pr, release=rl, product=ds)
+ (cmr_shortname,) = (
+ gravtk.utilities.cmr_product_shortname(
+ mission=mi, center=pr, release=rl, product=ds
+ )
+ )
# attempt to list objects in s3 bucket
try:
- objects = client.list_objects(Bucket=bucket,
- Prefix=cmr_shortname)
+ objects = client.list_objects(
+ Bucket=bucket, Prefix=cmr_shortname
+ )
except Exception as exc:
message = f'Error accessing S3 bucket {bucket}'
raise Exception(message) from exc
# query CMR for dataset
- ids,urls,mtimes = gravtk.utilities.cmr(
- mission=mi, center=pr, release=rl, product=ds,
- version=VERSION[i], provider='POCLOUD',
- endpoint=ENDPOINT)
+ ids, urls, mtimes = gravtk.utilities.cmr(
+ mission=mi,
+ center=pr,
+ release=rl,
+ product=ds,
+ version=VERSION[i],
+ provider='POCLOUD',
+ endpoint=ENDPOINT,
+ )
# regular expression operator for data product
rx = gravtk.utilities.compile_regex_pattern(
- pr, rl, ds, mission=shortname[mi])
+ pr, rl, ds, mission=shortname[mi]
+ )
# for each model id and url
- for id,url,mtime in zip(ids,urls,mtimes):
+ for id, url, mtime in zip(ids, urls, mtimes):
# retrieve GRACE/GRACE-FO files
granule = gravtk.utilities.url_split(url)[-1]
suffix = '.gz' if GZIP else ''
local_file = local_dir.joinpath(f'{granule}{suffix}')
# access data from endpoint
- if (ENDPOINT == 'data'):
- http_pull_file(url, mtime, local_file,
- GZIP=GZIP, TIMEOUT=TIMEOUT,
- CLOBBER=CLOBBER, MODE=MODE)
- elif (ENDPOINT == 's3'):
+ if ENDPOINT == 'data':
+ http_pull_file(
+ url,
+ mtime,
+ local_file,
+ GZIP=GZIP,
+ TIMEOUT=TIMEOUT,
+ CLOBBER=CLOBBER,
+ MODE=MODE,
+ )
+ elif ENDPOINT == 's3':
bucket = gravtk.utilities.s3_bucket(url)
key = gravtk.utilities.s3_key(url)
response = client.get_object(Bucket=bucket, Key=key)
- s3_pull_file(response, mtime, local_file,
- GZIP=GZIP, CLOBBER=CLOBBER, MODE=MODE)
+ s3_pull_file(
+ response,
+ mtime,
+ local_file,
+ GZIP=GZIP,
+ CLOBBER=CLOBBER,
+ MODE=MODE,
+ )
# find local GRACE/GRACE-FO files to create index
- granules = sorted([f.name for f in local_dir.iterdir()
- if rx.match(f.name)])
+ granules = sorted(
+ [
+ f.name
+ for f in local_dir.iterdir()
+ if rx.match(f.name)
+ ]
+ )
# reduce list of GRACE/GRACE-FO files to unique dates
granules = gravtk.time.reduce_by_date(granules)
# extend list of GRACE/GRACE-FO files with granules
@@ -298,10 +370,18 @@ def podaac_cumulus(client, DIRECTORY, PROC=[], DREL=[], VERSION=[],
if LOG:
LOGFILE.chmod(mode=MODE)
+
# PURPOSE: pull file from a remote host checking if file exists locally
# and if the remote file is newer than the local file
-def http_pull_file(remote_file, remote_mtime, local_file,
- GZIP=False, TIMEOUT=120, CLOBBER=False, MODE=0o775):
+def http_pull_file(
+ remote_file,
+ remote_mtime,
+ local_file,
+ GZIP=False,
+ TIMEOUT=120,
+ CLOBBER=False,
+ MODE=0o775,
+):
# if file exists in file system: check if remote file is newer
TEST = False
OVERWRITE = ' (clobber)'
@@ -311,8 +391,9 @@ def http_pull_file(remote_file, remote_mtime, local_file,
# check last modification time of local file
local_mtime = local_file.stat().st_mtime
# if remote file is newer: overwrite the local file
- if (gravtk.utilities.even(remote_mtime) >
- gravtk.utilities.even(local_mtime)):
+ if gravtk.utilities.even(remote_mtime) > gravtk.utilities.even(
+ local_mtime
+ ):
TEST = True
OVERWRITE = ' (overwrite)'
else:
@@ -329,8 +410,7 @@ def http_pull_file(remote_file, remote_mtime, local_file,
# There are a range of exceptions that can be thrown here
# including HTTPError and URLError.
request = gravtk.utilities.urllib2.Request(remote_file)
- response = gravtk.utilities.urllib2.urlopen(request,
- timeout=TIMEOUT)
+ response = gravtk.utilities.urllib2.urlopen(request, timeout=TIMEOUT)
# copy remote file contents to local file
if GZIP:
with gzip.GzipFile(local_file, 'wb', 9, None, remote_mtime) as f:
@@ -342,10 +422,12 @@ def http_pull_file(remote_file, remote_mtime, local_file,
os.utime(local_file, (local_file.stat().st_atime, remote_mtime))
local_file.chmod(mode=MODE)
+
# PURPOSE: pull file from AWS s3 bucket checking if file exists locally
# and if the remote file is newer than the local file
-def s3_pull_file(response, remote_mtime, local_file,
- GZIP=False, CLOBBER=False, MODE=0o775):
+def s3_pull_file(
+ response, remote_mtime, local_file, GZIP=False, CLOBBER=False, MODE=0o775
+):
# if file exists in file system: check if remote file is newer
TEST = False
OVERWRITE = ' (clobber)'
@@ -355,8 +437,9 @@ def s3_pull_file(response, remote_mtime, local_file,
# check last modification time of local file
local_mtime = local_file.stat().st_mtime
# if remote file is newer: overwrite the local file
- if (gravtk.utilities.even(remote_mtime) >
- gravtk.utilities.even(local_mtime)):
+ if gravtk.utilities.even(remote_mtime) > gravtk.utilities.even(
+ local_mtime
+ ):
TEST = True
OVERWRITE = ' (overwrite)'
else:
@@ -379,6 +462,7 @@ def s3_pull_file(response, remote_mtime, local_file,
os.utime(local_file, (local_file.stat().st_atime, remote_mtime))
local_file.chmod(mode=MODE)
+
# PURPOSE: create argument parser
def arguments():
parser = argparse.ArgumentParser(
@@ -388,71 +472,133 @@ def arguments():
)
# command line parameters
# NASA Earthdata credentials
- parser.add_argument('--user','-U',
- type=str, default=os.environ.get('EARTHDATA_USERNAME'),
- help='Username for NASA Earthdata Login')
- parser.add_argument('--password','-W',
- type=str, default=os.environ.get('EARTHDATA_PASSWORD'),
- help='Password for NASA Earthdata Login')
- parser.add_argument('--netrc','-N',
- type=pathlib.Path, default=pathlib.Path.home().joinpath('.netrc'),
- help='Path to .netrc file for authentication')
+ parser.add_argument(
+ '--user',
+ '-U',
+ type=str,
+ default=os.environ.get('EARTHDATA_USERNAME'),
+ help='Username for NASA Earthdata Login',
+ )
+ parser.add_argument(
+ '--password',
+ '-W',
+ type=str,
+ default=os.environ.get('EARTHDATA_PASSWORD'),
+ help='Password for NASA Earthdata Login',
+ )
+ parser.add_argument(
+ '--netrc',
+ '-N',
+ type=pathlib.Path,
+ default=pathlib.Path.home().joinpath('.netrc'),
+ help='Path to .netrc file for authentication',
+ )
# working data directory
- parser.add_argument('--directory','-D',
- type=pathlib.Path, default=pathlib.Path.cwd(),
- help='Working data directory')
+ parser.add_argument(
+ '--directory',
+ '-D',
+ type=pathlib.Path,
+ default=gravtk.utilities.get_cache_path(ensure_exists=False),
+ help='Working data directory',
+ )
# GRACE/GRACE-FO processing center
- parser.add_argument('--center','-c',
- metavar='PROC', type=str, nargs='+',
- default=['CSR','GFZ','JPL'], choices=['CSR','GFZ','JPL'],
- help='GRACE/GRACE-FO processing center')
+ parser.add_argument(
+ '--center',
+ '-c',
+ metavar='PROC',
+ type=str,
+ nargs='+',
+ default=['CSR', 'GFZ', 'JPL'],
+ choices=['CSR', 'GFZ', 'JPL'],
+ help='GRACE/GRACE-FO processing center',
+ )
# GRACE/GRACE-FO data release
- parser.add_argument('--release','-r',
- metavar='DREL', type=str, nargs='+',
+ parser.add_argument(
+ '--release',
+ '-r',
+ metavar='DREL',
+ type=str,
+ nargs='+',
default=['RL06'],
- help='GRACE/GRACE-FO data release')
+ help='GRACE/GRACE-FO data release',
+ )
# GRACE/GRACE-FO data version
- parser.add_argument('--version','-v',
- metavar='VERSION', type=str, nargs=2,
- default=['0','3'],
- help='GRACE/GRACE-FO Level-2 data version')
+ parser.add_argument(
+ '--version',
+ '-v',
+ metavar='VERSION',
+ type=str,
+ nargs=2,
+ default=['0', '3'],
+ help='GRACE/GRACE-FO Level-2 data version',
+ )
# GRACE/GRACE-FO dealiasing products
- parser.add_argument('--aod1b','-a',
- default=False, action='store_true',
- help='Sync GRACE/GRACE-FO Level-1B dealiasing products')
+ parser.add_argument(
+ '--aod1b',
+ '-a',
+ default=False,
+ action='store_true',
+ help='Sync GRACE/GRACE-FO Level-1B dealiasing products',
+ )
# CMR endpoint type
- parser.add_argument('--endpoint','-e',
- type=str, default='data', choices=['s3','data'],
- help='CMR url endpoint type')
+ parser.add_argument(
+ '--endpoint',
+ '-e',
+ type=str,
+ default='data',
+ choices=['s3', 'data'],
+ help='CMR url endpoint type',
+ )
# connection timeout
- parser.add_argument('--timeout','-t',
- type=int, default=360,
- help='Timeout in seconds for blocking operations')
+ parser.add_argument(
+ '--timeout',
+ '-t',
+ type=int,
+ default=360,
+ help='Timeout in seconds for blocking operations',
+ )
# output compressed files
- parser.add_argument('--gzip','-G',
- default=False, action='store_true',
- help='Compress output GRACE/GRACE-FO Level-2 granules')
+ parser.add_argument(
+ '--gzip',
+ '-G',
+ default=False,
+ action='store_true',
+ help='Compress output GRACE/GRACE-FO Level-2 granules',
+ )
# Output log file in form
# PODAAC_sync_2002-04-01.log
- parser.add_argument('--log','-l',
- default=False, action='store_true',
- help='Output log file')
+ parser.add_argument(
+ '--log',
+ '-l',
+ default=False,
+ action='store_true',
+ help='Output log file',
+ )
# sync options
- parser.add_argument('--clobber','-C',
- default=False, action='store_true',
- help='Overwrite existing data in transfer')
+ parser.add_argument(
+ '--clobber',
+ '-C',
+ default=False,
+ action='store_true',
+ help='Overwrite existing data in transfer',
+ )
# permissions mode of the directories and files synced (number in octal)
- parser.add_argument('--mode','-M',
- type=lambda x: int(x,base=8), default=0o775,
- help='Permission mode of directories and files synced')
+ parser.add_argument(
+ '--mode',
+ '-M',
+ type=lambda x: int(x, base=8),
+ default=0o775,
+ help='Permission mode of directories and files synced',
+ )
# return the parser
return parser
+
# This is the main part of the program that calls the individual functions
def main():
# Read the system arguments listed after the program
parser = arguments()
- args,_ = parser.parse_known_args()
+ args, _ = parser.parse_known_args()
# NASA Earthdata hostname
URS = 'urs.earthdata.nasa.gov'
@@ -460,26 +606,40 @@ def main():
HOST = 'https://archive.podaac.earthdata.nasa.gov/s3credentials'
# There are a range of exceptions that can be thrown here
# including HTTPError and URLError.
- if (args.endpoint == 's3'):
+ if args.endpoint == 's3':
# build opener for s3 client access
- opener = gravtk.utilities.attempt_login(URS,
- username=args.user, password=args.password,
- netrc=args.netrc)
+ opener = gravtk.utilities.attempt_login(
+ URS, username=args.user, password=args.password, netrc=args.netrc
+ )
# Create and submit request to create AWS session
client = gravtk.utilities.s3_client(HOST, args.timeout)
else:
# build opener for data client access
- opener = gravtk.utilities.attempt_login(URS,
- username=args.user, password=args.password,
- netrc=args.netrc, authorization_header=False)
+ opener = gravtk.utilities.attempt_login(
+ URS,
+ username=args.user,
+ password=args.password,
+ netrc=args.netrc,
+ authorization_header=False,
+ )
client = None
# retrieve data objects from s3 client or data endpoints
- podaac_cumulus(client, args.directory, PROC=args.center,
- DREL=args.release, VERSION=args.version, AOD1B=args.aod1b,
- ENDPOINT=args.endpoint, TIMEOUT=args.timeout,
- GZIP=args.gzip, LOG=args.log, CLOBBER=args.clobber,
- MODE=args.mode)
+ podaac_cumulus(
+ client,
+ args.directory,
+ PROC=args.center,
+ DREL=args.release,
+ VERSION=args.version,
+ AOD1B=args.aod1b,
+ ENDPOINT=args.endpoint,
+ TIMEOUT=args.timeout,
+ GZIP=args.gzip,
+ LOG=args.log,
+ CLOBBER=args.clobber,
+ MODE=args.mode,
+ )
+
# run main program
if __name__ == '__main__':
diff --git a/dealiasing/aod1b_geocenter.py b/dealiasing/aod1b_geocenter.py
index f763322a..c96f1440 100644
--- a/dealiasing/aod1b_geocenter.py
+++ b/dealiasing/aod1b_geocenter.py
@@ -1,5 +1,5 @@
#!/usr/bin/env python
-u"""
+"""
aod1b_geocenter.py
Written by Tyler Sutterley (05/2023)
Contributions by Hugo Lecomte (03/2021)
@@ -57,6 +57,7 @@
Updated 05-06/2016: oba=ocean bottom pressure, absolute import of shutil
Written 05/2016
"""
+
from __future__ import print_function, division
import sys
@@ -69,12 +70,9 @@
import numpy as np
import gravity_toolkit as gravtk
+
# program module to read the degree 1 coefficients of the AOD1b data
-def aod1b_geocenter(base_dir,
- DREL='',
- DSET='',
- CLOBBER=False,
- MODE=0o775):
+def aod1b_geocenter(base_dir, DREL='', DSET='', CLOBBER=False, MODE=0o775):
"""
Creates monthly files of geocenter variations at 6-hour or 3-hour intervals from
GRACE/GRACE-FO level-1b dealiasing data files
@@ -113,7 +111,7 @@ def aod1b_geocenter(base_dir,
# set number of hours in a file
# set the atmospheric and ocean model for a given release
# set the maximum degree and order of a release
- if DREL in ('RL01','RL02','RL03','RL04','RL05'):
+ if DREL in ('RL01', 'RL02', 'RL03', 'RL04', 'RL05'):
# for 00, 06, 12 and 18
n_time = 4
ATMOSPHERE = 'ECMWF'
@@ -128,7 +126,7 @@ def aod1b_geocenter(base_dir,
else:
raise ValueError('Invalid data release')
# Calculating the number of cos and sin harmonics up to LMAX
- n_harm = (LMAX**2 + 3*LMAX)//2 + 1
+ n_harm = (LMAX**2 + 3 * LMAX) // 2 + 1
# AOD1B data products
product = {}
@@ -139,7 +137,7 @@ def aod1b_geocenter(base_dir,
# AOD1B directory and output geocenter directory
base_dir = pathlib.Path(base_dir).expanduser().absolute()
- grace_dir = base_dir.joinpath('AOD1B',DREL)
+ grace_dir = base_dir.joinpath('AOD1B', DREL)
output_dir = grace_dir.joinpath('geocenter')
output_dir.mkdir(mode=MODE, parents=True, exist_ok=True)
@@ -149,8 +147,8 @@ def aod1b_geocenter(base_dir,
# for each tar file
for input_file in sorted(input_tar_files):
# extract the year and month from the file
- YY,MM,SFX = tx.findall(input_file.name).pop()
- YY,MM = np.array([YY, MM], dtype=np.int64)
+ YY, MM, SFX = tx.findall(input_file.name).pop()
+ YY, MM = np.array([YY, MM], dtype=np.int64)
# output monthly geocenter file
FILE = f'AOD1B_{DREL}_{DSET}_{YY:4d}_{MM:02d}.txt'
output_file = output_dir.joinpath(FILE)
@@ -163,7 +161,7 @@ def aod1b_geocenter(base_dir,
input_mtime = input_file.stat().st_mtime
output_mtime = output_file.stat().st_mtime
# if input tar file is newer: overwrite the output file
- if (input_mtime > output_mtime):
+ if input_mtime > output_mtime:
TEST = True
OVERWRITE = ' (overwrite)'
else:
@@ -179,7 +177,7 @@ def aod1b_geocenter(base_dir,
args = ('Geocenter time series', DREL, DSET)
print('# {0} from {1} AOD1b {2} Product'.format(*args), file=f)
print('# {0}'.format(product[DSET]), file=f)
- args = ('ISO-Time','X','Y','Z')
+ args = ('ISO-Time', 'X', 'Y', 'Z')
print('# {0:^15} {1:^12} {2:^12} {3:^12}'.format(*args), file=f)
# open the AOD1B monthly tar file
@@ -190,10 +188,10 @@ def aod1b_geocenter(base_dir,
# track tar file members
logging.debug(member.name)
# get calendar day from file
- DD,SFX = fx.findall(member.name).pop()
+ DD, SFX = fx.findall(member.name).pop()
DD = np.int64(DD)
# open data file for day
- if (SFX == '.gz'):
+ if SFX == '.gz':
fid = gzip.GzipFile(fileobj=tar.extractfile(member))
else:
fid = tar.extractfile(member)
@@ -207,7 +205,7 @@ def aod1b_geocenter(base_dir,
# create counter for hour in dataset
c = 0
# while loop ends when dataset is read
- while (c < n_time):
+ while c < n_time:
# read line
file_contents = fid.readline().decode('ISO-8859-1')
# find file header for data product
@@ -215,10 +213,10 @@ def aod1b_geocenter(base_dir,
# track file header lines
logging.debug(file_contents)
# extract hour from header and convert to float
- HH, = re.findall(r'(\d+):\d+:\d+',file_contents)
+ (HH,) = re.findall(r'(\d+):\d+:\d+', file_contents)
hours[c] = np.int64(HH)
# read each line of spherical harmonics
- for k in range(0,n_harm):
+ for k in range(0, n_harm):
file_contents = fid.readline().decode('ISO-8859-1')
# find numerical instances in the data line
line_contents = rx.findall(file_contents)
@@ -237,8 +235,8 @@ def aod1b_geocenter(base_dir,
# convert from spherical harmonics into geocenter
DEG1.to_cartesian()
# write to file for each hour (iterates each 6-hour block)
- for h,X,Y,Z in zip(hours,DEG1.X,DEG1.Y,DEG1.Z):
- print(fstr.format(YY,MM,DD,h,X,Y,Z), file=f)
+ for h, X, Y, Z in zip(hours, DEG1.X, DEG1.Y, DEG1.Z):
+ print(fstr.format(YY, MM, DD, h, X, Y, Z), file=f)
# close the tar file
tar.close()
@@ -247,49 +245,77 @@ def aod1b_geocenter(base_dir,
# set the permissions mode of the output file
output_file.chmod(mode=MODE)
+
# PURPOSE: create argument parser
def arguments():
parser = argparse.ArgumentParser(
description="""Creates monthly files of geocenter variations
at 3 or 6-hour intervals
""",
- fromfile_prefix_chars="@"
+ fromfile_prefix_chars='@',
)
parser.convert_arg_line_to_args = gravtk.utilities.convert_arg_line_to_args
# command line parameters
# working data directory
- parser.add_argument('--directory','-D',
- type=pathlib.Path, default=pathlib.Path.cwd(),
- help='Working data directory')
+ parser.add_argument(
+ '--directory',
+ '-D',
+ type=pathlib.Path,
+ default=gravtk.utilities.get_cache_path(ensure_exists=False),
+ help='Working data directory',
+ )
# GRACE/GRACE-FO data release
- parser.add_argument('--release','-r',
- metavar='DREL', type=str, default='',
- help='GRACE/GRACE-FO Data Release')
+ parser.add_argument(
+ '--release',
+ '-r',
+ metavar='DREL',
+ type=str,
+ default='',
+ help='GRACE/GRACE-FO Data Release',
+ )
# GRACE/GRACE-FO level-1b dealiasing product
- parser.add_argument('--product','-p',
- metavar='DSET', type=str.lower, nargs='+',
- choices=['atm','ocn','glo','oba'],
- help='GRACE/GRACE-FO Level-1b data product')
+ parser.add_argument(
+ '--product',
+ '-p',
+ metavar='DSET',
+ type=str.lower,
+ nargs='+',
+ choices=['atm', 'ocn', 'glo', 'oba'],
+ help='GRACE/GRACE-FO Level-1b data product',
+ )
# clobber will overwrite the existing data
- parser.add_argument('--clobber','-C',
- default=False, action='store_true',
- help='Overwrite existing data')
+ parser.add_argument(
+ '--clobber',
+ '-C',
+ default=False,
+ action='store_true',
+ help='Overwrite existing data',
+ )
# verbose will output information about each output file
- parser.add_argument('--verbose','-V',
- action='count', default=0,
- help='Verbose output of processing run')
+ parser.add_argument(
+ '--verbose',
+ '-V',
+ action='count',
+ default=0,
+ help='Verbose output of processing run',
+ )
# permissions mode of the local directories and files (number in octal)
- parser.add_argument('--mode','-M',
- type=lambda x: int(x,base=8), default=0o775,
- help='Permissions mode of output files')
+ parser.add_argument(
+ '--mode',
+ '-M',
+ type=lambda x: int(x, base=8),
+ default=0o775,
+ help='Permissions mode of output files',
+ )
# return the parser
return parser
+
# This is the main part of the program that calls the individual functions
def main():
# Read the system arguments listed after the program
parser = arguments()
- args,_ = parser.parse_known_args()
+ args, _ = parser.parse_known_args()
# create logger
loglevels = [logging.CRITICAL, logging.INFO, logging.DEBUG]
logging.basicConfig(level=loglevels[args.verbose])
@@ -297,11 +323,14 @@ def main():
# for each entered AOD1B dataset
for DSET in args.product:
# run AOD1b geocenter program with parameters
- aod1b_geocenter(args.directory,
+ aod1b_geocenter(
+ args.directory,
DREL=args.release,
DSET=DSET,
CLOBBER=args.clobber,
- MODE=args.mode)
+ MODE=args.mode,
+ )
+
# run main program
if __name__ == '__main__':
diff --git a/dealiasing/aod1b_oblateness.py b/dealiasing/aod1b_oblateness.py
index 047d2cac..a4fbff82 100644
--- a/dealiasing/aod1b_oblateness.py
+++ b/dealiasing/aod1b_oblateness.py
@@ -1,5 +1,5 @@
#!/usr/bin/env python
-u"""
+"""
aod1b_oblateness.py
Written by Tyler Sutterley (05/2023)
Contributions by Hugo Lecomte (03/2021)
@@ -57,6 +57,7 @@
Updated 05-06/2016: oba=ocean bottom pressure, absolute import of shutil
Written 05/2016
"""
+
from __future__ import print_function, division
import sys
@@ -69,12 +70,9 @@
import numpy as np
import gravity_toolkit as gravtk
+
# program module to read the C20 coefficients of the AOD1b data
-def aod1b_oblateness(base_dir,
- DREL='',
- DSET='',
- CLOBBER=False,
- MODE=0o775):
+def aod1b_oblateness(base_dir, DREL='', DSET='', CLOBBER=False, MODE=0o775):
"""
Creates monthly files of oblateness (C20) variations at 6-hour intervals
from GRACE/GRACE-FO level-1b dealiasing data files
@@ -114,7 +112,7 @@ def aod1b_oblateness(base_dir,
# set number of hours in a file
# set the atmospheric and ocean model for a given release
# set the maximum degree and order of a release
- if DREL in ('RL01','RL02','RL03','RL04','RL05'):
+ if DREL in ('RL01', 'RL02', 'RL03', 'RL04', 'RL05'):
# for 00, 06, 12 and 18
n_time = 4
ATMOSPHERE = 'ECMWF'
@@ -129,7 +127,7 @@ def aod1b_oblateness(base_dir,
else:
raise ValueError('Invalid data release')
# Calculating the number of cos and sin harmonics up to LMAX
- n_harm = (LMAX**2 + 3*LMAX)//2 + 1
+ n_harm = (LMAX**2 + 3 * LMAX) // 2 + 1
# AOD1B data products
product = {}
@@ -140,7 +138,7 @@ def aod1b_oblateness(base_dir,
# AOD1B directory and output oblateness directory
base_dir = pathlib.Path(base_dir).expanduser().absolute()
- grace_dir = base_dir.joinpath('AOD1B',DREL)
+ grace_dir = base_dir.joinpath('AOD1B', DREL)
output_dir = grace_dir.joinpath('oblateness')
output_dir.mkdir(mode=MODE, parents=True, exist_ok=True)
@@ -150,8 +148,8 @@ def aod1b_oblateness(base_dir,
# for each tar file
for input_file in sorted(input_tar_files):
# extract the year and month from the file
- YY,MM,SFX = tx.findall(input_file.name).pop()
- YY,MM = np.array([YY, MM], dtype=np.int64)
+ YY, MM, SFX = tx.findall(input_file.name).pop()
+ YY, MM = np.array([YY, MM], dtype=np.int64)
# output monthly oblateness file
FILE = f'AOD1B_{DREL}_{DSET}_{YY:4d}_{MM:02d}.txt'
output_file = output_dir.joinpath(FILE)
@@ -164,7 +162,7 @@ def aod1b_oblateness(base_dir,
input_mtime = input_file.stat().st_mtime
output_mtime = output_file.stat().st_mtime
# if input tar file is newer: overwrite the output file
- if (input_mtime > output_mtime):
+ if input_mtime > output_mtime:
TEST = True
OVERWRITE = ' (overwrite)'
else:
@@ -177,10 +175,10 @@ def aod1b_oblateness(base_dir,
logging.info(f'{str(output_file)}{OVERWRITE}')
# open output monthly oblateness file
f = output_file.open(mode='w', encoding='utf8')
- args = ('Oblateness time series',DREL,DSET)
+ args = ('Oblateness time series', DREL, DSET)
print('# {0} from {1} AOD1b {2} Product'.format(*args), file=f)
print('# {0}'.format(product[DSET]), file=f)
- print('# {0:^15} {1:^15}'.format('ISO-Time','C20'), file=f)
+ print('# {0:^15} {1:^15}'.format('ISO-Time', 'C20'), file=f)
# open the AOD1B monthly tar file
tar = tarfile.open(name=str(input_file), mode='r:gz')
@@ -190,21 +188,21 @@ def aod1b_oblateness(base_dir,
# track tar file members
logging.debug(member.name)
# get calendar day from file
- DD,SFX = fx.findall(member.name).pop()
+ DD, SFX = fx.findall(member.name).pop()
DD = np.int64(DD)
# open datafile for day
- if (SFX == '.gz'):
+ if SFX == '.gz':
fid = gzip.GzipFile(fileobj=tar.extractfile(member))
else:
fid = tar.extractfile(member)
# C20 spherical harmonics for day and hours
C20 = np.zeros((n_time))
- hours = np.zeros((n_time),dtype=np.int64)
+ hours = np.zeros((n_time), dtype=np.int64)
# create counter for hour in dataset
c = 0
# while loop ends when dataset is read
- while (c < n_time):
+ while c < n_time:
# read line
file_contents = fid.readline().decode('ISO-8859-1')
# find file header for data product
@@ -212,10 +210,10 @@ def aod1b_oblateness(base_dir,
# track file header lines
logging.debug(file_contents)
# extract hour from header and convert to float
- HH, = re.findall(r'(\d+):\d+:\d+',file_contents)
+ (HH,) = re.findall(r'(\d+):\d+:\d+', file_contents)
hours[c] = np.int64(HH)
# read each line of spherical harmonics
- for k in range(0,n_harm):
+ for k in range(0, n_harm):
file_contents = fid.readline().decode('ISO-8859-1')
# find numerical instances in the data line
line_contents = rx.findall(file_contents)
@@ -230,7 +228,7 @@ def aod1b_oblateness(base_dir,
fid.close()
# write to file for each hour
for h in range(4):
- print(fstr.format(YY,MM,DD,hours[h],C20[h]),file=f)
+ print(fstr.format(YY, MM, DD, hours[h], C20[h]), file=f)
# close the tar file
tar.close()
@@ -239,49 +237,77 @@ def aod1b_oblateness(base_dir,
# set the permissions mode of the output file
output_file.chmod(mode=MODE)
+
# PURPOSE: create argument parser
def arguments():
parser = argparse.ArgumentParser(
description="""Creates monthly files of oblateness (C20)
variations at 3 or 6-hour intervals
""",
- fromfile_prefix_chars="@"
+ fromfile_prefix_chars='@',
)
parser.convert_arg_line_to_args = gravtk.utilities.convert_arg_line_to_args
# command line parameters
# working data directory
- parser.add_argument('--directory','-D',
- type=pathlib.Path, default=pathlib.Path.cwd(),
- help='Working data directory')
+ parser.add_argument(
+ '--directory',
+ '-D',
+ type=pathlib.Path,
+ default=gravtk.utilities.get_cache_path(ensure_exists=False),
+ help='Working data directory',
+ )
# GRACE/GRACE-FO data release
- parser.add_argument('--release','-r',
- metavar='DREL', type=str, default='',
- help='GRACE/GRACE-FO Data Release')
+ parser.add_argument(
+ '--release',
+ '-r',
+ metavar='DREL',
+ type=str,
+ default='',
+ help='GRACE/GRACE-FO Data Release',
+ )
# GRACE/GRACE-FO level-1b dealiasing product
- parser.add_argument('--product','-p',
- metavar='DSET', type=str.lower, nargs='+',
- choices=['atm','ocn','glo','oba'],
- help='GRACE/GRACE-FO Level-1b data product')
+ parser.add_argument(
+ '--product',
+ '-p',
+ metavar='DSET',
+ type=str.lower,
+ nargs='+',
+ choices=['atm', 'ocn', 'glo', 'oba'],
+ help='GRACE/GRACE-FO Level-1b data product',
+ )
# clobber will overwrite the existing data
- parser.add_argument('--clobber','-C',
- default=False, action='store_true',
- help='Overwrite existing data')
+ parser.add_argument(
+ '--clobber',
+ '-C',
+ default=False,
+ action='store_true',
+ help='Overwrite existing data',
+ )
# verbose will output information about each output file
- parser.add_argument('--verbose','-V',
- action='count', default=0,
- help='Verbose output of processing run')
+ parser.add_argument(
+ '--verbose',
+ '-V',
+ action='count',
+ default=0,
+ help='Verbose output of processing run',
+ )
# permissions mode of the local directories and files (number in octal)
- parser.add_argument('--mode','-M',
- type=lambda x: int(x,base=8), default=0o775,
- help='Permissions mode of output files')
+ parser.add_argument(
+ '--mode',
+ '-M',
+ type=lambda x: int(x, base=8),
+ default=0o775,
+ help='Permissions mode of output files',
+ )
# return the parser
return parser
+
# This is the main part of the program that calls the individual functions
def main():
# Read the system arguments listed after the program
parser = arguments()
- args,_ = parser.parse_known_args()
+ args, _ = parser.parse_known_args()
# create logger
loglevels = [logging.CRITICAL, logging.INFO, logging.DEBUG]
@@ -290,11 +316,14 @@ def main():
# for each entered AOD1B dataset
for DSET in args.product:
# run AOD1b oblateness program with parameters
- aod1b_oblateness(args.directory,
+ aod1b_oblateness(
+ args.directory,
DREL=args.release,
DSET=DSET,
CLOBBER=args.clobber,
- MODE=args.mode)
+ MODE=args.mode,
+ )
+
# run main program
if __name__ == '__main__':
diff --git a/dealiasing/dealiasing_global_uplift.py b/dealiasing/dealiasing_global_uplift.py
index fd6fa239..ca5f4597 100644
--- a/dealiasing/dealiasing_global_uplift.py
+++ b/dealiasing/dealiasing_global_uplift.py
@@ -1,5 +1,5 @@
#!/usr/bin/env python
-u"""
+"""
dealiasing_global_uplift.py
Written by Tyler Sutterley (05/2023)
@@ -71,6 +71,7 @@
Updated 03/2023: attributes from units class for output netCDF4/HDF5 files
Written 03/2023
"""
+
from __future__ import print_function, division
import sys
@@ -86,6 +87,7 @@
import numpy as np
import gravity_toolkit as gravtk
+
# PURPOSE: keep track of threads
def info(args):
logging.info(pathlib.Path(sys.argv[0]).name)
@@ -95,9 +97,11 @@ def info(args):
logging.info(f'parent process: {os.getppid():d}')
logging.info(f'process id: {os.getpid():d}')
+
# PURPOSE: estimates global elastic uplift due to changes in atmospheric
# and oceanic loading
-def dealiasing_global_uplift(base_dir,
+def dealiasing_global_uplift(
+ base_dir,
DREL=None,
DSET=None,
YEAR=None,
@@ -108,8 +112,8 @@ def dealiasing_global_uplift(base_dir,
BOUNDS=None,
DATAFORM=None,
OUTPUT_DIRECTORY=None,
- MODE=0o775):
-
+ MODE=0o775,
+):
# input directory setup
base_dir = pathlib.Path(base_dir).expanduser().absolute()
grace_dir = base_dir.joinpath('AOD1B', DREL)
@@ -124,7 +128,7 @@ def dealiasing_global_uplift(base_dir,
# set number of hours in a file for a release
# set the atmospheric and ocean model for a given release
# set the maximum degree and order of a release
- if DREL in ('RL01','RL02','RL03','RL04','RL05'):
+ if DREL in ('RL01', 'RL02', 'RL03', 'RL04', 'RL05'):
# for 00, 06, 12 and 18
nt = 4
ATMOSPHERE = 'ECMWF'
@@ -139,7 +143,7 @@ def dealiasing_global_uplift(base_dir,
else:
raise ValueError('Invalid data release')
# Calculating the number of cos and sin harmonics up to LMAX
- n_harm = (LMAX**2 + 3*LMAX)//2 + 1
+ n_harm = (LMAX**2 + 3 * LMAX) // 2 + 1
# AOD1B data products
product = {}
@@ -156,10 +160,11 @@ def dealiasing_global_uplift(base_dir,
attributes['ROOT']['project_version'] = DREL
attributes['ROOT']['product_name'] = DSET
attributes['ROOT']['product_type'] = 'gravity_field'
- attributes['ROOT']['reference'] = \
+ attributes['ROOT']['reference'] = (
f'Output from {pathlib.Path(sys.argv[0]).name}'
+ )
# output suffix for data formats
- suffix = dict(ascii='txt',netCDF4='nc',HDF5='H5')
+ suffix = dict(ascii='txt', netCDF4='nc', HDF5='H5')
# compile regular expressions operators for file dates
# will extract the year and month from the tar file (.tar.gz)
@@ -179,33 +184,34 @@ def dealiasing_global_uplift(base_dir,
input_tar_files = [tf for tf in grace_dir.iterdir() if tx.match(tf.name)]
# Output Degree Spacing
- dlon,dlat = (DDEG[0],DDEG[0]) if (len(DDEG) == 1) else (DDEG[0],DDEG[1])
+ dlon, dlat = (DDEG[0], DDEG[0]) if (len(DDEG) == 1) else (DDEG[0], DDEG[1])
# Output spatial data
grid = gravtk.spatial()
# Output Degree Interval
- if (INTERVAL == 1):
+ if INTERVAL == 1:
# (0:360,90:-90)
- n_lon = np.int64((360.0/dlon)+1.0)
- n_lat = np.int64((180.0/dlat)+1.0)
- grid.lon = dlon*np.arange(0,n_lon)
- grid.lat = 90.0 - dlat*np.arange(0,n_lat)
- elif (INTERVAL == 2):
+ n_lon = np.int64((360.0 / dlon) + 1.0)
+ n_lat = np.int64((180.0 / dlat) + 1.0)
+ grid.lon = dlon * np.arange(0, n_lon)
+ grid.lat = 90.0 - dlat * np.arange(0, n_lat)
+ elif INTERVAL == 2:
# (Degree spacing)/2
- grid.lon = np.arange(dlon/2.0,360+dlon/2.0,dlon)
- grid.lat = np.arange(90.0-dlat/2.0,-90.0-dlat/2.0,-dlat)
+ grid.lon = np.arange(dlon / 2.0, 360 + dlon / 2.0, dlon)
+ grid.lat = np.arange(90.0 - dlat / 2.0, -90.0 - dlat / 2.0, -dlat)
n_lon = len(grid.lon)
n_lat = len(grid.lat)
- elif (INTERVAL == 3):
+ elif INTERVAL == 3:
# non-global grid set with BOUNDS parameter
- minlon,maxlon,minlat,maxlat = BOUNDS.copy()
- grid.lon = np.arange(minlon+dlon/2.0, maxlon+dlon/2.0, dlon)
- grid.lat = np.arange(maxlat-dlat/2.0, minlat-dlat/2.0, -dlat)
+ minlon, maxlon, minlat, maxlat = BOUNDS.copy()
+ grid.lon = np.arange(minlon + dlon / 2.0, maxlon + dlon / 2.0, dlon)
+ grid.lat = np.arange(maxlat - dlat / 2.0, minlat - dlat / 2.0, -dlat)
n_lon = len(grid.lon)
n_lat = len(grid.lat)
# read arrays of kl, hl, and ll Love Numbers
- LOVE = gravtk.load_love_numbers(LMAX, LOVE_NUMBERS=LOVE_NUMBERS,
- REFERENCE=REFERENCE, FORMAT='class')
+ LOVE = gravtk.load_love_numbers(
+ LMAX, LOVE_NUMBERS=LOVE_NUMBERS, REFERENCE=REFERENCE, FORMAT='class'
+ )
# add attributes for earth parameters
attributes['ROOT']['earth_model'] = LOVE.model
attributes['ROOT']['earth_love_numbers'] = LOVE.citation
@@ -239,13 +245,13 @@ def dealiasing_global_uplift(base_dir,
attributes['time']['standard_name'] = 'time'
# Computing plms for converting to spatial domain
- theta = (90.0 - grid.lat)*np.pi/180.0
+ theta = np.radians(90.0 - grid.lat)
PLM, dPLM = gravtk.plm_holmes(LMAX, np.cos(theta))
# for each tar file
for input_file in sorted(input_tar_files):
# extract the year and month from the file
- YY,MM,SFX = tx.findall(input_file.name).pop()
+ YY, MM, SFX = tx.findall(input_file.name).pop()
# number of days per month
dpm = gravtk.time.calendar_days(int(YY))
# output monthly spatial file
@@ -259,7 +265,7 @@ def dealiasing_global_uplift(base_dir,
input_mtime = input_file.stat().st_mtime
output_mtime = output_file.stat().st_mtime
# if input tar file is newer: overwrite the output file
- if (input_mtime > output_mtime):
+ if input_mtime > output_mtime:
TEST = True
else:
TEST = True
@@ -272,10 +278,9 @@ def dealiasing_global_uplift(base_dir,
# open the AOD1B monthly tar file
tar = tarfile.open(name=str(input_file), mode='r:gz')
# number of time points
- n_time = int(nt*dpm[int(MM)-1])
+ n_time = int(nt * dpm[int(MM) - 1])
# flattened harmonics object
- YLMS = gravtk.harmonics(lmax=LMAX, mmax=LMAX,
- flattened=True)
+ YLMS = gravtk.harmonics(lmax=LMAX, mmax=LMAX, flattened=True)
YLMS.l = np.zeros((n_harm), dtype=int)
YLMS.m = np.zeros((n_harm), dtype=int)
YLMS.clm = np.zeros((n_harm, n_time))
@@ -290,16 +295,16 @@ def dealiasing_global_uplift(base_dir,
# track tar file members
logging.debug(member.name)
# get calendar day from file
- DD,SFX = fx.findall(member.name).pop()
+ DD, SFX = fx.findall(member.name).pop()
# open data file for day
- if (SFX == '.gz'):
+ if SFX == '.gz':
fid = gzip.GzipFile(fileobj=tar.extractfile(member))
else:
fid = tar.extractfile(member)
# create counter for hour in dataset
c = 0
# while loop ends when dataset is read
- while (c < nt):
+ while c < nt:
# read line
file_contents = fid.readline().decode('ISO-8859-1')
# find file header for data product
@@ -307,15 +312,15 @@ def dealiasing_global_uplift(base_dir,
# track file header lines
logging.debug(file_contents)
# extract hour from header
- HH, = re.findall(r'(\d+):\d+:\d+',file_contents)
+ (HH,) = re.findall(r'(\d+):\d+:\d+', file_contents)
# convert dates to int and save to arrays
- i = (int(DD)-1)*nt + c
+ i = (int(DD) - 1) * nt + c
years[i] = np.int64(YY)
months[i] = np.int64(MM)
days[i] = np.int64(DD)
hours[i] = np.int64(HH)
# read each line of spherical harmonics
- for k in range(0,n_harm):
+ for k in range(0, n_harm):
file_contents = fid.readline().decode('ISO-8859-1')
# find numerical instances in the data line
line_contents = rx.findall(file_contents)
@@ -323,35 +328,34 @@ def dealiasing_global_uplift(base_dir,
YLMS.l[k] = np.int64(line_contents[0])
YLMS.m[k] = np.int64(line_contents[1])
# extract spherical harmonics
- YLMS.clm[k,i] = np.float64(line_contents[2])
- YLMS.slm[k,i] = np.float64(line_contents[3])
+ YLMS.clm[k, i] = np.float64(line_contents[2])
+ YLMS.slm[k, i] = np.float64(line_contents[3])
# add 1 to hour counter
c += 1
# close the input file for day
fid.close()
# calculate times for flattened harmonics
YLMS.time = gravtk.time.convert_calendar_decimal(
- years, months, day=days, hour=hours)
+ years, months, day=days, hour=hours
+ )
YLMS.month = gravtk.time.calendar_to_grace(YLMS.time)
# convert to expanded form in output units
Ylms = YLMS.expand(date=True).convolve(dfactor)
# convert harmonics to spatial domain
- grid.data = np.zeros((n_lat,n_lon,n_time))
- grid.mask = np.zeros((n_lat,n_lon,n_time), dtype=bool)
+ grid.data = np.zeros((n_lat, n_lon, n_time))
+ grid.mask = np.zeros((n_lat, n_lon, n_time), dtype=bool)
# calculate delta times for output spatial grids
- grid.time = np.array(hours + 24*(days-1), dtype=int)
+ grid.time = np.array(hours + 24 * (days - 1), dtype=int)
# for each date in the harmonics object
- for i,iYlm in enumerate(Ylms):
+ for i, iYlm in enumerate(Ylms):
# convert to spatial domain
- grid.data[:,:,i] = gravtk.harmonic_summation(
- iYlm.clm, iYlm.slm, grid.lon, grid.lat,
- LMAX=LMAX, PLM=PLM).T
+ grid.data[:, :, i] = gravtk.harmonic_summation(
+ iYlm.clm, iYlm.slm, grid.lon, grid.lat, LMAX=LMAX, PLM=PLM
+ ).T
# update attributes for time
- attributes['time']['units'] = \
- f'hours since {YY}-{MM}-01T00:00:00'
+ attributes['time']['units'] = f'hours since {YY}-{MM}-01T00:00:00'
# output spatial data to file
- grid.to_file(output_file, format=DATAFORM,
- attributes=attributes)
+ grid.to_file(output_file, format=DATAFORM, attributes=attributes)
# set the permissions mode of the output file
output_file.chmod(mode=MODE)
# append output file to list
@@ -362,10 +366,11 @@ def dealiasing_global_uplift(base_dir,
# return the list of output files
return output_files
+
# PURPOSE: print a file log for the AOD1b spatial analysis
def output_log_file(input_arguments, output_files):
# format: aod1b_spatial_run_2002-04-01_PID-70335.log
- args = (time.strftime('%Y-%m-%d',time.localtime()), os.getpid())
+ args = (time.strftime('%Y-%m-%d', time.localtime()), os.getpid())
LOGFILE = 'aod1b_spatial_run_{0}_PID-{1:d}.log'.format(*args)
# create a unique log and open the log file
DIRECTORY = pathlib.Path(input_arguments.output_directory)
@@ -382,10 +387,11 @@ def output_log_file(input_arguments, output_files):
# close the log file
fid.close()
+
# PURPOSE: print a error file log for the AOD1b spatial analysis
def output_error_log_file(input_arguments):
# format: aod1b_spatial_failed_run_2002-04-01_PID-70335.log
- args = (time.strftime('%Y-%m-%d',time.localtime()), os.getpid())
+ args = (time.strftime('%Y-%m-%d', time.localtime()), os.getpid())
LOGFILE = 'aod1b_spatial_failed_run_{0}_PID-{1:d}.log'.format(*args)
# create a unique log and open the log file
DIRECTORY = pathlib.Path(input_arguments.output_directory)
@@ -401,6 +407,7 @@ def output_error_log_file(input_arguments):
# close the log file
fid.close()
+
# PURPOSE: create argument parser
def arguments():
parser = argparse.ArgumentParser(
@@ -408,81 +415,148 @@ def arguments():
for global atmospheric and oceanic loading and estimates anomalies
in elastic crustal uplift
""",
- fromfile_prefix_chars="@"
+ fromfile_prefix_chars='@',
)
parser.convert_arg_line_to_args = gravtk.utilities.convert_arg_line_to_args
# command line parameters
# working data directory
- parser.add_argument('--directory','-D',
- type=pathlib.Path, default=pathlib.Path.cwd(),
- help='Working data directory')
- parser.add_argument('--output-directory','-O',
- type=pathlib.Path, default=pathlib.Path.cwd(),
- help='Output directory for spatial files')
+ parser.add_argument(
+ '--directory',
+ '-D',
+ type=pathlib.Path,
+ default=gravtk.utilities.get_cache_path(ensure_exists=False),
+ help='Working data directory',
+ )
+ parser.add_argument(
+ '--output-directory',
+ '-O',
+ type=pathlib.Path,
+ default=gravtk.utilities.get_cache_path(ensure_exists=False),
+ help='Output directory for spatial files',
+ )
# GRACE/GRACE-FO data release
- parser.add_argument('--release','-r',
- metavar='DREL', type=str, default='',
- help='GRACE/GRACE-FO Data Release')
+ parser.add_argument(
+ '--release',
+ '-r',
+ metavar='DREL',
+ type=str,
+ default='',
+ help='GRACE/GRACE-FO Data Release',
+ )
# GRACE/GRACE-FO level-1b dealiasing product
- parser.add_argument('--product','-p',
- metavar='DSET', type=str.lower, default='glo',
- choices=['atm','ocn','glo','oba'],
- help='GRACE/GRACE-FO Level-1b data product')
+ parser.add_argument(
+ '--product',
+ '-p',
+ metavar='DSET',
+ type=str.lower,
+ default='glo',
+ choices=['atm', 'ocn', 'glo', 'oba'],
+ help='GRACE/GRACE-FO Level-1b data product',
+ )
# years to run
- parser.add_argument('--year','-Y',
- type=int, nargs='+', default=range(2000,2024),
- help='Years of data to run')
+ parser.add_argument(
+ '--year',
+ '-Y',
+ type=int,
+ nargs='+',
+ default=range(2000, 2024),
+ help='Years of data to run',
+ )
# different treatments of the load Love numbers
# 0: Han and Wahr (1995) values from PREM
# 1: Gegout (2005) values from PREM
# 2: Wang et al. (2012) values from PREM
# 3: Wang et al. (2012) values from PREM with hard sediment
# 4: Wang et al. (2012) values from PREM with soft sediment
- parser.add_argument('--love','-n',
- type=int, default=0, choices=[0,1,2,3,4],
- help='Treatment of the Load Love numbers')
+ parser.add_argument(
+ '--love',
+ '-n',
+ type=int,
+ default=0,
+ choices=[0, 1, 2, 3, 4],
+ help='Treatment of the Load Love numbers',
+ )
# option for setting reference frame for gravitational load love number
# reference frame options (CF, CM, CE)
- parser.add_argument('--reference',
- type=str.upper, default='CF', choices=['CF','CM','CE'],
- help='Reference frame for load Love numbers')
+ parser.add_argument(
+ '--reference',
+ type=str.upper,
+ default='CF',
+ choices=['CF', 'CM', 'CE'],
+ help='Reference frame for load Love numbers',
+ )
# output grid parameters
- parser.add_argument('--spacing','-S',
- type=float, nargs='+', default=[0.5,0.5], metavar=('dlon','dlat'),
- help='Spatial resolution of output data')
- parser.add_argument('--interval','-I',
- type=int, default=2, choices=[1,2,3],
- help=('Output grid interval '
- '(1: global, 2: centered global, 3: non-global)'))
- parser.add_argument('--bounds','-B',
- type=float, nargs=4, metavar=('lon_min','lon_max','lat_min','lat_max'),
- help='Bounding box for non-global grid')
+ parser.add_argument(
+ '--spacing',
+ '-S',
+ type=float,
+ nargs='+',
+ default=[0.5, 0.5],
+ metavar=('dlon', 'dlat'),
+ help='Spatial resolution of output data',
+ )
+ parser.add_argument(
+ '--interval',
+ '-I',
+ type=int,
+ default=2,
+ choices=[1, 2, 3],
+ help=(
+ 'Output grid interval '
+ '(1: global, 2: centered global, 3: non-global)'
+ ),
+ )
+ parser.add_argument(
+ '--bounds',
+ '-B',
+ type=float,
+ nargs=4,
+ metavar=('lon_min', 'lon_max', 'lat_min', 'lat_max'),
+ help='Bounding box for non-global grid',
+ )
# input and output data format (ascii, netCDF4, HDF5)
- parser.add_argument('--format','-F',
- type=str, default='netCDF4', choices=['ascii','netCDF4','HDF5'],
- help='Input and output data format')
+ parser.add_argument(
+ '--format',
+ '-F',
+ type=str,
+ default='netCDF4',
+ choices=['ascii', 'netCDF4', 'HDF5'],
+ help='Input and output data format',
+ )
# Output log file for each job in forms
# aod1b_spatial_run_2002-04-01_PID-00000.log
# aod1b_spatial_failed_run_2002-04-01_PID-00000.log
- parser.add_argument('--log',
- default=False, action='store_true',
- help='Output log file for each job')
+ parser.add_argument(
+ '--log',
+ default=False,
+ action='store_true',
+ help='Output log file for each job',
+ )
# print information about each input and output file
- parser.add_argument('--verbose','-V',
- action='count', default=0,
- help='Verbose output of run')
+ parser.add_argument(
+ '--verbose',
+ '-V',
+ action='count',
+ default=0,
+ help='Verbose output of run',
+ )
# permissions mode of the output files (octal)
- parser.add_argument('--mode','-M',
- type=lambda x: int(x,base=8), default=0o775,
- help='Permissions mode of output files')
+ parser.add_argument(
+ '--mode',
+ '-M',
+ type=lambda x: int(x, base=8),
+ default=0o775,
+ help='Permissions mode of output files',
+ )
# return the parser
return parser
+
# This is the main part of the program that calls the individual functions
def main():
# Read the system arguments listed after the program
parser = arguments()
- args,_ = parser.parse_known_args()
+ args, _ = parser.parse_known_args()
# create logger
loglevels = [logging.CRITICAL, logging.INFO, logging.DEBUG]
logging.basicConfig(level=loglevels[args.verbose])
@@ -491,7 +565,8 @@ def main():
try:
info(args)
# run AOD1b uplift program with parameters
- output_files = dealiasing_global_uplift(args.directory,
+ output_files = dealiasing_global_uplift(
+ args.directory,
DREL=args.release,
DSET=args.product,
YEAR=args.year,
@@ -502,18 +577,20 @@ def main():
BOUNDS=args.bounds,
DATAFORM=args.format,
OUTPUT_DIRECTORY=args.output_directory,
- MODE=args.mode)
+ MODE=args.mode,
+ )
except Exception as exc:
# if there has been an error exception
# print the type, value, and stack trace of the
# current exception being handled
logging.critical(f'process id {os.getpid():d} failed')
logging.error(traceback.format_exc())
- if args.log:# write failed job completion log file
+ if args.log: # write failed job completion log file
output_error_log_file(args)
else:
- if args.log:# write successful job completion log file
- output_log_file(args,output_files)
+ if args.log: # write successful job completion log file
+ output_log_file(args, output_files)
+
# run main program
if __name__ == '__main__':
diff --git a/dealiasing/dealiasing_monthly_mean.py b/dealiasing/dealiasing_monthly_mean.py
index d5976757..0860272f 100755
--- a/dealiasing/dealiasing_monthly_mean.py
+++ b/dealiasing/dealiasing_monthly_mean.py
@@ -1,5 +1,5 @@
#!/usr/bin/env python
-u"""
+"""
dealiasing_monthly_mean.py
Written by Tyler Sutterley (05/2023)
@@ -74,6 +74,7 @@
Updated 03/2018: copy date file from input GSM directory to output directory
Written 03/2018
"""
+
from __future__ import print_function, division
import sys
@@ -88,25 +89,39 @@
import numpy as np
import gravity_toolkit as gravtk
+
# PURPOSE: calculate the Julian day from the year and the day of the year
# http://scienceworld.wolfram.com/astronomy/JulianDate.html
def calc_julian_day(YEAR, DAY_OF_YEAR):
- JD = 367.0*YEAR - np.floor(7.0*(YEAR + np.floor(10.0/12.0))/4.0) - \
- np.floor(3.0*(np.floor((YEAR + 8.0/7.0)/100.0) + 1.0)/4.0) + \
- np.floor(275.0/9.0) + np.float64(DAY_OF_YEAR) + 1721028.5
+ JD = (
+ 367.0 * YEAR
+ - np.floor(7.0 * (YEAR + np.floor(10.0 / 12.0)) / 4.0)
+ - np.floor(3.0 * (np.floor((YEAR + 8.0 / 7.0) / 100.0) + 1.0) / 4.0)
+ + np.floor(275.0 / 9.0)
+ + np.float64(DAY_OF_YEAR)
+ + 1721028.5
+ )
return JD
-# PURPOSE: reads the AOD1B data and outputs a monthly mean
-def dealiasing_monthly_mean(base_dir, PROC=None, DREL=None, DSET=None,
- LMAX=None, DATAFORM=None, CLOBBER=False, MODE=0o775):
+# PURPOSE: reads the AOD1B data and outputs a monthly mean
+def dealiasing_monthly_mean(
+ base_dir,
+ PROC=None,
+ DREL=None,
+ DSET=None,
+ LMAX=None,
+ DATAFORM=None,
+ CLOBBER=False,
+ MODE=0o775,
+):
# output data suffix
suffix = dict(ascii='txt', netCDF4='nc', HDF5='H5')
# aod1b data products
- aod1b_products = dict(GAA='atm',GAB='ocn',GAC='glo',GAD='oba')
+ aod1b_products = dict(GAA='atm', GAB='ocn', GAC='glo', GAD='oba')
# compile regular expressions operator for the clm/slm headers
# for the specific AOD1b product
- hx = re.compile(fr'^DATA.*SET.*{aod1b_products[DSET]}',re.VERBOSE)
+ hx = re.compile(rf'^DATA.*SET.*{aod1b_products[DSET]}', re.VERBOSE)
# compile regular expression operator to find numerical instances
# will extract the data from the file
regex_pattern = r'[-+]?(?:(?:\d*\.\d+)|(?:\d+\.?))(?:[Ee][+-]?\d+)?'
@@ -114,7 +129,7 @@ def dealiasing_monthly_mean(base_dir, PROC=None, DREL=None, DSET=None,
# set number of hours in a file
# set the ocean model for a given release
- if DREL in ('RL01','RL02','RL03','RL04','RL05'):
+ if DREL in ('RL01', 'RL02', 'RL03', 'RL04', 'RL05'):
# for 00, 06, 12 and 18
nt = 4
ATMOSPHERE = 'ECMWF'
@@ -133,7 +148,7 @@ def dealiasing_monthly_mean(base_dir, PROC=None, DREL=None, DSET=None,
# Maximum spherical harmonic degree (LMAX)
LMAX = default_lmax if not LMAX else LMAX
# Calculating the number of cos and sin harmonics up to d/o of file
- n_harm = (default_lmax**2 + 3*default_lmax)//2 + 1
+ n_harm = (default_lmax**2 + 3 * default_lmax) // 2 + 1
# AOD1B data products
product = {}
@@ -155,11 +170,11 @@ def dealiasing_monthly_mean(base_dir, PROC=None, DREL=None, DSET=None,
# file formatting string if outputting to SHM format
shm = '{0}-2_{1:4.0f}{2:03.0f}-{3:4.0f}{4:03.0f}_{5}_{6}_{7}_{8}00.gz'
# center name if outputting to SHM format
- if (PROC == 'CSR'):
+ if PROC == 'CSR':
CENTER = 'UTCSR'
- elif (PROC == 'GFZ'):
+ elif PROC == 'GFZ':
CENTER = default_center
- elif (PROC == 'JPL'):
+ elif PROC == 'JPL':
CENTER = 'JPLEM'
else:
CENTER = default_center
@@ -167,9 +182,9 @@ def dealiasing_monthly_mean(base_dir, PROC=None, DREL=None, DSET=None,
# read input DATE file from GSM data product
grace_date_file = f'{PROC}_{DREL}_DATES.txt'
# names and formats of GRACE/GRACE-FO date ascii file
- names = ('t','mon','styr','stday','endyr','endday','total')
- formats = ('f','i','i','i','i','i','i')
- dtype = np.dtype({'names':names, 'formats':formats})
+ names = ('t', 'mon', 'styr', 'stday', 'endyr', 'endday', 'total')
+ formats = ('f', 'i', 'i', 'i', 'i', 'i', 'i')
+ dtype = np.dtype({'names': names, 'formats': formats})
input_date_file = grace_dir.joinpath('GSM', grace_date_file)
date_input = np.loadtxt(input_date_file, skiprows=1, dtype=dtype)
tdec = date_input['t']
@@ -183,49 +198,73 @@ def dealiasing_monthly_mean(base_dir, PROC=None, DREL=None, DSET=None,
output_date_file = grace_dir.joinpath(DSET, grace_date_file)
f_out = output_date_file.open(mode='w', encoding='utf8')
# date file header information
- args = ('Mid-date','Month','Start_Day','End_Day','Total_Days')
+ args = ('Mid-date', 'Month', 'Start_Day', 'End_Day', 'Total_Days')
print('{0} {1:>10} {2:>11} {3:>10} {4:>13}'.format(*args), file=f_out)
# for each GRACE/GRACE-FO month
- for t,gm in enumerate(grace_month):
+ for t, gm in enumerate(grace_month):
# check if GRACE/GRACE-FO month crosses years
- if (start_yr[t] != end_yr[t]):
+ if start_yr[t] != end_yr[t]:
# check if start_yr is a Leap Year or Standard Year
dpy = gravtk.time.calendar_days(start_yr[t]).sum()
# list of Julian Days to read from both start and end year
julian_days_to_read = []
# add days to read from start and end years
- julian_days_to_read.extend([calc_julian_day(start_yr[t],D)
- for D in range(start_day[t],dpy+1)])
- julian_days_to_read.extend([calc_julian_day(end_yr[t],D)
- for D in range(1,end_day[t]+1)])
+ julian_days_to_read.extend(
+ [
+ calc_julian_day(start_yr[t], D)
+ for D in range(start_day[t], dpy + 1)
+ ]
+ )
+ julian_days_to_read.extend(
+ [
+ calc_julian_day(end_yr[t], D)
+ for D in range(1, end_day[t] + 1)
+ ]
+ )
else:
# Julian Days to read going from start_day to end_day
- julian_days_to_read = [calc_julian_day(start_yr[t],D)
- for D in range(start_day[t],end_day[t]+1)]
+ julian_days_to_read = [
+ calc_julian_day(start_yr[t], D)
+ for D in range(start_day[t], end_day[t] + 1)
+ ]
# output filename for GRACE/GRACE-FO month
- if (DATAFORM == 'SHM'):
+ if DATAFORM == 'SHM':
MISSION = 'GRAC' if (gm <= 186) else 'GRFO'
- FILE = shm.format(DSET.upper(),start_yr[t],start_day[t],
- end_yr[t],end_day[t],MISSION,CENTER,'BC01',DREL[2:])
+ FILE = shm.format(
+ DSET.upper(),
+ start_yr[t],
+ start_day[t],
+ end_yr[t],
+ end_day[t],
+ MISSION,
+ CENTER,
+ 'BC01',
+ DREL[2:],
+ )
else:
- args = (PROC,DREL,DSET.upper(),LMAX,gm,suffix[DATAFORM])
+ args = (PROC, DREL, DSET.upper(), LMAX, gm, suffix[DATAFORM])
FILE = '{0}_{1}_{2}_CLM_L{3:d}_{4:03d}.{5}'.format(*args)
# complete path to output filename
OUTPUT_FILE = grace_dir.joinpath(DSET, FILE)
# calendar dates to read
JD = np.array(julian_days_to_read)
- Y,M,D,h,m,s = gravtk.time.convert_julian(JD,
- astype='i', format='tuple')
+ Y, M, D, h, m, s = gravtk.time.convert_julian(
+ JD, astype='i', format='tuple'
+ )
# find unique year and month pairs to read
- rx1='|'.join(['{0:d}-{1:02d}'.format(*p) for p in set(zip(Y,M))])
- rx2='|'.join(['{0:0d}-{1:02d}-{2:02d}'.format(*p) for p in set(zip(Y,M,D))])
+ rx1 = '|'.join(['{0:d}-{1:02d}'.format(*p) for p in set(zip(Y, M))])
+ rx2 = '|'.join(
+ ['{0:0d}-{1:02d}-{2:02d}'.format(*p) for p in set(zip(Y, M, D))]
+ )
# compile regular expressions operators for finding tar files
tx = re.compile(rf'AOD1B_({rx1})_\d+.(tar.gz|tgz)$', re.VERBOSE)
# finding all of the tar files in the AOD1b directory
- input_tar_files = [tf for tf in aod1b_dir.iterdir() if tx.match(tf.name)]
+ input_tar_files = [
+ tf for tf in aod1b_dir.iterdir() if tx.match(tf.name)
+ ]
# compile regular expressions operators for file dates
# will extract year and month and calendar day from the ascii file
fx = re.compile(rf'AOD1B_({rx2})_X_\d+.asc(.gz)?$', re.VERBOSE)
@@ -265,10 +304,15 @@ def dealiasing_monthly_mean(base_dir, PROC=None, DREL=None, DSET=None,
# print GRACE/GRACE-FO dates if there is a complete month of AOD
if COMPLETE:
# print GRACE/GRACE-FO dates to file
- print((f'{tdec[t]:13.8f} {gm:03d} '
- f'{start_yr[t]:8.0f} {start_day[t]:03d} '
- f'{end_yr[t]:8.0f} {end_day[t]:03d} '
- f'{total_days[t]:8.0f}'), file=f_out)
+ print(
+ (
+ f'{tdec[t]:13.8f} {gm:03d} '
+ f'{start_yr[t]:8.0f} {start_day[t]:03d} '
+ f'{end_yr[t]:8.0f} {end_day[t]:03d} '
+ f'{total_days[t]:8.0f}'
+ ),
+ file=f_out,
+ )
# if there are new files, files to be rewritten or clobbered
if COMPLETE and (TEST or CLOBBER):
@@ -277,10 +321,11 @@ def dealiasing_monthly_mean(base_dir, PROC=None, DREL=None, DSET=None,
# allocate for the mean output harmonics
Ylms = gravtk.harmonics(lmax=LMAX, mmax=LMAX)
# number of time points
- n_time = len(julian_days_to_read)*nt
+ n_time = len(julian_days_to_read) * nt
# flattened harmonics object
- YLMS = gravtk.harmonics(lmax=default_lmax, mmax=default_lmax,
- flattened=True)
+ YLMS = gravtk.harmonics(
+ lmax=default_lmax, mmax=default_lmax, flattened=True
+ )
YLMS.l = np.zeros((n_harm), dtype=int)
YLMS.m = np.zeros((n_harm), dtype=int)
YLMS.clm = np.zeros((n_harm, n_time))
@@ -296,7 +341,9 @@ def dealiasing_monthly_mean(base_dir, PROC=None, DREL=None, DSET=None,
# open the AOD1B monthly tar file
tar = tarfile.open(name=str(input_file), mode='r:gz')
# for each ascii file within the tar file that matches fx
- monthly_members=[m for m in tar.getmembers() if fx.match(m.name)]
+ monthly_members = [
+ m for m in tar.getmembers() if fx.match(m.name)
+ ]
for member in monthly_members:
# track tar file members
logging.debug(member.name)
@@ -304,38 +351,44 @@ def dealiasing_monthly_mean(base_dir, PROC=None, DREL=None, DSET=None,
YMD, SFX = fx.findall(member.name).pop()
YY, MM, DD = re.findall(r'\d+', YMD)
# open datafile for day
- if (SFX == '.gz'):
+ if SFX == '.gz':
fid = gzip.GzipFile(fileobj=tar.extractfile(member))
else:
fid = tar.extractfile(member)
# create counters for hour in dataset
c = 0
# while loop ends when dataset is read
- while (c < nt):
+ while c < nt:
# read line
- file_contents=fid.readline().decode('ISO-8859-1')
+ file_contents = fid.readline().decode('ISO-8859-1')
# find file header for data product
if bool(hx.search(file_contents)):
# track file header lines
logging.debug(file_contents)
# extract hour from header and convert to float
- HH, = re.findall(r'(\d+):\d+:\d+',file_contents)
+ (HH,) = re.findall(r'(\d+):\d+:\d+', file_contents)
# convert dates to int and save to arrays
years[count] = np.int64(YY)
months[count] = np.int64(MM)
days[count] = np.int64(DD)
hours[count] = np.int64(HH)
# read each line of spherical harmonics
- for k in range(0,n_harm):
- file_contents=fid.readline().decode('ISO-8859-1')
+ for k in range(0, n_harm):
+ file_contents = fid.readline().decode(
+ 'ISO-8859-1'
+ )
# find numerical instances in the data line
line_contents = rx.findall(file_contents)
# spherical harmonic degree and order
YLMS.l[k] = np.int64(line_contents[0])
YLMS.m[k] = np.int64(line_contents[1])
# extract spherical harmonics
- YLMS.clm[k,count] = np.float64(line_contents[2])
- YLMS.slm[k,count] = np.float64(line_contents[3])
+ YLMS.clm[k, count] = np.float64(
+ line_contents[2]
+ )
+ YLMS.slm[k, count] = np.float64(
+ line_contents[3]
+ )
# add 1 to hour counter
c += 1
count += 1
@@ -344,7 +397,8 @@ def dealiasing_monthly_mean(base_dir, PROC=None, DREL=None, DSET=None,
# calculate times for flattened harmonics
YLMS.time = gravtk.time.convert_calendar_decimal(
- years, months, day=days, hour=hours)
+ years, months, day=days, hour=hours
+ )
YLMS.month = gravtk.time.calendar_to_grace(YLMS.time)
# convert to expanded form and truncate to LMAX
Ylms = YLMS.expand(date=True).truncate(LMAX)
@@ -359,25 +413,29 @@ def dealiasing_monthly_mean(base_dir, PROC=None, DREL=None, DSET=None,
mean_Ylms.product = DSET
# start and end time for month
start_time = gravtk.time.convert_julian(np.min(JD))
- mean_Ylms.start_time = [f"{start_time['year']:4.0f}",
- f"{start_time['month']:02.0f}",
- f"{start_time['day']:02.0f}"]
+ mean_Ylms.start_time = [
+ f'{start_time["year"]:4.0f}',
+ f'{start_time["month"]:02.0f}',
+ f'{start_time["day"]:02.0f}',
+ ]
end_time = gravtk.time.convert_julian(np.max(JD))
- mean_Ylms.end_time = [f"{end_time['year']:4.0f}",
- f"{end_time['month']:02.0f}",
- f"{end_time['day']:02.0f}"]
+ mean_Ylms.end_time = [
+ f'{end_time["year"]:4.0f}',
+ f'{end_time["month"]:02.0f}',
+ f'{end_time["day"]:02.0f}',
+ ]
# output mean Ylms to file
- if (DATAFORM == 'ascii'):
+ if DATAFORM == 'ascii':
# ascii (.txt)
mean_Ylms.to_ascii(OUTPUT_FILE)
- elif (DATAFORM == 'netCDF4'):
+ elif DATAFORM == 'netCDF4':
# netcdf (.nc)
mean_Ylms.to_netCDF4(OUTPUT_FILE, **attributes)
- elif (DATAFORM == 'HDF5'):
+ elif DATAFORM == 'HDF5':
# HDF5 (.H5)
mean_Ylms.to_HDF5(OUTPUT_FILE, **attributes)
- elif (DATAFORM == 'SHM'):
+ elif DATAFORM == 'SHM':
mean_Ylms.to_SHM(OUTPUT_FILE, gzip=True)
# set the permissions mode of the output file
OUTPUT_FILE.chmod(mode=MODE)
@@ -386,10 +444,13 @@ def dealiasing_monthly_mean(base_dir, PROC=None, DREL=None, DSET=None,
logging.info(f'File {FILE} not output (incomplete)')
# if outputting as spherical harmonic model files
- if (DATAFORM == 'SHM'):
+ if DATAFORM == 'SHM':
# Create an index file for the output GRACE product
- grace_files = [f.name for f in grace_dir.joinpath(DSET).iterdir() if
- re.match(rf'{DSET}-2(.*?)\.gz', f.name)]
+ grace_files = [
+ f.name
+ for f in grace_dir.joinpath(DSET).iterdir()
+ if re.match(rf'{DSET}-2(.*?)\.gz', f.name)
+ ]
# outputting GRACE filenames to index
grace_index_file = grace_dir.joinpath(DSET, 'index.txt')
with grace_index_file.open(mode='w', encoding='utf8') as fid:
@@ -403,16 +464,17 @@ def dealiasing_monthly_mean(base_dir, PROC=None, DREL=None, DSET=None,
# close the output date file
f_out.close()
+
# PURPOSE: additional routines for the harmonics module
class dealiasing(gravtk.harmonics):
def __init__(self, **kwargs):
super().__init__(**kwargs)
- self.center=None
- self.release='RLxx'
- self.product=None
- self.start_time=[None]*3
- self.end_time=[None]*3
- self.gzip=True
+ self.center = None
+ self.release = 'RLxx'
+ self.product = None
+ self.start_time = [None] * 3
+ self.end_time = [None] * 3
+ self.gzip = True
def from_harmonics(self, temp):
"""
@@ -420,8 +482,18 @@ def from_harmonics(self, temp):
"""
self = dealiasing(lmax=temp.lmax, mmax=temp.mmax)
# try to assign variables to self
- for key in ['clm','slm','time','month','filename',
- 'center','release','product','start_time','end_time']:
+ for key in [
+ 'clm',
+ 'slm',
+ 'time',
+ 'month',
+ 'filename',
+ 'center',
+ 'release',
+ 'product',
+ 'start_time',
+ 'end_time',
+ ]:
try:
val = getattr(temp, key)
setattr(self, key, np.copy(val))
@@ -441,7 +513,7 @@ def to_SHM(self, filename, **kwargs):
"""
self.filename = pathlib.Path(filename).expanduser().absolute()
# set default verbosity
- kwargs.setdefault('verbose',False)
+ kwargs.setdefault('verbose', False)
logging.info(str(self.filename))
# open the output file
if self.gzip:
@@ -452,18 +524,30 @@ def to_SHM(self, filename, **kwargs):
self.print_header(fid)
self.print_harmonic(fid)
self.print_global(fid)
- self.print_variables(fid,'double precision')
+ self.print_variables(fid, 'double precision')
# output file format
- file_format = ('{0:6} {1:4d} {2:4d} {3:+18.12E} {4:+18.12E} '
- '{5:10.4E} {6:10.4E} {7} {8} {9}')
+ file_format = (
+ '{0:6} {1:4d} {2:4d} {3:+18.12E} {4:+18.12E} '
+ '{5:10.4E} {6:10.4E} {7} {8} {9}'
+ )
# start and end time in line format
start_date = '{0}{1}{2}.0000'.format(*self.start_time)
end_date = '{0}{1}{2}.0000'.format(*self.end_time)
# write to file for each spherical harmonic degree and order
- for m in range(0, self.mmax+1):
- for l in range(m, self.lmax+1):
- args = ('GRCOF2', l, m, self.clm[l,m], self.slm[l,m],
- 0, 0, start_date, end_date, 'nnnn')
+ for m in range(0, self.mmax + 1):
+ for l in range(m, self.lmax + 1):
+ args = (
+ 'GRCOF2',
+ l,
+ m,
+ self.clm[l, m],
+ self.slm[l, m],
+ 0,
+ 0,
+ start_date,
+ end_date,
+ 'nnnn',
+ )
print(file_format.format(*args), file=fid)
# close the output file
fid.close()
@@ -474,8 +558,8 @@ def print_header(self, fid):
fid.write('{0}:\n'.format('header'))
# data dimensions
fid.write(' {0}:\n'.format('dimensions'))
- fid.write(' {0:22}: {1:d}\n'.format('degree',self.lmax))
- fid.write(' {0:22}: {1:d}\n'.format('order',self.lmax))
+ fid.write(' {0:22}: {1:d}\n'.format('degree', self.lmax))
+ fid.write(' {0:22}: {1:d}\n'.format('order', self.lmax))
fid.write('\n')
# PURPOSE: print spherical harmonic attributes to YAML header
@@ -484,84 +568,96 @@ def print_harmonic(self, fid):
fid.write(' {0}:\n'.format('non-standard_attributes'))
# product id
product_id = '{0}-2'.format(self.product)
- fid.write(' {0:22}: {1}\n'.format('product_id',product_id))
+ fid.write(' {0:22}: {1}\n'.format('product_id', product_id))
# format id
fid.write(' {0:22}:\n'.format('format_id'))
short_name = 'SHM'
- fid.write(' {0:20}: {1}\n'.format('short_name',short_name))
+ fid.write(' {0:20}: {1}\n'.format('short_name', short_name))
long_name = 'Earth Gravity Spherical Harmonic Model Format'
- fid.write(' {0:20}: {1}\n'.format('long_name',long_name))
+ fid.write(' {0:20}: {1}\n'.format('long_name', long_name))
# harmonic normalization
normalization = 'fully normalized'
- fid.write(' {0:22}: {1}\n'.format('normalization',
- normalization))
+ fid.write(' {0:22}: {1}\n'.format('normalization', normalization))
# earth parameters
# gravitational constant
fid.write(' {0:22}:\n'.format('earth_gravity_param'))
long_name = 'gravitational constant times mass of Earth'
- fid.write(' {0:20}: {1}\n'.format('long_name',long_name))
+ fid.write(' {0:20}: {1}\n'.format('long_name', long_name))
units = 'm3/s2'
- fid.write(' {0:20}: {1}\n'.format('units',units))
+ fid.write(' {0:20}: {1}\n'.format('units', units))
value = '3.9860044180E+14'
- fid.write(' {0:20}: {1}\n'.format('value',value))
+ fid.write(' {0:20}: {1}\n'.format('value', value))
# equatorial radius
fid.write(' {0:22}:\n'.format('mean_equator_radius'))
long_name = 'mean equator radius'
- fid.write(' {0:20}: {1}\n'.format('long_name',long_name))
+ fid.write(' {0:20}: {1}\n'.format('long_name', long_name))
units = 'meters'
- fid.write(' {0:20}: {1}\n'.format('units',units))
+ fid.write(' {0:20}: {1}\n'.format('units', units))
value = '6.3781366000E+06'
- fid.write(' {0:20}: {1}\n'.format('value',value))
+ fid.write(' {0:20}: {1}\n'.format('value', value))
fid.write('\n')
# PURPOSE: print global attributes to YAML header
def print_global(self, fid):
fid.write(' {0}:\n'.format('global_attributes'))
# product title
- if (self.month <= 186):
+ if self.month <= 186:
MISSION = 'GRACE'
PROJECT = 'NASA Gravity Recovery And Climate Experiment (GRACE)'
- ACKNOWLEDGEMENT = ('GRACE is a joint mission of NASA (USA) and '
- 'DLR (Germany).')
+ ACKNOWLEDGEMENT = (
+ 'GRACE is a joint mission of NASA (USA) and DLR (Germany).'
+ )
else:
MISSION = 'GRACE-FO'
- PROJECT = ('NASA Gravity Recovery And Climate Experiment '
- 'Follow-On (GRACE-FO)')
- ACKNOWLEDGEMENT = ('GRACE-FO is a joint mission of the US National '
+ PROJECT = (
+ 'NASA Gravity Recovery And Climate Experiment '
+ 'Follow-On (GRACE-FO)'
+ )
+ ACKNOWLEDGEMENT = (
+ 'GRACE-FO is a joint mission of the US National '
'Aeronautics and Space Administration and the German Research '
- 'Center for Geosciences.')
- args = (MISSION,self.product,self.center,self.release)
+ 'Center for Geosciences.'
+ )
+ args = (MISSION, self.product, self.center, self.release)
title = '{0} Geopotential {1} Coefficients {2} {3}'.format(*args)
- fid.write(' {0:22}: {1}\n'.format('title',title))
+ fid.write(' {0:22}: {1}\n'.format('title', title))
# product summaries
summaries = {}
- summaries['GAA'] = ("Spherical harmonic coefficients that represent "
+ summaries['GAA'] = (
+ 'Spherical harmonic coefficients that represent '
"anomalous contributions of the non-tidal atmosphere to the Earth's "
- "mean gravity field during the specified timespan. This includes the "
- "contribution of atmospheric surface pressure over the continents, "
- "the static contribution of atmospheric pressure to ocean bottom "
- "pressure elsewhere, and the contribution of upper-air density "
- "anomalies above both the continents and the oceans.")
- summaries['GAB'] = ("Spherical harmonic coefficients that represent "
- "anomalous contributions of the non-tidal dynamic ocean to ocean "
- "bottom pressure during the specified timespan.")
- summaries['GAC'] = ("Spherical harmonic coefficients that represent "
- "the sum of the ATM (or GAA) and OCN (or GAB) coefficients during "
- "the specified timespan. These coefficients represent anomalous "
- "contributions of the non-tidal dynamic ocean to ocean bottom "
- "pressure, the non-tidal atmospheric surface pressure over the "
- "continents, the static contribution of atmospheric pressure to "
- "ocean bottom pressure, and the upper-air density anomalies above "
- "both the continents and the oceans.")
- summaries['GAD'] = ("Spherical harmonic coefficients that are zero "
- "over the continents, and provide the anomalous simulated ocean "
- "bottom pressure that includes non-tidal air and water "
- "contributions elsewhere during the specified timespan. These "
- "coefficients differ from GLO (or GAC) coefficients over the "
- "ocean domain by disregarding upper air density anomalies.")
+ 'mean gravity field during the specified timespan. This includes the '
+ 'contribution of atmospheric surface pressure over the continents, '
+ 'the static contribution of atmospheric pressure to ocean bottom '
+ 'pressure elsewhere, and the contribution of upper-air density '
+ 'anomalies above both the continents and the oceans.'
+ )
+ summaries['GAB'] = (
+ 'Spherical harmonic coefficients that represent '
+ 'anomalous contributions of the non-tidal dynamic ocean to ocean '
+ 'bottom pressure during the specified timespan.'
+ )
+ summaries['GAC'] = (
+ 'Spherical harmonic coefficients that represent '
+ 'the sum of the ATM (or GAA) and OCN (or GAB) coefficients during '
+ 'the specified timespan. These coefficients represent anomalous '
+ 'contributions of the non-tidal dynamic ocean to ocean bottom '
+ 'pressure, the non-tidal atmospheric surface pressure over the '
+ 'continents, the static contribution of atmospheric pressure to '
+ 'ocean bottom pressure, and the upper-air density anomalies above '
+ 'both the continents and the oceans.'
+ )
+ summaries['GAD'] = (
+ 'Spherical harmonic coefficients that are zero '
+ 'over the continents, and provide the anomalous simulated ocean '
+ 'bottom pressure that includes non-tidal air and water '
+ 'contributions elsewhere during the specified timespan. These '
+ 'coefficients differ from GLO (or GAC) coefficients over the '
+ 'ocean domain by disregarding upper air density anomalies.'
+ )
summary = summaries[self.product]
- fid.write(' {0:22}: {1}\n'.format('summary',''.join(summary)))
- fid.write(' {0:22}: {1}\n'.format('project',PROJECT))
+ fid.write(' {0:22}: {1}\n'.format('summary', ''.join(summary)))
+ fid.write(' {0:22}: {1}\n'.format('project', PROJECT))
keywords = []
keywords.append('GRACE')
keywords.append('GRACE-FO') if (self.month > 186) else None
@@ -585,32 +681,38 @@ def print_global(self, fid):
keywords.append('Atmosphere')
keywords.append('Non-tidal Atmosphere')
keywords.append('Dealiasing Product')
- fid.write(' {0:22}: {1}\n'.format('keywords',', '.join(keywords)))
- vocabulary = 'NASA Global Change Master Directory (GCMD) Science Keywords'
- fid.write(' {0:22}: {1}\n'.format('keywords_vocabulary',vocabulary))
- if (self.center == 'CSR'):
+ fid.write(' {0:22}: {1}\n'.format('keywords', ', '.join(keywords)))
+ vocabulary = (
+ 'NASA Global Change Master Directory (GCMD) Science Keywords'
+ )
+ fid.write(' {0:22}: {1}\n'.format('keywords_vocabulary', vocabulary))
+ if self.center == 'CSR':
institution = 'UT-AUSTIN/CSR'
- elif (self.center == 'GFZ'):
+ elif self.center == 'GFZ':
institution = 'GFZ German Research Centre for Geosciences'
- elif (self.center == 'JPL'):
+ elif self.center == 'JPL':
institution = 'NASA/JPL'
else:
# default to GFZ
institution = 'GFZ German Research Centre for Geosciences'
- fid.write(' {0:22}: {1}\n'.format('institution',institution))
+ fid.write(' {0:22}: {1}\n'.format('institution', institution))
src = 'All data from AOD1B {0}'.format(self.release)
- fid.write(' {0:22}: {1}\n'.format('source',src))
- fid.write(' {0:22}: {1:d}\n'.format('processing_level',2))
- fid.write(' {0:22}: {1}\n'.format('acknowledgement',ACKNOWLEDGEMENT))
+ fid.write(' {0:22}: {1}\n'.format('source', src))
+ fid.write(' {0:22}: {1:d}\n'.format('processing_level', 2))
+ fid.write(
+ ' {0:22}: {1}\n'.format('acknowledgement', ACKNOWLEDGEMENT)
+ )
PRODUCT_VERSION = 'Release-{0}'.format(self.release[2:])
- fid.write(' {0:22}: {1}\n'.format('product_version',PRODUCT_VERSION))
+ fid.write(
+ ' {0:22}: {1}\n'.format('product_version', PRODUCT_VERSION)
+ )
fid.write(' {0:22}:\n'.format('references'))
# date range and date created
start_date = '{0}-{1}-{2}'.format(*self.start_time)
- fid.write(' {0:22}: {1}\n'.format('time_coverage_start',start_date))
+ fid.write(' {0:22}: {1}\n'.format('time_coverage_start', start_date))
end_date = '{0}-{1}-{2}'.format(*self.end_time)
- fid.write(' {0:22}: {1}\n'.format('time_coverage_end',end_date))
- today = time.strftime('%Y-%m-%d',time.localtime())
+ fid.write(' {0:22}: {1}\n'.format('time_coverage_end', end_date))
+ today = time.strftime('%Y-%m-%d', time.localtime())
fid.write(' {0:22}: {1}\n'.format('date_created', today))
fid.write('\n')
@@ -638,7 +740,9 @@ def print_variables(self, fid, data_precision):
fid.write(' {0:20}: {1}\n'.format('comment', '3rd column'))
# clm
fid.write(' {0:22}:\n'.format('clm'))
- long_name = 'Clm coefficient; cosine coefficient for degree l and order m'
+ long_name = (
+ 'Clm coefficient; cosine coefficient for degree l and order m'
+ )
fid.write(' {0:20}: {1}\n'.format('long_name', long_name))
fid.write(' {0:20}: {1}\n'.format('data_type', data_precision))
fid.write(' {0:20}: {1}\n'.format('comment', '4th column'))
@@ -678,8 +782,11 @@ def print_variables(self, fid, data_precision):
fid.write(' {0:22}:\n'.format('solution_flags'))
long_name = 'Coefficient adjustment and a priori flags'
fid.write(' {0:20}: {1}\n'.format('long_name', long_name))
- fid.write(' {0:20}: {1}\n'.format('coverage_content_type',
- 'auxiliaryInformation'))
+ fid.write(
+ ' {0:20}: {1}\n'.format(
+ 'coverage_content_type', 'auxiliaryInformation'
+ )
+ )
fid.write(' {0:20}: {1}\n'.format('data_type', 'byte'))
fid.write(' {0:20}:\n'.format('flag_meanings'))
# solution flag meanings
@@ -688,12 +795,13 @@ def print_variables(self, fid, data_precision):
m.append('Slm adjusted, y for yes and n for no')
m.append('stochastic a priori info for Clm, y for yes and n for no')
m.append('stochastic a priori info for Slm, y for yes and n for no')
- for i,meaning in enumerate(m):
+ for i, meaning in enumerate(m):
fid.write(' - char {0:d} = {1}\n'.format(i, meaning))
fid.write(' {0:20}: {1}\n'.format('comment', '10th column'))
# end of header
fid.write('\n\n# End of YAML header\n')
+
# PURPOSE: create argument parser
def arguments():
parser = argparse.ArgumentParser(
@@ -701,56 +809,96 @@ def arguments():
specific product and outputs monthly mean for a specific
GRACE/GRACE-FO processing center and data release
""",
- fromfile_prefix_chars="@"
+ fromfile_prefix_chars='@',
)
parser.convert_arg_line_to_args = gravtk.utilities.convert_arg_line_to_args
# command line parameters
# working data directory
- parser.add_argument('--directory','-D',
- type=pathlib.Path, default=pathlib.Path.cwd(),
- help='Working data directory')
+ parser.add_argument(
+ '--directory',
+ '-D',
+ type=pathlib.Path,
+ default=gravtk.utilities.get_cache_path(ensure_exists=False),
+ help='Working data directory',
+ )
# Data processing center or satellite mission
- parser.add_argument('--center','-c',
- metavar='PROC', type=str, required=True,
- help='GRACE/GRACE-FO Processing Center')
+ parser.add_argument(
+ '--center',
+ '-c',
+ metavar='PROC',
+ type=str,
+ required=True,
+ help='GRACE/GRACE-FO Processing Center',
+ )
# GRACE/GRACE-FO data release
- parser.add_argument('--release','-r',
- metavar='DREL', type=str, default='RL06',
- help='GRACE/GRACE-FO Data Release')
+ parser.add_argument(
+ '--release',
+ '-r',
+ metavar='DREL',
+ type=str,
+ default='RL06',
+ help='GRACE/GRACE-FO Data Release',
+ )
# GRACE/GRACE-FO dealiasing product
- parser.add_argument('--product','-p',
- metavar='DSET', type=str.upper, nargs='+',
- choices=['GAA','GAB','GAC','GAD'],
- help='GRACE/GRACE-FO dealiasing product')
+ parser.add_argument(
+ '--product',
+ '-p',
+ metavar='DSET',
+ type=str.upper,
+ nargs='+',
+ choices=['GAA', 'GAB', 'GAC', 'GAD'],
+ help='GRACE/GRACE-FO dealiasing product',
+ )
# maximum spherical harmonic degree and order
- parser.add_argument('--lmax','-l',
- type=int, default=180,
- help='Maximum spherical harmonic degree')
+ parser.add_argument(
+ '--lmax',
+ '-l',
+ type=int,
+ default=180,
+ help='Maximum spherical harmonic degree',
+ )
# input and output data format (ascii, netCDF4, HDF5, SHM)
- parser.add_argument('--format','-F',
- type=str, default='netCDF4',
- choices=['ascii','netCDF4','HDF5','SHM'],
- help='Output data format')
+ parser.add_argument(
+ '--format',
+ '-F',
+ type=str,
+ default='netCDF4',
+ choices=['ascii', 'netCDF4', 'HDF5', 'SHM'],
+ help='Output data format',
+ )
# clobber will overwrite the existing data
- parser.add_argument('--clobber','-C',
- default=False, action='store_true',
- help='Overwrite existing data')
+ parser.add_argument(
+ '--clobber',
+ '-C',
+ default=False,
+ action='store_true',
+ help='Overwrite existing data',
+ )
# verbose will output information about each output file
- parser.add_argument('--verbose','-V',
- action='count', default=0,
- help='Verbose output of run')
+ parser.add_argument(
+ '--verbose',
+ '-V',
+ action='count',
+ default=0,
+ help='Verbose output of run',
+ )
# permissions mode of the local directories and files (number in octal)
- parser.add_argument('--mode','-M',
- type=lambda x: int(x,base=8), default=0o775,
- help='Permissions mode of output files')
+ parser.add_argument(
+ '--mode',
+ '-M',
+ type=lambda x: int(x, base=8),
+ default=0o775,
+ help='Permissions mode of output files',
+ )
# return the parser
return parser
+
# This is the main part of the program that calls the individual functions
def main():
# Read the system arguments listed after the program
parser = arguments()
- args,_ = parser.parse_known_args()
+ args, _ = parser.parse_known_args()
# create logger for verbosity level
loglevels = [logging.CRITICAL, logging.INFO, logging.DEBUG]
@@ -758,14 +906,17 @@ def main():
for DSET in args.product:
# run monthly mean AOD1b program with parameters
- dealiasing_monthly_mean(args.directory,
+ dealiasing_monthly_mean(
+ args.directory,
PROC=args.center,
DREL=args.release,
DSET=DSET,
LMAX=args.lmax,
DATAFORM=args.format,
CLOBBER=args.clobber,
- MODE=args.mode)
+ MODE=args.mode,
+ )
+
# run main program
if __name__ == '__main__':
diff --git a/doc/source/_assets/geoid_height.svg b/doc/source/_assets/geoid_height.svg
index d8cc9ee7..997e42da 100644
--- a/doc/source/_assets/geoid_height.svg
+++ b/doc/source/_assets/geoid_height.svg
@@ -60,7 +60,7 @@
Geoid
`_
- Creates an index file for each data product
diff --git a/doc/source/api_reference/access/esa_costg_swarm_sync.rst b/doc/source/api_reference/access/esa_costg_swarm_sync.rst
index eef1663b..f2ac4918 100644
--- a/doc/source/api_reference/access/esa_costg_swarm_sync.rst
+++ b/doc/source/api_reference/access/esa_costg_swarm_sync.rst
@@ -1,6 +1,6 @@
-=======================
-esa_costg_swarm_sync.py
-=======================
+===========================
+``esa_costg_swarm_sync.py``
+===========================
- Syncs Swarm gravity field products from the `ESA Swarm Science Server `_
- Creates an index file for each data product
diff --git a/doc/source/api_reference/access/gfz_icgem_costg_ftp.rst b/doc/source/api_reference/access/gfz_icgem_costg_ftp.rst
index 6dd2edaa..47bfb931 100644
--- a/doc/source/api_reference/access/gfz_icgem_costg_ftp.rst
+++ b/doc/source/api_reference/access/gfz_icgem_costg_ftp.rst
@@ -1,6 +1,6 @@
-======================
-gfz_icgem_costg_ftp.py
-======================
+==========================
+``gfz_icgem_costg_ftp.py``
+==========================
- Syncs GRACE/GRACE-FO/Swarm COST-G data from the `GFZ International Centre for Global Earth Models (ICGEM) `_
- Creates an index file for each data product
diff --git a/doc/source/api_reference/access/gfz_isdc_dealiasing_sync.rst b/doc/source/api_reference/access/gfz_isdc_dealiasing_sync.rst
index da1867d6..dbcbe802 100644
--- a/doc/source/api_reference/access/gfz_isdc_dealiasing_sync.rst
+++ b/doc/source/api_reference/access/gfz_isdc_dealiasing_sync.rst
@@ -1,6 +1,6 @@
-===========================
-gfz_isdc_dealiasing_sync.py
-===========================
+===============================
+``gfz_isdc_dealiasing_sync.py``
+===============================
- Syncs GRACE Level-1b dealiasing products from the `GFZ Information System and Data Center (ISDC) `_
- Optionally outputs as monthly tar files
diff --git a/doc/source/api_reference/access/gfz_isdc_grace_sync.rst b/doc/source/api_reference/access/gfz_isdc_grace_sync.rst
index 303391fa..ac1e543a 100644
--- a/doc/source/api_reference/access/gfz_isdc_grace_sync.rst
+++ b/doc/source/api_reference/access/gfz_isdc_grace_sync.rst
@@ -1,6 +1,6 @@
-======================
-gfz_isdc_grace_sync.py
-======================
+==========================
+``gfz_isdc_grace_sync.py``
+==========================
- Syncs GRACE/GRACE-FO and auxiliary data from the `GFZ Information System and Data Center (ISDC) `_
- Syncs CSR/GFZ/JPL Level-2 spherical harmonic files
diff --git a/doc/source/api_reference/access/itsg_graz_grace_sync.rst b/doc/source/api_reference/access/itsg_graz_grace_sync.rst
index 65fcfd9a..fac46cb5 100644
--- a/doc/source/api_reference/access/itsg_graz_grace_sync.rst
+++ b/doc/source/api_reference/access/itsg_graz_grace_sync.rst
@@ -1,6 +1,6 @@
-=======================
-itsg_graz_grace_sync.py
-=======================
+===========================
+``itsg_graz_grace_sync.py``
+===========================
- Syncs GRACE/GRACE-FO and auxiliary data from the `ITSG GRAZ server `_
- Creates an index file for each data product
diff --git a/doc/source/api_reference/access/podaac_cumulus.rst b/doc/source/api_reference/access/podaac_cumulus.rst
index 274b5f56..095abbb5 100644
--- a/doc/source/api_reference/access/podaac_cumulus.rst
+++ b/doc/source/api_reference/access/podaac_cumulus.rst
@@ -1,6 +1,6 @@
-=================
-podaac_cumulus.py
-=================
+=====================
+``podaac_cumulus.py``
+=====================
- Syncs GRACE/GRACE-FO data from `NASA JPL PO.DAAC Cumulus AWS S3 bucket `_
- S3 Cumulus syncs are only available in AWS instances in ``us-west-2``
diff --git a/doc/source/api_reference/associated_legendre.rst b/doc/source/api_reference/associated_legendre.rst
index d198e54e..d608c7ba 100644
--- a/doc/source/api_reference/associated_legendre.rst
+++ b/doc/source/api_reference/associated_legendre.rst
@@ -1,6 +1,6 @@
-===================
-associated_legendre
-===================
+=======================
+``associated_legendre``
+=======================
- Computes fully-normalized associated Legendre Polynomials and their first derivative for a vector of ``x`` values
diff --git a/doc/source/api_reference/clenshaw_summation.rst b/doc/source/api_reference/clenshaw_summation.rst
index e6ff5c99..7acb2bf4 100644
--- a/doc/source/api_reference/clenshaw_summation.rst
+++ b/doc/source/api_reference/clenshaw_summation.rst
@@ -1,6 +1,6 @@
-==================
-clenshaw_summation
-==================
+======================
+``clenshaw_summation``
+======================
- Returns the spatial field for a series of spherical harmonics at a sequence of ungridded points
- Uses a Clenshaw summation to calculate the spherical harmonic summation
diff --git a/doc/source/api_reference/dealiasing/aod1b_geocenter.rst b/doc/source/api_reference/dealiasing/aod1b_geocenter.rst
index 14ca6606..ad215f02 100644
--- a/doc/source/api_reference/dealiasing/aod1b_geocenter.rst
+++ b/doc/source/api_reference/dealiasing/aod1b_geocenter.rst
@@ -1,6 +1,6 @@
-==================
-aod1b_geocenter.py
-==================
+======================
+``aod1b_geocenter.py``
+======================
- Reads GRACE/GRACE-FO level-1b dealiasing data files for a specific product
diff --git a/doc/source/api_reference/dealiasing/aod1b_oblateness.rst b/doc/source/api_reference/dealiasing/aod1b_oblateness.rst
index 8fceda7d..69df766f 100644
--- a/doc/source/api_reference/dealiasing/aod1b_oblateness.rst
+++ b/doc/source/api_reference/dealiasing/aod1b_oblateness.rst
@@ -1,6 +1,6 @@
-===================
-aod1b_oblateness.py
-===================
+=======================
+``aod1b_oblateness.py``
+=======================
- Reads GRACE/GRACE-FO level-1b dealiasing data files for a specific product
diff --git a/doc/source/api_reference/dealiasing/dealiasing_global_uplift.rst b/doc/source/api_reference/dealiasing/dealiasing_global_uplift.rst
index 0705b115..154e0444 100644
--- a/doc/source/api_reference/dealiasing/dealiasing_global_uplift.rst
+++ b/doc/source/api_reference/dealiasing/dealiasing_global_uplift.rst
@@ -1,6 +1,6 @@
-===========================
-dealiasing_global_uplift.py
-===========================
+===============================
+``dealiasing_global_uplift.py``
+===============================
- Reads GRACE/GRACE-FO level-1b dealiasing data files for global atmospheric and oceanic loading and estimates anomalies in elastic crustal uplift :cite:p:`Davis:2004il,Wahr:1998hy`
diff --git a/doc/source/api_reference/dealiasing/dealiasing_monthly_mean.rst b/doc/source/api_reference/dealiasing/dealiasing_monthly_mean.rst
index ba27f356..0e68d36b 100644
--- a/doc/source/api_reference/dealiasing/dealiasing_monthly_mean.rst
+++ b/doc/source/api_reference/dealiasing/dealiasing_monthly_mean.rst
@@ -1,6 +1,6 @@
-==========================
-dealiasing_monthly_mean.py
-==========================
+==============================
+``dealiasing_monthly_mean.py``
+==============================
- Reads GRACE/GRACE-FO level-1b dealiasing data files for a specific product and outputs monthly the mean for a specific GRACE/GRACE-FO processing center and data release
diff --git a/doc/source/api_reference/degree_amplitude.rst b/doc/source/api_reference/degree_amplitude.rst
index b041e0d2..aad8df00 100644
--- a/doc/source/api_reference/degree_amplitude.rst
+++ b/doc/source/api_reference/degree_amplitude.rst
@@ -1,6 +1,6 @@
-================
-degree_amplitude
-================
+====================
+``degree_amplitude``
+====================
- Calculates the amplitude of each spherical harmonic degree
diff --git a/doc/source/api_reference/destripe_harmonics.rst b/doc/source/api_reference/destripe_harmonics.rst
index ce876194..ae5990d2 100644
--- a/doc/source/api_reference/destripe_harmonics.rst
+++ b/doc/source/api_reference/destripe_harmonics.rst
@@ -1,6 +1,6 @@
-==================
-destripe_harmonics
-==================
+======================
+``destripe_harmonics``
+======================
- Filters spherical harmonic coefficients for correlated "striping" errors following :cite:t:`Swenson:2006hu`
diff --git a/doc/source/api_reference/fourier_legendre.rst b/doc/source/api_reference/fourier_legendre.rst
index 196f9849..d11a1823 100644
--- a/doc/source/api_reference/fourier_legendre.rst
+++ b/doc/source/api_reference/fourier_legendre.rst
@@ -1,6 +1,6 @@
-================
-fourier_legendre
-================
+====================
+``fourier_legendre``
+====================
- Computes Fourier coefficients of the associated Legendre functions
diff --git a/doc/source/api_reference/gauss_weights.rst b/doc/source/api_reference/gauss_weights.rst
index e438a439..fab531a4 100644
--- a/doc/source/api_reference/gauss_weights.rst
+++ b/doc/source/api_reference/gauss_weights.rst
@@ -1,6 +1,6 @@
-=============
-gauss_weights
-=============
+=================
+``gauss_weights``
+=================
- Computes the Gaussian weights as a function of degree
- A normalized version of Christopher Jekeli's Gaussian averaging function
diff --git a/doc/source/api_reference/gen_averaging_kernel.rst b/doc/source/api_reference/gen_averaging_kernel.rst
index 13dde4e2..ccd33d06 100644
--- a/doc/source/api_reference/gen_averaging_kernel.rst
+++ b/doc/source/api_reference/gen_averaging_kernel.rst
@@ -1,6 +1,6 @@
-====================
-gen_averaging_kernel
-====================
+========================
+``gen_averaging_kernel``
+========================
- Generates averaging kernel coefficients which minimize the total error
diff --git a/doc/source/api_reference/gen_disc_load.rst b/doc/source/api_reference/gen_disc_load.rst
index c801d298..72ee294f 100644
--- a/doc/source/api_reference/gen_disc_load.rst
+++ b/doc/source/api_reference/gen_disc_load.rst
@@ -1,6 +1,6 @@
-=============
-gen_disc_load
-=============
+=================
+``gen_disc_load``
+=================
- Calculates gravitational spherical harmonic coefficients for a uniform disc load
diff --git a/doc/source/api_reference/gen_harmonics.rst b/doc/source/api_reference/gen_harmonics.rst
index 428def38..d0b619f3 100644
--- a/doc/source/api_reference/gen_harmonics.rst
+++ b/doc/source/api_reference/gen_harmonics.rst
@@ -1,6 +1,6 @@
-=============
-gen_harmonics
-=============
+=================
+``gen_harmonics``
+=================
- Converts data from the spatial domain to spherical harmonic coefficients
- Does not compute the solid Earth elastic response or convert units
diff --git a/doc/source/api_reference/gen_point_load.rst b/doc/source/api_reference/gen_point_load.rst
index b8679a50..74d0ce8c 100644
--- a/doc/source/api_reference/gen_point_load.rst
+++ b/doc/source/api_reference/gen_point_load.rst
@@ -1,6 +1,6 @@
-==============
-gen_point_load
-==============
+==================
+``gen_point_load``
+==================
- Calculates gravitational spherical harmonic coefficients for point masses
@@ -18,4 +18,4 @@ Calling Sequence
.. autofunction:: gravity_toolkit.gen_point_load
-.. autofunction:: gravity_toolkit.gen_point_load.spherical_harmonic_matrix
+.. autofunction:: gravity_toolkit.gen_point_load._complex_harmonics
diff --git a/doc/source/api_reference/gen_spherical_cap.rst b/doc/source/api_reference/gen_spherical_cap.rst
index 069c0f28..e404f608 100644
--- a/doc/source/api_reference/gen_spherical_cap.rst
+++ b/doc/source/api_reference/gen_spherical_cap.rst
@@ -1,6 +1,6 @@
-=================
-gen_spherical_cap
-=================
+=====================
+``gen_spherical_cap``
+=====================
- Calculates gravitational spherical harmonic coefficients for a spherical cap
diff --git a/doc/source/api_reference/gen_stokes.rst b/doc/source/api_reference/gen_stokes.rst
index 460b40fa..635eeab9 100644
--- a/doc/source/api_reference/gen_stokes.rst
+++ b/doc/source/api_reference/gen_stokes.rst
@@ -1,6 +1,6 @@
-==========
-gen_stokes
-==========
+==============
+``gen_stokes``
+==============
- Converts data from the spatial domain to spherical harmonic coefficients
diff --git a/doc/source/api_reference/geocenter.rst b/doc/source/api_reference/geocenter.rst
index 5a3e5cb2..001c6fda 100644
--- a/doc/source/api_reference/geocenter.rst
+++ b/doc/source/api_reference/geocenter.rst
@@ -1,9 +1,15 @@
-=========
-geocenter
-=========
+=============
+``geocenter``
+=============
Data class for reading and processing geocenter data
+ - Can read geocenter files from data providers
+ - Can merge a list of :py:class:`geocenter` objects into a single object
+ - Can subset to a list of GRACE/GRACE-FO months
+ - Can output :py:class:`geocenter` objects to ascii and netCDF4 files
+
+
`Source code`__
.. __: https://github.com/tsutterley/gravity-toolkit/blob/main/gravity_toolkit/geocenter.py
diff --git a/doc/source/api_reference/geocenter/calc_degree_one.rst b/doc/source/api_reference/geocenter/calc_degree_one.rst
index 0692a5d9..9d382047 100644
--- a/doc/source/api_reference/geocenter/calc_degree_one.rst
+++ b/doc/source/api_reference/geocenter/calc_degree_one.rst
@@ -1,6 +1,6 @@
-==================
-calc_degree_one.py
-==================
+======================
+``calc_degree_one.py``
+======================
- Calculates degree 1 variations using GRACE/GRACE-FO coefficients of degree 2 and greater, and modeled ocean bottom pressure variations :cite:p:`Swenson:2008cr,Sutterley:2019bx`.
diff --git a/doc/source/api_reference/geocenter/monte_carlo_degree_one.rst b/doc/source/api_reference/geocenter/monte_carlo_degree_one.rst
index 17810600..951eefac 100644
--- a/doc/source/api_reference/geocenter/monte_carlo_degree_one.rst
+++ b/doc/source/api_reference/geocenter/monte_carlo_degree_one.rst
@@ -1,6 +1,6 @@
-=========================
-monte_carlo_degree_one.py
-=========================
+=============================
+``monte_carlo_degree_one.py``
+=============================
- Estimates uncertainties in degree 1 using GRACE/GRACE-FO coefficients of degree 2 and greater, and modeled ocean bottom pressure variations in a Monte Carlo scheme :cite:p:`Swenson:2008cr,Sutterley:2019bx`.
- Calculates the estimated spherical harmonic errors following :cite:t:`Wahr:2006bx`
diff --git a/doc/source/api_reference/grace_date.rst b/doc/source/api_reference/grace_date.rst
index e4c846ca..3cb7f773 100644
--- a/doc/source/api_reference/grace_date.rst
+++ b/doc/source/api_reference/grace_date.rst
@@ -1,6 +1,6 @@
-==========
-grace_date
-==========
+==============
+``grace_date``
+==============
- Reads GRACE/GRACE-FO index file from `podaac_cumulus.py` or `gfz_isdc_grace_ftp.py`
- Parses dates of each GRACE/GRACE-FO file and assigns the month number
diff --git a/doc/source/api_reference/grace_find_months.rst b/doc/source/api_reference/grace_find_months.rst
index 28baf31c..8bbd568d 100644
--- a/doc/source/api_reference/grace_find_months.rst
+++ b/doc/source/api_reference/grace_find_months.rst
@@ -1,6 +1,6 @@
-=================
-grace_find_months
-=================
+=====================
+``grace_find_months``
+=====================
- Finds the months available for a GRACE/GRACE-FO/Swarm product
- Finds the all months missing from the product
diff --git a/doc/source/api_reference/grace_input_months.rst b/doc/source/api_reference/grace_input_months.rst
index e168253b..89206109 100644
--- a/doc/source/api_reference/grace_input_months.rst
+++ b/doc/source/api_reference/grace_input_months.rst
@@ -1,6 +1,6 @@
-==================
-grace_input_months
-==================
+======================
+``grace_input_months``
+======================
- Reads GRACE/GRACE-FO/Swarm files for a specified spherical harmonic degree and order and for a specified date range
diff --git a/doc/source/api_reference/grace_months_index.rst b/doc/source/api_reference/grace_months_index.rst
index f39478d8..3f2ca68d 100644
--- a/doc/source/api_reference/grace_months_index.rst
+++ b/doc/source/api_reference/grace_months_index.rst
@@ -1,6 +1,6 @@
-==================
-grace_months_index
-==================
+======================
+``grace_months_index``
+======================
- Creates an index of dates for all GRACE/GRACE-FO processing centers
diff --git a/doc/source/api_reference/harmonic_gradients.rst b/doc/source/api_reference/harmonic_gradients.rst
index 59bcf431..711ba520 100644
--- a/doc/source/api_reference/harmonic_gradients.rst
+++ b/doc/source/api_reference/harmonic_gradients.rst
@@ -1,6 +1,6 @@
-==================
-harmonic_gradients
-==================
+======================
+``harmonic_gradients``
+======================
- Calculates the zonal and meridional gradients of a scalar field from a series of spherical harmonics
diff --git a/doc/source/api_reference/harmonic_summation.rst b/doc/source/api_reference/harmonic_summation.rst
index 317483fc..7a70b1f4 100644
--- a/doc/source/api_reference/harmonic_summation.rst
+++ b/doc/source/api_reference/harmonic_summation.rst
@@ -1,6 +1,6 @@
-==================
-harmonic_summation
-==================
+======================
+``harmonic_summation``
+======================
- Returns the spatial field for a series of spherical harmonics
diff --git a/doc/source/api_reference/harmonics.rst b/doc/source/api_reference/harmonics.rst
index ed10f90c..ae152401 100644
--- a/doc/source/api_reference/harmonics.rst
+++ b/doc/source/api_reference/harmonics.rst
@@ -1,16 +1,16 @@
-=========
-harmonics
-=========
+=============
+``harmonics``
+=============
Spherical harmonic data class for processing GRACE/GRACE-FO Level-2 data
- Can read ascii, netCDF4, HDF5 files
- Can read from an index of the above file types
- - Can merge a list of ``harmonics`` objects into a single object
+ - Can merge a list of :py:class:`harmonics` objects into a single object
- Can subset to a list of GRACE/GRACE-FO months
- - Can calculate the mean field of a ``harmonics`` object
- - Can filter ``harmonics`` for correlated "striping" errors
- - Can output ``harmonics`` objects to ascii, netCDF4 or HDF5 files
+ - Can calculate the mean field of a :py:class:`harmonics` object
+ - Can filter :py:class:`harmonics` for correlated "striping" errors
+ - Can output :py:class:`harmonics` objects to ascii, netCDF4 or HDF5 files
Calling Sequence
================
diff --git a/doc/source/api_reference/legendre.rst b/doc/source/api_reference/legendre.rst
index 011fd089..4736c781 100644
--- a/doc/source/api_reference/legendre.rst
+++ b/doc/source/api_reference/legendre.rst
@@ -1,6 +1,6 @@
-========
-legendre
-========
+============
+``legendre``
+============
- Computes associated Legendre functions of degree ``l`` evaluated for elements ``x``
- ``l`` must be a scalar integer and ``x`` must contain real values ranging -1 <= ``x`` <= 1
diff --git a/doc/source/api_reference/legendre_polynomials.rst b/doc/source/api_reference/legendre_polynomials.rst
index 6fda4075..a62f495a 100644
--- a/doc/source/api_reference/legendre_polynomials.rst
+++ b/doc/source/api_reference/legendre_polynomials.rst
@@ -1,6 +1,6 @@
-====================
-legendre_polynomials
-====================
+========================
+``legendre_polynomials``
+========================
- Computes fully-normalized Legendre polynomials for an array of ``x`` values and their first derivative
- Calculates Legendre polynomials for zonal harmonics (order 0)
diff --git a/doc/source/api_reference/mapping/plot_AIS_GrIS_maps.rst b/doc/source/api_reference/mapping/plot_AIS_GrIS_maps.rst
index 7d8e0028..a51e0363 100644
--- a/doc/source/api_reference/mapping/plot_AIS_GrIS_maps.rst
+++ b/doc/source/api_reference/mapping/plot_AIS_GrIS_maps.rst
@@ -1,6 +1,6 @@
-=====================
-plot_AIS_GrIS_maps.py
-=====================
+=========================
+``plot_AIS_GrIS_maps.py``
+=========================
- Creates GMT-like plots for the Greenland and Antarctic ice sheets
diff --git a/doc/source/api_reference/mapping/plot_AIS_grid_3maps.rst b/doc/source/api_reference/mapping/plot_AIS_grid_3maps.rst
index 47968ade..6c1d4e22 100644
--- a/doc/source/api_reference/mapping/plot_AIS_grid_3maps.rst
+++ b/doc/source/api_reference/mapping/plot_AIS_grid_3maps.rst
@@ -1,6 +1,6 @@
-======================
-plot_AIS_grid_3maps.py
-======================
+==========================
+``plot_AIS_grid_3maps.py``
+==========================
- Creates 3 GMT-like plots for the Antarctic Ice Sheet on a polar stereographic south (3031) projection
diff --git a/doc/source/api_reference/mapping/plot_AIS_grid_4maps.rst b/doc/source/api_reference/mapping/plot_AIS_grid_4maps.rst
index 41b45f43..b6f6d3d6 100644
--- a/doc/source/api_reference/mapping/plot_AIS_grid_4maps.rst
+++ b/doc/source/api_reference/mapping/plot_AIS_grid_4maps.rst
@@ -1,6 +1,6 @@
-======================
-plot_AIS_grid_4maps.py
-======================
+==========================
+``plot_AIS_grid_4maps.py``
+==========================
- Creates 4 GMT-like plots for the Antarctic Ice Sheet on a polar stereographic south (3031) projection
diff --git a/doc/source/api_reference/mapping/plot_AIS_grid_maps.rst b/doc/source/api_reference/mapping/plot_AIS_grid_maps.rst
index 6eba7179..7773f2c9 100644
--- a/doc/source/api_reference/mapping/plot_AIS_grid_maps.rst
+++ b/doc/source/api_reference/mapping/plot_AIS_grid_maps.rst
@@ -1,6 +1,6 @@
-=====================
-plot_AIS_grid_maps.py
-=====================
+=========================
+``plot_AIS_grid_maps.py``
+=========================
- Creates GMT-like plots for the Antarctic Ice Sheet on a polar stereographic south (3031) projection
diff --git a/doc/source/api_reference/mapping/plot_AIS_grid_movie.rst b/doc/source/api_reference/mapping/plot_AIS_grid_movie.rst
index 83d41c15..e252b75d 100644
--- a/doc/source/api_reference/mapping/plot_AIS_grid_movie.rst
+++ b/doc/source/api_reference/mapping/plot_AIS_grid_movie.rst
@@ -1,6 +1,6 @@
-======================
-plot_AIS_grid_movie.py
-======================
+==========================
+``plot_AIS_grid_movie.py``
+==========================
- Creates GMT-like anomations for the Antarctic Ice Sheet on a polar stereographic south (3031) projection
diff --git a/doc/source/api_reference/mapping/plot_AIS_regional_maps.rst b/doc/source/api_reference/mapping/plot_AIS_regional_maps.rst
index 2ecbceef..ca754533 100644
--- a/doc/source/api_reference/mapping/plot_AIS_regional_maps.rst
+++ b/doc/source/api_reference/mapping/plot_AIS_regional_maps.rst
@@ -1,6 +1,6 @@
-=========================
-plot_AIS_regional_maps.py
-=========================
+=============================
+``plot_AIS_regional_maps.py``
+=============================
- Creates GMT-like plots for sub-regions of Antarctica on a polar stereographic south (3031) projection
diff --git a/doc/source/api_reference/mapping/plot_AIS_regional_movie.rst b/doc/source/api_reference/mapping/plot_AIS_regional_movie.rst
index 39ed1b42..bb6ca333 100644
--- a/doc/source/api_reference/mapping/plot_AIS_regional_movie.rst
+++ b/doc/source/api_reference/mapping/plot_AIS_regional_movie.rst
@@ -1,6 +1,6 @@
-==========================
-plot_AIS_regional_movie.py
-==========================
+==============================
+``plot_AIS_regional_movie.py``
+==============================
- Creates GMT-like animations for sub-regions of Antarctica on a polar stereographic south (3031) projection
diff --git a/doc/source/api_reference/mapping/plot_GrIS_grid_3maps.rst b/doc/source/api_reference/mapping/plot_GrIS_grid_3maps.rst
index 729bf1c5..072ea029 100644
--- a/doc/source/api_reference/mapping/plot_GrIS_grid_3maps.rst
+++ b/doc/source/api_reference/mapping/plot_GrIS_grid_3maps.rst
@@ -1,6 +1,6 @@
-=======================
-plot_GrIS_grid_3maps.py
-=======================
+===========================
+``plot_GrIS_grid_3maps.py``
+===========================
- Creates 3 GMT-like plots for the Greenland Ice Sheet on a NSIDC polar stereographic north (3413) projection
diff --git a/doc/source/api_reference/mapping/plot_GrIS_grid_5maps.rst b/doc/source/api_reference/mapping/plot_GrIS_grid_5maps.rst
index 6760dd29..44a09956 100644
--- a/doc/source/api_reference/mapping/plot_GrIS_grid_5maps.rst
+++ b/doc/source/api_reference/mapping/plot_GrIS_grid_5maps.rst
@@ -1,6 +1,6 @@
-=======================
-plot_GrIS_grid_5maps.py
-=======================
+===========================
+``plot_GrIS_grid_5maps.py``
+===========================
- Creates 5 GMT-like plots for the Greenland Ice Sheet on a NSIDC polar stereographic north (3413) projection
diff --git a/doc/source/api_reference/mapping/plot_GrIS_grid_maps.rst b/doc/source/api_reference/mapping/plot_GrIS_grid_maps.rst
index 7103e776..94630749 100644
--- a/doc/source/api_reference/mapping/plot_GrIS_grid_maps.rst
+++ b/doc/source/api_reference/mapping/plot_GrIS_grid_maps.rst
@@ -1,6 +1,6 @@
-======================
-plot_GrIS_grid_maps.py
-======================
+==========================
+``plot_GrIS_grid_maps.py``
+==========================
- Creates GMT-like plots for the Greenland Ice Sheet on a NSIDC polar stereographic north (3413) projection
diff --git a/doc/source/api_reference/mapping/plot_GrIS_grid_movie.rst b/doc/source/api_reference/mapping/plot_GrIS_grid_movie.rst
index c93ffb24..3294fe46 100644
--- a/doc/source/api_reference/mapping/plot_GrIS_grid_movie.rst
+++ b/doc/source/api_reference/mapping/plot_GrIS_grid_movie.rst
@@ -1,6 +1,6 @@
-=======================
-plot_GrIS_grid_movie.py
-=======================
+===========================
+``plot_GrIS_grid_movie.py``
+===========================
- Creates GMT-like animations for the Greenland Ice Sheet on a NSIDC polar stereographic north (3413) projection
diff --git a/doc/source/api_reference/mapping/plot_global_grid_3maps.rst b/doc/source/api_reference/mapping/plot_global_grid_3maps.rst
index 9f01781a..d88a092b 100644
--- a/doc/source/api_reference/mapping/plot_global_grid_3maps.rst
+++ b/doc/source/api_reference/mapping/plot_global_grid_3maps.rst
@@ -1,6 +1,6 @@
-=========================
-plot_global_grid_3maps.py
-=========================
+=============================
+``plot_global_grid_3maps.py``
+=============================
- Creates 3 GMT-like plots on a global Plate Carr\ |eacute|\e (Equirectangular) projection
diff --git a/doc/source/api_reference/mapping/plot_global_grid_4maps.rst b/doc/source/api_reference/mapping/plot_global_grid_4maps.rst
index 556d6f98..c93f94e3 100644
--- a/doc/source/api_reference/mapping/plot_global_grid_4maps.rst
+++ b/doc/source/api_reference/mapping/plot_global_grid_4maps.rst
@@ -1,6 +1,6 @@
-=========================
-plot_global_grid_4maps.py
-=========================
+=============================
+``plot_global_grid_4maps.py``
+=============================
- Creates 4 GMT-like plots on a global Plate Carr\ |eacute|\e (Equirectangular) projection
diff --git a/doc/source/api_reference/mapping/plot_global_grid_5maps.rst b/doc/source/api_reference/mapping/plot_global_grid_5maps.rst
index b0538d79..602d5d4b 100644
--- a/doc/source/api_reference/mapping/plot_global_grid_5maps.rst
+++ b/doc/source/api_reference/mapping/plot_global_grid_5maps.rst
@@ -1,6 +1,6 @@
-=========================
-plot_global_grid_5maps.py
-=========================
+=============================
+``plot_global_grid_5maps.py``
+=============================
- Creates 5 GMT-like plots on a global Plate Carr\ |eacute|\e (Equirectangular) projection
diff --git a/doc/source/api_reference/mapping/plot_global_grid_9maps.rst b/doc/source/api_reference/mapping/plot_global_grid_9maps.rst
index 1b8c55cb..36ebc1ed 100644
--- a/doc/source/api_reference/mapping/plot_global_grid_9maps.rst
+++ b/doc/source/api_reference/mapping/plot_global_grid_9maps.rst
@@ -1,6 +1,6 @@
-=========================
-plot_global_grid_9maps.py
-=========================
+=============================
+``plot_global_grid_9maps.py``
+=============================
- Creates 9 GMT-like plots on a global Plate Carr\ |eacute|\e (Equirectangular) projection
diff --git a/doc/source/api_reference/mapping/plot_global_grid_maps.rst b/doc/source/api_reference/mapping/plot_global_grid_maps.rst
index a2286b4b..97217872 100644
--- a/doc/source/api_reference/mapping/plot_global_grid_maps.rst
+++ b/doc/source/api_reference/mapping/plot_global_grid_maps.rst
@@ -1,6 +1,6 @@
-========================
-plot_global_grid_maps.py
-========================
+============================
+``plot_global_grid_maps.py``
+============================
- Creates GMT-like plots on a global Plate Carr\ |eacute|\e (Equirectangular) projection
diff --git a/doc/source/api_reference/mapping/plot_global_grid_movie.rst b/doc/source/api_reference/mapping/plot_global_grid_movie.rst
index a8818f5b..9d9af07f 100644
--- a/doc/source/api_reference/mapping/plot_global_grid_movie.rst
+++ b/doc/source/api_reference/mapping/plot_global_grid_movie.rst
@@ -1,6 +1,6 @@
-=========================
-plot_global_grid_movie.py
-=========================
+=============================
+``plot_global_grid_movie.py``
+=============================
- Creates GMT-like animations on a global Plate Carr\ |eacute|\e (Equirectangular) projection
diff --git a/doc/source/api_reference/mascons.rst b/doc/source/api_reference/mascons.rst
index 1b10d56b..fa08aa9a 100644
--- a/doc/source/api_reference/mascons.rst
+++ b/doc/source/api_reference/mascons.rst
@@ -1,6 +1,6 @@
-=======
-mascons
-=======
+===========
+``mascons``
+===========
Conversion routines for publicly available GRACE/GRACE-FO mascon solutions
diff --git a/doc/source/api_reference/ocean_stokes.rst b/doc/source/api_reference/ocean_stokes.rst
index 4c84ce82..14226181 100644
--- a/doc/source/api_reference/ocean_stokes.rst
+++ b/doc/source/api_reference/ocean_stokes.rst
@@ -1,6 +1,6 @@
-============
-ocean_stokes
-============
+================
+``ocean_stokes``
+================
- Reads a land-sea mask and converts to a series of spherical harmonics
- `netCDF4 land-sea mask files `_ from :cite:p:`Sutterley:2020js`
diff --git a/doc/source/api_reference/read_GIA_model.rst b/doc/source/api_reference/read_GIA_model.rst
index 31245941..d245141c 100755
--- a/doc/source/api_reference/read_GIA_model.rst
+++ b/doc/source/api_reference/read_GIA_model.rst
@@ -1,6 +1,6 @@
-==============
-read_GIA_model
-==============
+==================
+``read_GIA_model``
+==================
- Reads Glacial Isostatic Adjustment (GIA) files for given modeling group formats
- Outputs spherical harmonics for the GIA rates and the GIA model parameters
diff --git a/doc/source/api_reference/read_GRACE_harmonics.rst b/doc/source/api_reference/read_GRACE_harmonics.rst
index 4be343c8..1d4b026d 100644
--- a/doc/source/api_reference/read_GRACE_harmonics.rst
+++ b/doc/source/api_reference/read_GRACE_harmonics.rst
@@ -1,6 +1,6 @@
-====================
-read_GRACE_harmonics
-====================
+========================
+``read_GRACE_harmonics``
+========================
- Reads GRACE/GRACE-FO files and extracts spherical harmonic data and drift rates (RL04)
- Adds drift rates to clm and slm for Release-4 harmonics
diff --git a/doc/source/api_reference/read_SLR_harmonics.rst b/doc/source/api_reference/read_SLR_harmonics.rst
index d3802bad..d979f094 100644
--- a/doc/source/api_reference/read_SLR_harmonics.rst
+++ b/doc/source/api_reference/read_SLR_harmonics.rst
@@ -1,6 +1,6 @@
-==================
-read_SLR_harmonics
-==================
+======================
+``read_SLR_harmonics``
+======================
- Reads 5\ |times|\ 5 spherical harmonic coefficients with 1 coefficient from degree 6 all calculated from satellite laser ranging (SLR) measurements
- Calculated by the University of Texas Center for Space Research (CSR) and NASA Goddard Space Flight Center (GSFC)
diff --git a/doc/source/api_reference/read_gfc_harmonics.rst b/doc/source/api_reference/read_gfc_harmonics.rst
index 297bbc14..fef77560 100644
--- a/doc/source/api_reference/read_gfc_harmonics.rst
+++ b/doc/source/api_reference/read_gfc_harmonics.rst
@@ -1,6 +1,6 @@
-==================
-read_gfc_harmonics
-==================
+======================
+``read_gfc_harmonics``
+======================
- Reads gfc files and extracts spherical harmonics for Swarm and GRAZ GRACE/GRACE-FO data
- Parses date of GRACE/GRACE-FO data from filename
diff --git a/doc/source/api_reference/read_love_numbers.rst b/doc/source/api_reference/read_love_numbers.rst
index a087a6c9..712bdea0 100644
--- a/doc/source/api_reference/read_love_numbers.rst
+++ b/doc/source/api_reference/read_love_numbers.rst
@@ -1,6 +1,6 @@
-=================
-read_love_numbers
-=================
+=====================
+``read_love_numbers``
+=====================
- Reads sets of load Love/Shida numbers computed using outputs from the Preliminary Reference Earth Model (PREM) or other Earth models
- Linearly interpolates load Love/Shida numbers for missing degrees
diff --git a/doc/source/api_reference/scripts/calc_harmonic_resolution.rst b/doc/source/api_reference/scripts/calc_harmonic_resolution.rst
index 4926eb5d..c23fe1cc 100644
--- a/doc/source/api_reference/scripts/calc_harmonic_resolution.rst
+++ b/doc/source/api_reference/scripts/calc_harmonic_resolution.rst
@@ -1,6 +1,6 @@
-===========================
-calc_harmonic_resolution.py
-===========================
+===============================
+``calc_harmonic_resolution.py``
+===============================
- Calculates the spatial resolution that can be resolved by the spherical harmonics of a certain degree :cite:p:`Barthelmes:2013fy,HofmannWellenhof:2006hy`
- Default method uses the smallest half-wavelength that can be resolved
diff --git a/doc/source/api_reference/scripts/calc_mascon.rst b/doc/source/api_reference/scripts/calc_mascon.rst
index b41ef5b2..67ad6636 100644
--- a/doc/source/api_reference/scripts/calc_mascon.rst
+++ b/doc/source/api_reference/scripts/calc_mascon.rst
@@ -1,6 +1,6 @@
-==============
-calc_mascon.py
-==============
+==================
+``calc_mascon.py``
+==================
- Reads in GRACE/GRACE-FO spherical harmonic coefficients
- Correct spherical harmonics with the specified GIA model group
diff --git a/doc/source/api_reference/scripts/calc_sensitivity_kernel.rst b/doc/source/api_reference/scripts/calc_sensitivity_kernel.rst
index 72276dd7..0abfa608 100644
--- a/doc/source/api_reference/scripts/calc_sensitivity_kernel.rst
+++ b/doc/source/api_reference/scripts/calc_sensitivity_kernel.rst
@@ -1,6 +1,6 @@
-==========================
-calc_sensitivity_kernel.py
-==========================
+==============================
+``calc_sensitivity_kernel.py``
+==============================
- Calculates spatial sensitivity kernels through a least-squares mascon procedure following :cite:t:`Tiwari:2009bx,Jacob:2012gv`
diff --git a/doc/source/api_reference/scripts/combine_harmonics.rst b/doc/source/api_reference/scripts/combine_harmonics.rst
index d7a383e4..d2481c33 100644
--- a/doc/source/api_reference/scripts/combine_harmonics.rst
+++ b/doc/source/api_reference/scripts/combine_harmonics.rst
@@ -1,6 +1,6 @@
-====================
-combine_harmonics.py
-====================
+========================
+``combine_harmonics.py``
+========================
- Converts a file from the spherical harmonic domain into the spatial domain :cite:p:`Wahr:1998hy`
diff --git a/doc/source/api_reference/scripts/convert_harmonics.rst b/doc/source/api_reference/scripts/convert_harmonics.rst
index ca37c135..9e720050 100644
--- a/doc/source/api_reference/scripts/convert_harmonics.rst
+++ b/doc/source/api_reference/scripts/convert_harmonics.rst
@@ -1,6 +1,6 @@
-====================
-convert_harmonics.py
-====================
+========================
+``convert_harmonics.py``
+========================
- Converts a file from the spatial domain into the spherical harmonic domain :cite:p:`Wahr:1998hy`
diff --git a/doc/source/api_reference/scripts/grace_mean_harmonics.rst b/doc/source/api_reference/scripts/grace_mean_harmonics.rst
index 91ab32c3..198d4608 100644
--- a/doc/source/api_reference/scripts/grace_mean_harmonics.rst
+++ b/doc/source/api_reference/scripts/grace_mean_harmonics.rst
@@ -1,6 +1,6 @@
-=======================
-grace_mean_harmonics.py
-=======================
+===========================
+``grace_mean_harmonics.py``
+===========================
- Calculates the temporal mean of the GRACE/GRACE-FO spherical harmonics for a specified date range
- Used to estimate the static gravitational field over a given date rage
diff --git a/doc/source/api_reference/scripts/grace_raster_grids.rst b/doc/source/api_reference/scripts/grace_raster_grids.rst
index 1867fb45..9cacebdc 100644
--- a/doc/source/api_reference/scripts/grace_raster_grids.rst
+++ b/doc/source/api_reference/scripts/grace_raster_grids.rst
@@ -1,6 +1,6 @@
-=====================
-grace_raster_grids.py
-=====================
+=========================
+``grace_raster_grids.py``
+=========================
- Reads in GRACE/GRACE-FO spherical harmonic coefficients and exports projected spatial fields
- Correct spherical harmonics with the specified GIA model group
diff --git a/doc/source/api_reference/scripts/grace_spatial_error.rst b/doc/source/api_reference/scripts/grace_spatial_error.rst
index 07f62f76..ca012ef2 100644
--- a/doc/source/api_reference/scripts/grace_spatial_error.rst
+++ b/doc/source/api_reference/scripts/grace_spatial_error.rst
@@ -1,6 +1,6 @@
-======================
-grace_spatial_error.py
-======================
+==========================
+``grace_spatial_error.py``
+==========================
- Reads in GRACE/GRACE-FO spherical harmonic coefficients and exports spatial error field following :cite:t:`Wahr:2006bx`
- Filters and smooths data with specified processing algorithms :cite:p:`Jekeli:1981vj,Swenson:2006hu`
diff --git a/doc/source/api_reference/scripts/grace_spatial_maps.rst b/doc/source/api_reference/scripts/grace_spatial_maps.rst
index 09dc7b98..e2614a09 100644
--- a/doc/source/api_reference/scripts/grace_spatial_maps.rst
+++ b/doc/source/api_reference/scripts/grace_spatial_maps.rst
@@ -1,6 +1,6 @@
-=====================
-grace_spatial_maps.py
-=====================
+=========================
+``grace_spatial_maps.py``
+=========================
- Reads in GRACE/GRACE-FO spherical harmonic coefficients and exports monthly spatial fields
- Correct spherical harmonics with the specified GIA model group
diff --git a/doc/source/api_reference/scripts/mascon_reconstruct.rst b/doc/source/api_reference/scripts/mascon_reconstruct.rst
index 39f33fc5..d1857f95 100644
--- a/doc/source/api_reference/scripts/mascon_reconstruct.rst
+++ b/doc/source/api_reference/scripts/mascon_reconstruct.rst
@@ -1,6 +1,6 @@
-=====================
-mascon_reconstruct.py
-=====================
+=========================
+``mascon_reconstruct.py``
+=========================
- Calculates the equivalent spherical harmonics from a mascon time series
diff --git a/doc/source/api_reference/scripts/piecewise_grace_maps.rst b/doc/source/api_reference/scripts/piecewise_grace_maps.rst
index 611aef51..2fba64dd 100644
--- a/doc/source/api_reference/scripts/piecewise_grace_maps.rst
+++ b/doc/source/api_reference/scripts/piecewise_grace_maps.rst
@@ -1,6 +1,6 @@
-=======================
-piecewise_grace_maps.py
-=======================
+===========================
+``piecewise_grace_maps.py``
+===========================
- Reads in GRACE/GRACE-FO spatial files and fits a piecewise regression model at each grid point for breakpoint analysis
diff --git a/doc/source/api_reference/scripts/regress_grace_maps.rst b/doc/source/api_reference/scripts/regress_grace_maps.rst
index 309c3480..14ead595 100644
--- a/doc/source/api_reference/scripts/regress_grace_maps.rst
+++ b/doc/source/api_reference/scripts/regress_grace_maps.rst
@@ -1,6 +1,6 @@
-=====================
-regress_grace_maps.py
-=====================
+=========================
+``regress_grace_maps.py``
+=========================
- Reads in GRACE/GRACE-FO spatial files and fits a regression model at each grid point
diff --git a/doc/source/api_reference/scripts/run_sea_level_equation.rst b/doc/source/api_reference/scripts/run_sea_level_equation.rst
index 00b0dd04..2149087a 100644
--- a/doc/source/api_reference/scripts/run_sea_level_equation.rst
+++ b/doc/source/api_reference/scripts/run_sea_level_equation.rst
@@ -1,6 +1,6 @@
-=========================
-run_sea_level_equation.py
-=========================
+=============================
+``run_sea_level_equation.py``
+=============================
- Solves the sea level equation with the option of including polar motion feedback :cite:p:`Farrell:1976hm,Kendall:2005ds,Mitrovica:2003cq`
- Uses a Clenshaw summation to calculate the spherical harmonic summation :cite:p:`Holmes:2002ff,Tscherning:1982tu`
diff --git a/doc/source/api_reference/scripts/scale_grace_maps.rst b/doc/source/api_reference/scripts/scale_grace_maps.rst
index 8f1a19ea..1fb72a7b 100644
--- a/doc/source/api_reference/scripts/scale_grace_maps.rst
+++ b/doc/source/api_reference/scripts/scale_grace_maps.rst
@@ -1,6 +1,6 @@
-===================
-scale_grace_maps.py
-===================
+=======================
+``scale_grace_maps.py``
+=======================
- Reads in GRACE/GRACE-FO spherical harmonic coefficients and exports scaled spatial fields
- Correct spherical harmonics with the specified GIA model group
diff --git a/doc/source/api_reference/sea_level_equation.rst b/doc/source/api_reference/sea_level_equation.rst
index 0be5ac99..449dc782 100644
--- a/doc/source/api_reference/sea_level_equation.rst
+++ b/doc/source/api_reference/sea_level_equation.rst
@@ -1,6 +1,6 @@
-==================
-sea_level_equation
-==================
+======================
+``sea_level_equation``
+======================
- Solves the sea level equation with the option of including polar motion feedback
diff --git a/doc/source/api_reference/spatial.rst b/doc/source/api_reference/spatial.rst
index d4b5c441..9048aeeb 100644
--- a/doc/source/api_reference/spatial.rst
+++ b/doc/source/api_reference/spatial.rst
@@ -1,15 +1,15 @@
-=======
-spatial
-=======
+===========
+``spatial``
+===========
Spatial data class for reading, writing and processing spatial data
- Can read ascii, netCDF4, HDF5 files
- Can read from an index of the above file types
- - Can merge a list of ``spatial`` objects into a single object
+ - Can merge a list of :py:class:`spatial` objects into a single object
- Can subset to a list of GRACE/GRACE-FO months
- - Can calculate the mean field of a ``spatial`` object
- - Can output ``spatial`` objects to ascii, netCDF4 or HDF5 files
+ - Can calculate the mean field of a :py:class:`spatial` object
+ - Can output :py:class:`spatial` objects to ascii, netCDF4 or HDF5 files
Calling Sequence
================
diff --git a/doc/source/api_reference/time.rst b/doc/source/api_reference/time.rst
index 339666e9..26334198 100644
--- a/doc/source/api_reference/time.rst
+++ b/doc/source/api_reference/time.rst
@@ -1,6 +1,6 @@
-====
-time
-====
+========
+``time``
+========
Utilities for calculating time operations
diff --git a/doc/source/api_reference/time_series/amplitude.rst b/doc/source/api_reference/time_series/amplitude.rst
index 062c336b..b9b93518 100644
--- a/doc/source/api_reference/time_series/amplitude.rst
+++ b/doc/source/api_reference/time_series/amplitude.rst
@@ -1,6 +1,6 @@
-=====================
-time_series.amplitude
-=====================
+=========================
+``time_series.amplitude``
+=========================
- Calculate the amplitude and phase of a harmonic function from calculated sine and cosine of a series of measurements
diff --git a/doc/source/api_reference/time_series/fit.rst b/doc/source/api_reference/time_series/fit.rst
index e39c6328..359bb306 100644
--- a/doc/source/api_reference/time_series/fit.rst
+++ b/doc/source/api_reference/time_series/fit.rst
@@ -1,6 +1,6 @@
-===============
-time_series.fit
-===============
+===================
+``time_series.fit``
+===================
- Utilities for fitting time-series data with regression models
diff --git a/doc/source/api_reference/time_series/lomb_scargle.rst b/doc/source/api_reference/time_series/lomb_scargle.rst
index f88f5a39..0f1aafeb 100644
--- a/doc/source/api_reference/time_series/lomb_scargle.rst
+++ b/doc/source/api_reference/time_series/lomb_scargle.rst
@@ -1,6 +1,6 @@
-========================
-time_series.lomb_scargle
-========================
+============================
+``time_series.lomb_scargle``
+============================
- Wrapper function for computing Lomb-Scargle periodograms using ``scipy.signal.lombscargle``
diff --git a/doc/source/api_reference/time_series/piecewise.rst b/doc/source/api_reference/time_series/piecewise.rst
index 58b43416..fc4f5bb9 100644
--- a/doc/source/api_reference/time_series/piecewise.rst
+++ b/doc/source/api_reference/time_series/piecewise.rst
@@ -1,6 +1,6 @@
-=====================
-time_series.piecewise
-=====================
+=========================
+``time_series.piecewise``
+=========================
- Fits a synthetic signal to data over a time period by ordinary or weighted least-squares for breakpoint analysis
diff --git a/doc/source/api_reference/time_series/regress.rst b/doc/source/api_reference/time_series/regress.rst
index 75fc1612..67dee571 100644
--- a/doc/source/api_reference/time_series/regress.rst
+++ b/doc/source/api_reference/time_series/regress.rst
@@ -1,6 +1,6 @@
-===================
-time_series.regress
-===================
+=======================
+``time_series.regress``
+=======================
- Fits a synthetic signal to data over a time period by ordinary or weighted least-squares
diff --git a/doc/source/api_reference/time_series/savitzky_golay.rst b/doc/source/api_reference/time_series/savitzky_golay.rst
index d50df073..92b133b2 100644
--- a/doc/source/api_reference/time_series/savitzky_golay.rst
+++ b/doc/source/api_reference/time_series/savitzky_golay.rst
@@ -1,6 +1,6 @@
-==========================
-time_series.savitzky_golay
-==========================
+==============================
+``time_series.savitzky_golay``
+==============================
- Smooth and optionally differentiate data of non-uniform sampling with a Savitzky-Golay filter
- A type of low-pass filter, particularly suited for smoothing noisy data
diff --git a/doc/source/api_reference/time_series/smooth.rst b/doc/source/api_reference/time_series/smooth.rst
index 093db7ee..7bfbf86c 100644
--- a/doc/source/api_reference/time_series/smooth.rst
+++ b/doc/source/api_reference/time_series/smooth.rst
@@ -1,6 +1,6 @@
-==================
-time_series.smooth
-==================
+======================
+``time_series.smooth``
+======================
- Computes the moving average of a time-series
diff --git a/doc/source/api_reference/tools.rst b/doc/source/api_reference/tools.rst
index d3fdbf56..30100e10 100644
--- a/doc/source/api_reference/tools.rst
+++ b/doc/source/api_reference/tools.rst
@@ -1,6 +1,6 @@
-=====
-tools
-=====
+=========
+``tools``
+=========
`User interface `_ and plotting tools for use in `Jupyter notebooks `_
diff --git a/doc/source/api_reference/units.rst b/doc/source/api_reference/units.rst
index 851d5327..9f445f3a 100644
--- a/doc/source/api_reference/units.rst
+++ b/doc/source/api_reference/units.rst
@@ -1,6 +1,6 @@
-=====
-units
-=====
+=========
+``units``
+=========
Class for converting GRACE/GRACE-FO Level-2 data to specific units
diff --git a/doc/source/api_reference/utilities.rst b/doc/source/api_reference/utilities.rst
index 001ccb0d..3ce419e2 100644
--- a/doc/source/api_reference/utilities.rst
+++ b/doc/source/api_reference/utilities.rst
@@ -1,6 +1,6 @@
-=========
-utilities
-=========
+=============
+``utilities``
+=============
Download and management utilities for syncing time and auxiliary files
@@ -18,6 +18,14 @@ General Methods
.. autofunction:: gravity_toolkit.utilities.get_data_path
+.. autofunction:: gravity_toolkit.utilities.get_cache_path
+
+.. autofunction:: gravity_toolkit.utilities.import_dependency
+
+.. autofunction:: gravity_toolkit.utilities.dependency_available
+
+.. autofunction:: gravity_toolkit.utilities.is_valid_url
+
.. autoclass:: gravity_toolkit.utilities.reify
:members:
diff --git a/doc/source/api_reference/utilities/make_grace_index.rst b/doc/source/api_reference/utilities/make_grace_index.rst
index b0f732e6..ad052001 100644
--- a/doc/source/api_reference/utilities/make_grace_index.rst
+++ b/doc/source/api_reference/utilities/make_grace_index.rst
@@ -1,6 +1,6 @@
-===================
-make_grace_index.py
-===================
+=======================
+``make_grace_index.py``
+=======================
- Creates index files of GRACE/GRACE-FO Level-2 spherical harmonic data files
diff --git a/doc/source/api_reference/utilities/quick_mascon_plot.rst b/doc/source/api_reference/utilities/quick_mascon_plot.rst
index da0911b2..676042a5 100644
--- a/doc/source/api_reference/utilities/quick_mascon_plot.rst
+++ b/doc/source/api_reference/utilities/quick_mascon_plot.rst
@@ -1,6 +1,6 @@
-====================
-quick_mascon_plot.py
-====================
+========================
+``quick_mascon_plot.py``
+========================
- Plots a mascon time series file for a particular format
diff --git a/doc/source/api_reference/utilities/quick_mascon_regress.rst b/doc/source/api_reference/utilities/quick_mascon_regress.rst
index 5b951bcc..837412dd 100644
--- a/doc/source/api_reference/utilities/quick_mascon_regress.rst
+++ b/doc/source/api_reference/utilities/quick_mascon_regress.rst
@@ -1,6 +1,6 @@
-=======================
-quick_mascon_regress.py
-=======================
+===========================
+``quick_mascon_regress.py``
+===========================
- Creates a regression summary file for a mascon time series file
diff --git a/doc/source/api_reference/utilities/run_grace_date.rst b/doc/source/api_reference/utilities/run_grace_date.rst
index 4638677f..2745c635 100644
--- a/doc/source/api_reference/utilities/run_grace_date.rst
+++ b/doc/source/api_reference/utilities/run_grace_date.rst
@@ -1,6 +1,6 @@
-=================
-run_grace_date.py
-=================
+=====================
+``run_grace_date.py``
+=====================
- Wrapper program for running GRACE date and months programs
- Reads GRACE/GRACE-FO index files
diff --git a/doc/source/background/Background.rst b/doc/source/background/Background.rst
index 7a68dab1..29713bd9 100644
--- a/doc/source/background/Background.rst
+++ b/doc/source/background/Background.rst
@@ -1,104 +1,81 @@
+.. _background:
+
==========
Background
==========
-Measurement Principle
-#####################
+Background information on the theoretical concepts used in ``gravity-toolkit``.
+*It is not necessary to read through this information* in order to use this library, but it may be helpful for understanding the outputs and assumptions.
+The information is organized into separate sections, which can be read in any order.
+
+.. grid:: 2 2 4 4
+ :padding: 0
+
+ .. grid-item-card:: Measurement Principle
+ :text-align: center
+ :link: ./Measurement-Principle.html
+
+ :material-outlined:`scale;5em`
+
+ .. grid-item-card:: Spherical Harmonics
+ :text-align: center
+ :link: ./Spherical-Harmonics.html
+
+ :octicon:`globe;5em`
+
+ .. grid-item-card:: Love Numbers
+ :text-align: center
+ :link: ./Love-Numbers.html
+
+ :material-outlined:`favorite_border;5em`
+
+ .. grid-item-card:: GRACE Data Products
+ :text-align: center
+ :link: ./GRACE-Data-Products.html
+
+ :material-outlined:`data_object;5em`
-GRACE and the GRACE Follow-on (GRACE-FO) missions each consist of twin satellites in similar low Earth orbits :cite:p:`Tapley:2019cm`.
-The primary and secondary instrumentation onboard the GRACE/GRACE-FO satellites are the ranging instrument
-(GRACE has a microwave ranging instrument, GRACE-FO has both a microwave ranging instrument and a laser interferometer),
-the global positioning system (GPS), the accelerometers and the star cameras.
-Data from these instruments are combined to estimate the distance between the two satellites,
-the positions of the satellites in space, the pointing vector of the satellites and any non-gravitational
-accelerations the satellites experience.
+.. grid:: 2 2 4 4
+ :padding: 0
-.. admonition:: The Big Idea
+ .. grid-item-card:: Geocenter Variations
+ :text-align: center
+ :link: ./Geocenter-Variations.html
- GRACE/GRACE-FO senses changes in gravity by measuring the change in distance between the two twin satellites:
+ :material-outlined:`swap_vertical_circle;5em`
- 1) As the satellites approach a mass anomaly: leading satellite "feels" a greater gravitational attraction and accelerates |rarr| **distance increases**
- 2) As the trailing satellite approaches: greater gravitational attraction |rarr| accelerated by the mass anomaly |rarr| **distance decreases**
- 3) Leading satellite passes the anomaly: gravitational attraction pulls backwards |rarr| decelerated by the mass anomaly |rarr| **distance decreases**
- 4) When the trailing satellite passes the anomaly and leading satellite is far from the anomaly: trailing satellite decelerated by mass anomaly |rarr| **distance increases back to standard separation**
+ .. grid-item-card:: Spatial Maps
+ :text-align: center
+ :link: ./Spatial-Maps.html
-All the onboard measurements are combined with estimates of the background gravity field, atmospheric and oceanic variability,
-and tides to create the `Level-2 spherical harmonic product of GRACE and GRACE-FO`__.
+ :material-outlined:`travel_explore;5em`
-.. __: https://archive.podaac.earthdata.nasa.gov/podaac-ops-cumulus-docs/gracefo/open/docs/GRACE-FO_L2_UserHandbook.pdf
+ .. grid-item-card:: Time Series Analysis
+ :text-align: center
+ :link: ./Time-Series-Analysis.html
-Data Processing
-###############
+ :material-outlined:`line_axis;5em`
-There are three main processing centers that create the Level-2 spherical harmonic data as part of the GRACE/GRACE-FO Science Data System (SDS):
-the `University of Texas Center for Space Research (CSR) `_,
-the `German Research Centre for Geosciences (GeoForschungsZentrum, GFZ) `_ and
-the `Jet Propulsion Laboratory (JPL) `_.
+ .. grid-item-card:: Glossary
+ :text-align: center
+ :link: ./Glossary.html
-GRACE/GRACE-FO data is freely available in the US from
-the `NASA Physical Oceanography Distributed Active Archive Center (PO.DAAC) `_ and
-internationally from the `GFZ Information System and Data Center (ISDC) `_.
+ :material-outlined:`format_list_bulleted;5em`
.. tip::
- There are programs within this repository that can sync with both of these data archives:
- ``podaac_cumulus.py`` for `PO.DAAC AWS `_ and
- ``gfz_isdc_grace_ftp.py`` for the `GFZ ISDC `_.
-
-Geoid Height
-############
-
-The Level-2 spherical harmonic product of GRACE and GRACE-FO provides monthly
-estimates of the Earth's gravitational field [see :ref:`fig-sphharm`].
-The Earth's gravitational field varies in time as masses on and within the
-Earth move and are exchanged between components of the Earth system :cite:p:`Wahr:1998hy`.
-The instantaneous shape of the Earth's gravitational field can be described
-in terms of an equipotential surface, a surface of constant potential energy
-where the gravitational potential is constant :cite:p:`HofmannWellenhof:2006hy`.
-The Earth's geoid is the equipotential surface that coincides with global mean
-sea level if the oceans were at rest :cite:p:`HofmannWellenhof:2006hy,Wahr:1998hy`.
-The distance between the geoid and an Earth reference ellipsoid is the
-geoid height (:math:`N`), or the geoidal undulation :cite:p:`HofmannWellenhof:2006hy`.
-
-.. figure:: ../_assets/geoid_height.svg
- :width: 400
- :align: center
-
- Relationship between ellipsoid height, geoid height, and topographic height :cite:p:`NRC:1997ea`
-
-In spherical coordinates, the change in the height of the geoid,
-:math:`\Delta N(\theta,\phi)`, at colatitude :math:`\theta` and longitude :math:`\phi`,
-can be estimated from a series of spherical harmonics as:
-
-.. math::
- :label: 1
-
- \Delta N(\theta,\phi) = a\sum_{l=1}^{l_{max}}\sum_{m=0}^lP_{lm}(\cos\theta)\left[\Delta C_{lm}\cos{m\phi} + \Delta S_{lm}\sin{m\phi}\right]
-
-where :math:`a` is the average radius of the Earth,
-:math:`P_{lm}(\cos\theta)` are the fully-normalized Legendre polynomials of degree :math:`l` and order :math:`m` for the cosine of colatitude :math:`\theta`, and
-:math:`\Delta C_{lm}`, :math:`\Delta S_{lm}` are the changes in the cosine and sine spherical harmonics of degree :math:`l` and order :math:`m` :cite:p:`Chao:1987fq`.
-
-Surface Mass Density
-####################
-
-The radial component of a density change within the Earth cannot be uniquely
-determined using satellite gravity observations alone :cite:p:`Wahr:1998hy`.
-However, fluctuations in water storage and transport can be assumed to be largely
-concentrated within a thin layer near the Earth's surface :cite:p:`Wahr:1998hy`.
-With this assumption, the Earth's surface mass density
-(:math:`\Delta\sigma(\theta,\phi)`), the integral of the density change
-(:math:`\Delta\rho(r,\theta,\phi)`) through the thin surface layer,
-can be estimated as the following:
-
-.. math::
- :label: 2
-
- \Delta\sigma(\theta,\phi) = \frac{a\rho_{ave}}{3}\sum_{l=0}^{l_{max}}\sum_{m=0}^l\frac{2l+1}{1+k_l}P_{lm}(\cos\theta)\left[\Delta C_{lm}\cos{m\phi} + \Delta S_{lm}\sin{m\phi}\right]
-
-where :math:`\rho_{ave}` is the average density of the Earth, and
-:math:`k_l` is the gravitational potential load Love number of degree :math:`l`.
-Using this assumption, solid Earth variations occurring outside of this
-thin layer, such as Glacial Isostatic Adjustment (GIA) effects,
-must be independently estimated and removed.
-
-.. |rarr| unicode:: U+2192 .. RIGHTWARDS ARROW
+ For a more in-depth understanding of the underlying concepts: refer to the :ref:`original literature ` and the :ref:`project resources `.
+
+.. toctree::
+ :hidden:
+ :maxdepth: 1
+ :numbered:
+ :caption: Background
+
+ ./Measurement-Principle.rst
+ ./Spherical-Harmonics.rst
+ ./Love-Numbers.rst
+ ./GRACE-Data-Products.rst
+ ./Geocenter-Variations.rst
+ ./Spatial-Maps.rst
+ ./Time-Series-Analysis.rst
+ ./Glossary.rst
diff --git a/doc/source/getting_started/GRACE-Data-File-Formats.rst b/doc/source/background/GRACE-Data-Products.rst
similarity index 82%
rename from doc/source/getting_started/GRACE-Data-File-Formats.rst
rename to doc/source/background/GRACE-Data-Products.rst
index 66bd180d..b0266c5e 100644
--- a/doc/source/getting_started/GRACE-Data-File-Formats.rst
+++ b/doc/source/background/GRACE-Data-Products.rst
@@ -1,6 +1,23 @@
-=================
-Data File Formats
-=================
+=============
+Data Products
+=============
+
+Data Processing
+###############
+
+There are three main processing centers that create the Level-2 spherical harmonic data as part of the GRACE/GRACE-FO Science Data System (SDS):
+the `University of Texas Center for Space Research (CSR) `_,
+the `German Research Centre for Geosciences (GeoForschungsZentrum, GFZ) `_ and
+the `Jet Propulsion Laboratory (JPL) `_.
+
+GRACE/GRACE-FO data is freely available in the US from
+the `NASA Physical Oceanography Distributed Active Archive Center (PO.DAAC) `_ and
+internationally from the `GFZ Information System and Data Center (ISDC) `_.
+
+.. tip::
+ There are programs within this repository that can sync with both of these data archives:
+ :py:mod:`podaac_cumulus.py` for `PO.DAAC AWS `_ and
+ :py:mod:`gfz_isdc_grace_ftp.py` for the `GFZ ISDC `_.
Product Identifier
##################
diff --git a/doc/source/background/Geocenter-Variations.rst b/doc/source/background/Geocenter-Variations.rst
index b6f3d548..f9cd5127 100644
--- a/doc/source/background/Geocenter-Variations.rst
+++ b/doc/source/background/Geocenter-Variations.rst
@@ -2,17 +2,10 @@
Geocenter Variations
====================
-Variations in the Earth's geocenter reflect the largest scale
-variability of mass within the Earth system, and are essential
-inclusions for the complete recovery of surface mass change from
-time-variable gravity.
-The Earth's geocenter is the translation between the Earth's
-center of mass (CM) and center of figure (CF) reference frames.
-Measurements of time-variable gravity from GRACE and GRACE Follow-On
-(GRACE-FO) are set in an instantaneous center of mass (CM) reference frame.
-For most science applications of time-variable gravity, a coordinate
-system with an origin coinciding with the Earth's center of figure
-(CF) is required.
+Variations in the Earth's geocenter reflect the largest scale variability of mass within the Earth system, and are essential inclusions for the complete recovery of surface mass change from time-variable gravity.
+The Earth's geocenter is the translation between the Earth's center of mass (CM) and center of figure (CF) reference frames.
+Measurements of time-variable gravity from GRACE and GRACE Follow-On (GRACE-FO) are set in an instantaneous center of mass (CM) reference frame.
+For most science applications of time-variable gravity, a coordinate system with an origin coinciding with the Earth's center of figure (CF) is required.
Geocenter variations are represented by the degree one spherical harmonic terms.
.. important::
@@ -21,13 +14,8 @@ Geocenter variations are represented by the degree one spherical harmonic terms.
of ocean mass, ice sheet mass change, and terrestrial hydrology due to
far-field signals leaking into each regional estimate :cite:p:`Velicogna:2009ft`.
-``calc_degree_one.py`` calculates coefficients of degree one by combining
-GRACE/GRACE-FO spherical harmonic products with estimates of
-ocean bottom pressure (OBP) following :cite:t:`Swenson:2008cr,Sutterley:2019bx`.
-The method assumes that the change in global surface mass density,
-:math:`\Delta\sigma(\theta,\phi)`, can be separated into individual
-land and ocean components using a land-function
-:math:`\vartheta(\theta,\phi)` :cite:p:`Swenson:2008cr`.
+:py:mod:`calc_degree_one.py` calculates coefficients of degree one by combining GRACE/GRACE-FO spherical harmonic products with estimates of ocean bottom pressure (OBP) following :cite:t:`Swenson:2008cr,Sutterley:2019bx`.
+The method assumes that the change in global surface mass density, :math:`\Delta\sigma(\theta,\phi)`, can be separated into individual land and ocean components using a land-function :math:`\vartheta(\theta,\phi)` :cite:p:`Swenson:2008cr`.
.. math::
:label: 4
@@ -35,30 +23,16 @@ land and ocean components using a land-function
\Delta\sigma(\theta,\phi) &= \Delta\sigma_{land}(\theta,\phi) + \Delta\sigma_{ocean}(\theta,\phi)\\
\Delta\sigma_{ocean}(\theta,\phi) &= \vartheta(\theta,\phi)~\Delta\sigma(\theta,\phi)
-The oceanic components of the change in degree one spherical harmonics
-(:math:`\Delta C^{ocean}_{10}`, :math:`\Delta C^{ocean}_{11}`, and :math:`\Delta S^{ocean}_{11}`)
-can then be calculated from the changes in ocean mass,
-:math:`\Delta\sigma_{ocean}(\theta,\phi)` :cite:p:`Swenson:2008cr,Wahr:1998hy`.
-If the oceanic contributions to degree one variability
-(:math:`\Delta C^{ocean}_{10}`, :math:`\Delta C^{ocean}_{11}`, and :math:`\Delta S^{ocean}_{11}`)
-can be estimated from an ocean model, then the unknown complete degree one terms
-(:math:`\Delta C_{10}`, :math:`\Delta C_{11}`, and :math:`\Delta S_{11}`) can be
-calculated from the residual between the oceanic degree one terms and the
-measured mass change over the ocean calculated using all other degrees of
-the global spherical harmonics from GRACE/GRACE-FO :cite:p:`Swenson:2008cr,Sutterley:2019bx`.
-
-The ``calc_degree_one.py`` program will output geocenter files in ascii format
-for each GRACE/GRACE-FO month following :cite:t:`Sutterley:2019bx`.
-Uncertainties in geocenter due to a combination of error sources can be
-estimated using the ``monte_carlo_degree_one.py`` program.
+The oceanic components of the change in degree one spherical harmonics (:math:`\Delta C^{ocean}_{10}`, :math:`\Delta C^{ocean}_{11}`, and :math:`\Delta S^{ocean}_{11}`) can then be calculated from the changes in ocean mass, :math:`\Delta\sigma_{ocean}(\theta,\phi)` :cite:p:`Swenson:2008cr,Wahr:1998hy`.
+If the oceanic contributions to degree one variability (:math:`\Delta C^{ocean}_{10}`, :math:`\Delta C^{ocean}_{11}`, and :math:`\Delta S^{ocean}_{11}`) can be estimated from an ocean model, then the unknown complete degree one terms (:math:`\Delta C_{10}`, :math:`\Delta C_{11}`, and :math:`\Delta S_{11}`) can be calculated from the residual between the oceanic degree one terms and the measured mass change over the ocean calculated using all other degrees of the global spherical harmonics from GRACE/GRACE-FO :cite:p:`Swenson:2008cr,Sutterley:2019bx`.
+
+The :py:mod:`calc_degree_one.py` program will output geocenter files in ascii format for each GRACE/GRACE-FO month following :cite:t:`Sutterley:2019bx`.
+Uncertainties in geocenter due to a combination of error sources can be estimated using the :py:mod:`monte_carlo_degree_one.py` program.
Load Love Numbers
#################
-The degree one Love number of gravitational potential :math:`k_1` is defined so
-that the degree one terms describe the offset between the center of mass (CM)
-of the combined surface mass and deformed solid Earth, and the center of figure (CF)
-of the deformed solid Earth surface :cite:p:`Trupin:1992kp,Blewitt:2003bz`.
+The degree one Love number of gravitational potential :math:`k_1` is defined so that the degree one terms describe the offset between the center of mass (CM) of the combined surface mass and deformed solid Earth, and the center of figure (CF) of the deformed solid Earth surface :cite:p:`Trupin:1992kp,Blewitt:2003bz`.
For the CF coordinate system, this means
.. math::
@@ -66,14 +40,12 @@ For the CF coordinate system, this means
k_1 = -(h_1 + 2\ell_1)/3
-where :math:`h_1` and :math:`\ell_1` are the degree one vertical and
-horizontal displacement Love numbers.
+where :math:`h_1` and :math:`\ell_1` are the degree one vertical and horizontal displacement Love numbers.
Geocenter and Degree One
########################
-Fully-normalized degree one variations can be converted to
-cartesian geocenter variations using the following relation:
+Fully-normalized degree one variations can be converted to cartesian geocenter variations using the following relation:
.. math::
:label: 6
@@ -83,6 +55,4 @@ cartesian geocenter variations using the following relation:
\Delta Z &= a\sqrt{3}~\Delta C_{10}
-The ``geocenter`` class has utilities for converting between
-spherical harmonics and geocenter variation along with
-readers for different geocenter datasets.
+The :py:class:`geocenter` class has utilities for converting between spherical harmonics and geocenter variation along with readers for different geocenter datasets.
diff --git a/doc/source/background/Glossary.rst b/doc/source/background/Glossary.rst
new file mode 100644
index 00000000..1bb7bd1e
--- /dev/null
+++ b/doc/source/background/Glossary.rst
@@ -0,0 +1,51 @@
+.. _gravity-glossary:
+
+========
+Glossary
+========
+
+.. glossary::
+
+ Body Tide
+ see :term:`Solid Earth Tide`
+
+ Chandler Wobble
+ small, semi-periodic deviations in the motion of the pole of rotation
+
+ Epoch
+ fixed point in time used as a reference value
+
+ Flattening
+ ratio of the difference between the semi-major and semi-minor axes of an ellipsoid to the semi-major axis
+
+ Free Core Nutation
+ nearly diurnal deviations in the motion of the pole of rotation due to the resonant motion of the Earth's core relative to the mantle
+
+ see :term:`Nutation`
+
+ Geopotential
+ the Earth's gravitational potential
+
+ Geoid
+ equipotential surface coinciding with the ocean surface in the absence of astronomical or dynamical effects
+
+ Love and Shida Numbers
+ dimensionless parameters relating the vertical (`h`), horizontal (`l`) and gravitational (`k`) elastic responses to tidal loading
+
+ Nutation
+ short-period oscillations in the motion of the pole of rotation of a freely rotating body
+
+ Polar Motion
+ irregular motion of the Earth's pole of rotation relative to the Earth's crust
+
+ Pole Tide
+ apparent tide due to variations in the Earth's axis of rotation about its mean
+
+ Precession
+ regular conical motion of the pole of rotation of a freely rotating body
+
+ Solid Earth Tide
+ deformation of the solid Earth due to gravitational forces
+
+ Tilt Factor
+ Combination of :term:`Love/Shida numbers ` describing the displacement of the Earth's ocean surface with respect to the Earth's deformed crust
diff --git a/doc/source/background/Love-Numbers.rst b/doc/source/background/Love-Numbers.rst
new file mode 100644
index 00000000..9c1fb014
--- /dev/null
+++ b/doc/source/background/Love-Numbers.rst
@@ -0,0 +1,36 @@
+.. _love-and-shida-numbers:
+
+======================
+Love and Shida Numbers
+======================
+
+When the mass distribution on the surface of the Earth varies, the solid Earth deforms both elastically and inelastically.
+This deformation has three distinct components:
+
+1. a vertical (radial) displacement of the surface
+2. a horizontal (tangential) displacement of the surface
+3. a change in the gravitational potential
+
+The magnitude of each individual component can be characterised by a dimensionless scaling factor :cite:p:`Love:1909eh,Shida:1912dj`.
+These factors are collectively known as :term:`Love/Shida Numbers `, and are defined for each :ref:`spherical harmonic degree `.
+
+.. list-table::
+ :header-rows: 1
+ :align: center
+
+ * - Symbol
+ - Name
+ * - :math:`h_l`
+ - Love number (vertical)
+ * - :math:`k_l`
+ - Love number (potential)
+ * - :math:`l_l`
+ - Shida number (horizontal)
+
+.. _load-love-numbers:
+
+Load Love Numbers
+-----------------
+
+Load Love numbers describe the deformation of the solid Earth in response to a change in *surface mass load*.
+The loading change acts upon the *surface of the Earth* :cite:p:`Wahr:1998hy`.
diff --git a/doc/source/background/Measurement-Principle.rst b/doc/source/background/Measurement-Principle.rst
new file mode 100644
index 00000000..0b6a8925
--- /dev/null
+++ b/doc/source/background/Measurement-Principle.rst
@@ -0,0 +1,26 @@
+Measurement Principle
+#####################
+
+GRACE and the GRACE Follow-on (GRACE-FO) missions each consist of twin satellites in similar low Earth orbits :cite:p:`Tapley:2019cm`.
+The primary and secondary instrumentation onboard the GRACE/GRACE-FO satellites are the ranging instrument
+(GRACE has a microwave ranging instrument, GRACE-FO has both a microwave ranging instrument and a laser interferometer),
+the global positioning system (GPS), the accelerometers and the star cameras.
+Data from these instruments are combined to estimate the distance between the two satellites,
+the positions of the satellites in space, the pointing vector of the satellites and any non-gravitational
+accelerations the satellites experience.
+
+.. admonition:: The Big Idea
+
+ GRACE/GRACE-FO senses changes in gravity by measuring the change in distance between the two twin satellites:
+
+ 1) As the satellites approach a mass anomaly: leading satellite "feels" a greater gravitational attraction and accelerates |rarr| **distance increases**
+ 2) As the trailing satellite approaches: greater gravitational attraction |rarr| accelerated by the mass anomaly |rarr| **distance decreases**
+ 3) Leading satellite passes the anomaly: gravitational attraction pulls backwards |rarr| decelerated by the mass anomaly |rarr| **distance decreases**
+ 4) When the trailing satellite passes the anomaly and leading satellite is far from the anomaly: trailing satellite decelerated by mass anomaly |rarr| **distance increases back to standard separation**
+
+All the onboard measurements are combined with estimates of the background gravity field, atmospheric and oceanic variability,
+and tides to create the `Level-2 spherical harmonic product of GRACE and GRACE-FO`__.
+
+.. __: https://archive.podaac.earthdata.nasa.gov/podaac-ops-cumulus-docs/gracefo/open/docs/GRACE-FO_L2_UserHandbook.pdf
+
+.. |rarr| unicode:: U+2192 .. RIGHTWARDS ARROW
diff --git a/doc/source/background/Spatial-Maps.rst b/doc/source/background/Spatial-Maps.rst
index f6126a5b..ba64ead7 100644
--- a/doc/source/background/Spatial-Maps.rst
+++ b/doc/source/background/Spatial-Maps.rst
@@ -11,7 +11,7 @@ remove unwanted sources of gravitational variability, and convert to appropriate
.. tip::
- The ``grace_spatial_maps.py`` program will output spatial files in ascii, netCDF4 or HDF5 format
+ The :py:mod:`grace_spatial_maps.py` program will output spatial files in ascii, netCDF4 or HDF5 format
for each GRACE/GRACE-FO month.
Load Love Numbers
@@ -29,7 +29,7 @@ removed from the spherical harmonic coefficients :cite:p:`Wahr:1998hy`.
Here, we use load Love and Shida numbers with parameters calculated from
the Preliminary Reference Earth model (PREM) :cite:p:`Farrell:1972cm,Dziewonski:1981bz`.
In order to help estimate the uncertainty in elastic deformation,
-``grace_spatial_maps.py`` can use different sets of load Love numbers by adjusting the
+:py:mod:`grace_spatial_maps.py` can use different sets of load Love numbers by adjusting the
``--love`` command line option.
Reference Frames
@@ -44,13 +44,13 @@ Applications set in a center of figure (CF) reference frame,
such as the recovery of mass variations of the oceans, hydrosphere and cryosphere,
require the inclusion of degree one terms to be fully accurate :cite:p:`Swenson:2008cr`.
-``grace_spatial_maps.py`` has geocenter options to select the degree one product to
+:py:mod:`grace_spatial_maps.py` has geocenter options to select the degree one product to
include with the GRACE/GRACE-FO derived harmonics.
There are options for using measurements from satellite laser ranging :cite:p:`Cheng:2013tz` and
calculations from time-variable gravity and ocean model outputs :cite:p:`Swenson:2008cr,Sutterley:2019bx`.
If including degree one harmonics and changing the reference frame,
the reference frame for the load Love numbers needs to be updated accordingly :cite:p:`Blewitt:2003bz`.
-In ``grace_spatial_maps.py`` and other GRACE/GRACE-FO programs, the reference frame for the load Love numbers
+In :py:mod:`grace_spatial_maps.py` and other GRACE/GRACE-FO programs, the reference frame for the load Love numbers
is adjusted by setting the ``--reference`` command line option to ``'CF'``.
Low-Degree Harmonics
@@ -83,7 +83,7 @@ The figure axis harmonics (:math:`C_{21}` and :math:`S_{21}`) may also be contam
by noise during the single-accelerometer months in the GFZ products :cite:p:`Dahle:2019jf`.
Measurements from satellite laser ranging (SLR) can provide an independent assessment
for some low degree and order spherical harmonics.
-``grace_spatial_maps.py`` has options for replacing
+:py:mod:`grace_spatial_maps.py` has options for replacing
:math:`C_{20}`,
:math:`C_{21}`,
:math:`S_{21}`,
diff --git a/doc/source/background/Spherical-Harmonics.rst b/doc/source/background/Spherical-Harmonics.rst
index ad5d1617..d8a4ea06 100644
--- a/doc/source/background/Spherical-Harmonics.rst
+++ b/doc/source/background/Spherical-Harmonics.rst
@@ -1,9 +1,51 @@
-:orphan:
+.. _spherical-harmonics:
+
+Spherical Harmonics
+===================
+
+Geoid Height
+------------
+
+The Level-2 spherical harmonic product of GRACE and GRACE-FO provides monthly estimates of the Earth's gravitational field [see :ref:`fig-sphharm`].
+The Earth's gravitational field varies in time as masses on and within the Earth move and are exchanged between components of the Earth system :cite:p:`Wahr:1998hy`.
+The instantaneous shape of the Earth's gravitational field can be described in terms of an equipotential surface, a surface of constant potential energy where the gravitational potential is constant :cite:p:`HofmannWellenhof:2006hy`.
+The Earth's geoid is the equipotential surface that coincides with global mean sea level if the oceans were at rest :cite:p:`HofmannWellenhof:2006hy,Wahr:1998hy`.
+The distance between the geoid and an Earth reference ellipsoid is the geoid height (:math:`N`), or the geoidal undulation :cite:p:`HofmannWellenhof:2006hy`.
+
+.. figure:: ../_assets/geoid_height.svg
+ :width: 400
+ :align: center
+
+ Relationship between ellipsoid height, geoid height, and topographic height :cite:p:`NRC:1997ea`
+
+In spherical coordinates, the change in the height of the geoid, :math:`\Delta N(\theta,\phi)`, at colatitude :math:`\theta` and longitude :math:`\phi`, can be estimated from a series of spherical harmonics as:
+
+.. math::
+ :label: 1
+
+ \Delta N(\theta,\phi) = a\sum_{l=1}^{l_{max}}\sum_{m=0}^lP_{lm}(\cos\theta)\left[\Delta C_{lm}\cos{m\phi} + \Delta S_{lm}\sin{m\phi}\right]
+
+where :math:`a` is the average radius of the Earth, :math:`P_{lm}(\cos\theta)` are the fully-normalized Legendre polynomials of degree :math:`l` and order :math:`m` for the cosine of colatitude :math:`\theta`, and :math:`\Delta C_{lm}`, :math:`\Delta S_{lm}` are the changes in the cosine and sine spherical harmonics of degree :math:`l` and order :math:`m` :cite:p:`Chao:1987fq`.
+
+Surface Mass Density
+--------------------
+
+The radial component of a density change within the Earth cannot be uniquely determined using satellite gravity observations alone :cite:p:`Wahr:1998hy`.
+However, fluctuations in water storage and transport can be assumed to be largely concentrated within a thin layer near the Earth's surface :cite:p:`Wahr:1998hy`.
+With this assumption, the Earth's surface mass density (:math:`\Delta\sigma(\theta,\phi)`), the integral of the density change (:math:`\Delta\rho(r,\theta,\phi)`) through the thin surface layer, can be estimated as the following:
+
+.. math::
+ :label: 2
+
+ \Delta\sigma(\theta,\phi) = \frac{a\rho_{ave}}{3}\sum_{l=0}^{l_{max}}\sum_{m=0}^l\frac{2l+1}{1+k_l}P_{lm}(\cos\theta)\left[\Delta C_{lm}\cos{m\phi} + \Delta S_{lm}\sin{m\phi}\right]
+
+where :math:`\rho_{ave}` is the average density of the Earth, and :math:`k_l` is the gravitational potential load Love number of degree :math:`l`.
+Using this assumption, solid Earth variations occurring outside of this thin layer, such as Glacial Isostatic Adjustment (GIA) effects, must be independently estimated and removed.
.. _fig-sphharm:
-Spherical Harmonics
--------------------
+Low-Degree Harmonics
+--------------------
.. plot:: ./background/sphharm.py
:show-source-link: False
diff --git a/doc/source/background/Time-Series-Analysis.rst b/doc/source/background/Time-Series-Analysis.rst
index 86f5234e..d60fa7ac 100644
--- a/doc/source/background/Time-Series-Analysis.rst
+++ b/doc/source/background/Time-Series-Analysis.rst
@@ -58,10 +58,10 @@ Getting the kernels "just right" in order to isolate regions of interest takes s
The set of least-squares mascon programs have been used in :cite:p:`Velicogna:2014km`
and other publications for regional time series analysis.
-The ``calc_mascon.py`` program additionally calculates the GRACE/GRACE-FO error
+The :py:mod:`calc_mascon.py` program additionally calculates the GRACE/GRACE-FO error
harmonics following :cite:t:`Wahr:2006bx`.
-The ``calc_mascon.py`` program will output a text file of the time series for each mascon
+The :py:mod:`calc_mascon.py` program will output a text file of the time series for each mascon
with columns: GRACE/GRACE-FO month, mid-month date in decimal-year format,
estimated monthly mass anomaly [Gt], estimated monthly error [Gt], and
mascon area [km\ :sup:`2`].
diff --git a/doc/source/background/sphharm.py b/doc/source/background/sphharm.py
index c7cca1b6..d340da92 100644
--- a/doc/source/background/sphharm.py
+++ b/doc/source/background/sphharm.py
@@ -6,8 +6,8 @@
# latitude and longitude
dlon, dlat = 0.625, 0.5
-lat = np.arange(-90 + dlat/2.0, 90 + dlat/2.0, dlat)
-lon = np.arange(0 + dlon/2.0, 360 + dlon/2.0, dlon)
+lat = np.arange(-90 + dlat / 2.0, 90 + dlat / 2.0, dlat)
+lon = np.arange(0 + dlon / 2.0, 360 + dlon / 2.0, dlon)
gridlon, gridlat = np.meshgrid(lon, lat)
nlat, nlon = gridlat.shape
# colatitude and longitude in radians
@@ -18,33 +18,39 @@
lmin, lmax = (1, 4)
# number of rows and columns for subplots
nrows = (lmax - lmin) + 1
-ncols = 2*lmax + 1
-# compute associated Legendre functions
+ncols = 2 * lmax + 1
+# compute associated Legendre functions
Plm, dPlm = gravtk.associated_legendre(lmax, np.cos(theta))
# reshape to [l,m,lat,lon]
-Plm = Plm.reshape((lmax+1, lmax+1, nlat, nlon))
+Plm = Plm.reshape((lmax + 1, lmax + 1, nlat, nlon))
# projection for the plots
projection = ccrs.Orthographic(central_longitude=0.0, central_latitude=0.0)
# plot spherical harmonics
-fig = plt.figure(num=1, figsize=(12,7))
-patch = mpatches.Rectangle((0, 0), 0.445, 1, color='0.975',
- zorder=0, transform=fig.transFigure)
+fig = plt.figure(num=1, figsize=(12, 7), facecolor='#fcfcfc')
+patch = mpatches.Rectangle(
+ (0, 0), 0.445, 1, color='0.95', zorder=0, transform=fig.transFigure
+)
fig.add_artist(patch)
-for n, l in enumerate(range(lmin, lmax+1)):
- for m in range(-l, l+1):
+for n, l in enumerate(range(lmin, lmax + 1)):
+ for m in range(-l, l + 1):
# setup subplot
- i = n*ncols + l + (lmax-l) + m + 1
+ i = n * ncols + l + (lmax - l) + m + 1
ax = fig.add_subplot(nrows, ncols, i, projection=projection)
# spherical harmonics of degree l and order m
- Ylms = Plm[l,np.abs(m),:,:]*np.exp(1j*m*phi)
+ Ylms = Plm[l, np.abs(m), :, :] * np.exp(1j * m * phi)
Ylm = Ylms.imag if (m < 0) else Ylms.real
# plot the surface
- ax.pcolormesh(lon, lat, Ylm,
+ ax.pcolormesh(
+ lon,
+ lat,
+ Ylm,
transform=ccrs.PlateCarree(),
- cmap='viridis', rasterized=True)
+ cmap='viridis',
+ rasterized=True,
+ )
# set the title
ax.set_title(f'$l={l}, m={m}$')
# add coastlines and set global
@@ -54,11 +60,25 @@
ax.set_axis_off()
# add labels for cosine and sine terms
-t1 = fig.text(0.05, 0.925, '$S_{lm}$', size=20,
- ha="center", va="center", transform=fig.transFigure)
-t2 = fig.text(0.95, 0.925, '$C_{lm}$', size=20,
- ha="center", va="center", transform=fig.transFigure)
+t1 = fig.text(
+ 0.05,
+ 0.925,
+ '$S_{lm}$',
+ size=20,
+ ha='center',
+ va='center',
+ transform=fig.transFigure,
+)
+t2 = fig.text(
+ 0.95,
+ 0.925,
+ '$C_{lm}$',
+ size=20,
+ ha='center',
+ va='center',
+ transform=fig.transFigure,
+)
# adjust spacing and show
plt.tight_layout()
-plt.show()
\ No newline at end of file
+plt.show()
diff --git a/doc/source/conf.py b/doc/source/conf.py
index de2d6649..5c47d145 100644
--- a/doc/source/conf.py
+++ b/doc/source/conf.py
@@ -11,25 +11,50 @@
# documentation root, use os.path.abspath to make it absolute, like shown here.
#
import os
+
# import sys
+import logging
import datetime
+import warnings
+
# sys.path.insert(0, os.path.abspath('.'))
import importlib.metadata
# -- Project information -----------------------------------------------------
+on_rtd = os.environ.get('READTHEDOCS') == 'True'
+on_github = os.environ.get('GITHUB_ACTIONS') == 'true'
# package metadata
-metadata = importlib.metadata.metadata("gravity-toolkit")
-project = metadata["Name"]
+metadata = importlib.metadata.metadata('gravity-toolkit')
+project = metadata['Name']
year = datetime.date.today().year
-copyright = f"2019\u2013{year}, Tyler C. Sutterley"
+copyright = f'2019\u2013{year}, Tyler C. Sutterley'
author = 'Tyler C. Sutterley'
# The full version, including alpha/beta/rc tags
-version = metadata["version"]
+version = metadata['version']
# append "v" before the version
-release = f"v{version}"
+release = f'v{version}'
+
+
+# filter out numfig warnings when building documentation, see
+# https://github.com/sphinx-doc/sphinx/issues/10316
+# https://github.com/sphinx-doc/sphinx/pull/14446
+class numfig_filter(logging.Filter):
+ def filter(self, record):
+ warning_type = getattr(record, 'type', '')
+ warning_subtype = getattr(record, 'subtype', '')
+ suppress_warning = (
+ f'{warning_type}.{warning_subtype}' == 'html.numfig_format'
+ or record.getMessage().startswith('numfig_format')
+ )
+ return not suppress_warning
+
+
+# suppress warnings in examples and documentation
+if on_rtd:
+ warnings.filterwarnings('ignore')
# -- General configuration ---------------------------------------------------
@@ -37,23 +62,37 @@
# extensions coming with Sphinx (named 'sphinx.ext.*') or your custom
# ones.
extensions = [
- "matplotlib.sphinxext.plot_directive",
- "myst_nb",
- "numpydoc",
+ 'matplotlib.sphinxext.plot_directive',
+ 'myst_nb',
+ 'numpydoc',
'sphinxcontrib.bibtex',
- "sphinx.ext.autodoc",
- "sphinx.ext.graphviz",
- "sphinx.ext.viewcode",
- "sphinx_design",
- "sphinxarg.ext"
+ 'sphinx.ext.autodoc',
+ 'sphinx.ext.graphviz',
+ 'sphinx.ext.viewcode',
+ 'sphinx_design',
+ 'sphinxarg.ext',
]
# use myst for notebooks
source_suffix = {
- ".rst": "restructuredtext",
- ".ipynb": "myst-nb",
+ '.rst': 'restructuredtext',
+ '.ipynb': 'myst-nb',
}
-nb_execution_mode = "off"
+# execute notebooks on build
+if on_rtd:
+ nb_execution_mode = 'auto'
+ nb_execution_excludepatterns = [
+ 'notebooks/*.ipynb',
+ ]
+ nb_output_stderr = 'remove-warn'
+elif on_github:
+ nb_execution_mode = 'off'
+else:
+ nb_execution_mode = 'auto'
+ nb_execution_excludepatterns = [
+ 'notebooks/*.ipynb',
+ ]
+ nb_output_stderr = 'remove-warn'
# Add any paths that contain templates here, relative to this directory.
templates_path = ['_templates']
@@ -80,38 +119,62 @@
# -- Options for HTML output -------------------------------------------------
# html_title = "gravity-toolkit"
-html_short_title = "gravity-toolkit"
+html_short_title = 'gravity-toolkit'
html_show_sourcelink = False
html_show_sphinx = True
html_show_copyright = True
+numfig_format = {
+ 'code-block': None,
+ 'figure': 'Figure %s:',
+ 'table': 'Table %s:',
+}
+
# The theme to use for HTML and HTML Help pages. See the documentation for
# a list of builtin themes.
#
html_theme = 'sphinx_rtd_theme'
html_theme_options = {
- "logo_only": True,
+ 'logo_only': True,
}
# Add any paths that contain custom static files (such as style sheets) here,
# relative to this directory. They are copied after the builtin static files,
# so a file named "default.css" will overwrite the builtin "default.css".
-html_logo = "_assets/logo.png"
+html_logo = '_assets/gravity_logo.png'
html_static_path = ['_static']
-repository_url = f"https://github.com/tsutterley/gravity-toolkit"
+# fetch the project urls
+project_urls = {}
+for project_url in metadata.get_all('Project-URL'):
+ name, _, url = project_url.partition(', ')
+ project_urls[name.lower()] = url
+# fetch the repository url
+github_url = project_urls.get('repository')
+*_, github_user, github_repo = github_url.split('/')
+# add html context
html_context = {
- "menu_links": [
+ 'display_github': True,
+ 'github_user': github_user,
+ 'github_repo': github_repo,
+ 'github_version': 'main',
+ 'conf_py_path': '/doc/source/',
+ 'menu_links': [
(
' Source Code',
- repository_url,
+ github_url,
),
(
' License',
- f"{repository_url}/blob/main/LICENSE",
+ f'{github_url}/blob/main/LICENSE',
+ ),
+ (
+ ' Discussions',
+ f'{github_url}/discussions',
),
],
}
+
# Load the custom CSS files (needs sphinx >= 1.6 for this to work)
def setup(app):
- app.add_css_file("style.css")
+ app.add_css_file('style.css')
diff --git a/doc/source/getting_started/Install.ipynb b/doc/source/getting_started/Install.ipynb
new file mode 100644
index 00000000..ff800b0b
--- /dev/null
+++ b/doc/source/getting_started/Install.ipynb
@@ -0,0 +1,181 @@
+{
+ "cells": [
+ {
+ "cell_type": "markdown",
+ "id": "2e01086c",
+ "metadata": {},
+ "source": [
+ "# Setup and Installation\n",
+ "\n",
+ "## Installation\n",
+ "\n",
+ "`gravity-toolkit` is available for download from the [GitHub repository](https://github.com/tsutterley/gravity-toolkit), the [Python Package Index (pypi)](https://pypi.org/project/gravity-toolkit/), and from [conda-forge](https://anaconda.org/conda-forge/gravity-toolkit).\n",
+ "\n",
+ "\n",
+ "The simplest installation for most users will likely be using `conda` or `mamba`:\n",
+ "\n",
+ "```bash\n",
+ "conda install -c conda-forge gravity-toolkit\n",
+ "```\n",
+ "\n",
+ "`conda` installed versions of `gravity-toolkit` can be upgraded to the latest stable release:\n",
+ "\n",
+ "```bash\n",
+ "conda update gravity-toolkit\n",
+ "```"
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "id": "af65ece3",
+ "metadata": {},
+ "source": [
+ "(development-install)=\n",
+ "\n",
+ "## Development Install\n",
+ "\n",
+ "To use the development repository, please fork `gravity-toolkit` into your own account and then clone onto your system:\n",
+ "\n",
+ "```bash\n",
+ "git clone https://github.com/tsutterley/gravity-toolkit.git\n",
+ "```\n",
+ "\n",
+ "`gravity-toolkit` can then be installed within the package directory using `pip`:\n",
+ "\n",
+ "```bash\n",
+ "python3 -m pip install --user .\n",
+ "```\n",
+ "\n",
+ "To include all optional dependencies:\n",
+ "\n",
+ "```bash\n",
+ "python3 -m pip install --user .[all]\n",
+ "```\n",
+ "\n",
+ "The development version of `gravity-toolkit` can also be installed directly from GitHub using `pip`:\n",
+ "\n",
+ "```bash\n",
+ "python3 -m pip install --user git+https://github.com/tsutterley/gravity-toolkit.git\n",
+ "```"
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "id": "14ae498c",
+ "metadata": {},
+ "source": [
+ "## Package Management with ``pixi``\n",
+ "\n",
+ "Alternatively `pixi` can be used to create a [streamlined environment](https://pixi.sh/) after cloning the repository:\n",
+ "\n",
+ "```bash\n",
+ "pixi install\n",
+ "```\n",
+ "\n",
+ "`pixi` maintains isolated environments for each project, allowing for different versions of `gravity-toolkit` and its dependencies to be used without conflict.\n",
+ "The `pixi.lock` file within the repository defines the required packages and versions for the environment.\n",
+ "\n",
+ "`pixi` can also create shells for running programs within the environment:\n",
+ "\n",
+ "```bash\n",
+ "pixi shell\n",
+ "```\n",
+ "\n",
+ "To see the available tasks within the `gravity-toolkit` workspace:\n",
+ "\n",
+ "```bash\n",
+ "pixi task list\n",
+ "```"
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": null,
+ "id": "15f4d37e",
+ "metadata": {
+ "tags": [
+ "remove-input"
+ ]
+ },
+ "outputs": [],
+ "source": [
+ "! pixi task list"
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "id": "158990b9",
+ "metadata": {},
+ "source": [
+ "```{note}\n",
+ "`pixi` is under active development and may change in future releases\n",
+ "```\n"
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "id": "286e9898",
+ "metadata": {},
+ "source": [
+ "## Verifying Installation"
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": null,
+ "id": "dbeee9a1",
+ "metadata": {},
+ "outputs": [],
+ "source": [
+ "import gravity_toolkit as gravtk\n",
+ "\n",
+ "print(f'gravity-toolkit version: {gravtk.__version__}')"
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "id": "1c110ff4",
+ "metadata": {},
+ "source": [
+ "## Configuration\n",
+ "\n",
+ "The `gravity-toolkit` cache directory is the default location for downloaded GRACE/GRACE-FO and auxiliary data.\n",
+ "The default cache directory is platform-specific and set by the `platformdirs` package.\n",
+ "This parametrized path can be _persistently_ overridden on a machine by setting the `GRAVTK_CACHE_DIR` environment variable.\n",
+ "\n",
+ "The location of the cache directory used by `gravity-toolkit` can be checked by:"
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": null,
+ "id": "ad800ca5",
+ "metadata": {},
+ "outputs": [],
+ "source": [
+ "gravtk.utilities.get_cache_path()"
+ ]
+ }
+ ],
+ "metadata": {
+ "kernelspec": {
+ "display_name": "py13",
+ "language": "python",
+ "name": "python3"
+ },
+ "language_info": {
+ "codemirror_mode": {
+ "name": "ipython",
+ "version": 3
+ },
+ "file_extension": ".py",
+ "mimetype": "text/x-python",
+ "name": "python",
+ "nbconvert_exporter": "python",
+ "pygments_lexer": "ipython3",
+ "version": "3.13.0"
+ }
+ },
+ "nbformat": 4,
+ "nbformat_minor": 5
+}
diff --git a/doc/source/getting_started/Install.rst b/doc/source/getting_started/Install.rst
deleted file mode 100644
index 2390efb8..00000000
--- a/doc/source/getting_started/Install.rst
+++ /dev/null
@@ -1,76 +0,0 @@
-============
-Installation
-============
-
-``gravity-toolkit`` is available for download from the `GitHub repository `_,
-the `Python Package Index (pypi) `_,
-and from `conda-forge `_.
-
-
-The simplest installation for most users will likely be using ``conda`` or ``mamba``:
-
-.. code-block:: bash
-
- conda install -c conda-forge gravity-toolkit
-
-``conda`` installed versions of ``gravity-toolkit`` can be upgraded to the latest stable release:
-
-.. code-block:: bash
-
- conda update gravity-toolkit
-
-Development Install
-###################
-
-To use the development repository, please fork ``gravity-toolkit`` into your own account and then clone onto your system:
-
-.. code-block:: bash
-
- git clone https://github.com/tsutterley/gravity-toolkit.git
-
-``gravity-toolkit`` can then be installed within the package directory using ``pip``:
-
-.. code-block:: bash
-
- python3 -m pip install --user .
-
-To include all optional dependencies:
-
-.. code-block:: bash
-
- python3 -m pip install --user .[all]
-
-The development version of ``gravity-toolkit`` can also be installed directly from GitHub using ``pip``:
-
-.. code-block:: bash
-
- python3 -m pip install --user git+https://github.com/tsutterley/gravity-toolkit.git
-
-Package Management with ``pixi``
-################################
-
-Alternatively ``pixi`` can be used to create a `streamlined environment `_ after cloning the repository:
-
-.. code-block:: bash
-
- pixi install
-
-``pixi`` maintains isolated environments for each project, allowing for different versions of
-``gravity-toolkit`` and its dependencies to be used without conflict. The ``pixi.lock`` file within the
-repository defines the required packages and versions for the environment.
-
-``pixi`` can also create shells for running programs within the environment:
-
-.. code-block:: bash
-
- pixi shell
-
-To see the available tasks within the ``gravity-toolkit`` workspace:
-
-.. code-block:: bash
-
- pixi task list
-
-.. note::
-
- ``pixi`` is under active development and may change in future releases
diff --git a/doc/source/getting_started/NASA-Earthdata.rst b/doc/source/getting_started/NASA-Earthdata.rst
deleted file mode 100644
index 441ec213..00000000
--- a/doc/source/getting_started/NASA-Earthdata.rst
+++ /dev/null
@@ -1,74 +0,0 @@
-==============
-NASA Earthdata
-==============
-
-NASA Data Distribution Centers
-##############################
-
-The NASA Earth Science Data Information Systems Project funds and operates
-`12 Distributed Active Archive Centers (DAACs) `_ throughout the United States.
-These centers have recently transitioned from ftp to https servers.
-The https updates are designed to increase performance and improve security during data retrieval.
-NASA Earthdata uses `OAuth2 `_, an approach to authentication that protects your personal information.
-
-- https://urs.earthdata.nasa.gov/documentation
-- https://wiki.earthdata.nasa.gov/display/EL/Knowledge+Base
-
-PO.DAAC
-#######
-The `Physical Oceanography Distributed Active Archive Center (PO.DAAC) `_
-provides data and related information pertaining to the physical processes and conditions of the global oceans,
-including measurements of ocean winds, temperature, topography, salinity, circulation and currents, and sea ice.
-PO.DAAC hosts
-
-PO.DAAC has `migrated its data archive to the Earthdata Cloud `_,
-which is hosted in Amazon Web Services (AWS).
-
-.. tip::
-
- If any problems contact JPL PO.DAAC support at `podaac@podaac.jpl.nasa.gov `_
- or the NASA EOSDIS support team `support@earthdata.nasa.gov `_.
-
-Steps to Sync from PO.DAAC
---------------------------
-
-1. `Register with NASA Earthdata Login system `_
-2. `Sync time-variable gravity data using your Earthdata credentials `_
-
-Can also create a ``.netrc`` file for permanently storing NASA Earthdata credentials:
-
-.. code-block:: bash
-
- echo "machine urs.earthdata.nasa.gov login password " >> ~/.netrc
- chmod 0600 ~/.netrc
-
-Or set environmental variables for your NASA Earthdata credentials:
-
-.. code-block:: bash
-
- export EARTHDATA_USERNAME=
- export EARTHDATA_PASSWORD=
-
-NASA Common Metadata Repository
-###############################
-
-The NASA Common Metadata Repository (CMR) is a catalog of all data
-and service metadata records contained as part of NASA's Earth
-Observing System Data and Information System (EOSDIS).
-Querying the CMR system is a way of quickly performing a search
-through the NASA Earthdata archive.
-Basic queries for the granule names, PO.DAAC URLs and modification times
-of GRACE/GRACE-FO data are available through the ``cmr`` routine in the
-``utilities`` module.
-For AWS instances in ``us-west-2``, CMR queries can access urls for S3 endpoints.
-
-.. code-block:: python
-
- ids,urls,mtimes = gravity_toolkit.utilities.cmr(mission='grace-fo',
- center='JPL', release='RL06', level='L2', product='GSM',
- solution='BA01', provider='POCLOUD', endpoint='s3', verbose=False)
-
-Other Data Access Examples
-##########################
-- `Curl and Wget `_
-- `Python `_
diff --git a/doc/source/getting_started/Resources.rst b/doc/source/getting_started/Resources.rst
index ff632027..6d1c64de 100644
--- a/doc/source/getting_started/Resources.rst
+++ b/doc/source/getting_started/Resources.rst
@@ -1,3 +1,5 @@
+.. _resources:
+
=========
Resources
=========
@@ -31,6 +33,7 @@ Product Information
Processing Standards
####################
+
- `GRACE-FO CSR Level-2 Processing Standards Document `_
- `GRACE-FO GFZ Level-2 Processing Standards Document `_
- `GRACE-FO JPL Level-2 Processing Standards Document `_
@@ -44,4 +47,4 @@ Software
- `frommle: python/C++ software suite for geodesy and Earth Sciences `_
- `geoid-toolkit: python utilities for calculating geoid heights from static gravity field coefficients `_
- `model-harmonics: python tools for working with model synthetic spherical harmonic coefficients `_
-- `dynamic_mascons: 3D global variable-size mascon development `_
\ No newline at end of file
+- `dynamic_mascons: 3D global variable-size mascon development `_
diff --git a/doc/source/index.rst b/doc/source/index.rst
index 60440b96..526b3c3d 100644
--- a/doc/source/index.rst
+++ b/doc/source/index.rst
@@ -9,9 +9,9 @@ coefficients from the NASA/DLR Gravity Recovery and Climate Experiment (GRACE) a
the NASA/GFZ Gravity Recovery and Climate Experiment Follow-On (GRACE-FO) missions.
Introduction
-------------
+============
-.. grid:: 2 2 4 4
+.. grid:: 2 2 2 2
:padding: 0
.. grid-item-card:: Installation
@@ -26,12 +26,30 @@ Introduction
:material-outlined:`hiking;5em`
+User Guide
+==========
+
+.. grid:: 2 2 4 4
+ :padding: 0
+
+ .. grid-item-card:: API Reference
+ :text-align: center
+ :link: ./api_reference/API-Reference.html
+
+ :material-outlined:`list_alt;5em`
+
.. grid-item-card:: Background
:text-align: center
:link: ./background/Background.html
:material-outlined:`library_books;5em`
+ .. grid-item-card:: NASA Earthdata
+ :text-align: center
+ :link: ./user_guide/NASA-Earthdata.html
+
+ :material-outlined:`satellite_alt;5em`
+
.. grid-item-card:: Examples
:text-align: center
:link: ./user_guide/Examples.html
@@ -39,7 +57,7 @@ Introduction
:material-outlined:`apps;5em`
Contribute
-----------
+==========
.. grid:: 2 2 4 4
:padding: 0
@@ -62,6 +80,36 @@ Contribute
:material-outlined:`forum;5em`
+ .. grid-item-card:: Issues
+ :text-align: center
+ :link: https://github.com/tsutterley/gravity-toolkit/issues
+
+ :material-outlined:`bug_report;5em`
+
+Project Details
+===============
+
+.. grid:: 2 2 4 4
+ :padding: 0
+
+ .. grid-item-card:: Release Notes
+ :text-align: center
+ :link: ./release_notes/Release-Notes.html
+
+ :material-outlined:`edit_note;5em`
+
+ .. grid-item-card:: Contributors
+ :text-align: center
+ :link: ./project/Contributors.html
+
+ :material-outlined:`diversity_1;5em`
+
+ .. grid-item-card:: License
+ :text-align: center
+ :link: ./project/Licenses.html
+
+ :material-outlined:`balance;5em`
+
.. grid-item-card:: Citation Information
:text-align: center
:link: ./project/Citations.html
@@ -73,168 +121,22 @@ Contribute
:hidden:
:caption: Getting Started
- getting_started/Install.rst
+ getting_started/Install.ipynb
getting_started/Getting-Started.rst
- getting_started/NASA-Earthdata.rst
- getting_started/GRACE-Data-File-Formats.rst
getting_started/Contributing.rst
getting_started/Code-of-Conduct.rst
getting_started/Resources.rst
-.. toctree::
- :maxdepth: 2
- :hidden:
- :caption: Background
-
- background/Background.rst
- background/Spatial-Maps.rst
- background/Time-Series-Analysis.rst
- background/Geocenter-Variations.rst
-
.. toctree::
:maxdepth: 1
:hidden:
:caption: User Guide
+ api_reference/API-Reference.rst
+ background/Background.rst
+ user_guide/NASA-Earthdata.ipynb
user_guide/Examples.rst
-.. toctree::
- :maxdepth: 1
- :hidden:
- :caption: API Reference
-
- api_reference/associated_legendre.rst
- api_reference/clenshaw_summation.rst
- api_reference/degree_amplitude.rst
- api_reference/destripe_harmonics.rst
- api_reference/fourier_legendre.rst
- api_reference/gauss_weights.rst
- api_reference/gen_averaging_kernel.rst
- api_reference/gen_disc_load.rst
- api_reference/gen_harmonics.rst
- api_reference/gen_point_load.rst
- api_reference/gen_spherical_cap.rst
- api_reference/gen_stokes.rst
- api_reference/geocenter.rst
- api_reference/grace_date.rst
- api_reference/grace_find_months.rst
- api_reference/grace_input_months.rst
- api_reference/grace_months_index.rst
- api_reference/harmonic_gradients.rst
- api_reference/harmonic_summation.rst
- api_reference/harmonics.rst
- api_reference/legendre.rst
- api_reference/legendre_polynomials.rst
- api_reference/mascons.rst
- api_reference/ocean_stokes.rst
- api_reference/read_gfc_harmonics.rst
- api_reference/read_GIA_model.rst
- api_reference/read_GRACE_harmonics.rst
- api_reference/read_love_numbers.rst
- api_reference/read_SLR_harmonics.rst
- api_reference/sea_level_equation.rst
- api_reference/SLR/C20.rst
- api_reference/SLR/CS2.rst
- api_reference/SLR/C30.rst
- api_reference/SLR/C40.rst
- api_reference/SLR/C50.rst
- api_reference/spatial.rst
- api_reference/time.rst
- api_reference/time_series/amplitude.rst
- api_reference/time_series/fit.rst
- api_reference/time_series/lomb_scargle.rst
- api_reference/time_series/piecewise.rst
- api_reference/time_series/regress.rst
- api_reference/time_series/savitzky_golay.rst
- api_reference/time_series/smooth.rst
- api_reference/tools.rst
- api_reference/units.rst
- api_reference/utilities.rst
-
-.. toctree::
- :maxdepth: 1
- :hidden:
- :caption: Access
-
- api_reference/access/cnes_grace_sync.rst
- api_reference/access/esa_costg_swarm_sync.rst
- api_reference/access/gfz_icgem_costg_ftp.rst
- api_reference/access/gfz_isdc_dealiasing_sync.rst
- api_reference/access/gfz_isdc_grace_sync.rst
- api_reference/access/itsg_graz_grace_sync.rst
- api_reference/access/podaac_cumulus.rst
-
-.. toctree::
- :maxdepth: 1
- :hidden:
- :caption: Dealiasing
-
- api_reference/dealiasing/aod1b_geocenter.rst
- api_reference/dealiasing/aod1b_oblateness.rst
- api_reference/dealiasing/dealiasing_global_uplift.rst
- api_reference/dealiasing/dealiasing_monthly_mean.rst
-
-.. toctree::
- :maxdepth: 1
- :hidden:
- :caption: Geocenter
-
- api_reference/geocenter/calc_degree_one.rst
- api_reference/geocenter/monte_carlo_degree_one.rst
-
-.. toctree::
- :maxdepth: 1
- :hidden:
- :caption: Use Cases
-
- api_reference/scripts/calc_mascon.rst
- api_reference/scripts/calc_harmonic_resolution.rst
- api_reference/scripts/calc_sensitivity_kernel.rst
- api_reference/scripts/combine_harmonics.rst
- api_reference/scripts/convert_harmonics.rst
- api_reference/scripts/grace_mean_harmonics.rst
- api_reference/scripts/grace_raster_grids.rst
- api_reference/scripts/grace_spatial_error.rst
- api_reference/scripts/grace_spatial_maps.rst
- api_reference/scripts/mascon_reconstruct.rst
- api_reference/scripts/piecewise_grace_maps.rst
- api_reference/scripts/regress_grace_maps.rst
- api_reference/scripts/run_sea_level_equation.rst
- api_reference/scripts/scale_grace_maps.rst
-
-.. toctree::
- :maxdepth: 1
- :hidden:
- :caption: Mapping
-
- api_reference/mapping/plot_AIS_grid_maps.rst
- api_reference/mapping/plot_AIS_grid_3maps.rst
- api_reference/mapping/plot_AIS_grid_4maps.rst
- api_reference/mapping/plot_AIS_grid_movie.rst
- api_reference/mapping/plot_AIS_GrIS_maps.rst
- api_reference/mapping/plot_AIS_regional_maps.rst
- api_reference/mapping/plot_AIS_regional_movie.rst
- api_reference/mapping/plot_global_grid_maps.rst
- api_reference/mapping/plot_global_grid_3maps.rst
- api_reference/mapping/plot_global_grid_4maps.rst
- api_reference/mapping/plot_global_grid_5maps.rst
- api_reference/mapping/plot_global_grid_9maps.rst
- api_reference/mapping/plot_global_grid_movie.rst
- api_reference/mapping/plot_GrIS_grid_maps.rst
- api_reference/mapping/plot_GrIS_grid_3maps.rst
- api_reference/mapping/plot_GrIS_grid_5maps.rst
- api_reference/mapping/plot_GrIS_grid_movie.rst
-
-.. toctree::
- :maxdepth: 1
- :hidden:
- :caption: Utilities
-
- api_reference/utilities/make_grace_index.rst
- api_reference/utilities/quick_mascon_plot.rst
- api_reference/utilities/quick_mascon_regress.rst
- api_reference/utilities/run_grace_date.rst
-
.. toctree::
:maxdepth: 1
:hidden:
@@ -245,6 +147,13 @@ Contribute
project/Testing.rst
project/Citations.rst
+.. toctree::
+ :maxdepth: 1
+ :hidden:
+ :caption: Release Notes
+
+ release_notes/Release-Notes.rst
+
.. toctree::
:maxdepth: 1
:hidden:
diff --git a/doc/source/notebooks/GRACE-Geostrophic-Maps.ipynb b/doc/source/notebooks/GRACE-Geostrophic-Maps.ipynb
index a147a48d..0221b80e 100644
--- a/doc/source/notebooks/GRACE-Geostrophic-Maps.ipynb
+++ b/doc/source/notebooks/GRACE-Geostrophic-Maps.ipynb
@@ -26,9 +26,10 @@
"source": [
"import numpy as np\n",
"import matplotlib\n",
+ "\n",
"matplotlib.rcParams['mathtext.default'] = 'regular'\n",
- "matplotlib.rcParams[\"animation.html\"] = \"jshtml\"\n",
- "matplotlib.rcParams[\"animation.embed_limit\"] = 50\n",
+ "matplotlib.rcParams['animation.html'] = 'jshtml'\n",
+ "matplotlib.rcParams['animation.embed_limit'] = 50\n",
"import matplotlib.pyplot as plt\n",
"import matplotlib.animation as animation\n",
"import matplotlib.offsetbox as offsetbox\n",
@@ -59,11 +60,7 @@
"# set the directory with GRACE/GRACE-FO data\n",
"# update local data with PO.DAAC https servers\n",
"widgets = gravtk.tools.widgets()\n",
- "ipywidgets.VBox([\n",
- " widgets.directory,\n",
- " widgets.update,\n",
- " widgets.endpoint\n",
- "])"
+ "ipywidgets.VBox([widgets.directory, widgets.update, widgets.endpoint])"
]
},
{
@@ -121,12 +118,9 @@
"# update widgets\n",
"widgets.select_product()\n",
"# display widgets for setting GRACE/GRACE-FO parameters\n",
- "ipywidgets.VBox([\n",
- " widgets.center,\n",
- " widgets.release,\n",
- " widgets.product,\n",
- " widgets.months\n",
- "])"
+ "ipywidgets.VBox(\n",
+ " [widgets.center, widgets.release, widgets.product, widgets.months]\n",
+ ")"
]
},
{
@@ -156,19 +150,21 @@
"# update widgets\n",
"widgets.select_options()\n",
"# display widgets for setting GRACE/GRACE-FO read parameters\n",
- "ipywidgets.VBox([\n",
- " widgets.lmax,\n",
- " widgets.mmax,\n",
- " widgets.geocenter,\n",
- " widgets.C20,\n",
- " widgets.CS21,\n",
- " widgets.CS22,\n",
- " widgets.C30,\n",
- " widgets.C40,\n",
- " widgets.C50,\n",
- " widgets.pole_tide,\n",
- " widgets.atm\n",
- "])"
+ "ipywidgets.VBox(\n",
+ " [\n",
+ " widgets.lmax,\n",
+ " widgets.mmax,\n",
+ " widgets.geocenter,\n",
+ " widgets.C20,\n",
+ " widgets.CS21,\n",
+ " widgets.CS22,\n",
+ " widgets.C30,\n",
+ " widgets.C40,\n",
+ " widgets.C50,\n",
+ " widgets.pole_tide,\n",
+ " widgets.atm,\n",
+ " ]\n",
+ ")"
]
},
{
@@ -206,11 +202,27 @@
"# read GRACE/GRACE-FO data for parameters\n",
"start_mon = np.min(months)\n",
"end_mon = np.max(months)\n",
- "missing = sorted(set(np.arange(start_mon,end_mon+1)) - set(months))\n",
- "Ylms = gravtk.grace_input_months(widgets.base_directory, PROC, DREL, DSET,\n",
- " LMAX, start_mon, end_mon, missing, SLR_C20, DEG1, MMAX=MMAX,\n",
- " SLR_21=SLR_21, SLR_22=SLR_22, SLR_C30=SLR_C30, SLR_C40=SLR_C40,\n",
- " SLR_C50=SLR_C50, POLE_TIDE=POLE_TIDE, ATM=ATM)\n",
+ "missing = sorted(set(np.arange(start_mon, end_mon + 1)) - set(months))\n",
+ "Ylms = gravtk.grace_input_months(\n",
+ " widgets.base_directory,\n",
+ " PROC,\n",
+ " DREL,\n",
+ " DSET,\n",
+ " LMAX,\n",
+ " start_mon,\n",
+ " end_mon,\n",
+ " missing,\n",
+ " SLR_C20,\n",
+ " DEG1,\n",
+ " MMAX=MMAX,\n",
+ " SLR_21=SLR_21,\n",
+ " SLR_22=SLR_22,\n",
+ " SLR_C30=SLR_C30,\n",
+ " SLR_C40=SLR_C40,\n",
+ " SLR_C50=SLR_C50,\n",
+ " POLE_TIDE=POLE_TIDE,\n",
+ " ATM=ATM,\n",
+ ")\n",
"# create harmonics object and remove mean\n",
"GRACE_Ylms = gravtk.harmonics().from_dict(Ylms)\n",
"GRACE_Ylms.mean(apply=True)\n",
@@ -310,15 +322,19 @@
"widgets.select_corrections()\n",
"widgets.select_output()\n",
"# display widgets for setting GRACE/GRACE-FO corrections parameters\n",
- "ipywidgets.VBox([\n",
- " widgets.GIA_file,\n",
- " widgets.GIA,\n",
- " widgets.remove_file,\n",
- " widgets.remove_format,\n",
- " widgets.redistribute_removed,\n",
- " widgets.mask,\n",
- " widgets.gaussian,\n",
- " widgets.destripe])"
+ "widgets.gaussian.value = 600.0\n",
+ "ipywidgets.VBox(\n",
+ " [\n",
+ " widgets.GIA_file,\n",
+ " widgets.GIA,\n",
+ " widgets.remove_file,\n",
+ " widgets.remove_format,\n",
+ " widgets.redistribute_removed,\n",
+ " widgets.mask,\n",
+ " widgets.gaussian,\n",
+ " widgets.destripe,\n",
+ " ]\n",
+ ")"
]
},
{
@@ -339,7 +355,7 @@
},
{
"cell_type": "code",
- "execution_count": 9,
+ "execution_count": null,
"metadata": {},
"outputs": [],
"source": [
@@ -350,43 +366,52 @@
"\n",
"# Read Smoothed Ocean and Land Functions\n",
"# will mask out land regions in the final current maps\n",
- "LANDMASK = gravtk.utilities.get_data_path(['data','land_fcn_300km.nc'])\n",
- "landsea = gravtk.spatial().from_netCDF4(LANDMASK,\n",
- " date=False, varname='LSMASK')\n",
+ "LANDMASK = gravtk.utilities.get_data_path(['data', 'land_fcn_300km.nc'])\n",
+ "landsea = gravtk.spatial().from_netCDF4(LANDMASK, date=False, varname='LSMASK')\n",
"# degree spacing and grid dimensions\n",
"# will create GRACE spatial fields with same dimensions\n",
- "dlon,dlat = landsea.spacing\n",
+ "dlon, dlat = landsea.spacing\n",
"nlat, nlon = landsea.shape\n",
"# shift landsea mask to have longitudes -180:180\n",
- "landsea.mask, landsea.lon = gravtk.tools.shift_grid(180.0 + dlon,\n",
- " landsea.mask, landsea.lon, CYCLIC=360)\n",
+ "landsea.mask, landsea.lon = gravtk.tools.shift_grid(\n",
+ " 180.0 + dlon, landsea.mask, landsea.lon, CYCLIC=360\n",
+ ")\n",
"# grid latitude and longitude\n",
"grid.lon = np.copy(landsea.lon)\n",
"grid.lat = np.copy(landsea.lat)\n",
+ "# mask equatorial regions due to hydrostrophic inaccuracies\n",
+ "(valid,) = np.nonzero((np.abs(grid.lat) > 10))\n",
"\n",
"# Computing plms for converting to spatial domain\n",
- "theta = (90.0 - grid.lat)*np.pi/180.0\n",
- "PLM, dPLM = gravtk.plm_holmes(LMAX, np.cos(theta))\n",
+ "theta = np.radians(90.0 - grid.lat)\n",
+ "PLM, dPLM = gravtk.plm_holmes(LMAX, np.cos(theta[valid]))\n",
"RAD = widgets.gaussian.value\n",
"\n",
"# read load love numbers file\n",
"# PREM outputs from Han and Wahr (1995)\n",
"# https://doi.org/10.1111/j.1365-246X.1995.tb01819.x\n",
- "love_numbers_file = gravtk.utilities.get_data_path(['data','love_numbers'])\n",
+ "love_numbers_file = gravtk.utilities.get_data_path(['data', 'love_numbers'])\n",
"header = 2\n",
- "columns = ['l','hl','kl','ll']\n",
+ "columns = ['l', 'hl', 'kl', 'll']\n",
"# LMAX of load love numbers from Han and Wahr (1995) is 696.\n",
"# from Wahr (2007) linearly interpolating kl works\n",
"# however, as we are linearly extrapolating out, do not make\n",
"# LMAX too much larger than 696\n",
"# read arrays of kl, hl, and ll Love Numbers\n",
- "LOVE = gravtk.read_love_numbers(love_numbers_file, LMAX=LMAX,\n",
- " HEADER=header, COLUMNS=columns, REFERENCE='CF', FORMAT='class')\n",
+ "LOVE = gravtk.read_love_numbers(\n",
+ " love_numbers_file,\n",
+ " LMAX=LMAX,\n",
+ " HEADER=header,\n",
+ " COLUMNS=columns,\n",
+ " REFERENCE='CF',\n",
+ " FORMAT='class',\n",
+ ")\n",
"\n",
"# read GIA data\n",
"GIA = widgets.GIA.value\n",
- "GIA_Ylms_rate = gravtk.gia(lmax=LMAX).from_GIA(widgets.GIA_model,\n",
- " GIA=GIA, mmax=MMAX)\n",
+ "GIA_Ylms_rate = gravtk.gia(lmax=LMAX).from_GIA(\n",
+ " widgets.GIA_model, GIA=GIA, mmax=MMAX\n",
+ ")\n",
"gia_str = '' if (GIA == '[None]') else f'_{GIA_Ylms_rate.title}'\n",
"# calculate the monthly mass change from GIA\n",
"# monthly GIA calculated by gia_rate*time elapsed\n",
@@ -397,8 +422,9 @@
"# if redistributing removed mass over the ocean\n",
"if widgets.redistribute_removed.value:\n",
" # read Land-Sea Mask and convert to spherical harmonics\n",
- " ocean_Ylms = gravtk.ocean_stokes(widgets.landmask, LMAX,\n",
- " MMAX=MMAX, LOVE=LOVE)\n",
+ " ocean_Ylms = gravtk.ocean_stokes(\n",
+ " widgets.landmask, LMAX, MMAX=MMAX, LOVE=LOVE\n",
+ " )\n",
"\n",
"# read data to be removed from GRACE/GRACE-FO monthly harmonics\n",
"remove_Ylms = GRACE_Ylms.zeros_like()\n",
@@ -407,50 +433,48 @@
"# If there are files to be removed from the GRACE/GRACE-FO data\n",
"# for each file separated by commas\n",
"for f in widgets.remove_files:\n",
- " if (widgets.remove_format.value == 'netCDF4'):\n",
+ " if widgets.remove_format.value == 'netCDF4':\n",
" # read netCDF4 file\n",
" Ylms = gravtk.harmonics().from_netCDF4(f)\n",
- " elif (widgets.remove_format.value == 'HDF5'):\n",
+ " elif widgets.remove_format.value == 'HDF5':\n",
" # read HDF5 file\n",
" Ylms = gravtk.harmonics().from_HDF5(f)\n",
- " elif (widgets.remove_format.value == 'index (ascii)'):\n",
+ " elif widgets.remove_format.value == 'index (ascii)':\n",
" # read index of ascii files\n",
- " Ylms = gravtk.harmonics().from_index(f,format='ascii')\n",
- " elif (widgets.remove_format.value == 'index (netCDF4)'):\n",
+ " Ylms = gravtk.harmonics().from_index(f, format='ascii')\n",
+ " elif widgets.remove_format.value == 'index (netCDF4)':\n",
" # read index of netCDF4 files\n",
- " Ylms = gravtk.harmonics().from_index(f,format='netCDF4')\n",
- " elif (widgets.remove_format.value == 'index (HDF5)'):\n",
+ " Ylms = gravtk.harmonics().from_index(f, format='netCDF4')\n",
+ " elif widgets.remove_format.value == 'index (HDF5)':\n",
" # read index of HDF5 files\n",
- " Ylms = gravtk.harmonics().from_index(f,format='HDF5')\n",
+ " Ylms = gravtk.harmonics().from_index(f, format='HDF5')\n",
" # reduce to months of interest and truncate to range\n",
- " Ylms = Ylms.subset(months).truncate(LMAX,mmax=MMAX)\n",
+ " Ylms = Ylms.subset(months).truncate(LMAX, mmax=MMAX)\n",
" # redistribute removed mass over the ocean\n",
" if widgets.redistribute_removed.value:\n",
" # calculate ratio between total removed mass and\n",
" # a uniformly distributed cm of water over the ocean\n",
- " ratio = Ylms.clm[0,0,:]/ocean_Ylms.clm[0,0]\n",
+ " ratio = Ylms.clm[0, 0, :] / ocean_Ylms.clm[0, 0]\n",
" # for each spherical harmonic\n",
- " for m in range(0,MMAX+1):\n",
- " for l in range(m,LMAX+1):\n",
+ " for m in range(0, MMAX + 1):\n",
+ " for l in range(m, LMAX + 1):\n",
" # remove the ratio*ocean Ylms from Ylms\n",
- " Ylms.clm[l,m,:]-=ratio*ocean_Ylms.clm[l,m]\n",
- " Ylms.slm[l,m,:]-=ratio*ocean_Ylms.slm[l,m]\n",
+ " Ylms.clm[l, m, :] -= ratio * ocean_Ylms.clm[l, m]\n",
+ " Ylms.slm[l, m, :] -= ratio * ocean_Ylms.slm[l, m]\n",
" # add the harmonics to be removed to the total\n",
" remove_Ylms.add(Ylms)\n",
"\n",
"# converting harmonics to truncated, smoothed coefficients in units\n",
"# combining harmonics to calculate output spatial fields\n",
"# output geostrophic current grid\n",
- "grid.data = np.zeros((nlat, nlon, 2,nt))\n",
- "grid.mask = np.ones((nlat, nlon, 2,nt), dtype=bool)\n",
- "# mask equatorial regions due to hydrostrophic inaccuracies\n",
- "valid, = np.nonzero((np.abs(grid.lat) > 10))\n",
- "grid.mask[valid,:,:,:] = False\n",
+ "grid.data = np.zeros((nlat, nlon, 2, nt))\n",
+ "grid.mask = np.ones((nlat, nlon, 2, nt), dtype=bool)\n",
+ "grid.mask[valid, :, :, :] = False\n",
"# set land values from land-sea mask to invalid\n",
- "indy,indx = np.nonzero(np.logical_not(landsea.mask))\n",
- "grid.mask[indy,indx,:,:] = True\n",
+ "indy, indx = np.nonzero(np.logical_not(landsea.mask))\n",
+ "grid.mask[indy, indx, :, :] = True\n",
"# for each GRACE/GRACE-FO month\n",
- "for i,grace_month in enumerate(GRACE_Ylms.month):\n",
+ "for i, grace_month in enumerate(GRACE_Ylms.month):\n",
" # GRACE/GRACE-FO harmonics for time t\n",
" # and monthly files to be removed\n",
" if widgets.destripe.value:\n",
@@ -462,11 +486,19 @@
" # Remove GIA rate for time\n",
" Ylms.subtract(GIA_Ylms.index(i))\n",
" # convert spherical harmonics to output spatial grid\n",
- " currents = gravtk.geostrophic_currents(Ylms.clm, Ylms.slm,\n",
- " grid.lon, grid.lat[valid], LMAX=LMAX, MMAX=MMAX,\n",
- " RAD=RAD, LOVE=LOVE, PLM=PLM)\n",
+ " currents = gravtk.geostrophic_currents(\n",
+ " Ylms.clm,\n",
+ " Ylms.slm,\n",
+ " grid.lon,\n",
+ " grid.lat[valid],\n",
+ " LMAX=LMAX,\n",
+ " MMAX=MMAX,\n",
+ " RAD=RAD,\n",
+ " LOVE=LOVE,\n",
+ " PLM=PLM,\n",
+ " )\n",
" # transpose to outputs to latxlon\n",
- " grid.data[valid,:,:,i] = currents.transpose(1,0,2)\n",
+ " grid.data[valid, :, :, i] = currents.transpose(1, 0, 2)\n",
"# update the mask and replace fill values\n",
"grid.update_mask();"
]
@@ -490,7 +522,7 @@
"vmax = np.ceil(np.nanmax(grid.data)).astype(np.int64)\n",
"cmap1 = gravtk.tools.colormap(vmin=vmin, vmax=vmax)\n",
"# display widgets for setting GRACE/GRACE-FO regression plot parameters\n",
- "ipywidgets.VBox([cmap1.range,cmap1.step,cmap1.name,cmap1.reverse])"
+ "ipywidgets.VBox([cmap1.range, cmap1.step, cmap1.name, cmap1.reverse])"
]
},
{
@@ -500,50 +532,94 @@
"outputs": [],
"source": [
"%matplotlib inline\n",
- "fig, (ax1,ax2) = plt.subplots(num=1, nrows=2, ncols=1, figsize=(10.375,11.625),\n",
- " sharex=True, sharey=True, subplot_kw=dict(projection=ccrs.PlateCarree()))\n",
+ "fig, (ax1, ax2) = plt.subplots(\n",
+ " num=1,\n",
+ " nrows=2,\n",
+ " ncols=1,\n",
+ " figsize=(10.375, 11.625),\n",
+ " sharex=True,\n",
+ " sharey=True,\n",
+ " subplot_kw=dict(projection=ccrs.PlateCarree()),\n",
+ ")\n",
"\n",
"# levels and normalization for plot range\n",
- "im1 = ax1.imshow(np.zeros((nlat, nlon)), interpolation='nearest',\n",
- " norm=cmap1.norm, cmap=cmap1.value, transform=ccrs.PlateCarree(),\n",
- " extent=grid.extent, origin='upper', animated=True)\n",
- "im2 = ax2.imshow(np.zeros((nlat, nlon)), interpolation='nearest',\n",
- " norm=cmap1.norm, cmap=cmap1.value, transform=ccrs.PlateCarree(),\n",
- " extent=grid.extent, origin='upper', animated=True)\n",
+ "im1 = ax1.imshow(\n",
+ " np.zeros((nlat, nlon)),\n",
+ " interpolation='nearest',\n",
+ " norm=cmap1.norm,\n",
+ " cmap=cmap1.value,\n",
+ " transform=ccrs.PlateCarree(),\n",
+ " extent=grid.extent,\n",
+ " origin='upper',\n",
+ " animated=True,\n",
+ ")\n",
+ "im2 = ax2.imshow(\n",
+ " np.zeros((nlat, nlon)),\n",
+ " interpolation='nearest',\n",
+ " norm=cmap1.norm,\n",
+ " cmap=cmap1.value,\n",
+ " transform=ccrs.PlateCarree(),\n",
+ " extent=grid.extent,\n",
+ " origin='upper',\n",
+ " animated=True,\n",
+ ")\n",
"\n",
"# add date label (year-calendar month e.g. 2002-01)\n",
- "time_text = ax1.text(0.025, 0.015, '', transform=fig.transFigure,\n",
- " color='k', size=24, weight='bold', ha='left', va='baseline')\n",
+ "time_text = ax1.text(\n",
+ " 0.025,\n",
+ " 0.015,\n",
+ " '',\n",
+ " transform=fig.transFigure,\n",
+ " color='k',\n",
+ " size=24,\n",
+ " weight='bold',\n",
+ " ha='left',\n",
+ " va='baseline',\n",
+ ")\n",
"\n",
"# Add colorbar\n",
"# Add an axes at position rect [left, bottom, width, height]\n",
"cbar_ax = fig.add_axes([0.095, 0.075, 0.81, 0.03])\n",
"# extend = add extension triangles to upper and lower bounds\n",
"# options: neither, both, min, max\n",
- "cbar = fig.colorbar(im1, cax=cbar_ax, extend='both',\n",
- " extendfrac=0.0375, drawedges=False, orientation='horizontal')\n",
+ "cbar = fig.colorbar(\n",
+ " im1,\n",
+ " cax=cbar_ax,\n",
+ " extend='both',\n",
+ " extendfrac=0.0375,\n",
+ " drawedges=False,\n",
+ " orientation='horizontal',\n",
+ ")\n",
"# rasterized colorbar to remove lines\n",
"cbar.solids.set_rasterized(True)\n",
"# Add label to the colorbar\n",
- "cbar.ax.set_title('Geostrophic Current', fontsize=18, rotation=0, y=-1.65, va='top')\n",
+ "cbar.ax.set_title(\n",
+ " 'Geostrophic Current', fontsize=18, rotation=0, y=-1.65, va='top'\n",
+ ")\n",
"cbar.ax.set_xlabel('cm/s', fontsize=18, rotation=0, va='center')\n",
"cbar.ax.xaxis.set_label_coords(1.085, 0.5)\n",
"# Set the tick levels for the colorbar\n",
"cbar.set_ticks(cmap1.levels)\n",
"cbar.set_ticklabels(cmap1.label)\n",
"# ticks lines all the way across\n",
- "cbar.ax.tick_params(which='both', width=1, length=25, labelsize=18,\n",
- " direction='in')\n",
+ "cbar.ax.tick_params(\n",
+ " which='both', width=1, length=25, labelsize=18, direction='in'\n",
+ ")\n",
"\n",
"# add labels, coastlines and adjust frames\n",
"labels = ['Zonal', 'Meridional']\n",
"for i, ax in enumerate([ax1, ax2]):\n",
" # add current label\n",
- " at = offsetbox.AnchoredText(labels[i],\n",
- " loc=3, pad=0, borderpad=0.25, frameon=True,\n",
- " prop=dict(size=24, weight='bold', color='k'))\n",
- " at.patch.set_boxstyle(\"Square,pad=0.2\")\n",
- " at.patch.set_edgecolor(\"white\")\n",
+ " at = offsetbox.AnchoredText(\n",
+ " labels[i],\n",
+ " loc=3,\n",
+ " pad=0,\n",
+ " borderpad=0.25,\n",
+ " frameon=True,\n",
+ " prop=dict(size=24, weight='bold', color='k'),\n",
+ " )\n",
+ " at.patch.set_boxstyle('Square,pad=0.2')\n",
+ " at.patch.set_edgecolor('white')\n",
" ax.axes.add_artist(at)\n",
" # add coastlines\n",
" ax.coastlines('50m')\n",
@@ -551,20 +627,23 @@
" ax.spines['geo'].set_linewidth(2.0)\n",
" ax.spines['geo'].set_zorder(10)\n",
" ax.spines['geo'].set_capstyle('projecting')\n",
- " \n",
+ "\n",
"# adjust subplot within figure\n",
"fig.patch.set_facecolor('white')\n",
- "fig.subplots_adjust(left=0.01, right=0.99, bottom=0.12, top=0.97,\n",
- " hspace=0.05, wspace=0.05)\n",
- " \n",
+ "fig.subplots_adjust(\n",
+ " left=0.01, right=0.99, bottom=0.12, top=0.97, hspace=0.05, wspace=0.05\n",
+ ")\n",
+ "\n",
+ "\n",
"# animate frames\n",
"def animate_frames(i):\n",
" # set image\n",
- " im1.set_data(grid.data[:,:,0,i])\n",
- " im2.set_data(grid.data[:,:,1,i])\n",
+ " im1.set_data(grid.data[:, :, 0, i])\n",
+ " im2.set_data(grid.data[:, :, 1, i])\n",
" # add date label (year-calendar month e.g. 2002-01)\n",
- " year,month = gravtk.time.grace_to_calendar(grid.month[i])\n",
- " time_text.set_text(u'{0:4d}\\u2013{1:02d}'.format(year,month))\n",
+ " year, month = gravtk.time.grace_to_calendar(grid.month[i])\n",
+ " time_text.set_text('{0:4d}\\u2013{1:02d}'.format(year, month))\n",
+ "\n",
"\n",
"# set animation\n",
"anim = animation.FuncAnimation(fig, animate_frames, frames=nt)\n",
@@ -575,7 +654,7 @@
],
"metadata": {
"kernelspec": {
- "display_name": "Python 3.8.10 64-bit",
+ "display_name": "py13",
"language": "python",
"name": "python3"
},
@@ -589,12 +668,7 @@
"name": "python",
"nbconvert_exporter": "python",
"pygments_lexer": "ipython3",
- "version": "3.10.6"
- },
- "vscode": {
- "interpreter": {
- "hash": "31f2aee4e71d21fbe5cf8b01ff0e069b9275f58929596ceb00d14d90e3e16cd6"
- }
+ "version": "3.13.0"
}
},
"nbformat": 4,
diff --git a/doc/source/notebooks/GRACE-Harmonic-Plots.ipynb b/doc/source/notebooks/GRACE-Harmonic-Plots.ipynb
index 8b3bbba6..6f83b711 100644
--- a/doc/source/notebooks/GRACE-Harmonic-Plots.ipynb
+++ b/doc/source/notebooks/GRACE-Harmonic-Plots.ipynb
@@ -26,9 +26,10 @@
"source": [
"import numpy as np\n",
"import matplotlib\n",
+ "\n",
"matplotlib.rcParams['mathtext.default'] = 'regular'\n",
- "matplotlib.rcParams[\"animation.html\"] = \"jshtml\"\n",
- "matplotlib.rcParams[\"animation.embed_limit\"] = 50\n",
+ "matplotlib.rcParams['animation.html'] = 'jshtml'\n",
+ "matplotlib.rcParams['animation.embed_limit'] = 50\n",
"import matplotlib.pyplot as plt\n",
"import matplotlib.animation as animation\n",
"import ipywidgets\n",
@@ -57,11 +58,7 @@
"# set the directory with GRACE/GRACE-FO data\n",
"# update local data with PO.DAAC https servers\n",
"widgets = gravtk.tools.widgets()\n",
- "ipywidgets.VBox([\n",
- " widgets.directory,\n",
- " widgets.update,\n",
- " widgets.endpoint\n",
- "])"
+ "ipywidgets.VBox([widgets.directory, widgets.update, widgets.endpoint])"
]
},
{
@@ -122,12 +119,9 @@
"# update widgets\n",
"widgets.select_product()\n",
"# display widgets for setting GRACE/GRACE-FO parameters\n",
- "ipywidgets.VBox([\n",
- " widgets.center,\n",
- " widgets.release,\n",
- " widgets.product,\n",
- " widgets.months\n",
- "])"
+ "ipywidgets.VBox(\n",
+ " [widgets.center, widgets.release, widgets.product, widgets.months]\n",
+ ")"
]
},
{
@@ -157,19 +151,21 @@
"# update widgets\n",
"widgets.select_options()\n",
"# display widgets for setting GRACE/GRACE-FO read parameters\n",
- "ipywidgets.VBox([\n",
- " widgets.lmax,\n",
- " widgets.mmax,\n",
- " widgets.geocenter,\n",
- " widgets.C20,\n",
- " widgets.CS21,\n",
- " widgets.CS22,\n",
- " widgets.C30,\n",
- " widgets.C40,\n",
- " widgets.C50,\n",
- " widgets.pole_tide,\n",
- " widgets.atm\n",
- "])"
+ "ipywidgets.VBox(\n",
+ " [\n",
+ " widgets.lmax,\n",
+ " widgets.mmax,\n",
+ " widgets.geocenter,\n",
+ " widgets.C20,\n",
+ " widgets.CS21,\n",
+ " widgets.CS22,\n",
+ " widgets.C30,\n",
+ " widgets.C40,\n",
+ " widgets.C50,\n",
+ " widgets.pole_tide,\n",
+ " widgets.atm,\n",
+ " ]\n",
+ ")"
]
},
{
@@ -207,11 +203,27 @@
"# read GRACE/GRACE-FO data for parameters\n",
"start_mon = np.min(months)\n",
"end_mon = np.max(months)\n",
- "missing = sorted(set(np.arange(start_mon,end_mon+1)) - set(months))\n",
- "Ylms = gravtk.grace_input_months(widgets.base_directory, PROC, DREL, DSET,\n",
- " LMAX, start_mon, end_mon, missing, SLR_C20, DEG1, MMAX=MMAX,\n",
- " SLR_21=SLR_21, SLR_22=SLR_22, SLR_C30=SLR_C30, SLR_C40=SLR_C40,\n",
- " SLR_C50=SLR_C50, POLE_TIDE=POLE_TIDE, ATM=ATM)\n",
+ "missing = sorted(set(np.arange(start_mon, end_mon + 1)) - set(months))\n",
+ "Ylms = gravtk.grace_input_months(\n",
+ " widgets.base_directory,\n",
+ " PROC,\n",
+ " DREL,\n",
+ " DSET,\n",
+ " LMAX,\n",
+ " start_mon,\n",
+ " end_mon,\n",
+ " missing,\n",
+ " SLR_C20,\n",
+ " DEG1,\n",
+ " MMAX=MMAX,\n",
+ " SLR_21=SLR_21,\n",
+ " SLR_22=SLR_22,\n",
+ " SLR_C30=SLR_C30,\n",
+ " SLR_C40=SLR_C40,\n",
+ " SLR_C50=SLR_C50,\n",
+ " POLE_TIDE=POLE_TIDE,\n",
+ " ATM=ATM,\n",
+ ")\n",
"# create harmonics object and remove mean\n",
"GRACE_Ylms = gravtk.harmonics().from_dict(Ylms)\n",
"GRACE_Ylms.mean(apply=True)\n",
@@ -247,16 +259,19 @@
"widgets.select_corrections(units=['cmwe', 'mmGH'])\n",
"widgets.select_output()\n",
"# display widgets for setting GRACE/GRACE-FO corrections parameters\n",
- "ipywidgets.VBox([\n",
- " widgets.GIA_file,\n",
- " widgets.GIA,\n",
- " widgets.remove_file,\n",
- " widgets.remove_format,\n",
- " widgets.redistribute_removed,\n",
- " widgets.mask,\n",
- " widgets.gaussian,\n",
- " widgets.destripe,\n",
- " widgets.units])"
+ "ipywidgets.VBox(\n",
+ " [\n",
+ " widgets.GIA_file,\n",
+ " widgets.GIA,\n",
+ " widgets.remove_file,\n",
+ " widgets.remove_format,\n",
+ " widgets.redistribute_removed,\n",
+ " widgets.mask,\n",
+ " widgets.gaussian,\n",
+ " widgets.destripe,\n",
+ " widgets.units,\n",
+ " ]\n",
+ ")"
]
},
{
@@ -281,22 +296,28 @@
"# read load love numbers file\n",
"# PREM outputs from Han and Wahr (1995)\n",
"# https://doi.org/10.1111/j.1365-246X.1995.tb01819.x\n",
- "love_numbers_file = gravtk.utilities.get_data_path(['data','love_numbers'])\n",
+ "love_numbers_file = gravtk.utilities.get_data_path(['data', 'love_numbers'])\n",
"header = 2\n",
- "columns = ['l','hl','kl','ll']\n",
+ "columns = ['l', 'hl', 'kl', 'll']\n",
"# LMAX of load love numbers from Han and Wahr (1995) is 696.\n",
"# from Wahr (2007) linearly interpolating kl works\n",
"# however, as we are linearly extrapolating out, do not make\n",
"# LMAX too much larger than 696\n",
"# read arrays of kl, hl, and ll Love Numbers\n",
- "hl,kl,ll = gravtk.read_love_numbers(love_numbers_file,\n",
- " LMAX=LMAX, HEADER=header, COLUMNS=columns,\n",
- " REFERENCE='CF', FORMAT='tuple')\n",
+ "hl, kl, ll = gravtk.read_love_numbers(\n",
+ " love_numbers_file,\n",
+ " LMAX=LMAX,\n",
+ " HEADER=header,\n",
+ " COLUMNS=columns,\n",
+ " REFERENCE='CF',\n",
+ " FORMAT='tuple',\n",
+ ")\n",
"\n",
"# read GIA data\n",
"GIA = widgets.GIA.value\n",
- "GIA_Ylms_rate = gravtk.gia(lmax=LMAX).from_GIA(widgets.GIA_model,\n",
- " GIA=GIA, mmax=MMAX)\n",
+ "GIA_Ylms_rate = gravtk.gia(lmax=LMAX).from_GIA(\n",
+ " widgets.GIA_model, GIA=GIA, mmax=MMAX\n",
+ ")\n",
"gia_str = '' if (GIA == '[None]') else f'_{GIA_Ylms_rate.title}'\n",
"# calculate the monthly mass change from GIA\n",
"# monthly GIA calculated by gia_rate*time elapsed\n",
@@ -307,9 +328,10 @@
"# if redistributing removed mass over the ocean\n",
"if widgets.redistribute_removed.value:\n",
" # read Land-Sea Mask and convert to spherical harmonics\n",
- " ocean_Ylms = gravtk.ocean_stokes(widgets.landmask, LMAX,\n",
- " MMAX=MMAX, LOVE=(hl,kl,ll))\n",
- " \n",
+ " ocean_Ylms = gravtk.ocean_stokes(\n",
+ " widgets.landmask, LMAX, MMAX=MMAX, LOVE=(hl, kl, ll)\n",
+ " )\n",
+ "\n",
"# read data to be removed from GRACE/GRACE-FO monthly harmonics\n",
"remove_Ylms = GRACE_Ylms.zeros_like()\n",
"remove_Ylms.time[:] = np.copy(GRACE_Ylms.time)\n",
@@ -317,45 +339,45 @@
"# If there are files to be removed from the GRACE/GRACE-FO data\n",
"# for each file separated by commas\n",
"for f in widgets.remove_files:\n",
- " if (widgets.remove_format.value == 'netCDF4'):\n",
+ " if widgets.remove_format.value == 'netCDF4':\n",
" # read netCDF4 file\n",
" Ylms = gravtk.harmonics().from_netCDF4(f)\n",
- " elif (widgets.remove_format.value == 'HDF5'):\n",
+ " elif widgets.remove_format.value == 'HDF5':\n",
" # read HDF5 file\n",
" Ylms = gravtk.harmonics().from_HDF5(f)\n",
- " elif (widgets.remove_format.value == 'index (ascii)'):\n",
+ " elif widgets.remove_format.value == 'index (ascii)':\n",
" # read index of ascii files\n",
- " Ylms = gravtk.harmonics().from_index(f,format='ascii')\n",
- " elif (widgets.remove_format.value == 'index (netCDF4)'):\n",
+ " Ylms = gravtk.harmonics().from_index(f, format='ascii')\n",
+ " elif widgets.remove_format.value == 'index (netCDF4)':\n",
" # read index of netCDF4 files\n",
- " Ylms = gravtk.harmonics().from_index(f,format='netCDF4')\n",
- " elif (widgets.remove_format.value == 'index (HDF5)'):\n",
+ " Ylms = gravtk.harmonics().from_index(f, format='netCDF4')\n",
+ " elif widgets.remove_format.value == 'index (HDF5)':\n",
" # read index of HDF5 files\n",
- " Ylms = gravtk.harmonics().from_index(f,format='HDF5')\n",
+ " Ylms = gravtk.harmonics().from_index(f, format='HDF5')\n",
" # reduce to months of interest and truncate to range\n",
- " Ylms = Ylms.subset(months).truncate(LMAX,mmax=MMAX)\n",
+ " Ylms = Ylms.subset(months).truncate(LMAX, mmax=MMAX)\n",
" # redistribute removed mass over the ocean\n",
" if widgets.redistribute_removed.value:\n",
" # calculate ratio between total removed mass and\n",
" # a uniformly distributed cm of water over the ocean\n",
- " ratio = Ylms.clm[0,0,:]/ocean_Ylms.clm[0,0]\n",
+ " ratio = Ylms.clm[0, 0, :] / ocean_Ylms.clm[0, 0]\n",
" # for each spherical harmonic\n",
- " for m in range(0,MMAX+1):\n",
- " for l in range(m,LMAX+1):\n",
+ " for m in range(0, MMAX + 1):\n",
+ " for l in range(m, LMAX + 1):\n",
" # remove the ratio*ocean Ylms from Ylms\n",
- " Ylms.clm[l,m,:]-=ratio*ocean_Ylms.clm[l,m]\n",
- " Ylms.slm[l,m,:]-=ratio*ocean_Ylms.slm[l,m]\n",
+ " Ylms.clm[l, m, :] -= ratio * ocean_Ylms.clm[l, m]\n",
+ " Ylms.slm[l, m, :] -= ratio * ocean_Ylms.slm[l, m]\n",
" # add the harmonics to be removed to the total\n",
" remove_Ylms.add(Ylms)\n",
"\n",
"# gaussian smoothing radius in km (Jekeli, 1981)\n",
"RAD = widgets.gaussian.value\n",
- "if (RAD != 0):\n",
- " wt = 2.0*np.pi*gravtk.gauss_weights(RAD,LMAX)\n",
+ "if RAD != 0:\n",
+ " wt = 2.0 * np.pi * gravtk.gauss_weights(RAD, LMAX)\n",
" gw_str = f'_r{RAD:0.0f}km'\n",
"else:\n",
" # else = 1\n",
- " wt = np.ones((LMAX+1))\n",
+ " wt = np.ones((LMAX + 1))\n",
" gw_str = ''\n",
"\n",
"# destriping the GRACE/GRACE-FO harmonics\n",
@@ -365,13 +387,13 @@
"UNITS = widgets.unit_index\n",
"# dfactor is the degree dependent coefficients\n",
"# for specific spherical harmonic output units\n",
- "factors = gravtk.units(lmax=LMAX).harmonic(hl,kl,ll)\n",
+ "factors = gravtk.units(lmax=LMAX).harmonic(hl, kl, ll)\n",
"# 1: cmwe, centimeters water equivalent\n",
"# 2: mmGH, millimeters geoid height\n",
"dfactor = factors.get(gravtk.units.bycode(UNITS))\n",
"# units strings for output files and plots\n",
"unit_label = ['cm', 'mm']\n",
- "unit_name = ['Equivalent Water Thickness','Geoid Height']\n",
+ "unit_name = ['Equivalent Water Thickness', 'Geoid Height']\n",
"\n",
"# converting harmonics to truncated, smoothed coefficients in units\n",
"if widgets.destripe.value:\n",
@@ -383,7 +405,7 @@
"# Remove GIA estimate for month\n",
"Ylms.subtract(GIA_Ylms)\n",
"# smooth harmonics and convert to output units\n",
- "Ylms.convolve(dfactor*wt)\n",
+ "Ylms.convolve(dfactor * wt)\n",
"# create merged masked array\n",
"triangle = Ylms.to_masked_array()"
]
@@ -405,7 +427,7 @@
"source": [
"# display widgets for setting GRACE/GRACE-FO regression plot parameters\n",
"cmap = gravtk.tools.colormap(vmin=-1, vmax=1)\n",
- "ipywidgets.VBox([cmap.name,cmap.reverse])"
+ "ipywidgets.VBox([cmap.name, cmap.reverse])"
]
},
{
@@ -424,30 +446,59 @@
"source": [
"%matplotlib inline\n",
"# plot spherical harmonics for each month\n",
- "fig, ax1 = plt.subplots(num=1, figsize=(8,4))\n",
+ "fig, ax1 = plt.subplots(num=1, figsize=(8, 4))\n",
"\n",
"# levels and normalization for plot range\n",
- "cmap.value.set_bad('lightgrey',1.)\n",
+ "cmap.value.set_bad('lightgrey', 1.0)\n",
"# imshow = show image (interpolation nearest for blocks)\n",
- "im = ax1.imshow(np.ma.zeros((LMAX+1,LMAX+1)), interpolation='nearest',\n",
- " cmap=cmap.value, extent=(-LMAX,LMAX,LMAX,0), animated=True)\n",
+ "im = ax1.imshow(\n",
+ " np.ma.zeros((LMAX + 1, LMAX + 1)),\n",
+ " interpolation='nearest',\n",
+ " cmap=cmap.value,\n",
+ " extent=(-LMAX, LMAX, LMAX, 0),\n",
+ " animated=True,\n",
+ ")\n",
"# Z color limit between -1 and 1\n",
- "im.set_clim(-1.0,1.0)\n",
+ "im.set_clim(-1.0, 1.0)\n",
"\n",
"# add date label (year-calendar month e.g. 2002-01)\n",
- "time_text = ax1.text(0.025, 0.025, '', transform=fig.transFigure,\n",
- " color='k', size=24, weight='bold', ha='left', va='baseline')\n",
+ "time_text = ax1.text(\n",
+ " 0.025,\n",
+ " 0.025,\n",
+ " '',\n",
+ " transform=fig.transFigure,\n",
+ " color='k',\n",
+ " size=24,\n",
+ " weight='bold',\n",
+ " ha='left',\n",
+ " va='baseline',\n",
+ ")\n",
"\n",
"# add text to label Slm side and Clm side\n",
- "t1 = ax1.text(0.39, 0.92, '$S_{lm}$', size=24, weight='bold', \n",
- " transform=ax1.transAxes, ha=\"center\", va=\"center\")\n",
- "t2 = ax1.text(0.61, 0.92, '$C_{lm}$', size=24, weight='bold', \n",
- " transform=ax1.transAxes, ha=\"center\", va=\"center\")\n",
+ "t1 = ax1.text(\n",
+ " 0.39,\n",
+ " 0.92,\n",
+ " '$S_{lm}$',\n",
+ " size=24,\n",
+ " weight='bold',\n",
+ " transform=ax1.transAxes,\n",
+ " ha='center',\n",
+ " va='center',\n",
+ ")\n",
+ "t2 = ax1.text(\n",
+ " 0.61,\n",
+ " 0.92,\n",
+ " '$C_{lm}$',\n",
+ " size=24,\n",
+ " weight='bold',\n",
+ " transform=ax1.transAxes,\n",
+ " ha='center',\n",
+ " va='center',\n",
+ ")\n",
"# add x and y labels\n",
"ax1.set_ylabel('Degree [l]', fontsize=13)\n",
"ax1.set_xlabel('Order [m]', fontsize=13)\n",
- "ax1.tick_params(axis='both', which='both',\n",
- " labelsize=13, direction='in')\n",
+ "ax1.tick_params(axis='both', which='both', labelsize=13, direction='in')\n",
"\n",
"# Add horizontal colorbar and adjust size\n",
"# extend = add extension triangles to upper and lower bounds\n",
@@ -455,32 +506,43 @@
"# pad = distance from main plot axis\n",
"# shrink = percent size of colorbar\n",
"# aspect = lengthXwidth aspect of colorbar\n",
- "cbar = plt.colorbar(im, ax=ax1, extend='both', extendfrac=0.0375,\n",
- " orientation='vertical', pad=0.025, shrink=0.85,\n",
- " aspect=15, drawedges=False)\n",
+ "cbar = plt.colorbar(\n",
+ " im,\n",
+ " ax=ax1,\n",
+ " extend='both',\n",
+ " extendfrac=0.0375,\n",
+ " orientation='vertical',\n",
+ " pad=0.025,\n",
+ " shrink=0.85,\n",
+ " aspect=15,\n",
+ " drawedges=False,\n",
+ ")\n",
"# rasterized colorbar to remove lines\n",
"cbar.solids.set_rasterized(True)\n",
"# Add label to the colorbar\n",
- "cbar.ax.set_ylabel(unit_name[UNITS-1], labelpad=5, fontsize=13)\n",
- "cbar.ax.set_xlabel(unit_label[UNITS-1], fontsize=13, rotation=0)\n",
- "cbar.ax.xaxis.set_label_coords(0.5,1.065)\n",
+ "cbar.ax.set_ylabel(unit_name[UNITS - 1], labelpad=5, fontsize=13)\n",
+ "cbar.ax.set_xlabel(unit_label[UNITS - 1], fontsize=13, rotation=0)\n",
+ "cbar.ax.xaxis.set_label_coords(0.5, 1.065)\n",
"# ticks lines all the way across\n",
- "cbar.ax.tick_params(which='both', width=1, length=15, labelsize=13,\n",
- " direction='in')\n",
- " \n",
+ "cbar.ax.tick_params(\n",
+ " which='both', width=1, length=15, labelsize=13, direction='in'\n",
+ ")\n",
+ "\n",
"# stronger linewidth on frame\n",
"[i.set_linewidth(2.0) for i in ax1.spines.values()]\n",
"# adjust subplot within figure\n",
"fig.patch.set_facecolor('white')\n",
- "fig.subplots_adjust(left=0.075,right=0.99,bottom=0.07,top=0.99)\n",
+ "fig.subplots_adjust(left=0.075, right=0.99, bottom=0.07, top=0.99)\n",
+ "\n",
"\n",
"# animate frames\n",
"def animate_frames(i):\n",
" # set image\n",
- " im.set_data(triangle[:,:,i])\n",
+ " im.set_data(triangle[:, :, i])\n",
" # add date label (year-calendar month e.g. 2002-01)\n",
- " year,month = gravtk.time.grace_to_calendar(Ylms.month[i])\n",
- " time_text.set_text(u'{0:4d}\\u2013{1:02d}'.format(year,month))\n",
+ " year, month = gravtk.time.grace_to_calendar(Ylms.month[i])\n",
+ " time_text.set_text('{0:4d}\\u2013{1:02d}'.format(year, month))\n",
+ "\n",
"\n",
"# set animation\n",
"anim = animation.FuncAnimation(fig, animate_frames, frames=nt)\n",
diff --git a/doc/source/notebooks/GRACE-Spatial-Error.ipynb b/doc/source/notebooks/GRACE-Spatial-Error.ipynb
index 1b765a04..b66aafe7 100644
--- a/doc/source/notebooks/GRACE-Spatial-Error.ipynb
+++ b/doc/source/notebooks/GRACE-Spatial-Error.ipynb
@@ -26,6 +26,7 @@
"source": [
"import numpy as np\n",
"import matplotlib\n",
+ "\n",
"matplotlib.rcParams['mathtext.default'] = 'regular'\n",
"import matplotlib.pyplot as plt\n",
"import cartopy.crs as ccrs\n",
@@ -54,11 +55,7 @@
"# set the directory with GRACE/GRACE-FO data\n",
"# update local data with PO.DAAC https servers\n",
"widgets = gravtk.tools.widgets()\n",
- "ipywidgets.VBox([\n",
- " widgets.directory,\n",
- " widgets.update,\n",
- " widgets.endpoint\n",
- "])"
+ "ipywidgets.VBox([widgets.directory, widgets.update, widgets.endpoint])"
]
},
{
@@ -119,12 +116,9 @@
"# update widgets\n",
"widgets.select_product()\n",
"# display widgets for setting GRACE/GRACE-FO parameters\n",
- "ipywidgets.VBox([\n",
- " widgets.center,\n",
- " widgets.release,\n",
- " widgets.product,\n",
- " widgets.months\n",
- "])"
+ "ipywidgets.VBox(\n",
+ " [widgets.center, widgets.release, widgets.product, widgets.months]\n",
+ ")"
]
},
{
@@ -154,19 +148,21 @@
"# update widgets\n",
"widgets.select_options()\n",
"# display widgets for setting GRACE/GRACE-FO read parameters\n",
- "ipywidgets.VBox([\n",
- " widgets.lmax,\n",
- " widgets.mmax,\n",
- " widgets.geocenter,\n",
- " widgets.C20,\n",
- " widgets.CS21,\n",
- " widgets.CS22,\n",
- " widgets.C30,\n",
- " widgets.C40,\n",
- " widgets.C50,\n",
- " widgets.pole_tide,\n",
- " widgets.atm,\n",
- "])"
+ "ipywidgets.VBox(\n",
+ " [\n",
+ " widgets.lmax,\n",
+ " widgets.mmax,\n",
+ " widgets.geocenter,\n",
+ " widgets.C20,\n",
+ " widgets.CS21,\n",
+ " widgets.CS22,\n",
+ " widgets.C30,\n",
+ " widgets.C40,\n",
+ " widgets.C50,\n",
+ " widgets.pole_tide,\n",
+ " widgets.atm,\n",
+ " ]\n",
+ ")"
]
},
{
@@ -204,11 +200,27 @@
"# read GRACE/GRACE-FO data for parameters\n",
"start_mon = np.min(months)\n",
"end_mon = np.max(months)\n",
- "missing = sorted(set(np.arange(start_mon,end_mon+1)) - set(months))\n",
- "Ylms = gravtk.grace_input_months(widgets.base_directory, PROC, DREL, DSET,\n",
- " LMAX, start_mon, end_mon, missing, SLR_C20, DEG1, MMAX=MMAX,\n",
- " SLR_21=SLR_21, SLR_22=SLR_22, SLR_C30=SLR_C30, SLR_C40=SLR_C40,\n",
- " SLR_C50=SLR_C50, POLE_TIDE=POLE_TIDE, ATM=ATM)\n",
+ "missing = sorted(set(np.arange(start_mon, end_mon + 1)) - set(months))\n",
+ "Ylms = gravtk.grace_input_months(\n",
+ " widgets.base_directory,\n",
+ " PROC,\n",
+ " DREL,\n",
+ " DSET,\n",
+ " LMAX,\n",
+ " start_mon,\n",
+ " end_mon,\n",
+ " missing,\n",
+ " SLR_C20,\n",
+ " DEG1,\n",
+ " MMAX=MMAX,\n",
+ " SLR_21=SLR_21,\n",
+ " SLR_22=SLR_22,\n",
+ " SLR_C30=SLR_C30,\n",
+ " SLR_C40=SLR_C40,\n",
+ " SLR_C50=SLR_C50,\n",
+ " POLE_TIDE=POLE_TIDE,\n",
+ " ATM=ATM,\n",
+ ")\n",
"# create harmonics object and remove mean\n",
"GRACE_Ylms = gravtk.harmonics().from_dict(Ylms)\n",
"GRACE_Ylms.mean(apply=True)\n",
@@ -241,11 +253,9 @@
"# update widgets\n",
"widgets.select_corrections()\n",
"# display widgets for setting GRACE/GRACE-FO corrections parameters\n",
- "ipywidgets.VBox([\n",
- " widgets.gaussian,\n",
- " widgets.destripe,\n",
- " widgets.spacing,\n",
- " widgets.interval])"
+ "ipywidgets.VBox(\n",
+ " [widgets.gaussian, widgets.destripe, widgets.spacing, widgets.interval]\n",
+ ")"
]
},
{
@@ -274,51 +284,57 @@
"dlat = widgets.spacing.value\n",
"# Output Degree Interval\n",
"INTERVAL = widgets.interval.index + 1\n",
- "if (INTERVAL == 1):\n",
+ "if INTERVAL == 1:\n",
" # (-180:180,90:-90)\n",
- " nlon = np.int64((360.0/dlon)+1.0)\n",
- " nlat = np.int64((180.0/dlat)+1.0)\n",
- " grid.lon = -180 + dlon*np.arange(0,nlon)\n",
- " grid.lat = 90.0 - dlat*np.arange(0,nlat)\n",
- "elif (INTERVAL == 2):\n",
+ " nlon = np.int64((360.0 / dlon) + 1.0)\n",
+ " nlat = np.int64((180.0 / dlat) + 1.0)\n",
+ " grid.lon = -180 + dlon * np.arange(0, nlon)\n",
+ " grid.lat = 90.0 - dlat * np.arange(0, nlat)\n",
+ "elif INTERVAL == 2:\n",
" # (Degree spacing)/2\n",
- " grid.lon = np.arange(-180+dlon/2.0,180+dlon/2.0,dlon)\n",
- " grid.lat = np.arange(90.0-dlat/2.0,-90.0-dlat/2.0,-dlat)\n",
+ " grid.lon = np.arange(-180 + dlon / 2.0, 180 + dlon / 2.0, dlon)\n",
+ " grid.lat = np.arange(90.0 - dlat / 2.0, -90.0 - dlat / 2.0, -dlat)\n",
" nlon = len(grid.lon)\n",
" nlat = len(grid.lat)\n",
"\n",
"# Computing plms for converting to spatial domain\n",
- "theta = (90.0 - grid.lat)*np.pi/180.0\n",
+ "theta = np.radians(90.0 - grid.lat)\n",
"PLM, dPLM = gravtk.plm_holmes(LMAX, np.cos(theta))\n",
"# square of legendre polynomials truncated to order MMAX\n",
- "mm = np.arange(0,MMAX+1)\n",
- "PLM2 = PLM[:,mm,:]**2\n",
+ "mm = np.arange(0, MMAX + 1)\n",
+ "PLM2 = PLM[:, mm, :] ** 2\n",
"# Calculating cos(m*phi)^2 and sin(m*phi)^2\n",
- "phi = grid.lon[np.newaxis,:]*np.pi/180.0\n",
- "ccos = np.cos(np.dot(mm[:,np.newaxis],phi))**2\n",
- "ssin = np.sin(np.dot(mm[:,np.newaxis],phi))**2\n",
- " \n",
+ "phi = np.radians(grid.lon[np.newaxis, :])\n",
+ "ccos = np.cos(np.dot(mm[:, np.newaxis], phi)) ** 2\n",
+ "ssin = np.sin(np.dot(mm[:, np.newaxis], phi)) ** 2\n",
+ "\n",
"# read load love numbers file\n",
"# PREM outputs from Han and Wahr (1995)\n",
"# https://doi.org/10.1111/j.1365-246X.1995.tb01819.x\n",
- "love_numbers_file = gravtk.utilities.get_data_path(['data','love_numbers'])\n",
+ "love_numbers_file = gravtk.utilities.get_data_path(['data', 'love_numbers'])\n",
"header = 2\n",
- "columns = ['l','hl','kl','ll']\n",
+ "columns = ['l', 'hl', 'kl', 'll']\n",
"# LMAX of load love numbers from Han and Wahr (1995) is 696.\n",
"# from Wahr (2007) linearly interpolating kl works\n",
"# however, as we are linearly extrapolating out, do not make\n",
"# LMAX too much larger than 696\n",
"# read arrays of kl, hl, and ll Love Numbers\n",
- "hl,kl,ll = gravtk.read_love_numbers(love_numbers_file, LMAX=LMAX,\n",
- " HEADER=header, COLUMNS=columns, REFERENCE='CF', FORMAT='tuple')\n",
+ "hl, kl, ll = gravtk.read_love_numbers(\n",
+ " love_numbers_file,\n",
+ " LMAX=LMAX,\n",
+ " HEADER=header,\n",
+ " COLUMNS=columns,\n",
+ " REFERENCE='CF',\n",
+ " FORMAT='tuple',\n",
+ ")\n",
"\n",
"# gaussian smoothing radius in km (Jekeli, 1981)\n",
"RAD = widgets.gaussian.value\n",
- "if (RAD != 0):\n",
- " wt = 2.0*np.pi*gravtk.gauss_weights(RAD,LMAX)\n",
+ "if RAD != 0:\n",
+ " wt = 2.0 * np.pi * gravtk.gauss_weights(RAD, LMAX)\n",
"else:\n",
" # else = 1\n",
- " wt = np.ones((LMAX+1))\n",
+ " wt = np.ones((LMAX + 1))\n",
"\n",
"# destriping the GRACE/GRACE-FO harmonics\n",
"if widgets.destripe.value:\n",
@@ -328,7 +344,7 @@
"\n",
"# dfactor is the degree dependent coefficients\n",
"# for converting to spherical harmonic output units\n",
- "factors = gravtk.units(lmax=LMAX).harmonic(hl,kl,ll).mmwe\n",
+ "factors = gravtk.units(lmax=LMAX).harmonic(hl, kl, ll).mmwe\n",
"# mmwe, millimeters water equivalent\n",
"dfactor = factors.get('mmwe')\n",
"# units strings for output plots\n",
@@ -336,50 +352,51 @@
"unit_name = 'Equivalent Water Thickness'\n",
"\n",
"# Delta coefficients of GRACE time series (Error components)\n",
- "delta_Ylms = gravtk.harmonics(lmax=LMAX,mmax=MMAX)\n",
- "delta_Ylms.clm = np.zeros((LMAX+1, MMAX+1))\n",
- "delta_Ylms.slm = np.zeros((LMAX+1, MMAX+1))\n",
+ "delta_Ylms = gravtk.harmonics(lmax=LMAX, mmax=MMAX)\n",
+ "delta_Ylms.clm = np.zeros((LMAX + 1, MMAX + 1))\n",
+ "delta_Ylms.slm = np.zeros((LMAX + 1, MMAX + 1))\n",
"# Smoothing Half-Width (CNES is a 10-day solution)\n",
"# All other solutions are monthly solutions (HFWTH for annual = 6)\n",
- "if ((PROC == 'CNES') and (DREL in ('RL01','RL02'))):\n",
+ "if (PROC == 'CNES') and (DREL in ('RL01', 'RL02')):\n",
" HFWTH = 19\n",
"else:\n",
" HFWTH = 6\n",
"# Equal to the noise of the smoothed time-series\n",
"# for each spherical harmonic order\n",
- "for m in range(0,MMAX+1):# MMAX+1 to include MMAX\n",
+ "for m in range(0, MMAX + 1): # MMAX+1 to include MMAX\n",
" # for each spherical harmonic degree\n",
- " for l in range(m,LMAX+1):# LMAX+1 to include LMAX\n",
+ " for l in range(m, LMAX + 1): # LMAX+1 to include LMAX\n",
" # Delta coefficients of GRACE time series\n",
- " for cs,csharm in enumerate(['clm','slm']):\n",
+ " for cs, csharm in enumerate(['clm', 'slm']):\n",
" # calculate GRACE Error (Noise of smoothed time-series)\n",
" # With Annual and Semi-Annual Terms\n",
" val1 = getattr(Ylms, csharm)\n",
- " smth = gravtk.time_series.smooth(Ylms.time, val1[l,m,:],\n",
- " HFWTH=HFWTH)\n",
+ " smth = gravtk.time_series.smooth(\n",
+ " Ylms.time, val1[l, m, :], HFWTH=HFWTH\n",
+ " )\n",
" # number of smoothed points\n",
" nsmth = len(smth['data'])\n",
" tsmth = np.mean(smth['time'])\n",
" # GRACE delta Ylms\n",
" # variance of data-(smoothed+annual+semi)\n",
" val2 = getattr(delta_Ylms, csharm)\n",
- " val2[l,m] = np.sqrt(np.sum(smth['noise']**2)/nsmth)\n",
- " \n",
+ " val2[l, m] = np.sqrt(np.sum(smth['noise'] ** 2) / nsmth)\n",
+ "\n",
"# convolve delta harmonics with degree dependent factors\n",
- "delta_Ylms = delta_Ylms.convolve(dfactor*wt)\n",
+ "delta_Ylms = delta_Ylms.convolve(dfactor * wt)\n",
"# smooth harmonics and convert to output units\n",
- "YLM2 = delta_Ylms.power(2.0).scale(1.0/nsmth)\n",
+ "YLM2 = delta_Ylms.power(2.0).scale(1.0 / nsmth)\n",
"# Calculate fourier coefficients\n",
- "d_cos = np.zeros((MMAX+1,nlat))# [m,th]\n",
- "d_sin = np.zeros((MMAX+1,nlat))# [m,th]\n",
+ "d_cos = np.zeros((MMAX + 1, nlat)) # [m,th]\n",
+ "d_sin = np.zeros((MMAX + 1, nlat)) # [m,th]\n",
"# Calculating delta spatial values\n",
- "for k in range(0,nlat):\n",
+ "for k in range(0, nlat):\n",
" # summation over all spherical harmonic degrees\n",
- " d_cos[:,k] = np.sum(PLM2[:,:,k]*YLM2.clm, axis=0)\n",
- " d_sin[:,k] = np.sum(PLM2[:,:,k]*YLM2.slm, axis=0)\n",
+ " d_cos[:, k] = np.sum(PLM2[:, :, k] * YLM2.clm, axis=0)\n",
+ " d_sin[:, k] = np.sum(PLM2[:, :, k] * YLM2.slm, axis=0)\n",
"\n",
"# Multiplying by c/s(phi#m) to get spatial maps (lon,lat)\n",
- "grid.data = np.sqrt(np.dot(ccos.T,d_cos) + np.dot(ssin.T,d_sin)).T\n",
+ "grid.data = np.sqrt(np.dot(ccos.T, d_cos) + np.dot(ssin.T, d_sin)).T\n",
"grid.mask = np.zeros_like(grid.data, dtype=bool)"
]
},
@@ -426,7 +443,7 @@
"vmax = np.ceil(np.max(grid.data)).astype(np.int64)\n",
"cmap = gravtk.tools.colormap(vmin=0, vmax=vmax)\n",
"# display widgets for setting GRACE/GRACE-FO plot parameters\n",
- "ipywidgets.VBox([cmap.range,cmap.step,cmap.name,cmap.reverse])"
+ "ipywidgets.VBox([cmap.range, cmap.step, cmap.name, cmap.reverse])"
]
},
{
@@ -435,13 +452,24 @@
"metadata": {},
"outputs": [],
"source": [
- "fig, ax2 = plt.subplots(num=2, nrows=1, ncols=1, figsize=(10.375,6.625),\n",
- " subplot_kw=dict(projection=ccrs.PlateCarree()))\n",
+ "fig, ax2 = plt.subplots(\n",
+ " num=2,\n",
+ " nrows=1,\n",
+ " ncols=1,\n",
+ " figsize=(10.375, 6.625),\n",
+ " subplot_kw=dict(projection=ccrs.PlateCarree()),\n",
+ ")\n",
"\n",
"# levels and normalization for plot range\n",
- "im = ax2.imshow(grid.data, interpolation='nearest',\n",
- " norm=cmap.norm, cmap=cmap.value, transform=ccrs.PlateCarree(),\n",
- " extent=grid.extent, origin='upper')\n",
+ "im = ax2.imshow(\n",
+ " grid.data,\n",
+ " interpolation='nearest',\n",
+ " norm=cmap.norm,\n",
+ " cmap=cmap.value,\n",
+ " transform=ccrs.PlateCarree(),\n",
+ " extent=grid.extent,\n",
+ " origin='upper',\n",
+ ")\n",
"ax2.coastlines('50m')\n",
"\n",
"# Add horizontal colorbar and adjust size\n",
@@ -450,27 +478,35 @@
"# pad = distance from main plot axis\n",
"# shrink = percent size of colorbar\n",
"# aspect = lengthXwidth aspect of colorbar\n",
- "cbar = plt.colorbar(im, ax=ax2, extend='both', extendfrac=0.0375,\n",
- " orientation='horizontal', pad=0.025, shrink=0.85,\n",
- " aspect=22, drawedges=False)\n",
+ "cbar = plt.colorbar(\n",
+ " im,\n",
+ " ax=ax2,\n",
+ " extend='both',\n",
+ " extendfrac=0.0375,\n",
+ " orientation='horizontal',\n",
+ " pad=0.025,\n",
+ " shrink=0.85,\n",
+ " aspect=22,\n",
+ " drawedges=False,\n",
+ ")\n",
"# rasterized colorbar to remove lines\n",
"cbar.solids.set_rasterized(True)\n",
"# Add label to the colorbar\n",
- "cbar.ax.set_xlabel(f'{unit_name} [{unit_label}]',\n",
- " labelpad=10, fontsize=24)\n",
+ "cbar.ax.set_xlabel(f'{unit_name} [{unit_label}]', labelpad=10, fontsize=24)\n",
"# Set the tick levels for the colorbar\n",
"cbar.set_ticks(cmap.levels)\n",
"cbar.set_ticklabels(cmap.label)\n",
"# ticks lines all the way across\n",
- "cbar.ax.tick_params(which='both', width=1, length=26, labelsize=24,\n",
- " direction='in')\n",
- " \n",
+ "cbar.ax.tick_params(\n",
+ " which='both', width=1, length=26, labelsize=24, direction='in'\n",
+ ")\n",
+ "\n",
"# stronger linewidth on frame\n",
"ax2.spines['geo'].set_linewidth(2.0)\n",
"ax2.spines['geo'].set_capstyle('projecting')\n",
"# adjust subplot within figure\n",
"fig.patch.set_facecolor('white')\n",
- "fig.subplots_adjust(left=0.02,right=0.98,bottom=0.05,top=0.98)\n",
+ "fig.subplots_adjust(left=0.02, right=0.98, bottom=0.05, top=0.98)\n",
"plt.show()"
]
}
diff --git a/doc/source/notebooks/GRACE-Spatial-Maps.ipynb b/doc/source/notebooks/GRACE-Spatial-Maps.ipynb
index 2b1b908f..a4361aee 100644
--- a/doc/source/notebooks/GRACE-Spatial-Maps.ipynb
+++ b/doc/source/notebooks/GRACE-Spatial-Maps.ipynb
@@ -37,9 +37,10 @@
"source": [
"import numpy as np\n",
"import matplotlib\n",
+ "\n",
"matplotlib.rcParams['mathtext.default'] = 'regular'\n",
- "matplotlib.rcParams[\"animation.html\"] = \"jshtml\"\n",
- "matplotlib.rcParams[\"animation.embed_limit\"] = 50\n",
+ "matplotlib.rcParams['animation.html'] = 'jshtml'\n",
+ "matplotlib.rcParams['animation.embed_limit'] = 50\n",
"import matplotlib.pyplot as plt\n",
"import matplotlib.animation as animation\n",
"import cartopy.crs as ccrs\n",
@@ -69,11 +70,7 @@
"# set the directory with GRACE/GRACE-FO data\n",
"# update local data with PO.DAAC https servers\n",
"widgets = gravtk.tools.widgets()\n",
- "ipywidgets.VBox([\n",
- " widgets.directory,\n",
- " widgets.update,\n",
- " widgets.endpoint\n",
- "])"
+ "ipywidgets.VBox([widgets.directory, widgets.update, widgets.endpoint])"
]
},
{
@@ -134,12 +131,9 @@
"# update widgets\n",
"widgets.select_product()\n",
"# display widgets for setting GRACE/GRACE-FO parameters\n",
- "ipywidgets.VBox([\n",
- " widgets.center,\n",
- " widgets.release,\n",
- " widgets.product,\n",
- " widgets.months\n",
- "])"
+ "ipywidgets.VBox(\n",
+ " [widgets.center, widgets.release, widgets.product, widgets.months]\n",
+ ")"
]
},
{
@@ -193,19 +187,21 @@
"# update widgets\n",
"widgets.select_options()\n",
"# display widgets for setting GRACE/GRACE-FO read parameters\n",
- "ipywidgets.VBox([\n",
- " widgets.lmax,\n",
- " widgets.mmax,\n",
- " widgets.geocenter,\n",
- " widgets.C20,\n",
- " widgets.CS21,\n",
- " widgets.CS22,\n",
- " widgets.C30,\n",
- " widgets.C40,\n",
- " widgets.C50,\n",
- " widgets.pole_tide,\n",
- " widgets.atm\n",
- "])"
+ "ipywidgets.VBox(\n",
+ " [\n",
+ " widgets.lmax,\n",
+ " widgets.mmax,\n",
+ " widgets.geocenter,\n",
+ " widgets.C20,\n",
+ " widgets.CS21,\n",
+ " widgets.CS22,\n",
+ " widgets.C30,\n",
+ " widgets.C40,\n",
+ " widgets.C50,\n",
+ " widgets.pole_tide,\n",
+ " widgets.atm,\n",
+ " ]\n",
+ ")"
]
},
{
@@ -243,11 +239,27 @@
"# read GRACE/GRACE-FO data for parameters\n",
"start_mon = np.min(months)\n",
"end_mon = np.max(months)\n",
- "missing = sorted(set(np.arange(start_mon,end_mon+1)) - set(months))\n",
- "Ylms = gravtk.grace_input_months(widgets.base_directory, PROC, DREL, DSET,\n",
- " LMAX, start_mon, end_mon, missing, SLR_C20, DEG1, MMAX=MMAX,\n",
- " SLR_21=SLR_21, SLR_22=SLR_22, SLR_C30=SLR_C30, SLR_C40=SLR_C40,\n",
- " SLR_C50=SLR_C50, POLE_TIDE=POLE_TIDE, ATM=ATM)\n",
+ "missing = sorted(set(np.arange(start_mon, end_mon + 1)) - set(months))\n",
+ "Ylms = gravtk.grace_input_months(\n",
+ " widgets.base_directory,\n",
+ " PROC,\n",
+ " DREL,\n",
+ " DSET,\n",
+ " LMAX,\n",
+ " start_mon,\n",
+ " end_mon,\n",
+ " missing,\n",
+ " SLR_C20,\n",
+ " DEG1,\n",
+ " MMAX=MMAX,\n",
+ " SLR_21=SLR_21,\n",
+ " SLR_22=SLR_22,\n",
+ " SLR_C30=SLR_C30,\n",
+ " SLR_C40=SLR_C40,\n",
+ " SLR_C50=SLR_C50,\n",
+ " POLE_TIDE=POLE_TIDE,\n",
+ " ATM=ATM,\n",
+ ")\n",
"# create harmonics object and remove mean\n",
"GRACE_Ylms = gravtk.harmonics().from_dict(Ylms)\n",
"GRACE_Ylms.mean(apply=True)\n",
@@ -343,19 +355,22 @@
"widgets.select_corrections()\n",
"widgets.select_output()\n",
"# display widgets for setting GRACE/GRACE-FO corrections parameters\n",
- "ipywidgets.VBox([\n",
- " widgets.GIA_file,\n",
- " widgets.GIA,\n",
- " widgets.remove_file,\n",
- " widgets.remove_format,\n",
- " widgets.redistribute_removed,\n",
- " widgets.mask,\n",
- " widgets.gaussian,\n",
- " widgets.destripe,\n",
- " widgets.spacing,\n",
- " widgets.interval,\n",
- " widgets.units,\n",
- " widgets.output_format])"
+ "ipywidgets.VBox(\n",
+ " [\n",
+ " widgets.GIA_file,\n",
+ " widgets.GIA,\n",
+ " widgets.remove_file,\n",
+ " widgets.remove_format,\n",
+ " widgets.redistribute_removed,\n",
+ " widgets.mask,\n",
+ " widgets.gaussian,\n",
+ " widgets.destripe,\n",
+ " widgets.spacing,\n",
+ " widgets.interval,\n",
+ " widgets.units,\n",
+ " widgets.output_format,\n",
+ " ]\n",
+ ")"
]
},
{
@@ -391,41 +406,48 @@
"dlat = widgets.spacing.value\n",
"# Output Degree Interval\n",
"INTERVAL = widgets.interval.index + 1\n",
- "if (INTERVAL == 1):\n",
+ "if INTERVAL == 1:\n",
" # (-180:180,90:-90)\n",
- " nlon = np.int64((360.0/dlon)+1.0)\n",
- " nlat = np.int64((180.0/dlat)+1.0)\n",
- " grid.lon = -180 + dlon*np.arange(0,nlon)\n",
- " grid.lat = 90.0 - dlat*np.arange(0,nlat)\n",
- "elif (INTERVAL == 2):\n",
+ " nlon = np.int64((360.0 / dlon) + 1.0)\n",
+ " nlat = np.int64((180.0 / dlat) + 1.0)\n",
+ " grid.lon = -180 + dlon * np.arange(0, nlon)\n",
+ " grid.lat = 90.0 - dlat * np.arange(0, nlat)\n",
+ "elif INTERVAL == 2:\n",
" # (Degree spacing)/2\n",
- " grid.lon = np.arange(-180+dlon/2.0,180+dlon/2.0,dlon)\n",
- " grid.lat = np.arange(90.0-dlat/2.0,-90.0-dlat/2.0,-dlat)\n",
+ " grid.lon = np.arange(-180 + dlon / 2.0, 180 + dlon / 2.0, dlon)\n",
+ " grid.lat = np.arange(90.0 - dlat / 2.0, -90.0 - dlat / 2.0, -dlat)\n",
" nlon = len(grid.lon)\n",
" nlat = len(grid.lat)\n",
"\n",
"# Computing plms for converting to spatial domain\n",
- "theta = (90.0-grid.lat)*np.pi/180.0\n",
+ "theta = np.radians(90.0 - grid.lat)\n",
"PLM, dPLM = gravtk.plm_holmes(LMAX, np.cos(theta))\n",
"\n",
"# read load love numbers file\n",
"# PREM outputs from Han and Wahr (1995)\n",
"# https://doi.org/10.1111/j.1365-246X.1995.tb01819.x\n",
- "love_numbers_file = gravtk.utilities.get_data_path(['data','love_numbers'])\n",
+ "love_numbers_file = gravtk.utilities.get_data_path(['data', 'love_numbers'])\n",
"header = 2\n",
- "columns = ['l','hl','kl','ll']\n",
+ "columns = ['l', 'hl', 'kl', 'll']\n",
"# LMAX of load love numbers from Han and Wahr (1995) is 696.\n",
"# from Wahr (2007) linearly interpolating kl works\n",
"# however, as we are linearly extrapolating out, do not make\n",
"# LMAX too much larger than 696\n",
"# read arrays of kl, hl, and ll Love Numbers\n",
- "hl,kl,ll = gravtk.read_love_numbers(love_numbers_file, LMAX=LMAX,\n",
- " HEADER=header, COLUMNS=columns, REFERENCE='CF', FORMAT='tuple')\n",
+ "hl, kl, ll = gravtk.read_love_numbers(\n",
+ " love_numbers_file,\n",
+ " LMAX=LMAX,\n",
+ " HEADER=header,\n",
+ " COLUMNS=columns,\n",
+ " REFERENCE='CF',\n",
+ " FORMAT='tuple',\n",
+ ")\n",
"\n",
"# read GIA data\n",
"GIA = widgets.GIA.value\n",
- "GIA_Ylms_rate = gravtk.gia(lmax=LMAX).from_GIA(widgets.GIA_model,\n",
- " GIA=GIA, mmax=MMAX)\n",
+ "GIA_Ylms_rate = gravtk.gia(lmax=LMAX).from_GIA(\n",
+ " widgets.GIA_model, GIA=GIA, mmax=MMAX\n",
+ ")\n",
"gia_str = '' if (GIA == '[None]') else f'_{GIA_Ylms_rate.title}'\n",
"# calculate the monthly mass change from GIA\n",
"# monthly GIA calculated by gia_rate*time elapsed\n",
@@ -436,9 +458,10 @@
"# if redistributing removed mass over the ocean\n",
"if widgets.redistribute_removed.value:\n",
" # read Land-Sea Mask and convert to spherical harmonics\n",
- " ocean_Ylms = gravtk.ocean_stokes(widgets.landmask, LMAX,\n",
- " MMAX=MMAX, LOVE=(hl,kl,ll))\n",
- " \n",
+ " ocean_Ylms = gravtk.ocean_stokes(\n",
+ " widgets.landmask, LMAX, MMAX=MMAX, LOVE=(hl, kl, ll)\n",
+ " )\n",
+ "\n",
"# read data to be removed from GRACE/GRACE-FO monthly harmonics\n",
"remove_Ylms = GRACE_Ylms.zeros_like()\n",
"remove_Ylms.time[:] = np.copy(GRACE_Ylms.time)\n",
@@ -446,45 +469,45 @@
"# If there are files to be removed from the GRACE/GRACE-FO data\n",
"# for each file separated by commas\n",
"for f in widgets.remove_files:\n",
- " if (widgets.remove_format.value == 'netCDF4'):\n",
+ " if widgets.remove_format.value == 'netCDF4':\n",
" # read netCDF4 file\n",
" Ylms = gravtk.harmonics().from_netCDF4(f)\n",
- " elif (widgets.remove_format.value == 'HDF5'):\n",
+ " elif widgets.remove_format.value == 'HDF5':\n",
" # read HDF5 file\n",
" Ylms = gravtk.harmonics().from_HDF5(f)\n",
- " elif (widgets.remove_format.value == 'index (ascii)'):\n",
+ " elif widgets.remove_format.value == 'index (ascii)':\n",
" # read index of ascii files\n",
- " Ylms = gravtk.harmonics().from_index(f,format='ascii')\n",
- " elif (widgets.remove_format.value == 'index (netCDF4)'):\n",
+ " Ylms = gravtk.harmonics().from_index(f, format='ascii')\n",
+ " elif widgets.remove_format.value == 'index (netCDF4)':\n",
" # read index of netCDF4 files\n",
- " Ylms = gravtk.harmonics().from_index(f,format='netCDF4')\n",
- " elif (widgets.remove_format.value == 'index (HDF5)'):\n",
+ " Ylms = gravtk.harmonics().from_index(f, format='netCDF4')\n",
+ " elif widgets.remove_format.value == 'index (HDF5)':\n",
" # read index of HDF5 files\n",
- " Ylms = gravtk.harmonics().from_index(f,format='HDF5')\n",
+ " Ylms = gravtk.harmonics().from_index(f, format='HDF5')\n",
" # reduce to months of interest and truncate to range\n",
- " Ylms = Ylms.subset(months).truncate(LMAX,mmax=MMAX)\n",
+ " Ylms = Ylms.subset(months).truncate(LMAX, mmax=MMAX)\n",
" # redistribute removed mass over the ocean\n",
" if widgets.redistribute_removed.value:\n",
" # calculate ratio between total removed mass and\n",
" # a uniformly distributed cm of water over the ocean\n",
- " ratio = Ylms.clm[0,0,:]/ocean_Ylms.clm[0,0]\n",
+ " ratio = Ylms.clm[0, 0, :] / ocean_Ylms.clm[0, 0]\n",
" # for each spherical harmonic\n",
- " for m in range(0,MMAX+1):\n",
- " for l in range(m,LMAX+1):\n",
+ " for m in range(0, MMAX + 1):\n",
+ " for l in range(m, LMAX + 1):\n",
" # remove the ratio*ocean Ylms from Ylms\n",
- " Ylms.clm[l,m,:]-=ratio*ocean_Ylms.clm[l,m]\n",
- " Ylms.slm[l,m,:]-=ratio*ocean_Ylms.slm[l,m]\n",
+ " Ylms.clm[l, m, :] -= ratio * ocean_Ylms.clm[l, m]\n",
+ " Ylms.slm[l, m, :] -= ratio * ocean_Ylms.slm[l, m]\n",
" # add the harmonics to be removed to the total\n",
" remove_Ylms.add(Ylms)\n",
"\n",
"# gaussian smoothing radius in km (Jekeli, 1981)\n",
"RAD = widgets.gaussian.value\n",
- "if (RAD != 0):\n",
- " wt = 2.0*np.pi*gravtk.gauss_weights(RAD,LMAX)\n",
+ "if RAD != 0:\n",
+ " wt = 2.0 * np.pi * gravtk.gauss_weights(RAD, LMAX)\n",
" gw_str = f'_r{RAD:0.0f}km'\n",
"else:\n",
" # else = 1\n",
- " wt = np.ones((LMAX+1))\n",
+ " wt = np.ones((LMAX + 1))\n",
" gw_str = ''\n",
"\n",
"# destriping the GRACE/GRACE-FO harmonics\n",
@@ -494,7 +517,7 @@
"UNITS = widgets.unit_index\n",
"# dfactor is the degree dependent coefficients\n",
"# for specific spherical harmonic output units\n",
- "factors = gravtk.units(lmax=LMAX).harmonic(hl,kl,ll)\n",
+ "factors = gravtk.units(lmax=LMAX).harmonic(hl, kl, ll)\n",
"# 1: cmwe, centimeters water equivalent\n",
"# 2: mmGH, millimeters geoid height\n",
"# 3: mmCU, millimeters elastic crustal deformation\n",
@@ -502,17 +525,21 @@
"# 5: mbar, millibars equivalent surface pressure\n",
"dfactor = factors.get(gravtk.units.bycode(UNITS))\n",
"# units strings for output files and plots\n",
- "unit_label = ['cm', 'mm', 'mm', u'\\u03BCGal', 'mb']\n",
- "unit_name = ['Equivalent Water Thickness', 'Geoid Height',\n",
- " 'Elastic Crustal Uplift', 'Gravitational Undulation',\n",
- " 'Equivalent Surface Pressure']\n",
+ "unit_label = ['cm', 'mm', 'mm', '\\u03bcGal', 'mb']\n",
+ "unit_name = [\n",
+ " 'Equivalent Water Thickness',\n",
+ " 'Geoid Height',\n",
+ " 'Elastic Crustal Uplift',\n",
+ " 'Gravitational Undulation',\n",
+ " 'Equivalent Surface Pressure',\n",
+ "]\n",
"\n",
"# converting harmonics to truncated, smoothed coefficients in units\n",
"# combining harmonics to calculate output spatial fields\n",
"# output spatial grid\n",
"grid.data = np.zeros((nlat, nlon, nt))\n",
"grid.mask = np.zeros((nlat, nlon, nt), dtype=bool)\n",
- "for i,grace_month in enumerate(GRACE_Ylms.month):\n",
+ "for i, grace_month in enumerate(GRACE_Ylms.month):\n",
" # GRACE/GRACE-FO harmonics for time t\n",
" # and monthly files to be removed\n",
" if widgets.destripe.value:\n",
@@ -524,10 +551,11 @@
" # Remove GIA rate for time\n",
" Ylms.subtract(GIA_Ylms.index(i))\n",
" # smooth harmonics and convert to output units\n",
- " Ylms.convolve(dfactor*wt)\n",
+ " Ylms.convolve(dfactor * wt)\n",
" # convert spherical harmonics to output spatial grid\n",
- " grid.data[:,:,i] = gravtk.harmonic_summation(Ylms.clm, Ylms.slm,\n",
- " grid.lon, grid.lat, LMAX=LMAX, MMAX=MMAX, PLM=PLM).T"
+ " grid.data[:, :, i] = gravtk.harmonic_summation(\n",
+ " Ylms.clm, Ylms.slm, grid.lon, grid.lat, LMAX=LMAX, MMAX=MMAX, PLM=PLM\n",
+ " ).T"
]
},
{
@@ -545,16 +573,33 @@
"outputs": [],
"source": [
"# output to netCDF4 or HDF5\n",
- "suffix = dict(netCDF4='nc',HDF5='H5')\n",
+ "suffix = dict(netCDF4='nc', HDF5='H5')\n",
"file_format = '{0}_{1}_{2}{3}{4}_{5}_L{6:d}{7}{8}{9}_{10:03d}-{11:03d}.{12}'\n",
- "if widgets.format in ('netCDF4','HDF5'):\n",
- " FILE = file_format.format(PROC,DREL,DSET,gia_str,GRACE_Ylms.title,\n",
- " widgets.units.value,LMAX,order_str,gw_str,ds_str,\n",
- " months[0],months[-1],suffix[widgets.format])\n",
- " grid.to_file(GRACE_Ylms.directory.joinpath(FILE),\n",
- " format=widgets.format, varname='z',\n",
- " units=widgets.units.value, longname=unit_name[UNITS-1],\n",
- " title='GRACE/GRACE-FO Spatial Data', date=True)\n"
+ "if widgets.format in ('netCDF4', 'HDF5'):\n",
+ " FILE = file_format.format(\n",
+ " PROC,\n",
+ " DREL,\n",
+ " DSET,\n",
+ " gia_str,\n",
+ " GRACE_Ylms.title,\n",
+ " widgets.units.value,\n",
+ " LMAX,\n",
+ " order_str,\n",
+ " gw_str,\n",
+ " ds_str,\n",
+ " months[0],\n",
+ " months[-1],\n",
+ " suffix[widgets.format],\n",
+ " )\n",
+ " grid.to_file(\n",
+ " GRACE_Ylms.directory.joinpath(FILE),\n",
+ " format=widgets.format,\n",
+ " varname='z',\n",
+ " units=widgets.units.value,\n",
+ " longname=unit_name[UNITS - 1],\n",
+ " title='GRACE/GRACE-FO Spatial Data',\n",
+ " date=True,\n",
+ " )"
]
},
{
@@ -576,7 +621,7 @@
"vmax = np.ceil(np.max(grid.data)).astype(np.int64)\n",
"cmap1 = gravtk.tools.colormap(vmin=vmin, vmax=vmax)\n",
"# display widgets for setting GRACE/GRACE-FO regression plot parameters\n",
- "ipywidgets.VBox([cmap1.range,cmap1.step,cmap1.name,cmap1.reverse])"
+ "ipywidgets.VBox([cmap1.range, cmap1.step, cmap1.name, cmap1.reverse])"
]
},
{
@@ -586,18 +631,39 @@
"outputs": [],
"source": [
"%matplotlib inline\n",
- "fig, ax1 = plt.subplots(num=1, nrows=1, ncols=1, figsize=(10.375,6.625),\n",
- " subplot_kw=dict(projection=ccrs.PlateCarree()))\n",
+ "fig, ax1 = plt.subplots(\n",
+ " num=1,\n",
+ " nrows=1,\n",
+ " ncols=1,\n",
+ " figsize=(10.375, 6.625),\n",
+ " subplot_kw=dict(projection=ccrs.PlateCarree()),\n",
+ ")\n",
"\n",
"# levels and normalization for plot range\n",
- "im = ax1.imshow(np.zeros((nlat, nlon)), interpolation='nearest',\n",
- " norm=cmap1.norm, cmap=cmap1.value, transform=ccrs.PlateCarree(),\n",
- " extent=grid.extent, origin='upper', animated=True)\n",
+ "im = ax1.imshow(\n",
+ " np.zeros((nlat, nlon)),\n",
+ " interpolation='nearest',\n",
+ " norm=cmap1.norm,\n",
+ " cmap=cmap1.value,\n",
+ " transform=ccrs.PlateCarree(),\n",
+ " extent=grid.extent,\n",
+ " origin='upper',\n",
+ " animated=True,\n",
+ ")\n",
"ax1.coastlines('50m')\n",
"\n",
"# add date label (year-calendar month e.g. 2002-01)\n",
- "time_text = ax1.text(0.025, 0.025, '', transform=fig.transFigure,\n",
- " color='k', size=24, weight='bold', ha='left', va='baseline')\n",
+ "time_text = ax1.text(\n",
+ " 0.025,\n",
+ " 0.025,\n",
+ " '',\n",
+ " transform=fig.transFigure,\n",
+ " color='k',\n",
+ " size=24,\n",
+ " weight='bold',\n",
+ " ha='left',\n",
+ " va='baseline',\n",
+ ")\n",
"\n",
"# Add horizontal colorbar and adjust size\n",
"# extend = add extension triangles to upper and lower bounds\n",
@@ -605,36 +671,47 @@
"# pad = distance from main plot axis\n",
"# shrink = percent size of colorbar\n",
"# aspect = lengthXwidth aspect of colorbar\n",
- "cbar = plt.colorbar(im, ax=ax1, extend='both', extendfrac=0.0375,\n",
- " orientation='horizontal', pad=0.025, shrink=0.85,\n",
- " aspect=22, drawedges=False)\n",
+ "cbar = plt.colorbar(\n",
+ " im,\n",
+ " ax=ax1,\n",
+ " extend='both',\n",
+ " extendfrac=0.0375,\n",
+ " orientation='horizontal',\n",
+ " pad=0.025,\n",
+ " shrink=0.85,\n",
+ " aspect=22,\n",
+ " drawedges=False,\n",
+ ")\n",
"# rasterized colorbar to remove lines\n",
"cbar.solids.set_rasterized(True)\n",
"# Add label to the colorbar\n",
- "cbar.ax.set_xlabel(unit_name[UNITS-1], labelpad=10, fontsize=24)\n",
- "cbar.ax.set_ylabel(unit_label[UNITS-1], fontsize=24, rotation=0)\n",
+ "cbar.ax.set_xlabel(unit_name[UNITS - 1], labelpad=10, fontsize=24)\n",
+ "cbar.ax.set_ylabel(unit_label[UNITS - 1], fontsize=24, rotation=0)\n",
"cbar.ax.yaxis.set_label_coords(1.045, 0.1)\n",
"# Set the tick levels for the colorbar\n",
"cbar.set_ticks(cmap1.levels)\n",
"cbar.set_ticklabels(cmap1.label)\n",
"# ticks lines all the way across\n",
- "cbar.ax.tick_params(which='both', width=1, length=26, labelsize=24,\n",
- " direction='in')\n",
- " \n",
+ "cbar.ax.tick_params(\n",
+ " which='both', width=1, length=26, labelsize=24, direction='in'\n",
+ ")\n",
+ "\n",
"# stronger linewidth on frame\n",
"ax1.spines['geo'].set_linewidth(2.0)\n",
"ax1.spines['geo'].set_capstyle('projecting')\n",
"# adjust subplot within figure\n",
"fig.patch.set_facecolor('white')\n",
- "fig.subplots_adjust(left=0.02,right=0.98,bottom=0.05,top=0.98)\n",
- " \n",
+ "fig.subplots_adjust(left=0.02, right=0.98, bottom=0.05, top=0.98)\n",
+ "\n",
+ "\n",
"# animate frames\n",
"def animate_frames(i):\n",
" # set image\n",
- " im.set_data(grid.data[:,:,i])\n",
+ " im.set_data(grid.data[:, :, i])\n",
" # add date label (year-calendar month e.g. 2002-01)\n",
- " year,month = gravtk.time.grace_to_calendar(grid.month[i])\n",
- " time_text.set_text(u'{0:4d}\\u2013{1:02d}'.format(year,month))\n",
+ " year, month = gravtk.time.grace_to_calendar(grid.month[i])\n",
+ " time_text.set_text('{0:4d}\\u2013{1:02d}'.format(year, month))\n",
+ "\n",
"\n",
"# set animation\n",
"anim = animation.FuncAnimation(fig, animate_frames, frames=nt)\n",
@@ -673,13 +750,13 @@
"# cyclical options\n",
"cyclicLabel = ipywidgets.Label('Cyclical Terms:')\n",
"cyclicCheckbox = {}\n",
- "for key in ['Annual','Semi-Annual']:\n",
+ "for key in ['Annual', 'Semi-Annual']:\n",
" cyclicCheckbox[key] = ipywidgets.Checkbox(\n",
" value=True,\n",
" description=key,\n",
" disabled=False,\n",
" )\n",
- "cyclic = ipywidgets.HBox([cyclicLabel,*cyclicCheckbox.values()])\n",
+ "cyclic = ipywidgets.HBox([cyclicLabel, *cyclicCheckbox.values()])\n",
"\n",
"# custom fit terms\n",
"termsLabel = ipywidgets.Label('Fit Terms:')\n",
@@ -688,10 +765,10 @@
" description='S2 Tide',\n",
" disabled=False,\n",
")\n",
- "terms = ipywidgets.HBox([termsLabel,termsCheckbox])\n",
+ "terms = ipywidgets.HBox([termsLabel, termsCheckbox])\n",
"\n",
"# display widgets for setting GRACE/GRACE-FO regression parameters\n",
- "ipywidgets.VBox([orderText,cyclic,terms])"
+ "ipywidgets.VBox([orderText, cyclic, terms])"
]
},
{
@@ -702,16 +779,21 @@
"source": [
"# build list of regression fit components\n",
"ORDER = orderText.value\n",
- "PHASES = {'Annual':1.0,'Semi-Annual':0.5}\n",
- "CYCLES = [v for k,v in PHASES.items() if cyclicCheckbox[k].value]\n",
+ "PHASES = {'Annual': 1.0, 'Semi-Annual': 0.5}\n",
+ "CYCLES = [v for k, v in PHASES.items() if cyclicCheckbox[k].value]\n",
"TERMS = []\n",
"if termsCheckbox.value:\n",
" TERMS.extend(gravtk.time_series.aliasing_terms(grid.time))\n",
"# total number of fit terms\n",
- "ncomp = (ORDER + 1) + 2*len(CYCLES) + len(TERMS)\n",
+ "ncomp = (ORDER + 1) + 2 * len(CYCLES) + len(TERMS)\n",
"# Allocating memory for output variables\n",
- "out = gravtk.spatial(spacing=grid.spacing, nlon=nlon, nlat=nlat,\n",
- " extent=grid.extent, fill_value=grid.fill_value)\n",
+ "out = gravtk.spatial(\n",
+ " spacing=grid.spacing,\n",
+ " nlon=nlon,\n",
+ " nlat=nlat,\n",
+ " extent=grid.extent,\n",
+ " fill_value=grid.fill_value,\n",
+ ")\n",
"out.data = np.zeros((nlat, nlon, ncomp))\n",
"# update mask and dimensions\n",
"out.update_mask()\n",
@@ -720,11 +802,16 @@
"for i in range(nlat):\n",
" for j in range(nlon):\n",
" # Calculating the regression coefficients\n",
- " tsbeta = gravtk.time_series.regress(grid.time, grid.data[i,j,:],\n",
- " ORDER=ORDER, CYCLES=CYCLES, TERMS=TERMS)\n",
+ " tsbeta = gravtk.time_series.regress(\n",
+ " grid.time,\n",
+ " grid.data[i, j, :],\n",
+ " ORDER=ORDER,\n",
+ " CYCLES=CYCLES,\n",
+ " TERMS=TERMS,\n",
+ " )\n",
" # save regression components\n",
" for k in range(0, ncomp):\n",
- " out.data[i,j,k] = tsbeta['beta'][k]"
+ " out.data[i, j, k] = tsbeta['beta'][k]"
]
},
{
@@ -743,27 +830,53 @@
"outputs": [],
"source": [
"# strings for polynomial terms\n",
- "if (ORDER == 0):# Mean\n",
+ "if ORDER == 0: # Mean\n",
" variable_longname = ['Mean']\n",
- "elif (ORDER == 1):# Trend\n",
- " variable_longname = ['Constant','Trend']\n",
- "elif (ORDER == 2):# Quadratic\n",
- " variable_longname = ['Constant','Linear','Quadratic']\n",
- "unit_suffix = [' yr$^{{{0:d}}}$'.format(-o) if o else '' for o in range(ORDER+1)]\n",
+ "elif ORDER == 1: # Trend\n",
+ " variable_longname = ['Constant', 'Trend']\n",
+ "elif ORDER == 2: # Quadratic\n",
+ " variable_longname = ['Constant', 'Linear', 'Quadratic']\n",
+ "unit_suffix = [\n",
+ " ' yr$^{{{0:d}}}$'.format(-o) if o else '' for o in range(ORDER + 1)\n",
+ "]\n",
"# strings for cyclical terms\n",
"cyclic_longname = {}\n",
"cyclic_longname['Annual'] = ['Annual Sine', 'Annual Cosine']\n",
"cyclic_longname['Semi-Annual'] = ['Semi-Annual Sine', 'Semi-Annual Cosine']\n",
"# strings for custom fit terms\n",
"terms_longname = {}\n",
- "terms_longname['S2 Tide (GRACE)'] = ['S2 Tidal Alias Sine (GRACE)', 'S2 Tidal Alias Cosine (GRACE)']\n",
- "terms_longname['S2 Tide (GRACE-FO)'] = ['S2 Tidal Alias Sine (GRACE-FO)', 'S2 Tidal Alias Cosine (GRACE-FO)']\n",
+ "terms_longname['S2 Tide (GRACE)'] = [\n",
+ " 'S2 Tidal Alias Sine (GRACE)',\n",
+ " 'S2 Tidal Alias Cosine (GRACE)',\n",
+ "]\n",
+ "terms_longname['S2 Tide (GRACE-FO)'] = [\n",
+ " 'S2 Tidal Alias Sine (GRACE-FO)',\n",
+ " 'S2 Tidal Alias Cosine (GRACE-FO)',\n",
+ "]\n",
"\n",
"# combined strings for all components\n",
- "variable_longname.extend([i for k,v in cyclic_longname.items() for i in v if cyclicCheckbox[k].value])\n",
- "unit_suffix.extend(['' for k,v in cyclic_longname.items() for i in v if cyclicCheckbox[k].value])\n",
- "variable_longname.extend([i for k,v in terms_longname.items() for i in v if termsCheckbox.value])\n",
- "unit_suffix.extend(['' for k,v in terms_longname.items() for i in v if termsCheckbox.value])\n",
+ "variable_longname.extend(\n",
+ " [\n",
+ " i\n",
+ " for k, v in cyclic_longname.items()\n",
+ " for i in v\n",
+ " if cyclicCheckbox[k].value\n",
+ " ]\n",
+ ")\n",
+ "unit_suffix.extend(\n",
+ " [\n",
+ " ''\n",
+ " for k, v in cyclic_longname.items()\n",
+ " for i in v\n",
+ " if cyclicCheckbox[k].value\n",
+ " ]\n",
+ ")\n",
+ "variable_longname.extend(\n",
+ " [i for k, v in terms_longname.items() for i in v if termsCheckbox.value]\n",
+ ")\n",
+ "unit_suffix.extend(\n",
+ " ['' for k, v in terms_longname.items() for i in v if termsCheckbox.value]\n",
+ ")\n",
"\n",
"# variable of interest\n",
"variableDropdown = ipywidgets.Dropdown(\n",
@@ -775,25 +888,29 @@
"\n",
"# slider for the plot min and max for normalization\n",
"i = variableDropdown.index\n",
- "vmin = np.min(out.data[:,:,i]).astype(np.int64)\n",
- "vmax = np.ceil(np.max(out.data[:,:,i])).astype(np.int64)\n",
+ "vmin = np.min(out.data[:, :, i]).astype(np.int64)\n",
+ "vmax = np.ceil(np.max(out.data[:, :, i])).astype(np.int64)\n",
"cmap2 = gravtk.tools.colormap(vmin=vmin, vmax=vmax)\n",
"\n",
+ "\n",
"# set range and step size for variable\n",
"def set_range_and_step(sender):\n",
" i = variableDropdown.index\n",
- " cmin = np.min(out.data[:,:,i]).astype(np.int64)\n",
- " cmax = np.ceil(np.max(out.data[:,:,i])).astype(np.int64)\n",
+ " cmin = np.min(out.data[:, :, i]).astype(np.int64)\n",
+ " cmax = np.ceil(np.max(out.data[:, :, i])).astype(np.int64)\n",
" cmap2.range.min = cmin\n",
" cmap2.range.max = cmax\n",
- " cmap2.range.value = [cmin,cmax]\n",
+ " cmap2.range.value = [cmin, cmax]\n",
" cmap2.step.max = cmax - cmin\n",
"\n",
+ "\n",
"# watch variable widget for changes\n",
"variableDropdown.observe(set_range_and_step)\n",
"\n",
"# display widgets for setting GRACE/GRACE-FO regression plot parameters\n",
- "ipywidgets.VBox([variableDropdown,cmap2.range,cmap2.step,cmap2.name,cmap2.reverse])"
+ "ipywidgets.VBox(\n",
+ " [variableDropdown, cmap2.range, cmap2.step, cmap2.name, cmap2.reverse]\n",
+ ")"
]
},
{
@@ -810,14 +927,25 @@
"metadata": {},
"outputs": [],
"source": [
- "fig, ax2 = plt.subplots(num=2, nrows=1, ncols=1, figsize=(10.375,6.625),\n",
- " subplot_kw=dict(projection=ccrs.PlateCarree()))\n",
+ "fig, ax2 = plt.subplots(\n",
+ " num=2,\n",
+ " nrows=1,\n",
+ " ncols=1,\n",
+ " figsize=(10.375, 6.625),\n",
+ " subplot_kw=dict(projection=ccrs.PlateCarree()),\n",
+ ")\n",
"\n",
"# levels and normalization for plot range\n",
"i = variableDropdown.index\n",
- "im = ax2.imshow(out.data[:,:,i], interpolation='nearest',\n",
- " norm=cmap2.norm, cmap=cmap2.value, transform=ccrs.PlateCarree(),\n",
- " extent=grid.extent, origin='upper')\n",
+ "im = ax2.imshow(\n",
+ " out.data[:, :, i],\n",
+ " interpolation='nearest',\n",
+ " norm=cmap2.norm,\n",
+ " cmap=cmap2.value,\n",
+ " transform=ccrs.PlateCarree(),\n",
+ " extent=grid.extent,\n",
+ " origin='upper',\n",
+ ")\n",
"ax2.coastlines('50m')\n",
"\n",
"# Add horizontal colorbar and adjust size\n",
@@ -826,27 +954,36 @@
"# pad = distance from main plot axis\n",
"# shrink = percent size of colorbar\n",
"# aspect = lengthXwidth aspect of colorbar\n",
- "cbar = plt.colorbar(im, ax=ax2, extend='both', extendfrac=0.0375,\n",
- " orientation='horizontal', pad=0.025, shrink=0.85,\n",
- " aspect=22, drawedges=False)\n",
+ "cbar = plt.colorbar(\n",
+ " im,\n",
+ " ax=ax2,\n",
+ " extend='both',\n",
+ " extendfrac=0.0375,\n",
+ " orientation='horizontal',\n",
+ " pad=0.025,\n",
+ " shrink=0.85,\n",
+ " aspect=22,\n",
+ " drawedges=False,\n",
+ ")\n",
"# rasterized colorbar to remove lines\n",
"cbar.solids.set_rasterized(True)\n",
"# Add label to the colorbar\n",
- "lbl = f'{unit_name[UNITS-1]} [{unit_label[UNITS-1]}{unit_suffix[i]}]'\n",
+ "lbl = f'{unit_name[UNITS - 1]} [{unit_label[UNITS - 1]}{unit_suffix[i]}]'\n",
"cbar.ax.set_xlabel(lbl, labelpad=10, fontsize=24)\n",
"# Set the tick levels for the colorbar\n",
"cbar.set_ticks(cmap2.levels)\n",
"cbar.set_ticklabels(cmap2.label)\n",
"# ticks lines all the way across\n",
- "cbar.ax.tick_params(which='both', width=1, length=26, labelsize=24,\n",
- " direction='in')\n",
- " \n",
+ "cbar.ax.tick_params(\n",
+ " which='both', width=1, length=26, labelsize=24, direction='in'\n",
+ ")\n",
+ "\n",
"# stronger linewidth on frame\n",
"ax2.spines['geo'].set_linewidth(2.0)\n",
"ax2.spines['geo'].set_capstyle('projecting')\n",
"# adjust subplot within figure\n",
"fig.patch.set_facecolor('white')\n",
- "fig.subplots_adjust(left=0.02,right=0.98,bottom=0.05,top=0.98)\n",
+ "fig.subplots_adjust(left=0.02, right=0.98, bottom=0.05, top=0.98)\n",
"plt.show()"
]
}
diff --git a/doc/source/project/Bibliography.rst b/doc/source/project/Bibliography.rst
index 73b0782c..52774b51 100644
--- a/doc/source/project/Bibliography.rst
+++ b/doc/source/project/Bibliography.rst
@@ -1,3 +1,5 @@
+.. _bibliography:
+
============
Bibliography
============
diff --git a/doc/source/project/Citations.rst b/doc/source/project/Citations.rst
index 6fe33a86..22c21973 100644
--- a/doc/source/project/Citations.rst
+++ b/doc/source/project/Citations.rst
@@ -38,18 +38,33 @@ Dependencies
This software is also dependent on other commonly used Python packages:
-- `cartopy: Python package designed for geospatial data processing `_
+- `boto3: Amazon Web Services (AWS) SDK for Python `_
- `future: Compatibility layer between Python 2 and Python 3 `_
-- `h5py: Python interface for Hierarchal Data Format 5 (HDF5) `_
-- `ipywidgets: interactive HTML widgets for Jupyter notebooks and IPython `_
- `lxml: processing XML and HTML in Python `_
- `matplotlib: Python 2D plotting library `_
- `netCDF4: Python interface to the netCDF C library `_
- `numpy: Scientific Computing Tools For Python `_
+- `platformdirs: Python module for determining platform-specific directories `_
- `python-dateutil: powerful extensions to datetime `_
- `PyYAML: YAML parser and emitter for Python `_
- `scipy: Scientific Tools for Python `_
+
+Optional Dependencies
+---------------------
+
+- `cartopy: Python package designed for geospatial data processing `_
+- `dask: Parallel computing with task scheduling `_
+- `geoid-toolkit: Python utilities for calculating geoid heights from static gravity field coefficients `_
+- `gdal: Pythonic interface to the Geospatial Data Abstraction Library (GDAL) `_
+- `h5py: Python interface for Hierarchal Data Format 5 (HDF5) `_
+- `ipywidgets: interactive HTML widgets for Jupyter notebooks and IPython `_
+- `obstore: Simple, high-throughput Python interface for object storage `_
+- `pyarrow: Apache Arrow Python bindings `_
+- `pyshp: Python read/write support for ESRI Shapefile format `_
+- `s3fs: Pythonic file interface to S3 built on top of botocore `_
+- `shapely: PostGIS-ish operations outside a database context for Python `_
- `tkinter: Python interface to the Tcl/Tk GUI toolkit `_
+- `zarr: Chunked, compressed, N-dimensional arrays in Python `_
Disclaimer
##########
diff --git a/doc/source/release_notes/Release-Notes.rst b/doc/source/release_notes/Release-Notes.rst
new file mode 100644
index 00000000..ae4d55e3
--- /dev/null
+++ b/doc/source/release_notes/Release-Notes.rst
@@ -0,0 +1,10 @@
+=============
+Release Notes
+=============
+
+.. toctree::
+ :maxdepth: 1
+ :glob:
+ :reversed:
+
+ *
diff --git a/doc/source/release_notes/release-v1.0.2.0.rst b/doc/source/release_notes/release-v1.0.2.0.rst
new file mode 100644
index 00000000..3a53fe36
--- /dev/null
+++ b/doc/source/release_notes/release-v1.0.2.0.rst
@@ -0,0 +1,221 @@
+.. _release-v1.0.2.0:
+
+=====================
+`Release v1.0.2.0`__
+=====================
+
+* ``docs``: Update documentation to use sphinx and readthedocs
+* ``docs``: update ``readthedocs.yml`` to use conda
+* ``fix``: update ``environment.yml`` to include pip
+* ``docs``: different environment for docs
+* ``feat``: add AOD1B oblateness and isomorphic parameters
+* ``feat``: add netCDF4 and HDF5 options to read_GIA_model.py
+* ``feat``: add GIA step to getting started
+* ``feat``: added ``harmonics`` class for correcting GRACE/GRACE-FO data
+* ``feat``: add ``mean`` and ``from_dict`` to ``harmonics`` class
+* ``feat``: use ``harmonics`` class to ``index``, ``add``, ``subtract``, ``convolve`` and ``destripe``
+* ``refactor``: separate ``units`` into its own class
+* ``feat``: ``units`` factors for ``harmonics`` and ``spatial``
+* ``docs``: add header notes to notebook describing GRACE/GRACE-FO measurements
+* ``fix``: podaac program default release to RL06
+* ``fix``: enumeration in ``GRACE-Data-File-Formats.md``
+* ``feat``: add gravity model read from GFZ ICGEM
+* ``feat``: add more functionality and mathematical functions to ``harmonics`` class
+* ``feat``: check dimensions of ``harmonics`` objects if using mathematics functions
+* ``feat``: add viscoelastic crustal uplift to ``units`` class for converting GIA rates
+* ``docs``: include source code links to documents
+* ``fix``: include degree and order in read GIA program
+* ``docs``: include level-2 handbooks and processing standard documents
+* ``docs``: include notes about pole tide and atmospheric corrections
+* ``feat``: add ocean redistribution to ipynb, add spherical harmonic calculations
+* ``feat``: add ocean redistribution to ipynb, add spherical harmonic calculations
+* ``feat``: add date option to ncdf/hdf5 write programs
+* ``fix``: in ``harmonics`` ``from_list`` separate date and sort
+* ``feat``: get ``harmonics`` third dimension from shape
+* ``feat``: add options to ``flatten`` and ``expand`` ``harmonics`` matrices or arrays
+* ``docs``: updated ``README.md`` to have data repositories
+* ``docs``: add more notes about spatial units and conversion from harmonics
+* ``feat``: include file of load love numbers `(Han and Wahr, 1995) `_
+* ``docs``: update html link to `Martin Mohlenkamp's uguide `_
+* ``docs``: add link to love numbers to getting started doc
+* ``feat``: add more legendre and harmonics programs
+* ``docs``: updated readme and documentation
+* ``docs``: update html links to https
+* ``feat``: add webdav program for retrieving PO.DAAC credentials
+* ``feat``: add ``netrc`` option to PO.DAAC sync program
+* ``feat``: ``netrc`` file can be appended from webdav program
+* ``docs``: updated README, getting started and program documentation
+* ``feat``: add additional date and conversion programs
+* ``feat``: output list of filenames if using ``from_list()`` in ``harmonics``
+* ``fix``: subset and index can output the harmonics filename if set
+* ``fix``: increase timeout to 2 minutes in ``podaac_grace_sync.py``
+* ``docs``: Add install with pip from git to readme
+* ``docs``: update readme to note CC4 license for non-code content
+* ``feat``: Add ``spatial`` class for reading, writing and processing grids
+* ``feat``: additional capablities within ``spatial`` class
+* ``docs``: update ``spatial`` class documentation
+* ``refactor``: reorganize code structure
+* ``docs``: update documentation for new code structure
+* ``fix``: use dependencies from ``requirements.txt`` in ``setup.py``
+* ``fix``: include files within scripts directory in ``setup.py``
+* ``feat``: add CLI spatial and regression programs
+* ``feat``: add ``zeros_like`` to ``harmonics`` class
+* ``docs``: update documentation for added modules
+* ``docs``: update readme for added modules
+* ``feat``: add GRACE spatial error program
+* ``docs``: update documentation and readme for changes
+* ``feat``: update setup to mark new version
+* ``docs``: add note about John to citations
+* ``docs``: add function docstrings
+* ``feat``: add back level-1b dealiasing sync programs
+* ``docs``: update documentation and readme
+* ``feat``: add case insensitive file search
+* ``feat``: update mask if no ``fill_value`` in ``spatial``
+* ``feat``: update jupyter notebook to use ``spatial`` class in plot
+* ``ci``: add github actions for continuous integration
+* ``ci``: ``flake8`` linter updates for CI
+* ``fix``: add ``scipy`` to ``environment.yml`` and ``requirements.txt``
+* ``feat``: add github dependency for ``geocenter`` to requirements
+* ``fix``: remove dependency links in lieu of requirements
+* ``ci``: add github actions for continuous integration
+* ``test``: add spherical harmonic conversion test
+* ``fix``: update regular expressions for ``flake8`` compat
+* ``test``: add spherical harmonic conversion test
+* ``test``: add test for downloading and reading GRACE data
+* ``refactor``: move build opener to ``utilities`` routines
+* ``docs``: update readme and documentation for utilities
+* ``test``: add more tests for downloading and reading GRACE data
+* ``feat``: use ``podaac_list()`` within ``podaac_grace_sync`` program
+* ``feat``: ``read_GRACE_harmonics()`` can read ``bytesIO`` objects
+* ``feat``: added GFZ ftp download and read test
+* ``feat``: added compression options to ``harmonic`` and ``spatial`` file input
+* ``fix``: ``flake8`` updates for ``python3``
+* ``fix``: update legendre polynomial programs for divide by zero in differentials
+* ``fix``: add ``KeyError`` to ``from_dict``
+* ``fix``: ``flake8`` updates for python3
+* ``feat``: use ``utilities`` to define path to load love numbers file
+* ``feat``: include data in package
+* ``feat``: Update ``MANIFEST.in`` for included data
+* ``ci``: add ``macos-latest`` to testing strategy
+* ``ci``: will use homebrew package manager to install dependencies
+* ``feat``: add podaac sync within jupyter notebook with magics
+* ``refactor``: reorganize base directory: .binder and notebooks
+* ``test``: calculate test coverage
+* ``test``: upload coverage file in github actions
+* ``feat``: include GSFC GRACE mascons in dates
+* ``fix``: update python language support
+* ``fix``: use ``urllib`` from ``gravity_toolkit`` ``utilities``
+* ``feat``: generalize build opener for different earthdata instances
+* ``refactor``: switching to main branch as primary
+* ``chore``: update links to main branch in readme and docs
+* ``feat``: use ``argparse`` to set parameters
+* ``feat``: ``abspath`` and ``expanduser`` in ``argparse`` paths
+* ``feat``: add ``spatial`` ascii header option
+* ``fix``: update ``spatial`` ``mean`` to catch more exceptions
+* ``feat``: add more routines to ``spatial`` class `(#17) `_
+* ``refactor``: update podaac programs to simplify args `(#17) `_
+* ``feat``: add updated CNES sync program `(#18) `_
+* ``feat``: add GFZ ICGEM list for static models `(#18) `_
+* ``docs``: update documentation `(#18) `_
+* ``feat``: added more love number options and from gfc for mean files `(#19) `_
+* ``feat``: add `Sutterley and Velicogna geocenter `_ download `(#19) `_
+* ``feat``: updated SLR geocenter for new solutions from Minkang Cheng `(#19) `_
+* ``feat``: added download for satellite laser ranging (SLR) files from UTCSR `(#19) `_
+* ``feat``: add first public versions of mascon programs `(#20) `_
+* ``docs``: add documentation outlining programs `(#20) `_
+* ``docs``: add documentation outlining grace/grace-fo processing `(#20) `_
+* ``docs``: add blurbs to add Yara's comments `(#20) `_
+* ``docs``: use restructuredtext for background `(#20) `_
+* ``refactor``: generalize utilities for downloading from JPL drive (PO.DAAC/ECCO) `(#21) `_
+* ``feat``: can calculate means (``spatial`` and ``harmonic``) for a subset `(#21) `_
+* ``feat``: add pressure harmonics routines for OBP/surface pressure `(#21) `_
+* ``fix``: update requirements `(#21) `_
+* ``feat``: added ``time`` module to be able to convert delta times `(#22) `_
+* ``refactor``: merged ``convert_calendar_decimal`` and ``convert_julian`` with ``time`` module `(#22) `_
+* ``feat``: update netCDF4 and HDF5 programs for attributes and references `(#22) `_
+* ``docs``: update documentation `(#22) `_
+* ``test``: add test module for time programs `(#22) `_
+* ``feat``: update netCDF and HDF5 programs to read from memory `(#23) `_
+* ``feat``: update ``ftp_list`` and read for protected ftp `(#23) `_
+* ``test``: add ftp connection check `(#23) `_
+* ``refactor``: update ftp programs to use ``utilities`` `(#24) `_
+* ``feat``: add even rounding utility `(#24) `_
+* ``refactor``: moved pressure harmonics function to ``model_harmonics`` `(#24) `_
+* ``feat``: use ``harmonics`` class as output from SH generators `(#25) `_
+* ``feat``: add piecewise regression routine for breakpoint analysis `(#26) `_
+* ``docs``: add harmonic triangle plot notebook
+* ``docs``: add regression plots to spatial map notebook
+* ``docs``: use ``sphinx_rtd_theme`` for documentation
+* ``docs``: change some markdown docs to rst
+* ``feat``: add date parser for cases when only a date and no units
+* ``docs``: add badges to examples documentation
+* ``feat``: add kfactor calculation program to ``spatial`` class
+* ``feat``: add degree amplitude function to ``harmonics`` class
+* ``fix``: prevent warnings with python3 compatible regex strings in nc/hdf5 read
+* ``test``: add point mass test
+* ``fix``: modify legendre case with underflow
+* ``docs``: update references in point harmonics programs
+* ``feat``: added ``replace_masked`` to replace masked values in ``spatial`` data
+* ``fix``: in ``spatial`` broadcast mask over third dimension
+* ``refactor``: changed remove index to files with specified formats `(#27) `_
+* ``feat``: added generic reader, generic writer and write to list functions `(#27) `_
+* ``feat``: added ``adjust_months`` function to fix "special" months cases `(#27) `_
+* ``feat``: include geocenter read program for coefficents from Sean `(#27) `_
+* ``fix``: replaced ``numpy`` bool to prevent deprecation warning `(#27) `_
+* ``refactor``: generalize kwargs to ascii, netCDF4 and HDF5 readers and writers
+* ``refactor``: moved model mascon programs to ``model_harmonics``
+* ``refactor``: merged read ICGEM harmonics with ``geoid_toolkit`` reader
+* ``docs``: add contribution guidelines `(#28) `_
+* ``docs``: more documentation standardization `(#28) `_
+* ``ci``: remove python 3.5 from tests `(#28) `_
+* ``docs``: documentation standardization
+* ``docs``: update documentation `(#29) `_
+* ``feat``: set a default netrc file and check access `(#29) `_
+* ``feat``: default credentials from environmental variables `(#29) `_
+* ``docs``: add rst format citations to documentation `(#30) `_
+* ``fix``: update CSR SLR function (thanks @hulecom for pointing out the file format change) `(#30) `_
+* ``fix``: update setup file to check if ``readthedocs`` `(#30) `_
+* ``feat``: adding more SLR low-degree replacements `(#31) `_
+* ``docs``: update documentation for SLR harmonics `(#31) `_
+* ``feat``: add parser object for removing commented or empty lines `(#32) `_
+* ``feat``: add GFZ SLR solutions for C20/C21+S21/C30 `(#33) `_
+* ``feat``: add GFZ GravIS geocenter solutions `(#33) `_
+* ``docs``: update documentation for GFZ solutions `(#33) `_
+* ``fix``: update grace input months for GFZ SLR `(#33) `_
+* ``feat``: added option for connection timeout to sync programs `(#34) `_
+* ``fix``: define int/float precision to prevent deprecation warning `(#35) `_
+* ``feat``: use try/except for retrieving netrc credentials `(#35) `_
+* ``feat``: add figshare secure FTP uploader to utilities `(#35) `_
+* ``ci``: use cartopy no-binary in build `(#35) `_
+* ``fix``: use first value in requirements in setup `(#35) `_
+* ``ci``: brew install ``pkg-config`` `(#35) `_
+* ``ci``: use older proj7 in brew install for cartopy `(#35) `_
+* ``ci``: add LD and CPP flags for proj7 `(#35) `_
+* ``ci``: add cython to installations `(#35) `_
+* ``ci``: ``ACCEPT_USE_OF_DEPRECATED_PROJ_API_H`` `(#35) `_
+* ``ci``: set pkg-config path `(#35) `_
+* ``refactor``: switch from parameter files to argparse arguments `(#36) `_
+* ``fix``: degree spacing in spatial programs `(#36) `_
+* ``fix``: cycles in regression program `(#38) `_
+* ``fix``: documentation for spatial programs `(#38) `_
+* ``refactor``: simplified file exports using wrappers in harmonics `(#39) `_
+* ``fix``: gfc format in ``from_file`` wrapper in harmonics `(#39) `_
+* ``fix``: format for mean files `(#40) `_
+* ``docs``: clenshaw summation citations `(#40) `_
+* ``feat``: Add 3-hour AOD interval for RL06 `#37 `_ `(#41) `_
+* ``feat``: release monthly dealiasing (for CSR GAA etc) `(#41) `_
+* ``fix``: inputs to AOD-corrected SLR geocenter coefficients `(#41) `_
+* ``feat``: output index file for monthly dealiasing SHM files `(#42) `_
+* ``feat``: added check if needing to interpolate love numbers `(#42) `_
+* ``feat``: added path to default land-sea mask for mass redistribution `(#42) `_
+* ``feat``: added option to output mean harmonics in gfc format `(#43) `_
+* ``refactor``: rename monthly mean dealiasing program `(#43) `_
+* ``feat``: output uncalibrated spherical harmonic errors (eclm and eslm) `(#43) `_
+* ``fix``: remove choices for argparse processing centers `(#43) `_
+* ``fix``: remove defaults in monthly dealiasing `(#43) `_
+* ``refactor``: no default processing center `(#43) `_
+* ``fix``: require processing center argument `(#43) `_
+* ``feat``: add months option to gfz dealiasing sync `(#43) `_
+* ``feat``: add harmonic resolution calculator `(#44) `_
+
+.. __: https://github.com/tsutterley/gravity-toolkit/releases/tag/v1.0.2.0
diff --git a/doc/source/release_notes/release-v1.0.2.4.rst b/doc/source/release_notes/release-v1.0.2.4.rst
new file mode 100644
index 00000000..831cd9fc
--- /dev/null
+++ b/doc/source/release_notes/release-v1.0.2.4.rst
@@ -0,0 +1,62 @@
+.. _release-v1.0.2.4:
+
+=====================
+`Release v1.0.2.4`__
+=====================
+
+* ``fix``: uncertainties for SLR CS21 and CS22
+* ``feat``: add option for setting input format of the mascon files `(#46) `_
+* ``feat``: add averaging kernel program `(#46) `_
+* ``feat``: time-variable gravity data from COST-G, GRAZ, SWARM `#37 `_ `(#47) `_
+* ``feat``: time-variable gravity data from COST-G GRAZ, SWARM `#37 `_ `(#47) `_
+* ``feat``: need to add sync programs for all products `(#47) `_
+* ``refactor``: call read ICGEM from read gfc `(#47) `_
+* ``fix``: Swarm strings and titles `(#47) `_
+* ``docs``: update documentation to add additional information `(#47) `_
+* ``feat``: add sync programs for Swarm and GRACE COST-G `(#47) `_
+* ``feat``: add ITSG GRAZ GRACE sync `(#47) `_
+* ``fix``: accidental copy paste `(#47) `_
+* ``fix``: output index in separate loop for COST-G `(#47) `_
+* ``test``: add tests for COST-G, GRAZ and Swarm gfc files `(#47) `_
+* ``test``: use fixture to download geocenter files `(#47) `_
+* ``refactor``: merged integration and fourier harmonics programs ``fix``: use fill values for input ascii files in convert_harmonics ``fix``: update grid attributes after allocating for data in combine_harmonics ``ci``: install proj from source for cartopy dependency ``ci``: install devel cartopy from repo `(#48) `_
+* ``refactor``: adding new tools module to simplify notebooks `(#49) `_
+* ``feat``: adding new time functions for to/from grace months `(#49) `_
+* ``docs``: add documentation for tools `(#49) `_
+* ``fix``: adjust months if final in time series `(#49) `_
+* ``fix``: adjustable minimum in gaussian weights
+* ``refactor``: slim requirements `(#50) `_
+* ``refactor``: using python logging for handling verbose output `(#51) `_
+* ``feat``: add version program and add package __version__ attribute `(#51) `_
+* ``feat``: add more remove file options in GRACE maps `(#52) `_
+* ``fix``: remove file choices in calc mascon `(#52) `_
+* ``fix``: logging ``CRITICAL``
+* ``fix``: logging with filenames
+* ``feat``: grid conversion routines for publicly available mascon solutions `(#53) `_
+* ``test``: attempt to download cost-g from http `(#53) `_
+* ``refactor``: use logging to print multiprocessing exceptions
+* ``refactor``: netCDF4 and HDF5 input programs `(#54) `_
+* ``ci``: update for geoid-toolkit dependencies `(#54) `_
+* ``docs``: pin docutils to 0.18 `(#54) `_
+* ``fix``: modify legendre normalization to prevent high degree overflows `(#55) `_
+* ``test``: add legendre test `(#55) `_
+* ``test``: add associated legendre polynomial test `(#56) `_
+* ``fix``: HDF5 and netCDF4 io for single time case `(#56) `_
+* ``fix``: format for index in notebooks `(#56) `_
+* ``feat``: use new GSFC weekly 5x5s for CS2 and C50 `(#57) `_
+* ``refactor``: create geocenter program for reading and operating `(#58) `_
+* ``test``: include new geocenter class `(#58) `_
+* ``feat``: add load love numbers for unit conversion `(#58) `_
+* ``feat``: add mean function to geocenter `(#58) `_
+* ``refactor``: rename SLF geocenter reader `(#58) `_
+* ``feat``: add more geocenter operations `(#58) `_
+* ``feat``: add geocenter figure programs from Sutterley and Velicogna (2019)
+* ``feat``: update geocenter plot programs for AGU `(#60) `_
+* ``feat``: added netCDF4 reader for UCI iteration files `(#60) `_
+* ``feat``: added custom colormap function for some common scales `(#60) `_
+* ``feat``: added UNITS list option for converting from custom units `(#60) `_
+* ``feat``: option to specify a specific geocenter correction file `(#61) `_
+* ``feat``: can use variable loglevels for verbose output `(#61) `_
+* ``fix``: fix default file prefix to include center and release information `(#61) `_
+
+.. __: https://github.com/tsutterley/gravity-toolkit/releases/tag/v1.0.2.4
diff --git a/doc/source/release_notes/release-v1.0.2.5.rst b/doc/source/release_notes/release-v1.0.2.5.rst
new file mode 100644
index 00000000..c08a4496
--- /dev/null
+++ b/doc/source/release_notes/release-v1.0.2.5.rst
@@ -0,0 +1,38 @@
+.. _release-v1.0.2.5:
+
+=====================
+`Release v1.0.2.5`__
+=====================
+
+* ``fix``: add try/except for read_GRACE_geocenter import
+* ``feat``: S3 access using PO.DAAC cumulus to address `#59 `_ `(#63) `_
+* ``fix``: update readable granule for L1A/B grav
+* ``fix``: for now use podaac drive provider
+* ``docs``: updated docstrings to numpy documentation format `(#65) `_
+* ``docs``: use autodoc to build documentation `(#65) `_
+* ``feat``: new internal ncdf/hdf5 read/write within harmonics and spatial classes `(#65) `_
+* ``feat``: add citations and references to read_GIA_model `(#65) `_
+* ``docs``: update headers to remove deprecated ncdf/hdf5 read/write modules `(#65) `_
+* ``refactor``: moved Load love number wrapper function to within read `(#66) `_
+* ``feat``: change badge for pangeo to aws us-west-2 for podaac cloud access `(#66) `_
+* ``fix``: pin markupsafe to 2.0.1 to prevent soft_unicode error `(#66) `_
+* ``fix``: change function name back to load_love_numbers `(#67) `_
+* ``docs``: update history in headers `(#67) `_
+* ``test``: try docker build with windows to address `#64 `_ `(#68) `_
+* ``fix``: include utf-8 encoding in reads to be windows compliant `(#68) `_
+* ``fix``: only sync newsletters for mission of interest
+* ``fix``: spatial field mapping for output
+* ``fix``: mask in sea level equation
+* ``feat``: add from_GIA to harmonics class `(#69) `_
+* ``feat``: prepare CMR queries for version 1 of RL06 `(#69) `_
+* ``fix``: expansion and squeezing of mask variable if None `(#69) `_
+* ``feat``: add option for L2 version in sync programs `(#69) `_
+* ``fix``: improved passing of filename attribute in harmonic objects `(#70) `_
+* ``fix``: include filename when copying spatial objects `(#70) `_
+* ``refactor``: always try syncing from both grace and grace-fo missions `(#71) `_
+* ``feat``: added AW13 models using IJ05-R2 ice history `(#71) `_
+* ``feat``: allow input ascii harmonic files to have additional columns `(#71) `_
+* ``docs``: update environment file `(#71) `_
+* ``fix``: index using granules `(#71) `_
+
+.. __: https://github.com/tsutterley/gravity-toolkit/releases/tag/v1.0.2.5
diff --git a/doc/source/release_notes/release-v1.0.2.6.rst b/doc/source/release_notes/release-v1.0.2.6.rst
new file mode 100644
index 00000000..3609aeee
--- /dev/null
+++ b/doc/source/release_notes/release-v1.0.2.6.rst
@@ -0,0 +1,14 @@
+.. _release-v1.0.2.6:
+
+====================
+`Release v1.0.2.6`__
+====================
+
+* ``docs``: use argparse descriptions within sphinx documentation `(#72) `_
+* ``feat``: initial version of public geocenter programs `(#73) `_
+* ``feat``: output full citation for each GIA model group `(#73) `_
+* ``docs``: update background and add geocenter section `(#73) `_
+* ``feat``: added notebook for visualizing harmonic errors `(#74) `_
+* ``feat``: add changes for uploading to pypi `(#75) `_
+
+.. __: https://github.com/tsutterley/gravity-toolkit/releases/tag/1.0.2.6
diff --git a/doc/source/release_notes/release-v1.0.2.7.rst b/doc/source/release_notes/release-v1.0.2.7.rst
new file mode 100644
index 00000000..aaa4509f
--- /dev/null
+++ b/doc/source/release_notes/release-v1.0.2.7.rst
@@ -0,0 +1,27 @@
+.. _release-v1.0.2.7:
+
+====================
+`Release v1.0.2.7`__
+====================
+
+* ``fix``: expand mask variable within if statement `(#76) `_
+* ``fix``: place ipython and tkinter imports within try/except `(#77) `_
+* ``fix``: remove cartopy for slimmer build `(#78) `_
+* ``fix``: create mask for output gridded variables `(#79) `_
+* ``refactor``: set plot tick formatter to not use offsets `(#79) `_
+* ``docs``: updated structure of documentation `(#80) `_
+* ``feat``: add program for overwriting the GRACE/GRACE-FO index `(#81) `_
+* ``feat``: made creating the spatial sensitivity kernel optional to improve compute time `(#81) `_
+* ``refactor``: moved regular expression function to utilities `(#81) `_
+* ``feat``: Dynamically select newest version of granules for index `(#82) `_
+* ``feat``: include geocenter land sea in data `(#83) `_
+* ``feat``: add GSFC SLR 5x5 download function `(#85) `_
+* ``feat``: add logging for debugging level verbose output `(#85) `_
+* ``fix``: place index filename within try/except statement `(#85)