diff --git a/.github/ISSUE_TEMPLATE/4-release_checklist.md b/.github/ISSUE_TEMPLATE/4-release_checklist.md
index 83fe809724a..87a164b15c8 100644
--- a/.github/ISSUE_TEMPLATE/4-release_checklist.md
+++ b/.github/ISSUE_TEMPLATE/4-release_checklist.md
@@ -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:
- [ ] 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/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 a005ba5e87b..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.5.0
+ 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 a74d4fbfcd5..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: |
@@ -163,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 2d9091ab384..44b6276ac58 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.2.1
+ 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.5.0
+ 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.2
+ uses: codecov/codecov-action@v5.3.1
if: success() || failure()
with:
use_oidc: true
diff --git a/.github/workflows/ci_tests_dev.yaml b/.github/workflows/ci_tests_dev.yaml
index 6a026a5099f..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.5.0
+ 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..dd371d715b5 100644
--- a/.github/workflows/ci_tests_legacy.yaml
+++ b/.github/workflows/ci_tests_legacy.yaml
@@ -34,7 +34,7 @@ jobs:
strategy:
fail-fast: false
matrix:
- os: [ubuntu-20.04, macos-13, windows-2019]
+ os: [ubuntu-22.04, macos-13, windows-2019]
gmt_version: ['6.4']
timeout-minutes: 30
defaults:
@@ -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/publish-to-pypi.yml b/.github/workflows/publish-to-pypi.yml
index 2dbc12cbef1..9432143a8f0 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
- uses: pypa/gh-action-pypi-publish@v1.12.3
+ - 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.4
with:
repository-url: https://test.pypi.org/legacy/
- - name: Publish to PyPI
- if: startsWith(github.ref, 'refs/tags')
- uses: pypa/gh-action-pypi-publish@v1.12.3
+ 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.4
diff --git a/.github/workflows/release-drafter.yml b/.github/workflows/release-drafter.yml
index 118778439e1..5865c1af305 100644
--- a/.github/workflows/release-drafter.yml
+++ b/.github/workflows/release-drafter.yml
@@ -18,7 +18,7 @@ jobs:
steps:
# Drafts your next Release notes as Pull Requests are merged into "main"
- - uses: release-drafter/release-drafter@v6.0.0
+ - uses: release-drafter/release-drafter@v6.1.0
with:
# (Optional) specify config name to use, relative to .github/. Default: release-drafter.yml
config-name: release-drafter.yml
diff --git a/CITATION.cff b/CITATION.cff
index a18999e8f2d..c9cd6c58c0a 100644
--- a/CITATION.cff
+++ b/CITATION.cff
@@ -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-12-31
-doi: 10.5281/zenodo.14535921
+date-released: 2025-02-01
+doi: 10.5281/zenodo.14742338
license: BSD-3-Clause
repository-code: https://github.com/GenericMappingTools/pygmt
type: software
-version: 0.14.0
+version: 0.14.1
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 edcf9959769..13f1f023e02 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_14535921,
+ pygmt_2025_14742338,
author = {Tian, Dongdong and
Uieda, Leonardo and
Leong, Wei Ji 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 = dec,
- year = 2024,
+ month = feb,
+ year = 2025,
publisher = {Zenodo},
- version = {0.14.0},
- doi = {10.5281/zenodo.14535921},
- url = {https://doi.org/10.5281/zenodo.14535921}
+ version = {0.14.1},
+ doi = {10.5281/zenodo.14742338},
+ url = {https://doi.org/10.5281/zenodo.14742338}
}
```
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/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 5450cb9d6af..c904d9c107b 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.1': 'v0.14.1',
'v0.14.0': 'v0.14.0',
'v0.13.0': 'v0.13.0',
'v0.12.0': 'v0.12.0',
diff --git a/doc/api/index.rst b/doc/api/index.rst
index 1f573dcb8eb..25de6d44adf 100644
--- a/doc/api/index.rst
+++ b/doc/api/index.rst
@@ -335,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 21250127374..322b09fffa4 100644
--- a/doc/changes.md
+++ b/doc/changes.md
@@ -1,5 +1,31 @@
# Changelog
+## Release v0.14.1 (2025/02/01)
+
+[![Digital Object Identifier for PyGMT v0.14.1](https://zenodo.org/badge/DOI/10.5281/zenodo.14742338.svg)](https://doi.org/10.5281/zenodo.14742338)
+
+### Highlights
+
+- **Patch release fixing critical bugs in PyGMT v0.14.0**
+- Fix the bug of converting Python sequence of datetime-like objects ([#3760](https://github.com/GenericMappingTools/pygmt/pull/3760))
+
+### Maintenance
+
+- CI: Separate jobs for publishing to TestPyPI and PyPI ([#3742](https://github.com/GenericMappingTools/pygmt/pull/3742))
+- clib.conversion._to_numpy: Add tests for Python sequence of datetime-like objects ([#3758](https://github.com/GenericMappingTools/pygmt/pull/3758))
+- Fix an image in README.md (broken on PyPI) and rewrap to 88 characters ([#3740](https://github.com/GenericMappingTools/pygmt/pull/3740))
+- Fix the dataset link in the RGB image gallery example ([#3781](https://github.com/GenericMappingTools/pygmt/pull/3781))
+- Update License year to 2025 ([#3737](https://github.com/GenericMappingTools/pygmt/pull/3737))
+
+**Full Changelog**:
+
+### Contributors
+
+* [Dongdong Tian](https://github.com/seisman)
+* [Wei Ji Leong](https://github.com/weiji14)
+
+---
+
## 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)
@@ -102,6 +128,8 @@
* [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)
@@ -280,6 +308,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/minversions.md b/doc/minversions.md
index b10150b5ce1..5fb0d3d2236 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.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 |
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 ce30de4825c..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
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..af548c7c0b3 100644
--- a/examples/gallery/images/rgb_image.py
+++ b/examples/gallery/images/rgb_image.py
@@ -22,13 +22,13 @@
# %%
# Read 3-band data from GeoTIFF into an xarray.DataArray object:
with rioxarray.open_rasterio(
- filename="https://oin-hotosm.s3.us-east-1.amazonaws.com/64d6a49a19cb3a000147a65b/0/64d6a49a19cb3a000147a65c.tif",
+ filename="https://oin-hotosm-temp.s3.us-east-1.amazonaws.com/64d6a49a19cb3a000147a65b/0/64d6a49a19cb3a000147a65c.tif",
overview_level=5,
) as img:
# 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/hlines_vlines.py b/examples/gallery/lines/hlines_vlines.py
new file mode 100644
index 00000000000..ec0d73dddab
--- /dev/null
+++ b/examples/gallery/lines/hlines_vlines.py
@@ -0,0 +1,117 @@
+"""
+Horizontal and vertical lines
+=============================
+
+The :meth:`pygmt.Figure.hlines` and :meth:`pygmt.Figure.vlines` methods allow to plot
+horizontal and vertical lines in Cartesian, geographic and polar coordinate systems.
+"""
+
+# %%
+# Cartesian coordinate system
+# ---------------------------
+# In Cartesian coordinate systems lines are plotted as straight lines.
+
+import pygmt
+
+fig = pygmt.Figure()
+
+fig.basemap(
+ region=[0, 10, 0, 10], projection="X10c/10c", frame=["+tCartesian hlines", "af"]
+)
+
+# Add a horizontal line at y=9
+fig.hlines(y=9, pen="1.5p,red3", label="Line 1")
+# Add a horizontal line at y=8 with x from 2 to 8
+fig.hlines(y=8, xmin=2, xmax=8, pen="1.5p,gray30,-", label="Line 2")
+# Add two horizontal lines at y=6 and y=7 both with x from 3 to 7
+fig.hlines(y=[6, 7], xmin=3, xmax=7, pen="1.5p,salmon", label="Lines 3 & 4")
+# Add two horizontal lines at y=4 and y=5 both with x from 4 to 9
+fig.hlines(y=[4, 5], xmin=4, xmax=9, pen="1.5p,black,.", label="Lines 5 & 6")
+# Add two horizontal lines at y=2 and y=3 with different x limits
+fig.hlines(
+ y=[2, 3], xmin=[0, 1], xmax=[7, 7.5], pen="1.5p,dodgerblue3", label="Lines 7 & 8"
+)
+fig.legend(position="JBR+jBR+o0.2c", box="+gwhite+p1p")
+
+fig.shift_origin(xshift="w+2c")
+
+fig.basemap(
+ region=[0, 10, 0, 10], projection="X10c/10c", frame=["+tCartesian vlines", "af"]
+)
+# Add a vertical line at x=1
+fig.vlines(x=1, pen="1.5p,red3", label="Line 1")
+# Add a vertical line at x=2 with y from 2 to 8
+fig.vlines(x=2, ymin=2, ymax=8, pen="1.5p,gray30,-", label="Line 2")
+# Add two vertical lines at x=3 and x=4 both with y from 3 to 7
+fig.vlines(x=[3, 4], ymin=3, ymax=7, pen="1.5p,salmon", label="Lines 3 & 4")
+# Add two vertical lines at x=5 and x=6 both with y from 4 to 9
+fig.vlines(x=[5, 6], ymin=4, ymax=9, pen="1.5p,black,.", label="Lines 5 & 6")
+# Add two vertical lines at x=7 and x=8 with different y limits
+fig.vlines(
+ x=[7, 8], ymin=[0, 1], ymax=[7, 7.5], pen="1.5p,dodgerblue3", label="Lines 7 & 8"
+)
+fig.legend()
+
+fig.show()
+
+# %%
+# Geographic coordinate system
+# ----------------------------
+# The same can be done in geographic coordinate systems where "horizontal" means lines
+# are plotted along parallels (constant latitude) while "vertical" means lines are
+# plotted along meridians (constant longitude).
+
+fig = pygmt.Figure()
+
+fig.basemap(region="g", projection="R15c", frame=["+tGeographic hlines", "af"])
+# Add a line at 70°N
+fig.hlines(y=70, pen="1.5p,red3", label="Line 1")
+# Add a line at 50°N with longitude limits at 20°E and 160°E
+fig.hlines(y=50, xmin=20, xmax=160, pen="1.5p,dodgerblue3", label="Line 2")
+# Add a line at 30°S with longitude limits at 60°E and 270°E
+fig.hlines(y=-30, xmin=60, xmax=270, pen="1.5p,gray30,-", label="Line 3")
+fig.legend()
+
+fig.shift_origin(xshift="w+2c")
+
+fig.basemap(region="g", projection="R15c", frame=["+tGeographic vlines", "af"])
+# Add a line at 70°E
+fig.vlines(x=70, pen="1.5p,red3", label="Line 1")
+# Add a line at 20°E with latitude limits at 50°S and 70°N
+fig.vlines(x=120, ymin=-50, ymax=70, pen="1.5p,dodgerblue3", label="Line 2")
+# Add a line at 230°E with latitude limits at 70°S and 80°N
+fig.vlines(x=230, ymin=-70, ymax=80, pen="1.5p,gray30,-", label="Line 3")
+fig.legend()
+
+fig.show()
+
+# %%
+# Polar coordinate system
+# -----------------------
+# When using polar coordinate systems "horizontal" means lines are plotted as arcs along
+# a constant radius while "vertical" means lines are plotted as straight lines along
+# radius at a specified azimuth.
+
+fig = pygmt.Figure()
+
+fig.basemap(region=[0, 360, 0, 1], projection="P10c", frame=["+tPolar hlines", "af"])
+# Add a line along radius=0.8
+fig.hlines(y=0.8, pen="1.5p,red3", label="Line 1")
+# Add a line along radius=0.5 with azimuth limits at 30° and 160°
+fig.hlines(y=0.5, xmin=30, xmax=160, pen="1.5p,dodgerblue3", label="Line 2")
+# Add a line along radius=0.25 with azimuth limits at 60° and 270°
+fig.hlines(y=0.25, xmin=60, xmax=270, pen="1.5p,gray30,-", label="Line 3")
+fig.legend()
+
+fig.shift_origin(xshift="w+2c")
+
+fig.basemap(region=[0, 360, 0, 1], projection="P10c", frame=["+tPolar vlines", "af"])
+# Add a line along azimuth=120°
+fig.vlines(x=120, pen="1.5p,red3", label="Line 1")
+# Add a line along azimuth=190° with radius limits at 0.2 and 0.8
+fig.vlines(x=190, ymin=0.2, ymax=0.8, pen="1.5p,dodgerblue3", label="Line 2")
+# Add a line along azimuth=320 with radius limits at 0.5 and 0.9
+fig.vlines(x=320, ymin=0.5, ymax=0.9, pen="1.5p,gray30,-", label="Line 3")
+fig.legend()
+
+fig.show()
diff --git a/examples/gallery/lines/linestrings.py b/examples/gallery/lines/linestrings.py
index d3793645c84..18f94502f16 100644
--- a/examples/gallery/lines/linestrings.py
+++ b/examples/gallery/lines/linestrings.py
@@ -25,7 +25,7 @@
# Convert object to EPSG 4326 coordinate system
gdf = gdf.to_crs("EPSG:4326")
-print(gdf.head())
+gdf.head()
# %%
fig = pygmt.Figure()
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/maps/choropleth_map.py b/examples/gallery/maps/choropleth_map.py
index 19376f3c61c..f1cce8c3014 100644
--- a/examples/gallery/maps/choropleth_map.py
+++ b/examples/gallery/maps/choropleth_map.py
@@ -6,7 +6,7 @@
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`
+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
@@ -20,7 +20,7 @@
# Read the example dataset provided by geodatasets.
gdf = gpd.read_file(geodatasets.get_path("geoda airbnb"))
-print(gdf)
+print(gdf.head())
# %%
fig = pygmt.Figure()
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 c54923705dc..7823aa32103 100644
--- a/pygmt/clib/conversion.py
+++ b/pygmt/clib/conversion.py
@@ -192,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 52f710ec398..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.
@@ -594,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.
@@ -946,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.
@@ -1005,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.
@@ -1059,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.
@@ -1204,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.
@@ -1389,24 +1395,6 @@ def open_virtualfile(
msg = f"Failed to close virtual file '{vfname}'."
raise GMTCLibError(msg)
- # TODO(PyGMT>=0.15.0): Remove the deprecated open_virtual_file method.
- 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
@@ -1920,43 +1908,6 @@ def virtualfile_in(
file_context = _virtualfile_from(_data)
return file_context
- # TODO(PyGMT>=0.15.0): Remove the deprecated virtualfile_from_data method.
- 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/datasets/load_remote_dataset.py b/pygmt/datasets/load_remote_dataset.py
index b19c055425c..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={
@@ -94,6 +99,7 @@ class GMTRemoteDataset(NamedTuple):
),
"earth_dist": GMTRemoteDataset(
description="GSHHG Earth distance to shoreline",
+ kind="grid",
units="kilometers",
extra_attributes={"horizontal_datum": "WGS84"},
resolutions={
@@ -112,6 +118,7 @@ class GMTRemoteDataset(NamedTuple):
),
"earth_edefl": GMTRemoteDataset(
description="IGPP Earth east-west deflection",
+ kind="grid",
units="micro-radians",
extra_attributes={"horizontal_datum": "WGS84"},
resolutions={
@@ -130,6 +137,7 @@ class GMTRemoteDataset(NamedTuple):
),
"earth_faa": GMTRemoteDataset(
description="IGPP Earth free-air anomaly",
+ kind="grid",
units="mGal",
extra_attributes={"horizontal_datum": "WGS84"},
resolutions={
@@ -148,6 +156,7 @@ class GMTRemoteDataset(NamedTuple):
),
"earth_faaerror": GMTRemoteDataset(
description="IGPP Earth free-air anomaly errors",
+ kind="grid",
units="mGal",
extra_attributes={"horizontal_datum": "WGS84"},
resolutions={
@@ -166,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={
@@ -188,6 +198,7 @@ class GMTRemoteDataset(NamedTuple):
),
"earth_geoid": GMTRemoteDataset(
description="EGM2008 Earth geoid",
+ kind="grid",
units="meters",
extra_attributes={"horizontal_datum": "WGS84"},
resolutions={
@@ -206,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={
@@ -228,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={
@@ -245,6 +258,7 @@ class GMTRemoteDataset(NamedTuple):
),
"earth_mask": GMTRemoteDataset(
description="GSHHG Earth mask",
+ kind="grid",
units=None,
extra_attributes={"horizontal_datum": "WGS84"},
resolutions={
@@ -265,6 +279,7 @@ class GMTRemoteDataset(NamedTuple):
),
"earth_mss": GMTRemoteDataset(
description="CNES Earth mean sea surface",
+ kind="grid",
units="meters",
extra_attributes={"horizontal_datum": "WGS84"},
resolutions={
@@ -283,6 +298,7 @@ class GMTRemoteDataset(NamedTuple):
),
"earth_night": GMTRemoteDataset(
description="NASA Night Images",
+ kind="image",
units=None,
extra_attributes={"long_name": "black_marble", "horizontal_datum": "WGS84"},
resolutions={
@@ -302,6 +318,7 @@ class GMTRemoteDataset(NamedTuple):
),
"earth_mdt": GMTRemoteDataset(
description="CNES Earth mean dynamic topography",
+ kind="grid",
units="meters",
extra_attributes={"horizontal_datum": "WGS84"},
resolutions={
@@ -315,6 +332,7 @@ class GMTRemoteDataset(NamedTuple):
),
"earth_ndefl": GMTRemoteDataset(
description="IGPP Earth north-south deflection",
+ kind="grid",
units="micro-radians",
extra_attributes={"horizontal_datum": "WGS84"},
resolutions={
@@ -333,6 +351,7 @@ class GMTRemoteDataset(NamedTuple):
),
"earth_vgg": GMTRemoteDataset(
description="IGPP Earth vertical gravity gradient",
+ kind="grid",
units="Eotvos",
extra_attributes={"horizontal_datum": "WGS84"},
resolutions={
@@ -351,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={
@@ -367,6 +387,7 @@ class GMTRemoteDataset(NamedTuple):
),
"mars_relief": GMTRemoteDataset(
description="NASA Mars (MOLA) relief",
+ kind="grid",
units="meters",
extra_attributes={},
resolutions={
@@ -388,6 +409,7 @@ class GMTRemoteDataset(NamedTuple):
),
"moon_relief": GMTRemoteDataset(
description="USGS Moon (LOLA) relief",
+ kind="grid",
units="meters",
extra_attributes={},
resolutions={
@@ -409,6 +431,7 @@ class GMTRemoteDataset(NamedTuple):
),
"mercury_relief": GMTRemoteDataset(
description="USGS Mercury relief",
+ kind="grid",
units="meters",
extra_attributes={},
resolutions={
@@ -428,6 +451,7 @@ class GMTRemoteDataset(NamedTuple):
),
"pluto_relief": GMTRemoteDataset(
description="USGS Pluto relief",
+ kind="grid",
units="meters",
extra_attributes={},
resolutions={
@@ -447,6 +471,7 @@ class GMTRemoteDataset(NamedTuple):
),
"venus_relief": GMTRemoteDataset(
description="NASA Magellan Venus relief",
+ kind="grid",
units="meters",
extra_attributes={},
resolutions={
@@ -545,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/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 374eb1d8fee..5c5d4734ce6 100644
--- a/pygmt/figure.py
+++ b/pygmt/figure.py
@@ -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.
@@ -268,7 +268,7 @@ def show(
width: int = 500,
waiting: float = 0.5,
**kwargs,
- ):
+ ) -> None:
"""
Display a preview of the figure.
@@ -442,7 +442,7 @@ def _repr_html_(self) -> str:
)
-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 19d3eae0559..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.
"""
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 6d8dfd74fec..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.
"""
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/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
index b277358d981..e2c390b4eea 100644
--- a/pygmt/src/hlines.py
+++ b/pygmt/src/hlines.py
@@ -38,9 +38,10 @@ def hlines(
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.
+ - **Cartesian**: lines are plotted as straight lines.
+ - **Polar**: lines are plotted as arcs along a constant radius.
+ - **Geographic**: lines are plotted as arcs along parallels (i.e., constant
+ latitude).
Parameters
----------
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/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/vlines.py b/pygmt/src/vlines.py
index 2483df99f27..7f1919baa9c 100644
--- a/pygmt/src/vlines.py
+++ b/pygmt/src/vlines.py
@@ -38,10 +38,10 @@ def vlines(
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.
+ - **Cartesian**: lines are plotted as straight lines.
+ - **Polar**: lines are plotted as straight lines along a constant azimuth.
+ - **Geographic**: lines are plotted as arcs along meridians (i.e., constant
+ longitude).
Parameters
----------
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_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/test_clib_to_numpy.py b/pygmt/tests/test_clib_to_numpy.py
index c3bc413a0b1..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
@@ -80,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.
#
@@ -153,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.
#
@@ -335,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.
#
@@ -466,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)
@@ -512,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_in.py b/pygmt/tests/test_clib_virtualfile_in.py
index 7b091fc423c..8a43c1dc273 100644
--- a/pygmt/tests/test_clib_virtualfile_in.py
+++ b/pygmt/tests/test_clib_virtualfile_in.py
@@ -128,35 +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.
-
-
-# TODO(PyGMT>=0.16.0): Remove this test in PyGMT v0.16.0 in which the old usage of
-# virtualfile_from_data is removed.
-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 b25891864f6..ba48200deae 100644
--- a/pygmt/tests/test_clib_virtualfiles.py
+++ b/pygmt/tests/test_clib_virtualfiles.py
@@ -107,35 +107,3 @@ def test_open_virtualfile_bad_direction():
with pytest.raises(GMTInvalidInput):
with lib.open_virtualfile(*vfargs):
pass
-
-
-# TODO(PyGMT>=0.15.0): Remove the test after removing the deprecated open_virtual_file
-# method.
-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_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_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_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/pyproject.toml b/pyproject.toml
index 2150af08d7b..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"]
@@ -159,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