diff --git a/.github/ISSUE_TEMPLATE/4-release_checklist.md b/.github/ISSUE_TEMPLATE/4-release_checklist.md
index 59919b8fb25..87a164b15c8 100644
--- a/.github/ISSUE_TEMPLATE/4-release_checklist.md
+++ b/.github/ISSUE_TEMPLATE/4-release_checklist.md
@@ -1,6 +1,6 @@
---
name: PyGMT release checklist
-about: Checklist for a new PyGMT release.
+about: Checklist for a new PyGMT release. [For project maintainers only!]
title: Release PyGMT vX.Y.Z
labels: maintenance
assignees: ''
@@ -19,17 +19,15 @@ assignees: ''
**Before release**:
-- [ ] Check [SPEC 0](https://scientific-python.org/specs/spec-0000/) to see if we need to bump the minimum supported versions of GMT, Python and
- core package dependencies (NumPy, pandas, Xarray)
+- [ ] Check [SPEC 0](https://scientific-python.org/specs/spec-0000/) to see if we need to bump the minimum supported versions of GMT, Python and core package dependencies (NumPy, pandas, Xarray)
- [ ] Review the ["PyGMT Team" page](https://www.pygmt.org/dev/team.html)
+- [ ] README looks good on TestPyPI. Visit [TestPyPI](https://test.pypi.org/project/pygmt/#history), click the latest pre-release, and check the homepage.
- [ ] Check to ensure that:
- - [ ] Deprecations and related tests are removed for this version by running `grep --include="*.py" -r vX.Y.Z` from the base of the repository
+ - [ ] Deprecated workarounds/codes/tests are removed. Run `grep "# TODO" **/*.py` to find all potential TODOs.
- [ ] All tests pass in the ["GMT Legacy Tests" workflow](https://github.com/GenericMappingTools/pygmt/actions/workflows/ci_tests_legacy.yaml)
- [ ] All tests pass in the ["GMT Dev Tests" workflow](https://github.com/GenericMappingTools/pygmt/actions/workflows/ci_tests_dev.yaml)
- [ ] All tests pass in the ["Doctests" workflow](https://github.com/GenericMappingTools/pygmt/actions/workflows/ci_doctests.yaml)
-- [ ] Update warnings in `pygmt/_show_versions.py` as well as notes in
- [Not working transparency](https://www.pygmt.org/dev/install.html#not-working-transparency)
- regarding GMT-Ghostscript incompatibility
+- [ ] Update warnings in `pygmt/_show_versions.py` as well as notes in [Not working transparency](https://www.pygmt.org/dev/install.html#not-working-transparency) regarding GMT-Ghostscript incompatibility
- [ ] Reserve a DOI on [Zenodo](https://zenodo.org) by clicking on "New Version"
- [ ] Finish up the "Changelog entry for v0.x.x" Pull Request (Use the previous changelog PR as a reference)
- [ ] Run `make codespell` to check common misspellings. If there are any, either fix them or add them to `ignore-words-list` in `pyproject.toml`
@@ -41,18 +39,16 @@ assignees: ''
- [ ] Edit the draft release notes with the finalized changelog
- [ ] Set the tag version and release title to vX.Y.Z
- [ ] Make a release by clicking the 'Publish Release' button, this will automatically create a tag too
-- [ ] Download pygmt-X.Y.Z.zip (rename to pygmt-vX.Y.Z.zip) and baseline-images.zip from
- the release page, and upload the two zip files to https://zenodo.org/deposit,
- ensure that they are filed under the correct reserved DOI
+- [ ] Download pygmt-X.Y.Z.zip (rename to pygmt-vX.Y.Z.zip) and baseline-images.zip from the release page, and upload the two zip files to https://zenodo.org/deposit, ensure that they are filed under the correct reserved DOI
**After release**:
-- [ ] Update conda-forge [pygmt-feedstock](https://github.com/conda-forge/pygmt-feedstock)
- [Done automatically by conda-forge's bot. Remember to pin Python and SPEC0 versions]
+- [ ] Update conda-forge [pygmt-feedstock](https://github.com/conda-forge/pygmt-feedstock) [Done automatically by conda-forge's bot. Remember to pin GMT, Python and SPEC0 versions]
- [ ] Bump PyGMT version on https://github.com/GenericMappingTools/try-gmt (after conda-forge update)
- [ ] Announce the release on:
- [ ] GMT [forum](https://forum.generic-mapping-tools.org/c/news/) (do this announcement first! Requires moderator status)
- - [ ] [ResearchGate](https://www.researchgate.net) (after forum announcement, add new version as research item via the **code** category, be sure to include the corresponding new Zenodo DOI)
+ - [ ] [ResearchGate](https://www.researchgate.net) (after forum announcement; download the ZIP file of the new release from the release page and add it as research item via the **code** category, be sure to include the corresponding new Zenodo DOI)
+- [ ] Update release checklist template with any additional bullet points that may have arisen during the release
---
diff --git a/.github/ISSUE_TEMPLATE/5-bump_gmt_checklist.md b/.github/ISSUE_TEMPLATE/5-bump_gmt_checklist.md
index 9652f2150ee..1ca5117ff5f 100644
--- a/.github/ISSUE_TEMPLATE/5-bump_gmt_checklist.md
+++ b/.github/ISSUE_TEMPLATE/5-bump_gmt_checklist.md
@@ -1,6 +1,6 @@
---
name: Bump GMT version checklist
-about: Checklist for bumping the minimum required GMT version.
+about: Checklist for bumping the minimum required GMT version. [For project maintainers only!]
title: Bump to GMT X.Y.Z
labels: maintenance
assignees: ''
diff --git a/.github/PULL_REQUEST_TEMPLATE.md b/.github/PULL_REQUEST_TEMPLATE.md
index 14b75d86f5a..0c259cf5672 100644
--- a/.github/PULL_REQUEST_TEMPLATE.md
+++ b/.github/PULL_REQUEST_TEMPLATE.md
@@ -20,7 +20,6 @@ Fixes #
- [ ] Write detailed docstrings for all functions/methods.
- [ ] If wrapping a new module, open a 'Wrap new GMT module' issue and submit reasonably-sized PRs.
- [ ] If adding new functionality, add an example to docstrings or tutorials.
-- [ ] Use underscores (not hyphens) in names of Python files and directories.
**Slash Commands**
diff --git a/.github/workflows/benchmarks.yml b/.github/workflows/benchmarks.yml
index 2fcc724ddae..f219ae76bdc 100644
--- a/.github/workflows/benchmarks.yml
+++ b/.github/workflows/benchmarks.yml
@@ -45,7 +45,7 @@ jobs:
# Install Micromamba with conda-forge dependencies
- name: Setup Micromamba
- uses: mamba-org/setup-micromamba@v2.0.3
+ uses: mamba-org/setup-micromamba@v2.0.4
with:
environment-name: pygmt
condarc: |
diff --git a/.github/workflows/cache_data.yaml b/.github/workflows/cache_data.yaml
index 7e12513b0e9..46c37211424 100644
--- a/.github/workflows/cache_data.yaml
+++ b/.github/workflows/cache_data.yaml
@@ -43,7 +43,7 @@ jobs:
# Install Micromamba with conda-forge dependencies
- name: Setup Micromamba
- uses: mamba-org/setup-micromamba@v2.0.3
+ uses: mamba-org/setup-micromamba@v2.0.4
with:
environment-name: pygmt
condarc: |
@@ -76,7 +76,7 @@ jobs:
# Upload the downloaded files as artifacts to GitHub
- name: Upload artifacts to GitHub
- uses: actions/upload-artifact@v4.4.3
+ uses: actions/upload-artifact@v4.6.0
with:
name: gmt-cache
include-hidden-files: true
diff --git a/.github/workflows/check-links.yml b/.github/workflows/check-links.yml
index b1f45b73df8..0ed425da8d6 100644
--- a/.github/workflows/check-links.yml
+++ b/.github/workflows/check-links.yml
@@ -35,7 +35,7 @@ jobs:
- name: Link Checker
id: lychee
- uses: lycheeverse/lychee-action@v2.1.0
+ uses: lycheeverse/lychee-action@v2.2.0
with:
fail: false # Don't fail action on broken links
output: /tmp/lychee-out.md
diff --git a/.github/workflows/ci_docs.yml b/.github/workflows/ci_docs.yml
index 749d5b42b00..b02d953c0f7 100644
--- a/.github/workflows/ci_docs.yml
+++ b/.github/workflows/ci_docs.yml
@@ -80,7 +80,7 @@ jobs:
# Install Micromamba with conda-forge dependencies
- name: Setup Micromamba
- uses: mamba-org/setup-micromamba@v2.0.3
+ uses: mamba-org/setup-micromamba@v2.0.4
with:
environment-name: pygmt
condarc: |
@@ -108,6 +108,7 @@ jobs:
make
pip
python-build
+ geodatasets
myst-nb
panel
sphinx>=6.2
@@ -162,9 +163,14 @@ jobs:
# to get the right commit hash.
message="Deploy $version from $(git rev-parse --short HEAD)"
cd deploy
- # Need to have this file so that GitHub doesn't try to run Jekyll
+ # Create some files in the root directory.
+ # .nojekyll: Need to have this file so that GitHub doesn't try to run Jekyll
touch .nojekyll
- # Delete all the files and replace with our new set
+ # CNAME: Set the custom domain name
+ echo "www.pygmt.org" > CNAME
+ # index.html: Redirect to the latest version
+ echo '' > index.html
+ # Delete all the files and replace with our new set
echo -e "\nRemoving old files from previous builds of ${version}:"
rm -rvf ${version}
echo -e "\nCopying HTML files to ${version}:"
diff --git a/.github/workflows/ci_doctests.yaml b/.github/workflows/ci_doctests.yaml
index 1d0cdac4f98..af28e6ac710 100644
--- a/.github/workflows/ci_doctests.yaml
+++ b/.github/workflows/ci_doctests.yaml
@@ -42,7 +42,7 @@ jobs:
# Install Micromamba with conda-forge dependencies
- name: Setup Micromamba
- uses: mamba-org/setup-micromamba@v2.0.3
+ uses: mamba-org/setup-micromamba@v2.0.4
with:
environment-name: pygmt
condarc: |
diff --git a/.github/workflows/ci_tests.yaml b/.github/workflows/ci_tests.yaml
index 8ed7e728c02..44416d210e0 100644
--- a/.github/workflows/ci_tests.yaml
+++ b/.github/workflows/ci_tests.yaml
@@ -113,7 +113,7 @@ jobs:
# Install Micromamba with conda-forge dependencies
- name: Setup Micromamba
- uses: mamba-org/setup-micromamba@v2.0.3
+ uses: mamba-org/setup-micromamba@v2.0.4
with:
environment-name: pygmt
condarc: |
@@ -154,20 +154,18 @@ jobs:
GH_TOKEN: ${{ github.token }}
- name: Install uv
- uses: astral-sh/setup-uv@v4.2.0
+ uses: astral-sh/setup-uv@v5.1.0
+ with:
+ python-version: ${{ matrix.python-version }}
- name: Install dvc
run: |
- uv venv
- source .venv/bin/activate
uv pip install dvc
uv pip list
# Pull baseline image data from dvc remote (DAGsHub)
- name: Pull baseline image data from dvc remote
- run: |
- source .venv/bin/activate
- uv run dvc pull --no-run-cache --verbose && ls -lhR pygmt/tests/baseline/
+ run: uv run dvc pull --no-run-cache --verbose && ls -lhR pygmt/tests/baseline/
# Install the package that we want to test
- name: Install the package
@@ -179,7 +177,7 @@ jobs:
# Upload diff images on test failure
- name: Upload diff images if any test fails
- uses: actions/upload-artifact@v4.4.3
+ uses: actions/upload-artifact@v4.6.0
if: failure()
with:
name: artifact-${{ runner.os }}-${{ matrix.python-version }}
@@ -187,7 +185,7 @@ jobs:
# Upload coverage to Codecov
- name: Upload coverage to Codecov
- uses: codecov/codecov-action@v5.1.1
+ uses: codecov/codecov-action@v5.1.2
if: success() || failure()
with:
use_oidc: true
diff --git a/.github/workflows/ci_tests_dev.yaml b/.github/workflows/ci_tests_dev.yaml
index d8259827f1d..6932a0d520b 100644
--- a/.github/workflows/ci_tests_dev.yaml
+++ b/.github/workflows/ci_tests_dev.yaml
@@ -57,7 +57,7 @@ jobs:
# Install Micromamba with conda-forge dependencies
- name: Setup Micromamba
- uses: mamba-org/setup-micromamba@v2.0.3
+ uses: mamba-org/setup-micromamba@v2.0.4
with:
environment-name: pygmt
condarc: |
@@ -187,7 +187,7 @@ jobs:
# Upload diff images on test failure
- name: Upload diff images if any test fails
- uses: actions/upload-artifact@v4.4.3
+ uses: actions/upload-artifact@v4.6.0
if: ${{ failure() }}
with:
name: artifact-GMT-${{ matrix.gmt_git_ref }}-${{ runner.os }}
diff --git a/.github/workflows/ci_tests_legacy.yaml b/.github/workflows/ci_tests_legacy.yaml
index 1af5e00cd38..0c4ae574235 100644
--- a/.github/workflows/ci_tests_legacy.yaml
+++ b/.github/workflows/ci_tests_legacy.yaml
@@ -51,7 +51,7 @@ jobs:
# Install Micromamba with conda-forge dependencies
- name: Setup Micromamba
- uses: mamba-org/setup-micromamba@v2.0.3
+ uses: mamba-org/setup-micromamba@v2.0.4
with:
environment-name: pygmt
condarc: |
diff --git a/.github/workflows/format-command.yml b/.github/workflows/format-command.yml
index ceb2604ba2b..3ca7d72d71f 100644
--- a/.github/workflows/format-command.yml
+++ b/.github/workflows/format-command.yml
@@ -11,7 +11,7 @@ jobs:
runs-on: ubuntu-latest
steps:
# Generate token from GenericMappingTools bot
- - uses: actions/create-github-app-token@v1.11.0
+ - uses: actions/create-github-app-token@v1.11.1
id: generate-token
with:
app-id: ${{ secrets.APP_ID }}
diff --git a/.github/workflows/publish-to-pypi.yml b/.github/workflows/publish-to-pypi.yml
index 2dbc12cbef1..917353ee85a 100644
--- a/.github/workflows/publish-to-pypi.yml
+++ b/.github/workflows/publish-to-pypi.yml
@@ -35,13 +35,9 @@ on:
# - main
jobs:
- publish-pypi:
- name: Publish to PyPI
+ build:
+ name: Build distribution 📦
runs-on: ubuntu-latest
- permissions:
- # This permission is mandatory for OIDC publishing
- id-token: write
- if: github.repository == 'GenericMappingTools/pygmt'
steps:
- name: Checkout
@@ -49,6 +45,7 @@ jobs:
with:
# fetch all history so that setuptools-scm works
fetch-depth: 0
+ persist-credentials: false
- name: Set up Python
uses: actions/setup-python@v5.3.0
@@ -74,11 +71,54 @@ jobs:
echo "Generated files:"
ls -lh dist/
- - name: Publish to Test PyPI
+ - name: Store the distribution packages
+ uses: actions/upload-artifact@v4.6.0
+ with:
+ name: python-package-distributions
+ path: dist/
+
+ publish-to-testpypi:
+ name: Publish Python 🐍 distribution 📦 to TestPyPI
+ if: github.repository == 'GenericMappingTools/pygmt'
+ needs:
+ - build
+ runs-on: ubuntu-latest
+ environment:
+ name: testpypi
+ url: https://test.pypi.org/project/pygmt
+ permissions:
+ id-token: write # IMPORTANT: mandatory for trusted OIDC publishing
+
+ steps:
+ - name: Download all the dists
+ uses: actions/download-artifact@v4.1.8
+ with:
+ name: python-package-distributions
+ path: dist/
+
+ - name: Publish distribution 📦 to TestPyPI
uses: pypa/gh-action-pypi-publish@v1.12.3
with:
repository-url: https://test.pypi.org/legacy/
- - name: Publish to PyPI
- if: startsWith(github.ref, 'refs/tags')
+ publish-pypi:
+ name: Publish Python 🐍 distribution 📦 to PyPI
+ if: github.repository == 'GenericMappingTools/pygmt' && startsWith(github.ref, 'refs/tags/')
+ needs:
+ - build
+ runs-on: ubuntu-latest
+ environment:
+ name: pypi
+ url: https://pypi.org/project/pygmt/
+ permissions:
+ id-token: write # IMPORTANT: mandatory for trusted OIDC publishing
+
+ steps:
+ - name: Download all the dists
+ uses: actions/download-artifact@v4.1.8
+ with:
+ name: python-package-distributions
+ path: dist/
+
+ - name: Publish distribution 📦 to PyPI
uses: pypa/gh-action-pypi-publish@v1.12.3
diff --git a/.github/workflows/style_checks.yaml b/.github/workflows/style_checks.yaml
index f15bcde0012..43fa5acd6c7 100644
--- a/.github/workflows/style_checks.yaml
+++ b/.github/workflows/style_checks.yaml
@@ -52,3 +52,15 @@ jobs:
rm output.txt
exit $nfiles
fi
+
+ - name: Ensure hyphens are not used in names of directories and Python files
+ run: |
+ git ls-files '*.py' | grep '-' > output.txt || true
+ git ls-tree -rd --name-only HEAD | grep '-' >> output.txt || true
+ nfiles=$(wc --lines output.txt | awk '{print $1}')
+ if [[ $nfiles > 0 ]]; then
+ echo "Following directories/files use hyphens in file names:"
+ cat output.txt
+ rm output.txt
+ exit $nfiles
+ fi
diff --git a/CITATION.cff b/CITATION.cff
index e6d79e2530d..a18999e8f2d 100644
--- a/CITATION.cff
+++ b/CITATION.cff
@@ -40,14 +40,14 @@ authors:
family-names: Yao
affiliation: Nanyang Technological University, Singapore
orcid: https://orcid.org/0000-0001-7036-4238
+- given-names: Jing-Hui
+ family-names: Tong
+ affiliation: National Taiwan Normal University, Taiwan
+ orcid: https://orcid.org/0009-0002-7195-3071
- given-names: Yohai
family-names: Magen
affiliation: Tel Aviv University, Israel
orcid: https://orcid.org/0000-0002-4892-4013
-- given-names: Tong
- family-names: Jing-Hui
- affiliation: National Taiwan Normal University, Taiwan
- orcid: https://orcid.org/0009-0002-7195-3071
- given-names: Kathryn
family-names: Materna
affiliation: US Geological Survey, USA
@@ -76,9 +76,9 @@ authors:
family-names: Wessel
affiliation: University of Hawaiʻi at Mānoa, USA
orcid: https://orcid.org/0000-0001-5708-7336
-date-released: 2024-09-05
-doi: 10.5281/zenodo.13679420
+date-released: 2024-12-31
+doi: 10.5281/zenodo.14535921
license: BSD-3-Clause
repository-code: https://github.com/GenericMappingTools/pygmt
type: software
-version: 0.13.0
+version: 0.14.0
diff --git a/LICENSE.txt b/LICENSE.txt
index 6411f912cea..c6c569c4bc6 100644
--- a/LICENSE.txt
+++ b/LICENSE.txt
@@ -1,4 +1,4 @@
-Copyright (c) 2017-2024 The PyGMT Developers
+Copyright (c) 2017-2025 The PyGMT Developers
All rights reserved.
Redistribution and use in source and binary forms, with or without modification,
diff --git a/README.md b/README.md
index 44f4065aeb7..ba4e47d8fdb 100644
--- a/README.md
+++ b/README.md
@@ -22,22 +22,22 @@
## Why PyGMT?
-A beautiful map is worth a thousand words. To truly understand how powerful PyGMT is, play with it online on
-[Binder](https://github.com/GenericMappingTools/try-gmt)! For a quicker introduction, check out our
-[3 minute overview](https://youtu.be/4iPnITXrxVU)!
+A beautiful map is worth a thousand words. To truly understand how powerful PyGMT is,
+play with it online on [Binder](https://github.com/GenericMappingTools/try-gmt)! For a
+quicker introduction, check out our [3 minute overview](https://youtu.be/4iPnITXrxVU)!
-Afterwards, feel free to look at our [Tutorials](https://www.pygmt.org/latest/tutorials), visit the
-[Gallery](https://www.pygmt.org/latest/gallery), and check out some
+Afterwards, feel free to look at our [Tutorials](https://www.pygmt.org/latest/tutorials),
+visit the [Gallery](https://www.pygmt.org/latest/gallery), and check out some
[external PyGMT examples](https://www.pygmt.org/latest/external_resources.html)!
-![Quick Introduction to PyGMT YouTube Video](doc/_static/scipy2022-youtube-thumbnail.jpg)
+![Quick Introduction to PyGMT YouTube Video](https://raw.githubusercontent.com/GenericMappingTools/pygmt/refs/heads/main/doc/_static/scipy2022-youtube-thumbnail.jpg)
## About
-PyGMT is a library for processing geospatial and geophysical data and making publication-quality
-maps and figures. It provides a Pythonic interface for the
-[Generic Mapping Tools (GMT)](https://github.com/GenericMappingTools/gmt), a command-line program
-widely used across the Earth, Ocean, and Planetary sciences and beyond.
+PyGMT is a library for processing geospatial and geophysical data and making
+publication-quality maps and figures. It provides a Pythonic interface for the
+[Generic Mapping Tools (GMT)](https://github.com/GenericMappingTools/gmt), a command-line
+program widely used across the Earth, Ocean, and Planetary sciences and beyond.
## Project goals
@@ -45,8 +45,9 @@ widely used across the Earth, Ocean, and Planetary sciences and beyond.
- Build a Pythonic API for GMT.
- Interface with the GMT C API directly using ctypes (no system calls).
- Support for rich display in the Jupyter notebook.
-- Integration with the [scientific Python ecosystem](https://scientific-python.org/): `numpy.ndarray` or
- `pandas.DataFrame` for data tables, `xarray.DataArray` for grids, and `geopandas.GeoDataFrame` for geographical data.
+- Integration with the [scientific Python ecosystem](https://scientific-python.org/):
+ `numpy.ndarray` or `pandas.DataFrame` for data tables, `xarray.DataArray` for grids,
+ and `geopandas.GeoDataFrame` for geographical data.
## Quickstart
@@ -69,7 +70,8 @@ For other ways to install `pygmt`, see the [full installation instructions](http
### Getting started
As a starting point, you can open a [Python interpreter](https://docs.python.org/3/tutorial/interpreter.html)
-or a [Jupyter notebook](https://docs.jupyter.org/en/latest/running.html), and try the following example:
+or a [Jupyter notebook](https://docs.jupyter.org/en/latest/running.html), and try the
+following example:
``` python
import pygmt
@@ -79,18 +81,18 @@ fig.text(position="MC", text="PyGMT", font="80p,Helvetica-Bold,red@75")
fig.show()
```
-You should see a global map with land and water masses colored in tan and lightblue, respectively. On top,
-there should be the semi-transparent text "PyGMT". For more examples, please have a look at the
-[Gallery](https://www.pygmt.org/latest/gallery/index.html) and
+You should see a global map with land and water masses colored in tan and lightblue,
+respectively. On top, there should be the semi-transparent text "PyGMT". For more examples,
+please have a look at the [Gallery](https://www.pygmt.org/latest/gallery/index.html) and
[Tutorials](https://www.pygmt.org/latest/tutorials/index.html).
## Contacting us
- Most discussion happens [on GitHub](https://github.com/GenericMappingTools/pygmt).
- Feel free to [open an issue](https://github.com/GenericMappingTools/pygmt/issues/new) or comment on any open
- issue or pull request.
-- We have a [Discourse forum](https://forum.generic-mapping-tools.org/c/questions/pygmt-q-a) where you can ask
- questions and leave comments.
+ Feel free to [open an issue](https://github.com/GenericMappingTools/pygmt/issues/new)
+ or comment on any open issue or pull request.
+- We have a [Discourse forum](https://forum.generic-mapping-tools.org/c/questions/pygmt-q-a)
+ where you can ask questions and leave comments.
## Contributing
@@ -109,30 +111,33 @@ to see how you can help and give feedback.
**We want your help.** No, really.
-There may be a little voice inside your head that is telling you that you're not ready to be an open source
-contributor; that your skills aren't nearly good enough to contribute. What could you possibly offer?
+There may be a little voice inside your head that is telling you that you're not ready
+to be an open source contributor; that your skills aren't nearly good enough to
+contribute. What could you possibly offer?
We assure you that the little voice in your head is wrong.
-**Being a contributor doesn't just mean writing code.** Equally important contributions include: writing or
-proof-reading documentation, suggesting or implementing tests, or even giving feedback about the project
-(including giving feedback about the contribution process). If you're coming to the project with fresh eyes,
-you might see the errors and assumptions that seasoned contributors have glossed over. If you can write any
-code at all, you can contribute code to open source. We are constantly trying out new skills, making mistakes,
-and learning from those mistakes. That's how we all improve and we are happy to help others learn.
+**Being a contributor doesn't just mean writing code.** Equally important contributions
+include: writing or proof-reading documentation, suggesting or implementing tests, or
+even giving feedback about the project (including giving feedback about the contribution
+process). If you're coming to the project with fresh eyes, you might see the errors and
+assumptions that seasoned contributors have glossed over. If you can write any code at
+all, you can contribute code to open source. We are constantly trying out new skills,
+making mistakes, and learning from those mistakes. That's how we all improve and we are
+happy to help others learn.
*This disclaimer was adapted from the* [MetPy project](https://github.com/Unidata/MetPy).
## Citing PyGMT
PyGMT is a community developed project. See the
-[AUTHORS.md](https://github.com/GenericMappingTools/pygmt/blob/main/AUTHORS.md) file on GitHub for a list of
-the people involved and a definition of the term "PyGMT Developers". Feel free to cite our work in your
-research using the following BibTeX:
+[AUTHORS.md](https://github.com/GenericMappingTools/pygmt/blob/main/AUTHORS.md) file
+on GitHub for a list of the people involved and a definition of the term "PyGMT Developers".
+Feel free to cite our work in your research using the following BibTeX:
```
@software{
- pygmt_2024_13679420,
+ pygmt_2024_14535921,
author = {Tian, Dongdong and
Uieda, Leonardo and
Leong, Wei Ji and
@@ -142,8 +147,8 @@ research using the following BibTeX:
Jones, Max and
Toney, Liam and
Yao, Jiayuan and
- Magen, Yohai and
Tong, Jing-Hui and
+ Magen, Yohai and
Materna, Kathryn and
Belem, Andre and
Newton, Tyler and
@@ -152,20 +157,20 @@ research using the following BibTeX:
Quinn, Jamie and
Wessel, Paul},
title = {{PyGMT: A Python interface for the Generic Mapping Tools}},
- month = sep,
+ month = dec,
year = 2024,
publisher = {Zenodo},
- version = {0.13.0},
- doi = {10.5281/zenodo.13679420},
- url = {https://doi.org/10.5281/zenodo.13679420}
+ version = {0.14.0},
+ doi = {10.5281/zenodo.14535921},
+ url = {https://doi.org/10.5281/zenodo.14535921}
}
```
To cite a specific version of PyGMT, go to our Zenodo page at
-and use the "Export to BibTeX" function there. It is also strongly recommended to cite the
-[GMT 6 paper](https://doi.org/10.1029/2019GC008515) (which PyGMT wraps around). Note that some modules
-like `dimfilter`, `surface`, and `x2sys` also have their dedicated citations. Further information for
-all these can be found at .
+and use the "Export to BibTeX" function there. It is also strongly recommended to cite
+the [GMT 6 paper](https://doi.org/10.1029/2019GC008515) (which PyGMT wraps around). Note
+that some modules like `dimfilter`, `surface`, and `x2sys` also have their dedicated
+citations. Further information for all these can be found at .
## License
diff --git a/ci/requirements/docs.yml b/ci/requirements/docs.yml
index 84be526f47f..2a6fa860961 100644
--- a/ci/requirements/docs.yml
+++ b/ci/requirements/docs.yml
@@ -23,6 +23,7 @@ dependencies:
- pip
- python-build
# Dev dependencies (building documentation)
+ - geodatasets
- myst-nb
- panel
- sphinx>=6.2
diff --git a/doc/Makefile b/doc/Makefile
index b82215c44c4..04b1c1ab549 100644
--- a/doc/Makefile
+++ b/doc/Makefile
@@ -1,14 +1,12 @@
# Makefile for Sphinx documentation
# You can set these variables from the command line.
-SPHINXOPTS = -j auto
-SPHINXBUILD = sphinx-build
+SPHINXOPTS ?= -j auto
+SPHINXBUILD ?= sphinx-build
SPHINXAUTOGEN = sphinx-autogen
+SOURCEDIR = .
BUILDDIR = _build
-# Internal variables.
-ALLSPHINXOPTS = -d $(BUILDDIR)/doctrees $(SPHINXOPTS) .
-
.PHONY: help all api html server clean
help:
@@ -28,20 +26,20 @@ api:
@echo
$(SPHINXAUTOGEN) -i -t _templates -o api/generated api/*.rst
-html: api
+html latex: api
@echo
- @echo "Building HTML files."
+ @echo "Building "$@" files."
@echo
# Set PYGMT_USE_EXTERNAL_DISPLAY to "false" to disable external display
- PYGMT_USE_EXTERNAL_DISPLAY="false" $(SPHINXBUILD) -b html $(ALLSPHINXOPTS) $(BUILDDIR)/html
+ PYGMT_USE_EXTERNAL_DISPLAY="false" $(SPHINXBUILD) -M $@ "$(SOURCEDIR)" "$(BUILDDIR)" $(SPHINXOPTS)
@echo
- @echo "Build finished. The HTML pages are in $(BUILDDIR)/html."
+ @echo "Build finished. The files are in $(BUILDDIR)/$@."
html-noplot: api
@echo
@echo "Building HTML files without example plots."
@echo
- $(SPHINXBUILD) -D plot_gallery=0 -b html $(ALLSPHINXOPTS) $(BUILDDIR)/html
+ $(SPHINXBUILD) -M html "$(SOURCEDIR)" "$(BUILDDIR)" $(SPHINXOPTS) -D plot_gallery=0
@echo
@echo "Build finished. The HTML pages are in $(BUILDDIR)/html."
diff --git a/doc/_static/version_switch.js b/doc/_static/version_switch.js
index acedd8c1d4c..5450cb9d6af 100644
--- a/doc/_static/version_switch.js
+++ b/doc/_static/version_switch.js
@@ -12,6 +12,7 @@
var all_versions = {
'latest': 'latest',
'dev': 'dev',
+ 'v0.14.0': 'v0.14.0',
'v0.13.0': 'v0.13.0',
'v0.12.0': 'v0.12.0',
'v0.11.0': 'v0.11.0',
diff --git a/doc/api/index.rst b/doc/api/index.rst
index 07f76aff217..25de6d44adf 100644
--- a/doc/api/index.rst
+++ b/doc/api/index.rst
@@ -29,12 +29,14 @@ Plotting map elements
Figure.basemap
Figure.coast
Figure.colorbar
+ Figure.hlines
Figure.inset
Figure.legend
Figure.logo
Figure.solar
Figure.text
Figure.timestamp
+ Figure.vlines
Plotting tabular data
~~~~~~~~~~~~~~~~~~~~~
@@ -233,10 +235,14 @@ and store them in GMT's user data directory.
datasets.load_black_marble
datasets.load_blue_marble
datasets.load_earth_age
+ datasets.load_earth_deflection
+ datasets.load_earth_dist
datasets.load_earth_free_air_anomaly
datasets.load_earth_geoid
datasets.load_earth_magnetic_anomaly
datasets.load_earth_mask
+ datasets.load_earth_mean_dynamic_topography
+ datasets.load_earth_mean_sea_surface
datasets.load_earth_relief
datasets.load_earth_vertical_gravity_gradient
datasets.load_mars_relief
@@ -329,7 +335,6 @@ Low level access (these are mostly used by the :mod:`pygmt.clib` package):
clib.Session.read_virtualfile
clib.Session.extract_region
clib.Session.get_libgmt_func
- clib.Session.virtualfile_from_data
clib.Session.virtualfile_from_grid
clib.Session.virtualfile_from_stringio
clib.Session.virtualfile_from_matrix
diff --git a/doc/changes.md b/doc/changes.md
index 2efe67a7f1d..8e7142d2fd8 100644
--- a/doc/changes.md
+++ b/doc/changes.md
@@ -1,5 +1,109 @@
# Changelog
+## Release v0.14.0 (2024/12/31)
+
+[![Digital Object Identifier for PyGMT v0.14.0](https://zenodo.org/badge/DOI/10.5281/zenodo.14535921.svg)](https://doi.org/10.5281/zenodo.14535921)
+
+### Highlights
+
+* 🎉 **Fourteenth minor release of PyGMT** 🎉
+* Bump minimum supported version to GMT>=6.4.0 ([#3450](https://github.com/GenericMappingTools/pygmt/pull/3450))
+* Two new plotting methods and six new functions to access more GMT remote datasets
+* PyArrow as an optional dependency and improved support of PyArrow data types ([#3592](https://github.com/GenericMappingTools/pygmt/pull/3592))
+
+### New Features
+
+* Add Figure.hlines for plotting horizontal lines ([#923](https://github.com/GenericMappingTools/pygmt/pull/923))
+* Add Figure.vlines for plotting vertical lines ([#3726](https://github.com/GenericMappingTools/pygmt/pull/3726))
+* Add load_black_marble to load "Black Marble" dataset ([#3469](https://github.com/GenericMappingTools/pygmt/pull/3469))
+* Add load_blue_marble to load "Blue Marble" dataset ([#2235](https://github.com/GenericMappingTools/pygmt/pull/2235))
+* Add load_earth_deflection to load "IGPP Earth east-west and north-south deflection" datasets ([#3728](https://github.com/GenericMappingTools/pygmt/pull/3728))
+* Add load_earth_dist to load "GSHHG Earth distance to shoreline" dataset ([#3706](https://github.com/GenericMappingTools/pygmt/pull/3706))
+* Add load_earth_mean_dynamic_topography to load "CNES Earth Mean Dynamic Topography" dataset ([#3718](https://github.com/GenericMappingTools/pygmt/pull/3718))
+* Add load_earth_mean_sea_surface to load "CNES Earth Mean Sea Surface" dataset ([#3717](https://github.com/GenericMappingTools/pygmt/pull/3717))
+* load_earth_free_air_anomaly: Add "uncertainty" parameter to load the "IGPP Earth free-air anomaly uncertainty" dataset ([#3727](https://github.com/GenericMappingTools/pygmt/pull/3727))
+
+
+### Enhancements
+
+* Figure.plot: Add the "symbol" parameter to support plotting data points with varying symbols ([#1117](https://github.com/GenericMappingTools/pygmt/pull/1117))
+* Figure.plot3d: Add the "symbol" parameter to support plotting data points with varying symbols ([#3559](https://github.com/GenericMappingTools/pygmt/pull/3559))
+* Figure.legend: Support passing a StringIO object as the legend specification ([#3438](https://github.com/GenericMappingTools/pygmt/pull/3438))
+* load_tile_map: Add parameter "crs" to set the CRS of the returned dataarray ([#3554](https://github.com/GenericMappingTools/pygmt/pull/3554))
+* PyArrow: Support pyarrow arrays with string/large_string/string_view types ([#3619](https://github.com/GenericMappingTools/pygmt/pull/3619))
+* Support 1-D/2-D numpy arrays with longlong and ulonglong dtype ([#3566](https://github.com/GenericMappingTools/pygmt/pull/3566))
+* GMT_IMAGE: Implement the to_dataarray method for 3-band images ([#3128](https://github.com/GenericMappingTools/pygmt/pull/3128))
+* Ensure non-ASCII characters are typeset correctly even if PS_CHAR_ENCODING is not "ISOLatin1+" ([#3611](https://github.com/GenericMappingTools/pygmt/pull/3611))
+* Add enums GridRegistration and GridType for grid registration and type ([#3693](https://github.com/GenericMappingTools/pygmt/pull/3693))
+
+### Deprecations
+
+* SPEC 0: Bump minimum supported versions to Python 3.11, NumPy 1.25, pandas>=2.0 and xarray>=2023.04 ([#3460](https://github.com/GenericMappingTools/pygmt/pull/3460), [#3606](https://github.com/GenericMappingTools/pygmt/pull/3606), [#3697](https://github.com/GenericMappingTools/pygmt/pull/3697))
+* clib.Session.virtualfile_from_vectors: Now takes a sequence of vectors as its single argument (Passing multiple arguments will be unsupported in v0.16.0) ([#3522](https://github.com/GenericMappingTools/pygmt/pull/3522))
+* Remove the deprecated build_arg_string function (deprecated since v0.12.0) ([#3427](https://github.com/GenericMappingTools/pygmt/pull/3427))
+* Figure.grdcontour: Remove the deprecated syntax for the 'annotation' parameter (deprecated since v0.12.0) ([#3428](https://github.com/GenericMappingTools/pygmt/pull/3428))
+
+### Bug Fixes
+
+* launch_external_viewer: Use full path when opening the file in a web browser ([#3647](https://github.com/GenericMappingTools/pygmt/pull/3647))
+* PyArrow: Map date32[day]/date64[ms] dtypes in pandas objects to np.datetime64 with correct date/time units ([#3617](https://github.com/GenericMappingTools/pygmt/pull/3617))
+* clib.session: Add the GMT_SESSION_NOGDALCLOSE flag to keep GDAL open ([#3672](https://github.com/GenericMappingTools/pygmt/pull/3672))
+* Set the "Conventions" attribute to "CF-1.7" for netCDF grids only ([#3463](https://github.com/GenericMappingTools/pygmt/pull/3463))
+* Fix the conversion error for pandas.Series with missing values in pandas<=2.1 ([#3505](https://github.com/GenericMappingTools/pygmt/pull/3505), [#3596](https://github.com/GenericMappingTools/pygmt/pull/3596))
+* GeoPandas: Explicitly convert columns with overflow integers to avoid OverflowError with fiona 1.10 ([#3455](https://github.com/GenericMappingTools/pygmt/pull/3455))
+* Figure.plot/Figure.plot3d: Improve the check of the "style" parameter for "v" or "V" ([#3603](https://github.com/GenericMappingTools/pygmt/pull/3603))
+* Correctly reserve the grid data dtype by converting ctypes array to numpy array with np.ctypeslib.as_array ([#3446](https://github.com/GenericMappingTools/pygmt/pull/3446))
+* **Breaking**: Figure.text: Fix typesetting of integers when mixed with floating-point values ([#3493](https://github.com/GenericMappingTools/pygmt/pull/3493))
+
+### Documentation
+
+* Add basic tutorial "Plotting polygons" ([#3593](https://github.com/GenericMappingTools/pygmt/pull/3593))
+* Update the gallery example for plotting lines with LineString/MultiLineString geometry ([#3711](https://github.com/GenericMappingTools/pygmt/pull/3711))
+* Add the PyGMT ecosystem page ([#3475](https://github.com/GenericMappingTools/pygmt/pull/3475))
+* Document the support policy for optional packages ([#3616](https://github.com/GenericMappingTools/pygmt/pull/3616))
+* Document the environment variables that can affect the behavior of PyGMT ([#3432](https://github.com/GenericMappingTools/pygmt/pull/3432))
+* Document the built-in patterns in the Technical Reference section ([#3466](https://github.com/GenericMappingTools/pygmt/pull/3466))
+* Document Continuous Benchmarking in Maintainers Guides ([#3631](https://github.com/GenericMappingTools/pygmt/pull/3631))
+* Add instructions for installing optional dependencies ([#3506](https://github.com/GenericMappingTools/pygmt/pull/3506))
+* Update "PyData Ecosystem" to "Scientific Python Ecosystem" ([#3447](https://github.com/GenericMappingTools/pygmt/pull/3447))
+* Figure.savefig: Clarify that the "transparent" parameter also works for the PNG file associated with the KML format ([#3579](https://github.com/GenericMappingTools/pygmt/pull/3579))
+* Add the PyGMT talk at AGU24 to the "Overview" section ([#3685](https://github.com/GenericMappingTools/pygmt/pull/3685))
+* Add the GMT/PyGMT pre-conference workshop at AGU24 to the "External resources" section ([#3689](https://github.com/GenericMappingTools/pygmt/pull/3689))
+* Add TODO comments in the maintainers guides and update the release checklist ([#3724](https://github.com/GenericMappingTools/pygmt/pull/3724))
+
+### Maintenance
+
+* **Breaking**: data_kind: data is None and required now returns the "empty" kind ([#3482](https://github.com/GenericMappingTools/pygmt/pull/3482))
+* **Breaking**: data_kind: Now "matrix" represents a 2-D numpy array and unrecognized data types fall back to "vectors" ([#3351](https://github.com/GenericMappingTools/pygmt/pull/3351))
+* Add Support for Python 3.13 ([#3490](https://github.com/GenericMappingTools/pygmt/pull/3490))
+* Add the Session.virtualfile_from_stringio method to allow StringIO input for certain functions/methods ([#3326](https://github.com/GenericMappingTools/pygmt/pull/3326))
+* Add "geodatasets" as a dependency for docs and update the choropleth example ([#3719](https://github.com/GenericMappingTools/pygmt/pull/3719))
+* PyArrow: Check compatibility of pyarrow.array with string type ([#2933](https://github.com/GenericMappingTools/pygmt/pull/2933))
+* Rename sphinx-gallery's README.txt to GALLERY_HEADER.rst and require Sphinx-Gallery>=0.17.0 ([#3348](https://github.com/GenericMappingTools/pygmt/pull/3348))
+* clib.conversion: Remove the as_c_contiguous function and use np.ascontiguousarray instead ([#3492](https://github.com/GenericMappingTools/pygmt/pull/3492))
+* Use TODO comments to track deprecations and workarounds ([#3722](https://github.com/GenericMappingTools/pygmt/pull/3722))
+* Move Figure.psconvert into a separate file ([#3553](https://github.com/GenericMappingTools/pygmt/pull/3553))
+* Improve the data type checking for 2-D arrays passed to the GMT C API ([#3563](https://github.com/GenericMappingTools/pygmt/pull/3563))
+* Enable ruff's TD (flake8-todos), COM (flake8-commas), TRY (tryceratops), and EM (flake8-errmsg) rules ([#3723](https://github.com/GenericMappingTools/pygmt/pull/3723), [#3531](https://github.com/GenericMappingTools/pygmt/pull/3531), [#3665](https://github.com/GenericMappingTools/pygmt/pull/3665), [#3661](https://github.com/GenericMappingTools/pygmt/pull/3661))
+* CI: Install pyarrow-core instead of pyarrow from conda-forge ([#3698](https://github.com/GenericMappingTools/pygmt/pull/3698))
+* CI: Ensure no hyphens in Python file and directory names in the "Style Checks" workflow ([#3703](https://github.com/GenericMappingTools/pygmt/pull/3703))
+* Bump to ruff>=0.8.0 and rename rule TCH to TC ([#3662](https://github.com/GenericMappingTools/pygmt/pull/3662))
+* Bump to Ghostscript 10.04.0 ([#3443](https://github.com/GenericMappingTools/pygmt/pull/3443))
+* Add enums GridFormat for GMT grid format ID ([#3449](https://github.com/GenericMappingTools/pygmt/pull/3449))
+
+**Full Changelog**:
+
+### Contributors
+
+* [Dongdong Tian](https://github.com/seisman)
+* [Yvonne Fröhlich](https://github.com/yvonnefroehlich)
+* [Wei Ji Leong](https://github.com/weiji14)
+* [Michael Grund](https://github.com/michaelgrund)
+* [Will Schlitzer](https://github.com/willschlitzer)
+* [Jiayuan Yao](https://github.com/core-man)
+
+---
+
## Release v0.13.0 (2024/09/05)
[![Digital Object Identifier for PyGMT v0.13.0](https://zenodo.org/badge/DOI/10.5281/zenodo.13679420.svg)](https://doi.org/10.5281/zenodo.13679420)
@@ -178,6 +282,8 @@
* [Michael Grund](https://github.com/michaelgrund)
* [Wei Ji Leong](https://github.com/weiji14)
+---
+
## Release v0.11.0 (2024/02/01)
[![Digital Object Identifier for PyGMT v0.11.0](https://zenodo.org/badge/DOI/10.5281/zenodo.10578540.svg)](https://doi.org/10.5281/zenodo.10578540)
diff --git a/doc/conf.py b/doc/conf.py
index 2b41acd3f61..f3cb59228c2 100644
--- a/doc/conf.py
+++ b/doc/conf.py
@@ -210,12 +210,10 @@
repository = "GenericMappingTools/pygmt"
repository_url = "https://github.com/GenericMappingTools/pygmt"
if __commit__:
- commit_link = (
- f'{ __commit__[:8] }'
- )
+ commit_link = f'{__commit__[:8]}'
else:
commit_link = (
- f'{ __version__ }'
+ f'{__version__}'
)
html_context = {
"menu_links": [
diff --git a/doc/contributing.md b/doc/contributing.md
index 5dd93418f53..36b0d76e984 100644
--- a/doc/contributing.md
+++ b/doc/contributing.md
@@ -130,9 +130,9 @@ our tests. This way, the *main* branch is always stable.
integrated separately.
- Bug fixes should be submitted in separate PRs.
* How to write and submit a PR
- - Use underscores for all Python (*.py) files as per
- [PEP8](https://www.python.org/dev/peps/pep-0008/), not hyphens. Directory
- names should also use underscores instead of hyphens.
+ - Use underscores for all Python (\*.py) files as per
+ [PEP8](https://www.python.org/dev/peps/pep-0008/), not hyphens. Directory names
+ should also use underscores instead of hyphens.
- Describe what your PR changes and *why* this is a good thing. Be as
specific as you can. The PR description is how we keep track of the changes
made to the project over time.
diff --git a/doc/external_resources.md b/doc/external_resources.md
index b0e8ad10e34..30c04764e50 100644
--- a/doc/external_resources.md
+++ b/doc/external_resources.md
@@ -13,7 +13,7 @@ to submit a pull request with your recommended addition to the
:::::{grid} 1 2 2 3
::::{grid-item-card} 2024 AGU PREWS9: Mastering Geospatial Visualizations with GMT/PyGMT
-:link: https://github.com/GenericMappingTools/agu24workshop
+:link: https://www.generic-mapping-tools.org/agu24workshop/
:text-align: center
:margin: 0 3 0 0
diff --git a/doc/maintenance.md b/doc/maintenance.md
index 41c64769f98..0664a2850eb 100644
--- a/doc/maintenance.md
+++ b/doc/maintenance.md
@@ -148,34 +148,35 @@ patch release.
## Backwards Compatibility and Deprecation Policy
-PyGMT is still undergoing rapid development. All of the API is subject to change
-until the v1.0.0 release. Versioning in PyGMT is based on the
+PyGMT is still undergoing rapid development. All of the API is subject to change until
+the v1.0.0 release. Versioning in PyGMT is based on the
[semantic versioning specification](https://semver.org/spec/v2.0.0.html)
-(i.e., v*MAJOR*.*MINOR*.*PATCH*).
-Basic policy for backwards compatibility:
+(i.e., v*MAJOR*.*MINOR*.*PATCH*). Basic policy for backwards compatibility:
- Any incompatible changes should go through the deprecation process below.
-- Incompatible changes are only allowed in major and minor releases, not in
- patch releases.
+- Incompatible changes are only allowed in major and minor releases, not in patch releases.
- Incompatible changes should be documented in the release notes.
When making incompatible changes, we should follow the process:
- Discuss whether the incompatible changes are necessary on GitHub.
-- Make the changes in a backwards compatible way, and raise a `FutureWarning`
- warning for the old usage. At least one test using the old usage should be added.
-- The warning message should clearly explain the changes and include the versions
- in which the old usage is deprecated and is expected to be removed.
-- The `FutureWarning` warning should appear for 2-4 minor versions, depending on
- the impact of the changes. It means the deprecation period usually lasts
- 3-12 months.
+- Make the changes in a backwards compatible way, and raise a `FutureWarning` warning
+ for the old usage. At least one test using the old usage should be added.
+- The warning message should clearly explain the changes and include the versions in
+ which the old usage is deprecated and is expected to be removed.
+- The `FutureWarning` warning should appear for 2-4 minor versions, depending on the
+ impact of the changes. It means the deprecation period usually lasts 3-12 months.
- Remove the old usage and warning when reaching the declared version.
-To rename a function parameter, add the `@deprecate_parameter` decorator near
-the top after the `@fmt_docstring` decorator but before the `@use_alias`
-decorator (if those two exist). Here is an example:
+### Deprecating a function parameter
-```
+To rename a function parameter, add the `@deprecate_parameter` decorator near the top
+after the `@fmt_docstring` decorator but before the `@use_alias` decorator (if those two
+exist). A `TODO` comment should also be added to indicate the deprecation period (see below).
+Here is an example:
+
+```python
+# TODO(PyGMT>=0.6.0): Remove the deprecated "columns" parameter.
@fmt_docstring
@deprecate_parameter("columns", "incols", "v0.4.0", remove_version="v0.6.0")
@use_alias(J="projection", R="region", V="verbose", i="incols")
@@ -184,8 +185,30 @@ def plot(self, x=None, y=None, data=None, size=None, direction=None, **kwargs):
pass
```
-In this case, the old parameter name `columns` is deprecated since v0.4.0, and
-will be fully removed in v0.6.0. The new parameter name is `incols`.
+In this case, the old parameter name `columns` is deprecated since v0.4.0, and will be
+fully removed in v0.6.0. The new parameter name is `incols`.
+
+### TODO comments
+
+Occasionally, we need to implement temporary code that should be removed in the future.
+This can occur in situations such as:
+
+- When a parameter, function, or method is deprecated and scheduled for removal.
+- When workarounds are necessary to address issues in older or upcoming versions of GMT
+ or other dependencies.
+
+To track these temporary codes or workarounds, we use TODO comments. These comments
+should adhere to the following format:
+
+```python
+# TODO(package>=X.Y.Z): A brief description of the TODO item.
+# Additional details if necessary.
+```
+The TODO comment indicates that we should address the item when *package* version
+*X.Y.Z* or later is required.
+
+It's important not to overuse TODO comments for tracking unimplemented features.
+Instead, open issues to monitor these features.
## Making a Release
diff --git a/doc/minversions.md b/doc/minversions.md
index 1d348ec6922..b10150b5ce1 100644
--- a/doc/minversions.md
+++ b/doc/minversions.md
@@ -41,6 +41,7 @@ compatibility reasons.
| PyGMT Version | GMT | Python | NumPy | pandas | Xarray |
|---|---|---|---|---|---|
| [Dev][]* [] | {{ requires.gmt }} | {{ requires.python }} | {{ requires.numpy }} | {{ requires.pandas }} | {{ requires.xarray }} |
+| [] | >=6.4.0 | >=3.11 | >=1.25 | >=2.0 | >=2023.04 |
| [] | >=6.3.0 | >=3.10 | >=1.24 | >=1.5 | >=2022.09 |
| [] | >=6.3.0 | >=3.10 | >=1.23 | >=1.5 | >=2022.06 |
| [] | >=6.3.0 | >=3.9 | >=1.23 | | |
diff --git a/doc/techref/patterns.md b/doc/techref/patterns.md
index 17deb045aa3..de7df02d047 100644
--- a/doc/techref/patterns.md
+++ b/doc/techref/patterns.md
@@ -11,7 +11,7 @@ image raster file. The former will result in one of the 90 predefined 64x64 bit-
provided by GMT (see the figure below). The latter allows the user to create customized,
repeating images using image raster files.
-By specifying upper case **P** instead of **p** the image will be bit-reversed, i.e.,
+By specifying uppercase **P** instead of **p** the image will be bit-reversed, i.e.,
white and black areas will be interchanged (only applies to 1-bit images or predefined
bit-image patterns). For these patterns and other 1-bit images one may specify
alternative **b**ackground and **f**oreground colors (by appending **+b**_color_ and/or
diff --git a/environment.yml b/environment.yml
index 620b1bad91b..c51b2967fc2 100644
--- a/environment.yml
+++ b/environment.yml
@@ -27,7 +27,7 @@ dependencies:
# Dev dependencies (style checks)
- codespell
- pre-commit
- - ruff>=0.8.2
+ - ruff>=0.9.0
# Dev dependencies (unit testing)
- matplotlib-base
- pytest>=6.0
@@ -35,6 +35,7 @@ dependencies:
- pytest-doctestplus
- pytest-mpl
# Dev dependencies (building documentation)
+ - geodatasets
- myst-nb
- panel
- sphinx>=6.2
diff --git a/examples/gallery/basemaps/double_y_axes.py b/examples/gallery/basemaps/double_y_axes.py
index aa8ba8a6815..c6f970e59ca 100644
--- a/examples/gallery/basemaps/double_y_axes.py
+++ b/examples/gallery/basemaps/double_y_axes.py
@@ -5,7 +5,7 @@
The ``frame`` parameter of the plotting methods of the :class:`pygmt.Figure`
class can control which axes should be plotted and optionally show annotations,
tick marks, and gridlines. By default, all 4 axes are plotted, along with
-annotations and tick marks (denoted **W**, **S**, **E**, **N**). Lower case
+annotations and tick marks (denoted **W**, **S**, **E**, **N**). Lowercase
versions (**w**, **s**, **e**, **n**) can be used to denote to only plot the
axes with tick marks. We can also only plot the axes without annotations and
tick marks using **l** (left axis), **r** (right axis), **t** (top axis),
diff --git a/examples/gallery/embellishments/scalebar.py b/examples/gallery/embellishments/scalebar.py
index bca7d0b9703..4b829165fe5 100644
--- a/examples/gallery/embellishments/scalebar.py
+++ b/examples/gallery/embellishments/scalebar.py
@@ -12,8 +12,9 @@
- **g**: Give map coordinates as *longitude*\/\ *latitude*.
- **j**\|\ **J**: Specify a two-character (order independent) code.
Choose from vertical **T**\(op), **M**\(iddle), or **B**\(ottom) and
- horizontal **L**\(eft), **C**\(entre), or **R**\(ight). Lower / upper
- case **j** / **J** mean inside / outside of the map bounding box.
+ horizontal **L**\(eft), **C**\(entre), or **R**\(ight). Lower /
+ uppercase **j** / **J** mean inside / outside of the map bounding
+ box.
- **n**: Give normalized bounding box coordinates as *nx*\/\ *ny*.
- **x**: Give plot coordinates as *x*\/\ *y*.
diff --git a/examples/gallery/images/cross_section.py b/examples/gallery/images/cross_section.py
index 0f0cd8e352e..d451c8c3ee8 100644
--- a/examples/gallery/images/cross_section.py
+++ b/examples/gallery/images/cross_section.py
@@ -37,7 +37,7 @@
# Add a colorbar for the elevation
fig.colorbar(
- # Place the colorbar inside the plot (lower-case "j") in the Bottom Right (BR)
+ # Place the colorbar inside the plot (lowercase "j") in the Bottom Right (BR)
# corner with an offset ("+o") of 0.7 centimeters and 0.3 centimeters in x or y
# directions, respectively; move the x label above the horizontal colorbar ("+ml")
position="jBR+o0.7c/0.8c+h+w5c/0.3c+ml",
diff --git a/examples/gallery/images/rgb_image.py b/examples/gallery/images/rgb_image.py
index 56d7b9d2f70..8afb2c93e25 100644
--- a/examples/gallery/images/rgb_image.py
+++ b/examples/gallery/images/rgb_image.py
@@ -28,7 +28,7 @@
# Subset to area of Lāhainā in EPSG:32604 coordinates
image = img.rio.clip_box(minx=738000, maxx=755000, miny=2300000, maxy=2318000)
image = image.load() # Force loading the DataArray into memory
-image # noqa: B018
+image
# %%
# Plot the RGB imagery:
diff --git a/examples/gallery/lines/decorated_lines.py b/examples/gallery/lines/decorated_lines.py
index 42ad5dc7be9..cbe6b6b510d 100644
--- a/examples/gallery/lines/decorated_lines.py
+++ b/examples/gallery/lines/decorated_lines.py
@@ -51,7 +51,7 @@
"~d1c:+sd0.5c+gtan+p1p,black+n-0.2c/0.1c",
# Give the number of equally spaced symbols by using "n" instead of "d"
"~n6:+sn0.5c+gtan+p1p,black",
- # Use upper-case "N" to have symbols at the start and end of the line
+ # Use uppercase "N" to have symbols at the start and end of the line
"~N6:+sh0.5c+gtan+p1p,black",
# Suppress the main decorated line by appending "+i"
"~d1c:+sg0.5c+gtan+p1p,black+i",
diff --git a/examples/gallery/lines/linestrings.py b/examples/gallery/lines/linestrings.py
new file mode 100644
index 00000000000..18f94502f16
--- /dev/null
+++ b/examples/gallery/lines/linestrings.py
@@ -0,0 +1,46 @@
+"""
+GeoPandas: Plotting lines with LineString or MultiLineString geometry
+=====================================================================
+
+The :meth:`pygmt.Figure.plot` method allows us to plot geographical data such as lines
+with LineString or MultiLineString geometry types stored in a
+:class:`geopandas.GeoDataFrame` object or any object that implements the
+`__geo_interface__ `__ property.
+
+Use :func:`geopandas.read_file` to load data from any supported OGR format such as a
+shapefile (.shp), GeoJSON (.geojson), geopackage (.gpkg), etc. Then, pass the
+:class:`geopandas.GeoDataFrame` object as an argument to the ``data`` parameter of
+:meth:`pygmt.Figure.plot`, and style the lines using the ``pen`` parameter.
+"""
+
+# %%
+import geodatasets
+import geopandas as gpd
+import pygmt
+
+# Read a sample dataset provided by the geodatasets package.
+# The dataset contains large rivers in Europe, stored as LineString/MultiLineString
+# geometry types.
+gdf = gpd.read_file(geodatasets.get_path("eea large_rivers"))
+
+# Convert object to EPSG 4326 coordinate system
+gdf = gdf.to_crs("EPSG:4326")
+gdf.head()
+
+# %%
+fig = pygmt.Figure()
+
+fig.coast(
+ projection="M10c",
+ region=[-10, 30, 35, 57],
+ resolution="l",
+ land="gray95",
+ shorelines="1/0.1p,gray50",
+ borders="1/0.1,gray30",
+ frame=True,
+)
+
+# Add rivers to map
+fig.plot(data=gdf, pen="1p,steelblue")
+
+fig.show()
diff --git a/examples/gallery/lines/quoted_lines.py b/examples/gallery/lines/quoted_lines.py
index 9e70ec15c4a..2ccfb1309a6 100644
--- a/examples/gallery/lines/quoted_lines.py
+++ b/examples/gallery/lines/quoted_lines.py
@@ -33,7 +33,7 @@
"qd1c:+ltext+i",
# Give the number of equally spaced labels by using "n" instead of "d"
"qn5:+ltext",
- # Use upper-case "N" to have labels at the start and end of the line
+ # Use uppercase "N" to have labels at the start and end of the line
"qN5:+ltext",
# To only plot a label at the start of the line use "N-1"
"qN-1:+ltext",
diff --git a/examples/gallery/lines/roads.py b/examples/gallery/lines/roads.py
deleted file mode 100644
index c2a5f69980a..00000000000
--- a/examples/gallery/lines/roads.py
+++ /dev/null
@@ -1,46 +0,0 @@
-# ruff: noqa: RUF003
-"""
-Roads
-=====
-
-The :meth:`pygmt.Figure.plot` method allows us to plot geographical data such
-as lines which are stored in a :class:`geopandas.GeoDataFrame` object. Use
-:func:`geopandas.read_file` to load data from any supported OGR format such as
-a shapefile (.shp), GeoJSON (.geojson), geopackage (.gpkg), etc. Then, pass the
-:class:`geopandas.GeoDataFrame` as an argument to the ``data`` parameter of
-:meth:`pygmt.Figure.plot`, and style the geometry using the ``pen`` parameter.
-"""
-
-# %%
-import geopandas as gpd
-import pygmt
-
-# Read shapefile data using geopandas
-gdf = gpd.read_file(
- "https://www2.census.gov/geo/tiger/TIGER2015/PRISECROADS/tl_2015_15_prisecroads.zip"
-)
-# The dataset contains different road types listed in the RTTYP column,
-# here we select the following ones to plot:
-roads_common = gdf[gdf.RTTYP == "M"] # Common name roads
-roads_state = gdf[gdf.RTTYP == "S"] # State recognized roads
-roads_interstate = gdf[gdf.RTTYP == "I"] # Interstate roads
-
-fig = pygmt.Figure()
-
-# Define target region around Oʻahu (Hawaiʻi)
-region = [-158.3, -157.6, 21.2, 21.75] # xmin, xmax, ymin, ymax
-
-title = "Main roads of O`ahu (Hawai`i)" # Approximating the Okina letter ʻ with `
-fig.basemap(region=region, projection="M12c", frame=["af", f"WSne+t{title}"])
-fig.coast(land="gray", water="dodgerblue4", shorelines="1p,black")
-
-# Plot the individual road types with different pen settings and assign labels
-# which are displayed in the legend
-fig.plot(data=roads_common, pen="5p,dodgerblue", label="CommonName")
-fig.plot(data=roads_state, pen="2p,gold", label="StateRecognized")
-fig.plot(data=roads_interstate, pen="2p,red", label="Interstate")
-
-# Add legend
-fig.legend()
-
-fig.show()
diff --git a/examples/gallery/maps/choropleth_map.py b/examples/gallery/maps/choropleth_map.py
index 6c43d24d3dd..f1cce8c3014 100644
--- a/examples/gallery/maps/choropleth_map.py
+++ b/examples/gallery/maps/choropleth_map.py
@@ -2,25 +2,27 @@
Choropleth map
==============
-The :meth:`pygmt.Figure.plot` method allows us to plot geographical data such
-as polygons which are stored in a :class:`geopandas.GeoDataFrame` object. Use
-:func:`geopandas.read_file` to load data from any supported OGR format such as
-a shapefile (.shp), GeoJSON (.geojson), geopackage (.gpkg), etc. You can also
-use a full URL pointing to your desired data source. Then, pass the
-:class:`geopandas.GeoDataFrame` as an argument to the ``data`` parameter of
-:meth:`pygmt.Figure.plot`, and style the geometry using the ``pen`` parameter.
-To fill the polygons based on a corresponding column you need to set
-``fill="+z"`` as well as select the appropriate column using the ``aspatial``
-parameter as shown in the example below.
+The :meth:`pygmt.Figure.plot` method allows us to plot geographical data such as
+polygons which are stored in a :class:`geopandas.GeoDataFrame` object. Use
+:func:`geopandas.read_file` to load data from any supported OGR format such as a
+shapefile (.shp), GeoJSON (.geojson), geopackage (.gpkg), etc. You can also use a full
+URL pointing to your desired data source. Then, pass the :class:`geopandas.GeoDataFrame`
+as an argument to the ``data`` parameter of :meth:`pygmt.Figure.plot`, and style the
+geometry using the ``pen`` parameter. To fill the polygons based on a corresponding
+column you need to set ``fill="+z"`` as well as select the appropriate column using the
+``aspatial`` parameter as shown in the example below.
"""
# %%
+import geodatasets
import geopandas as gpd
import pygmt
-# Read polygon data using geopandas
-gdf = gpd.read_file("https://geodacenter.github.io/data-and-lab/data/airbnb.zip")
+# Read the example dataset provided by geodatasets.
+gdf = gpd.read_file(geodatasets.get_path("geoda airbnb"))
+print(gdf.head())
+# %%
fig = pygmt.Figure()
fig.basemap(
@@ -29,11 +31,10 @@
frame="+tPopulation of Chicago",
)
-# The dataset contains different attributes, here we select
-# the "population" column to plot.
+# The dataset contains different attributes, here we select the "population" column to
+# plot.
-# First, we define the colormap to fill the polygons based on
-# the "population" column.
+# First, we define the colormap to fill the polygons based on the "population" column.
pygmt.makecpt(
cmap="acton",
series=[gdf["population"].min(), gdf["population"].max(), 10],
@@ -41,8 +42,8 @@
reverse=True,
)
-# Next, we plot the polygons and fill them using the defined colormap.
-# The target column is defined by the aspatial parameter.
+# Next, we plot the polygons and fill them using the defined colormap. The target column
+# is defined by the aspatial parameter.
fig.plot(
data=gdf,
pen="0.3p,gray10",
@@ -51,7 +52,7 @@
aspatial="Z=population",
)
-# Add colorbar legend
+# Add colorbar legend.
fig.colorbar(frame="x+lPopulation", position="jML+o-0.5c+w3.5c/0.2c")
fig.show()
diff --git a/examples/gallery/symbols/multi_parameter_symbols.py b/examples/gallery/symbols/multi_parameter_symbols.py
index 6baa607f1c5..72480aba063 100644
--- a/examples/gallery/symbols/multi_parameter_symbols.py
+++ b/examples/gallery/symbols/multi_parameter_symbols.py
@@ -33,7 +33,7 @@
# directions given in degrees counter-clockwise from horizontal. Append **+i** and the
# desired value to apply an inner diameter.
#
-# Upper-case versions **E**, **J**, and **W** are similar to **e**, **j**, and **w**
+# Uppercase versions **E**, **J**, and **W** are similar to **e**, **j**, and **w**
# but expect geographic azimuths and distances.
fig = pygmt.Figure()
diff --git a/examples/projections/cyl/cyl_oblique_mercator.py b/examples/projections/cyl/cyl_oblique_mercator.py
index 17bf589d48b..22db4f973da 100644
--- a/examples/projections/cyl/cyl_oblique_mercator.py
+++ b/examples/projections/cyl/cyl_oblique_mercator.py
@@ -9,7 +9,7 @@
The projection is set with **o** or **O**. There are three different specification
ways (**a**\|\ **A**, **b**\|\ **B**, **c**\|\ **C**) available. For all three
-definitions, the upper case letter mean the projection pole is set in the southern
+definitions, the uppercase letter mean the projection pole is set in the southern
hemisphere [Default is northern hemisphere]. Align the y-axis with the optional
modifier **+v**. The figure size is set with *scale* or *width*.
"""
diff --git a/examples/projections/nongeo/cartesian_linear.py b/examples/projections/nongeo/cartesian_linear.py
index 021d080ae94..83e986f6739 100644
--- a/examples/projections/nongeo/cartesian_linear.py
+++ b/examples/projections/nongeo/cartesian_linear.py
@@ -4,7 +4,7 @@
**X**\ *width*\ [/*height*] or **x**\ *x-scale*\ [/*y-scale*]
-Give the *width* of the figure and the optional *height*. The lower-case version
+Give the *width* of the figure and the optional *height*. The lowercase version
**x** is similar to **X** but expects an *x-scale* and an optional *y-scale*.
The Cartesian linear projection is primarily designed for regular floating point
diff --git a/examples/projections/nongeo/cartesian_logarithmic.py b/examples/projections/nongeo/cartesian_logarithmic.py
index ef354dba73f..0e4619075ea 100644
--- a/examples/projections/nongeo/cartesian_logarithmic.py
+++ b/examples/projections/nongeo/cartesian_logarithmic.py
@@ -6,7 +6,7 @@
**x**\ *x-scale*\ [**l**][/*y-scale*\ [**l**]]
Give the *width* of the figure and the optional *height*.
-The lower-case version **x** is similar to **X** but expects
+The lowercase version **x** is similar to **X** but expects
an *x-scale* and an optional *y-scale*.
Each axis with a logarithmic transformation requires **l** after
its size argument.
diff --git a/examples/projections/nongeo/cartesian_power.py b/examples/projections/nongeo/cartesian_power.py
index 862ceba8595..b5f856713ed 100644
--- a/examples/projections/nongeo/cartesian_power.py
+++ b/examples/projections/nongeo/cartesian_power.py
@@ -6,7 +6,7 @@
**x**\ *x-scale*\ [**p**\ *pvalue*][/*y-scale*\ [**p**\ *pvalue*]]
Give the *width* of the figure and the optional argument *height*.
-The lower-case version **x** is similar to **X** but expects
+The lowercase version **x** is similar to **X** but expects
an *x-scale* and an optional *y-scale*.
Each axis with a power transformation requires **p** and the exponent
for that axis after its size argument.
diff --git a/examples/projections/nongeo/polar.py b/examples/projections/nongeo/polar.py
index 5c71e517c17..760838b34ac 100644
--- a/examples/projections/nongeo/polar.py
+++ b/examples/projections/nongeo/polar.py
@@ -12,7 +12,7 @@
Limits are set via the ``region`` parameter
([*theta_min*, *theta_max*, *radius_min*, *radius_max*]). When using **P**\ *width* you
-have to give the *width* of the figure. The lower-case version **p** is similar to **P**
+have to give the *width* of the figure. The lowercase version **p** is similar to **P**
but expects a *scale* instead of a width (**p**\ *scale*).
The following customizing modifiers are available:
diff --git a/examples/tutorials/advanced/cartesian_histograms.py b/examples/tutorials/advanced/cartesian_histograms.py
index 7191d18cbfd..3a547fd92f5 100644
--- a/examples/tutorials/advanced/cartesian_histograms.py
+++ b/examples/tutorials/advanced/cartesian_histograms.py
@@ -348,7 +348,7 @@
# of the bin width
# Offset ("+o") the bars to align each bar with the left limit of the corresponding
# bin
- barwidth=f"{binwidth/2}+o-{binwidth/4}",
+ barwidth=f"{binwidth / 2}+o-{binwidth / 4}",
label="data01",
)
@@ -359,7 +359,7 @@
fill="orange",
pen="1p,darkgray,solid",
histtype=0,
- barwidth=f"{binwidth/2}+o{binwidth/4}",
+ barwidth=f"{binwidth / 2}+o{binwidth / 4}",
label="data02",
)
diff --git a/examples/tutorials/basics/frames.py b/examples/tutorials/basics/frames.py
index 56b4e9ac130..b3fb466fa23 100644
--- a/examples/tutorials/basics/frames.py
+++ b/examples/tutorials/basics/frames.py
@@ -90,9 +90,9 @@
# :meth:`pygmt.Figure.basemap`. The map boundaries (or plot axes) are named as
# West/west/left (**W**, **w**, **l**), South/south/bottom
# (**S**, **s**, **b**), North/north/top (**N**, **n**, **t**), and
-# East/east/right (**E**, **e**, **r**) sides of a figure. If an upper-case
+# East/east/right (**E**, **e**, **r**) sides of a figure. If an uppercase
# letter (**W**, **S**, **N**, **E**) is passed, the axis is plotted with
-# tick marks and annotations. The lower-case version
+# tick marks and annotations. The lowercase version
# (**w**, **s**, **n**, **e**) plots the axis only with tick marks.
# To only plot the axis pass **l**, **b**, **t**, **r**. By default
# (``frame=True`` or ``frame="af"``), the West and the South axes are
diff --git a/examples/tutorials/basics/plot.py b/examples/tutorials/basics/plot.py
index babfb60d771..42cb8aad5ae 100644
--- a/examples/tutorials/basics/plot.py
+++ b/examples/tutorials/basics/plot.py
@@ -18,7 +18,9 @@
# The data are loaded as a :class:`pandas.DataFrame`.
data = pygmt.datasets.load_sample_data(name="japan_quakes")
+data.head()
+# %%
# Set the region for the plot to be slightly larger than the data bounds.
region = [
data.longitude.min() - 1,
@@ -26,9 +28,7 @@
data.latitude.min() - 1,
data.latitude.max() + 1,
]
-
-print(region)
-print(data.head())
+region
# %%
# We'll use the :meth:`pygmt.Figure.plot` method to plot circles on the
diff --git a/pygmt/_show_versions.py b/pygmt/_show_versions.py
index e529f053e46..f0d4b4e3c2f 100644
--- a/pygmt/_show_versions.py
+++ b/pygmt/_show_versions.py
@@ -16,7 +16,7 @@
from pygmt.clib import Session, __gmt_version__
# Get semantic version through setuptools-scm
-__version__ = f'v{version("pygmt")}' # e.g. v0.1.2.dev3+g0ab3cd78
+__version__ = f"v{version('pygmt')}" # e.g. v0.1.2.dev3+g0ab3cd78
__commit__ = __version__.split("+g")[-1] if "+g" in __version__ else "" # 0ab3cd78
@@ -85,7 +85,7 @@ def _check_ghostscript_version(gs_version: str | None) -> str | None:
return None
-def show_versions(file: TextIO | None = sys.stdout):
+def show_versions(file: TextIO | None = sys.stdout) -> None:
"""
Print various dependency versions which are useful when submitting bug reports.
diff --git a/pygmt/clib/conversion.py b/pygmt/clib/conversion.py
index 5a1d1cf51b9..7823aa32103 100644
--- a/pygmt/clib/conversion.py
+++ b/pygmt/clib/conversion.py
@@ -173,12 +173,11 @@ def _to_numpy(data: Any) -> np.ndarray:
# The numpy dtype for the result numpy array, but can be None.
numpy_dtype = dtypes.get(str(dtype))
+ # TODO(pandas>=2.2): Remove the workaround for pandas<2.2.
+ #
# pandas numeric dtypes were converted to np.object_ dtype prior pandas 2.2, and are
# converted to suitable NumPy dtypes since pandas 2.2. Refer to the following link
# for details: https://pandas.pydata.org/docs/whatsnew/v2.2.0.html#to-numpy-for-numpy-nullable-and-arrow-types-converts-to-suitable-numpy-dtype
- #
- # Workarounds for pandas < 2.2. Following SPEC 0, pandas 2.1 should be dropped in
- # 2025 Q3, so it's likely we can remove the workaround in PyGMT v0.17.0.
if (
Version(pd.__version__) < Version("2.2") # pandas < 2.2 only.
and hasattr(data, "dtype") # NumPy array or pandas objects only.
@@ -193,8 +192,24 @@ def _to_numpy(data: Any) -> np.ndarray:
numpy_dtype = np.float64
data = data.to_numpy(na_value=np.nan)
+ # Deal with timezone-aware datetime dtypes.
+ if isinstance(dtype, pd.DatetimeTZDtype): # pandas.DatetimeTZDtype
+ numpy_dtype = getattr(dtype, "base", None)
+ elif isinstance(dtype, pd.ArrowDtype) and hasattr(dtype.pyarrow_dtype, "tz"):
+ # pd.ArrowDtype[pa.Timestamp]
+ numpy_dtype = getattr(dtype, "numpy_dtype", None)
+ # TODO(pandas>=2.1): Remove the workaround for pandas<2.1.
+ if Version(pd.__version__) < Version("2.1"):
+ # In pandas 2.0, dtype.numpy_type is dtype("O").
+ numpy_dtype = np.dtype(f"M8[{dtype.pyarrow_dtype.unit}]") # type: ignore[assignment, attr-defined]
+
array = np.ascontiguousarray(data, dtype=numpy_dtype)
+ # Check if a np.object_ or np.str_ array can be converted to np.datetime64.
+ if array.dtype.type in {np.object_, np.str_}:
+ with contextlib.suppress(TypeError, ValueError):
+ return np.ascontiguousarray(array, dtype=np.datetime64)
+
# Check if a np.object_ array can be converted to np.str_.
if array.dtype == np.object_:
with contextlib.suppress(TypeError, ValueError):
diff --git a/pygmt/clib/session.py b/pygmt/clib/session.py
index ee37c55d59f..179737d35f9 100644
--- a/pygmt/clib/session.py
+++ b/pygmt/clib/session.py
@@ -327,7 +327,7 @@ def get_libgmt_func(
function.restype = restype
return function
- def create(self, name: str):
+ def create(self, name: str) -> None:
"""
Create a new GMT C API session.
@@ -384,7 +384,8 @@ def print_func(file_pointer, message): # noqa: ARG001
We'll capture the messages and print them to stderr so that they will show
up on the Jupyter notebook.
"""
- # Have to use try..except due to upstream GMT bug in GMT <= 6.5.0.
+ # TODO(GMT>6.5.0): Remove the workaround for upstream bug in GMT<=6.5.0.
+ # Have to use try..except due to upstream GMT bug in GMT<=6.5.0.
# See https://github.com/GenericMappingTools/pygmt/issues/3205.
try:
message = message.decode().strip()
@@ -593,7 +594,7 @@ def get_common(self, option: str) -> bool | int | float | np.ndarray:
case _: # 'status' is the option value (in integer type).
return status
- def call_module(self, module: str, args: str | list[str]):
+ def call_module(self, module: str, args: str | list[str]) -> None:
"""
Call a GMT module with the given arguments.
@@ -945,7 +946,9 @@ def _check_dtype_and_dim(self, array: np.ndarray, ndim: int) -> int:
raise GMTInvalidInput(msg)
return self[DTYPES[dtype]]
- def put_vector(self, dataset: ctp.c_void_p, column: int, vector: np.ndarray):
+ def put_vector(
+ self, dataset: ctp.c_void_p, column: int, vector: np.ndarray
+ ) -> None:
r"""
Attach a 1-D numpy array as a column on a GMT dataset.
@@ -1004,7 +1007,9 @@ def put_vector(self, dataset: ctp.c_void_p, column: int, vector: np.ndarray):
)
raise GMTCLibError(msg)
- def put_strings(self, dataset: ctp.c_void_p, family: str, strings: np.ndarray):
+ def put_strings(
+ self, dataset: ctp.c_void_p, family: str, strings: np.ndarray
+ ) -> None:
"""
Attach a 1-D numpy array of dtype str as a column on a GMT dataset.
@@ -1058,7 +1063,9 @@ def put_strings(self, dataset: ctp.c_void_p, family: str, strings: np.ndarray):
msg = f"Failed to put strings of type {strings.dtype} into dataset."
raise GMTCLibError(msg)
- def put_matrix(self, dataset: ctp.c_void_p, matrix: np.ndarray, pad: int = 0):
+ def put_matrix(
+ self, dataset: ctp.c_void_p, matrix: np.ndarray, pad: int = 0
+ ) -> None:
"""
Attach a 2-D numpy array to a GMT dataset.
@@ -1203,7 +1210,7 @@ def read_data(
raise GMTCLibError(msg)
return ctp.cast(data_ptr, ctp.POINTER(dtype))
- def write_data(self, family, geometry, mode, wesn, output, data):
+ def write_data(self, family, geometry, mode, wesn, output, data) -> None:
"""
Write a GMT data container to a file.
@@ -1388,23 +1395,6 @@ def open_virtualfile(
msg = f"Failed to close virtual file '{vfname}'."
raise GMTCLibError(msg)
- def open_virtual_file(self, family, geometry, direction, data):
- """
- Open a GMT virtual file associated with a data object for reading or writing.
-
- .. deprecated: 0.11.0
-
- Will be removed in v0.15.0. Use :meth:`pygmt.clib.Session.open_virtualfile`
- instead.
- """
- msg = (
- "API function `Session.open_virtual_file()' has been deprecated "
- "since v0.11.0 and will be removed in v0.15.0. "
- "Use `Session.open_virtualfile()' instead."
- )
- warnings.warn(msg, category=FutureWarning, stacklevel=2)
- return self.open_virtualfile(family, geometry, direction, data)
-
@contextlib.contextmanager
def virtualfile_from_vectors(
self, vectors: Sequence, *args
@@ -1454,9 +1444,9 @@ def virtualfile_from_vectors(
... print(fout.read().strip())
: N = 3 <1/3> <4/6> <7/9>
"""
+ # TODO(PyGMT>=0.16.0): Remove the "*args" parameter and related codes.
# "*args" is added in v0.14.0 for backward-compatibility with the deprecated
# syntax of passing multiple vectors as positional arguments.
- # Remove it in v0.16.0.
if len(args) > 0:
msg = (
"Passing multiple arguments to Session.virtualfile_from_vectors is "
@@ -1918,42 +1908,6 @@ def virtualfile_in(
file_context = _virtualfile_from(_data)
return file_context
- def virtualfile_from_data(
- self,
- check_kind=None,
- data=None,
- x=None,
- y=None,
- z=None,
- extra_arrays=None,
- required_z=False,
- required_data=True,
- ):
- """
- Store any data inside a virtual file.
-
- .. deprecated: 0.13.0
-
- Will be removed in v0.15.0. Use :meth:`pygmt.clib.Session.virtualfile_in`
- instead.
- """
- msg = (
- "API function 'Session.virtualfile_from_data()' has been deprecated since "
- "v0.13.0 and will be removed in v0.15.0. Use 'Session.virtualfile_in()' "
- "instead."
- )
- warnings.warn(msg, category=FutureWarning, stacklevel=2)
- return self.virtualfile_in(
- check_kind=check_kind,
- data=data,
- x=x,
- y=y,
- z=z,
- extra_arrays=extra_arrays,
- required_z=required_z,
- required_data=required_data,
- )
-
@contextlib.contextmanager
def virtualfile_out(
self,
diff --git a/pygmt/conftest.py b/pygmt/conftest.py
index bc896d44732..ee491b7f8bc 100644
--- a/pygmt/conftest.py
+++ b/pygmt/conftest.py
@@ -5,7 +5,7 @@
import numpy as np
from packaging.version import Version
-# Keep this until we require numpy to be >=2.0
+# TODO(NumPy>=2.0): Remove the conftest.py file.
# Address https://github.com/GenericMappingTools/pygmt/issues/2628.
if Version(np.__version__) >= Version("2.0.0.dev0+git20230726"):
np.set_printoptions(legacy="1.25") # type: ignore[arg-type]
diff --git a/pygmt/datasets/__init__.py b/pygmt/datasets/__init__.py
index d70eec5a1de..3d44a8fe676 100644
--- a/pygmt/datasets/__init__.py
+++ b/pygmt/datasets/__init__.py
@@ -6,10 +6,16 @@
from pygmt.datasets.earth_age import load_earth_age
from pygmt.datasets.earth_day import load_blue_marble
+from pygmt.datasets.earth_deflection import load_earth_deflection
+from pygmt.datasets.earth_dist import load_earth_dist
from pygmt.datasets.earth_free_air_anomaly import load_earth_free_air_anomaly
from pygmt.datasets.earth_geoid import load_earth_geoid
from pygmt.datasets.earth_magnetic_anomaly import load_earth_magnetic_anomaly
from pygmt.datasets.earth_mask import load_earth_mask
+from pygmt.datasets.earth_mean_dynamic_topography import (
+ load_earth_mean_dynamic_topography,
+)
+from pygmt.datasets.earth_mean_sea_surface import load_earth_mean_sea_surface
from pygmt.datasets.earth_night import load_black_marble
from pygmt.datasets.earth_relief import load_earth_relief
from pygmt.datasets.earth_vertical_gravity_gradient import (
diff --git a/pygmt/datasets/earth_deflection.py b/pygmt/datasets/earth_deflection.py
new file mode 100644
index 00000000000..c0a9cbf406f
--- /dev/null
+++ b/pygmt/datasets/earth_deflection.py
@@ -0,0 +1,119 @@
+"""
+Function to download the IGPP Earth east-west and north-south deflection datasets from
+the GMT data server, and load as :class:`xarray.DataArray`.
+
+The grids are available in various resolutions.
+"""
+
+from collections.abc import Sequence
+from typing import Literal
+
+import xarray as xr
+from pygmt.datasets.load_remote_dataset import _load_remote_dataset
+
+__doctest_skip__ = ["load_earth_deflection"]
+
+
+def load_earth_deflection(
+ resolution: Literal[
+ "01d", "30m", "20m", "15m", "10m", "06m", "05m", "04m", "03m", "02m", "01m"
+ ] = "01d",
+ region: Sequence[float] | str | None = None,
+ registration: Literal["gridline", "pixel", None] = None,
+ component: Literal["east", "north"] = "east",
+) -> xr.DataArray:
+ r"""
+ Load the IGPP Earth east-west and north-south deflection datasets in various
+ resolutions.
+
+ .. list-table::
+ :widths: 50 50
+ :header-rows: 1
+
+ * - IGPP Earth east-west deflection
+ - IGPP Earth north-south deflection
+ * - .. figure:: https://www.generic-mapping-tools.org/remote-datasets/_images/GMT_earth_edefl.jpg
+ - .. figure:: https://www.generic-mapping-tools.org/remote-datasets/_images/GMT_earth_ndefl.jpg
+
+ The grids are downloaded to a user data directory (usually
+ ``~/.gmt/server/earth/earth_edefl/`` and ``~/.gmt/server/earth/earth_ndefl/`` the
+ first time you invoke this function. Afterwards, it will load the grid from the
+ data directory. So you'll need an internet connection the first time around.
+
+ These grids can also be accessed by passing in the file name
+ **@**\ *earth_defl_type*\_\ *res*\[_\ *reg*] to any grid processing function or
+ plotting method. *earth_defl_type* is the GMT name for the dataset. The available
+ options are **earth_edefl** and **earth_ndefl**. *res* is the grid resolution (see
+ below), and *reg* is the grid registration type (**p** for pixel registration or
+ **g** for gridline registration).
+
+ The default color palette table (CPTs) for this dataset is *@earth_defl.cpt*. It's
+ implicitly used when passing in the file name of the dataset to any grid plotting
+ method if no CPT is explicitly specified. When the dataset is loaded and plotted as
+ an :class:`xarray.DataArray` object, the default CPT is ignored, and GMT's default
+ CPT (*turbo*) is used. To use the dataset-specific CPT, you need to explicitly set
+ ``cmap="@earth_defl.cpt"``.
+
+ Refer to :gmt-datasets:`earth-edefl.html` and :gmt-datasets:`earth-ndefl.html` for
+ more details about available datasets, including version information and references.
+
+ Parameters
+ ----------
+ resolution
+ The grid resolution. The suffix ``d`` and ``m`` stand for arc-degrees and
+ arc-minutes.
+ region
+ The subregion of the grid to load, in the form of a sequence [*xmin*, *xmax*,
+ *ymin*, *ymax*] or an ISO country code. Required for grids with resolutions
+ higher than 5 arc-minutes (i.e., ``"05m"``).
+ registration
+ Grid registration type. Either ``"pixel"`` for pixel registration or
+ ``"gridline"`` for gridline registration. Default is ``None``, which means
+ ``"gridline"`` for all resolutions except ``"01m"`` which is ``"pixel"`` only.
+ component
+ By default, the east-west deflection (``component="east"``) is returned,
+ set ``component="north"`` to return the north-south deflection.
+
+ Returns
+ -------
+ grid
+ The Earth east-west or north-south deflection grid. Coordinates are latitude
+ and longitude in degrees. Deflection values are in micro-radians, where
+ positive (negative) values indicate a deflection to the east or north (west
+ or south).
+
+ Note
+ ----
+ The registration and coordinate system type of the returned
+ :class:`xarray.DataArray` grid can be accessed via the GMT accessors (i.e.,
+ ``grid.gmt.registration`` and ``grid.gmt.gtype`` respectively). However, these
+ properties may be lost after specific grid operations (such as slicing) and will
+ need to be manually set before passing the grid to any PyGMT data processing or
+ plotting functions. Refer to :class:`pygmt.GMTDataArrayAccessor` for detailed
+ explanations and workarounds.
+
+ Examples
+ --------
+
+ >>> from pygmt.datasets import load_earth_deflection
+ >>> # load the default grid for east-west deflection (gridline-registered
+ >>> # 1 arc-degree grid)
+ >>> grid = load_earth_deflection()
+ >>> # load the default grid for north-south deflection
+ >>> grid = load_earth_deflection(component="north")
+ >>> # load the 30 arc-minutes grid with "gridline" registration
+ >>> grid = load_earth_deflection(resolution="30m", registration="gridline")
+ >>> # load high-resolution (5 arc-minutes) grid for a specific region
+ >>> grid = load_earth_deflection(
+ ... resolution="05m", region=[120, 160, 30, 60], registration="gridline"
+ ... )
+ """
+ prefix = "earth_ndefl" if component == "north" else "earth_edefl"
+ grid = _load_remote_dataset(
+ name=prefix,
+ prefix=prefix,
+ resolution=resolution,
+ region=region,
+ registration=registration,
+ )
+ return grid
diff --git a/pygmt/datasets/earth_dist.py b/pygmt/datasets/earth_dist.py
new file mode 100644
index 00000000000..4897c475b43
--- /dev/null
+++ b/pygmt/datasets/earth_dist.py
@@ -0,0 +1,105 @@
+"""
+Function to download the GSHHG Earth distance to shoreline dataset from the GMT data
+server, and load as :class:`xarray.DataArray`.
+
+The grids are available in various resolutions.
+"""
+
+from collections.abc import Sequence
+from typing import Literal
+
+import xarray as xr
+from pygmt.datasets.load_remote_dataset import _load_remote_dataset
+
+__doctest_skip__ = ["load_earth_dist"]
+
+
+def load_earth_dist(
+ resolution: Literal[
+ "01d", "30m", "20m", "15m", "10m", "06m", "05m", "04m", "03m", "02m", "01m"
+ ] = "01d",
+ region: Sequence[float] | str | None = None,
+ registration: Literal["gridline", "pixel"] = "gridline",
+) -> xr.DataArray:
+ r"""
+ Load the GSHHG Earth distance to shoreline dataset in various resolutions.
+
+ .. figure:: https://www.generic-mapping-tools.org/remote-datasets/_images/GMT_earth_dist.jpg
+ :width: 80 %
+ :align: center
+
+ GSHHG Earth distance to shoreline dataset.
+
+ The grids are downloaded to a user data directory (usually
+ ``~/.gmt/server/earth/earth_dist/``) the first time you invoke this function.
+ Afterwards, it will load the grid from the data directory. So you'll need an
+ internet connection the first time around.
+
+ These grids can also be accessed by passing in the file name
+ **@earth_dist**\_\ *res*\[_\ *reg*] to any grid processing function or plotting
+ method. *res* is the grid resolution (see below), and *reg* is the grid registration
+ type (**p** for pixel registration or **g** for gridline registration).
+
+ The default color palette table (CPT) for this dataset is *@earth_dist.cpt*. It's
+ implicitly used when passing in the file name of the dataset to any grid plotting
+ method if no CPT is explicitly specified. When the dataset is loaded and plotted
+ as an :class:`xarray.DataArray` object, the default CPT is ignored, and GMT's
+ default CPT (*turbo*) is used. To use the dataset-specific CPT, you need to
+ explicitly set ``cmap="@earth_dist.cpt"``.
+
+ Refer to :gmt-datasets:`earth-dist.html` for more details about available datasets,
+ including version information and references.
+
+ Parameters
+ ----------
+ resolution
+ The grid resolution. The suffix ``d`` and ``m`` stand for arc-degrees and
+ arc-minutes.
+ region
+ The subregion of the grid to load, in the form of a sequence [*xmin*, *xmax*,
+ *ymin*, *ymax*] or an ISO country code. Required for grids with resolutions
+ higher than 5 arc-minutes (i.e., ``"05m"``).
+ registration
+ Grid registration type. Either ``"pixel"`` for pixel registration or
+ ``"gridline"`` for gridline registration.
+
+ Returns
+ -------
+ grid
+ The GSHHG Earth distance to shoreline grid. Coordinates are latitude and
+ longitude in degrees. Distances are in kilometers, where positive (negative)
+ values mean land to coastline (ocean to coastline).
+
+ Note
+ ----
+ The registration and coordinate system type of the returned
+ :class:`xarray.DataArray` grid can be accessed via the GMT accessors (i.e.,
+ ``grid.gmt.registration`` and ``grid.gmt.gtype`` respectively). However, these
+ properties may be lost after specific grid operations (such as slicing) and will
+ need to be manually set before passing the grid to any PyGMT data processing or
+ plotting functions. Refer to :class:`pygmt.GMTDataArrayAccessor` for detailed
+ explanations and workarounds.
+
+ Examples
+ --------
+
+ >>> from pygmt.datasets import load_earth_dist
+ >>> # load the default grid (gridline-registered 1 arc-degree grid)
+ >>> grid = load_earth_dist()
+ >>> # load the 30 arc-minutes grid with "gridline" registration
+ >>> grid = load_earth_dist(resolution="30m", registration="gridline")
+ >>> # load high-resolution (5 arc-minutes) grid for a specific region
+ >>> grid = load_earth_dist(
+ ... resolution="05m",
+ ... region=[120, 160, 30, 60],
+ ... registration="gridline",
+ ... )
+ """
+ grid = _load_remote_dataset(
+ name="earth_dist",
+ prefix="earth_dist",
+ resolution=resolution,
+ region=region,
+ registration=registration,
+ )
+ return grid
diff --git a/pygmt/datasets/earth_free_air_anomaly.py b/pygmt/datasets/earth_free_air_anomaly.py
index da48977d688..d85911496d6 100644
--- a/pygmt/datasets/earth_free_air_anomaly.py
+++ b/pygmt/datasets/earth_free_air_anomaly.py
@@ -1,6 +1,6 @@
"""
-Function to download the IGPP Earth free-air anomaly dataset from the GMT data server,
-and load as :class:`xarray.DataArray`.
+Function to download the IGPP Earth free-air anomaly and uncertainty datasets from
+the GMT data server, and load as :class:`xarray.DataArray`.
The grids are available in various resolutions.
"""
@@ -20,36 +20,43 @@ def load_earth_free_air_anomaly(
] = "01d",
region: Sequence[float] | str | None = None,
registration: Literal["gridline", "pixel", None] = None,
+ uncertainty: bool = False,
) -> xr.DataArray:
r"""
- Load the IGPP Earth free-air anomaly dataset in various resolutions.
+ Load the IGPP Earth free-air anomaly and uncertainty datasets in various
+ resolutions.
- .. figure:: https://www.generic-mapping-tools.org/remote-datasets/_images/GMT_earth_faa.jpg
- :width: 80 %
- :align: center
+ .. list-table::
+ :widths: 50 50
+ :header-rows: 1
- IGPP Earth free-air anomaly dataset.
+ * - IGPP Earth free-air anomaly
+ - IGPP Earth free-air anomaly uncertainty
+ * - .. figure:: https://www.generic-mapping-tools.org/remote-datasets/_images/GMT_earth_faa.jpg
+ - .. figure:: https://www.generic-mapping-tools.org/remote-datasets/_images/GMT_earth_faaerror.jpg
- The grids are downloaded to a user data directory
- (usually ``~/.gmt/server/earth/earth_faa/``) the first time you invoke
- this function. Afterwards, it will load the grid from the data directory.
- So you'll need an internet connection the first time around.
+ The grids are downloaded to a user data directory (usually
+ ``~/.gmt/server/earth/earth_faa/`` or ``~/.gmt/server/earth/earth_faaerror/``) the
+ first time you invoke this function. Afterwards, it will load the grid from data
+ directory. So you'll need an internet connection the first time around.
These grids can also be accessed by passing in the file name
- **@earth_faa**\_\ *res*\[_\ *reg*] to any grid processing function or
- plotting method. *res* is the grid resolution (see below), and *reg* is
- the grid registration type (**p** for pixel registration or **g** for
- gridline registration).
-
- The default color palette table (CPT) for this dataset is *@earth_faa.cpt*.
- It's implicitly used when passing in the file name of the dataset to any
- grid plotting method if no CPT is explicitly specified. When the dataset
- is loaded and plotted as an :class:`xarray.DataArray` object, the default
- CPT is ignored, and GMT's default CPT (*turbo*) is used. To use the
- dataset-specific CPT, you need to explicitly set ``cmap="@earth_faa.cpt"``.
-
- Refer to :gmt-datasets:`earth-faa.html` for more details about available
- datasets, including version information and references.
+ **@earth_faa_type**\_\ *res*\[_\ *reg*] to any grid processing function or
+ plotting method. *earth_faa_type* is the GMT name for the dataset. The available
+ options are **earth_faa** and **earth_faaerror**. *res* is the grid resolution (see
+ below), and *reg* is the grid registration type (**p** for pixel registration or
+ **g** for gridline registration).
+
+ The default color palette tables (CPTs) for these datasets are *@earth_faa.cpt* and
+ *@earth_faaerror.cpt*. The dataset-specific CPT is implicitly used when passing in
+ the file name of the dataset to any grid plotting method if no CPT is explicitly
+ specified. When the dataset is loaded and plotted as an :class:`xarray.DataArray`
+ object, the default CPT is ignored, and GMT's default CPT (*turbo*) is used. To use
+ the dataset-specific CPT, you need to explicitly set ``cmap="@earth_faa.cpt"`` or
+ ``cmap="@earth_faaerror.cpt"``.
+
+ Refer to :gmt-datasets:`earth-faa.html` and :gmt-datasets:`earth-faaerror.html` for
+ more details about available datasets, including version information and references.
Parameters
----------
@@ -62,26 +69,28 @@ def load_earth_free_air_anomaly(
higher than 5 arc-minutes (i.e., ``"05m"``).
registration
Grid registration type. Either ``"pixel"`` for pixel registration or
- ``"gridline"`` for gridline registration. Default is ``None``, means
- ``"gridline"`` for all resolutions except ``"01m"`` which is
- ``"pixel"`` only.
+ ``"gridline"`` for gridline registration. Default is ``None``, which means
+ ``"gridline"`` for all resolutions except ``"01m"`` which is ``"pixel"``
+ only.
+ uncertainty
+ By default, the Earth free-air anomaly values are returned. Set to ``True`` to
+ return the related uncertainties instead.
Returns
-------
grid
- The Earth free-air anomaly grid. Coordinates are latitude and
- longitude in degrees. Units are in mGal.
+ The Earth free-air anomaly (uncertainty) grid. Coordinates are latitude and
+ longitude in degrees. Values and uncertainties are in mGal.
Note
----
The registration and coordinate system type of the returned
- :class:`xarray.DataArray` grid can be accessed via the GMT accessors
- (i.e., ``grid.gmt.registration`` and ``grid.gmt.gtype`` respectively).
- However, these properties may be lost after specific grid operations (such
- as slicing) and will need to be manually set before passing the grid to any
- PyGMT data processing or plotting functions. Refer to
- :class:`pygmt.GMTDataArrayAccessor` for detailed explanations and
- workarounds.
+ :class:`xarray.DataArray` grid can be accessed via the GMT accessors (i.e.,
+ ``grid.gmt.registration`` and ``grid.gmt.gtype`` respectively). However, these
+ properties may be lost after specific grid operations (such as slicing) and will
+ need to be manually set before passing the grid to any PyGMT data processing or
+ plotting functions. Refer to :class:`pygmt.GMTDataArrayAccessor` for detailed
+ explanations and workarounds.
Examples
--------
@@ -89,18 +98,19 @@ def load_earth_free_air_anomaly(
>>> from pygmt.datasets import load_earth_free_air_anomaly
>>> # load the default grid (gridline-registered 1 arc-degree grid)
>>> grid = load_earth_free_air_anomaly()
+ >>> # load the uncertainties related to the default grid
+ >>> grid = load_earth_free_air_anomaly(uncertainty=True)
>>> # load the 30 arc-minutes grid with "gridline" registration
>>> grid = load_earth_free_air_anomaly(resolution="30m", registration="gridline")
>>> # load high-resolution (5 arc-minutes) grid for a specific region
>>> grid = load_earth_free_air_anomaly(
- ... resolution="05m",
- ... region=[120, 160, 30, 60],
- ... registration="gridline",
+ ... resolution="05m", region=[120, 160, 30, 60], registration="gridline"
... )
"""
+ prefix = "earth_faaerror" if uncertainty is True else "earth_faa"
grid = _load_remote_dataset(
- name="earth_faa",
- prefix="earth_faa",
+ name=prefix,
+ prefix=prefix,
resolution=resolution,
region=region,
registration=registration,
diff --git a/pygmt/datasets/earth_magnetic_anomaly.py b/pygmt/datasets/earth_magnetic_anomaly.py
index 70ffcf54248..463f7cf93b9 100644
--- a/pygmt/datasets/earth_magnetic_anomaly.py
+++ b/pygmt/datasets/earth_magnetic_anomaly.py
@@ -76,10 +76,10 @@ def load_earth_magnetic_anomaly(
higher than 5 arc-minutes (i.e., ``"05m"``).
registration
Grid registration type. Either ``"pixel"`` for pixel registration or
- ``"gridline"`` for gridline registration. Default is ``None``, means
+ ``"gridline"`` for gridline registration. Default is ``None``, which means
``"gridline"`` for all resolutions except ``"02m"`` for
- ``data_source="emag2"`` or ``data_source="emag2_4km"``, which are
- ``"pixel"`` only.
+ ``data_source="emag2"`` or ``data_source="emag2_4km"``, which are ``"pixel"``
+ only.
data_source
Select the source of the magnetic anomaly data. Available options are:
diff --git a/pygmt/datasets/earth_mean_dynamic_topography.py b/pygmt/datasets/earth_mean_dynamic_topography.py
new file mode 100644
index 00000000000..4ca50e476be
--- /dev/null
+++ b/pygmt/datasets/earth_mean_dynamic_topography.py
@@ -0,0 +1,103 @@
+"""
+Function to download the CNES Earth mean dynamic topography dataset from the GMT data
+server, and load as :class:`xarray.DataArray`.
+
+The grids are available in various resolutions.
+"""
+
+from collections.abc import Sequence
+from typing import Literal
+
+import xarray as xr
+from pygmt.datasets.load_remote_dataset import _load_remote_dataset
+
+__doctest_skip__ = ["load_earth_mean_dynamic_topography"]
+
+
+def load_earth_mean_dynamic_topography(
+ resolution: Literal["01d", "30m", "20m", "15m", "10m", "07m"] = "01d",
+ region: Sequence[float] | str | None = None,
+ registration: Literal["gridline", "pixel"] = "gridline",
+) -> xr.DataArray:
+ r"""
+ Load the CNES Earth mean dynamic topography dataset in various resolutions.
+
+ .. figure:: https://www.generic-mapping-tools.org/remote-datasets/_images/GMT_earth_mdt.jpg
+ :width: 80 %
+ :align: center
+
+ CNES Earth mean dynamic topography dataset.
+
+ The grids are downloaded to a user data directory (usually
+ ``~/.gmt/server/earth/earth_mdt/``) the first time you invoke this function.
+ Afterwards, it will load the grid from the data directory. So you'll need an
+ internet connection the first time around.
+
+ These grids can also be accessed by passing in the file name
+ **@earth_mdt**\_\ *res*\[_\ *reg*] to any grid processing function or plotting
+ method. *res* is the grid resolution (see below), and *reg* is the grid registration
+ type (**p** for pixel registration or **g** for gridline registration).
+
+ The default color palette table (CPT) for this dataset is *@earth_mdt.cpt*. It's
+ implicitly used when passing in the file name of the dataset to any grid plotting
+ method if no CPT is explicitly specified. When the dataset is loaded and plotted
+ as an :class:`xarray.DataArray` object, the default CPT is ignored, and GMT's
+ default CPT (*turbo*) is used. To use the dataset-specific CPT, you need to
+ explicitly set ``cmap="@earth_mdt.cpt"``.
+
+ Refer to :gmt-datasets:`earth-mdt.html` for more details about available datasets,
+ including version information and references.
+
+ Parameters
+ ----------
+ resolution
+ The grid resolution. The suffix ``d`` and ``m`` stand for arc-degrees and
+ arc-minutes. Note that ``"07m"`` refers to a resolution of 7.5 arc-minutes.
+ region
+ The subregion of the grid to load, in the form of a sequence [*xmin*, *xmax*,
+ *ymin*, *ymax*] or an ISO country code.
+ registration
+ Grid registration type. Either ``"pixel"`` for pixel registration or
+ ``"gridline"`` for gridline registration.
+
+ Returns
+ -------
+ grid
+ The CNES Earth mean dynamic topography grid. Coordinates are latitude and
+ longitude in degrees. Values are in meters.
+
+ Note
+ ----
+ The registration and coordinate system type of the returned
+ :class:`xarray.DataArray` grid can be accessed via the GMT accessors (i.e.,
+ ``grid.gmt.registration`` and ``grid.gmt.gtype`` respectively). However, these
+ properties may be lost after specific grid operations (such as slicing) and will
+ need to be manually set before passing the grid to any PyGMT data processing or
+ plotting functions. Refer to :class:`pygmt.GMTDataArrayAccessor` for detailed
+ explanations and workarounds.
+
+ Examples
+ --------
+
+ >>> from pygmt.datasets import load_earth_mean_dynamic_topography
+ >>> # load the default grid (gridline-registered 1 arc-degree grid)
+ >>> grid = load_earth_mean_dynamic_topography()
+ >>> # load the 30 arc-minutes grid with "gridline" registration
+ >>> grid = load_earth_mean_dynamic_topography(
+ ... resolution="30m", registration="gridline"
+ ... )
+ >>> # load high-resolution (7 arc-minutes) grid for a specific region
+ >>> grid = load_earth_mean_dynamic_topography(
+ ... resolution="07m",
+ ... region=[120, 160, 30, 60],
+ ... registration="gridline",
+ ... )
+ """
+ grid = _load_remote_dataset(
+ name="earth_mdt",
+ prefix="earth_mdt",
+ resolution=resolution,
+ region=region,
+ registration=registration,
+ )
+ return grid
diff --git a/pygmt/datasets/earth_mean_sea_surface.py b/pygmt/datasets/earth_mean_sea_surface.py
new file mode 100644
index 00000000000..f4856d98626
--- /dev/null
+++ b/pygmt/datasets/earth_mean_sea_surface.py
@@ -0,0 +1,104 @@
+"""
+Function to download the CNES Earth mean sea surface dataset from the GMT data
+server, and load as :class:`xarray.DataArray`.
+
+The grids are available in various resolutions.
+"""
+
+from collections.abc import Sequence
+from typing import Literal
+
+import xarray as xr
+from pygmt.datasets.load_remote_dataset import _load_remote_dataset
+
+__doctest_skip__ = ["load_earth_mean_sea_surface"]
+
+
+def load_earth_mean_sea_surface(
+ resolution: Literal[
+ "01d", "30m", "20m", "15m", "10m", "06m", "05m", "04m", "03m", "02m", "01m"
+ ] = "01d",
+ region: Sequence[float] | str | None = None,
+ registration: Literal["gridline", "pixel"] = "gridline",
+) -> xr.DataArray:
+ r"""
+ Load the CNES Earth mean sea surface dataset in various resolutions.
+
+ .. figure:: https://www.generic-mapping-tools.org/remote-datasets/_images/GMT_earth_mss.jpg
+ :width: 80 %
+ :align: center
+
+ CNES Earth mean sea surface dataset.
+
+ The grids are downloaded to a user data directory (usually
+ ``~/.gmt/server/earth/earth_mss/``) the first time you invoke this function.
+ Afterwards, it will load the grid from the data directory. So you'll need an
+ internet connection the first time around.
+
+ These grids can also be accessed by passing in the file name
+ **@earth_mss**\_\ *res*\[_\ *reg*] to any grid processing function or plotting
+ method. *res* is the grid resolution (see below), and *reg* is the grid registration
+ type (**p** for pixel registration or **g** for gridline registration).
+
+ The default color palette table (CPT) for this dataset is *@earth_mss.cpt*. It's
+ implicitly used when passing in the file name of the dataset to any grid plotting
+ method if no CPT is explicitly specified. When the dataset is loaded and plotted
+ as an :class:`xarray.DataArray` object, the default CPT is ignored, and GMT's
+ default CPT (*turbo*) is used. To use the dataset-specific CPT, you need to
+ explicitly set ``cmap="@earth_mss.cpt"``.
+
+ Refer to :gmt-datasets:`earth-mss.html` for more details about available datasets,
+ including version information and references.
+
+ Parameters
+ ----------
+ resolution
+ The grid resolution. The suffix ``d`` and ``m`` stand for arc-degrees and
+ arc-minutes.
+ region
+ The subregion of the grid to load, in the form of a sequence [*xmin*, *xmax*,
+ *ymin*, *ymax*] or an ISO country code. Required for grids with resolutions
+ higher than 5 arc-minutes (i.e., ``"05m"``).
+ registration
+ Grid registration type. Either ``"pixel"`` for pixel registration or
+ ``"gridline"`` for gridline registration.
+
+ Returns
+ -------
+ grid
+ The CNES Earth mean sea surface grid. Coordinates are latitude and
+ longitude in degrees. Values are in meters.
+
+ Note
+ ----
+ The registration and coordinate system type of the returned
+ :class:`xarray.DataArray` grid can be accessed via the GMT accessors (i.e.,
+ ``grid.gmt.registration`` and ``grid.gmt.gtype`` respectively). However, these
+ properties may be lost after specific grid operations (such as slicing) and will
+ need to be manually set before passing the grid to any PyGMT data processing or
+ plotting functions. Refer to :class:`pygmt.GMTDataArrayAccessor` for detailed
+ explanations and workarounds.
+
+ Examples
+ --------
+
+ >>> from pygmt.datasets import load_earth_mean_sea_surface
+ >>> # load the default grid (gridline-registered 1 arc-degree grid)
+ >>> grid = load_earth_mean_sea_surface()
+ >>> # load the 30 arc-minutes grid with "gridline" registration
+ >>> grid = load_earth_mean_sea_surface(resolution="30m", registration="gridline")
+ >>> # load high-resolution (5 arc-minutes) grid for a specific region
+ >>> grid = load_earth_mean_sea_surface(
+ ... resolution="05m",
+ ... region=[120, 160, 30, 60],
+ ... registration="gridline",
+ ... )
+ """
+ grid = _load_remote_dataset(
+ name="earth_mss",
+ prefix="earth_mss",
+ resolution=resolution,
+ region=region,
+ registration=registration,
+ )
+ return grid
diff --git a/pygmt/datasets/earth_relief.py b/pygmt/datasets/earth_relief.py
index 3ff03613491..f0090ca3bd2 100644
--- a/pygmt/datasets/earth_relief.py
+++ b/pygmt/datasets/earth_relief.py
@@ -83,9 +83,9 @@ def load_earth_relief(
higher than 5 arc-minutes (i.e., ``"05m"``).
registration
Grid registration type. Either ``"pixel"`` for pixel registration or
- ``"gridline"`` for gridline registration. Default is ``None``, means
- ``"gridline"`` for all resolutions except ``"15s"`` which is
- ``"pixel"`` only.
+ ``"gridline"`` for gridline registration. Default is ``None``, which means
+ ``"gridline"`` for all resolutions except ``"15s"`` which is ``"pixel"``
+ only.
data_source
Select the source for the Earth relief data. Available options are:
diff --git a/pygmt/datasets/earth_vertical_gravity_gradient.py b/pygmt/datasets/earth_vertical_gravity_gradient.py
index 2ebba4563bc..bcf00099fc2 100644
--- a/pygmt/datasets/earth_vertical_gravity_gradient.py
+++ b/pygmt/datasets/earth_vertical_gravity_gradient.py
@@ -62,9 +62,9 @@ def load_earth_vertical_gravity_gradient(
higher than 5 arc-minutes (i.e., ``"05m"``).
registration
Grid registration type. Either ``"pixel"`` for pixel registration or
- ``"gridline"`` for gridline registration. Default is ``None``, means
- ``"gridline"`` for all resolutions except ``"01m"`` which is
- ``"pixel"`` only.
+ ``"gridline"`` for gridline registration. Default is ``None``, which means
+ ``"gridline"`` for all resolutions except ``"01m"`` which is ``"pixel"``
+ only.
Returns
-------
diff --git a/pygmt/datasets/load_remote_dataset.py b/pygmt/datasets/load_remote_dataset.py
index 168a93583b2..41fe729d0e0 100644
--- a/pygmt/datasets/load_remote_dataset.py
+++ b/pygmt/datasets/load_remote_dataset.py
@@ -39,7 +39,9 @@ class GMTRemoteDataset(NamedTuple):
Attributes
----------
description
- The name assigned as an attribute to the DataArray.
+ The name assigned as an attribute to the DataArray.
+ kind
+ The kind of the dataset source. Valid values are ``"grid"`` and ``"image"``.
units
The units of the values in the DataArray.
resolutions
@@ -49,6 +51,7 @@ class GMTRemoteDataset(NamedTuple):
"""
description: str
+ kind: Literal["grid", "image"]
units: str | None
resolutions: dict[str, Resolution]
extra_attributes: dict[str, Any]
@@ -57,6 +60,7 @@ class GMTRemoteDataset(NamedTuple):
datasets = {
"earth_age": GMTRemoteDataset(
description="EarthByte Earth seafloor crustal age",
+ kind="grid",
units="Myr",
extra_attributes={"horizontal_datum": "WGS84"},
resolutions={
@@ -75,6 +79,7 @@ class GMTRemoteDataset(NamedTuple):
),
"earth_day": GMTRemoteDataset(
description="NASA Day Images",
+ kind="image",
units=None,
extra_attributes={"long_name": "blue_marble", "horizontal_datum": "WGS84"},
resolutions={
@@ -92,8 +97,66 @@ class GMTRemoteDataset(NamedTuple):
"30s": Resolution("30s", registrations=["pixel"]),
},
),
+ "earth_dist": GMTRemoteDataset(
+ description="GSHHG Earth distance to shoreline",
+ kind="grid",
+ units="kilometers",
+ extra_attributes={"horizontal_datum": "WGS84"},
+ resolutions={
+ "01d": Resolution("01d"),
+ "30m": Resolution("30m"),
+ "20m": Resolution("20m"),
+ "15m": Resolution("15m"),
+ "10m": Resolution("10m"),
+ "06m": Resolution("06m"),
+ "05m": Resolution("05m", tiled=True),
+ "04m": Resolution("04m", tiled=True),
+ "03m": Resolution("03m", tiled=True),
+ "02m": Resolution("02m", tiled=True),
+ "01m": Resolution("01m", registrations=["gridline"], tiled=True),
+ },
+ ),
+ "earth_edefl": GMTRemoteDataset(
+ description="IGPP Earth east-west deflection",
+ kind="grid",
+ units="micro-radians",
+ extra_attributes={"horizontal_datum": "WGS84"},
+ resolutions={
+ "01d": Resolution("01d"),
+ "30m": Resolution("30m"),
+ "20m": Resolution("20m"),
+ "15m": Resolution("15m"),
+ "10m": Resolution("10m"),
+ "06m": Resolution("06m"),
+ "05m": Resolution("05m", tiled=True),
+ "04m": Resolution("04m", tiled=True),
+ "03m": Resolution("03m", tiled=True),
+ "02m": Resolution("02m", tiled=True),
+ "01m": Resolution("01m", registrations=["pixel"], tiled=True),
+ },
+ ),
"earth_faa": GMTRemoteDataset(
description="IGPP Earth free-air anomaly",
+ kind="grid",
+ units="mGal",
+ extra_attributes={"horizontal_datum": "WGS84"},
+ resolutions={
+ "01d": Resolution("01d"),
+ "30m": Resolution("30m"),
+ "20m": Resolution("20m"),
+ "15m": Resolution("15m"),
+ "10m": Resolution("10m"),
+ "06m": Resolution("06m"),
+ "05m": Resolution("05m", tiled=True),
+ "04m": Resolution("04m", tiled=True),
+ "03m": Resolution("03m", tiled=True),
+ "02m": Resolution("02m", tiled=True),
+ "01m": Resolution("01m", registrations=["pixel"], tiled=True),
+ },
+ ),
+ "earth_faaerror": GMTRemoteDataset(
+ description="IGPP Earth free-air anomaly errors",
+ kind="grid",
units="mGal",
extra_attributes={"horizontal_datum": "WGS84"},
resolutions={
@@ -112,6 +175,7 @@ class GMTRemoteDataset(NamedTuple):
),
"earth_gebco": GMTRemoteDataset(
description="GEBCO Earth relief",
+ kind="grid",
units="meters",
extra_attributes={"vertical_datum": "EGM96", "horizontal_datum": "WGS84"},
resolutions={
@@ -134,7 +198,8 @@ class GMTRemoteDataset(NamedTuple):
),
"earth_geoid": GMTRemoteDataset(
description="EGM2008 Earth geoid",
- units="m",
+ kind="grid",
+ units="meters",
extra_attributes={"horizontal_datum": "WGS84"},
resolutions={
"01d": Resolution("01d"),
@@ -152,6 +217,7 @@ class GMTRemoteDataset(NamedTuple):
),
"earth_igpp": GMTRemoteDataset(
description="IGPP Earth relief",
+ kind="grid",
units="meters",
extra_attributes={"vertical_datum": "EGM96", "horizontal_datum": "WGS84"},
resolutions={
@@ -174,6 +240,7 @@ class GMTRemoteDataset(NamedTuple):
),
"earth_mag": GMTRemoteDataset(
description="EMAG2 Earth Magnetic Anomaly Model",
+ kind="grid",
units="nT",
extra_attributes={"horizontal_datum": "WGS84"},
resolutions={
@@ -191,6 +258,7 @@ class GMTRemoteDataset(NamedTuple):
),
"earth_mask": GMTRemoteDataset(
description="GSHHG Earth mask",
+ kind="grid",
units=None,
extra_attributes={"horizontal_datum": "WGS84"},
resolutions={
@@ -209,8 +277,28 @@ class GMTRemoteDataset(NamedTuple):
"15s": Resolution("15s"),
},
),
+ "earth_mss": GMTRemoteDataset(
+ description="CNES Earth mean sea surface",
+ kind="grid",
+ units="meters",
+ extra_attributes={"horizontal_datum": "WGS84"},
+ resolutions={
+ "01d": Resolution("01d"),
+ "30m": Resolution("30m"),
+ "20m": Resolution("20m"),
+ "15m": Resolution("15m"),
+ "10m": Resolution("10m"),
+ "06m": Resolution("06m"),
+ "05m": Resolution("05m", tiled=True),
+ "04m": Resolution("04m", tiled=True),
+ "03m": Resolution("03m", tiled=True),
+ "02m": Resolution("02m", tiled=True),
+ "01m": Resolution("01m", tiled=True, registrations=["gridline"]),
+ },
+ ),
"earth_night": GMTRemoteDataset(
description="NASA Night Images",
+ kind="image",
units=None,
extra_attributes={"long_name": "black_marble", "horizontal_datum": "WGS84"},
resolutions={
@@ -228,8 +316,42 @@ class GMTRemoteDataset(NamedTuple):
"30s": Resolution("30s", registrations=["pixel"]),
},
),
+ "earth_mdt": GMTRemoteDataset(
+ description="CNES Earth mean dynamic topography",
+ kind="grid",
+ units="meters",
+ extra_attributes={"horizontal_datum": "WGS84"},
+ resolutions={
+ "01d": Resolution("01d"),
+ "30m": Resolution("30m"),
+ "20m": Resolution("20m"),
+ "15m": Resolution("15m"),
+ "10m": Resolution("10m"),
+ "07m": Resolution("07m", registrations=["gridline"]),
+ },
+ ),
+ "earth_ndefl": GMTRemoteDataset(
+ description="IGPP Earth north-south deflection",
+ kind="grid",
+ units="micro-radians",
+ extra_attributes={"horizontal_datum": "WGS84"},
+ resolutions={
+ "01d": Resolution("01d"),
+ "30m": Resolution("30m"),
+ "20m": Resolution("20m"),
+ "15m": Resolution("15m"),
+ "10m": Resolution("10m"),
+ "06m": Resolution("06m"),
+ "05m": Resolution("05m", tiled=True),
+ "04m": Resolution("04m", tiled=True),
+ "03m": Resolution("03m", tiled=True),
+ "02m": Resolution("02m", tiled=True),
+ "01m": Resolution("01m", registrations=["pixel"], tiled=True),
+ },
+ ),
"earth_vgg": GMTRemoteDataset(
description="IGPP Earth vertical gravity gradient",
+ kind="grid",
units="Eotvos",
extra_attributes={"horizontal_datum": "WGS84"},
resolutions={
@@ -248,6 +370,7 @@ class GMTRemoteDataset(NamedTuple):
),
"earth_wdmam": GMTRemoteDataset(
description="WDMAM World Digital Magnetic Anomaly Map",
+ kind="grid",
units="nT",
extra_attributes={"horizontal_datum": "WGS84"},
resolutions={
@@ -264,6 +387,7 @@ class GMTRemoteDataset(NamedTuple):
),
"mars_relief": GMTRemoteDataset(
description="NASA Mars (MOLA) relief",
+ kind="grid",
units="meters",
extra_attributes={},
resolutions={
@@ -285,6 +409,7 @@ class GMTRemoteDataset(NamedTuple):
),
"moon_relief": GMTRemoteDataset(
description="USGS Moon (LOLA) relief",
+ kind="grid",
units="meters",
extra_attributes={},
resolutions={
@@ -306,6 +431,7 @@ class GMTRemoteDataset(NamedTuple):
),
"mercury_relief": GMTRemoteDataset(
description="USGS Mercury relief",
+ kind="grid",
units="meters",
extra_attributes={},
resolutions={
@@ -325,6 +451,7 @@ class GMTRemoteDataset(NamedTuple):
),
"pluto_relief": GMTRemoteDataset(
description="USGS Pluto relief",
+ kind="grid",
units="meters",
extra_attributes={},
resolutions={
@@ -344,6 +471,7 @@ class GMTRemoteDataset(NamedTuple):
),
"venus_relief": GMTRemoteDataset(
description="NASA Magellan Venus relief",
+ kind="grid",
units="meters",
extra_attributes={},
resolutions={
@@ -442,15 +570,16 @@ def _load_remote_dataset(
raise GMTInvalidInput(msg)
fname = f"@{prefix}_{resolution}_{reg}"
- kind = "image" if name in {"earth_day", "earth_night"} else "grid"
- kwdict = {"R": region, "T": {"grid": "g", "image": "i"}[kind]}
+ kwdict = {"R": region, "T": {"grid": "g", "image": "i"}[dataset.kind]}
with Session() as lib:
- with lib.virtualfile_out(kind=kind) as voutgrd:
+ with lib.virtualfile_out(kind=dataset.kind) as voutgrd:
lib.call_module(
module="read",
args=[fname, voutgrd, *build_arg_list(kwdict)],
)
- grid = lib.virtualfile_to_raster(kind=kind, outgrid=None, vfname=voutgrd)
+ grid = lib.virtualfile_to_raster(
+ kind=dataset.kind, outgrid=None, vfname=voutgrd
+ )
# Full path to the grid if not tiled grids.
source = which(fname, download="a") if not resinfo.tiled else None
diff --git a/pygmt/datasets/mars_relief.py b/pygmt/datasets/mars_relief.py
index 1d2cb631fd9..e04f048d42f 100644
--- a/pygmt/datasets/mars_relief.py
+++ b/pygmt/datasets/mars_relief.py
@@ -67,14 +67,15 @@ def load_mars_relief(
----------
resolution
The grid resolution. The suffix ``d``, ``m`` and ``s`` stand for arc-degrees,
- arc-minutes and arc-seconds.
+ arc-minutes and arc-seconds. Note that ``"12s"`` refers to a resolution of
+ 12.1468873601 arc-seconds.
region
The subregion of the grid to load, in the form of a sequence [*xmin*, *xmax*,
*ymin*, *ymax*] or an ISO country code. Required for grids with resolutions
higher than 5 arc-minutes (i.e., ``"05m"``).
registration
Grid registration type. Either ``"pixel"`` for pixel registration or
- ``"gridline"`` for gridline registration. Default is ``None``, means
+ ``"gridline"`` for gridline registration. Default is ``None``, which means
``"gridline"`` for all resolutions except for ``"12s"`` which is ``"pixel"``
only.
diff --git a/pygmt/datasets/mercury_relief.py b/pygmt/datasets/mercury_relief.py
index f3c360c356a..06da8194e4a 100644
--- a/pygmt/datasets/mercury_relief.py
+++ b/pygmt/datasets/mercury_relief.py
@@ -65,14 +65,15 @@ def load_mercury_relief(
----------
resolution
The grid resolution. The suffix ``d``, ``m`` and ``s`` stand for arc-degrees,
- arc-minutes and arc-seconds.
+ arc-minutes and arc-seconds. Note that ``"56s"`` refers to a resolution of
+ 56.25 arc-seconds.
region
The subregion of the grid to load, in the form of a sequence [*xmin*, *xmax*,
*ymin*, *ymax*] or an ISO country code. Required for grids with resolutions
higher than 5 arc-minutes (i.e., ``"05m"``).
registration
Grid registration type. Either ``"pixel"`` for pixel registration or
- ``"gridline"`` for gridline registration. Default is ``None``, means
+ ``"gridline"`` for gridline registration. Default is ``None``, which means
``"gridline"`` for all resolutions except for ``"56s"`` which is ``"pixel"``
only.
diff --git a/pygmt/datasets/moon_relief.py b/pygmt/datasets/moon_relief.py
index 9daab0f47a5..66817f42d08 100644
--- a/pygmt/datasets/moon_relief.py
+++ b/pygmt/datasets/moon_relief.py
@@ -67,14 +67,15 @@ def load_moon_relief(
----------
resolution
The grid resolution. The suffix ``d``, ``m`` and ``s`` stand for arc-degrees,
- arc-minutes and arc-seconds.
+ arc-minutes and arc-seconds. Note that ``"14s"`` refers to a resolution of
+ 14.0625 arc-seconds.
region
The subregion of the grid to load, in the form of a sequence [*xmin*, *xmax*,
*ymin*, *ymax*] or an ISO country code. Required for grids with resolutions
higher than 5 arc-minutes (i.e., ``"05m"``).
registration
Grid registration type. Either ``"pixel"`` for pixel registration or
- ``"gridline"`` for gridline registration. Default is ``None``, means
+ ``"gridline"`` for gridline registration. Default is ``None``, which means
``"gridline"`` for all resolutions except for ``"14s"`` which is ``"pixel"``
only.
diff --git a/pygmt/datasets/pluto_relief.py b/pygmt/datasets/pluto_relief.py
index 620545899da..9a9998d228a 100644
--- a/pygmt/datasets/pluto_relief.py
+++ b/pygmt/datasets/pluto_relief.py
@@ -65,14 +65,15 @@ def load_pluto_relief(
----------
resolution
The grid resolution. The suffix ``d``, ``m`` and ``s`` stand for arc-degrees,
- arc-minutes and arc-seconds.
+ arc-minutes and arc-seconds. Note that ``"52s"`` refers to a resolution of
+ 52.0732883317 arc-seconds.
region
The subregion of the grid to load, in the form of a sequence [*xmin*, *xmax*,
*ymin*, *ymax*] or an ISO country code. Required for grids with resolutions
higher than 5 arc-minutes (i.e., ``"05m"``).
registration
Grid registration type. Either ``"pixel"`` for pixel registration or
- ``"gridline"`` for gridline registration. Default is ``None``, means
+ ``"gridline"`` for gridline registration. Default is ``None``, which means
``"gridline"`` for all resolutions except for ``"52s"`` which is ``"pixel"``
only.
diff --git a/pygmt/datasets/tile_map.py b/pygmt/datasets/tile_map.py
index d18bce423f8..caa18dacd84 100644
--- a/pygmt/datasets/tile_map.py
+++ b/pygmt/datasets/tile_map.py
@@ -166,6 +166,7 @@ def load_tile_map(
"wait": wait,
"max_retries": max_retries,
}
+ # TODO(contextily>=1.5.0): Remove the check for the 'zoom_adjust' parameter.
if zoom_adjust is not None:
if Version(contextily.__version__) < Version("1.5.0"):
msg = (
diff --git a/pygmt/encodings.py b/pygmt/encodings.py
index 0c7b7ddc895..09c749c4c82 100644
--- a/pygmt/encodings.py
+++ b/pygmt/encodings.py
@@ -1,3 +1,4 @@
+# noqa: A005
"""
Character encodings supported by GMT.
diff --git a/pygmt/figure.py b/pygmt/figure.py
index 4163ab52eb1..5c5d4734ce6 100644
--- a/pygmt/figure.py
+++ b/pygmt/figure.py
@@ -6,7 +6,7 @@
import os
from pathlib import Path, PurePath
from tempfile import TemporaryDirectory
-from typing import Literal
+from typing import Literal, overload
try:
import IPython
@@ -95,19 +95,19 @@ class Figure:
122.94, 145.82, 20.53, 45.52
"""
- def __init__(self):
+ def __init__(self) -> None:
self._name = unique_name()
self._preview_dir = TemporaryDirectory(prefix=f"{self._name}-preview-")
self._activate_figure()
- def __del__(self):
+ def __del__(self) -> None:
"""
Clean up the temporary directory that stores the previews.
"""
if hasattr(self, "_preview_dir"):
self._preview_dir.cleanup()
- def _activate_figure(self):
+ def _activate_figure(self) -> None:
"""
Start and/or activate the current figure.
@@ -144,7 +144,7 @@ def savefig(
show: bool = False,
worldfile: bool = False,
**kwargs,
- ):
+ ) -> None:
"""
Save the figure to an image file.
@@ -248,6 +248,7 @@ def savefig(
kwargs.pop("metadata", None)
self.psconvert(prefix=prefix, fmt=fmts[ext], crop=crop, **kwargs)
+ # TODO(GMT>=6.5.0): Remove the workaround for upstream bug in GMT<6.5.0.
# Remove the .pgw world file if exists. Not necessary after GMT 6.5.0.
# See upstream fix https://github.com/GenericMappingTools/gmt/pull/7865
if ext == "tiff":
@@ -267,7 +268,7 @@ def show(
width: int = 500,
waiting: float = 0.5,
**kwargs,
- ):
+ ) -> None:
"""
Display a preview of the figure.
@@ -353,6 +354,14 @@ def show(
)
raise GMTInvalidInput(msg)
+ @overload
+ def _preview(
+ self, fmt: str, dpi: int, as_bytes: Literal[True] = True, **kwargs
+ ) -> bytes: ...
+ @overload
+ def _preview(
+ self, fmt: str, dpi: int, as_bytes: Literal[False] = False, **kwargs
+ ) -> str: ...
def _preview(self, fmt: str, dpi: int, as_bytes: bool = False, **kwargs):
"""
Grab a preview of the figure.
@@ -380,7 +389,7 @@ def _preview(self, fmt: str, dpi: int, as_bytes: bool = False, **kwargs):
return fname.read_bytes()
return fname
- def _repr_png_(self):
+ def _repr_png_(self) -> bytes:
"""
Show a PNG preview if the object is returned in an interactive shell.
@@ -389,7 +398,7 @@ def _repr_png_(self):
png = self._preview(fmt="png", dpi=70, anti_alias=True, as_bytes=True)
return png
- def _repr_html_(self):
+ def _repr_html_(self) -> str:
"""
Show the PNG image embedded in HTML with a controlled width.
@@ -409,6 +418,7 @@ def _repr_html_(self):
grdimage,
grdview,
histogram,
+ hlines,
image,
inset,
legend,
@@ -427,11 +437,12 @@ def _repr_html_(self):
tilemap,
timestamp,
velo,
+ vlines,
wiggle,
)
-def set_display(method: Literal["external", "notebook", "none", None] = None):
+def set_display(method: Literal["external", "notebook", "none", None] = None) -> None:
"""
Set the display method when calling :meth:`pygmt.Figure.show`.
diff --git a/pygmt/helpers/caching.py b/pygmt/helpers/caching.py
index 26648b17060..ea6bed8d4cf 100644
--- a/pygmt/helpers/caching.py
+++ b/pygmt/helpers/caching.py
@@ -5,7 +5,7 @@
from pygmt.src import which
-def cache_data():
+def cache_data() -> None:
"""
Download GMT remote data files used in PyGMT tests and docs to cache folder.
"""
@@ -14,7 +14,10 @@ def cache_data():
# List of GMT remote datasets.
"@earth_age_01d_g",
"@earth_day_01d",
+ "@earth_dist_01d",
+ "@earth_edefl_01d",
"@earth_faa_01d_g",
+ "@earth_faaerror_01d_g",
"@earth_gebco_01d_g",
"@earth_gebcosi_01d_g",
"@earth_gebcosi_15m_p",
@@ -22,6 +25,10 @@ def cache_data():
"@earth_mag_01d_g",
"@earth_mag4km_01d_g",
"@earth_mask_01d_g",
+ "@earth_mdt_01d_g",
+ "@earth_mdt_07m_g",
+ "@earth_mss_01d_g",
+ "@earth_ndefl_01d",
"@earth_night_01d",
"@earth_relief_01d_g",
"@earth_relief_01d_p",
@@ -45,10 +52,15 @@ def cache_data():
"@N00W030.earth_age_01m_g.nc",
"@N30E060.earth_age_01m_g.nc",
"@N30E090.earth_age_01m_g.nc",
+ "@N00W030.earth_dist_01m_g.nc",
+ "@N00W030.earth_edefl_01m_p.nc",
"@N00W030.earth_faa_01m_p.nc",
+ "@N00W030.earth_faaerror_01m_p.nc",
"@N00W030.earth_geoid_01m_g.nc",
"@S30W060.earth_mag_02m_p.nc",
"@S30W120.earth_mag4km_02m_p.nc",
+ "@N30E090.earth_mss_01m_g.nc",
+ "@N30E090.earth_ndefl_01m_p.nc",
"@N00W090.earth_relief_03m_p.nc",
"@N00E135.earth_relief_30s_g.nc",
"@N00W010.earth_relief_15s_p.nc",
diff --git a/pygmt/helpers/decorators.py b/pygmt/helpers/decorators.py
index a22d49334af..3c4f9dd5510 100644
--- a/pygmt/helpers/decorators.py
+++ b/pygmt/helpers/decorators.py
@@ -37,17 +37,17 @@
(using ``binary="o"``), where *ncols* is the number of data columns
of *type*, which must be one of:
- - **c** - int8_t (1-byte signed char)
- - **u** - uint8_t (1-byte unsigned char)
- - **h** - int16_t (2-byte signed int)
- - **H** - uint16_t (2-byte unsigned int)
- - **i** - int32_t (4-byte signed int)
- - **I** - uint32_t (4-byte unsigned int)
- - **l** - int64_t (8-byte signed int)
- - **L** - uint64_t (8-byte unsigned int)
- - **f** - 4-byte single-precision float
- - **d** - 8-byte double-precision float
- - **x** - use to skip *ncols* anywhere in the record
+ - **c**: int8_t (1-byte signed char)
+ - **u**: uint8_t (1-byte unsigned char)
+ - **h**: int16_t (2-byte signed int)
+ - **H**: uint16_t (2-byte unsigned int)
+ - **i**: int32_t (4-byte signed int)
+ - **I**: uint32_t (4-byte unsigned int)
+ - **l**: int64_t (8-byte signed int)
+ - **L**: uint64_t (8-byte unsigned int)
+ - **f**: 4-byte single-precision float
+ - **d**: 8-byte double-precision float
+ - **x**: use to skip *ncols* anywhere in the record
For records with mixed types, append additional comma-separated
combinations of *ncols* *type* (no space). The following modifiers
@@ -84,9 +84,9 @@
**e**\|\ **f**\|\ **g**.
Determine how spherical distances are calculated.
- - **e** - Ellipsoidal (or geodesic) mode
- - **f** - Flat Earth mode
- - **g** - Great circle distance [Default]
+ - **e**: Ellipsoidal (or geodesic) mode
+ - **f**: Flat Earth mode
+ - **g**: Great circle distance [Default]
All spherical distance calculations depend on the current ellipsoid
(:gmt-term:`PROJ_ELLIPSOID`), the definition of the mean radius
@@ -118,16 +118,16 @@
a list with each item containing a string describing one set of
criteria.
- - **x**\|\ **X** - define a gap when there is a large enough
- change in the x coordinates (upper case to use projected
+ - **x**\|\ **X**: define a gap when there is a large enough
+ change in the x coordinates (uppercase to use projected
coordinates).
- - **y**\|\ **Y** - define a gap when there is a large enough
- change in the y coordinates (upper case to use projected
+ - **y**\|\ **Y**: define a gap when there is a large enough
+ change in the y coordinates (uppercase to use projected
coordinates).
- - **d**\|\ **D** - define a gap when there is a large enough
- distance between coordinates (upper case to use projected
+ - **d**\|\ **D**: define a gap when there is a large enough
+ distance between coordinates (uppercase to use projected
coordinates).
- - **z** - define a gap when there is a large enough change in
+ - **z**: define a gap when there is a large enough change in
the z data. Use **+c**\ *col* to change the z data column
[Default *col* is 2 (i.e., 3rd column)].
@@ -146,9 +146,9 @@
One of the following modifiers can be appended:
- - **+n** - specify that the previous value minus the current
+ - **+n**: specify that the previous value minus the current
column value must exceed *gap* for a break to be imposed.
- - **+p** - specify that the current value minus the previous
+ - **+p**: specify that the current value minus the previous
value must exceed *gap* for a break to be imposed.""",
"grid": r"""
grid : str or xarray.DataArray
@@ -367,13 +367,13 @@
Select verbosity level [Default is **w**], which modulates the messages
written to stderr. Choose among 7 levels of verbosity:
- - **q** - Quiet, not even fatal error messages are produced
- - **e** - Error messages only
- - **w** - Warnings [Default]
- - **t** - Timings (report runtimes for time-intensive algorithms)
- - **i** - Informational messages (same as ``verbose=True``)
- - **c** - Compatibility warnings
- - **d** - Debugging messages""",
+ - **q**: Quiet, not even fatal error messages are produced
+ - **e**: Error messages only
+ - **w**: Warnings [Default]
+ - **t**: Timings (report runtimes for time-intensive algorithms)
+ - **i**: Informational messages (same as ``verbose=True``)
+ - **c**: Compatibility warnings
+ - **d**: Debugging messages""",
"wrap": r"""
wrap : str
**y**\|\ **a**\|\ **w**\|\ **d**\|\ **h**\|\ **m**\|\ **s**\|\
@@ -382,14 +382,14 @@
different column if selected via **+c**\ *col*. The following
cyclical coordinate transformations are supported:
- - **y** - yearly cycle (normalized)
- - **a** - annual cycle (monthly)
- - **w** - weekly cycle (day)
- - **d** - daily cycle (hour)
- - **h** - hourly cycle (minute)
- - **m** - minute cycle (second)
- - **s** - second cycle (second)
- - **c** - custom cycle (normalized)
+ - **y**: yearly cycle (normalized)
+ - **a**: annual cycle (monthly)
+ - **w**: weekly cycle (day)
+ - **d**: daily cycle (hour)
+ - **h**: hourly cycle (minute)
+ - **m**: minute cycle (second)
+ - **s**: second cycle (second)
+ - **c**: custom cycle (normalized)
Full documentation is at :gmt-docs:`gmt.html#w-full`.""",
}
diff --git a/pygmt/helpers/tempfile.py b/pygmt/helpers/tempfile.py
index 191d6bc088c..70cc688156a 100644
--- a/pygmt/helpers/tempfile.py
+++ b/pygmt/helpers/tempfile.py
@@ -1,3 +1,4 @@
+# noqa: A005
"""
Utilities for dealing with temporary file management.
"""
@@ -59,7 +60,7 @@ class GMTTempFile:
[0. 0. 0.] [1. 1. 1.] [2. 2. 2.]
"""
- def __init__(self, prefix: str = "pygmt-", suffix: str = ".txt"):
+ def __init__(self, prefix: str = "pygmt-", suffix: str = ".txt") -> None:
"""
Initialize the object.
"""
@@ -144,6 +145,8 @@ def tempfile_from_geojson(geojson):
# https://github.com/geopandas/geopandas/issues/967#issuecomment-842877704
# https://github.com/GenericMappingTools/pygmt/issues/2497
int32_info = np.iinfo(np.int32)
+ # TODO(GeoPandas>=1.0): Remove the workaround for GeoPandas < 1.
+ # The default engine is "fiona" in v0.x and "pyogrio" in v1.x.
if Version(gpd.__version__).major < 1: # GeoPandas v0.x
# The default engine 'fiona' supports the 'schema' parameter.
if geojson.index.name is None:
diff --git a/pygmt/helpers/utils.py b/pygmt/helpers/utils.py
index 32fad37e4ff..e32f5bbe03f 100644
--- a/pygmt/helpers/utils.py
+++ b/pygmt/helpers/utils.py
@@ -43,7 +43,7 @@
def _validate_data_input(
data=None, x=None, y=None, z=None, required_z=False, required_data=True, kind=None
-):
+) -> None:
"""
Check if the combination of data/x/y/z is valid.
@@ -552,7 +552,7 @@ def is_nonstr_iter(value):
return isinstance(value, Iterable) and not isinstance(value, str)
-def launch_external_viewer(fname: str, waiting: float = 0):
+def launch_external_viewer(fname: str, waiting: float = 0) -> None:
"""
Open a file in an external viewer program.
@@ -574,9 +574,8 @@ def launch_external_viewer(fname: str, waiting: float = 0):
}
match sys.platform:
- case name if (
- (name == "linux" or name.startswith("freebsd"))
- and (xdgopen := shutil.which("xdg-open"))
+ case name if (name == "linux" or name.startswith("freebsd")) and (
+ xdgopen := shutil.which("xdg-open")
): # Linux/FreeBSD
subprocess.run([xdgopen, fname], check=False, **run_args) # type:ignore[call-overload]
case "darwin": # macOS
diff --git a/pygmt/io.py b/pygmt/io.py
index 9451de36c8f..a4ba289c7d9 100644
--- a/pygmt/io.py
+++ b/pygmt/io.py
@@ -1,3 +1,4 @@
+# noqa: A005
"""
PyGMT input/output (I/O) utilities.
"""
diff --git a/pygmt/session_management.py b/pygmt/session_management.py
index be3cff9539c..ac18218c858 100644
--- a/pygmt/session_management.py
+++ b/pygmt/session_management.py
@@ -9,7 +9,7 @@
from pygmt.helpers import unique_name
-def begin():
+def begin() -> None:
"""
Initiate a new GMT modern mode session.
@@ -28,7 +28,7 @@ def begin():
lib.call_module(module="set", args=["GMT_COMPATIBILITY=6"])
-def end():
+def end() -> None:
"""
Terminate the GMT modern mode session created by :func:`pygmt.begin`.
diff --git a/pygmt/src/__init__.py b/pygmt/src/__init__.py
index e4db7321963..8905124f917 100644
--- a/pygmt/src/__init__.py
+++ b/pygmt/src/__init__.py
@@ -29,6 +29,7 @@
from pygmt.src.grdview import grdview
from pygmt.src.grdvolume import grdvolume
from pygmt.src.histogram import histogram
+from pygmt.src.hlines import hlines
from pygmt.src.image import image
from pygmt.src.info import info
from pygmt.src.inset import inset
@@ -56,6 +57,7 @@
from pygmt.src.timestamp import timestamp
from pygmt.src.triangulate import triangulate
from pygmt.src.velo import velo
+from pygmt.src.vlines import vlines
from pygmt.src.which import which
from pygmt.src.wiggle import wiggle
from pygmt.src.x2sys_cross import x2sys_cross
diff --git a/pygmt/src/filter1d.py b/pygmt/src/filter1d.py
index 206c3a2fd48..6d0e97938cf 100644
--- a/pygmt/src/filter1d.py
+++ b/pygmt/src/filter1d.py
@@ -75,7 +75,7 @@ def filter1d(
- **u**: upper (absolute). Return maximum of all values.
- **U**: upper. Return maximum of all negative values only.
- Upper case type **B**, **C**, **G**, **M**, **P**, **F** will use
+ Uppercase type **B**, **C**, **G**, **M**, **P**, **F** will use
robust filter versions: i.e., replace outliers (2.5 L1 scale off
median, using 1.4826 \* median absolute deviation [MAD]) with median
during filtering.
diff --git a/pygmt/src/grd2xyz.py b/pygmt/src/grd2xyz.py
index a44dc996c6d..b31d0013a25 100644
--- a/pygmt/src/grd2xyz.py
+++ b/pygmt/src/grd2xyz.py
@@ -145,8 +145,7 @@ def grd2xyz(
if kwargs.get("o") is not None and output_type == "pandas":
msg = (
- "If 'outcols' is specified, 'output_type' must be either 'numpy' "
- "or 'file'."
+ "If 'outcols' is specified, 'output_type' must be either 'numpy' or 'file'."
)
raise GMTInvalidInput(msg)
# Set the default column names for the pandas DataFrame header.
diff --git a/pygmt/src/grdfilter.py b/pygmt/src/grdfilter.py
index 1ad044d0b5e..786e280dd61 100644
--- a/pygmt/src/grdfilter.py
+++ b/pygmt/src/grdfilter.py
@@ -48,13 +48,13 @@ def grdfilter(grid, outgrid: str | None = None, **kwargs) -> xr.DataArray | None
[/*width2*\][*modifiers*].
Name of the filter type you wish to apply, followed by the *width*:
- - **b** - Box Car
- - **c** - Cosine Arch
- - **g** - Gaussian
- - **o** - Operator
- - **m** - Median
- - **p** - Maximum Likelihood probability
- - **h** - Histogram
+ - **b**: Box Car
+ - **c**: Cosine Arch
+ - **g**: Gaussian
+ - **o**: Operator
+ - **m**: Median
+ - **p**: Maximum Likelihood probability
+ - **h**: Histogram
distance : str
State how the grid (x,y) relates to the filter *width*:
diff --git a/pygmt/src/grdgradient.py b/pygmt/src/grdgradient.py
index a793be04df0..96c4c61937f 100644
--- a/pygmt/src/grdgradient.py
+++ b/pygmt/src/grdgradient.py
@@ -69,11 +69,11 @@ def grdgradient(grid, outgrid: str | None = None, **kwargs) -> xr.DataArray | No
Find the direction of the positive (up-slope) gradient of the data.
The following options are supported:
- - **a** - Find the aspect (i.e., the down-slope direction)
- - **c** - Use the conventional Cartesian angles measured
+ - **a**: Find the aspect (i.e., the down-slope direction)
+ - **c**: Use the conventional Cartesian angles measured
counterclockwise from the positive x (east) direction.
- - **o** - Report orientations (0-180) rather than directions (0-360).
- - **n** - Add 90 degrees to all angles (e.g., to give local strikes of
+ - **o**: Report orientations (0-180) rather than directions (0-360).
+ - **n**: Add 90 degrees to all angles (e.g., to give local strikes of
the surface).
radiance : str or list
[**m**\|\ **s**\|\ **p**]\ *azim/elev*\ [**+a**\ *ambient*][**+d**\
@@ -102,14 +102,14 @@ def grdgradient(grid, outgrid: str | None = None, **kwargs) -> xr.DataArray | No
given, it is set to the average of :math:`g`. The following forms are
supported:
- - **True** - Normalize using :math:`g_n = \mbox{{amp}}\
+ - **True**: Normalize using :math:`g_n = \mbox{{amp}}\
(\frac{{g - \mbox{{offset}}}}{{max(|g - \mbox{{offset}}|)}})`
- - **e** - Normalize using a cumulative Laplace distribution yielding:
+ - **e**: Normalize using a cumulative Laplace distribution yielding:
:math:`g_n = \mbox{{amp}}(1 - \
\exp{{(\sqrt{{2}}\frac{{g - \mbox{{offset}}}}{{\sigma}}))}}`, where
:math:`\sigma` is estimated using the L1 norm of
:math:`(g - \mbox{{offset}})` if it is not given.
- - **t** - Normalize using a cumulative Cauchy distribution yielding:
+ - **t**: Normalize using a cumulative Cauchy distribution yielding:
:math:`g_n = \
\frac{{2(\mbox{{amp}})}}{{\pi}}(\tan^{{-1}}(\frac{{g - \
\mbox{{offset}}}}{{\sigma}}))` where :math:`\sigma` is estimated
diff --git a/pygmt/src/grdview.py b/pygmt/src/grdview.py
index ba7341046e0..77ace573a30 100644
--- a/pygmt/src/grdview.py
+++ b/pygmt/src/grdview.py
@@ -76,12 +76,12 @@ def grdview(self, grid, **kwargs):
Specify cover type of the grid.
Select one of following settings:
- - **m** - mesh plot [Default].
- - **mx** or **my** - waterfall plots (row or column profiles).
- - **s** - surface plot, and optionally append **m** to have mesh lines
+ - **m**: mesh plot [Default].
+ - **mx** or **my**: waterfall plots (row or column profiles).
+ - **s**: surface plot, and optionally append **m** to have mesh lines
drawn on top of the surface.
- - **i** - image plot.
- - **c** - Same as **i** but will make nodes with z = NaN transparent.
+ - **i**: image plot.
+ - **c**: Same as **i** but will make nodes with z = NaN transparent.
For any of these choices, you may force a monochrome image by
appending the modifier **+m**.
diff --git a/pygmt/src/hlines.py b/pygmt/src/hlines.py
new file mode 100644
index 00000000000..b277358d981
--- /dev/null
+++ b/pygmt/src/hlines.py
@@ -0,0 +1,137 @@
+"""
+hlines - Plot horizontal lines.
+"""
+
+from collections.abc import Sequence
+
+import numpy as np
+from pygmt.exceptions import GMTInvalidInput
+
+__doctest_skip__ = ["hlines"]
+
+
+def hlines(
+ self,
+ y: float | Sequence[float],
+ xmin: float | Sequence[float] | None = None,
+ xmax: float | Sequence[float] | None = None,
+ pen: str | None = None,
+ label: str | None = None,
+ no_clip: bool = False,
+ perspective: str | bool | None = None,
+):
+ """
+ Plot one or multiple horizontal line(s).
+
+ This method is a high-level wrapper around :meth:`pygmt.Figure.plot` that focuses on
+ plotting horizontal lines at Y-coordinates specified by the ``y`` parameter. The
+ ``y`` parameter can be a single value (for a single horizontal line) or a sequence
+ of values (for multiple horizontal lines).
+
+ By default, the X-coordinates of the start and end points of the lines are set to
+ be the X-limits of the current plot, but this can be overridden by specifying the
+ ``xmin`` and ``xmax`` parameters. ``xmin`` and ``xmax`` can be either a single
+ value or a sequence of values. If a single value is provided, it is applied to all
+ lines. If a sequence is provided, the length of ``xmin`` and ``xmax`` must match
+ the length of ``y``.
+
+ The term "horizontal" lines can be interpreted differently in different coordinate
+ systems:
+
+ - **Cartesian** coordinate system: lines are plotted as straight lines.
+ - **Polar** projection: lines are plotted as arcs along a constant radius.
+ - **Geographic** projection: lines are plotted as parallels along constant latitude.
+
+ Parameters
+ ----------
+ y
+ Y-coordinates to plot the lines. It can be a single value (for a single line)
+ or a sequence of values (for multiple lines).
+ xmin/xmax
+ X-coordinates of the start/end point(s) of the line(s). If ``None``, defaults to
+ the X-limits of the current plot. ``xmin`` and ``xmax`` can be either a single
+ value or a sequence of values. If a single value is provided, it is applied to
+ all lines. If a sequence is provided, the length of ``xmin`` and ``xmax`` must
+ match the length of ``y``.
+ pen
+ Pen attributes for the line(s), in the format of *width,color,style*.
+ label
+ Label for the line(s), to be displayed in the legend.
+ no_clip
+ If ``True``, do not clip lines outside the plot region. Only makes sense in the
+ Cartesian coordinate system.
+ perspective
+ Select perspective view and set the azimuth and elevation angle of the
+ viewpoint. Refer to :meth:`pygmt.Figure.plot` for details.
+
+ Examples
+ --------
+ >>> import pygmt
+ >>> fig = pygmt.Figure()
+ >>> fig.basemap(region=[0, 10, 0, 10], projection="X10c/10c", frame=True)
+ >>> fig.hlines(y=1, pen="1p,black", label="Line at y=1")
+ >>> fig.hlines(y=2, xmin=2, xmax=8, pen="1p,red,-", label="Line at y=2")
+ >>> fig.hlines(y=[3, 4], xmin=3, xmax=7, pen="1p,black,.", label="Lines at y=3,4")
+ >>> fig.hlines(y=[5, 6], xmin=4, xmax=9, pen="1p,red", label="Lines at y=5,6")
+ >>> fig.hlines(
+ ... y=[7, 8], xmin=[0, 1], xmax=[7, 8], pen="1p,blue", label="Lines at y=7,8"
+ ... )
+ >>> fig.legend()
+ >>> fig.show()
+ """
+ self._preprocess()
+
+ # Determine the x limits from the current plot region if not specified.
+ if xmin is None or xmax is None:
+ xlimits = self.region[:2]
+ if xmin is None:
+ xmin = xlimits[0]
+ if xmax is None:
+ xmax = xlimits[1]
+
+ # Ensure y/xmin/xmax are 1-D arrays.
+ _y = np.atleast_1d(y)
+ _xmin = np.atleast_1d(xmin)
+ _xmax = np.atleast_1d(xmax)
+
+ nlines = len(_y) # Number of lines to plot.
+
+ # Check if xmin/xmax are scalars or have the expected length.
+ if _xmin.size not in {1, nlines} or _xmax.size not in {1, nlines}:
+ msg = (
+ f"'xmin' and 'xmax' are expected to be scalars or have lengths '{nlines}', "
+ f"but lengths '{_xmin.size}' and '{_xmax.size}' are given."
+ )
+ raise GMTInvalidInput(msg)
+
+ # Repeat xmin/xmax to match the length of y if they are scalars.
+ if nlines != 1:
+ if _xmin.size == 1:
+ _xmin = np.repeat(_xmin, nlines)
+ if _xmax.size == 1:
+ _xmax = np.repeat(_xmax, nlines)
+
+ # Call the Figure.plot method to plot the lines.
+ for i in range(nlines):
+ # Special handling for label.
+ # 1. Only specify a label when plotting the first line.
+ # 2. The -l option can accept comma-separated labels for labeling multiple lines
+ # with auto-coloring enabled. We don't need this feature here, so we need to
+ # replace comma with \054 if the label contains commas.
+ _label = label.replace(",", "\\054") if label and i == 0 else None
+
+ # By default, points are connected as great circle arcs in geographic coordinate
+ # systems and straight lines in Cartesian coordinate systems (including polar
+ # projection). To plot "horizontal" lines along constant latitude (in geographic
+ # coordinate systems) or constant radius (in polar projection), we need to
+ # resample the line to at least 4 points.
+ npoints = 4 # 2 for Cartesian, at least 4 for geographic and polar projections.
+ self.plot(
+ x=np.linspace(_xmin[i], _xmax[i], npoints),
+ y=[_y[i]] * npoints,
+ pen=pen,
+ label=_label,
+ no_clip=no_clip,
+ perspective=perspective,
+ straight_line="x",
+ )
diff --git a/pygmt/src/meca.py b/pygmt/src/meca.py
index 75b69d2facb..ba6de1f7868 100644
--- a/pygmt/src/meca.py
+++ b/pygmt/src/meca.py
@@ -227,169 +227,145 @@ def meca( # noqa: PLR0912, PLR0913, PLR0915
Parameters
----------
- spec : str, 1-D array, 2-D array, dict, or :class:`pandas.DataFrame`
+ spec : str, 1-D numpy array, 2-D numpy array, dict, or pandas.DataFrame
Data that contain focal mechanism parameters.
``spec`` can be specified in either of the following types:
- - *str*: a file name containing focal mechanism parameters as
- columns. The meaning of each column is:
+ - *str*: a file name containing focal mechanism parameters as columns. The
+ meaning of each column is:
- Columns 1 and 2: event longitude and latitude
- - Column 3: event depth (in km)
- - Columns 4 to 3+n: focal mechanism parameters. The number of columns
- *n* depends on the choice of ``convention``, which will be
- described below.
- - Columns 4+n and 5+n: longitude, latitude at which to place
- beachball. Using ``0 0`` will plot the beachball at the longitude,
- latitude given in columns 1 and 2. [optional and requires
- ``offset=True`` to take effect].
- - Text string to appear near the beachball [optional].
-
- - *1-D array*: focal mechanism parameters of a single event.
+ - Column 3: event depth (in kilometers)
+ - Columns 4 to 3+n: focal mechanism parameters. The number of columns *n*
+ depends on the choice of ``convention``, which is described below.
+ - Columns 4+n and 5+n: longitude and latitude at which to place the
+ beachball. ``0 0`` plots the beachball at the longitude and latitude
+ given in the columns 1 and 2. [optional; requires ``offset=True``].
+ - Last Column: text string to appear near the beachball [optional].
+
+ - *1-D np.array*: focal mechanism parameters of a single event.
The meanings of columns are the same as above.
- - *2-D array*: focal mechanism parameters of multiple events.
+ - *2-D np.array*: focal mechanism parameters of multiple events.
The meanings of columns are the same as above.
- - *dictionary or :class:`pandas.DataFrame`*: The dictionary keys or
+ - *dict* or :class:`pandas.DataFrame`: The dict keys or
:class:`pandas.DataFrame` column names determine the focal mechanism
- convention. For different conventions, the following combination of
- keys are allowed:
-
- - ``"aki"``: *strike, dip, rake, magnitude*
- - ``"gcmt"``: *strike1, dip1, rake1, strike2, dip2, rake2, mantissa,*
- *exponent*
- - ``"mt"``: *mrr, mtt, mff, mrt, mrf, mtf, exponent*
- - ``"partial"``: *strike1, dip1, strike2, fault_type, magnitude*
- - ``"principal_axis"``: *t_value, t_azimuth, t_plunge, n_value,
- n_azimuth, n_plunge, p_value, p_azimuth, p_plunge, exponent*
-
- A dictionary may contain values for a single focal mechanism or
- lists of values for multiple focal mechanisms.
-
- Both dictionary and :class:`pandas.DataFrame` may optionally contain
- keys/column names: ``latitude``, ``longitude``, ``depth``,
- ``plot_longitude``, ``plot_latitude``, and/or ``event_name``.
-
- If ``spec`` is either a str, a 1-D array or a 2-D array, the
- ``convention`` parameter is required so we know how to interpret the
- columns. If ``spec`` is a dictionary or a :class:`pandas.DataFrame`,
- ``convention`` is not needed and is ignored if specified.
+ convention. For the different conventions, the following combination of
+ keys / column names are required:
+
+ - ``"aki"``: *strike*, *dip*, *rake*, *magnitude*
+ - ``"gcmt"``: *strike1*, *dip1*, *rake1*, *strike2*, *dip2*, *rake2*,
+ *mantissa*, *exponent*
+ - ``"mt"``: *mrr*, *mtt*, *mff*, *mrt*, *mrf*, *mtf*, *exponent*
+ - ``"partial"``: *strike1*, *dip1*, *strike2*, *fault_type*, *magnitude*
+ - ``"principal_axis"``: *t_value*, *t_azimuth*, *t_plunge*, *n_value*,
+ *n_azimuth*, *n_plunge*, *p_value*, *p_azimuth*, *p_plunge*, *exponent*
+
+ A dict may contain values for a single focal mechanism or lists of
+ values for multiple focal mechanisms.
+
+ Both dict and :class:`pandas.DataFrame` may optionally contain the keys /
+ column names: ``latitude``, ``longitude``, ``depth``, ``plot_longitude``,
+ ``plot_latitude``, and/or ``event_name``.
+
+ If ``spec`` is either a str or a 1-D or 2-D numpy array, the ``convention``
+ parameter is required to interpret the columns. If ``spec`` is a dict or
+ a :class:`pandas.DataFrame`, ``convention`` is not needed and ignored if
+ specified.
scale : float or str
*scale*\ [**+a**\ *angle*][**+f**\ *font*][**+j**\ *justify*]\
[**+l**][**+m**][**+o**\ *dx*\ [/\ *dy*]][**+s**\ *reference*].
- Adjust scaling of the radius of the beachball, which is
- proportional to the magnitude. By default, *scale* defines the
- size for magnitude = 5 (i.e., scalar seismic moment
- M0 = 4.0E23 dynes-cm). If **+l** is used the radius will be
- proportional to the seismic moment instead. Use **+s** and give
- a *reference* to change the reference magnitude (or moment), and
- use **+m** to plot all beachballs with the same size. A text
- string can be specified to appear near the beachball
- (corresponding to column or parameter ``event_name``).
- Append **+a**\ *angle* to change the angle of the text string;
- append **+f**\ *font* to change its font (size,fontname,color);
- append **+j**\ *justify* to change the text location relative
- to the beachball [Default is ``"TC"``, i.e., Top Center];
- append **+o** to offset the text string by *dx*\ /*dy*.
+ Adjust scaling of the radius of the beachball, which is proportional to the
+ magnitude. By default, *scale* defines the size for magnitude = 5 (i.e., scalar
+ seismic moment M0 = 4.0E23 dynes-cm). If **+l** is used the radius will be
+ proportional to the seismic moment instead. Use **+s** and give a *reference*
+ to change the reference magnitude (or moment), and use **+m** to plot all
+ beachballs with the same size. A text string can be specified to appear near
+ the beachball (corresponding to column or parameter ``event_name``). Append
+ **+a**\ *angle* to change the angle of the text string; append **+f**\ *font*
+ to change its font (size,fontname,color); append **+j**\ *justify* to change
+ the text location relative to the beachball [Default is ``"TC"``, i.e., Top
+ Center]; append **+o** to offset the text string by *dx*\ /*dy*.
convention : str
Focal mechanism convention. Choose from:
- - ``"aki"`` (Aki & Richards)
+ - ``"aki"`` (Aki and Richards)
- ``"gcmt"`` (global CMT)
- ``"mt"`` (seismic moment tensor)
- ``"partial"`` (partial focal mechanism)
- ``"principal_axis"`` (principal axis)
- Ignored if ``spec`` is a dictionary or :class:`pandas.DataFrame`.
+ Ignored if ``spec`` is a dict or :class:`pandas.DataFrame`.
component : str
The component of the seismic moment tensor to plot.
- ``"full"``: the full seismic moment tensor
- - ``"dc"``: the closest double couple defined from the moment tensor
- (zero trace and zero determinant)
+ - ``"dc"``: the closest double couple defined from the moment tensor (zero
+ trace and zero determinant)
- ``"deviatoric"``: deviatoric part of the moment tensor (zero trace)
- longitude : float, list, or 1-D numpy array
- Longitude(s) of event location(s). Must be the same length as the
- number of events. Will override the ``longitude`` values
- in ``spec`` if ``spec`` is a dictionary or :class:`pandas.DataFrame`.
- latitude : float, list, or 1-D numpy array
- Latitude(s) of event location(s). Must be the same length as the
- number of events. Will override the ``latitude`` values
- in ``spec`` if ``spec`` is a dictionary or :class:`pandas.DataFrame`.
- depth : float, list, or 1-D numpy array
- Depth(s) of event location(s) in kilometers. Must be the same length
- as the number of events. Will override the ``depth`` values in ``spec``
- if ``spec`` is a dictionary or :class:`pandas.DataFrame`.
- plot_longitude : float, str, list, or 1-D numpy array
- Longitude(s) at which to place beachball(s). Must be the same length
- as the number of events. Will override the ``plot_longitude`` values
- in ``spec`` if ``spec`` is a dictionary or :class:`pandas.DataFrame`.
- plot_latitude : float, str, list, or 1-D numpy array
- Latitude(s) at which to place beachball(s). List must be the same
- length as the number of events. Will override the ``plot_latitude``
- values in ``spec`` if ``spec`` is a dictionary or :class:`pandas.DataFrame`.
+ longitude/latitude/depth : float, list, or 1-D numpy array
+ Longitude(s) / latitude(s) / depth(s) of the event(s). Length must match the
+ number of events. Overrides the ``longitude`` / ``latitude`` / ``depth`` values
+ in ``spec`` if ``spec`` is a dict or :class:`pandas.DataFrame`.
+ plot_longitude/plot_latitude : float, str, list, or 1-D numpy array
+ Longitude(s) / Latitude(s) at which to place the beachball(s). Length must match
+ the number of events. Overrides the ``plot_longitude`` / ``plot_latitude``
+ values in ``spec`` if ``spec`` is a dict or :class:`pandas.DataFrame`.
event_name : str, list of str, or 1-D numpy array
- Text string(s), e.g., event name(s) to appear near the beachball(s).
- List must be the same length as the number of events. Will override
- the ``event_name`` labels in ``spec`` if ``spec`` is a dictionary
- or :class:`pandas.DataFrame`.
+ Text string(s), e.g., event name(s) to appear near the beachball(s). Length
+ must match the number of events. Overrides the ``event_name`` labels in ``spec``
+ if ``spec`` is a dict or :class:`pandas.DataFrame`.
labelbox : bool or str
[*fill*].
- Draw a box behind the label if given. Use *fill* to give a fill color
- [Default is ``"white"``].
+ Draw a box behind the label if given via ``event_name``. Use *fill* to give a
+ fill color [Default is ``"white"``].
offset : bool or str
[**+p**\ *pen*][**+s**\ *size*].
- Offset beachball(s) to longitude(s) and latitude(s) specified in the
- the last two columns of the input file or array, or by
- ``plot_longitude`` and ``plot_latitude`` if provided. A small circle
- is plotted at the initial location and a line connects the beachball
- to the circle. Use **+s**\ *size* to set the diameter of the circle
- [Default is no circle]. Use **+p**\ *pen* to set the pen attributes
- for this feature [Default is set via ``pen``]. The fill of the
- circle is set via ``compressionfill`` or ``cmap``, i.e.,
- corresponds to the fill of the compressive quadrants.
+ Offset beachball(s) to the longitude(s) and latitude(s) specified in the last
+ two columns of the input file or array, or by ``plot_longitude`` and
+ ``plot_latitude`` if provided. A line from the beachball to the initial location
+ is drawn. Use **+s**\ *size* to plot a small circle at the initial location and
+ to set the diameter of this circle [Default is no circle]. Use **+p**\ *pen* to
+ set the pen attributes for this feature [Default is set via ``pen``]. The fill
+ of the circle is set via ``compressionfill`` or ``cmap``, i.e., corresponds to
+ the fill of the compressive quadrants.
compressionfill : str
- Set color or pattern for filling compressive quadrants
- [Default is ``"black"``]. This setting also applies to the fill of
- the circle defined via ``offset``.
+ Set color or pattern for filling compressive quadrants [Default is ``"black"``].
+ This setting also applies to the fill of the circle defined via ``offset``.
extensionfill : str
- Set color or pattern for filling extensive quadrants
- [Default is ``"white"``].
+ Set color or pattern for filling extensive quadrants [Default is ``"white"``].
pen : str
- Set pen attributes for all lines related to beachball [Default is
- ``"0.25p,black,solid"``]. This setting applies to ``outline``,
- ``nodal``, and ``offset``, unless overruled by arguments passed to
- those parameters. Draws circumference of beachball.
+ Set (default) pen attributes for all lines related to the beachball [Default is
+ ``"0.25p,black,solid"``]. This setting applies to ``outline``, ``nodal``, and
+ ``offset``, unless overruled by arguments passed to those parameters. Draws the
+ circumference of the beachball.
outline : bool or str
[*pen*].
- Draw circumference and nodal planes of beachball. Use *pen* to set
- the pen attributes for this feature [Default is set via ``pen``].
+ Draw circumference and nodal planes of the beachball. Use *pen* to set the pen
+ attributes for this feature [Default is set via ``pen``].
nodal : bool, int, or str
[*nplane*][/*pen*].
- Plot the nodal planes and outline the bubble which is transparent.
- If *nplane* is
+ Plot the nodal planes and outline the bubble which is transparent. If *nplane*
+ is
- ``0`` or ``True``: both nodal planes are plotted [Default].
- ``1``: only the first nodal plane is plotted.
- ``2``: only the second nodal plane is plotted.
- Use /*pen* to set the pen attributes for this feature [Default is
- set via ``pen``].
- For double couple mechanisms, ``nodal`` renders the beachball
- transparent by drawing only the nodal planes and the circumference.
- For non-double couple mechanisms, ``nodal=0`` overlays best
- double couple transparently.
+ Use /*pen* to set the pen attributes for this feature [Default is set via
+ ``pen``].
+ For double couple mechanisms, ``nodal`` renders the beachball transparent by
+ drawing only the nodal planes and the circumference. For non-double couple
+ mechanisms, ``nodal=0`` overlays best double couple transparently.
cmap : str
File name of a CPT file or a series of comma-separated colors (e.g.,
- *color1,color2,color3*) to build a linear continuous CPT from those
- colors automatically. The color of the compressive quadrants is
- determined by the z-value (i.e., event depth or the third column for
- an input file). This setting also applies to the fill of the circle
- defined via ``offset``.
+ *color1,color2,color3*) to build a linear continuous CPT from those colors
+ automatically. The color of the compressive quadrants is determined by the
+ z-value (i.e., event depth or the third column for an input file). This setting
+ also applies to the fill of the circle defined via ``offset``.
no_clip : bool
- Do **not** skip symbols that fall outside the frame boundaries
- [Default is ``False``, i.e., plot symbols inside the frame
- boundaries only].
+ Do **not** skip symbols that fall outside the frame boundaries [Default is
+ ``False``, i.e., plot symbols inside the frame boundaries only].
{projection}
{region}
{frame}
diff --git a/pygmt/src/plot.py b/pygmt/src/plot.py
index ebfb2f22f3c..23c5bde12fd 100644
--- a/pygmt/src/plot.py
+++ b/pygmt/src/plot.py
@@ -2,6 +2,8 @@
plot - Plot in two dimensions.
"""
+from typing import Literal
+
from pygmt.clib import Session
from pygmt.exceptions import GMTInvalidInput
from pygmt.helpers import (
@@ -49,7 +51,15 @@
)
@kwargs_to_strings(R="sequence", c="sequence_comma", i="sequence_comma", p="sequence")
def plot(
- self, data=None, x=None, y=None, size=None, symbol=None, direction=None, **kwargs
+ self,
+ data=None,
+ x=None,
+ y=None,
+ size=None,
+ symbol=None,
+ direction=None,
+ straight_line: bool | Literal["x", "y"] = False, # noqa: ARG001
+ **kwargs,
):
r"""
Plot lines, polygons, and symbols in 2-D.
@@ -98,18 +108,29 @@ def plot(
depending on the style options chosen.
{projection}
{region}
- straight_line : bool or str
- [**m**\|\ **p**\|\ **x**\|\ **y**].
- By default, geographic line segments are drawn as great circle
- arcs. To draw them as straight lines, use
- ``straight_line=True``.
- Alternatively, add **m** to draw the line by first following a
- meridian, then a parallel. Or append **p** to start following a
- parallel, then a meridian. (This can be practical to draw a line
- along parallels, for example). For Cartesian data, points are
- simply connected, unless you append **x** or **y** to draw
- stair-case curves that whose first move is along *x* or *y*,
- respectively.
+ straight_line
+ By default, line segments are drawn as straight lines in the Cartesian and polar
+ coordinate systems, and as great circle arcs (by resampling coarse input data
+ along such arcs) in the geographic coordinate system. The ``straight_line``
+ parameter can control the drawing of line segments. Valid values are:
+
+ - ``True``: Draw line segments as straight lines in geographic coordinate
+ systems.
+ - ``"x"``: Draw line segments by first along *x*, then along *y*.
+ - ``"y"``: Draw line segments by first along *y*, then along *x*.
+
+ Here, *x* and *y* have different meanings depending on the coordinate system:
+
+ - **Cartesian** coordinate system: *x* and *y* are the X- and Y-axes.
+ - **Polar** coordinate system: *x* and *y* are theta and radius.
+ - **Geographic** coordinate system: *x* and *y* are parallels and meridians.
+
+ .. attention::
+
+ There exits a bug in GMT<=6.5.0 that, in geographic coordinate systems, the
+ meaning of *x* and *y* is reversed, i.e., *x* means meridians and *y* means
+ parallels. The bug is fixed by upstream
+ `PR #8648 `__.
{frame}
{cmap}
offset : str
@@ -206,6 +227,8 @@ def plot(
``x``/``y``.
{wrap}
"""
+ # TODO(GMT>6.5.0): Remove the note for the upstream bug of the "straight_line"
+ # parameter.
kwargs = self._preprocess(**kwargs)
kind = data_kind(data)
diff --git a/pygmt/src/plot3d.py b/pygmt/src/plot3d.py
index f7f2b08290a..e8e75382d74 100644
--- a/pygmt/src/plot3d.py
+++ b/pygmt/src/plot3d.py
@@ -2,6 +2,8 @@
plot3d - Plot in three dimensions.
"""
+from typing import Literal
+
from pygmt.clib import Session
from pygmt.exceptions import GMTInvalidInput
from pygmt.helpers import (
@@ -58,6 +60,7 @@ def plot3d(
size=None,
symbol=None,
direction=None,
+ straight_line: bool | Literal["x", "y"] = False, # noqa: ARG001
**kwargs,
):
r"""
@@ -108,18 +111,31 @@ def plot3d(
zscale/zsize : float or str
Set z-axis scaling or z-axis size.
{region}
- straight_line : bool or str
- [**m**\|\ **p**\|\ **x**\|\ **y**].
- By default, geographic line segments are drawn as great circle
- arcs. To draw them as straight lines, use ``straight_line``.
- Alternatively, add **m** to draw the line by first following a
- meridian, then a parallel. Or append **p** to start following a
- parallel, then a meridian. (This can be practical to draw a line
- along parallels, for example). For Cartesian data, points are
- simply connected, unless you append **x** or **y** to draw
- stair-case curves that whose first move is along *x* or *y*,
- respectively. **Note**: The ``straight_line`` parameter requires
- constant *z*-coordinates.
+ straight_line
+ By default, line segments are drawn as straight lines in the Cartesian and polar
+ coordinate systems, and as great circle arcs (by resampling coarse input data
+ along such arcs) in the geographic coordinate system. The ``straight_line``
+ parameter can control the drawing of line segments. Valid values are:
+
+ - ``True``: Draw line segments as straight lines in geographic coordinate
+ systems.
+ - ``"x"``: Draw line segments by first along *x*, then along *y*.
+ - ``"y"``: Draw line segments by first along *y*, then along *x*.
+
+ Here, *x* and *y* have different meanings depending on the coordinate system:
+
+ - **Cartesian** coordinate system: *x* and *y* are the X- and Y-axes.
+ - **Polar** coordinate system: *x* and *y* are theta and radius.
+ - **Geographic** coordinate system: *x* and *y* are parallels and meridians.
+
+ **NOTE**: The ``straight_line`` parameter requires constant *z*-coordinates.
+
+ .. attention::
+
+ There exits a bug in GMT<=6.5.0 that, in geographic coordinate systems, the
+ meaning of *x* and *y* is reversed, i.e., *x* means meridians and *y* means
+ parallels. The bug is fixed by upstream
+ `PR #8648 `__.
{frame}
{cmap}
offset : str
@@ -189,6 +205,8 @@ def plot3d(
``x``/``y``/``z``.
{wrap}
"""
+ # TODO(GMT>6.5.0): Remove the note for the upstream bug of the "straight_line"
+ # parameter.
kwargs = self._preprocess(**kwargs)
kind = data_kind(data)
diff --git a/pygmt/src/project.py b/pygmt/src/project.py
index f90e517b202..a49d5a1ad1f 100644
--- a/pygmt/src/project.py
+++ b/pygmt/src/project.py
@@ -136,7 +136,7 @@ def project(
convention : str
Specify the desired output using any combination of **xyzpqrs**, in
any order [Default is **xypqrsz**]. Do not space between the letters.
- Use lower case. The output will be columns of values corresponding to
+ Use lowercase. The output will be columns of values corresponding to
your ``convention``. The **z** flag is special and refers to all
numerical columns beyond the leading **x** and **y** in your input
record. The **z** flag also includes any trailing text (which is
diff --git a/pygmt/src/select.py b/pygmt/src/select.py
index ecd6d12bfad..a7db421a210 100644
--- a/pygmt/src/select.py
+++ b/pygmt/src/select.py
@@ -1,3 +1,4 @@
+# noqa: A005
"""
select - Select data table subsets based on multiple spatial criteria.
"""
diff --git a/pygmt/src/sphdistance.py b/pygmt/src/sphdistance.py
index ab49f961e03..279db4e3590 100644
--- a/pygmt/src/sphdistance.py
+++ b/pygmt/src/sphdistance.py
@@ -65,10 +65,10 @@ def sphdistance(
Specify the quantity that should be assigned to the grid nodes [Default
is **d**]:
- - **d** - compute distances to the nearest data point
- - **n** - assign the ID numbers of the Voronoi polygons that each
+ - **d**: compute distances to the nearest data point
+ - **n**: assign the ID numbers of the Voronoi polygons that each
grid node is inside
- - **z** - assign all nodes inside the polygon the z-value of the center
+ - **z**: assign all nodes inside the polygon the z-value of the center
node for a natural nearest-neighbor grid.
Optionally, append the resampling interval along Voronoi arcs in
diff --git a/pygmt/src/ternary.py b/pygmt/src/ternary.py
index 5bc477fab99..633707cf427 100644
--- a/pygmt/src/ternary.py
+++ b/pygmt/src/ternary.py
@@ -87,7 +87,7 @@ def ternary(
if any(v is not None for v in labels):
kwargs["L"] = "/".join(str(v) if v is not None else "-" for v in labels)
- # Patch for GMT < 6.5.0.
+ # TODO(GMT>=6.5.0): Remove the patch for upstream bug fixed in GMT 6.5.0.
# See https://github.com/GenericMappingTools/pygmt/pull/2138
if Version(__gmt_version__) < Version("6.5.0") and isinstance(data, pd.DataFrame):
data = data.to_numpy()
diff --git a/pygmt/src/text.py b/pygmt/src/text.py
index 75b2653043c..b507510f620 100644
--- a/pygmt/src/text.py
+++ b/pygmt/src/text.py
@@ -138,11 +138,11 @@ def text_( # noqa: PLR0912
**i** for inches, or **p** for points; if not given we consult
:gmt-term:`PROJ_LENGTH_UNIT`) or *%* for a percentage of the font
size. Optionally, use modifier **+t** to set the shape of the text
- box when using ``fill`` and/or ``pen``. Append lower case **o**
- to get a straight rectangle [Default is **o**]. Append upper case
+ box when using ``fill`` and/or ``pen``. Append lowercase **o**
+ to get a straight rectangle [Default is **o**]. Append uppercase
**O** to get a rounded rectangle. In paragraph mode (*paragraph*)
- you can also append lower case **c** to get a concave rectangle or
- append upper case **C** to get a convex rectangle.
+ you can also append lowercase **c** to get a concave rectangle or
+ append uppercase **C** to get a convex rectangle.
fill : str
Set color for filling text boxes [Default is no fill].
offset : str
diff --git a/pygmt/src/timestamp.py b/pygmt/src/timestamp.py
index 3db9ff694d9..13f278b2816 100644
--- a/pygmt/src/timestamp.py
+++ b/pygmt/src/timestamp.py
@@ -84,6 +84,7 @@ def timestamp(
kwdict["U"] += f"{label}"
kwdict["U"] += f"+j{justify}"
+ # TODO(GMT>=6.5.0): Remove the patch for upstream bug fixed in GMT 6.5.0.
if Version(__gmt_version__) < Version("6.5.0") and "/" not in str(offset):
# Giving a single offset doesn't work in GMT < 6.5.0.
# See https://github.com/GenericMappingTools/gmt/issues/7107.
@@ -99,6 +100,7 @@ def timestamp(
"The given text string will be truncated to 64 characters."
)
warnings.warn(message=msg, category=RuntimeWarning, stacklevel=2)
+ # TODO(GMT>=6.5.0): Remove the workaround for the new '+t' modifier.
if Version(__gmt_version__) < Version("6.5.0"):
# Workaround for GMT<6.5.0 by overriding the 'timefmt' parameter
timefmt = text[:64]
diff --git a/pygmt/src/vlines.py b/pygmt/src/vlines.py
new file mode 100644
index 00000000000..2483df99f27
--- /dev/null
+++ b/pygmt/src/vlines.py
@@ -0,0 +1,132 @@
+"""
+vlines - Plot vertical lines.
+"""
+
+from collections.abc import Sequence
+
+import numpy as np
+from pygmt.exceptions import GMTInvalidInput
+
+__doctest_skip__ = ["vlines"]
+
+
+def vlines(
+ self,
+ x: float | Sequence[float],
+ ymin: float | Sequence[float] | None = None,
+ ymax: float | Sequence[float] | None = None,
+ pen: str | None = None,
+ label: str | None = None,
+ no_clip: bool = False,
+ perspective: str | bool | None = None,
+):
+ """
+ Plot one or multiple vertical line(s).
+
+ This method is a high-level wrapper around :meth:`pygmt.Figure.plot` that focuses on
+ plotting vertical lines at X-coordinates specified by the ``x`` parameter. The ``x``
+ parameter can be a single value (for a single vertical line) or a sequence of values
+ (for multiple vertical lines).
+
+ By default, the Y-coordinates of the start and end points of the lines are set to be
+ the Y-limits of the current plot, but this can be overridden by specifying the
+ ``ymin`` and ``ymax`` parameters. ``ymin`` and ``ymax`` can be either a single value
+ or a sequence of values. If a single value is provided, it is applied to all lines.
+ If a sequence is provided, the length of ``ymin`` and ``ymax`` must match the length
+ of ``x``.
+
+ The term "vertical" lines can be interpreted differently in different coordinate
+ systems:
+
+ - **Cartesian** coordinate system: lines are plotted as straight lines.
+ - **Polar** projection: lines are plotted as straight lines along radius.
+ - **Geographic** projection: lines are plotted as meridians along constant
+ longitude.
+
+ Parameters
+ ----------
+ x
+ X-coordinates to plot the lines. It can be a single value (for a single line)
+ or a sequence of values (for multiple lines).
+ ymin/ymax
+ Y-coordinates of the start/end point(s) of the line(s). If ``None``, defaults to
+ the Y-limits of the current plot. ``ymin`` and ``ymax`` can either be a single
+ value or a sequence of values. If a single value is provided, it is applied to
+ all lines. If a sequence is provided, the length of ``ymin`` and ``ymax`` must
+ match the length of ``x``.
+ pen
+ Pen attributes for the line(s), in the format of *width,color,style*.
+ label
+ Label for the line(s), to be displayed in the legend.
+ no_clip
+ If ``True``, do not clip lines outside the plot region. Only makes sense in the
+ Cartesian coordinate system.
+ perspective
+ Select perspective view and set the azimuth and elevation angle of the
+ viewpoint. Refer to :meth:`pygmt.Figure.plot` for details.
+
+ Examples
+ --------
+ >>> import pygmt
+ >>> fig = pygmt.Figure()
+ >>> fig.basemap(region=[0, 10, 0, 10], projection="X10c/10c", frame=True)
+ >>> fig.vlines(x=1, pen="1p,black", label="Line at x=1")
+ >>> fig.vlines(x=2, ymin=2, ymax=8, pen="1p,red,-", label="Line at x=2")
+ >>> fig.vlines(x=[3, 4], ymin=3, ymax=7, pen="1p,black,.", label="Lines at x=3,4")
+ >>> fig.vlines(x=[5, 6], ymin=4, ymax=9, pen="1p,red", label="Lines at x=5,6")
+ >>> fig.vlines(
+ ... x=[7, 8], ymin=[0, 1], ymax=[7, 8], pen="1p,blue", label="Lines at x=7,8"
+ ... )
+ >>> fig.legend()
+ >>> fig.show()
+ """
+ self._preprocess()
+
+ # Determine the y limits from the current plot region if not specified.
+ if ymin is None or ymax is None:
+ ylimits = self.region[2:]
+ if ymin is None:
+ ymin = ylimits[0]
+ if ymax is None:
+ ymax = ylimits[1]
+
+ # Ensure x/ymin/ymax are 1-D arrays.
+ _x = np.atleast_1d(x)
+ _ymin = np.atleast_1d(ymin)
+ _ymax = np.atleast_1d(ymax)
+
+ nlines = len(_x) # Number of lines to plot.
+
+ # Check if ymin/ymax are scalars or have the expected length.
+ if _ymin.size not in {1, nlines} or _ymax.size not in {1, nlines}:
+ msg = (
+ f"'ymin' and 'ymax' are expected to be scalars or have lengths '{nlines}', "
+ f"but lengths '{_ymin.size}' and '{_ymax.size}' are given."
+ )
+ raise GMTInvalidInput(msg)
+
+ # Repeat ymin/ymax to match the length of x if they are scalars.
+ if nlines != 1:
+ if _ymin.size == 1:
+ _ymin = np.repeat(_ymin, nlines)
+ if _ymax.size == 1:
+ _ymax = np.repeat(_ymax, nlines)
+
+ # Call the Figure.plot method to plot the lines.
+ for i in range(nlines):
+ # Special handling for label.
+ # 1. Only specify a label when plotting the first line.
+ # 2. The -l option can accept comma-separated labels for labeling multiple lines
+ # with auto-coloring enabled. We don't need this feature here, so we need to
+ # replace comma with \054 if the label contains commas.
+ _label = label.replace(",", "\\054") if label and i == 0 else None
+
+ self.plot(
+ x=[_x[i], _x[i]],
+ y=[_ymin[i], _ymax[i]],
+ pen=pen,
+ label=_label,
+ no_clip=no_clip,
+ perspective=perspective,
+ straight_line="y",
+ )
diff --git a/pygmt/src/x2sys_cross.py b/pygmt/src/x2sys_cross.py
index f7d9f8e1843..4e209d33d18 100644
--- a/pygmt/src/x2sys_cross.py
+++ b/pygmt/src/x2sys_cross.py
@@ -145,9 +145,9 @@ def x2sys_cross(
Sets the interpolation mode for estimating values at the crossover.
Choose among:
- - **l** - Linear interpolation [Default].
- - **a** - Akima spline interpolation.
- - **c** - Cubic spline interpolation.
+ - **l**: Linear interpolation [Default].
+ - **a**: Akima spline interpolation.
+ - **c**: Cubic spline interpolation.
coe : str
Use **e** for external COEs only, and **i** for internal COEs only
diff --git a/pygmt/src/x2sys_init.py b/pygmt/src/x2sys_init.py
index 99af7211424..6e36263fe0e 100644
--- a/pygmt/src/x2sys_init.py
+++ b/pygmt/src/x2sys_init.py
@@ -85,13 +85,13 @@ def x2sys_init(tag, **kwargs):
programs. Append **d** for distance or **s** for speed, then give the
desired *unit* as:
- - **c** - Cartesian userdist or userdist/usertime
- - **e** - meters or m/s
- - **f** - feet or ft/s
- - **k** - kilometers or km/hr
- - **m** - miles or mi/hr
- - **n** - nautical miles or knots
- - **u** - survey feet or sft/s
+ - **c**: Cartesian userdist or userdist/usertime
+ - **e**: meters or m/s
+ - **f**: feet or ft/s
+ - **k**: kilometers or km/hr
+ - **m**: miles or mi/hr
+ - **n**: nautical miles or knots
+ - **u**: survey feet or sft/s
[Default is ``units=["dk", "se"]`` (km and m/s) if ``discontinuity`` is
set, and ``units=["dc", "sc"]`` otherwise (e.g., for Cartesian units)].
diff --git a/pygmt/tests/baseline/test_hlines_clip.png.dvc b/pygmt/tests/baseline/test_hlines_clip.png.dvc
new file mode 100644
index 00000000000..1c24bb1c16d
--- /dev/null
+++ b/pygmt/tests/baseline/test_hlines_clip.png.dvc
@@ -0,0 +1,5 @@
+outs:
+- md5: e87ea1b80ae5d32d49e9ad94a5c25f96
+ size: 7199
+ hash: md5
+ path: test_hlines_clip.png
diff --git a/pygmt/tests/baseline/test_hlines_geographic_global_d.png.dvc b/pygmt/tests/baseline/test_hlines_geographic_global_d.png.dvc
new file mode 100644
index 00000000000..960f3a05fdc
--- /dev/null
+++ b/pygmt/tests/baseline/test_hlines_geographic_global_d.png.dvc
@@ -0,0 +1,5 @@
+outs:
+- md5: b7055f03ff5bc152c0f6b72f2d39f32c
+ size: 29336
+ hash: md5
+ path: test_hlines_geographic_global_d.png
diff --git a/pygmt/tests/baseline/test_hlines_geographic_global_g.png.dvc b/pygmt/tests/baseline/test_hlines_geographic_global_g.png.dvc
new file mode 100644
index 00000000000..29b83d3b44f
--- /dev/null
+++ b/pygmt/tests/baseline/test_hlines_geographic_global_g.png.dvc
@@ -0,0 +1,5 @@
+outs:
+- md5: ab2e7717cad6ac4132fd3e3af1fefa89
+ size: 29798
+ hash: md5
+ path: test_hlines_geographic_global_g.png
diff --git a/pygmt/tests/baseline/test_hlines_multiple_lines.png.dvc b/pygmt/tests/baseline/test_hlines_multiple_lines.png.dvc
new file mode 100644
index 00000000000..e915a5a65f0
--- /dev/null
+++ b/pygmt/tests/baseline/test_hlines_multiple_lines.png.dvc
@@ -0,0 +1,5 @@
+outs:
+- md5: 70c8decbffd37fc48b2eb9ff84442ec0
+ size: 14139
+ hash: md5
+ path: test_hlines_multiple_lines.png
diff --git a/pygmt/tests/baseline/test_hlines_one_line.png.dvc b/pygmt/tests/baseline/test_hlines_one_line.png.dvc
new file mode 100644
index 00000000000..aa42ce3f492
--- /dev/null
+++ b/pygmt/tests/baseline/test_hlines_one_line.png.dvc
@@ -0,0 +1,5 @@
+outs:
+- md5: 121970f75d34c552e632cacc692f09e9
+ size: 13685
+ hash: md5
+ path: test_hlines_one_line.png
diff --git a/pygmt/tests/baseline/test_hlines_polar_projection.png.dvc b/pygmt/tests/baseline/test_hlines_polar_projection.png.dvc
new file mode 100644
index 00000000000..4e5bef96dc6
--- /dev/null
+++ b/pygmt/tests/baseline/test_hlines_polar_projection.png.dvc
@@ -0,0 +1,5 @@
+outs:
+- md5: 0c0eeb160dd6beb06bb6d3dcc264127a
+ size: 57789
+ hash: md5
+ path: test_hlines_polar_projection.png
diff --git a/pygmt/tests/baseline/test_plot_datetime.png.dvc b/pygmt/tests/baseline/test_plot_datetime.png.dvc
index 714104995ba..1450b29ef82 100644
--- a/pygmt/tests/baseline/test_plot_datetime.png.dvc
+++ b/pygmt/tests/baseline/test_plot_datetime.png.dvc
@@ -1,5 +1,5 @@
outs:
-- md5: 583947facaa873122f0bf18137809cd4
- size: 12695
+- md5: 0a2eae0da1e3d5b71d7392de1c081346
+ size: 13124
path: test_plot_datetime.png
hash: md5
diff --git a/pygmt/tests/baseline/test_vlines_clip.png.dvc b/pygmt/tests/baseline/test_vlines_clip.png.dvc
new file mode 100644
index 00000000000..f20f77ae249
--- /dev/null
+++ b/pygmt/tests/baseline/test_vlines_clip.png.dvc
@@ -0,0 +1,5 @@
+outs:
+- md5: 4eb9c7fd7e3a803dcc3cde1409ad7fa7
+ size: 7361
+ hash: md5
+ path: test_vlines_clip.png
diff --git a/pygmt/tests/baseline/test_vlines_geographic_global.png.dvc b/pygmt/tests/baseline/test_vlines_geographic_global.png.dvc
new file mode 100644
index 00000000000..d09fa8f8d82
--- /dev/null
+++ b/pygmt/tests/baseline/test_vlines_geographic_global.png.dvc
@@ -0,0 +1,5 @@
+outs:
+- md5: 3fb4a271c670e4cbe647838b6fee5a8c
+ size: 67128
+ hash: md5
+ path: test_vlines_geographic_global.png
diff --git a/pygmt/tests/baseline/test_vlines_multiple_lines.png.dvc b/pygmt/tests/baseline/test_vlines_multiple_lines.png.dvc
new file mode 100644
index 00000000000..da9a4bf8aed
--- /dev/null
+++ b/pygmt/tests/baseline/test_vlines_multiple_lines.png.dvc
@@ -0,0 +1,5 @@
+outs:
+- md5: 499b2d08832247673f208b1c0a282c4c
+ size: 13874
+ hash: md5
+ path: test_vlines_multiple_lines.png
diff --git a/pygmt/tests/baseline/test_vlines_one_line.png.dvc b/pygmt/tests/baseline/test_vlines_one_line.png.dvc
new file mode 100644
index 00000000000..efc2df680b3
--- /dev/null
+++ b/pygmt/tests/baseline/test_vlines_one_line.png.dvc
@@ -0,0 +1,5 @@
+outs:
+- md5: 2cd30ad55fc660123c67e6a684a5ea21
+ size: 13589
+ hash: md5
+ path: test_vlines_one_line.png
diff --git a/pygmt/tests/baseline/test_vlines_polar_projection.png.dvc b/pygmt/tests/baseline/test_vlines_polar_projection.png.dvc
new file mode 100644
index 00000000000..1252a2d0455
--- /dev/null
+++ b/pygmt/tests/baseline/test_vlines_polar_projection.png.dvc
@@ -0,0 +1,5 @@
+outs:
+- md5: 1981df3bd9c57cd975b6e74946496175
+ size: 44621
+ hash: md5
+ path: test_vlines_polar_projection.png
diff --git a/pygmt/tests/test_accessor.py b/pygmt/tests/test_accessor.py
index 2701d2a0b3a..07ece609b69 100644
--- a/pygmt/tests/test_accessor.py
+++ b/pygmt/tests/test_accessor.py
@@ -73,6 +73,7 @@ def test_accessor_set_non_boolean():
grid.gmt.gtype = 2
+# TODO(GMT>=6.5.0): Remove the xfail marker for GMT>=6.5.0.
@pytest.mark.xfail(
condition=sys.platform == "win32" and Version(__gmt_version__) < Version("6.5.0"),
reason="Upstream bug fixed in https://github.com/GenericMappingTools/gmt/pull/7573",
diff --git a/pygmt/tests/test_clib_loading.py b/pygmt/tests/test_clib_loading.py
index 98d9ae148c0..8b96128c0da 100644
--- a/pygmt/tests/test_clib_loading.py
+++ b/pygmt/tests/test_clib_loading.py
@@ -9,6 +9,7 @@
import sys
import types
from pathlib import PurePath
+from unittest import mock
import pytest
from pygmt.clib.loading import (
@@ -70,24 +71,15 @@ def test_load_libgmt():
check_libgmt(load_libgmt())
-def test_load_libgmt_fails(monkeypatch):
+def test_load_libgmt_fails():
"""
Test that GMTCLibNotFoundError is raised when GMT's shared library cannot be found.
"""
- with monkeypatch.context() as mpatch:
- if sys.platform == "win32":
- mpatch.setattr(ctypes.util, "find_library", lambda name: "fakegmt.dll") # noqa: ARG005
- mpatch.setattr(
- sys,
- "platform",
- # Pretend to be on macOS if running on Linux, and vice versa
- "darwin" if sys.platform == "linux" else "linux",
- )
- mpatch.setattr(
- subprocess,
- "check_output",
- lambda cmd, encoding: "libfakegmt.so", # noqa: ARG005
- )
+ with (
+ mock.patch("ctypes.util.find_library", return_value="fakegmt.dll"),
+ mock.patch("sys.platform", "darwin" if sys.platform == "linux" else "linux"),
+ mock.patch("subprocess.check_output", return_value="libfakegmt.so"),
+ ):
with pytest.raises(GMTCLibNotFoundError):
check_libgmt(load_libgmt())
@@ -214,42 +206,25 @@ def test_brokenlib_brokenlib_workinglib(self):
assert check_libgmt(load_libgmt(lib_fullnames=lib_fullnames)) is None
-class TestLibgmtCount:
+def test_libgmt_load_counter():
"""
- Test that the GMT library is not repeatedly loaded in every session.
+ Make sure that the GMT library is not loaded in every session.
"""
-
- loaded_libgmt = load_libgmt() # Load the GMT library and reuse it when necessary
- counter = 0 # Global counter for how many times ctypes.CDLL is called
-
- def _mock_ctypes_cdll_return(self, libname): # noqa: ARG002
- """
- Mock ctypes.CDLL to count how many times the function is called.
-
- If ctypes.CDLL is called, the counter increases by one.
- """
- self.counter += 1 # Increase the counter
- return self.loaded_libgmt
-
- def test_libgmt_load_counter(self, monkeypatch):
- """
- Make sure that the GMT library is not loaded in every session.
- """
- # Monkeypatch the ctypes.CDLL function
- monkeypatch.setattr(ctypes, "CDLL", self._mock_ctypes_cdll_return)
-
- # Create two sessions and check the global counter
+ loaded_libgmt = load_libgmt() # Load the GMT library and reuse it when necessary.
+ with mock.patch("ctypes.CDLL", return_value=loaded_libgmt) as mock_cdll:
+ # Create two sessions and check the call count
with Session() as lib:
_ = lib
with Session() as lib:
_ = lib
- assert self.counter == 0 # ctypes.CDLL is not called after two sessions.
+ # ctypes.CDLL is not called after two sessions.
+ assert mock_cdll.call_count == 0
- # Explicitly calling load_libgmt to make sure the mock function is correct
+ # Explicitly calling load_libgmt to make sure the mock function is correct.
load_libgmt()
- assert self.counter == 1
+ assert mock_cdll.call_count == 1
load_libgmt()
- assert self.counter == 2
+ assert mock_cdll.call_count == 2
###############################################################################
diff --git a/pygmt/tests/test_clib_to_numpy.py b/pygmt/tests/test_clib_to_numpy.py
index 29fc50826ab..40b45e466d8 100644
--- a/pygmt/tests/test_clib_to_numpy.py
+++ b/pygmt/tests/test_clib_to_numpy.py
@@ -2,8 +2,8 @@
Tests for the _to_numpy function in the clib.conversion module.
"""
+import datetime
import sys
-from datetime import date, datetime
import numpy as np
import numpy.testing as npt
@@ -54,6 +54,7 @@ def _check_result(result, expected_dtype):
@pytest.mark.parametrize(
("data", "expected_dtype"),
[
+ # TODO(NumPy>=2.0): Remove the if-else statement after NumPy>=2.0.
pytest.param(
[1, 2, 3],
np.int32
@@ -79,6 +80,70 @@ def test_to_numpy_python_types(data, expected_dtype):
npt.assert_array_equal(result, data)
+@pytest.mark.parametrize(
+ "data",
+ [
+ pytest.param(
+ ["2018", "2018-02", "2018-03-01", "2018-04-01T01:02:03"], id="iso8601"
+ ),
+ pytest.param(
+ [
+ datetime.date(2018, 1, 1),
+ datetime.datetime(2018, 2, 1),
+ datetime.date(2018, 3, 1),
+ datetime.datetime(2018, 4, 1, 1, 2, 3),
+ ],
+ id="datetime",
+ ),
+ pytest.param(
+ [
+ np.datetime64("2018"),
+ np.datetime64("2018-02"),
+ np.datetime64("2018-03-01"),
+ np.datetime64("2018-04-01T01:02:03"),
+ ],
+ id="np_datetime64",
+ ),
+ pytest.param(
+ [
+ pd.Timestamp("2018-01-01"),
+ pd.Timestamp("2018-02-01"),
+ pd.Timestamp("2018-03-01"),
+ pd.Timestamp("2018-04-01T01:02:03"),
+ ],
+ id="pd_timestamp",
+ ),
+ pytest.param(
+ [
+ "2018-01-01",
+ np.datetime64("2018-02-01"),
+ datetime.datetime(2018, 3, 1),
+ pd.Timestamp("2018-04-01T01:02:03"),
+ ],
+ id="mixed",
+ ),
+ ],
+)
+def test_to_numpy_python_datetime(data):
+ """
+ Test the _to_numpy function with Python sequence of datetime types.
+ """
+ result = _to_numpy(data)
+ assert result.dtype.type == np.datetime64
+ npt.assert_array_equal(
+ result,
+ np.array(
+ [
+ "2018-01-01T00:00:00",
+ "2018-02-01T00:00:00",
+ "2018-03-01T00:00:00",
+ "2018-04-01T01:02:03",
+ ],
+ dtype="datetime64[s]",
+ ),
+ )
+
+
########################################################################################
# Test the _to_numpy function with NumPy arrays.
#
@@ -152,6 +217,36 @@ def test_to_numpy_numpy_string(dtype):
npt.assert_array_equal(result, array)
+@pytest.mark.parametrize(
+ "dtype",
+ [
+ np.datetime64, # The expected dtype is "datetime64[D]" for this test.
+ "datetime64[Y]",
+ "datetime64[M]",
+ "datetime64[W]",
+ "datetime64[D]",
+ "datetime64[h]",
+ "datetime64[m]",
+ "datetime64[s]",
+ "datetime64[ms]",
+ "datetime64[us]",
+ "datetime64[ns]",
+ ],
+)
+def test_to_numpy_numpy_datetime(dtype):
+ """
+ Test the _to_ndarray function with 1-D NumPy arrays of datetime.
+
+ Time units "fs", "as", "ps" are not tested here because they can only represent a
+ small range of times in 1969-1970.
+ """
+ array = np.array(["2024-01-01", "2024-01-02", "2024-01-03"], dtype=dtype)
+ result = _to_numpy(array)
+ _check_result(result, np.datetime64)
+ assert result.dtype == (dtype if isinstance(dtype, str) else "datetime64[D]")
+ npt.assert_array_equal(result, array)
+
+
########################################################################################
# Test the _to_numpy function with pandas.Series.
#
@@ -218,9 +313,10 @@ def test_to_numpy_pandas_numeric(dtype, expected_dtype):
Test the _to_numpy function with pandas.Series of numeric dtypes.
"""
data = [1.0, 2.0, 3.0, 4.0, 5.0, 6.0]
+ # TODO(pandas>=2.2): Remove the workaround for float16 dtype in pandas<2.2.
+ # float16 needs special handling for pandas < 2.2.
+ # Example from https://arrow.apache.org/docs/python/generated/pyarrow.float16.html
if dtype == "float16[pyarrow]" and Version(pd.__version__) < Version("2.2"):
- # float16 needs special handling for pandas < 2.2.
- # Example from https://arrow.apache.org/docs/python/generated/pyarrow.float16.html
data = np.array(data, dtype=np.float16)
series = pd.Series(data, dtype=dtype)[::2] # Not C-contiguous
result = _to_numpy(series)
@@ -264,9 +360,10 @@ def test_to_numpy_pandas_numeric_with_na(dtype, expected_dtype):
dtypes and missing values (NA).
"""
data = [1.0, 2.0, None, 4.0, 5.0, 6.0]
+ # TODO(pandas>=2.2): Remove the workaround for float16 dtype in pandas<2.2.
+ # float16 needs special handling for pandas < 2.2.
+ # Example from https://arrow.apache.org/docs/python/generated/pyarrow.float16.html
if dtype == "float16[pyarrow]" and Version(pd.__version__) < Version("2.2"):
- # float16 needs special handling for pandas < 2.2.
- # Example from https://arrow.apache.org/docs/python/generated/pyarrow.float16.html
data = np.array(data, dtype=np.float16)
series = pd.Series(data, dtype=dtype)[::2] # Not C-contiguous
assert series.isna().any()
@@ -287,6 +384,7 @@ def test_to_numpy_pandas_numeric_with_na(dtype, expected_dtype):
"string[pyarrow_numpy]",
marks=[
skip_if_no(package="pyarrow"),
+ # TODO(pandas>=2.1): Remove the skipif marker for pandas<2.1.
pytest.mark.skipif(
Version(pd.__version__) < Version("2.1"),
reason="string[pyarrow_numpy] was added since pandas 2.1",
@@ -331,6 +429,113 @@ def test_to_numpy_pandas_date(dtype, expected_dtype):
)
+pandas_old_version = pytest.mark.xfail(
+ condition=Version(pd.__version__) < Version("2.1"),
+ reason="pandas 2.0 bug reported in https://github.com/pandas-dev/pandas/issues/52705",
+)
+
+
+@pytest.mark.parametrize(
+ ("dtype", "expected_dtype"),
+ [
+ # NumPy datetime64 types. Only unit 's'/'ms'/'us'/'ns' are supported.
+ pytest.param("datetime64[s]", "datetime64[s]", id="datetime64[s]"),
+ pytest.param("datetime64[ms]", "datetime64[ms]", id="datetime64[ms]"),
+ pytest.param("datetime64[us]", "datetime64[us]", id="datetime64[us]"),
+ pytest.param("datetime64[ns]", "datetime64[ns]", id="datetime64[ns]"),
+ # pandas.DatetimeTZDtype can be given in two ways [tz is required]:
+ # 1. pandas.DatetimeTZDtype(unit, tz)
+ # 2. String aliases: "datetime64[unit, tz]"
+ pytest.param(
+ "datetime64[s, UTC]",
+ "datetime64[s]",
+ id="datetime64[s, tz=UTC]",
+ marks=pandas_old_version,
+ ),
+ pytest.param(
+ "datetime64[s, America/New_York]",
+ "datetime64[s]",
+ id="datetime64[s, tz=America/New_York]",
+ marks=pandas_old_version,
+ ),
+ pytest.param(
+ "datetime64[s, +07:30]",
+ "datetime64[s]",
+ id="datetime64[s, +07:30]",
+ marks=pandas_old_version,
+ ),
+ # PyArrow timestamp types can be given in two ways [tz is optional]:
+ # 1. pd.ArrowDtype(pyarrow.Timestamp(unit, tz=tz))
+ # 2. String aliases: "timestamp[unit, tz][pyarrow]"
+ pytest.param(
+ "timestamp[s][pyarrow]",
+ "datetime64[s]",
+ id="timestamp[s][pyarrow]",
+ marks=skip_if_no(package="pyarrow"),
+ ),
+ pytest.param(
+ "timestamp[ms][pyarrow]",
+ "datetime64[ms]",
+ id="timestamp[ms][pyarrow]",
+ marks=[skip_if_no(package="pyarrow"), pandas_old_version],
+ ),
+ pytest.param(
+ "timestamp[us][pyarrow]",
+ "datetime64[us]",
+ id="timestamp[us][pyarrow]",
+ marks=[skip_if_no(package="pyarrow"), pandas_old_version],
+ ),
+ pytest.param(
+ "timestamp[ns][pyarrow]",
+ "datetime64[ns]",
+ id="timestamp[ns][pyarrow]",
+ marks=skip_if_no(package="pyarrow"),
+ ),
+ pytest.param(
+ "timestamp[s, UTC][pyarrow]",
+ "datetime64[s]",
+ id="timestamp[s, UTC][pyarrow]",
+ marks=skip_if_no(package="pyarrow"),
+ ),
+ pytest.param(
+ "timestamp[s, America/New_York][pyarrow]",
+ "datetime64[s]",
+ id="timestamp[s, America/New_York][pyarrow]",
+ marks=skip_if_no(package="pyarrow"),
+ ),
+ pytest.param(
+ "timestamp[s, +08:00][pyarrow]",
+ "datetime64[s]",
+ id="timestamp[s, +08:00][pyarrow]",
+ marks=skip_if_no(package="pyarrow"),
+ ),
+ ],
+)
+def test_to_numpy_pandas_datetime(dtype, expected_dtype):
+ """
+ Test the _to_numpy function with pandas.Series of datetime types.
+ """
+ series = pd.Series(
+ [pd.Timestamp("2024-01-02T03:04:05"), pd.Timestamp("2024-01-02T03:04:06")],
+ dtype=dtype,
+ )
+ result = _to_numpy(series)
+ _check_result(result, np.datetime64)
+ assert result.dtype == expected_dtype
+
+ # Convert to UTC if the dtype is timezone-aware
+ if "," in str(dtype): # A hacky way to decide if the dtype is timezone-aware.
+ # TODO(pandas>=2.1): Simplify the if-else statement.
+ if Version(pd.__version__) < Version("2.1") and dtype.startswith("timestamp"):
+ # pandas 2.0 doesn't have the dt.tz_convert method for pyarrow.Timestamp.
+ series = pd.to_datetime(series, utc=True)
+ else:
+ series = series.dt.tz_convert("UTC")
+ # Remove time zone information and preserve local time.
+ expected_series = series.dt.tz_localize(tz=None)
+ npt.assert_array_equal(result, np.array(expected_series, dtype=expected_dtype))
+
+
########################################################################################
# Test the _to_numpy function with PyArrow arrays.
#
@@ -426,6 +631,7 @@ def test_to_numpy_pyarrow_numeric_with_na(dtype, expected_dtype):
"large_utf8", # alias for large_string
pytest.param(
"string_view",
+ # TODO(pyarrow>=16): Remove the skipif marker for pyarrow<16.
marks=pytest.mark.skipif(
Version(pa.__version__) < Version("16"),
reason="string_view type was added since pyarrow 16",
@@ -461,9 +667,9 @@ def test_to_numpy_pyarrow_date(dtype, expected_dtype):
Here we explicitly check the dtype and date unit of the result.
"""
data = [
- date(2024, 1, 1),
- datetime(2024, 1, 2),
- datetime(2024, 1, 3),
+ datetime.date(2024, 1, 1),
+ datetime.datetime(2024, 1, 2),
+ datetime.datetime(2024, 1, 3),
]
array = pa.array(data, type=dtype)
result = _to_numpy(array)
@@ -507,7 +713,10 @@ def test_to_numpy_pyarrow_timestamp(dtype, expected_dtype):
Reference: https://arrow.apache.org/docs/python/generated/pyarrow.timestamp.html
"""
- data = [datetime(2024, 1, 2, 3, 4, 5), datetime(2024, 1, 2, 3, 4, 6)]
+ data = [
+ datetime.datetime(2024, 1, 2, 3, 4, 5),
+ datetime.datetime(2024, 1, 2, 3, 4, 6),
+ ]
array = pa.array(data, type=dtype)
result = _to_numpy(array)
_check_result(result, np.datetime64)
diff --git a/pygmt/tests/test_clib_virtualfile_from_stringio.py b/pygmt/tests/test_clib_virtualfile_from_stringio.py
index ce6de238a88..62daaa688c8 100644
--- a/pygmt/tests/test_clib_virtualfile_from_stringio.py
+++ b/pygmt/tests/test_clib_virtualfile_from_stringio.py
@@ -43,14 +43,9 @@ def test_virtualfile_from_stringio():
Test the virtualfile_from_stringio method.
"""
data = io.StringIO(
- "# Comment\n"
- "H 24p Legend\n"
- "N 2\n"
- "S 0.1i c 0.15i p300/12 0.25p 0.3i My circle\n"
- )
- expected = (
- ">\n" "H 24p Legend\n" "N 2\n" "S 0.1i c 0.15i p300/12 0.25p 0.3i My circle\n"
+ "# Comment\nH 24p Legend\nN 2\nS 0.1i c 0.15i p300/12 0.25p 0.3i My circle\n"
)
+ expected = ">\nH 24p Legend\nN 2\nS 0.1i c 0.15i p300/12 0.25p 0.3i My circle\n"
assert _stringio_to_dataset(data) == expected
@@ -66,13 +61,7 @@ def test_one_segment():
"6 7 8 9 FGHIJK LMN OPQ\n"
"RSTUVWXYZ\n"
)
- expected = (
- "> Segment 1\n"
- "1 2 3 ABC\n"
- "4 5 DE\n"
- "6 7 8 9 FGHIJK LMN OPQ\n"
- "RSTUVWXYZ\n"
- )
+ expected = "> Segment 1\n1 2 3 ABC\n4 5 DE\n6 7 8 9 FGHIJK LMN OPQ\nRSTUVWXYZ\n"
assert _stringio_to_dataset(data) == expected
diff --git a/pygmt/tests/test_clib_virtualfile_from_vectors.py b/pygmt/tests/test_clib_virtualfile_from_vectors.py
index b76a9bfe168..234ba01d7cc 100644
--- a/pygmt/tests/test_clib_virtualfile_from_vectors.py
+++ b/pygmt/tests/test_clib_virtualfile_from_vectors.py
@@ -192,6 +192,8 @@ def test_virtualfile_from_vectors_arraylike():
assert output == expected
+# TODO(PyGMT>=0.16.0): Remove this test in PyGMT v0.16.0 in which the "*args" parameter
+# will be removed.
def test_virtualfile_from_vectors_args():
"""
Test the backward compatibility of the deprecated syntax for passing multiple
diff --git a/pygmt/tests/test_clib_virtualfile_in.py b/pygmt/tests/test_clib_virtualfile_in.py
index aac8e4af772..8a43c1dc273 100644
--- a/pygmt/tests/test_clib_virtualfile_in.py
+++ b/pygmt/tests/test_clib_virtualfile_in.py
@@ -105,6 +105,7 @@ def test_virtualfile_in_fail_non_valid_data(data):
)
+# TODO(GMT>6.5.0): Remove the xfail marker for GMT<=6.5.0.
@pytest.mark.xfail(
condition=Version(__gmt_version__) <= Version("6.5.0"),
reason="Upstream bug fixed in https://github.com/GenericMappingTools/gmt/pull/8600",
@@ -127,33 +128,3 @@ def test_virtualfile_in_matrix_string_dtype():
assert output == "347.5 348.5 -30.5 -30\n"
# Should check that lib.virtualfile_from_vectors is called once,
# not lib.virtualfile_from_matrix, but it's technically complicated.
-
-
-def test_virtualfile_from_data():
- """
- Test the backwards compatibility of the virtualfile_from_data method.
-
- This test is the same as test_virtualfile_in_required_z_matrix, but using the
- deprecated method.
- """
- shape = (5, 3)
- dataframe = pd.DataFrame(
- data=np.arange(shape[0] * shape[1]).reshape(shape), columns=["x", "y", "z"]
- )
- data = np.array(dataframe)
- with clib.Session() as lib:
- with pytest.warns(FutureWarning, match="virtualfile_from_data"):
- with lib.virtualfile_from_data(
- data=data, required_z=True, check_kind="vector"
- ) as vfile:
- with GMTTempFile() as outfile:
- lib.call_module("info", [vfile, f"->{outfile.name}"])
- output = outfile.read(keep_tabs=True)
- bounds = "\t".join(
- [
- f"<{i.min():.0f}/{i.max():.0f}>"
- for i in (dataframe.x, dataframe.y, dataframe.z)
- ]
- )
- expected = f": N = {shape[0]}\t{bounds}\n"
- assert output == expected
diff --git a/pygmt/tests/test_clib_virtualfiles.py b/pygmt/tests/test_clib_virtualfiles.py
index a45a662de71..ba48200deae 100644
--- a/pygmt/tests/test_clib_virtualfiles.py
+++ b/pygmt/tests/test_clib_virtualfiles.py
@@ -107,33 +107,3 @@ def test_open_virtualfile_bad_direction():
with pytest.raises(GMTInvalidInput):
with lib.open_virtualfile(*vfargs):
pass
-
-
-def test_open_virtual_file():
- """
- Test the deprecated Session.open_virtual_file method.
-
- This test is the same as test_open_virtualfile, but using the deprecated method.
- """
- shape = (5, 3)
- with clib.Session() as lib:
- family = "GMT_IS_DATASET|GMT_VIA_MATRIX"
- geometry = "GMT_IS_POINT"
- dataset = lib.create_data(
- family=family,
- geometry=geometry,
- mode="GMT_CONTAINER_ONLY",
- dim=[shape[1], shape[0], 1, 0], # columns, rows, layers, dtype
- )
- data = np.arange(shape[0] * shape[1]).reshape(shape)
- lib.put_matrix(dataset, matrix=data)
- # Add the dataset to a virtual file and pass it along to gmt info
- with pytest.warns(FutureWarning, match="open_virtual_file"):
- vfargs = (family, geometry, "GMT_IN|GMT_IS_REFERENCE", dataset)
- with lib.open_virtual_file(*vfargs) as vfile:
- with GMTTempFile() as outfile:
- lib.call_module("info", [vfile, f"->{outfile.name}"])
- output = outfile.read(keep_tabs=True)
- bounds = "\t".join([f"<{col.min():.0f}/{col.max():.0f}>" for col in data.T])
- expected = f": N = {shape[0]}\t{bounds}\n"
- assert output == expected
diff --git a/pygmt/tests/test_datasets_earth_deflection.py b/pygmt/tests/test_datasets_earth_deflection.py
new file mode 100644
index 00000000000..9118779a379
--- /dev/null
+++ b/pygmt/tests/test_datasets_earth_deflection.py
@@ -0,0 +1,106 @@
+"""
+Test basic functionality for loading IGPP Earth east-west and south-north deflection
+datasets.
+"""
+
+import numpy as np
+import numpy.testing as npt
+from pygmt.datasets import load_earth_deflection
+
+
+def test_earth_edefl_01d():
+ """
+ Test some properties of the Earth east-west deflection 01d data.
+ """
+ data = load_earth_deflection(resolution="01d")
+ assert data.name == "z"
+ assert data.attrs["long_name"] == "edefl (microradians)"
+ assert data.attrs["description"] == "IGPP Earth east-west deflection"
+ assert data.attrs["units"] == "micro-radians"
+ assert data.attrs["horizontal_datum"] == "WGS84"
+ assert data.shape == (181, 361)
+ assert data.gmt.registration == 0
+ npt.assert_allclose(data.lat, np.arange(-90, 91, 1))
+ npt.assert_allclose(data.lon, np.arange(-180, 181, 1))
+ npt.assert_allclose(data.min(), -142.64, atol=0.04)
+ npt.assert_allclose(data.max(), 178.32, atol=0.04)
+
+
+def test_earth_edefl_01d_with_region():
+ """
+ Test loading low-resolution Earth east-west deflection with "region".
+ """
+ data = load_earth_deflection(resolution="01d", region=[-10, 10, -5, 5])
+ assert data.shape == (11, 21)
+ assert data.gmt.registration == 0
+ npt.assert_allclose(data.lat, np.arange(-5, 6, 1))
+ npt.assert_allclose(data.lon, np.arange(-10, 11, 1))
+ npt.assert_allclose(data.min(), -28.92, atol=0.04)
+ npt.assert_allclose(data.max(), 24.72, atol=0.04)
+
+
+def test_earth_edefl_01m_default_registration():
+ """
+ Test that the grid returned by default for the 1 arc-minute resolution has a "pixel"
+ registration.
+ """
+ data = load_earth_deflection(resolution="01m", region=[-10, -9, 3, 5])
+ assert data.shape == (120, 60)
+ assert data.gmt.registration == 1
+ npt.assert_allclose(data.coords["lat"].data.min(), 3.008333333)
+ npt.assert_allclose(data.coords["lat"].data.max(), 4.991666666)
+ npt.assert_allclose(data.coords["lon"].data.min(), -9.99166666)
+ npt.assert_allclose(data.coords["lon"].data.max(), -9.00833333)
+ npt.assert_allclose(data.min(), -62.24, atol=0.04)
+ npt.assert_allclose(data.max(), 15.52, atol=0.04)
+
+
+def test_earth_ndefl_01d():
+ """
+ Test some properties of the Earth north-south deflection 01d data.
+ """
+ data = load_earth_deflection(resolution="01d", component="north")
+ assert data.name == "z"
+ assert data.attrs["long_name"] == "ndefl (microradians)"
+ assert data.attrs["description"] == "IGPP Earth north-south deflection"
+ assert data.attrs["units"] == "micro-radians"
+ assert data.attrs["horizontal_datum"] == "WGS84"
+ assert data.shape == (181, 361)
+ assert data.gmt.registration == 0
+ npt.assert_allclose(data.lat, np.arange(-90, 91, 1))
+ npt.assert_allclose(data.lon, np.arange(-180, 181, 1))
+ npt.assert_allclose(data.min(), -214.8, atol=0.04)
+ npt.assert_allclose(data.max(), 163.04, atol=0.04)
+
+
+def test_earth_ndefl_01d_with_region():
+ """
+ Test loading low-resolution Earth north-south deflection with "region".
+ """
+ data = load_earth_deflection(
+ resolution="01d", region=[-10, 10, -5, 5], component="north"
+ )
+ assert data.shape == (11, 21)
+ assert data.gmt.registration == 0
+ npt.assert_allclose(data.lat, np.arange(-5, 6, 1))
+ npt.assert_allclose(data.lon, np.arange(-10, 11, 1))
+ npt.assert_allclose(data.min(), -48.08, atol=0.04)
+ npt.assert_allclose(data.max(), 18.92, atol=0.04)
+
+
+def test_earth_ndefl_01m_default_registration():
+ """
+ Test that the grid returned by default for the 1 arc-minute resolution has a "pixel"
+ registration.
+ """
+ data = load_earth_deflection(
+ resolution="01m", region=[-10, -9, 3, 5], component="north"
+ )
+ assert data.shape == (120, 60)
+ assert data.gmt.registration == 1
+ npt.assert_allclose(data.coords["lat"].data.min(), 3.008333333)
+ npt.assert_allclose(data.coords["lat"].data.max(), 4.991666666)
+ npt.assert_allclose(data.coords["lon"].data.min(), -9.99166666)
+ npt.assert_allclose(data.coords["lon"].data.max(), -9.00833333)
+ npt.assert_allclose(data.min(), -107.04, atol=0.04)
+ npt.assert_allclose(data.max(), 20.28, atol=0.04)
diff --git a/pygmt/tests/test_datasets_earth_dist.py b/pygmt/tests/test_datasets_earth_dist.py
new file mode 100644
index 00000000000..a5f61a0b5f2
--- /dev/null
+++ b/pygmt/tests/test_datasets_earth_dist.py
@@ -0,0 +1,53 @@
+"""
+Test basic functionality for loading Earth distance to shoreline datasets.
+"""
+
+import numpy as np
+import numpy.testing as npt
+from pygmt.datasets import load_earth_dist
+
+
+def test_earth_dist_01d():
+ """
+ Test some properties of the Earth distance to shoreline 01d data.
+ """
+ data = load_earth_dist(resolution="01d")
+ assert data.name == "z"
+ assert data.attrs["description"] == "GSHHG Earth distance to shoreline"
+ assert data.attrs["units"] == "kilometers"
+ assert data.attrs["horizontal_datum"] == "WGS84"
+ assert data.shape == (181, 361)
+ assert data.gmt.registration == 0
+ npt.assert_allclose(data.lat, np.arange(-90, 91, 1))
+ npt.assert_allclose(data.lon, np.arange(-180, 181, 1))
+ npt.assert_allclose(data.min(), -2655.7, atol=0.01)
+ npt.assert_allclose(data.max(), 2463.42, atol=0.01)
+
+
+def test_earth_dist_01d_with_region():
+ """
+ Test loading low-resolution Earth distance to shoreline with "region".
+ """
+ data = load_earth_dist(resolution="01d", region=[-10, 10, -5, 5])
+ assert data.shape == (11, 21)
+ assert data.gmt.registration == 0
+ npt.assert_allclose(data.lat, np.arange(-5, 6, 1))
+ npt.assert_allclose(data.lon, np.arange(-10, 11, 1))
+ npt.assert_allclose(data.min(), -1081.94, atol=0.01)
+ npt.assert_allclose(data.max(), 105.18, atol=0.01)
+
+
+def test_earth_dist_01m_default_registration():
+ """
+ Test that the grid returned by default for the 1 arc-minute resolution has a
+ "gridline" registration.
+ """
+ data = load_earth_dist(resolution="01m", region=[-10, -9, 3, 5])
+ assert data.shape == (121, 61)
+ assert data.gmt.registration == 0
+ assert data.coords["lat"].data.min() == 3.0
+ assert data.coords["lat"].data.max() == 5.0
+ assert data.coords["lon"].data.min() == -10.0
+ assert data.coords["lon"].data.max() == -9.0
+ npt.assert_allclose(data.min(), -243.62, atol=0.01)
+ npt.assert_allclose(data.max(), 2.94, atol=0.01)
diff --git a/pygmt/tests/test_datasets_earth_free_air_anomaly.py b/pygmt/tests/test_datasets_earth_free_air_anomaly.py
index 517a1bb9b89..7ce5fc2662c 100644
--- a/pygmt/tests/test_datasets_earth_free_air_anomaly.py
+++ b/pygmt/tests/test_datasets_earth_free_air_anomaly.py
@@ -52,3 +52,54 @@ def test_earth_faa_01m_default_registration():
npt.assert_allclose(data.coords["lon"].data.max(), -9.00833333)
npt.assert_allclose(data.min(), -49.225, atol=0.025)
npt.assert_allclose(data.max(), 115.0, atol=0.025)
+
+
+def test_earth_faaerror_01d():
+ """
+ Test some properties of the free air anomaly error 01d data.
+ """
+ data = load_earth_free_air_anomaly(resolution="01d", uncertainty=True)
+ assert data.name == "z"
+ assert data.attrs["long_name"] == "faaerror (mGal)"
+ assert data.attrs["description"] == "IGPP Earth free-air anomaly errors"
+ assert data.attrs["units"] == "mGal"
+ assert data.attrs["horizontal_datum"] == "WGS84"
+ assert data.shape == (181, 361)
+ assert data.gmt.registration == 0
+ npt.assert_allclose(data.lat, np.arange(-90, 91, 1))
+ npt.assert_allclose(data.lon, np.arange(-180, 181, 1))
+ npt.assert_allclose(data.min(), 0.0, atol=0.04)
+ npt.assert_allclose(data.max(), 49.16, atol=0.04)
+
+
+def test_earth_faaerror_01d_with_region():
+ """
+ Test loading low-resolution earth free air anomaly error with 'region'.
+ """
+ data = load_earth_free_air_anomaly(
+ resolution="01d", region=[-10, 10, -5, 5], uncertainty=True
+ )
+ assert data.shape == (11, 21)
+ assert data.gmt.registration == 0
+ npt.assert_allclose(data.lat, np.arange(-5, 6, 1))
+ npt.assert_allclose(data.lon, np.arange(-10, 11, 1))
+ npt.assert_allclose(data.min(), 0.72, atol=0.04)
+ npt.assert_allclose(data.max(), 21.04, atol=0.04)
+
+
+def test_earth_faaerror_01m_default_registration():
+ """
+ Test that the grid returned by default for the 1 arc-minute resolution has a "pixel"
+ registration.
+ """
+ data = load_earth_free_air_anomaly(
+ resolution="01m", region=[-10, -9, 3, 5], uncertainty=True
+ )
+ assert data.shape == (120, 60)
+ assert data.gmt.registration == 1
+ npt.assert_allclose(data.coords["lat"].data.min(), 3.008333333)
+ npt.assert_allclose(data.coords["lat"].data.max(), 4.991666666)
+ npt.assert_allclose(data.coords["lon"].data.min(), -9.99166666)
+ npt.assert_allclose(data.coords["lon"].data.max(), -9.00833333)
+ npt.assert_allclose(data.min(), 0.40, atol=0.04)
+ npt.assert_allclose(data.max(), 13.36, atol=0.04)
diff --git a/pygmt/tests/test_datasets_earth_geoid.py b/pygmt/tests/test_datasets_earth_geoid.py
index 84bfc5d7bf4..af72969b032 100644
--- a/pygmt/tests/test_datasets_earth_geoid.py
+++ b/pygmt/tests/test_datasets_earth_geoid.py
@@ -15,7 +15,7 @@ def test_earth_geoid_01d():
assert data.name == "z"
assert data.attrs["long_name"] == "geoid (m)"
assert data.attrs["description"] == "EGM2008 Earth geoid"
- assert data.attrs["units"] == "m"
+ assert data.attrs["units"] == "meters"
assert data.attrs["horizontal_datum"] == "WGS84"
assert data.shape == (181, 361)
assert data.gmt.registration == 0
diff --git a/pygmt/tests/test_datasets_earth_mean_sea_surface.py b/pygmt/tests/test_datasets_earth_mean_sea_surface.py
new file mode 100644
index 00000000000..84b2b7123cc
--- /dev/null
+++ b/pygmt/tests/test_datasets_earth_mean_sea_surface.py
@@ -0,0 +1,53 @@
+"""
+Test basic functionality for loading Earth mean sea surface datasets.
+"""
+
+import numpy as np
+import numpy.testing as npt
+from pygmt.datasets import load_earth_mean_sea_surface
+
+
+def test_earth_mss_01d():
+ """
+ Test some properties of the Earth mean sea surface 01d data.
+ """
+ data = load_earth_mean_sea_surface(resolution="01d")
+ assert data.name == "z"
+ assert data.attrs["description"] == "CNES Earth mean sea surface"
+ assert data.attrs["units"] == "meters"
+ assert data.attrs["horizontal_datum"] == "WGS84"
+ assert data.shape == (181, 361)
+ assert data.gmt.registration == 0
+ npt.assert_allclose(data.lat, np.arange(-90, 91, 1))
+ npt.assert_allclose(data.lon, np.arange(-180, 181, 1))
+ npt.assert_allclose(data.min(), -104.71, atol=0.01)
+ npt.assert_allclose(data.max(), 82.38, atol=0.01)
+
+
+def test_earth_mss_01d_with_region():
+ """
+ Test loading low-resolution Earth mean sea surface with "region".
+ """
+ data = load_earth_mean_sea_surface(resolution="01d", region=[-10, 10, -5, 5])
+ assert data.shape == (11, 21)
+ assert data.gmt.registration == 0
+ npt.assert_allclose(data.lat, np.arange(-5, 6, 1))
+ npt.assert_allclose(data.lon, np.arange(-10, 11, 1))
+ npt.assert_allclose(data.min(), 6.53, atol=0.01)
+ npt.assert_allclose(data.max(), 29.31, atol=0.01)
+
+
+def test_earth_mss_01m_default_registration():
+ """
+ Test that the grid returned by default for the 1 arc-minute resolution has a
+ "gridline" registration.
+ """
+ data = load_earth_mean_sea_surface(resolution="01m", region=[-10, -9, 3, 5])
+ assert data.shape == (121, 61)
+ assert data.gmt.registration == 0
+ assert data.coords["lat"].data.min() == 3.0
+ assert data.coords["lat"].data.max() == 5.0
+ assert data.coords["lon"].data.min() == -10.0
+ assert data.coords["lon"].data.max() == -9.0
+ npt.assert_allclose(data.min(), 21.27, atol=0.01)
+ npt.assert_allclose(data.max(), 31.11, atol=0.01)
diff --git a/pygmt/tests/test_datasets_earth_relief.py b/pygmt/tests/test_datasets_earth_relief.py
index 44af0e4ff98..b0851430907 100644
--- a/pygmt/tests/test_datasets_earth_relief.py
+++ b/pygmt/tests/test_datasets_earth_relief.py
@@ -192,6 +192,7 @@ def test_earth_relief_15s_default_registration():
npt.assert_allclose(data.max(), -76.5, atol=0.5)
+# TODO(GMT X.Y.Z): Upstream bug which is not fixed yet.
@pytest.mark.xfail(
condition=Version(__gmt_version__) >= Version("6.5.0"),
reason="Upstream bug tracked in https://github.com/GenericMappingTools/pygmt/issues/2511",
diff --git a/pygmt/tests/test_datasets_mean_dynamic_topography.py b/pygmt/tests/test_datasets_mean_dynamic_topography.py
new file mode 100644
index 00000000000..deae6e90a60
--- /dev/null
+++ b/pygmt/tests/test_datasets_mean_dynamic_topography.py
@@ -0,0 +1,53 @@
+"""
+Test basic functionality for loading Earth mean dynamic topography datasets.
+"""
+
+import numpy as np
+import numpy.testing as npt
+from pygmt.datasets import load_earth_mean_dynamic_topography
+
+
+def test_earth_mdt_01d():
+ """
+ Test some properties of the Earth mean dynamic topography 01d data.
+ """
+ data = load_earth_mean_dynamic_topography(resolution="01d")
+ assert data.name == "z"
+ assert data.attrs["description"] == "CNES Earth mean dynamic topography"
+ assert data.attrs["units"] == "meters"
+ assert data.attrs["horizontal_datum"] == "WGS84"
+ assert data.shape == (181, 361)
+ assert data.gmt.registration == 0
+ npt.assert_allclose(data.lat, np.arange(-90, 91, 1))
+ npt.assert_allclose(data.lon, np.arange(-180, 181, 1))
+ npt.assert_allclose(data.min(), -1.4668, atol=0.0001)
+ npt.assert_allclose(data.max(), 1.7151, atol=0.0001)
+
+
+def test_earth_mdt_01d_with_region():
+ """
+ Test loading low-resolution Earth mean dynamic topography with "region".
+ """
+ data = load_earth_mean_dynamic_topography(resolution="01d", region=[-10, 10, -5, 5])
+ assert data.shape == (11, 21)
+ assert data.gmt.registration == 0
+ npt.assert_allclose(data.lat, np.arange(-5, 6, 1))
+ npt.assert_allclose(data.lon, np.arange(-10, 11, 1))
+ npt.assert_allclose(data.min(), 0.346, atol=0.0001)
+ npt.assert_allclose(data.max(), 0.4839, atol=0.0001)
+
+
+def test_earth_mdt_07m_default_registration():
+ """
+ Test that the grid returned by default for the 7 arc-minutes resolution has a
+ "gridline" registration.
+ """
+ data = load_earth_mean_dynamic_topography(resolution="07m", region=[-10, -9, 3, 5])
+ assert data.shape == (17, 9)
+ assert data.gmt.registration == 0
+ assert data.coords["lat"].data.min() == 3.0
+ assert data.coords["lat"].data.max() == 5.0
+ assert data.coords["lon"].data.min() == -10.0
+ assert data.coords["lon"].data.max() == -9.0
+ npt.assert_allclose(data.min(), 0.4138, atol=0.0001)
+ npt.assert_allclose(data.max(), 0.4302, atol=0.0001)
diff --git a/pygmt/tests/test_grdimage.py b/pygmt/tests/test_grdimage.py
index 943b3f12ddc..907886eb0b3 100644
--- a/pygmt/tests/test_grdimage.py
+++ b/pygmt/tests/test_grdimage.py
@@ -256,6 +256,7 @@ def test_grdimage_imgout_fails(grid):
fig.grdimage(grid, A="out.png")
+# TODO(GMT>6.5.0): Remove the xfail marker for GMT<=6.5.0.
@pytest.mark.xfail(
condition=Version(__gmt_version__) <= Version("6.5.0"),
reason="Upstream bug fixed in https://github.com/GenericMappingTools/gmt/pull/8554",
diff --git a/pygmt/tests/test_grdview.py b/pygmt/tests/test_grdview.py
index f73b1150e54..3be4ed7aa42 100644
--- a/pygmt/tests/test_grdview.py
+++ b/pygmt/tests/test_grdview.py
@@ -161,7 +161,7 @@ def test_grdview_with_perspective_and_zaxis_frame(xrgrid, region):
a Transverse Mercator (T) projection.
"""
fig = Figure()
- projection = f"T{(region[0]+region[1])/2}/{abs((region[2]+region[3])/2)}"
+ projection = f"T{(region[0] + region[1]) / 2}/{abs((region[2] + region[3]) / 2)}"
fig.grdview(
grid=xrgrid,
projection=projection,
diff --git a/pygmt/tests/test_hlines.py b/pygmt/tests/test_hlines.py
new file mode 100644
index 00000000000..aaaddad4f08
--- /dev/null
+++ b/pygmt/tests/test_hlines.py
@@ -0,0 +1,121 @@
+"""
+Tests for Figure.hlines.
+"""
+
+import pytest
+from pygmt import Figure
+from pygmt.exceptions import GMTInvalidInput
+
+
+@pytest.mark.mpl_image_compare
+def test_hlines_one_line():
+ """
+ Plot one horizontal line.
+ """
+ fig = Figure()
+ fig.basemap(region=[0, 10, 0, 10], projection="X10c/10c", frame=True)
+ fig.hlines(1)
+ fig.hlines(2, xmin=1)
+ fig.hlines(3, xmax=9)
+ fig.hlines(4, xmin=3, xmax=8)
+ fig.hlines(5, xmin=4, xmax=8, pen="1p,blue", label="Line at y=5")
+ fig.hlines(6, xmin=5, xmax=7, pen="1p,red", label="Line at y=6")
+ fig.legend()
+ return fig
+
+
+@pytest.mark.mpl_image_compare
+def test_hlines_multiple_lines():
+ """
+ Plot multiple horizontal lines.
+ """
+ fig = Figure()
+ fig.basemap(region=[0, 10, 0, 16], projection="X10c/10c", frame=True)
+ fig.hlines([1, 2])
+ fig.hlines([3, 4, 5], xmin=[1, 2, 3])
+ fig.hlines([6, 7, 8], xmax=[7, 8, 9])
+ fig.hlines([9, 10], xmin=[1, 2], xmax=[9, 10])
+ fig.hlines([11, 12], xmin=1, xmax=9, pen="1p,blue", label="Lines at y=11,12")
+ fig.hlines(
+ [13, 14], xmin=[3, 4], xmax=[8, 9], pen="1p,red", label="Lines at y=13,14"
+ )
+ fig.legend()
+ return fig
+
+
+@pytest.mark.mpl_image_compare
+def test_hlines_clip():
+ """
+ Plot horizontal lines with clipping or not.
+ """
+ fig = Figure()
+ fig.basemap(region=[0, 10, 0, 4], projection="X10c/4c", frame=True)
+ fig.hlines(1, xmin=-2, xmax=12)
+ fig.hlines(2, xmin=-2, xmax=12, no_clip=True)
+ return fig
+
+
+@pytest.mark.mpl_image_compare
+@pytest.mark.parametrize("region", ["g", "d"])
+def test_hlines_geographic_global(region):
+ """
+ Plot horizontal lines in geographic coordinates.
+ """
+ fig = Figure()
+ fig.basemap(region=region, projection="R15c", frame=True)
+ # Plot lines with longitude range of 0 to 360.
+ fig.hlines(10, pen="1p")
+ fig.hlines(20, xmin=0, xmax=360, pen="1p")
+ fig.hlines(30, xmin=0, xmax=180, pen="1p")
+ fig.hlines(40, xmin=180, xmax=360, pen="1p")
+ fig.hlines(50, xmin=0, xmax=90, pen="1p")
+ fig.hlines(60, xmin=90, xmax=180, pen="1p")
+ fig.hlines(70, xmin=180, xmax=270, pen="1p")
+ fig.hlines(80, xmin=270, xmax=360, pen="1p")
+
+ # Plot lines with longitude range of -180 to 180.
+ fig.hlines(-10, pen="1p,red")
+ fig.hlines(-20, xmin=-180, xmax=180, pen="1p,red")
+ fig.hlines(-30, xmin=-180, xmax=0, pen="1p,red")
+ fig.hlines(-40, xmin=0, xmax=180, pen="1p,red")
+ fig.hlines(-50, xmin=-180, xmax=-90, pen="1p,red")
+ fig.hlines(-60, xmin=-90, xmax=0, pen="1p,red")
+ fig.hlines(-70, xmin=0, xmax=90, pen="1p,red")
+ fig.hlines(-80, xmin=90, xmax=180, pen="1p,red")
+ return fig
+
+
+@pytest.mark.mpl_image_compare
+def test_hlines_polar_projection():
+ """
+ Plot horizontal lines in polar projection.
+ """
+ fig = Figure()
+ fig.basemap(region=[0, 360, 0, 1], projection="P15c", frame=True)
+ fig.hlines(0.1, pen="1p")
+ fig.hlines(0.2, xmin=0, xmax=360, pen="1p")
+ fig.hlines(0.3, xmin=0, xmax=180, pen="1p")
+ fig.hlines(0.4, xmin=180, xmax=360, pen="1p")
+ fig.hlines(0.5, xmin=0, xmax=90, pen="1p")
+ fig.hlines(0.6, xmin=90, xmax=180, pen="1p")
+ fig.hlines(0.7, xmin=180, xmax=270, pen="1p")
+ fig.hlines(0.8, xmin=270, xmax=360, pen="1p")
+ return fig
+
+
+def test_hlines_invalid_input():
+ """
+ Test invalid input for hlines.
+ """
+ fig = Figure()
+ fig.basemap(region=[0, 10, 0, 6], projection="X10c/6c", frame=True)
+ with pytest.raises(GMTInvalidInput):
+ fig.hlines(1, xmin=2, xmax=[3, 4])
+ with pytest.raises(GMTInvalidInput):
+ fig.hlines(1, xmin=[2, 3], xmax=4)
+ with pytest.raises(GMTInvalidInput):
+ fig.hlines(1, xmin=[2, 3], xmax=[4, 5])
+ with pytest.raises(GMTInvalidInput):
+ fig.hlines([1, 2], xmin=[2, 3, 4], xmax=3)
+ with pytest.raises(GMTInvalidInput):
+ fig.hlines([1, 2], xmin=[2, 3], xmax=[4, 5, 6])
diff --git a/pygmt/tests/test_info.py b/pygmt/tests/test_info.py
index 3ac9f27c4e1..d055abb61ec 100644
--- a/pygmt/tests/test_info.py
+++ b/pygmt/tests/test_info.py
@@ -23,10 +23,7 @@ def test_info():
"""
output = info(data=POINTS_DATA)
expected_output = (
- f"{POINTS_DATA}: N = 20 "
- "<11.5309/61.7074> "
- "<-2.9289/7.8648> "
- "<0.1412/0.9338>\n"
+ f"{POINTS_DATA}: N = 20 <11.5309/61.7074> <-2.9289/7.8648> <0.1412/0.9338>\n"
)
assert output == expected_output
@@ -57,10 +54,7 @@ def test_info_path(table):
"""
output = info(data=table)
expected_output = (
- f"{POINTS_DATA}: N = 20 "
- "<11.5309/61.7074> "
- "<-2.9289/7.8648> "
- "<0.1412/0.9338>\n"
+ f"{POINTS_DATA}: N = 20 <11.5309/61.7074> <-2.9289/7.8648> <0.1412/0.9338>\n"
)
assert output == expected_output
diff --git a/pygmt/tests/test_meca.py b/pygmt/tests/test_meca.py
index 2e71cc9665d..424486ac408 100644
--- a/pygmt/tests/test_meca.py
+++ b/pygmt/tests/test_meca.py
@@ -143,6 +143,7 @@ def test_meca_spec_multiple_focalmecha(inputtype):
return fig
+# TODO(GMT>=6.5.0): Remove the skipif condition for GMT>=6.5.0.
@pytest.mark.mpl_image_compare(filename="test_meca_offset.png")
@pytest.mark.parametrize(
"inputtype",
@@ -201,8 +202,9 @@ def test_meca_offset(inputtype):
return fig
-# Passing event names via pandas doesn't work for GMT<=6.4, thus marked as
-# xfail. See https://github.com/GenericMappingTools/pygmt/issues/2524.
+# TODO(GMT>=6.5.0): Remove the skipif marker for GMT>=6.5.0.
+# Passing event names via pandas doesn't work for GMT<=6.4.
+# See https://github.com/GenericMappingTools/pygmt/issues/2524.
@pytest.mark.mpl_image_compare(filename="test_meca_eventname.png")
@pytest.mark.parametrize(
"inputtype",
diff --git a/pygmt/tests/test_plot.py b/pygmt/tests/test_plot.py
index 721b7841307..c2f2b846724 100644
--- a/pygmt/tests/test_plot.py
+++ b/pygmt/tests/test_plot.py
@@ -467,9 +467,14 @@ def test_plot_datetime():
fig.plot(x=x, y=y, style="a0.2c", pen="1p")
# the Python built-in datetime and date
- x = [datetime.date(2018, 1, 1), datetime.datetime(2019, 1, 1)]
+ x = [datetime.date(2018, 1, 1), datetime.datetime(2019, 1, 1, 0, 0, 0)]
y = [8.5, 9.5]
fig.plot(x=x, y=y, style="i0.2c", pen="1p")
+
+ # Python sequence of pd.Timestamp
+ x = [pd.Timestamp("2018-01-01"), pd.Timestamp("2019-01-01")]
+ y = [5.5, 6.5]
+ fig.plot(x=x, y=y, style="d0.2c", pen="1p")
return fig
diff --git a/pygmt/tests/test_vlines.py b/pygmt/tests/test_vlines.py
new file mode 100644
index 00000000000..21aff1c06d5
--- /dev/null
+++ b/pygmt/tests/test_vlines.py
@@ -0,0 +1,102 @@
+"""
+Tests for Figure.vlines.
+"""
+
+import pytest
+from pygmt import Figure
+from pygmt.exceptions import GMTInvalidInput
+
+
+@pytest.mark.mpl_image_compare
+def test_vlines_one_line():
+ """
+ Plot one vertical line.
+ """
+ fig = Figure()
+ fig.basemap(region=[0, 10, 0, 10], projection="X10c/10c", frame=True)
+ fig.vlines(1)
+ fig.vlines(2, ymin=1)
+ fig.vlines(3, ymax=9)
+ fig.vlines(4, ymin=3, ymax=8)
+ fig.vlines(5, ymin=4, ymax=8, pen="1p,blue", label="Line at x=5")
+ fig.vlines(6, ymin=5, ymax=7, pen="1p,red", label="Line at x=6")
+ fig.legend()
+ return fig
+
+
+@pytest.mark.mpl_image_compare
+def test_vlines_multiple_lines():
+ """
+ Plot multiple vertical lines.
+ """
+ fig = Figure()
+ fig.basemap(region=[0, 16, 0, 10], projection="X10c/10c", frame=True)
+ fig.vlines([1, 2])
+ fig.vlines([3, 4, 5], ymin=[1, 2, 3])
+ fig.vlines([6, 7, 8], ymax=[7, 8, 9])
+ fig.vlines([9, 10], ymin=[1, 2], ymax=[9, 10])
+ fig.vlines([11, 12], ymin=1, ymax=8, pen="1p,blue", label="Lines at x=11,12")
+ fig.vlines(
+ [13, 14], ymin=[3, 4], ymax=[7, 8], pen="1p,red", label="Lines at x=13,14"
+ )
+ fig.legend()
+ return fig
+
+
+@pytest.mark.mpl_image_compare
+def test_vlines_clip():
+ """
+ Plot vertical lines with clipping or not.
+ """
+ fig = Figure()
+ fig.basemap(region=[0, 10, 0, 4], projection="X10c/4c", frame=True)
+ fig.vlines(1, ymin=-1, ymax=5)
+ fig.vlines(2, ymin=-1, ymax=5, no_clip=True)
+ return fig
+
+
+@pytest.mark.mpl_image_compare
+def test_vlines_geographic_global():
+ """
+ Plot vertical lines in geographic coordinates.
+ """
+ fig = Figure()
+ fig.basemap(region=[-180, 180, -90, 90], projection="R15c", frame="a30g30")
+ fig.vlines(30, pen="1p")
+ fig.vlines(90, ymin=-60, pen="1p,blue")
+ fig.vlines(-90, ymax=60, pen="1p,blue")
+ fig.vlines(120, ymin=-60, ymax=60, pen="1p,blue")
+ return fig
+
+
+@pytest.mark.mpl_image_compare
+def test_vlines_polar_projection():
+ """
+ Plot vertical lines in polar projection.
+ """
+ fig = Figure()
+ fig.basemap(region=[0, 360, 0, 1], projection="P15c", frame=True)
+ fig.vlines(0, pen="1p")
+ fig.vlines(30, ymin=0, ymax=1, pen="1p")
+ fig.vlines(60, ymin=0.5, pen="1p")
+ fig.vlines(90, ymax=0.5, pen="1p")
+ fig.vlines(120, ymin=0.25, ymax=0.75, pen="1p")
+ return fig
+
+
+def test_vlines_invalid_input():
+ """
+ Test invalid input for vlines.
+ """
+ fig = Figure()
+ fig.basemap(region=[0, 10, 0, 6], projection="X10c/6c", frame=True)
+ with pytest.raises(GMTInvalidInput):
+ fig.vlines(1, ymin=2, ymax=[3, 4])
+ with pytest.raises(GMTInvalidInput):
+ fig.vlines(1, ymin=[2, 3], ymax=4)
+ with pytest.raises(GMTInvalidInput):
+ fig.vlines(1, ymin=[2, 3], ymax=[4, 5])
+ with pytest.raises(GMTInvalidInput):
+ fig.vlines([1, 2], ymin=[2, 3, 4], ymax=3)
+ with pytest.raises(GMTInvalidInput):
+ fig.vlines([1, 2], ymin=[2, 3], ymax=[4, 5, 6])
diff --git a/pygmt/tests/test_x2sys_cross.py b/pygmt/tests/test_x2sys_cross.py
index c72cca04420..70f0c3cf003 100644
--- a/pygmt/tests/test_x2sys_cross.py
+++ b/pygmt/tests/test_x2sys_cross.py
@@ -37,6 +37,7 @@ def fixture_tracks():
return [dataframe.query(expr="z > -20")] # reduce size of dataset
+# TODO(GMT>=6.5.0): Remove the xfail marker for the upstream bug fixed in GMT 6.5.0.
@pytest.mark.usefixtures("mock_x2sys_home")
@pytest.mark.xfail(
condition=Version(__gmt_version__) < Version("6.5.0"),
@@ -66,6 +67,7 @@ def test_x2sys_cross_input_file_output_file():
npt.assert_allclose(result["i_1"].max(), 82945.9370, rtol=1.0e-4)
+# TODO(GMT>=6.5.0): Remove the xfail marker for the upstream bug fixed in GMT 6.5.0.
@pytest.mark.usefixtures("mock_x2sys_home")
@pytest.mark.xfail(
condition=Version(__gmt_version__) < Version("6.5.0"),
@@ -244,6 +246,7 @@ def test_x2sys_cross_invalid_tracks_input_type(tracks):
x2sys_cross(tracks=[invalid_tracks])
+# TODO(GMT>=6.5.0): Remove the xfail marker for the upstream bug fixed in GMT 6.5.0.
@pytest.mark.usefixtures("mock_x2sys_home")
@pytest.mark.xfail(
condition=Version(__gmt_version__) < Version("6.5.0"),
@@ -279,6 +282,7 @@ def test_x2sys_cross_region_interpolation_numpoints():
npt.assert_allclose(output.z_M.mean(), -2896.875915, rtol=1e-4)
+# TODO(GMT>=6.5.0): Remove the xfail marker for the upstream bug fixed in GMT 6.5.0.
@pytest.mark.usefixtures("mock_x2sys_home")
@pytest.mark.xfail(
condition=Version(__gmt_version__) < Version("6.5.0"),
diff --git a/pyproject.toml b/pyproject.toml
index b4907e6dc64..61c6a541fef 100644
--- a/pyproject.toml
+++ b/pyproject.toml
@@ -48,10 +48,11 @@ all = [
]
[project.urls]
-homepage = "https://www.pygmt.org"
-documentation = "https://www.pygmt.org"
-repository = "https://github.com/GenericMappingTools/pygmt"
-changelog = "https://www.pygmt.org/latest/changes.html"
+"Homepage" = "https://www.pygmt.org"
+"Documentation" = "https://www.pygmt.org"
+"Source Code" = "https://github.com/GenericMappingTools/pygmt"
+"Changelog" = "https://www.pygmt.org/latest/changes.html"
+"Issue Tracker" = "https://github.com/GenericMappingTools/pygmt/issues"
[tool.setuptools]
platforms = ["Any"]
@@ -66,7 +67,7 @@ local_scheme = "node-and-date"
fallback_version = "999.999.999+unknown"
[tool.codespell]
-ignore-words-list = "astroid,oints,reenable,tripel,trough"
+ignore-words-list = "astroid,oints,reenable,tripel,trough,ND"
[tool.coverage.run]
omit = ["*/tests/*", "*pygmt/__init__.py"]
@@ -119,6 +120,7 @@ select = [
"SIM", # flake8-simplify
"T20", # flake8-print
"TC", # flake8-type-checking
+ "TD", # flake8-todos
"TID", # flake8-tidy-imports
"TRY", # tryceratops
"UP", # pyupgrade
@@ -147,6 +149,7 @@ ignore = [
"RET504", # Allow variable assignment and return immediately for readability
"S603", # Allow method calls that initiate a subprocess without a shell
"SIM117", # Allow nested `with` statements
+ "TD003", # Allow TODO comments without associated issue link
]
preview = true
explicit-preview-rules = true
@@ -157,7 +160,10 @@ known-third-party = ["pygmt"]
[tool.ruff.lint.per-file-ignores]
"__init__.py" = ["F401"] # Ignore `F401` (unused-import) in all `__init__.py` files
"*/tests/test_*.py" = ["S101"] # Ignore `S101` (use of assert) in all tests files
-"examples/**/*.py" = ["T201"] # Allow `print` in examples
+"examples/**/*.py" = [ # Ignore rules in examples
+ "B018", # Allow useless expressions in Jupyter Notebooks
+ "T201", # Allow `print` statements
+]
[tool.ruff.lint.pycodestyle]
max-doc-length = 88