From 44b7af3d3ba005ddd2bdebd1edbfea674621e23f Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Wed, 25 Dec 2024 07:14:03 +0800 Subject: [PATCH 01/55] Build(deps): Bump actions/create-github-app-token from 1.11.0 to 1.11.1 (#3713) Bumps [actions/create-github-app-token](https://github.com/actions/create-github-app-token) from 1.11.0 to 1.11.1. - [Release notes](https://github.com/actions/create-github-app-token/releases) - [Commits](https://github.com/actions/create-github-app-token/compare/v1.11.0...v1.11.1) --- updated-dependencies: - dependency-name: actions/create-github-app-token dependency-type: direct:production update-type: version-update:semver-patch ... Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- .github/workflows/format-command.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/format-command.yml b/.github/workflows/format-command.yml index ceb2604ba2b..3ca7d72d71f 100644 --- a/.github/workflows/format-command.yml +++ b/.github/workflows/format-command.yml @@ -11,7 +11,7 @@ jobs: runs-on: ubuntu-latest steps: # Generate token from GenericMappingTools bot - - uses: actions/create-github-app-token@v1.11.0 + - uses: actions/create-github-app-token@v1.11.1 id: generate-token with: app-id: ${{ secrets.APP_ID }} From 9b5882c4581f43b1326d47bbefef9a7e2039d8e0 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Wed, 25 Dec 2024 07:15:08 +0800 Subject: [PATCH 02/55] Build(deps): Bump codecov/codecov-action from 5.1.1 to 5.1.2 (#3712) Bumps [codecov/codecov-action](https://github.com/codecov/codecov-action) from 5.1.1 to 5.1.2. - [Release notes](https://github.com/codecov/codecov-action/releases) - [Changelog](https://github.com/codecov/codecov-action/blob/main/CHANGELOG.md) - [Commits](https://github.com/codecov/codecov-action/compare/v5.1.1...v5.1.2) --- updated-dependencies: - dependency-name: codecov/codecov-action dependency-type: direct:production update-type: version-update:semver-patch ... Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- .github/workflows/ci_tests.yaml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/ci_tests.yaml b/.github/workflows/ci_tests.yaml index 8ed7e728c02..c1012487d22 100644 --- a/.github/workflows/ci_tests.yaml +++ b/.github/workflows/ci_tests.yaml @@ -187,7 +187,7 @@ jobs: # Upload coverage to Codecov - name: Upload coverage to Codecov - uses: codecov/codecov-action@v5.1.1 + uses: codecov/codecov-action@v5.1.2 if: success() || failure() with: use_oidc: true From b90fa469a2fd6cd1ec25e4ed99499eb3c48589e8 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Yvonne=20Fr=C3=B6hlich?= <94163266+yvonnefroehlich@users.noreply.github.com> Date: Wed, 25 Dec 2024 12:20:06 +0100 Subject: [PATCH 03/55] External Resources / AGU24 workshop: Link directly to Jupter book instead of GitHub repo (#3716) Link directly to Jupter book instead of GitHub repo --- doc/external_resources.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/doc/external_resources.md b/doc/external_resources.md index b0e8ad10e34..30c04764e50 100644 --- a/doc/external_resources.md +++ b/doc/external_resources.md @@ -13,7 +13,7 @@ to submit a pull request with your recommended addition to the :::::{grid} 1 2 2 3 ::::{grid-item-card} 2024 AGU PREWS9: Mastering Geospatial Visualizations with GMT/PyGMT -:link: https://github.com/GenericMappingTools/agu24workshop +:link: https://www.generic-mapping-tools.org/agu24workshop/ :text-align: center :margin: 0 3 0 0 From b80b44ef9be5e4a26c5197d16e33c169aacf3c90 Mon Sep 17 00:00:00 2001 From: Dongdong Tian Date: Wed, 25 Dec 2024 22:50:28 +0800 Subject: [PATCH 04/55] Figure: Add type hints to the _preview method (#3715) --- pygmt/figure.py | 14 +++++++++++--- 1 file changed, 11 insertions(+), 3 deletions(-) diff --git a/pygmt/figure.py b/pygmt/figure.py index 4163ab52eb1..032b9f37190 100644 --- a/pygmt/figure.py +++ b/pygmt/figure.py @@ -6,7 +6,7 @@ import os from pathlib import Path, PurePath from tempfile import TemporaryDirectory -from typing import Literal +from typing import Literal, overload try: import IPython @@ -353,6 +353,14 @@ def show( ) raise GMTInvalidInput(msg) + @overload + def _preview( + self, fmt: str, dpi: int, as_bytes: Literal[True] = True, **kwargs + ) -> bytes: ... + @overload + def _preview( + self, fmt: str, dpi: int, as_bytes: Literal[False] = False, **kwargs + ) -> str: ... def _preview(self, fmt: str, dpi: int, as_bytes: bool = False, **kwargs): """ Grab a preview of the figure. @@ -380,7 +388,7 @@ def _preview(self, fmt: str, dpi: int, as_bytes: bool = False, **kwargs): return fname.read_bytes() return fname - def _repr_png_(self): + def _repr_png_(self) -> bytes: """ Show a PNG preview if the object is returned in an interactive shell. @@ -389,7 +397,7 @@ def _repr_png_(self): png = self._preview(fmt="png", dpi=70, anti_alias=True, as_bytes=True) return png - def _repr_html_(self): + def _repr_html_(self) -> str: """ Show the PNG image embedded in HTML with a controlled width. From 359e457ba80e84063dc1713cbf158d44ea45882f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Yvonne=20Fr=C3=B6hlich?= <94163266+yvonnefroehlich@users.noreply.github.com> Date: Thu, 26 Dec 2024 01:41:30 +0100 Subject: [PATCH 05/55] Remote datasets: Add note for exact resolution (#3721) * Add note for '12s' resolution of Mars relief dataset * Add note for '56s' resolution of Mercury relief dataset * Add note for '14s' resolution of Moon relief dataset * Add note for '52s' resolution of Pluto relief dataset --- pygmt/datasets/mars_relief.py | 3 ++- pygmt/datasets/mercury_relief.py | 3 ++- pygmt/datasets/moon_relief.py | 3 ++- pygmt/datasets/pluto_relief.py | 3 ++- 4 files changed, 8 insertions(+), 4 deletions(-) diff --git a/pygmt/datasets/mars_relief.py b/pygmt/datasets/mars_relief.py index 1d2cb631fd9..edfd8535210 100644 --- a/pygmt/datasets/mars_relief.py +++ b/pygmt/datasets/mars_relief.py @@ -67,7 +67,8 @@ def load_mars_relief( ---------- resolution The grid resolution. The suffix ``d``, ``m`` and ``s`` stand for arc-degrees, - arc-minutes and arc-seconds. + arc-minutes and arc-seconds. Note that ``"12s"`` refers to a resolution of + 12.1468873601 arc-seconds. region The subregion of the grid to load, in the form of a sequence [*xmin*, *xmax*, *ymin*, *ymax*] or an ISO country code. Required for grids with resolutions diff --git a/pygmt/datasets/mercury_relief.py b/pygmt/datasets/mercury_relief.py index f3c360c356a..398ae65dd4d 100644 --- a/pygmt/datasets/mercury_relief.py +++ b/pygmt/datasets/mercury_relief.py @@ -65,7 +65,8 @@ def load_mercury_relief( ---------- resolution The grid resolution. The suffix ``d``, ``m`` and ``s`` stand for arc-degrees, - arc-minutes and arc-seconds. + arc-minutes and arc-seconds. Note that ``"56s"`` refers to a resolution of + 56.25 arc-seconds. region The subregion of the grid to load, in the form of a sequence [*xmin*, *xmax*, *ymin*, *ymax*] or an ISO country code. Required for grids with resolutions diff --git a/pygmt/datasets/moon_relief.py b/pygmt/datasets/moon_relief.py index 9daab0f47a5..add30b0f051 100644 --- a/pygmt/datasets/moon_relief.py +++ b/pygmt/datasets/moon_relief.py @@ -67,7 +67,8 @@ def load_moon_relief( ---------- resolution The grid resolution. The suffix ``d``, ``m`` and ``s`` stand for arc-degrees, - arc-minutes and arc-seconds. + arc-minutes and arc-seconds. Note that ``"14s"`` refers to a resolution of + 14.0625 arc-seconds. region The subregion of the grid to load, in the form of a sequence [*xmin*, *xmax*, *ymin*, *ymax*] or an ISO country code. Required for grids with resolutions diff --git a/pygmt/datasets/pluto_relief.py b/pygmt/datasets/pluto_relief.py index 620545899da..224e89beb10 100644 --- a/pygmt/datasets/pluto_relief.py +++ b/pygmt/datasets/pluto_relief.py @@ -65,7 +65,8 @@ def load_pluto_relief( ---------- resolution The grid resolution. The suffix ``d``, ``m`` and ``s`` stand for arc-degrees, - arc-minutes and arc-seconds. + arc-minutes and arc-seconds. Note that ``"52s"`` refers to a resolution of + 52.0732883317 arc-seconds. region The subregion of the grid to load, in the form of a sequence [*xmin*, *xmax*, *ymin*, *ymax*] or an ISO country code. Required for grids with resolutions From 33a4bac2ec2a01e11f7d04e98b058173f9884121 Mon Sep 17 00:00:00 2001 From: Dongdong Tian Date: Thu, 26 Dec 2024 10:49:07 +0800 Subject: [PATCH 06/55] CI: Ensure no hyphens in Python file and directory names in the "Style Checks" workflow (#3703) --- .github/PULL_REQUEST_TEMPLATE.md | 1 - .github/workflows/style_checks.yaml | 12 ++++++++++++ doc/contributing.md | 6 +++--- 3 files changed, 15 insertions(+), 4 deletions(-) diff --git a/.github/PULL_REQUEST_TEMPLATE.md b/.github/PULL_REQUEST_TEMPLATE.md index 14b75d86f5a..0c259cf5672 100644 --- a/.github/PULL_REQUEST_TEMPLATE.md +++ b/.github/PULL_REQUEST_TEMPLATE.md @@ -20,7 +20,6 @@ Fixes # - [ ] Write detailed docstrings for all functions/methods. - [ ] If wrapping a new module, open a 'Wrap new GMT module' issue and submit reasonably-sized PRs. - [ ] If adding new functionality, add an example to docstrings or tutorials. -- [ ] Use underscores (not hyphens) in names of Python files and directories. **Slash Commands** diff --git a/.github/workflows/style_checks.yaml b/.github/workflows/style_checks.yaml index f15bcde0012..43fa5acd6c7 100644 --- a/.github/workflows/style_checks.yaml +++ b/.github/workflows/style_checks.yaml @@ -52,3 +52,15 @@ jobs: rm output.txt exit $nfiles fi + + - name: Ensure hyphens are not used in names of directories and Python files + run: | + git ls-files '*.py' | grep '-' > output.txt || true + git ls-tree -rd --name-only HEAD | grep '-' >> output.txt || true + nfiles=$(wc --lines output.txt | awk '{print $1}') + if [[ $nfiles > 0 ]]; then + echo "Following directories/files use hyphens in file names:" + cat output.txt + rm output.txt + exit $nfiles + fi diff --git a/doc/contributing.md b/doc/contributing.md index 5dd93418f53..36b0d76e984 100644 --- a/doc/contributing.md +++ b/doc/contributing.md @@ -130,9 +130,9 @@ our tests. This way, the *main* branch is always stable. integrated separately. - Bug fixes should be submitted in separate PRs. * How to write and submit a PR - - Use underscores for all Python (*.py) files as per - [PEP8](https://www.python.org/dev/peps/pep-0008/), not hyphens. Directory - names should also use underscores instead of hyphens. + - Use underscores for all Python (\*.py) files as per + [PEP8](https://www.python.org/dev/peps/pep-0008/), not hyphens. Directory names + should also use underscores instead of hyphens. - Describe what your PR changes and *why* this is a good thing. Be as specific as you can. The PR description is how we keep track of the changes made to the project over time. From ebe9df140ceee4566fe1e8cc3ce9eecc4322c7c6 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Yvonne=20Fr=C3=B6hlich?= <94163266+yvonnefroehlich@users.noreply.github.com> Date: Thu, 26 Dec 2024 03:51:05 +0100 Subject: [PATCH 07/55] Remote datasets: Add "load_earth_dist" to load "GSHHG Earth distance to shoreline" dataset (#3706) Co-authored-by: Dongdong Tian --- doc/api/index.rst | 1 + pygmt/datasets/__init__.py | 1 + pygmt/datasets/earth_dist.py | 105 ++++++++++++++++++++++++ pygmt/datasets/load_remote_dataset.py | 18 ++++ pygmt/helpers/caching.py | 2 + pygmt/tests/test_datasets_earth_dist.py | 53 ++++++++++++ 6 files changed, 180 insertions(+) create mode 100644 pygmt/datasets/earth_dist.py create mode 100644 pygmt/tests/test_datasets_earth_dist.py diff --git a/doc/api/index.rst b/doc/api/index.rst index 07f76aff217..6c7025fd94c 100644 --- a/doc/api/index.rst +++ b/doc/api/index.rst @@ -233,6 +233,7 @@ and store them in GMT's user data directory. datasets.load_black_marble datasets.load_blue_marble datasets.load_earth_age + datasets.load_earth_dist datasets.load_earth_free_air_anomaly datasets.load_earth_geoid datasets.load_earth_magnetic_anomaly diff --git a/pygmt/datasets/__init__.py b/pygmt/datasets/__init__.py index d70eec5a1de..c557f03c466 100644 --- a/pygmt/datasets/__init__.py +++ b/pygmt/datasets/__init__.py @@ -6,6 +6,7 @@ from pygmt.datasets.earth_age import load_earth_age from pygmt.datasets.earth_day import load_blue_marble +from pygmt.datasets.earth_dist import load_earth_dist from pygmt.datasets.earth_free_air_anomaly import load_earth_free_air_anomaly from pygmt.datasets.earth_geoid import load_earth_geoid from pygmt.datasets.earth_magnetic_anomaly import load_earth_magnetic_anomaly diff --git a/pygmt/datasets/earth_dist.py b/pygmt/datasets/earth_dist.py new file mode 100644 index 00000000000..4897c475b43 --- /dev/null +++ b/pygmt/datasets/earth_dist.py @@ -0,0 +1,105 @@ +""" +Function to download the GSHHG Earth distance to shoreline dataset from the GMT data +server, and load as :class:`xarray.DataArray`. + +The grids are available in various resolutions. +""" + +from collections.abc import Sequence +from typing import Literal + +import xarray as xr +from pygmt.datasets.load_remote_dataset import _load_remote_dataset + +__doctest_skip__ = ["load_earth_dist"] + + +def load_earth_dist( + resolution: Literal[ + "01d", "30m", "20m", "15m", "10m", "06m", "05m", "04m", "03m", "02m", "01m" + ] = "01d", + region: Sequence[float] | str | None = None, + registration: Literal["gridline", "pixel"] = "gridline", +) -> xr.DataArray: + r""" + Load the GSHHG Earth distance to shoreline dataset in various resolutions. + + .. figure:: https://www.generic-mapping-tools.org/remote-datasets/_images/GMT_earth_dist.jpg + :width: 80 % + :align: center + + GSHHG Earth distance to shoreline dataset. + + The grids are downloaded to a user data directory (usually + ``~/.gmt/server/earth/earth_dist/``) the first time you invoke this function. + Afterwards, it will load the grid from the data directory. So you'll need an + internet connection the first time around. + + These grids can also be accessed by passing in the file name + **@earth_dist**\_\ *res*\[_\ *reg*] to any grid processing function or plotting + method. *res* is the grid resolution (see below), and *reg* is the grid registration + type (**p** for pixel registration or **g** for gridline registration). + + The default color palette table (CPT) for this dataset is *@earth_dist.cpt*. It's + implicitly used when passing in the file name of the dataset to any grid plotting + method if no CPT is explicitly specified. When the dataset is loaded and plotted + as an :class:`xarray.DataArray` object, the default CPT is ignored, and GMT's + default CPT (*turbo*) is used. To use the dataset-specific CPT, you need to + explicitly set ``cmap="@earth_dist.cpt"``. + + Refer to :gmt-datasets:`earth-dist.html` for more details about available datasets, + including version information and references. + + Parameters + ---------- + resolution + The grid resolution. The suffix ``d`` and ``m`` stand for arc-degrees and + arc-minutes. + region + The subregion of the grid to load, in the form of a sequence [*xmin*, *xmax*, + *ymin*, *ymax*] or an ISO country code. Required for grids with resolutions + higher than 5 arc-minutes (i.e., ``"05m"``). + registration + Grid registration type. Either ``"pixel"`` for pixel registration or + ``"gridline"`` for gridline registration. + + Returns + ------- + grid + The GSHHG Earth distance to shoreline grid. Coordinates are latitude and + longitude in degrees. Distances are in kilometers, where positive (negative) + values mean land to coastline (ocean to coastline). + + Note + ---- + The registration and coordinate system type of the returned + :class:`xarray.DataArray` grid can be accessed via the GMT accessors (i.e., + ``grid.gmt.registration`` and ``grid.gmt.gtype`` respectively). However, these + properties may be lost after specific grid operations (such as slicing) and will + need to be manually set before passing the grid to any PyGMT data processing or + plotting functions. Refer to :class:`pygmt.GMTDataArrayAccessor` for detailed + explanations and workarounds. + + Examples + -------- + + >>> from pygmt.datasets import load_earth_dist + >>> # load the default grid (gridline-registered 1 arc-degree grid) + >>> grid = load_earth_dist() + >>> # load the 30 arc-minutes grid with "gridline" registration + >>> grid = load_earth_dist(resolution="30m", registration="gridline") + >>> # load high-resolution (5 arc-minutes) grid for a specific region + >>> grid = load_earth_dist( + ... resolution="05m", + ... region=[120, 160, 30, 60], + ... registration="gridline", + ... ) + """ + grid = _load_remote_dataset( + name="earth_dist", + prefix="earth_dist", + resolution=resolution, + region=region, + registration=registration, + ) + return grid diff --git a/pygmt/datasets/load_remote_dataset.py b/pygmt/datasets/load_remote_dataset.py index 168a93583b2..717396c145c 100644 --- a/pygmt/datasets/load_remote_dataset.py +++ b/pygmt/datasets/load_remote_dataset.py @@ -92,6 +92,24 @@ class GMTRemoteDataset(NamedTuple): "30s": Resolution("30s", registrations=["pixel"]), }, ), + "earth_dist": GMTRemoteDataset( + description="GSHHG Earth distance to shoreline", + units="km", + extra_attributes={"horizontal_datum": "WGS84"}, + resolutions={ + "01d": Resolution("01d"), + "30m": Resolution("30m"), + "20m": Resolution("20m"), + "15m": Resolution("15m"), + "10m": Resolution("10m"), + "06m": Resolution("06m"), + "05m": Resolution("05m", tiled=True), + "04m": Resolution("04m", tiled=True), + "03m": Resolution("03m", tiled=True), + "02m": Resolution("02m", tiled=True), + "01m": Resolution("01m", registrations=["gridline"], tiled=True), + }, + ), "earth_faa": GMTRemoteDataset( description="IGPP Earth free-air anomaly", units="mGal", diff --git a/pygmt/helpers/caching.py b/pygmt/helpers/caching.py index 26648b17060..183bab97161 100644 --- a/pygmt/helpers/caching.py +++ b/pygmt/helpers/caching.py @@ -14,6 +14,7 @@ def cache_data(): # List of GMT remote datasets. "@earth_age_01d_g", "@earth_day_01d", + "@earth_dist_01d", "@earth_faa_01d_g", "@earth_gebco_01d_g", "@earth_gebcosi_01d_g", @@ -45,6 +46,7 @@ def cache_data(): "@N00W030.earth_age_01m_g.nc", "@N30E060.earth_age_01m_g.nc", "@N30E090.earth_age_01m_g.nc", + "@N00W030.earth_dist_01m_g.nc", "@N00W030.earth_faa_01m_p.nc", "@N00W030.earth_geoid_01m_g.nc", "@S30W060.earth_mag_02m_p.nc", diff --git a/pygmt/tests/test_datasets_earth_dist.py b/pygmt/tests/test_datasets_earth_dist.py new file mode 100644 index 00000000000..e0d91d77a84 --- /dev/null +++ b/pygmt/tests/test_datasets_earth_dist.py @@ -0,0 +1,53 @@ +""" +Test basic functionality for loading Earth distance to shoreline datasets. +""" + +import numpy as np +import numpy.testing as npt +from pygmt.datasets import load_earth_dist + + +def test_earth_dist_01d(): + """ + Test some properties of the Earth distance to shoreline 01d data. + """ + data = load_earth_dist(resolution="01d") + assert data.name == "z" + assert data.attrs["description"] == "GSHHG Earth distance to shoreline" + assert data.attrs["units"] == "km" + assert data.attrs["horizontal_datum"] == "WGS84" + assert data.shape == (181, 361) + assert data.gmt.registration == 0 + npt.assert_allclose(data.lat, np.arange(-90, 91, 1)) + npt.assert_allclose(data.lon, np.arange(-180, 181, 1)) + npt.assert_allclose(data.min(), -2655.7, atol=0.01) + npt.assert_allclose(data.max(), 2463.42, atol=0.01) + + +def test_earth_dist_01d_with_region(): + """ + Test loading low-resolution Earth distance to shoreline with "region". + """ + data = load_earth_dist(resolution="01d", region=[-10, 10, -5, 5]) + assert data.shape == (11, 21) + assert data.gmt.registration == 0 + npt.assert_allclose(data.lat, np.arange(-5, 6, 1)) + npt.assert_allclose(data.lon, np.arange(-10, 11, 1)) + npt.assert_allclose(data.min(), -1081.94, atol=0.01) + npt.assert_allclose(data.max(), 105.18, atol=0.01) + + +def test_earth_dist_01m_default_registration(): + """ + Test that the grid returned by default for the 1 arc-minute resolution has a + "gridline" registration. + """ + data = load_earth_dist(resolution="01m", region=[-10, -9, 3, 5]) + assert data.shape == (121, 61) + assert data.gmt.registration == 0 + assert data.coords["lat"].data.min() == 3.0 + assert data.coords["lat"].data.max() == 5.0 + assert data.coords["lon"].data.min() == -10.0 + assert data.coords["lon"].data.max() == -9.0 + npt.assert_allclose(data.min(), -243.62, atol=0.01) + npt.assert_allclose(data.max(), 2.94, atol=0.01) From 48aeafaba80da856670bc6623b04e33677255221 Mon Sep 17 00:00:00 2001 From: Dongdong Tian Date: Thu, 26 Dec 2024 15:22:31 +0800 Subject: [PATCH 08/55] Add notes for issue templates that are only meant for project maintainers (#3714) --- .github/ISSUE_TEMPLATE/4-release_checklist.md | 2 +- .github/ISSUE_TEMPLATE/5-bump_gmt_checklist.md | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/.github/ISSUE_TEMPLATE/4-release_checklist.md b/.github/ISSUE_TEMPLATE/4-release_checklist.md index 59919b8fb25..7d46be2538b 100644 --- a/.github/ISSUE_TEMPLATE/4-release_checklist.md +++ b/.github/ISSUE_TEMPLATE/4-release_checklist.md @@ -1,6 +1,6 @@ --- name: PyGMT release checklist -about: Checklist for a new PyGMT release. +about: Checklist for a new PyGMT release. [For project maintainers only!] title: Release PyGMT vX.Y.Z labels: maintenance assignees: '' diff --git a/.github/ISSUE_TEMPLATE/5-bump_gmt_checklist.md b/.github/ISSUE_TEMPLATE/5-bump_gmt_checklist.md index 9652f2150ee..1ca5117ff5f 100644 --- a/.github/ISSUE_TEMPLATE/5-bump_gmt_checklist.md +++ b/.github/ISSUE_TEMPLATE/5-bump_gmt_checklist.md @@ -1,6 +1,6 @@ --- name: Bump GMT version checklist -about: Checklist for bumping the minimum required GMT version. +about: Checklist for bumping the minimum required GMT version. [For project maintainers only!] title: Bump to GMT X.Y.Z labels: maintenance assignees: '' From 41702d71eca46ebccb128217542b91f550661c54 Mon Sep 17 00:00:00 2001 From: Dongdong Tian Date: Thu, 26 Dec 2024 19:21:10 +0800 Subject: [PATCH 09/55] Add 'geodatasets' as a dependency for docs and update the choropleth example (#3719) --- .github/workflows/ci_docs.yml | 1 + ci/requirements/docs.yml | 1 + environment.yml | 1 + examples/gallery/maps/choropleth_map.py | 39 +++++++++++++------------ 4 files changed, 23 insertions(+), 19 deletions(-) diff --git a/.github/workflows/ci_docs.yml b/.github/workflows/ci_docs.yml index 749d5b42b00..a74d4fbfcd5 100644 --- a/.github/workflows/ci_docs.yml +++ b/.github/workflows/ci_docs.yml @@ -108,6 +108,7 @@ jobs: make pip python-build + geodatasets myst-nb panel sphinx>=6.2 diff --git a/ci/requirements/docs.yml b/ci/requirements/docs.yml index 84be526f47f..2a6fa860961 100644 --- a/ci/requirements/docs.yml +++ b/ci/requirements/docs.yml @@ -23,6 +23,7 @@ dependencies: - pip - python-build # Dev dependencies (building documentation) + - geodatasets - myst-nb - panel - sphinx>=6.2 diff --git a/environment.yml b/environment.yml index 620b1bad91b..ce30de4825c 100644 --- a/environment.yml +++ b/environment.yml @@ -35,6 +35,7 @@ dependencies: - pytest-doctestplus - pytest-mpl # Dev dependencies (building documentation) + - geodatasets - myst-nb - panel - sphinx>=6.2 diff --git a/examples/gallery/maps/choropleth_map.py b/examples/gallery/maps/choropleth_map.py index 6c43d24d3dd..19376f3c61c 100644 --- a/examples/gallery/maps/choropleth_map.py +++ b/examples/gallery/maps/choropleth_map.py @@ -2,25 +2,27 @@ Choropleth map ============== -The :meth:`pygmt.Figure.plot` method allows us to plot geographical data such -as polygons which are stored in a :class:`geopandas.GeoDataFrame` object. Use -:func:`geopandas.read_file` to load data from any supported OGR format such as -a shapefile (.shp), GeoJSON (.geojson), geopackage (.gpkg), etc. You can also -use a full URL pointing to your desired data source. Then, pass the -:class:`geopandas.GeoDataFrame` as an argument to the ``data`` parameter of -:meth:`pygmt.Figure.plot`, and style the geometry using the ``pen`` parameter. -To fill the polygons based on a corresponding column you need to set -``fill="+z"`` as well as select the appropriate column using the ``aspatial`` -parameter as shown in the example below. +The :meth:`pygmt.Figure.plot` method allows us to plot geographical data such as +polygons which are stored in a :class:`geopandas.GeoDataFrame` object. Use +:func:`geopandas.read_file` to load data from any supported OGR format such as a +shapefile (.shp), GeoJSON (.geojson), geopackage (.gpkg), etc. You can also use a full +URL pointing to your desired data source. Then, pass the class:`geopandas.GeoDataFrame` +as an argument to the ``data`` parameter of :meth:`pygmt.Figure.plot`, and style the +geometry using the ``pen`` parameter. To fill the polygons based on a corresponding +column you need to set ``fill="+z"`` as well as select the appropriate column using the +``aspatial`` parameter as shown in the example below. """ # %% +import geodatasets import geopandas as gpd import pygmt -# Read polygon data using geopandas -gdf = gpd.read_file("https://geodacenter.github.io/data-and-lab/data/airbnb.zip") +# Read the example dataset provided by geodatasets. +gdf = gpd.read_file(geodatasets.get_path("geoda airbnb")) +print(gdf) +# %% fig = pygmt.Figure() fig.basemap( @@ -29,11 +31,10 @@ frame="+tPopulation of Chicago", ) -# The dataset contains different attributes, here we select -# the "population" column to plot. +# The dataset contains different attributes, here we select the "population" column to +# plot. -# First, we define the colormap to fill the polygons based on -# the "population" column. +# First, we define the colormap to fill the polygons based on the "population" column. pygmt.makecpt( cmap="acton", series=[gdf["population"].min(), gdf["population"].max(), 10], @@ -41,8 +42,8 @@ reverse=True, ) -# Next, we plot the polygons and fill them using the defined colormap. -# The target column is defined by the aspatial parameter. +# Next, we plot the polygons and fill them using the defined colormap. The target column +# is defined by the aspatial parameter. fig.plot( data=gdf, pen="0.3p,gray10", @@ -51,7 +52,7 @@ aspatial="Z=population", ) -# Add colorbar legend +# Add colorbar legend. fig.colorbar(frame="x+lPopulation", position="jML+o-0.5c+w3.5c/0.2c") fig.show() From 874b6a421e7cd383377a0cd4a33d76b66dba9e46 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Yvonne=20Fr=C3=B6hlich?= <94163266+yvonnefroehlich@users.noreply.github.com> Date: Thu, 26 Dec 2024 12:33:21 +0100 Subject: [PATCH 10/55] Remote datasets: Add "load_earth_mean_dynamic_topography" to load "CNES Earth Mean Dynamic Topography" dataset (#3718) Co-authored-by: Dongdong Tian --- doc/api/index.rst | 1 + pygmt/datasets/__init__.py | 3 + .../datasets/earth_mean_dynamic_topography.py | 103 ++++++++++++++++++ pygmt/datasets/load_remote_dataset.py | 13 +++ pygmt/helpers/caching.py | 2 + .../test_datasets_mean_dynamic_topography.py | 53 +++++++++ 6 files changed, 175 insertions(+) create mode 100644 pygmt/datasets/earth_mean_dynamic_topography.py create mode 100644 pygmt/tests/test_datasets_mean_dynamic_topography.py diff --git a/doc/api/index.rst b/doc/api/index.rst index 6c7025fd94c..6f6d87b8b7f 100644 --- a/doc/api/index.rst +++ b/doc/api/index.rst @@ -238,6 +238,7 @@ and store them in GMT's user data directory. datasets.load_earth_geoid datasets.load_earth_magnetic_anomaly datasets.load_earth_mask + datasets.load_earth_mean_dynamic_topography datasets.load_earth_relief datasets.load_earth_vertical_gravity_gradient datasets.load_mars_relief diff --git a/pygmt/datasets/__init__.py b/pygmt/datasets/__init__.py index c557f03c466..f60739a6e1b 100644 --- a/pygmt/datasets/__init__.py +++ b/pygmt/datasets/__init__.py @@ -11,6 +11,9 @@ from pygmt.datasets.earth_geoid import load_earth_geoid from pygmt.datasets.earth_magnetic_anomaly import load_earth_magnetic_anomaly from pygmt.datasets.earth_mask import load_earth_mask +from pygmt.datasets.earth_mean_dynamic_topography import ( + load_earth_mean_dynamic_topography, +) from pygmt.datasets.earth_night import load_black_marble from pygmt.datasets.earth_relief import load_earth_relief from pygmt.datasets.earth_vertical_gravity_gradient import ( diff --git a/pygmt/datasets/earth_mean_dynamic_topography.py b/pygmt/datasets/earth_mean_dynamic_topography.py new file mode 100644 index 00000000000..17e9ad860a0 --- /dev/null +++ b/pygmt/datasets/earth_mean_dynamic_topography.py @@ -0,0 +1,103 @@ +""" +Function to download the CNES Earth mean dynamic topography dataset from the GMT data +server, and load as :class:`xarray.DataArray`. + +The grids are available in various resolutions. +""" + +from collections.abc import Sequence +from typing import Literal + +import xarray as xr +from pygmt.datasets.load_remote_dataset import _load_remote_dataset + +__doctest_skip__ = ["load_earth_mean_dynamic_topography"] + + +def load_earth_mean_dynamic_topography( + resolution: Literal["01d", "30m", "20m", "15m", "10m", "07m"] = "01d", + region: Sequence[float] | str | None = None, + registration: Literal["gridline", "pixel"] = "gridline", +) -> xr.DataArray: + r""" + Load the CNES Earth mean dynamic topography dataset in various resolutions. + + .. figure:: https://www.generic-mapping-tools.org/remote-datasets/_images/GMT_earth_mdt.jpg + :width: 80 % + :align: center + + CNES Earth mean dynamic topography dataset. + + The grids are downloaded to a user data directory (usually + ``~/.gmt/server/earth/earth_mdt/``) the first time you invoke this function. + Afterwards, it will load the grid from the data directory. So you'll need an + internet connection the first time around. + + These grids can also be accessed by passing in the file name + **@earth_mdt**\_\ *res*\[_\ *reg*] to any grid processing function or plotting + method. *res* is the grid resolution (see below), and *reg* is the grid registration + type (**p** for pixel registration or **g** for gridline registration). + + The default color palette table (CPT) for this dataset is *@earth_mdt.cpt*. It's + implicitly used when passing in the file name of the dataset to any grid plotting + method if no CPT is explicitly specified. When the dataset is loaded and plotted + as an :class:`xarray.DataArray` object, the default CPT is ignored, and GMT's + default CPT (*turbo*) is used. To use the dataset-specific CPT, you need to + explicitly set ``cmap="@earth_mdt.cpt"``. + + Refer to :gmt-datasets:`earth-mdt.html` for more details about available datasets, + including version information and references. + + Parameters + ---------- + resolution + The grid resolution. The suffix ``d`` and ``m`` stand for arc-degrees and + arc-minutes. Note that ``"07m"`` refers to a resolution of 7.5 arc-minutes. + region + The subregion of the grid to load, in the form of a sequence [*xmin*, *xmax*, + *ymin*, *ymax*] or an ISO country code. + registration + Grid registration type. Either ``"pixel"`` for pixel registration or + ``"gridline"`` for gridline registration. + + Returns + ------- + grid + The CNES Earth mean dynamic topography grid. Coordinates are latitude and + longitude in degrees. Values are in meters. + + Note + ---- + The registration and coordinate system type of the returned + :class:`xarray.DataArray` grid can be accessed via the GMT accessors (i.e., + ``grid.gmt.registration`` and ``grid.gmt.gtype`` respectively). However, these + properties may be lost after specific grid operations (such as slicing) and will + need to be manually set before passing the grid to any PyGMT data processing or + plotting functions. Refer to :class:`pygmt.GMTDataArrayAccessor` for detailed + explanations and workarounds. + + Examples + -------- + + >>> from pygmt.datasets import load_earth_mean_dynamic_topography + >>> # load the default grid (gridline-registered 1 arc-degree grid) + >>> grid = load_earth_mean_dynamic_topography() + >>> # load the 30 arc-minutes grid with "gridline" registration + >>> grid = load_earth_mean_dynamic_topography( + resolution="30m", registration="gridline" + ...) + >>> # load high-resolution (5 arc-minutes) grid for a specific region + >>> grid = load_earth_mean_dynamic_topography( + ... resolution="05m", + ... region=[120, 160, 30, 60], + ... registration="gridline", + ... ) + """ + grid = _load_remote_dataset( + name="earth_mdt", + prefix="earth_mdt", + resolution=resolution, + region=region, + registration=registration, + ) + return grid diff --git a/pygmt/datasets/load_remote_dataset.py b/pygmt/datasets/load_remote_dataset.py index 717396c145c..9275d694707 100644 --- a/pygmt/datasets/load_remote_dataset.py +++ b/pygmt/datasets/load_remote_dataset.py @@ -246,6 +246,19 @@ class GMTRemoteDataset(NamedTuple): "30s": Resolution("30s", registrations=["pixel"]), }, ), + "earth_mdt": GMTRemoteDataset( + description="CNES Earth mean dynamic topography", + units="meters", + extra_attributes={"horizontal_datum": "WGS84"}, + resolutions={ + "01d": Resolution("01d"), + "30m": Resolution("30m"), + "20m": Resolution("20m"), + "15m": Resolution("15m"), + "10m": Resolution("10m"), + "07m": Resolution("07m", registrations=["gridline"]), + }, + ), "earth_vgg": GMTRemoteDataset( description="IGPP Earth vertical gravity gradient", units="Eotvos", diff --git a/pygmt/helpers/caching.py b/pygmt/helpers/caching.py index 183bab97161..054de7a4f85 100644 --- a/pygmt/helpers/caching.py +++ b/pygmt/helpers/caching.py @@ -23,6 +23,8 @@ def cache_data(): "@earth_mag_01d_g", "@earth_mag4km_01d_g", "@earth_mask_01d_g", + "@earth_mdt_01d_g", + "@earth_mdt_07m_g", "@earth_night_01d", "@earth_relief_01d_g", "@earth_relief_01d_p", diff --git a/pygmt/tests/test_datasets_mean_dynamic_topography.py b/pygmt/tests/test_datasets_mean_dynamic_topography.py new file mode 100644 index 00000000000..deae6e90a60 --- /dev/null +++ b/pygmt/tests/test_datasets_mean_dynamic_topography.py @@ -0,0 +1,53 @@ +""" +Test basic functionality for loading Earth mean dynamic topography datasets. +""" + +import numpy as np +import numpy.testing as npt +from pygmt.datasets import load_earth_mean_dynamic_topography + + +def test_earth_mdt_01d(): + """ + Test some properties of the Earth mean dynamic topography 01d data. + """ + data = load_earth_mean_dynamic_topography(resolution="01d") + assert data.name == "z" + assert data.attrs["description"] == "CNES Earth mean dynamic topography" + assert data.attrs["units"] == "meters" + assert data.attrs["horizontal_datum"] == "WGS84" + assert data.shape == (181, 361) + assert data.gmt.registration == 0 + npt.assert_allclose(data.lat, np.arange(-90, 91, 1)) + npt.assert_allclose(data.lon, np.arange(-180, 181, 1)) + npt.assert_allclose(data.min(), -1.4668, atol=0.0001) + npt.assert_allclose(data.max(), 1.7151, atol=0.0001) + + +def test_earth_mdt_01d_with_region(): + """ + Test loading low-resolution Earth mean dynamic topography with "region". + """ + data = load_earth_mean_dynamic_topography(resolution="01d", region=[-10, 10, -5, 5]) + assert data.shape == (11, 21) + assert data.gmt.registration == 0 + npt.assert_allclose(data.lat, np.arange(-5, 6, 1)) + npt.assert_allclose(data.lon, np.arange(-10, 11, 1)) + npt.assert_allclose(data.min(), 0.346, atol=0.0001) + npt.assert_allclose(data.max(), 0.4839, atol=0.0001) + + +def test_earth_mdt_07m_default_registration(): + """ + Test that the grid returned by default for the 7 arc-minutes resolution has a + "gridline" registration. + """ + data = load_earth_mean_dynamic_topography(resolution="07m", region=[-10, -9, 3, 5]) + assert data.shape == (17, 9) + assert data.gmt.registration == 0 + assert data.coords["lat"].data.min() == 3.0 + assert data.coords["lat"].data.max() == 5.0 + assert data.coords["lon"].data.min() == -10.0 + assert data.coords["lon"].data.max() == -9.0 + npt.assert_allclose(data.min(), 0.4138, atol=0.0001) + npt.assert_allclose(data.max(), 0.4302, atol=0.0001) From 16919adbfeb4bfe36bb0c03be4270270d265878f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Yvonne=20Fr=C3=B6hlich?= <94163266+yvonnefroehlich@users.noreply.github.com> Date: Thu, 26 Dec 2024 12:43:07 +0100 Subject: [PATCH 11/55] Remote datasets: Add "load_earth_mean_sea_surface" to load "CNES Earth Mean Sea Surface" dataset (#3717) --- doc/api/index.rst | 1 + pygmt/datasets/__init__.py | 1 + pygmt/datasets/earth_mean_sea_surface.py | 104 ++++++++++++++++++ pygmt/datasets/load_remote_dataset.py | 18 +++ pygmt/helpers/caching.py | 2 + .../test_datasets_earth_mean_sea_surface.py | 53 +++++++++ 6 files changed, 179 insertions(+) create mode 100644 pygmt/datasets/earth_mean_sea_surface.py create mode 100644 pygmt/tests/test_datasets_earth_mean_sea_surface.py diff --git a/doc/api/index.rst b/doc/api/index.rst index 6f6d87b8b7f..7f46afdf413 100644 --- a/doc/api/index.rst +++ b/doc/api/index.rst @@ -239,6 +239,7 @@ and store them in GMT's user data directory. datasets.load_earth_magnetic_anomaly datasets.load_earth_mask datasets.load_earth_mean_dynamic_topography + datasets.load_earth_mean_sea_surface datasets.load_earth_relief datasets.load_earth_vertical_gravity_gradient datasets.load_mars_relief diff --git a/pygmt/datasets/__init__.py b/pygmt/datasets/__init__.py index f60739a6e1b..9e163b33e3c 100644 --- a/pygmt/datasets/__init__.py +++ b/pygmt/datasets/__init__.py @@ -14,6 +14,7 @@ from pygmt.datasets.earth_mean_dynamic_topography import ( load_earth_mean_dynamic_topography, ) +from pygmt.datasets.earth_mean_sea_surface import load_earth_mean_sea_surface from pygmt.datasets.earth_night import load_black_marble from pygmt.datasets.earth_relief import load_earth_relief from pygmt.datasets.earth_vertical_gravity_gradient import ( diff --git a/pygmt/datasets/earth_mean_sea_surface.py b/pygmt/datasets/earth_mean_sea_surface.py new file mode 100644 index 00000000000..f4856d98626 --- /dev/null +++ b/pygmt/datasets/earth_mean_sea_surface.py @@ -0,0 +1,104 @@ +""" +Function to download the CNES Earth mean sea surface dataset from the GMT data +server, and load as :class:`xarray.DataArray`. + +The grids are available in various resolutions. +""" + +from collections.abc import Sequence +from typing import Literal + +import xarray as xr +from pygmt.datasets.load_remote_dataset import _load_remote_dataset + +__doctest_skip__ = ["load_earth_mean_sea_surface"] + + +def load_earth_mean_sea_surface( + resolution: Literal[ + "01d", "30m", "20m", "15m", "10m", "06m", "05m", "04m", "03m", "02m", "01m" + ] = "01d", + region: Sequence[float] | str | None = None, + registration: Literal["gridline", "pixel"] = "gridline", +) -> xr.DataArray: + r""" + Load the CNES Earth mean sea surface dataset in various resolutions. + + .. figure:: https://www.generic-mapping-tools.org/remote-datasets/_images/GMT_earth_mss.jpg + :width: 80 % + :align: center + + CNES Earth mean sea surface dataset. + + The grids are downloaded to a user data directory (usually + ``~/.gmt/server/earth/earth_mss/``) the first time you invoke this function. + Afterwards, it will load the grid from the data directory. So you'll need an + internet connection the first time around. + + These grids can also be accessed by passing in the file name + **@earth_mss**\_\ *res*\[_\ *reg*] to any grid processing function or plotting + method. *res* is the grid resolution (see below), and *reg* is the grid registration + type (**p** for pixel registration or **g** for gridline registration). + + The default color palette table (CPT) for this dataset is *@earth_mss.cpt*. It's + implicitly used when passing in the file name of the dataset to any grid plotting + method if no CPT is explicitly specified. When the dataset is loaded and plotted + as an :class:`xarray.DataArray` object, the default CPT is ignored, and GMT's + default CPT (*turbo*) is used. To use the dataset-specific CPT, you need to + explicitly set ``cmap="@earth_mss.cpt"``. + + Refer to :gmt-datasets:`earth-mss.html` for more details about available datasets, + including version information and references. + + Parameters + ---------- + resolution + The grid resolution. The suffix ``d`` and ``m`` stand for arc-degrees and + arc-minutes. + region + The subregion of the grid to load, in the form of a sequence [*xmin*, *xmax*, + *ymin*, *ymax*] or an ISO country code. Required for grids with resolutions + higher than 5 arc-minutes (i.e., ``"05m"``). + registration + Grid registration type. Either ``"pixel"`` for pixel registration or + ``"gridline"`` for gridline registration. + + Returns + ------- + grid + The CNES Earth mean sea surface grid. Coordinates are latitude and + longitude in degrees. Values are in meters. + + Note + ---- + The registration and coordinate system type of the returned + :class:`xarray.DataArray` grid can be accessed via the GMT accessors (i.e., + ``grid.gmt.registration`` and ``grid.gmt.gtype`` respectively). However, these + properties may be lost after specific grid operations (such as slicing) and will + need to be manually set before passing the grid to any PyGMT data processing or + plotting functions. Refer to :class:`pygmt.GMTDataArrayAccessor` for detailed + explanations and workarounds. + + Examples + -------- + + >>> from pygmt.datasets import load_earth_mean_sea_surface + >>> # load the default grid (gridline-registered 1 arc-degree grid) + >>> grid = load_earth_mean_sea_surface() + >>> # load the 30 arc-minutes grid with "gridline" registration + >>> grid = load_earth_mean_sea_surface(resolution="30m", registration="gridline") + >>> # load high-resolution (5 arc-minutes) grid for a specific region + >>> grid = load_earth_mean_sea_surface( + ... resolution="05m", + ... region=[120, 160, 30, 60], + ... registration="gridline", + ... ) + """ + grid = _load_remote_dataset( + name="earth_mss", + prefix="earth_mss", + resolution=resolution, + region=region, + registration=registration, + ) + return grid diff --git a/pygmt/datasets/load_remote_dataset.py b/pygmt/datasets/load_remote_dataset.py index 9275d694707..0cd68796a7c 100644 --- a/pygmt/datasets/load_remote_dataset.py +++ b/pygmt/datasets/load_remote_dataset.py @@ -227,6 +227,24 @@ class GMTRemoteDataset(NamedTuple): "15s": Resolution("15s"), }, ), + "earth_mss": GMTRemoteDataset( + description="CNES Earth mean sea surface", + units="meters", + extra_attributes={"horizontal_datum": "WGS84"}, + resolutions={ + "01d": Resolution("01d"), + "30m": Resolution("30m"), + "20m": Resolution("20m"), + "15m": Resolution("15m"), + "10m": Resolution("10m"), + "06m": Resolution("06m"), + "05m": Resolution("05m", tiled=True), + "04m": Resolution("04m", tiled=True), + "03m": Resolution("03m", tiled=True), + "02m": Resolution("02m", tiled=True), + "01m": Resolution("01m", tiled=True, registrations=["gridline"]), + }, + ), "earth_night": GMTRemoteDataset( description="NASA Night Images", units=None, diff --git a/pygmt/helpers/caching.py b/pygmt/helpers/caching.py index 054de7a4f85..d0bfd8fb46e 100644 --- a/pygmt/helpers/caching.py +++ b/pygmt/helpers/caching.py @@ -25,6 +25,7 @@ def cache_data(): "@earth_mask_01d_g", "@earth_mdt_01d_g", "@earth_mdt_07m_g", + "@earth_mss_01d_g", "@earth_night_01d", "@earth_relief_01d_g", "@earth_relief_01d_p", @@ -53,6 +54,7 @@ def cache_data(): "@N00W030.earth_geoid_01m_g.nc", "@S30W060.earth_mag_02m_p.nc", "@S30W120.earth_mag4km_02m_p.nc", + "@N30E090.earth_mss_01m_g.nc", "@N00W090.earth_relief_03m_p.nc", "@N00E135.earth_relief_30s_g.nc", "@N00W010.earth_relief_15s_p.nc", diff --git a/pygmt/tests/test_datasets_earth_mean_sea_surface.py b/pygmt/tests/test_datasets_earth_mean_sea_surface.py new file mode 100644 index 00000000000..84b2b7123cc --- /dev/null +++ b/pygmt/tests/test_datasets_earth_mean_sea_surface.py @@ -0,0 +1,53 @@ +""" +Test basic functionality for loading Earth mean sea surface datasets. +""" + +import numpy as np +import numpy.testing as npt +from pygmt.datasets import load_earth_mean_sea_surface + + +def test_earth_mss_01d(): + """ + Test some properties of the Earth mean sea surface 01d data. + """ + data = load_earth_mean_sea_surface(resolution="01d") + assert data.name == "z" + assert data.attrs["description"] == "CNES Earth mean sea surface" + assert data.attrs["units"] == "meters" + assert data.attrs["horizontal_datum"] == "WGS84" + assert data.shape == (181, 361) + assert data.gmt.registration == 0 + npt.assert_allclose(data.lat, np.arange(-90, 91, 1)) + npt.assert_allclose(data.lon, np.arange(-180, 181, 1)) + npt.assert_allclose(data.min(), -104.71, atol=0.01) + npt.assert_allclose(data.max(), 82.38, atol=0.01) + + +def test_earth_mss_01d_with_region(): + """ + Test loading low-resolution Earth mean sea surface with "region". + """ + data = load_earth_mean_sea_surface(resolution="01d", region=[-10, 10, -5, 5]) + assert data.shape == (11, 21) + assert data.gmt.registration == 0 + npt.assert_allclose(data.lat, np.arange(-5, 6, 1)) + npt.assert_allclose(data.lon, np.arange(-10, 11, 1)) + npt.assert_allclose(data.min(), 6.53, atol=0.01) + npt.assert_allclose(data.max(), 29.31, atol=0.01) + + +def test_earth_mss_01m_default_registration(): + """ + Test that the grid returned by default for the 1 arc-minute resolution has a + "gridline" registration. + """ + data = load_earth_mean_sea_surface(resolution="01m", region=[-10, -9, 3, 5]) + assert data.shape == (121, 61) + assert data.gmt.registration == 0 + assert data.coords["lat"].data.min() == 3.0 + assert data.coords["lat"].data.max() == 5.0 + assert data.coords["lon"].data.min() == -10.0 + assert data.coords["lon"].data.max() == -9.0 + npt.assert_allclose(data.min(), 21.27, atol=0.01) + npt.assert_allclose(data.max(), 31.11, atol=0.01) From c44b5a5e21129f183db3781875f6ea38c3c5d5ab Mon Sep 17 00:00:00 2001 From: Dongdong Tian Date: Thu, 26 Dec 2024 20:35:25 +0800 Subject: [PATCH 12/55] Enable ruff's flake8-todos (TD) rules (#3723) --- pyproject.toml | 2 ++ 1 file changed, 2 insertions(+) diff --git a/pyproject.toml b/pyproject.toml index b4907e6dc64..dfea29b5c46 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -119,6 +119,7 @@ select = [ "SIM", # flake8-simplify "T20", # flake8-print "TC", # flake8-type-checking + "TD", # flake8-todos "TID", # flake8-tidy-imports "TRY", # tryceratops "UP", # pyupgrade @@ -147,6 +148,7 @@ ignore = [ "RET504", # Allow variable assignment and return immediately for readability "S603", # Allow method calls that initiate a subprocess without a shell "SIM117", # Allow nested `with` statements + "TD003", # Allow TODO comments without associated issue link ] preview = true explicit-preview-rules = true From 85c78f8ec8989c4f3ca010e1d662b959ca1eb4df Mon Sep 17 00:00:00 2001 From: Dongdong Tian Date: Thu, 26 Dec 2024 20:52:31 +0800 Subject: [PATCH 13/55] Add TODO comments in the maintainers guides and update the release checklist (#3724) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Co-authored-by: Yvonne Fröhlich <94163266+yvonnefroehlich@users.noreply.github.com> --- .github/ISSUE_TEMPLATE/4-release_checklist.md | 2 +- doc/maintenance.md | 61 +++++++++++++------ 2 files changed, 43 insertions(+), 20 deletions(-) diff --git a/.github/ISSUE_TEMPLATE/4-release_checklist.md b/.github/ISSUE_TEMPLATE/4-release_checklist.md index 7d46be2538b..83fe809724a 100644 --- a/.github/ISSUE_TEMPLATE/4-release_checklist.md +++ b/.github/ISSUE_TEMPLATE/4-release_checklist.md @@ -23,7 +23,7 @@ assignees: '' core package dependencies (NumPy, pandas, Xarray) - [ ] Review the ["PyGMT Team" page](https://www.pygmt.org/dev/team.html) - [ ] Check to ensure that: - - [ ] Deprecations and related tests are removed for this version by running `grep --include="*.py" -r vX.Y.Z` from the base of the repository + - [ ] Deprecated workarounds/codes/tests are removed. Run `grep "# TODO" **/*.py` to find all potential TODOs. - [ ] All tests pass in the ["GMT Legacy Tests" workflow](https://github.com/GenericMappingTools/pygmt/actions/workflows/ci_tests_legacy.yaml) - [ ] All tests pass in the ["GMT Dev Tests" workflow](https://github.com/GenericMappingTools/pygmt/actions/workflows/ci_tests_dev.yaml) - [ ] All tests pass in the ["Doctests" workflow](https://github.com/GenericMappingTools/pygmt/actions/workflows/ci_doctests.yaml) diff --git a/doc/maintenance.md b/doc/maintenance.md index 41c64769f98..0664a2850eb 100644 --- a/doc/maintenance.md +++ b/doc/maintenance.md @@ -148,34 +148,35 @@ patch release. ## Backwards Compatibility and Deprecation Policy -PyGMT is still undergoing rapid development. All of the API is subject to change -until the v1.0.0 release. Versioning in PyGMT is based on the +PyGMT is still undergoing rapid development. All of the API is subject to change until +the v1.0.0 release. Versioning in PyGMT is based on the [semantic versioning specification](https://semver.org/spec/v2.0.0.html) -(i.e., v*MAJOR*.*MINOR*.*PATCH*). -Basic policy for backwards compatibility: +(i.e., v*MAJOR*.*MINOR*.*PATCH*). Basic policy for backwards compatibility: - Any incompatible changes should go through the deprecation process below. -- Incompatible changes are only allowed in major and minor releases, not in - patch releases. +- Incompatible changes are only allowed in major and minor releases, not in patch releases. - Incompatible changes should be documented in the release notes. When making incompatible changes, we should follow the process: - Discuss whether the incompatible changes are necessary on GitHub. -- Make the changes in a backwards compatible way, and raise a `FutureWarning` - warning for the old usage. At least one test using the old usage should be added. -- The warning message should clearly explain the changes and include the versions - in which the old usage is deprecated and is expected to be removed. -- The `FutureWarning` warning should appear for 2-4 minor versions, depending on - the impact of the changes. It means the deprecation period usually lasts - 3-12 months. +- Make the changes in a backwards compatible way, and raise a `FutureWarning` warning + for the old usage. At least one test using the old usage should be added. +- The warning message should clearly explain the changes and include the versions in + which the old usage is deprecated and is expected to be removed. +- The `FutureWarning` warning should appear for 2-4 minor versions, depending on the + impact of the changes. It means the deprecation period usually lasts 3-12 months. - Remove the old usage and warning when reaching the declared version. -To rename a function parameter, add the `@deprecate_parameter` decorator near -the top after the `@fmt_docstring` decorator but before the `@use_alias` -decorator (if those two exist). Here is an example: +### Deprecating a function parameter -``` +To rename a function parameter, add the `@deprecate_parameter` decorator near the top +after the `@fmt_docstring` decorator but before the `@use_alias` decorator (if those two +exist). A `TODO` comment should also be added to indicate the deprecation period (see below). +Here is an example: + +```python +# TODO(PyGMT>=0.6.0): Remove the deprecated "columns" parameter. @fmt_docstring @deprecate_parameter("columns", "incols", "v0.4.0", remove_version="v0.6.0") @use_alias(J="projection", R="region", V="verbose", i="incols") @@ -184,8 +185,30 @@ def plot(self, x=None, y=None, data=None, size=None, direction=None, **kwargs): pass ``` -In this case, the old parameter name `columns` is deprecated since v0.4.0, and -will be fully removed in v0.6.0. The new parameter name is `incols`. +In this case, the old parameter name `columns` is deprecated since v0.4.0, and will be +fully removed in v0.6.0. The new parameter name is `incols`. + +### TODO comments + +Occasionally, we need to implement temporary code that should be removed in the future. +This can occur in situations such as: + +- When a parameter, function, or method is deprecated and scheduled for removal. +- When workarounds are necessary to address issues in older or upcoming versions of GMT + or other dependencies. + +To track these temporary codes or workarounds, we use TODO comments. These comments +should adhere to the following format: + +```python +# TODO(package>=X.Y.Z): A brief description of the TODO item. +# Additional details if necessary. +``` +The TODO comment indicates that we should address the item when *package* version +*X.Y.Z* or later is required. + +It's important not to overuse TODO comments for tracking unimplemented features. +Instead, open issues to monitor these features. ## Making a Release From efbc0fb4756c0c36898efa5c53d548dcba9e4a2f Mon Sep 17 00:00:00 2001 From: Michael Grund <23025878+michaelgrund@users.noreply.github.com> Date: Thu, 26 Dec 2024 14:41:04 +0100 Subject: [PATCH 14/55] Add Figure.hlines for plotting horizontal lines (#923) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Co-authored-by: Dongdong Tian Co-authored-by: Yvonne Fröhlich <94163266+yvonnefroehlich@users.noreply.github.com> Co-authored-by: actions-bot <58130806+actions-bot@users.noreply.github.com> --- doc/api/index.rst | 1 + pygmt/figure.py | 1 + pygmt/src/__init__.py | 1 + pygmt/src/hlines.py | 137 ++++++++++++++++++ pygmt/tests/baseline/test_hlines_clip.png.dvc | 5 + .../test_hlines_geographic_global_d.png.dvc | 5 + .../test_hlines_geographic_global_g.png.dvc | 5 + .../test_hlines_multiple_lines.png.dvc | 5 + .../baseline/test_hlines_one_line.png.dvc | 5 + .../test_hlines_polar_projection.png.dvc | 5 + pygmt/tests/test_hlines.py | 121 ++++++++++++++++ 11 files changed, 291 insertions(+) create mode 100644 pygmt/src/hlines.py create mode 100644 pygmt/tests/baseline/test_hlines_clip.png.dvc create mode 100644 pygmt/tests/baseline/test_hlines_geographic_global_d.png.dvc create mode 100644 pygmt/tests/baseline/test_hlines_geographic_global_g.png.dvc create mode 100644 pygmt/tests/baseline/test_hlines_multiple_lines.png.dvc create mode 100644 pygmt/tests/baseline/test_hlines_one_line.png.dvc create mode 100644 pygmt/tests/baseline/test_hlines_polar_projection.png.dvc create mode 100644 pygmt/tests/test_hlines.py diff --git a/doc/api/index.rst b/doc/api/index.rst index 7f46afdf413..b64e3de4bc4 100644 --- a/doc/api/index.rst +++ b/doc/api/index.rst @@ -29,6 +29,7 @@ Plotting map elements Figure.basemap Figure.coast Figure.colorbar + Figure.hlines Figure.inset Figure.legend Figure.logo diff --git a/pygmt/figure.py b/pygmt/figure.py index 032b9f37190..c42c9ba69e8 100644 --- a/pygmt/figure.py +++ b/pygmt/figure.py @@ -417,6 +417,7 @@ def _repr_html_(self) -> str: grdimage, grdview, histogram, + hlines, image, inset, legend, diff --git a/pygmt/src/__init__.py b/pygmt/src/__init__.py index e4db7321963..7fa068f4505 100644 --- a/pygmt/src/__init__.py +++ b/pygmt/src/__init__.py @@ -29,6 +29,7 @@ from pygmt.src.grdview import grdview from pygmt.src.grdvolume import grdvolume from pygmt.src.histogram import histogram +from pygmt.src.hlines import hlines from pygmt.src.image import image from pygmt.src.info import info from pygmt.src.inset import inset diff --git a/pygmt/src/hlines.py b/pygmt/src/hlines.py new file mode 100644 index 00000000000..8871bd3a825 --- /dev/null +++ b/pygmt/src/hlines.py @@ -0,0 +1,137 @@ +""" +hlines - Plot horizontal lines. +""" + +from collections.abc import Sequence + +import numpy as np +from pygmt.exceptions import GMTInvalidInput + +__doctest_skip__ = ["hlines"] + + +def hlines( + self, + y: float | Sequence[float], + xmin: float | Sequence[float] | None = None, + xmax: float | Sequence[float] | None = None, + pen: str | None = None, + label: str | None = None, + no_clip: bool = False, + perspective: str | bool | None = None, +): + """ + Plot one or multiple horizontal line(s). + + This method is a high-level wrapper around :meth:`pygmt.Figure.plot` that focuses on + plotting horizontal lines at Y-coordinates specified by the ``y`` parameter. The + ``y`` parameter can be a single value (for a single horizontal line) or a sequence + of values (for multiple horizontal lines). + + By default, the X-coordinates of the start and end points of the lines are set to + be the X-limits of the current plot, but this can be overridden by specifying the + ``xmin`` and ``xmax`` parameters. ``xmin`` and ``xmax`` can be either a single + value or a sequence of values. If a single value is provided, it is applied to all + lines. If a sequence is provided, the length of ``xmin`` and ``xmax`` must match + the length of ``y``. + + The term "horizontal" lines can be interpreted differently in different coordinate + systems: + + - **Cartesian** coordinate system: lines are plotted as straight lines. + - **Polar** projection: lines are plotted as arcs along a constant radius. + - **Geographic** projection: lines are plotted as parallels along constant latitude. + + Parameters + ---------- + y + Y-coordinates to plot the lines. It can be a single value (for a single line) + or a sequence of values (for multiple lines). + xmin/xmax + X-coordinates of the start/end point of the line(s). If ``None``, defaults to + the X-limits of the current plot. ``xmin`` and ``xmax`` can be either a single + value or a sequence of values. If a single value is provided, it is applied to + all lines. If a sequence is provided, the length of ``xmin`` and ``xmax`` must + match the length of ``y``. + pen + Pen attributes for the line(s), in the format of *width,color,style*. + label + Label for the line(s), to be displayed in the legend. + no_clip + If ``True``, do not clip lines outside the plot region. Only makes sense in the + Cartesian coordinate system. + perspective + Select perspective view and set the azimuth and elevation angle of the + viewpoint. Refer to :meth:`pygmt.Figure.plot` for details. + + Examples + -------- + >>> import pygmt + >>> fig = pygmt.Figure() + >>> fig.basemap(region=[0, 10, 0, 10], projection="X10c/10c", frame=True) + >>> fig.hlines(y=1, pen="1p,black", label="Line at y=1") + >>> fig.hlines(y=2, xmin=2, xmax=8, pen="1p,red,-", label="Line at y=2") + >>> fig.hlines(y=[3, 4], xmin=3, xmax=7, pen="1p,black,.", label="Lines at y=3,4") + >>> fig.hlines(y=[5, 6], xmin=4, xmax=9, pen="1p,red", label="Lines at y=5,6") + >>> fig.hlines( + ... y=[7, 8], xmin=[0, 1], xmax=[7, 8], pen="1p,blue", label="Lines at y=7,8" + ... ) + >>> fig.legend() + >>> fig.show() + """ + self._preprocess() + + # Determine the x limits from the current plot region if not specified. + if xmin is None or xmax is None: + xlimits = self.region[:2] + if xmin is None: + xmin = xlimits[0] + if xmax is None: + xmax = xlimits[1] + + # Ensure y/xmin/xmax are 1-D arrays. + _y = np.atleast_1d(y) + _xmin = np.atleast_1d(xmin) + _xmax = np.atleast_1d(xmax) + + nlines = len(_y) # Number of lines to plot. + + # Check if xmin/xmax are scalars or have the expected length. + if _xmin.size not in {1, nlines} or _xmax.size not in {1, nlines}: + msg = ( + f"'xmin' and 'xmax' are expected to be scalars or have lengths '{nlines}', " + f"but lengths '{_xmin.size}' and '{_xmax.size}' are given." + ) + raise GMTInvalidInput(msg) + + # Repeat xmin/xmax to match the length of y if they are scalars. + if nlines != 1: + if _xmin.size == 1: + _xmin = np.repeat(_xmin, nlines) + if _xmax.size == 1: + _xmax = np.repeat(_xmax, nlines) + + # Call the Figure.plot method to plot the lines. + for i in range(nlines): + # Special handling for label. + # 1. Only specify a label when plotting the first line. + # 2. The -l option can accept comma-separated labels for labeling multiple lines + # with auto-coloring enabled. We don't need this feature here, so we need to + # replace comma with \054 if the label contains commas. + _label = label.replace(",", "\\054") if label and i == 0 else None + + # By default, points are connected as great circle arcs in geographic coordinate + # systems and straight lines in Cartesian coordinate systems (including polar + # projection). To plot "horizontal" lines along constant latitude (in geographic + # coordinate systems) or constant radius (in polar projection), we need to + # resample the line to at least 4 points. + npoints = 4 # 2 for Cartesian, at least 4 for geographic and polar projections. + self.plot( + x=np.linspace(_xmin[i], _xmax[i], npoints), + y=[_y[i]] * npoints, + pen=pen, + label=_label, + no_clip=no_clip, + perspective=perspective, + straight_line="x", + ) diff --git a/pygmt/tests/baseline/test_hlines_clip.png.dvc b/pygmt/tests/baseline/test_hlines_clip.png.dvc new file mode 100644 index 00000000000..1c24bb1c16d --- /dev/null +++ b/pygmt/tests/baseline/test_hlines_clip.png.dvc @@ -0,0 +1,5 @@ +outs: +- md5: e87ea1b80ae5d32d49e9ad94a5c25f96 + size: 7199 + hash: md5 + path: test_hlines_clip.png diff --git a/pygmt/tests/baseline/test_hlines_geographic_global_d.png.dvc b/pygmt/tests/baseline/test_hlines_geographic_global_d.png.dvc new file mode 100644 index 00000000000..960f3a05fdc --- /dev/null +++ b/pygmt/tests/baseline/test_hlines_geographic_global_d.png.dvc @@ -0,0 +1,5 @@ +outs: +- md5: b7055f03ff5bc152c0f6b72f2d39f32c + size: 29336 + hash: md5 + path: test_hlines_geographic_global_d.png diff --git a/pygmt/tests/baseline/test_hlines_geographic_global_g.png.dvc b/pygmt/tests/baseline/test_hlines_geographic_global_g.png.dvc new file mode 100644 index 00000000000..29b83d3b44f --- /dev/null +++ b/pygmt/tests/baseline/test_hlines_geographic_global_g.png.dvc @@ -0,0 +1,5 @@ +outs: +- md5: ab2e7717cad6ac4132fd3e3af1fefa89 + size: 29798 + hash: md5 + path: test_hlines_geographic_global_g.png diff --git a/pygmt/tests/baseline/test_hlines_multiple_lines.png.dvc b/pygmt/tests/baseline/test_hlines_multiple_lines.png.dvc new file mode 100644 index 00000000000..e915a5a65f0 --- /dev/null +++ b/pygmt/tests/baseline/test_hlines_multiple_lines.png.dvc @@ -0,0 +1,5 @@ +outs: +- md5: 70c8decbffd37fc48b2eb9ff84442ec0 + size: 14139 + hash: md5 + path: test_hlines_multiple_lines.png diff --git a/pygmt/tests/baseline/test_hlines_one_line.png.dvc b/pygmt/tests/baseline/test_hlines_one_line.png.dvc new file mode 100644 index 00000000000..aa42ce3f492 --- /dev/null +++ b/pygmt/tests/baseline/test_hlines_one_line.png.dvc @@ -0,0 +1,5 @@ +outs: +- md5: 121970f75d34c552e632cacc692f09e9 + size: 13685 + hash: md5 + path: test_hlines_one_line.png diff --git a/pygmt/tests/baseline/test_hlines_polar_projection.png.dvc b/pygmt/tests/baseline/test_hlines_polar_projection.png.dvc new file mode 100644 index 00000000000..4e5bef96dc6 --- /dev/null +++ b/pygmt/tests/baseline/test_hlines_polar_projection.png.dvc @@ -0,0 +1,5 @@ +outs: +- md5: 0c0eeb160dd6beb06bb6d3dcc264127a + size: 57789 + hash: md5 + path: test_hlines_polar_projection.png diff --git a/pygmt/tests/test_hlines.py b/pygmt/tests/test_hlines.py new file mode 100644 index 00000000000..aaaddad4f08 --- /dev/null +++ b/pygmt/tests/test_hlines.py @@ -0,0 +1,121 @@ +""" +Tests for Figure.hlines. +""" + +import pytest +from pygmt import Figure +from pygmt.exceptions import GMTInvalidInput + + +@pytest.mark.mpl_image_compare +def test_hlines_one_line(): + """ + Plot one horizontal line. + """ + fig = Figure() + fig.basemap(region=[0, 10, 0, 10], projection="X10c/10c", frame=True) + fig.hlines(1) + fig.hlines(2, xmin=1) + fig.hlines(3, xmax=9) + fig.hlines(4, xmin=3, xmax=8) + fig.hlines(5, xmin=4, xmax=8, pen="1p,blue", label="Line at y=5") + fig.hlines(6, xmin=5, xmax=7, pen="1p,red", label="Line at y=6") + fig.legend() + return fig + + +@pytest.mark.mpl_image_compare +def test_hlines_multiple_lines(): + """ + Plot multiple horizontal lines. + """ + fig = Figure() + fig.basemap(region=[0, 10, 0, 16], projection="X10c/10c", frame=True) + fig.hlines([1, 2]) + fig.hlines([3, 4, 5], xmin=[1, 2, 3]) + fig.hlines([6, 7, 8], xmax=[7, 8, 9]) + fig.hlines([9, 10], xmin=[1, 2], xmax=[9, 10]) + fig.hlines([11, 12], xmin=1, xmax=9, pen="1p,blue", label="Lines at y=11,12") + fig.hlines( + [13, 14], xmin=[3, 4], xmax=[8, 9], pen="1p,red", label="Lines at y=13,14" + ) + fig.legend() + return fig + + +@pytest.mark.mpl_image_compare +def test_hlines_clip(): + """ + Plot horizontal lines with clipping or not. + """ + fig = Figure() + fig.basemap(region=[0, 10, 0, 4], projection="X10c/4c", frame=True) + fig.hlines(1, xmin=-2, xmax=12) + fig.hlines(2, xmin=-2, xmax=12, no_clip=True) + return fig + + +@pytest.mark.mpl_image_compare +@pytest.mark.parametrize("region", ["g", "d"]) +def test_hlines_geographic_global(region): + """ + Plot horizontal lines in geographic coordinates. + """ + fig = Figure() + fig.basemap(region=region, projection="R15c", frame=True) + # Plot lines with longitude range of 0 to 360. + fig.hlines(10, pen="1p") + fig.hlines(20, xmin=0, xmax=360, pen="1p") + fig.hlines(30, xmin=0, xmax=180, pen="1p") + fig.hlines(40, xmin=180, xmax=360, pen="1p") + fig.hlines(50, xmin=0, xmax=90, pen="1p") + fig.hlines(60, xmin=90, xmax=180, pen="1p") + fig.hlines(70, xmin=180, xmax=270, pen="1p") + fig.hlines(80, xmin=270, xmax=360, pen="1p") + + # Plot lines with longitude range of -180 to 180. + fig.hlines(-10, pen="1p,red") + fig.hlines(-20, xmin=-180, xmax=180, pen="1p,red") + fig.hlines(-30, xmin=-180, xmax=0, pen="1p,red") + fig.hlines(-40, xmin=0, xmax=180, pen="1p,red") + fig.hlines(-50, xmin=-180, xmax=-90, pen="1p,red") + fig.hlines(-60, xmin=-90, xmax=0, pen="1p,red") + fig.hlines(-70, xmin=0, xmax=90, pen="1p,red") + fig.hlines(-80, xmin=90, xmax=180, pen="1p,red") + return fig + + +@pytest.mark.mpl_image_compare +def test_hlines_polar_projection(): + """ + Plot horizontal lines in polar projection. + """ + fig = Figure() + fig.basemap(region=[0, 360, 0, 1], projection="P15c", frame=True) + fig.hlines(0.1, pen="1p") + fig.hlines(0.2, xmin=0, xmax=360, pen="1p") + fig.hlines(0.3, xmin=0, xmax=180, pen="1p") + fig.hlines(0.4, xmin=180, xmax=360, pen="1p") + fig.hlines(0.5, xmin=0, xmax=90, pen="1p") + fig.hlines(0.6, xmin=90, xmax=180, pen="1p") + fig.hlines(0.7, xmin=180, xmax=270, pen="1p") + fig.hlines(0.8, xmin=270, xmax=360, pen="1p") + return fig + + +def test_hlines_invalid_input(): + """ + Test invalid input for hlines. + """ + fig = Figure() + fig.basemap(region=[0, 10, 0, 6], projection="X10c/6c", frame=True) + with pytest.raises(GMTInvalidInput): + fig.hlines(1, xmin=2, xmax=[3, 4]) + with pytest.raises(GMTInvalidInput): + fig.hlines(1, xmin=[2, 3], xmax=4) + with pytest.raises(GMTInvalidInput): + fig.hlines(1, xmin=[2, 3], xmax=[4, 5]) + with pytest.raises(GMTInvalidInput): + fig.hlines([1, 2], xmin=[2, 3, 4], xmax=3) + with pytest.raises(GMTInvalidInput): + fig.hlines([1, 2], xmin=[2, 3], xmax=[4, 5, 6]) From c68ea48c577f35b20425f3031ea30a1e7b73ea6c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Yvonne=20Fr=C3=B6hlich?= <94163266+yvonnefroehlich@users.noreply.github.com> Date: Thu, 26 Dec 2024 17:20:50 +0100 Subject: [PATCH 15/55] Remote Datasets: Use complete word for units in "load_remote_dataset" and related tests (#3725) --- pygmt/datasets/load_remote_dataset.py | 4 ++-- pygmt/tests/test_datasets_earth_dist.py | 2 +- pygmt/tests/test_datasets_earth_geoid.py | 2 +- 3 files changed, 4 insertions(+), 4 deletions(-) diff --git a/pygmt/datasets/load_remote_dataset.py b/pygmt/datasets/load_remote_dataset.py index 0cd68796a7c..3949a2e6f95 100644 --- a/pygmt/datasets/load_remote_dataset.py +++ b/pygmt/datasets/load_remote_dataset.py @@ -94,7 +94,7 @@ class GMTRemoteDataset(NamedTuple): ), "earth_dist": GMTRemoteDataset( description="GSHHG Earth distance to shoreline", - units="km", + units="kilometers", extra_attributes={"horizontal_datum": "WGS84"}, resolutions={ "01d": Resolution("01d"), @@ -152,7 +152,7 @@ class GMTRemoteDataset(NamedTuple): ), "earth_geoid": GMTRemoteDataset( description="EGM2008 Earth geoid", - units="m", + units="meters", extra_attributes={"horizontal_datum": "WGS84"}, resolutions={ "01d": Resolution("01d"), diff --git a/pygmt/tests/test_datasets_earth_dist.py b/pygmt/tests/test_datasets_earth_dist.py index e0d91d77a84..a5f61a0b5f2 100644 --- a/pygmt/tests/test_datasets_earth_dist.py +++ b/pygmt/tests/test_datasets_earth_dist.py @@ -14,7 +14,7 @@ def test_earth_dist_01d(): data = load_earth_dist(resolution="01d") assert data.name == "z" assert data.attrs["description"] == "GSHHG Earth distance to shoreline" - assert data.attrs["units"] == "km" + assert data.attrs["units"] == "kilometers" assert data.attrs["horizontal_datum"] == "WGS84" assert data.shape == (181, 361) assert data.gmt.registration == 0 diff --git a/pygmt/tests/test_datasets_earth_geoid.py b/pygmt/tests/test_datasets_earth_geoid.py index 84bfc5d7bf4..af72969b032 100644 --- a/pygmt/tests/test_datasets_earth_geoid.py +++ b/pygmt/tests/test_datasets_earth_geoid.py @@ -15,7 +15,7 @@ def test_earth_geoid_01d(): assert data.name == "z" assert data.attrs["long_name"] == "geoid (m)" assert data.attrs["description"] == "EGM2008 Earth geoid" - assert data.attrs["units"] == "m" + assert data.attrs["units"] == "meters" assert data.attrs["horizontal_datum"] == "WGS84" assert data.shape == (181, 361) assert data.gmt.registration == 0 From e5efc918ebb67dc6e95a64fdc3d005345ea5fe3f Mon Sep 17 00:00:00 2001 From: Dongdong Tian Date: Fri, 27 Dec 2024 12:02:14 +0800 Subject: [PATCH 16/55] Simplify two clib loading tests using unittest.mock (#3690) --- pygmt/tests/test_clib_loading.py | 59 +++++++++----------------------- 1 file changed, 17 insertions(+), 42 deletions(-) diff --git a/pygmt/tests/test_clib_loading.py b/pygmt/tests/test_clib_loading.py index 98d9ae148c0..8b96128c0da 100644 --- a/pygmt/tests/test_clib_loading.py +++ b/pygmt/tests/test_clib_loading.py @@ -9,6 +9,7 @@ import sys import types from pathlib import PurePath +from unittest import mock import pytest from pygmt.clib.loading import ( @@ -70,24 +71,15 @@ def test_load_libgmt(): check_libgmt(load_libgmt()) -def test_load_libgmt_fails(monkeypatch): +def test_load_libgmt_fails(): """ Test that GMTCLibNotFoundError is raised when GMT's shared library cannot be found. """ - with monkeypatch.context() as mpatch: - if sys.platform == "win32": - mpatch.setattr(ctypes.util, "find_library", lambda name: "fakegmt.dll") # noqa: ARG005 - mpatch.setattr( - sys, - "platform", - # Pretend to be on macOS if running on Linux, and vice versa - "darwin" if sys.platform == "linux" else "linux", - ) - mpatch.setattr( - subprocess, - "check_output", - lambda cmd, encoding: "libfakegmt.so", # noqa: ARG005 - ) + with ( + mock.patch("ctypes.util.find_library", return_value="fakegmt.dll"), + mock.patch("sys.platform", "darwin" if sys.platform == "linux" else "linux"), + mock.patch("subprocess.check_output", return_value="libfakegmt.so"), + ): with pytest.raises(GMTCLibNotFoundError): check_libgmt(load_libgmt()) @@ -214,42 +206,25 @@ def test_brokenlib_brokenlib_workinglib(self): assert check_libgmt(load_libgmt(lib_fullnames=lib_fullnames)) is None -class TestLibgmtCount: +def test_libgmt_load_counter(): """ - Test that the GMT library is not repeatedly loaded in every session. + Make sure that the GMT library is not loaded in every session. """ - - loaded_libgmt = load_libgmt() # Load the GMT library and reuse it when necessary - counter = 0 # Global counter for how many times ctypes.CDLL is called - - def _mock_ctypes_cdll_return(self, libname): # noqa: ARG002 - """ - Mock ctypes.CDLL to count how many times the function is called. - - If ctypes.CDLL is called, the counter increases by one. - """ - self.counter += 1 # Increase the counter - return self.loaded_libgmt - - def test_libgmt_load_counter(self, monkeypatch): - """ - Make sure that the GMT library is not loaded in every session. - """ - # Monkeypatch the ctypes.CDLL function - monkeypatch.setattr(ctypes, "CDLL", self._mock_ctypes_cdll_return) - - # Create two sessions and check the global counter + loaded_libgmt = load_libgmt() # Load the GMT library and reuse it when necessary. + with mock.patch("ctypes.CDLL", return_value=loaded_libgmt) as mock_cdll: + # Create two sessions and check the call count with Session() as lib: _ = lib with Session() as lib: _ = lib - assert self.counter == 0 # ctypes.CDLL is not called after two sessions. + # ctypes.CDLL is not called after two sessions. + assert mock_cdll.call_count == 0 - # Explicitly calling load_libgmt to make sure the mock function is correct + # Explicitly calling load_libgmt to make sure the mock function is correct. load_libgmt() - assert self.counter == 1 + assert mock_cdll.call_count == 1 load_libgmt() - assert self.counter == 2 + assert mock_cdll.call_count == 2 ############################################################################### From d890e9196efca08c0d90e71f9fa1047ea8b4bd55 Mon Sep 17 00:00:00 2001 From: Dongdong Tian Date: Sat, 28 Dec 2024 22:10:18 +0800 Subject: [PATCH 17/55] Use TODO comments to track deprecations and workarounds (#3722) --- pygmt/clib/conversion.py | 5 ++--- pygmt/clib/session.py | 7 +++++-- pygmt/conftest.py | 2 +- pygmt/datasets/tile_map.py | 1 + pygmt/figure.py | 1 + pygmt/helpers/tempfile.py | 2 ++ pygmt/src/ternary.py | 2 +- pygmt/src/timestamp.py | 2 ++ pygmt/tests/test_accessor.py | 1 + pygmt/tests/test_clib_to_numpy.py | 13 +++++++++---- pygmt/tests/test_clib_virtualfile_from_vectors.py | 2 ++ pygmt/tests/test_clib_virtualfile_in.py | 3 +++ pygmt/tests/test_clib_virtualfiles.py | 2 ++ pygmt/tests/test_datasets_earth_relief.py | 1 + pygmt/tests/test_grdimage.py | 1 + pygmt/tests/test_meca.py | 6 ++++-- pygmt/tests/test_x2sys_cross.py | 4 ++++ 17 files changed, 42 insertions(+), 13 deletions(-) diff --git a/pygmt/clib/conversion.py b/pygmt/clib/conversion.py index 5a1d1cf51b9..c54923705dc 100644 --- a/pygmt/clib/conversion.py +++ b/pygmt/clib/conversion.py @@ -173,12 +173,11 @@ def _to_numpy(data: Any) -> np.ndarray: # The numpy dtype for the result numpy array, but can be None. numpy_dtype = dtypes.get(str(dtype)) + # TODO(pandas>=2.2): Remove the workaround for pandas<2.2. + # # pandas numeric dtypes were converted to np.object_ dtype prior pandas 2.2, and are # converted to suitable NumPy dtypes since pandas 2.2. Refer to the following link # for details: https://pandas.pydata.org/docs/whatsnew/v2.2.0.html#to-numpy-for-numpy-nullable-and-arrow-types-converts-to-suitable-numpy-dtype - # - # Workarounds for pandas < 2.2. Following SPEC 0, pandas 2.1 should be dropped in - # 2025 Q3, so it's likely we can remove the workaround in PyGMT v0.17.0. if ( Version(pd.__version__) < Version("2.2") # pandas < 2.2 only. and hasattr(data, "dtype") # NumPy array or pandas objects only. diff --git a/pygmt/clib/session.py b/pygmt/clib/session.py index ee37c55d59f..52f710ec398 100644 --- a/pygmt/clib/session.py +++ b/pygmt/clib/session.py @@ -384,7 +384,8 @@ def print_func(file_pointer, message): # noqa: ARG001 We'll capture the messages and print them to stderr so that they will show up on the Jupyter notebook. """ - # Have to use try..except due to upstream GMT bug in GMT <= 6.5.0. + # TODO(GMT>6.5.0): Remove the workaround for upstream bug in GMT<=6.5.0. + # Have to use try..except due to upstream GMT bug in GMT<=6.5.0. # See https://github.com/GenericMappingTools/pygmt/issues/3205. try: message = message.decode().strip() @@ -1388,6 +1389,7 @@ 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. @@ -1454,9 +1456,9 @@ def virtualfile_from_vectors( ... print(fout.read().strip()) : N = 3 <1/3> <4/6> <7/9> """ + # TODO(PyGMT>=0.16.0): Remove the "*args" parameter and related codes. # "*args" is added in v0.14.0 for backward-compatibility with the deprecated # syntax of passing multiple vectors as positional arguments. - # Remove it in v0.16.0. if len(args) > 0: msg = ( "Passing multiple arguments to Session.virtualfile_from_vectors is " @@ -1918,6 +1920,7 @@ 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, diff --git a/pygmt/conftest.py b/pygmt/conftest.py index bc896d44732..ee491b7f8bc 100644 --- a/pygmt/conftest.py +++ b/pygmt/conftest.py @@ -5,7 +5,7 @@ import numpy as np from packaging.version import Version -# Keep this until we require numpy to be >=2.0 +# TODO(NumPy>=2.0): Remove the conftest.py file. # Address https://github.com/GenericMappingTools/pygmt/issues/2628. if Version(np.__version__) >= Version("2.0.0.dev0+git20230726"): np.set_printoptions(legacy="1.25") # type: ignore[arg-type] diff --git a/pygmt/datasets/tile_map.py b/pygmt/datasets/tile_map.py index d18bce423f8..caa18dacd84 100644 --- a/pygmt/datasets/tile_map.py +++ b/pygmt/datasets/tile_map.py @@ -166,6 +166,7 @@ def load_tile_map( "wait": wait, "max_retries": max_retries, } + # TODO(contextily>=1.5.0): Remove the check for the 'zoom_adjust' parameter. if zoom_adjust is not None: if Version(contextily.__version__) < Version("1.5.0"): msg = ( diff --git a/pygmt/figure.py b/pygmt/figure.py index c42c9ba69e8..21c9557081e 100644 --- a/pygmt/figure.py +++ b/pygmt/figure.py @@ -248,6 +248,7 @@ def savefig( kwargs.pop("metadata", None) self.psconvert(prefix=prefix, fmt=fmts[ext], crop=crop, **kwargs) + # TODO(GMT>=6.5.0): Remve the workaround for upstream bug in GMT<6.5.0. # Remove the .pgw world file if exists. Not necessary after GMT 6.5.0. # See upstream fix https://github.com/GenericMappingTools/gmt/pull/7865 if ext == "tiff": diff --git a/pygmt/helpers/tempfile.py b/pygmt/helpers/tempfile.py index 191d6bc088c..6d8dfd74fec 100644 --- a/pygmt/helpers/tempfile.py +++ b/pygmt/helpers/tempfile.py @@ -144,6 +144,8 @@ def tempfile_from_geojson(geojson): # https://github.com/geopandas/geopandas/issues/967#issuecomment-842877704 # https://github.com/GenericMappingTools/pygmt/issues/2497 int32_info = np.iinfo(np.int32) + # TODO(GeoPandas>=1.0): Remove the workaround for GeoPandas < 1. + # The default engine is "fiona" in v0.x and "pyogrio" in v1.x. if Version(gpd.__version__).major < 1: # GeoPandas v0.x # The default engine 'fiona' supports the 'schema' parameter. if geojson.index.name is None: diff --git a/pygmt/src/ternary.py b/pygmt/src/ternary.py index 5bc477fab99..633707cf427 100644 --- a/pygmt/src/ternary.py +++ b/pygmt/src/ternary.py @@ -87,7 +87,7 @@ def ternary( if any(v is not None for v in labels): kwargs["L"] = "/".join(str(v) if v is not None else "-" for v in labels) - # Patch for GMT < 6.5.0. + # TODO(GMT>=6.5.0): Remove the patch for upstream bug fixed in GMT 6.5.0. # See https://github.com/GenericMappingTools/pygmt/pull/2138 if Version(__gmt_version__) < Version("6.5.0") and isinstance(data, pd.DataFrame): data = data.to_numpy() diff --git a/pygmt/src/timestamp.py b/pygmt/src/timestamp.py index 3db9ff694d9..13f278b2816 100644 --- a/pygmt/src/timestamp.py +++ b/pygmt/src/timestamp.py @@ -84,6 +84,7 @@ def timestamp( kwdict["U"] += f"{label}" kwdict["U"] += f"+j{justify}" + # TODO(GMT>=6.5.0): Remove the patch for upstream bug fixed in GMT 6.5.0. if Version(__gmt_version__) < Version("6.5.0") and "/" not in str(offset): # Giving a single offset doesn't work in GMT < 6.5.0. # See https://github.com/GenericMappingTools/gmt/issues/7107. @@ -99,6 +100,7 @@ def timestamp( "The given text string will be truncated to 64 characters." ) warnings.warn(message=msg, category=RuntimeWarning, stacklevel=2) + # TODO(GMT>=6.5.0): Remove the workaround for the new '+t' modifier. if Version(__gmt_version__) < Version("6.5.0"): # Workaround for GMT<6.5.0 by overriding the 'timefmt' parameter timefmt = text[:64] diff --git a/pygmt/tests/test_accessor.py b/pygmt/tests/test_accessor.py index 2701d2a0b3a..07ece609b69 100644 --- a/pygmt/tests/test_accessor.py +++ b/pygmt/tests/test_accessor.py @@ -73,6 +73,7 @@ def test_accessor_set_non_boolean(): grid.gmt.gtype = 2 +# TODO(GMT>=6.5.0): Remove the xfail marker for GMT>=6.5.0. @pytest.mark.xfail( condition=sys.platform == "win32" and Version(__gmt_version__) < Version("6.5.0"), reason="Upstream bug fixed in https://github.com/GenericMappingTools/gmt/pull/7573", diff --git a/pygmt/tests/test_clib_to_numpy.py b/pygmt/tests/test_clib_to_numpy.py index 29fc50826ab..c3bc413a0b1 100644 --- a/pygmt/tests/test_clib_to_numpy.py +++ b/pygmt/tests/test_clib_to_numpy.py @@ -54,6 +54,7 @@ def _check_result(result, expected_dtype): @pytest.mark.parametrize( ("data", "expected_dtype"), [ + # TODO(NumPy>=2.0): Remove the if-else statement after NumPy>=2.0. pytest.param( [1, 2, 3], np.int32 @@ -218,9 +219,10 @@ def test_to_numpy_pandas_numeric(dtype, expected_dtype): Test the _to_numpy function with pandas.Series of numeric dtypes. """ data = [1.0, 2.0, 3.0, 4.0, 5.0, 6.0] + # TODO(pandas>=2.2): Remove the workaround for float16 dtype in pandas<2.2. + # float16 needs special handling for pandas < 2.2. + # Example from https://arrow.apache.org/docs/python/generated/pyarrow.float16.html if dtype == "float16[pyarrow]" and Version(pd.__version__) < Version("2.2"): - # float16 needs special handling for pandas < 2.2. - # Example from https://arrow.apache.org/docs/python/generated/pyarrow.float16.html data = np.array(data, dtype=np.float16) series = pd.Series(data, dtype=dtype)[::2] # Not C-contiguous result = _to_numpy(series) @@ -264,9 +266,10 @@ def test_to_numpy_pandas_numeric_with_na(dtype, expected_dtype): dtypes and missing values (NA). """ data = [1.0, 2.0, None, 4.0, 5.0, 6.0] + # TODO(pandas>=2.2): Remove the workaround for float16 dtype in pandas<2.2. + # float16 needs special handling for pandas < 2.2. + # Example from https://arrow.apache.org/docs/python/generated/pyarrow.float16.html if dtype == "float16[pyarrow]" and Version(pd.__version__) < Version("2.2"): - # float16 needs special handling for pandas < 2.2. - # Example from https://arrow.apache.org/docs/python/generated/pyarrow.float16.html data = np.array(data, dtype=np.float16) series = pd.Series(data, dtype=dtype)[::2] # Not C-contiguous assert series.isna().any() @@ -287,6 +290,7 @@ def test_to_numpy_pandas_numeric_with_na(dtype, expected_dtype): "string[pyarrow_numpy]", marks=[ skip_if_no(package="pyarrow"), + # TODO(pandas>=2.1): Remove the skipif marker for pandas<2.1. pytest.mark.skipif( Version(pd.__version__) < Version("2.1"), reason="string[pyarrow_numpy] was added since pandas 2.1", @@ -426,6 +430,7 @@ def test_to_numpy_pyarrow_numeric_with_na(dtype, expected_dtype): "large_utf8", # alias for large_string pytest.param( "string_view", + # TODO(pyarrow>=16): Remove the skipif marker for pyarrow<16. marks=pytest.mark.skipif( Version(pa.__version__) < Version("16"), reason="string_view type was added since pyarrow 16", diff --git a/pygmt/tests/test_clib_virtualfile_from_vectors.py b/pygmt/tests/test_clib_virtualfile_from_vectors.py index b76a9bfe168..234ba01d7cc 100644 --- a/pygmt/tests/test_clib_virtualfile_from_vectors.py +++ b/pygmt/tests/test_clib_virtualfile_from_vectors.py @@ -192,6 +192,8 @@ def test_virtualfile_from_vectors_arraylike(): assert output == expected +# TODO(PyGMT>=0.16.0): Remove this test in PyGMT v0.16.0 in which the "*args" parameter +# will be removed. def test_virtualfile_from_vectors_args(): """ Test the backward compatibility of the deprecated syntax for passing multiple diff --git a/pygmt/tests/test_clib_virtualfile_in.py b/pygmt/tests/test_clib_virtualfile_in.py index aac8e4af772..7b091fc423c 100644 --- a/pygmt/tests/test_clib_virtualfile_in.py +++ b/pygmt/tests/test_clib_virtualfile_in.py @@ -105,6 +105,7 @@ def test_virtualfile_in_fail_non_valid_data(data): ) +# TODO(GMT>6.5.0): Remove the xfail marker for GMT<=6.5.0. @pytest.mark.xfail( condition=Version(__gmt_version__) <= Version("6.5.0"), reason="Upstream bug fixed in https://github.com/GenericMappingTools/gmt/pull/8600", @@ -129,6 +130,8 @@ def test_virtualfile_in_matrix_string_dtype(): # 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. diff --git a/pygmt/tests/test_clib_virtualfiles.py b/pygmt/tests/test_clib_virtualfiles.py index a45a662de71..b25891864f6 100644 --- a/pygmt/tests/test_clib_virtualfiles.py +++ b/pygmt/tests/test_clib_virtualfiles.py @@ -109,6 +109,8 @@ def test_open_virtualfile_bad_direction(): 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. diff --git a/pygmt/tests/test_datasets_earth_relief.py b/pygmt/tests/test_datasets_earth_relief.py index 44af0e4ff98..b0851430907 100644 --- a/pygmt/tests/test_datasets_earth_relief.py +++ b/pygmt/tests/test_datasets_earth_relief.py @@ -192,6 +192,7 @@ def test_earth_relief_15s_default_registration(): npt.assert_allclose(data.max(), -76.5, atol=0.5) +# TODO(GMT X.Y.Z): Upstream bug which is not fixed yet. @pytest.mark.xfail( condition=Version(__gmt_version__) >= Version("6.5.0"), reason="Upstream bug tracked in https://github.com/GenericMappingTools/pygmt/issues/2511", diff --git a/pygmt/tests/test_grdimage.py b/pygmt/tests/test_grdimage.py index 943b3f12ddc..907886eb0b3 100644 --- a/pygmt/tests/test_grdimage.py +++ b/pygmt/tests/test_grdimage.py @@ -256,6 +256,7 @@ def test_grdimage_imgout_fails(grid): fig.grdimage(grid, A="out.png") +# TODO(GMT>6.5.0): Remove the xfail marker for GMT<=6.5.0. @pytest.mark.xfail( condition=Version(__gmt_version__) <= Version("6.5.0"), reason="Upstream bug fixed in https://github.com/GenericMappingTools/gmt/pull/8554", diff --git a/pygmt/tests/test_meca.py b/pygmt/tests/test_meca.py index 2e71cc9665d..424486ac408 100644 --- a/pygmt/tests/test_meca.py +++ b/pygmt/tests/test_meca.py @@ -143,6 +143,7 @@ def test_meca_spec_multiple_focalmecha(inputtype): return fig +# TODO(GMT>=6.5.0): Remove the skipif condition for GMT>=6.5.0. @pytest.mark.mpl_image_compare(filename="test_meca_offset.png") @pytest.mark.parametrize( "inputtype", @@ -201,8 +202,9 @@ def test_meca_offset(inputtype): return fig -# Passing event names via pandas doesn't work for GMT<=6.4, thus marked as -# xfail. See https://github.com/GenericMappingTools/pygmt/issues/2524. +# TODO(GMT>=6.5.0): Remove the skipif marker for GMT>=6.5.0. +# Passing event names via pandas doesn't work for GMT<=6.4. +# See https://github.com/GenericMappingTools/pygmt/issues/2524. @pytest.mark.mpl_image_compare(filename="test_meca_eventname.png") @pytest.mark.parametrize( "inputtype", diff --git a/pygmt/tests/test_x2sys_cross.py b/pygmt/tests/test_x2sys_cross.py index c72cca04420..70f0c3cf003 100644 --- a/pygmt/tests/test_x2sys_cross.py +++ b/pygmt/tests/test_x2sys_cross.py @@ -37,6 +37,7 @@ def fixture_tracks(): return [dataframe.query(expr="z > -20")] # reduce size of dataset +# TODO(GMT>=6.5.0): Remove the xfail marker for the upstream bug fixed in GMT 6.5.0. @pytest.mark.usefixtures("mock_x2sys_home") @pytest.mark.xfail( condition=Version(__gmt_version__) < Version("6.5.0"), @@ -66,6 +67,7 @@ def test_x2sys_cross_input_file_output_file(): npt.assert_allclose(result["i_1"].max(), 82945.9370, rtol=1.0e-4) +# TODO(GMT>=6.5.0): Remove the xfail marker for the upstream bug fixed in GMT 6.5.0. @pytest.mark.usefixtures("mock_x2sys_home") @pytest.mark.xfail( condition=Version(__gmt_version__) < Version("6.5.0"), @@ -244,6 +246,7 @@ def test_x2sys_cross_invalid_tracks_input_type(tracks): x2sys_cross(tracks=[invalid_tracks]) +# TODO(GMT>=6.5.0): Remove the xfail marker for the upstream bug fixed in GMT 6.5.0. @pytest.mark.usefixtures("mock_x2sys_home") @pytest.mark.xfail( condition=Version(__gmt_version__) < Version("6.5.0"), @@ -279,6 +282,7 @@ def test_x2sys_cross_region_interpolation_numpoints(): npt.assert_allclose(output.z_M.mean(), -2896.875915, rtol=1e-4) +# TODO(GMT>=6.5.0): Remove the xfail marker for the upstream bug fixed in GMT 6.5.0. @pytest.mark.usefixtures("mock_x2sys_home") @pytest.mark.xfail( condition=Version(__gmt_version__) < Version("6.5.0"), From 2c0f75f0846c072d8887308ef9b799c7345a4632 Mon Sep 17 00:00:00 2001 From: Dongdong Tian Date: Sat, 28 Dec 2024 22:10:48 +0800 Subject: [PATCH 18/55] Add Figure.vlines for plotting vertical lines (#3726) --- doc/api/index.rst | 1 + pygmt/figure.py | 1 + pygmt/src/__init__.py | 1 + pygmt/src/hlines.py | 2 +- pygmt/src/vlines.py | 132 ++++++++++++++++++ pygmt/tests/baseline/test_vlines_clip.png.dvc | 5 + .../test_vlines_geographic_global.png.dvc | 5 + .../test_vlines_multiple_lines.png.dvc | 5 + .../baseline/test_vlines_one_line.png.dvc | 5 + .../test_vlines_polar_projection.png.dvc | 5 + pygmt/tests/test_vlines.py | 102 ++++++++++++++ 11 files changed, 263 insertions(+), 1 deletion(-) create mode 100644 pygmt/src/vlines.py create mode 100644 pygmt/tests/baseline/test_vlines_clip.png.dvc create mode 100644 pygmt/tests/baseline/test_vlines_geographic_global.png.dvc create mode 100644 pygmt/tests/baseline/test_vlines_multiple_lines.png.dvc create mode 100644 pygmt/tests/baseline/test_vlines_one_line.png.dvc create mode 100644 pygmt/tests/baseline/test_vlines_polar_projection.png.dvc create mode 100644 pygmt/tests/test_vlines.py diff --git a/doc/api/index.rst b/doc/api/index.rst index b64e3de4bc4..09d20fa1415 100644 --- a/doc/api/index.rst +++ b/doc/api/index.rst @@ -36,6 +36,7 @@ Plotting map elements Figure.solar Figure.text Figure.timestamp + Figure.vlines Plotting tabular data ~~~~~~~~~~~~~~~~~~~~~ diff --git a/pygmt/figure.py b/pygmt/figure.py index 21c9557081e..1d051c0dacc 100644 --- a/pygmt/figure.py +++ b/pygmt/figure.py @@ -437,6 +437,7 @@ def _repr_html_(self) -> str: tilemap, timestamp, velo, + vlines, wiggle, ) diff --git a/pygmt/src/__init__.py b/pygmt/src/__init__.py index 7fa068f4505..8905124f917 100644 --- a/pygmt/src/__init__.py +++ b/pygmt/src/__init__.py @@ -57,6 +57,7 @@ from pygmt.src.timestamp import timestamp from pygmt.src.triangulate import triangulate from pygmt.src.velo import velo +from pygmt.src.vlines import vlines from pygmt.src.which import which from pygmt.src.wiggle import wiggle from pygmt.src.x2sys_cross import x2sys_cross diff --git a/pygmt/src/hlines.py b/pygmt/src/hlines.py index 8871bd3a825..b277358d981 100644 --- a/pygmt/src/hlines.py +++ b/pygmt/src/hlines.py @@ -48,7 +48,7 @@ def hlines( Y-coordinates to plot the lines. It can be a single value (for a single line) or a sequence of values (for multiple lines). xmin/xmax - X-coordinates of the start/end point of the line(s). If ``None``, defaults to + X-coordinates of the start/end point(s) of the line(s). If ``None``, defaults to the X-limits of the current plot. ``xmin`` and ``xmax`` can be either a single value or a sequence of values. If a single value is provided, it is applied to all lines. If a sequence is provided, the length of ``xmin`` and ``xmax`` must diff --git a/pygmt/src/vlines.py b/pygmt/src/vlines.py new file mode 100644 index 00000000000..2483df99f27 --- /dev/null +++ b/pygmt/src/vlines.py @@ -0,0 +1,132 @@ +""" +vlines - Plot vertical lines. +""" + +from collections.abc import Sequence + +import numpy as np +from pygmt.exceptions import GMTInvalidInput + +__doctest_skip__ = ["vlines"] + + +def vlines( + self, + x: float | Sequence[float], + ymin: float | Sequence[float] | None = None, + ymax: float | Sequence[float] | None = None, + pen: str | None = None, + label: str | None = None, + no_clip: bool = False, + perspective: str | bool | None = None, +): + """ + Plot one or multiple vertical line(s). + + This method is a high-level wrapper around :meth:`pygmt.Figure.plot` that focuses on + plotting vertical lines at X-coordinates specified by the ``x`` parameter. The ``x`` + parameter can be a single value (for a single vertical line) or a sequence of values + (for multiple vertical lines). + + By default, the Y-coordinates of the start and end points of the lines are set to be + the Y-limits of the current plot, but this can be overridden by specifying the + ``ymin`` and ``ymax`` parameters. ``ymin`` and ``ymax`` can be either a single value + or a sequence of values. If a single value is provided, it is applied to all lines. + If a sequence is provided, the length of ``ymin`` and ``ymax`` must match the length + of ``x``. + + The term "vertical" lines can be interpreted differently in different coordinate + systems: + + - **Cartesian** coordinate system: lines are plotted as straight lines. + - **Polar** projection: lines are plotted as straight lines along radius. + - **Geographic** projection: lines are plotted as meridians along constant + longitude. + + Parameters + ---------- + x + X-coordinates to plot the lines. It can be a single value (for a single line) + or a sequence of values (for multiple lines). + ymin/ymax + Y-coordinates of the start/end point(s) of the line(s). If ``None``, defaults to + the Y-limits of the current plot. ``ymin`` and ``ymax`` can either be a single + value or a sequence of values. If a single value is provided, it is applied to + all lines. If a sequence is provided, the length of ``ymin`` and ``ymax`` must + match the length of ``x``. + pen + Pen attributes for the line(s), in the format of *width,color,style*. + label + Label for the line(s), to be displayed in the legend. + no_clip + If ``True``, do not clip lines outside the plot region. Only makes sense in the + Cartesian coordinate system. + perspective + Select perspective view and set the azimuth and elevation angle of the + viewpoint. Refer to :meth:`pygmt.Figure.plot` for details. + + Examples + -------- + >>> import pygmt + >>> fig = pygmt.Figure() + >>> fig.basemap(region=[0, 10, 0, 10], projection="X10c/10c", frame=True) + >>> fig.vlines(x=1, pen="1p,black", label="Line at x=1") + >>> fig.vlines(x=2, ymin=2, ymax=8, pen="1p,red,-", label="Line at x=2") + >>> fig.vlines(x=[3, 4], ymin=3, ymax=7, pen="1p,black,.", label="Lines at x=3,4") + >>> fig.vlines(x=[5, 6], ymin=4, ymax=9, pen="1p,red", label="Lines at x=5,6") + >>> fig.vlines( + ... x=[7, 8], ymin=[0, 1], ymax=[7, 8], pen="1p,blue", label="Lines at x=7,8" + ... ) + >>> fig.legend() + >>> fig.show() + """ + self._preprocess() + + # Determine the y limits from the current plot region if not specified. + if ymin is None or ymax is None: + ylimits = self.region[2:] + if ymin is None: + ymin = ylimits[0] + if ymax is None: + ymax = ylimits[1] + + # Ensure x/ymin/ymax are 1-D arrays. + _x = np.atleast_1d(x) + _ymin = np.atleast_1d(ymin) + _ymax = np.atleast_1d(ymax) + + nlines = len(_x) # Number of lines to plot. + + # Check if ymin/ymax are scalars or have the expected length. + if _ymin.size not in {1, nlines} or _ymax.size not in {1, nlines}: + msg = ( + f"'ymin' and 'ymax' are expected to be scalars or have lengths '{nlines}', " + f"but lengths '{_ymin.size}' and '{_ymax.size}' are given." + ) + raise GMTInvalidInput(msg) + + # Repeat ymin/ymax to match the length of x if they are scalars. + if nlines != 1: + if _ymin.size == 1: + _ymin = np.repeat(_ymin, nlines) + if _ymax.size == 1: + _ymax = np.repeat(_ymax, nlines) + + # Call the Figure.plot method to plot the lines. + for i in range(nlines): + # Special handling for label. + # 1. Only specify a label when plotting the first line. + # 2. The -l option can accept comma-separated labels for labeling multiple lines + # with auto-coloring enabled. We don't need this feature here, so we need to + # replace comma with \054 if the label contains commas. + _label = label.replace(",", "\\054") if label and i == 0 else None + + self.plot( + x=[_x[i], _x[i]], + y=[_ymin[i], _ymax[i]], + pen=pen, + label=_label, + no_clip=no_clip, + perspective=perspective, + straight_line="y", + ) diff --git a/pygmt/tests/baseline/test_vlines_clip.png.dvc b/pygmt/tests/baseline/test_vlines_clip.png.dvc new file mode 100644 index 00000000000..f20f77ae249 --- /dev/null +++ b/pygmt/tests/baseline/test_vlines_clip.png.dvc @@ -0,0 +1,5 @@ +outs: +- md5: 4eb9c7fd7e3a803dcc3cde1409ad7fa7 + size: 7361 + hash: md5 + path: test_vlines_clip.png diff --git a/pygmt/tests/baseline/test_vlines_geographic_global.png.dvc b/pygmt/tests/baseline/test_vlines_geographic_global.png.dvc new file mode 100644 index 00000000000..d09fa8f8d82 --- /dev/null +++ b/pygmt/tests/baseline/test_vlines_geographic_global.png.dvc @@ -0,0 +1,5 @@ +outs: +- md5: 3fb4a271c670e4cbe647838b6fee5a8c + size: 67128 + hash: md5 + path: test_vlines_geographic_global.png diff --git a/pygmt/tests/baseline/test_vlines_multiple_lines.png.dvc b/pygmt/tests/baseline/test_vlines_multiple_lines.png.dvc new file mode 100644 index 00000000000..da9a4bf8aed --- /dev/null +++ b/pygmt/tests/baseline/test_vlines_multiple_lines.png.dvc @@ -0,0 +1,5 @@ +outs: +- md5: 499b2d08832247673f208b1c0a282c4c + size: 13874 + hash: md5 + path: test_vlines_multiple_lines.png diff --git a/pygmt/tests/baseline/test_vlines_one_line.png.dvc b/pygmt/tests/baseline/test_vlines_one_line.png.dvc new file mode 100644 index 00000000000..efc2df680b3 --- /dev/null +++ b/pygmt/tests/baseline/test_vlines_one_line.png.dvc @@ -0,0 +1,5 @@ +outs: +- md5: 2cd30ad55fc660123c67e6a684a5ea21 + size: 13589 + hash: md5 + path: test_vlines_one_line.png diff --git a/pygmt/tests/baseline/test_vlines_polar_projection.png.dvc b/pygmt/tests/baseline/test_vlines_polar_projection.png.dvc new file mode 100644 index 00000000000..1252a2d0455 --- /dev/null +++ b/pygmt/tests/baseline/test_vlines_polar_projection.png.dvc @@ -0,0 +1,5 @@ +outs: +- md5: 1981df3bd9c57cd975b6e74946496175 + size: 44621 + hash: md5 + path: test_vlines_polar_projection.png diff --git a/pygmt/tests/test_vlines.py b/pygmt/tests/test_vlines.py new file mode 100644 index 00000000000..21aff1c06d5 --- /dev/null +++ b/pygmt/tests/test_vlines.py @@ -0,0 +1,102 @@ +""" +Tests for Figure.vlines. +""" + +import pytest +from pygmt import Figure +from pygmt.exceptions import GMTInvalidInput + + +@pytest.mark.mpl_image_compare +def test_vlines_one_line(): + """ + Plot one vertical line. + """ + fig = Figure() + fig.basemap(region=[0, 10, 0, 10], projection="X10c/10c", frame=True) + fig.vlines(1) + fig.vlines(2, ymin=1) + fig.vlines(3, ymax=9) + fig.vlines(4, ymin=3, ymax=8) + fig.vlines(5, ymin=4, ymax=8, pen="1p,blue", label="Line at x=5") + fig.vlines(6, ymin=5, ymax=7, pen="1p,red", label="Line at x=6") + fig.legend() + return fig + + +@pytest.mark.mpl_image_compare +def test_vlines_multiple_lines(): + """ + Plot multiple vertical lines. + """ + fig = Figure() + fig.basemap(region=[0, 16, 0, 10], projection="X10c/10c", frame=True) + fig.vlines([1, 2]) + fig.vlines([3, 4, 5], ymin=[1, 2, 3]) + fig.vlines([6, 7, 8], ymax=[7, 8, 9]) + fig.vlines([9, 10], ymin=[1, 2], ymax=[9, 10]) + fig.vlines([11, 12], ymin=1, ymax=8, pen="1p,blue", label="Lines at x=11,12") + fig.vlines( + [13, 14], ymin=[3, 4], ymax=[7, 8], pen="1p,red", label="Lines at x=13,14" + ) + fig.legend() + return fig + + +@pytest.mark.mpl_image_compare +def test_vlines_clip(): + """ + Plot vertical lines with clipping or not. + """ + fig = Figure() + fig.basemap(region=[0, 10, 0, 4], projection="X10c/4c", frame=True) + fig.vlines(1, ymin=-1, ymax=5) + fig.vlines(2, ymin=-1, ymax=5, no_clip=True) + return fig + + +@pytest.mark.mpl_image_compare +def test_vlines_geographic_global(): + """ + Plot vertical lines in geographic coordinates. + """ + fig = Figure() + fig.basemap(region=[-180, 180, -90, 90], projection="R15c", frame="a30g30") + fig.vlines(30, pen="1p") + fig.vlines(90, ymin=-60, pen="1p,blue") + fig.vlines(-90, ymax=60, pen="1p,blue") + fig.vlines(120, ymin=-60, ymax=60, pen="1p,blue") + return fig + + +@pytest.mark.mpl_image_compare +def test_vlines_polar_projection(): + """ + Plot vertical lines in polar projection. + """ + fig = Figure() + fig.basemap(region=[0, 360, 0, 1], projection="P15c", frame=True) + fig.vlines(0, pen="1p") + fig.vlines(30, ymin=0, ymax=1, pen="1p") + fig.vlines(60, ymin=0.5, pen="1p") + fig.vlines(90, ymax=0.5, pen="1p") + fig.vlines(120, ymin=0.25, ymax=0.75, pen="1p") + return fig + + +def test_vlines_invalid_input(): + """ + Test invalid input for vlines. + """ + fig = Figure() + fig.basemap(region=[0, 10, 0, 6], projection="X10c/6c", frame=True) + with pytest.raises(GMTInvalidInput): + fig.vlines(1, ymin=2, ymax=[3, 4]) + with pytest.raises(GMTInvalidInput): + fig.vlines(1, ymin=[2, 3], ymax=4) + with pytest.raises(GMTInvalidInput): + fig.vlines(1, ymin=[2, 3], ymax=[4, 5]) + with pytest.raises(GMTInvalidInput): + fig.vlines([1, 2], ymin=[2, 3, 4], ymax=3) + with pytest.raises(GMTInvalidInput): + fig.vlines([1, 2], ymin=[2, 3], ymax=[4, 5, 6]) From 230ff31b4a6dd9770a226e1d23d7e3cbf3a3a7ec Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Yvonne=20Fr=C3=B6hlich?= <94163266+yvonnefroehlich@users.noreply.github.com> Date: Sat, 28 Dec 2024 15:15:40 +0100 Subject: [PATCH 19/55] Remote datasets: Add "uncertainty" parameter to "load_earth_free_air_anomaly" to load the "free-air anomaly uncertainty" dataset (#3727) --- pygmt/datasets/earth_free_air_anomaly.py | 93 ++++++++++--------- pygmt/datasets/load_remote_dataset.py | 18 ++++ pygmt/helpers/caching.py | 2 + .../test_datasets_earth_free_air_anomaly.py | 51 ++++++++++ 4 files changed, 122 insertions(+), 42 deletions(-) diff --git a/pygmt/datasets/earth_free_air_anomaly.py b/pygmt/datasets/earth_free_air_anomaly.py index da48977d688..df1154f2aaa 100644 --- a/pygmt/datasets/earth_free_air_anomaly.py +++ b/pygmt/datasets/earth_free_air_anomaly.py @@ -1,6 +1,6 @@ """ -Function to download the IGPP Earth free-air anomaly dataset from the GMT data server, -and load as :class:`xarray.DataArray`. +Function to download the IGPP Earth free-air anomaly and uncertainty datasets from +the GMT data server, and load as :class:`xarray.DataArray`. The grids are available in various resolutions. """ @@ -20,36 +20,43 @@ def load_earth_free_air_anomaly( ] = "01d", region: Sequence[float] | str | None = None, registration: Literal["gridline", "pixel", None] = None, + uncertainty: bool = False, ) -> xr.DataArray: r""" - Load the IGPP Earth free-air anomaly dataset in various resolutions. + Load the IGPP Earth free-air anomaly and uncertainty datasets in various + resolutions. - .. figure:: https://www.generic-mapping-tools.org/remote-datasets/_images/GMT_earth_faa.jpg - :width: 80 % - :align: center + .. list-table:: + :widths: 50 50 + :header-rows: 1 - IGPP Earth free-air anomaly dataset. + * - IGPP Earth free-Air anomaly + - IGPP Earth free-Air anomaly uncertainty + * - .. figure:: https://www.generic-mapping-tools.org/remote-datasets/_images/GMT_earth_faa.jpg + - .. figure:: https://www.generic-mapping-tools.org/remote-datasets/_images/GMT_earth_faaerror.jpg - The grids are downloaded to a user data directory - (usually ``~/.gmt/server/earth/earth_faa/``) the first time you invoke - this function. Afterwards, it will load the grid from the data directory. - So you'll need an internet connection the first time around. + The grids are downloaded to a user data directory (usually + ``~/.gmt/server/earth/earth_faa/`` or ``~/.gmt/server/earth/earth_faaerror/``) the + first time you invoke this function. Afterwards, it will load the grid from data + directory. So you'll need an internet connection the first time around. These grids can also be accessed by passing in the file name - **@earth_faa**\_\ *res*\[_\ *reg*] to any grid processing function or - plotting method. *res* is the grid resolution (see below), and *reg* is - the grid registration type (**p** for pixel registration or **g** for - gridline registration). - - The default color palette table (CPT) for this dataset is *@earth_faa.cpt*. - It's implicitly used when passing in the file name of the dataset to any - grid plotting method if no CPT is explicitly specified. When the dataset - is loaded and plotted as an :class:`xarray.DataArray` object, the default - CPT is ignored, and GMT's default CPT (*turbo*) is used. To use the - dataset-specific CPT, you need to explicitly set ``cmap="@earth_faa.cpt"``. - - Refer to :gmt-datasets:`earth-faa.html` for more details about available - datasets, including version information and references. + **@earth_faa_type**\_\ *res*\[_\ *reg*] to any grid processing function or + plotting method. *earth_faa_type* is the GMT name for the dataset. The available + options are **earth_faa** and **earth_faaerror**. *res* is the grid resolution (see + below), and *reg* is the grid registration type (**p** for pixel registration or + **g** for gridline registration). + + The default color palette tables (CPTs) for these datasets are *@earth_faa.cpt* and + *@earth_faaerror.cpt*. The dataset-specific CPT is implicitly used when passing in + the file name of the dataset to any grid plotting method if no CPT is explicitly + specified. When the dataset is loaded and plotted as an :class:`xarray.DataArray` + object, the default CPT is ignored, and GMT's default CPT (*turbo*) is used. To use + the dataset-specific CPT, you need to explicitly set ``cmap="@earth_faa.cpt"`` or + ``cmap="@earth_faaerror.cpt"``. + + Refer to :gmt-datasets:`earth-faa.html` and :gmt-datasets:`earth-faaerror.html` for + more details about available datasets, including version information and references. Parameters ---------- @@ -62,26 +69,27 @@ def load_earth_free_air_anomaly( higher than 5 arc-minutes (i.e., ``"05m"``). registration Grid registration type. Either ``"pixel"`` for pixel registration or - ``"gridline"`` for gridline registration. Default is ``None``, means - ``"gridline"`` for all resolutions except ``"01m"`` which is - ``"pixel"`` only. + ``"gridline"`` for gridline registration. Default is ``None`` which means + ``"gridline"`` for all resolutions except ``"01m"`` which is ``"pixel"`` only. + uncertainty + By default, the Earth free-air anomaly values are returned. Set to ``True`` to + return the related uncertainties instead. Returns ------- grid - The Earth free-air anomaly grid. Coordinates are latitude and - longitude in degrees. Units are in mGal. + The Earth free-air anomaly (uncertainty) grid. Coordinates are latitude and + longitude in degrees. Values and uncertainties are in mGal. Note ---- The registration and coordinate system type of the returned - :class:`xarray.DataArray` grid can be accessed via the GMT accessors - (i.e., ``grid.gmt.registration`` and ``grid.gmt.gtype`` respectively). - However, these properties may be lost after specific grid operations (such - as slicing) and will need to be manually set before passing the grid to any - PyGMT data processing or plotting functions. Refer to - :class:`pygmt.GMTDataArrayAccessor` for detailed explanations and - workarounds. + :class:`xarray.DataArray` grid can be accessed via the GMT accessors (i.e., + ``grid.gmt.registration`` and ``grid.gmt.gtype`` respectively). However, these + properties may be lost after specific grid operations (such as slicing) and will + need to be manually set before passing the grid to any PyGMT data processing or + plotting functions. Refer to :class:`pygmt.GMTDataArrayAccessor` for detailed + explanations and workarounds. Examples -------- @@ -89,18 +97,19 @@ def load_earth_free_air_anomaly( >>> from pygmt.datasets import load_earth_free_air_anomaly >>> # load the default grid (gridline-registered 1 arc-degree grid) >>> grid = load_earth_free_air_anomaly() + >>> # load the uncertainties related to the default grid + >>> grid = load_earth_free_air_anomaly(uncertainty=True) >>> # load the 30 arc-minutes grid with "gridline" registration >>> grid = load_earth_free_air_anomaly(resolution="30m", registration="gridline") >>> # load high-resolution (5 arc-minutes) grid for a specific region >>> grid = load_earth_free_air_anomaly( - ... resolution="05m", - ... region=[120, 160, 30, 60], - ... registration="gridline", + ... resolution="05m", region=[120, 160, 30, 60], registration="gridline" ... ) """ + prefix = "earth_faaerror" if uncertainty is True else "earth_faa" grid = _load_remote_dataset( - name="earth_faa", - prefix="earth_faa", + name=prefix, + prefix=prefix, resolution=resolution, region=region, registration=registration, diff --git a/pygmt/datasets/load_remote_dataset.py b/pygmt/datasets/load_remote_dataset.py index 3949a2e6f95..da21cc7ae94 100644 --- a/pygmt/datasets/load_remote_dataset.py +++ b/pygmt/datasets/load_remote_dataset.py @@ -128,6 +128,24 @@ class GMTRemoteDataset(NamedTuple): "01m": Resolution("01m", registrations=["pixel"], tiled=True), }, ), + "earth_faaerror": GMTRemoteDataset( + description="IGPP Earth free-air anomaly errors", + units="mGal", + extra_attributes={"horizontal_datum": "WGS84"}, + resolutions={ + "01d": Resolution("01d"), + "30m": Resolution("30m"), + "20m": Resolution("20m"), + "15m": Resolution("15m"), + "10m": Resolution("10m"), + "06m": Resolution("06m"), + "05m": Resolution("05m", tiled=True), + "04m": Resolution("04m", tiled=True), + "03m": Resolution("03m", tiled=True), + "02m": Resolution("02m", tiled=True), + "01m": Resolution("01m", registrations=["pixel"], tiled=True), + }, + ), "earth_gebco": GMTRemoteDataset( description="GEBCO Earth relief", units="meters", diff --git a/pygmt/helpers/caching.py b/pygmt/helpers/caching.py index d0bfd8fb46e..0175839e97d 100644 --- a/pygmt/helpers/caching.py +++ b/pygmt/helpers/caching.py @@ -16,6 +16,7 @@ def cache_data(): "@earth_day_01d", "@earth_dist_01d", "@earth_faa_01d_g", + "@earth_faaerror_01d_g", "@earth_gebco_01d_g", "@earth_gebcosi_01d_g", "@earth_gebcosi_15m_p", @@ -51,6 +52,7 @@ def cache_data(): "@N30E090.earth_age_01m_g.nc", "@N00W030.earth_dist_01m_g.nc", "@N00W030.earth_faa_01m_p.nc", + "@N00W030.earth_faaerror_01m_p.nc", "@N00W030.earth_geoid_01m_g.nc", "@S30W060.earth_mag_02m_p.nc", "@S30W120.earth_mag4km_02m_p.nc", diff --git a/pygmt/tests/test_datasets_earth_free_air_anomaly.py b/pygmt/tests/test_datasets_earth_free_air_anomaly.py index 517a1bb9b89..7ce5fc2662c 100644 --- a/pygmt/tests/test_datasets_earth_free_air_anomaly.py +++ b/pygmt/tests/test_datasets_earth_free_air_anomaly.py @@ -52,3 +52,54 @@ def test_earth_faa_01m_default_registration(): npt.assert_allclose(data.coords["lon"].data.max(), -9.00833333) npt.assert_allclose(data.min(), -49.225, atol=0.025) npt.assert_allclose(data.max(), 115.0, atol=0.025) + + +def test_earth_faaerror_01d(): + """ + Test some properties of the free air anomaly error 01d data. + """ + data = load_earth_free_air_anomaly(resolution="01d", uncertainty=True) + assert data.name == "z" + assert data.attrs["long_name"] == "faaerror (mGal)" + assert data.attrs["description"] == "IGPP Earth free-air anomaly errors" + assert data.attrs["units"] == "mGal" + assert data.attrs["horizontal_datum"] == "WGS84" + assert data.shape == (181, 361) + assert data.gmt.registration == 0 + npt.assert_allclose(data.lat, np.arange(-90, 91, 1)) + npt.assert_allclose(data.lon, np.arange(-180, 181, 1)) + npt.assert_allclose(data.min(), 0.0, atol=0.04) + npt.assert_allclose(data.max(), 49.16, atol=0.04) + + +def test_earth_faaerror_01d_with_region(): + """ + Test loading low-resolution earth free air anomaly error with 'region'. + """ + data = load_earth_free_air_anomaly( + resolution="01d", region=[-10, 10, -5, 5], uncertainty=True + ) + assert data.shape == (11, 21) + assert data.gmt.registration == 0 + npt.assert_allclose(data.lat, np.arange(-5, 6, 1)) + npt.assert_allclose(data.lon, np.arange(-10, 11, 1)) + npt.assert_allclose(data.min(), 0.72, atol=0.04) + npt.assert_allclose(data.max(), 21.04, atol=0.04) + + +def test_earth_faaerror_01m_default_registration(): + """ + Test that the grid returned by default for the 1 arc-minute resolution has a "pixel" + registration. + """ + data = load_earth_free_air_anomaly( + resolution="01m", region=[-10, -9, 3, 5], uncertainty=True + ) + assert data.shape == (120, 60) + assert data.gmt.registration == 1 + npt.assert_allclose(data.coords["lat"].data.min(), 3.008333333) + npt.assert_allclose(data.coords["lat"].data.max(), 4.991666666) + npt.assert_allclose(data.coords["lon"].data.min(), -9.99166666) + npt.assert_allclose(data.coords["lon"].data.max(), -9.00833333) + npt.assert_allclose(data.min(), 0.40, atol=0.04) + npt.assert_allclose(data.max(), 13.36, atol=0.04) From 24a79f263065e936560351fc5b7ddb8b31283db0 Mon Sep 17 00:00:00 2001 From: Dongdong Tian Date: Sat, 28 Dec 2024 22:33:28 +0800 Subject: [PATCH 20/55] Fix a typo in pygmt/datasets/earth_mean_dynamic_topography.py --- pygmt/datasets/earth_mean_dynamic_topography.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/pygmt/datasets/earth_mean_dynamic_topography.py b/pygmt/datasets/earth_mean_dynamic_topography.py index 17e9ad860a0..3e7e947043b 100644 --- a/pygmt/datasets/earth_mean_dynamic_topography.py +++ b/pygmt/datasets/earth_mean_dynamic_topography.py @@ -84,8 +84,8 @@ def load_earth_mean_dynamic_topography( >>> grid = load_earth_mean_dynamic_topography() >>> # load the 30 arc-minutes grid with "gridline" registration >>> grid = load_earth_mean_dynamic_topography( - resolution="30m", registration="gridline" - ...) + ... resolution="30m", registration="gridline" + ... ) >>> # load high-resolution (5 arc-minutes) grid for a specific region >>> grid = load_earth_mean_dynamic_topography( ... resolution="05m", From 7edff1d50136862af833f793407dc2a233c332b4 Mon Sep 17 00:00:00 2001 From: Dongdong Tian Date: Sat, 28 Dec 2024 22:38:50 +0800 Subject: [PATCH 21/55] Fix a doctest in pygmt/datasets/earth_mean_dynamic_topography.py --- pygmt/datasets/earth_mean_dynamic_topography.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/pygmt/datasets/earth_mean_dynamic_topography.py b/pygmt/datasets/earth_mean_dynamic_topography.py index 3e7e947043b..4ca50e476be 100644 --- a/pygmt/datasets/earth_mean_dynamic_topography.py +++ b/pygmt/datasets/earth_mean_dynamic_topography.py @@ -86,9 +86,9 @@ def load_earth_mean_dynamic_topography( >>> grid = load_earth_mean_dynamic_topography( ... resolution="30m", registration="gridline" ... ) - >>> # load high-resolution (5 arc-minutes) grid for a specific region + >>> # load high-resolution (7 arc-minutes) grid for a specific region >>> grid = load_earth_mean_dynamic_topography( - ... resolution="05m", + ... resolution="07m", ... region=[120, 160, 30, 60], ... registration="gridline", ... ) From 5babf22a4a8720726986e6fd9de43d4e9f2cc3b7 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Yvonne=20Fr=C3=B6hlich?= <94163266+yvonnefroehlich@users.noreply.github.com> Date: Sun, 29 Dec 2024 06:15:51 +0100 Subject: [PATCH 22/55] DOC: Typo fixes / formulation improvements (#3729) --- pygmt/datasets/earth_free_air_anomaly.py | 5 +++-- pygmt/datasets/earth_magnetic_anomaly.py | 6 +++--- pygmt/datasets/earth_relief.py | 6 +++--- pygmt/datasets/earth_vertical_gravity_gradient.py | 6 +++--- pygmt/datasets/mars_relief.py | 2 +- pygmt/datasets/mercury_relief.py | 2 +- pygmt/datasets/moon_relief.py | 2 +- pygmt/datasets/pluto_relief.py | 2 +- 8 files changed, 16 insertions(+), 15 deletions(-) diff --git a/pygmt/datasets/earth_free_air_anomaly.py b/pygmt/datasets/earth_free_air_anomaly.py index df1154f2aaa..190f218d858 100644 --- a/pygmt/datasets/earth_free_air_anomaly.py +++ b/pygmt/datasets/earth_free_air_anomaly.py @@ -69,8 +69,9 @@ def load_earth_free_air_anomaly( higher than 5 arc-minutes (i.e., ``"05m"``). registration Grid registration type. Either ``"pixel"`` for pixel registration or - ``"gridline"`` for gridline registration. Default is ``None`` which means - ``"gridline"`` for all resolutions except ``"01m"`` which is ``"pixel"`` only. + ``"gridline"`` for gridline registration. Default is ``None``, which means + ``"gridline"`` for all resolutions except ``"01m"`` which is ``"pixel"`` + only. uncertainty By default, the Earth free-air anomaly values are returned. Set to ``True`` to return the related uncertainties instead. diff --git a/pygmt/datasets/earth_magnetic_anomaly.py b/pygmt/datasets/earth_magnetic_anomaly.py index 70ffcf54248..463f7cf93b9 100644 --- a/pygmt/datasets/earth_magnetic_anomaly.py +++ b/pygmt/datasets/earth_magnetic_anomaly.py @@ -76,10 +76,10 @@ def load_earth_magnetic_anomaly( higher than 5 arc-minutes (i.e., ``"05m"``). registration Grid registration type. Either ``"pixel"`` for pixel registration or - ``"gridline"`` for gridline registration. Default is ``None``, means + ``"gridline"`` for gridline registration. Default is ``None``, which means ``"gridline"`` for all resolutions except ``"02m"`` for - ``data_source="emag2"`` or ``data_source="emag2_4km"``, which are - ``"pixel"`` only. + ``data_source="emag2"`` or ``data_source="emag2_4km"``, which are ``"pixel"`` + only. data_source Select the source of the magnetic anomaly data. Available options are: diff --git a/pygmt/datasets/earth_relief.py b/pygmt/datasets/earth_relief.py index 3ff03613491..f0090ca3bd2 100644 --- a/pygmt/datasets/earth_relief.py +++ b/pygmt/datasets/earth_relief.py @@ -83,9 +83,9 @@ def load_earth_relief( higher than 5 arc-minutes (i.e., ``"05m"``). registration Grid registration type. Either ``"pixel"`` for pixel registration or - ``"gridline"`` for gridline registration. Default is ``None``, means - ``"gridline"`` for all resolutions except ``"15s"`` which is - ``"pixel"`` only. + ``"gridline"`` for gridline registration. Default is ``None``, which means + ``"gridline"`` for all resolutions except ``"15s"`` which is ``"pixel"`` + only. data_source Select the source for the Earth relief data. Available options are: diff --git a/pygmt/datasets/earth_vertical_gravity_gradient.py b/pygmt/datasets/earth_vertical_gravity_gradient.py index 2ebba4563bc..bcf00099fc2 100644 --- a/pygmt/datasets/earth_vertical_gravity_gradient.py +++ b/pygmt/datasets/earth_vertical_gravity_gradient.py @@ -62,9 +62,9 @@ def load_earth_vertical_gravity_gradient( higher than 5 arc-minutes (i.e., ``"05m"``). registration Grid registration type. Either ``"pixel"`` for pixel registration or - ``"gridline"`` for gridline registration. Default is ``None``, means - ``"gridline"`` for all resolutions except ``"01m"`` which is - ``"pixel"`` only. + ``"gridline"`` for gridline registration. Default is ``None``, which means + ``"gridline"`` for all resolutions except ``"01m"`` which is ``"pixel"`` + only. Returns ------- diff --git a/pygmt/datasets/mars_relief.py b/pygmt/datasets/mars_relief.py index edfd8535210..e04f048d42f 100644 --- a/pygmt/datasets/mars_relief.py +++ b/pygmt/datasets/mars_relief.py @@ -75,7 +75,7 @@ def load_mars_relief( higher than 5 arc-minutes (i.e., ``"05m"``). registration Grid registration type. Either ``"pixel"`` for pixel registration or - ``"gridline"`` for gridline registration. Default is ``None``, means + ``"gridline"`` for gridline registration. Default is ``None``, which means ``"gridline"`` for all resolutions except for ``"12s"`` which is ``"pixel"`` only. diff --git a/pygmt/datasets/mercury_relief.py b/pygmt/datasets/mercury_relief.py index 398ae65dd4d..06da8194e4a 100644 --- a/pygmt/datasets/mercury_relief.py +++ b/pygmt/datasets/mercury_relief.py @@ -73,7 +73,7 @@ def load_mercury_relief( higher than 5 arc-minutes (i.e., ``"05m"``). registration Grid registration type. Either ``"pixel"`` for pixel registration or - ``"gridline"`` for gridline registration. Default is ``None``, means + ``"gridline"`` for gridline registration. Default is ``None``, which means ``"gridline"`` for all resolutions except for ``"56s"`` which is ``"pixel"`` only. diff --git a/pygmt/datasets/moon_relief.py b/pygmt/datasets/moon_relief.py index add30b0f051..66817f42d08 100644 --- a/pygmt/datasets/moon_relief.py +++ b/pygmt/datasets/moon_relief.py @@ -75,7 +75,7 @@ def load_moon_relief( higher than 5 arc-minutes (i.e., ``"05m"``). registration Grid registration type. Either ``"pixel"`` for pixel registration or - ``"gridline"`` for gridline registration. Default is ``None``, means + ``"gridline"`` for gridline registration. Default is ``None``, which means ``"gridline"`` for all resolutions except for ``"14s"`` which is ``"pixel"`` only. diff --git a/pygmt/datasets/pluto_relief.py b/pygmt/datasets/pluto_relief.py index 224e89beb10..9a9998d228a 100644 --- a/pygmt/datasets/pluto_relief.py +++ b/pygmt/datasets/pluto_relief.py @@ -73,7 +73,7 @@ def load_pluto_relief( higher than 5 arc-minutes (i.e., ``"05m"``). registration Grid registration type. Either ``"pixel"`` for pixel registration or - ``"gridline"`` for gridline registration. Default is ``None``, means + ``"gridline"`` for gridline registration. Default is ``None``, which means ``"gridline"`` for all resolutions except for ``"52s"`` which is ``"pixel"`` only. From 1256d1506291a03aedf838ca243a049b8511e083 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Yvonne=20Fr=C3=B6hlich?= <94163266+yvonnefroehlich@users.noreply.github.com> Date: Sun, 29 Dec 2024 07:00:11 +0100 Subject: [PATCH 23/55] Figure.meca: Improve documentation (#3517) --- pygmt/src/meca.py | 212 ++++++++++++++++++++-------------------------- 1 file changed, 94 insertions(+), 118 deletions(-) diff --git a/pygmt/src/meca.py b/pygmt/src/meca.py index 75b69d2facb..f24ea0bbfbf 100644 --- a/pygmt/src/meca.py +++ b/pygmt/src/meca.py @@ -227,169 +227,145 @@ def meca( # noqa: PLR0912, PLR0913, PLR0915 Parameters ---------- - spec : str, 1-D array, 2-D array, dict, or :class:`pandas.DataFrame` + spec : str, 1-D numpy array, 2-D numpy array, dict, or pandas.DataFrame Data that contain focal mechanism parameters. ``spec`` can be specified in either of the following types: - - *str*: a file name containing focal mechanism parameters as - columns. The meaning of each column is: + - *str*: a file name containing focal mechanism parameters as columns. The + meaning of each column is: - Columns 1 and 2: event longitude and latitude - - Column 3: event depth (in km) - - Columns 4 to 3+n: focal mechanism parameters. The number of columns - *n* depends on the choice of ``convention``, which will be - described below. - - Columns 4+n and 5+n: longitude, latitude at which to place - beachball. Using ``0 0`` will plot the beachball at the longitude, - latitude given in columns 1 and 2. [optional and requires - ``offset=True`` to take effect]. - - Text string to appear near the beachball [optional]. - - - *1-D array*: focal mechanism parameters of a single event. + - Column 3: event depth (in kilometers) + - Columns 4 to 3+n: focal mechanism parameters. The number of columns *n* + depends on the choice of ``convention``, which is described below. + - Columns 4+n and 5+n: longitude and latitude at which to place the + beachball. ``0 0`` plots the beachball at the longitude and latitude + given in the columns 1 and 2. [optional; requires ``offset=True``]. + - Last Column: text string to appear near the beachball [optional]. + + - *1-D np.array*: focal mechanism parameters of a single event. The meanings of columns are the same as above. - - *2-D array*: focal mechanism parameters of multiple events. + - *2-D np.array*: focal mechanism parameters of multiple events. The meanings of columns are the same as above. - - *dictionary or :class:`pandas.DataFrame`*: The dictionary keys or + - *dict* or :class:`pandas.DataFrame`: The dict keys or :class:`pandas.DataFrame` column names determine the focal mechanism - convention. For different conventions, the following combination of - keys are allowed: - - - ``"aki"``: *strike, dip, rake, magnitude* - - ``"gcmt"``: *strike1, dip1, rake1, strike2, dip2, rake2, mantissa,* - *exponent* - - ``"mt"``: *mrr, mtt, mff, mrt, mrf, mtf, exponent* - - ``"partial"``: *strike1, dip1, strike2, fault_type, magnitude* - - ``"principal_axis"``: *t_value, t_azimuth, t_plunge, n_value, - n_azimuth, n_plunge, p_value, p_azimuth, p_plunge, exponent* - - A dictionary may contain values for a single focal mechanism or - lists of values for multiple focal mechanisms. - - Both dictionary and :class:`pandas.DataFrame` may optionally contain - keys/column names: ``latitude``, ``longitude``, ``depth``, - ``plot_longitude``, ``plot_latitude``, and/or ``event_name``. - - If ``spec`` is either a str, a 1-D array or a 2-D array, the - ``convention`` parameter is required so we know how to interpret the - columns. If ``spec`` is a dictionary or a :class:`pandas.DataFrame`, - ``convention`` is not needed and is ignored if specified. + convention. For the different conventions, the following combination of + keys / column names are required: + + - ``"aki"``: *strike*, *dip*, *rake*, *magnitude* + - ``"gcmt"``: *strike1*, *dip1*, *rake1*, *strike2*, *dip2*, *rake2*, + *mantissa*, *exponent* + - ``"mt"``: *mrr*, *mtt*, *mff*, *mrt*, *mrf*, *mtf*, *exponent* + - ``"partial"``: *strike1*, *dip1*, *strike2*, *fault_type*, *magnitude* + - ``"principal_axis"``: *t_value*, *t_azimuth*, *t_plunge*, *n_value*, + *n_azimuth*, *n_plunge*, *p_value*, *p_azimuth*, *p_plunge*, *exponent* + + A dict may contain values for a single focal mechanism or lists of + values for multiple focal mechanisms. + + Both dict and :class:`pandas.DataFrame` may optionally contain the keys / + column names: ``latitude``, ``longitude``, ``depth``, ``plot_longitude``, + ``plot_latitude``, and/or ``event_name``. + + If ``spec`` is either a str or a 1-D or 2-D numpy array, the ``convention`` + parameter is required to interpret the columns. If ``spec`` is a dict or + a :class:`pandas.DataFrame`, ``convention`` is not needed and ignored if + specified. scale : float or str *scale*\ [**+a**\ *angle*][**+f**\ *font*][**+j**\ *justify*]\ [**+l**][**+m**][**+o**\ *dx*\ [/\ *dy*]][**+s**\ *reference*]. - Adjust scaling of the radius of the beachball, which is - proportional to the magnitude. By default, *scale* defines the - size for magnitude = 5 (i.e., scalar seismic moment - M0 = 4.0E23 dynes-cm). If **+l** is used the radius will be - proportional to the seismic moment instead. Use **+s** and give - a *reference* to change the reference magnitude (or moment), and - use **+m** to plot all beachballs with the same size. A text - string can be specified to appear near the beachball - (corresponding to column or parameter ``event_name``). - Append **+a**\ *angle* to change the angle of the text string; - append **+f**\ *font* to change its font (size,fontname,color); - append **+j**\ *justify* to change the text location relative - to the beachball [Default is ``"TC"``, i.e., Top Center]; - append **+o** to offset the text string by *dx*\ /*dy*. + Adjust scaling of the radius of the beachball, which is proportional to the + magnitude. By default, *scale* defines the size for magnitude = 5 (i.e., scalar + seismic moment M0 = 4.0E23 dynes-cm). If **+l** is used the radius will be + proportional to the seismic moment instead. Use **+s** and give a *reference* + to change the reference magnitude (or moment), and use **+m** to plot all + beachballs with the same size. A text string can be specified to appear near + the beachball (corresponding to column or parameter ``event_name``). Append + **+a**\ *angle* to change the angle of the text string; append **+f**\ *font* + to change its font (size,fontname,color); append **+j**\ *justify* to change + the text location relative to the beachball [Default is ``"TC"``, i.e., Top + Center]; append **+o** to offset the text string by *dx*\ /*dy*. convention : str Focal mechanism convention. Choose from: - - ``"aki"`` (Aki & Richards) + - ``"aki"`` (Aki and Richards) - ``"gcmt"`` (global CMT) - ``"mt"`` (seismic moment tensor) - ``"partial"`` (partial focal mechanism) - ``"principal_axis"`` (principal axis) - Ignored if ``spec`` is a dictionary or :class:`pandas.DataFrame`. + Ignored if ``spec`` is a dict or :class:`pandas.DataFrame`. component : str The component of the seismic moment tensor to plot. - ``"full"``: the full seismic moment tensor - - ``"dc"``: the closest double couple defined from the moment tensor - (zero trace and zero determinant) + - ``"dc"``: the closest double couple defined from the moment tensor (zero + trace and zero determinant) - ``"deviatoric"``: deviatoric part of the moment tensor (zero trace) - longitude : float, list, or 1-D numpy array - Longitude(s) of event location(s). Must be the same length as the - number of events. Will override the ``longitude`` values - in ``spec`` if ``spec`` is a dictionary or :class:`pandas.DataFrame`. - latitude : float, list, or 1-D numpy array - Latitude(s) of event location(s). Must be the same length as the - number of events. Will override the ``latitude`` values - in ``spec`` if ``spec`` is a dictionary or :class:`pandas.DataFrame`. - depth : float, list, or 1-D numpy array - Depth(s) of event location(s) in kilometers. Must be the same length - as the number of events. Will override the ``depth`` values in ``spec`` - if ``spec`` is a dictionary or :class:`pandas.DataFrame`. - plot_longitude : float, str, list, or 1-D numpy array - Longitude(s) at which to place beachball(s). Must be the same length - as the number of events. Will override the ``plot_longitude`` values - in ``spec`` if ``spec`` is a dictionary or :class:`pandas.DataFrame`. - plot_latitude : float, str, list, or 1-D numpy array - Latitude(s) at which to place beachball(s). List must be the same - length as the number of events. Will override the ``plot_latitude`` - values in ``spec`` if ``spec`` is a dictionary or :class:`pandas.DataFrame`. + longitude/latitude/depth : float, list, or 1-D numpy array + Longitude(s) / latitude(s) / depth(s) of the event(s). Length must match the + number of events. Overrides the ``longitude`` / ``latitude`` / ``depth`` values + in ``spec`` if ``spec`` is a dict or :class:`pandas.DataFrame`. + plot_longitude/plot_latitude : float, str, list, or 1-D numpy array + Longitude(s) / Latitude(s) at which to place the beachball(s). Length must match + the number of events. Overrides the ``plot_longitude`` / ``plot_latitude`` + values in ``spec`` if ``spec`` is a dict or :class:`pandas.DataFrame`. event_name : str, list of str, or 1-D numpy array - Text string(s), e.g., event name(s) to appear near the beachball(s). - List must be the same length as the number of events. Will override - the ``event_name`` labels in ``spec`` if ``spec`` is a dictionary - or :class:`pandas.DataFrame`. + Text string(s), e.g., event name(s) to appear near the beachball(s). Length + must match the number of events. Overrides the ``event_name`` labels in ``spec`` + if ``spec`` is a dict or :class:`pandas.DataFrame`. labelbox : bool or str [*fill*]. - Draw a box behind the label if given. Use *fill* to give a fill color - [Default is ``"white"``]. + Draw a box behind the label if given via ``event_name``. Use *fill* to give a + fill color [Default is ``"white"``]. offset : bool or str [**+p**\ *pen*][**+s**\ *size*]. - Offset beachball(s) to longitude(s) and latitude(s) specified in the - the last two columns of the input file or array, or by - ``plot_longitude`` and ``plot_latitude`` if provided. A small circle - is plotted at the initial location and a line connects the beachball - to the circle. Use **+s**\ *size* to set the diameter of the circle - [Default is no circle]. Use **+p**\ *pen* to set the pen attributes - for this feature [Default is set via ``pen``]. The fill of the - circle is set via ``compressionfill`` or ``cmap``, i.e., - corresponds to the fill of the compressive quadrants. + Offset beachball(s) to the longitude(s) and latitude(s) specified in the last + two columns of the input file or array, or by ``plot_longitude`` and + ``plot_latitude`` if provided. A line from the beachball to the inital location + is drawn. Use **+s**\ *size* to plot a small circle at the initial location and + to set the diameter of this circle [Default is no circle]. Use **+p**\ *pen* to + set the pen attributes for this feature [Default is set via ``pen``]. The fill + of the circle is set via ``compressionfill`` or ``cmap``, i.e., corresponds to + the fill of the compressive quadrants. compressionfill : str - Set color or pattern for filling compressive quadrants - [Default is ``"black"``]. This setting also applies to the fill of - the circle defined via ``offset``. + Set color or pattern for filling compressive quadrants [Default is ``"black"``]. + This setting also applies to the fill of the circle defined via ``offset``. extensionfill : str - Set color or pattern for filling extensive quadrants - [Default is ``"white"``]. + Set color or pattern for filling extensive quadrants [Default is ``"white"``]. pen : str - Set pen attributes for all lines related to beachball [Default is - ``"0.25p,black,solid"``]. This setting applies to ``outline``, - ``nodal``, and ``offset``, unless overruled by arguments passed to - those parameters. Draws circumference of beachball. + Set (default) pen attributes for all lines related to the beachball [Default is + ``"0.25p,black,solid"``]. This setting applies to ``outline``, ``nodal``, and + ``offset``, unless overruled by arguments passed to those parameters. Draws the + circumference of the beachball. outline : bool or str [*pen*]. - Draw circumference and nodal planes of beachball. Use *pen* to set - the pen attributes for this feature [Default is set via ``pen``]. + Draw circumference and nodal planes of the beachball. Use *pen* to set the pen + attributes for this feature [Default is set via ``pen``]. nodal : bool, int, or str [*nplane*][/*pen*]. - Plot the nodal planes and outline the bubble which is transparent. - If *nplane* is + Plot the nodal planes and outline the bubble which is transparent. If *nplane* + is - ``0`` or ``True``: both nodal planes are plotted [Default]. - ``1``: only the first nodal plane is plotted. - ``2``: only the second nodal plane is plotted. - Use /*pen* to set the pen attributes for this feature [Default is - set via ``pen``]. - For double couple mechanisms, ``nodal`` renders the beachball - transparent by drawing only the nodal planes and the circumference. - For non-double couple mechanisms, ``nodal=0`` overlays best - double couple transparently. + Use /*pen* to set the pen attributes for this feature [Default is set via + ``pen``]. + For double couple mechanisms, ``nodal`` renders the beachball transparent by + drawing only the nodal planes and the circumference. For non-double couple + mechanisms, ``nodal=0`` overlays best double couple transparently. cmap : str File name of a CPT file or a series of comma-separated colors (e.g., - *color1,color2,color3*) to build a linear continuous CPT from those - colors automatically. The color of the compressive quadrants is - determined by the z-value (i.e., event depth or the third column for - an input file). This setting also applies to the fill of the circle - defined via ``offset``. + *color1,color2,color3*) to build a linear continuous CPT from those colors + automatically. The color of the compressive quadrants is determined by the + z-value (i.e., event depth or the third column for an input file). This setting + also applies to the fill of the circle defined via ``offset``. no_clip : bool - Do **not** skip symbols that fall outside the frame boundaries - [Default is ``False``, i.e., plot symbols inside the frame - boundaries only]. + Do **not** skip symbols that fall outside the frame boundaries [Default is + ``False``, i.e., plot symbols inside the frame boundaries only]. {projection} {region} {frame} From 3134188cb4ce2b1bccb5044be7c737d049313f87 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Yvonne=20Fr=C3=B6hlich?= <94163266+yvonnefroehlich@users.noreply.github.com> Date: Mon, 30 Dec 2024 21:18:40 +0100 Subject: [PATCH 24/55] Remote datasets: Add load_earth_deflection to load "IGPP Earth east-west and north-south deflection" datasets (#3728) --- doc/api/index.rst | 1 + pygmt/datasets/__init__.py | 1 + pygmt/datasets/earth_deflection.py | 119 ++++++++++++++++++ pygmt/datasets/load_remote_dataset.py | 36 ++++++ pygmt/helpers/caching.py | 4 + pygmt/tests/test_datasets_earth_deflection.py | 106 ++++++++++++++++ 6 files changed, 267 insertions(+) create mode 100644 pygmt/datasets/earth_deflection.py create mode 100644 pygmt/tests/test_datasets_earth_deflection.py diff --git a/doc/api/index.rst b/doc/api/index.rst index 09d20fa1415..1f573dcb8eb 100644 --- a/doc/api/index.rst +++ b/doc/api/index.rst @@ -235,6 +235,7 @@ and store them in GMT's user data directory. datasets.load_black_marble datasets.load_blue_marble datasets.load_earth_age + datasets.load_earth_deflection datasets.load_earth_dist datasets.load_earth_free_air_anomaly datasets.load_earth_geoid diff --git a/pygmt/datasets/__init__.py b/pygmt/datasets/__init__.py index 9e163b33e3c..3d44a8fe676 100644 --- a/pygmt/datasets/__init__.py +++ b/pygmt/datasets/__init__.py @@ -6,6 +6,7 @@ from pygmt.datasets.earth_age import load_earth_age from pygmt.datasets.earth_day import load_blue_marble +from pygmt.datasets.earth_deflection import load_earth_deflection from pygmt.datasets.earth_dist import load_earth_dist from pygmt.datasets.earth_free_air_anomaly import load_earth_free_air_anomaly from pygmt.datasets.earth_geoid import load_earth_geoid diff --git a/pygmt/datasets/earth_deflection.py b/pygmt/datasets/earth_deflection.py new file mode 100644 index 00000000000..c0a9cbf406f --- /dev/null +++ b/pygmt/datasets/earth_deflection.py @@ -0,0 +1,119 @@ +""" +Function to download the IGPP Earth east-west and north-south deflection datasets from +the GMT data server, and load as :class:`xarray.DataArray`. + +The grids are available in various resolutions. +""" + +from collections.abc import Sequence +from typing import Literal + +import xarray as xr +from pygmt.datasets.load_remote_dataset import _load_remote_dataset + +__doctest_skip__ = ["load_earth_deflection"] + + +def load_earth_deflection( + resolution: Literal[ + "01d", "30m", "20m", "15m", "10m", "06m", "05m", "04m", "03m", "02m", "01m" + ] = "01d", + region: Sequence[float] | str | None = None, + registration: Literal["gridline", "pixel", None] = None, + component: Literal["east", "north"] = "east", +) -> xr.DataArray: + r""" + Load the IGPP Earth east-west and north-south deflection datasets in various + resolutions. + + .. list-table:: + :widths: 50 50 + :header-rows: 1 + + * - IGPP Earth east-west deflection + - IGPP Earth north-south deflection + * - .. figure:: https://www.generic-mapping-tools.org/remote-datasets/_images/GMT_earth_edefl.jpg + - .. figure:: https://www.generic-mapping-tools.org/remote-datasets/_images/GMT_earth_ndefl.jpg + + The grids are downloaded to a user data directory (usually + ``~/.gmt/server/earth/earth_edefl/`` and ``~/.gmt/server/earth/earth_ndefl/`` the + first time you invoke this function. Afterwards, it will load the grid from the + data directory. So you'll need an internet connection the first time around. + + These grids can also be accessed by passing in the file name + **@**\ *earth_defl_type*\_\ *res*\[_\ *reg*] to any grid processing function or + plotting method. *earth_defl_type* is the GMT name for the dataset. The available + options are **earth_edefl** and **earth_ndefl**. *res* is the grid resolution (see + below), and *reg* is the grid registration type (**p** for pixel registration or + **g** for gridline registration). + + The default color palette table (CPTs) for this dataset is *@earth_defl.cpt*. It's + implicitly used when passing in the file name of the dataset to any grid plotting + method if no CPT is explicitly specified. When the dataset is loaded and plotted as + an :class:`xarray.DataArray` object, the default CPT is ignored, and GMT's default + CPT (*turbo*) is used. To use the dataset-specific CPT, you need to explicitly set + ``cmap="@earth_defl.cpt"``. + + Refer to :gmt-datasets:`earth-edefl.html` and :gmt-datasets:`earth-ndefl.html` for + more details about available datasets, including version information and references. + + Parameters + ---------- + resolution + The grid resolution. The suffix ``d`` and ``m`` stand for arc-degrees and + arc-minutes. + region + The subregion of the grid to load, in the form of a sequence [*xmin*, *xmax*, + *ymin*, *ymax*] or an ISO country code. Required for grids with resolutions + higher than 5 arc-minutes (i.e., ``"05m"``). + registration + Grid registration type. Either ``"pixel"`` for pixel registration or + ``"gridline"`` for gridline registration. Default is ``None``, which means + ``"gridline"`` for all resolutions except ``"01m"`` which is ``"pixel"`` only. + component + By default, the east-west deflection (``component="east"``) is returned, + set ``component="north"`` to return the north-south deflection. + + Returns + ------- + grid + The Earth east-west or north-south deflection grid. Coordinates are latitude + and longitude in degrees. Deflection values are in micro-radians, where + positive (negative) values indicate a deflection to the east or north (west + or south). + + Note + ---- + The registration and coordinate system type of the returned + :class:`xarray.DataArray` grid can be accessed via the GMT accessors (i.e., + ``grid.gmt.registration`` and ``grid.gmt.gtype`` respectively). However, these + properties may be lost after specific grid operations (such as slicing) and will + need to be manually set before passing the grid to any PyGMT data processing or + plotting functions. Refer to :class:`pygmt.GMTDataArrayAccessor` for detailed + explanations and workarounds. + + Examples + -------- + + >>> from pygmt.datasets import load_earth_deflection + >>> # load the default grid for east-west deflection (gridline-registered + >>> # 1 arc-degree grid) + >>> grid = load_earth_deflection() + >>> # load the default grid for north-south deflection + >>> grid = load_earth_deflection(component="north") + >>> # load the 30 arc-minutes grid with "gridline" registration + >>> grid = load_earth_deflection(resolution="30m", registration="gridline") + >>> # load high-resolution (5 arc-minutes) grid for a specific region + >>> grid = load_earth_deflection( + ... resolution="05m", region=[120, 160, 30, 60], registration="gridline" + ... ) + """ + prefix = "earth_ndefl" if component == "north" else "earth_edefl" + grid = _load_remote_dataset( + name=prefix, + prefix=prefix, + resolution=resolution, + region=region, + registration=registration, + ) + return grid diff --git a/pygmt/datasets/load_remote_dataset.py b/pygmt/datasets/load_remote_dataset.py index da21cc7ae94..b19c055425c 100644 --- a/pygmt/datasets/load_remote_dataset.py +++ b/pygmt/datasets/load_remote_dataset.py @@ -110,6 +110,24 @@ class GMTRemoteDataset(NamedTuple): "01m": Resolution("01m", registrations=["gridline"], tiled=True), }, ), + "earth_edefl": GMTRemoteDataset( + description="IGPP Earth east-west deflection", + units="micro-radians", + extra_attributes={"horizontal_datum": "WGS84"}, + resolutions={ + "01d": Resolution("01d"), + "30m": Resolution("30m"), + "20m": Resolution("20m"), + "15m": Resolution("15m"), + "10m": Resolution("10m"), + "06m": Resolution("06m"), + "05m": Resolution("05m", tiled=True), + "04m": Resolution("04m", tiled=True), + "03m": Resolution("03m", tiled=True), + "02m": Resolution("02m", tiled=True), + "01m": Resolution("01m", registrations=["pixel"], tiled=True), + }, + ), "earth_faa": GMTRemoteDataset( description="IGPP Earth free-air anomaly", units="mGal", @@ -295,6 +313,24 @@ class GMTRemoteDataset(NamedTuple): "07m": Resolution("07m", registrations=["gridline"]), }, ), + "earth_ndefl": GMTRemoteDataset( + description="IGPP Earth north-south deflection", + units="micro-radians", + extra_attributes={"horizontal_datum": "WGS84"}, + resolutions={ + "01d": Resolution("01d"), + "30m": Resolution("30m"), + "20m": Resolution("20m"), + "15m": Resolution("15m"), + "10m": Resolution("10m"), + "06m": Resolution("06m"), + "05m": Resolution("05m", tiled=True), + "04m": Resolution("04m", tiled=True), + "03m": Resolution("03m", tiled=True), + "02m": Resolution("02m", tiled=True), + "01m": Resolution("01m", registrations=["pixel"], tiled=True), + }, + ), "earth_vgg": GMTRemoteDataset( description="IGPP Earth vertical gravity gradient", units="Eotvos", diff --git a/pygmt/helpers/caching.py b/pygmt/helpers/caching.py index 0175839e97d..19d3eae0559 100644 --- a/pygmt/helpers/caching.py +++ b/pygmt/helpers/caching.py @@ -15,6 +15,7 @@ def cache_data(): "@earth_age_01d_g", "@earth_day_01d", "@earth_dist_01d", + "@earth_edefl_01d", "@earth_faa_01d_g", "@earth_faaerror_01d_g", "@earth_gebco_01d_g", @@ -27,6 +28,7 @@ def cache_data(): "@earth_mdt_01d_g", "@earth_mdt_07m_g", "@earth_mss_01d_g", + "@earth_ndefl_01d", "@earth_night_01d", "@earth_relief_01d_g", "@earth_relief_01d_p", @@ -51,12 +53,14 @@ def cache_data(): "@N30E060.earth_age_01m_g.nc", "@N30E090.earth_age_01m_g.nc", "@N00W030.earth_dist_01m_g.nc", + "@N00W030.earth_edefl_01m_p.nc", "@N00W030.earth_faa_01m_p.nc", "@N00W030.earth_faaerror_01m_p.nc", "@N00W030.earth_geoid_01m_g.nc", "@S30W060.earth_mag_02m_p.nc", "@S30W120.earth_mag4km_02m_p.nc", "@N30E090.earth_mss_01m_g.nc", + "@N30E090.earth_ndefl_01m_p.nc", "@N00W090.earth_relief_03m_p.nc", "@N00E135.earth_relief_30s_g.nc", "@N00W010.earth_relief_15s_p.nc", diff --git a/pygmt/tests/test_datasets_earth_deflection.py b/pygmt/tests/test_datasets_earth_deflection.py new file mode 100644 index 00000000000..9118779a379 --- /dev/null +++ b/pygmt/tests/test_datasets_earth_deflection.py @@ -0,0 +1,106 @@ +""" +Test basic functionality for loading IGPP Earth east-west and south-north deflection +datasets. +""" + +import numpy as np +import numpy.testing as npt +from pygmt.datasets import load_earth_deflection + + +def test_earth_edefl_01d(): + """ + Test some properties of the Earth east-west deflection 01d data. + """ + data = load_earth_deflection(resolution="01d") + assert data.name == "z" + assert data.attrs["long_name"] == "edefl (microradians)" + assert data.attrs["description"] == "IGPP Earth east-west deflection" + assert data.attrs["units"] == "micro-radians" + assert data.attrs["horizontal_datum"] == "WGS84" + assert data.shape == (181, 361) + assert data.gmt.registration == 0 + npt.assert_allclose(data.lat, np.arange(-90, 91, 1)) + npt.assert_allclose(data.lon, np.arange(-180, 181, 1)) + npt.assert_allclose(data.min(), -142.64, atol=0.04) + npt.assert_allclose(data.max(), 178.32, atol=0.04) + + +def test_earth_edefl_01d_with_region(): + """ + Test loading low-resolution Earth east-west deflection with "region". + """ + data = load_earth_deflection(resolution="01d", region=[-10, 10, -5, 5]) + assert data.shape == (11, 21) + assert data.gmt.registration == 0 + npt.assert_allclose(data.lat, np.arange(-5, 6, 1)) + npt.assert_allclose(data.lon, np.arange(-10, 11, 1)) + npt.assert_allclose(data.min(), -28.92, atol=0.04) + npt.assert_allclose(data.max(), 24.72, atol=0.04) + + +def test_earth_edefl_01m_default_registration(): + """ + Test that the grid returned by default for the 1 arc-minute resolution has a "pixel" + registration. + """ + data = load_earth_deflection(resolution="01m", region=[-10, -9, 3, 5]) + assert data.shape == (120, 60) + assert data.gmt.registration == 1 + npt.assert_allclose(data.coords["lat"].data.min(), 3.008333333) + npt.assert_allclose(data.coords["lat"].data.max(), 4.991666666) + npt.assert_allclose(data.coords["lon"].data.min(), -9.99166666) + npt.assert_allclose(data.coords["lon"].data.max(), -9.00833333) + npt.assert_allclose(data.min(), -62.24, atol=0.04) + npt.assert_allclose(data.max(), 15.52, atol=0.04) + + +def test_earth_ndefl_01d(): + """ + Test some properties of the Earth north-south deflection 01d data. + """ + data = load_earth_deflection(resolution="01d", component="north") + assert data.name == "z" + assert data.attrs["long_name"] == "ndefl (microradians)" + assert data.attrs["description"] == "IGPP Earth north-south deflection" + assert data.attrs["units"] == "micro-radians" + assert data.attrs["horizontal_datum"] == "WGS84" + assert data.shape == (181, 361) + assert data.gmt.registration == 0 + npt.assert_allclose(data.lat, np.arange(-90, 91, 1)) + npt.assert_allclose(data.lon, np.arange(-180, 181, 1)) + npt.assert_allclose(data.min(), -214.8, atol=0.04) + npt.assert_allclose(data.max(), 163.04, atol=0.04) + + +def test_earth_ndefl_01d_with_region(): + """ + Test loading low-resolution Earth north-south deflection with "region". + """ + data = load_earth_deflection( + resolution="01d", region=[-10, 10, -5, 5], component="north" + ) + assert data.shape == (11, 21) + assert data.gmt.registration == 0 + npt.assert_allclose(data.lat, np.arange(-5, 6, 1)) + npt.assert_allclose(data.lon, np.arange(-10, 11, 1)) + npt.assert_allclose(data.min(), -48.08, atol=0.04) + npt.assert_allclose(data.max(), 18.92, atol=0.04) + + +def test_earth_ndefl_01m_default_registration(): + """ + Test that the grid returned by default for the 1 arc-minute resolution has a "pixel" + registration. + """ + data = load_earth_deflection( + resolution="01m", region=[-10, -9, 3, 5], component="north" + ) + assert data.shape == (120, 60) + assert data.gmt.registration == 1 + npt.assert_allclose(data.coords["lat"].data.min(), 3.008333333) + npt.assert_allclose(data.coords["lat"].data.max(), 4.991666666) + npt.assert_allclose(data.coords["lon"].data.min(), -9.99166666) + npt.assert_allclose(data.coords["lon"].data.max(), -9.00833333) + npt.assert_allclose(data.min(), -107.04, atol=0.04) + npt.assert_allclose(data.max(), 20.28, atol=0.04) From 32cc7a2296246ed649d98fda3656c825db182a58 Mon Sep 17 00:00:00 2001 From: Michael Grund <23025878+michaelgrund@users.noreply.github.com> Date: Tue, 31 Dec 2024 16:57:18 +0100 Subject: [PATCH 25/55] Update the gallery example for plotting lines with LineString/MultiLineString geometry (#3711) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Co-authored-by: Dongdong Tian Co-authored-by: actions-bot <58130806+actions-bot@users.noreply.github.com> Co-authored-by: Yvonne Fröhlich <94163266+yvonnefroehlich@users.noreply.github.com> --- examples/gallery/lines/linestrings.py | 46 +++++++++++++++++++++++++++ examples/gallery/lines/roads.py | 46 --------------------------- 2 files changed, 46 insertions(+), 46 deletions(-) create mode 100644 examples/gallery/lines/linestrings.py delete mode 100644 examples/gallery/lines/roads.py diff --git a/examples/gallery/lines/linestrings.py b/examples/gallery/lines/linestrings.py new file mode 100644 index 00000000000..d3793645c84 --- /dev/null +++ b/examples/gallery/lines/linestrings.py @@ -0,0 +1,46 @@ +""" +GeoPandas: Plotting lines with LineString or MultiLineString geometry +===================================================================== + +The :meth:`pygmt.Figure.plot` method allows us to plot geographical data such as lines +with LineString or MultiLineString geometry types stored in a +:class:`geopandas.GeoDataFrame` object or any object that implements the +`__geo_interface__ `__ property. + +Use :func:`geopandas.read_file` to load data from any supported OGR format such as a +shapefile (.shp), GeoJSON (.geojson), geopackage (.gpkg), etc. Then, pass the +:class:`geopandas.GeoDataFrame` object as an argument to the ``data`` parameter of +:meth:`pygmt.Figure.plot`, and style the lines using the ``pen`` parameter. +""" + +# %% +import geodatasets +import geopandas as gpd +import pygmt + +# Read a sample dataset provided by the geodatasets package. +# The dataset contains large rivers in Europe, stored as LineString/MultiLineString +# geometry types. +gdf = gpd.read_file(geodatasets.get_path("eea large_rivers")) + +# Convert object to EPSG 4326 coordinate system +gdf = gdf.to_crs("EPSG:4326") +print(gdf.head()) + +# %% +fig = pygmt.Figure() + +fig.coast( + projection="M10c", + region=[-10, 30, 35, 57], + resolution="l", + land="gray95", + shorelines="1/0.1p,gray50", + borders="1/0.1,gray30", + frame=True, +) + +# Add rivers to map +fig.plot(data=gdf, pen="1p,steelblue") + +fig.show() diff --git a/examples/gallery/lines/roads.py b/examples/gallery/lines/roads.py deleted file mode 100644 index c2a5f69980a..00000000000 --- a/examples/gallery/lines/roads.py +++ /dev/null @@ -1,46 +0,0 @@ -# ruff: noqa: RUF003 -""" -Roads -===== - -The :meth:`pygmt.Figure.plot` method allows us to plot geographical data such -as lines which are stored in a :class:`geopandas.GeoDataFrame` object. Use -:func:`geopandas.read_file` to load data from any supported OGR format such as -a shapefile (.shp), GeoJSON (.geojson), geopackage (.gpkg), etc. Then, pass the -:class:`geopandas.GeoDataFrame` as an argument to the ``data`` parameter of -:meth:`pygmt.Figure.plot`, and style the geometry using the ``pen`` parameter. -""" - -# %% -import geopandas as gpd -import pygmt - -# Read shapefile data using geopandas -gdf = gpd.read_file( - "https://www2.census.gov/geo/tiger/TIGER2015/PRISECROADS/tl_2015_15_prisecroads.zip" -) -# The dataset contains different road types listed in the RTTYP column, -# here we select the following ones to plot: -roads_common = gdf[gdf.RTTYP == "M"] # Common name roads -roads_state = gdf[gdf.RTTYP == "S"] # State recognized roads -roads_interstate = gdf[gdf.RTTYP == "I"] # Interstate roads - -fig = pygmt.Figure() - -# Define target region around Oʻahu (Hawaiʻi) -region = [-158.3, -157.6, 21.2, 21.75] # xmin, xmax, ymin, ymax - -title = "Main roads of O`ahu (Hawai`i)" # Approximating the Okina letter ʻ with ` -fig.basemap(region=region, projection="M12c", frame=["af", f"WSne+t{title}"]) -fig.coast(land="gray", water="dodgerblue4", shorelines="1p,black") - -# Plot the individual road types with different pen settings and assign labels -# which are displayed in the legend -fig.plot(data=roads_common, pen="5p,dodgerblue", label="CommonName") -fig.plot(data=roads_state, pen="2p,gold", label="StateRecognized") -fig.plot(data=roads_interstate, pen="2p,red", label="Interstate") - -# Add legend -fig.legend() - -fig.show() From df1ae21daa42b7c5bddd2cc17c57ccd47876b52a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Yvonne=20Fr=C3=B6hlich?= <94163266+yvonnefroehlich@users.noreply.github.com> Date: Tue, 31 Dec 2024 17:11:31 +0100 Subject: [PATCH 26/55] Typo fixes before v0.14.0 (#3733) --- pygmt/datasets/earth_free_air_anomaly.py | 4 ++-- pygmt/figure.py | 2 +- pygmt/src/meca.py | 2 +- pyproject.toml | 2 +- 4 files changed, 5 insertions(+), 5 deletions(-) diff --git a/pygmt/datasets/earth_free_air_anomaly.py b/pygmt/datasets/earth_free_air_anomaly.py index 190f218d858..d85911496d6 100644 --- a/pygmt/datasets/earth_free_air_anomaly.py +++ b/pygmt/datasets/earth_free_air_anomaly.py @@ -30,8 +30,8 @@ def load_earth_free_air_anomaly( :widths: 50 50 :header-rows: 1 - * - IGPP Earth free-Air anomaly - - IGPP Earth free-Air anomaly uncertainty + * - IGPP Earth free-air anomaly + - IGPP Earth free-air anomaly uncertainty * - .. figure:: https://www.generic-mapping-tools.org/remote-datasets/_images/GMT_earth_faa.jpg - .. figure:: https://www.generic-mapping-tools.org/remote-datasets/_images/GMT_earth_faaerror.jpg diff --git a/pygmt/figure.py b/pygmt/figure.py index 1d051c0dacc..374eb1d8fee 100644 --- a/pygmt/figure.py +++ b/pygmt/figure.py @@ -248,7 +248,7 @@ def savefig( kwargs.pop("metadata", None) self.psconvert(prefix=prefix, fmt=fmts[ext], crop=crop, **kwargs) - # TODO(GMT>=6.5.0): Remve the workaround for upstream bug in GMT<6.5.0. + # TODO(GMT>=6.5.0): Remove the workaround for upstream bug in GMT<6.5.0. # Remove the .pgw world file if exists. Not necessary after GMT 6.5.0. # See upstream fix https://github.com/GenericMappingTools/gmt/pull/7865 if ext == "tiff": diff --git a/pygmt/src/meca.py b/pygmt/src/meca.py index f24ea0bbfbf..ba6de1f7868 100644 --- a/pygmt/src/meca.py +++ b/pygmt/src/meca.py @@ -323,7 +323,7 @@ def meca( # noqa: PLR0912, PLR0913, PLR0915 [**+p**\ *pen*][**+s**\ *size*]. Offset beachball(s) to the longitude(s) and latitude(s) specified in the last two columns of the input file or array, or by ``plot_longitude`` and - ``plot_latitude`` if provided. A line from the beachball to the inital location + ``plot_latitude`` if provided. A line from the beachball to the initial location is drawn. Use **+s**\ *size* to plot a small circle at the initial location and to set the diameter of this circle [Default is no circle]. Use **+p**\ *pen* to set the pen attributes for this feature [Default is set via ``pen``]. The fill diff --git a/pyproject.toml b/pyproject.toml index dfea29b5c46..2150af08d7b 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -66,7 +66,7 @@ local_scheme = "node-and-date" fallback_version = "999.999.999+unknown" [tool.codespell] -ignore-words-list = "astroid,oints,reenable,tripel,trough" +ignore-words-list = "astroid,oints,reenable,tripel,trough,ND" [tool.coverage.run] omit = ["*/tests/*", "*pygmt/__init__.py"] From 2a84b5e4465cced3df8dd8ce9aa423797583a583 Mon Sep 17 00:00:00 2001 From: Dongdong Tian Date: Wed, 1 Jan 2025 00:45:37 +0800 Subject: [PATCH 27/55] Changelog entry for v0.14.0 (#3730) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Co-authored-by: Michael Grund <23025878+michaelgrund@users.noreply.github.com> Co-authored-by: Yvonne Fröhlich <94163266+yvonnefroehlich@users.noreply.github.com> Co-authored-by: Wei Ji <23487320+weiji14@users.noreply.github.com> --- CITATION.cff | 14 ++--- README.md | 12 ++-- doc/_static/version_switch.js | 1 + doc/changes.md | 102 ++++++++++++++++++++++++++++++++++ doc/minversions.md | 1 + 5 files changed, 117 insertions(+), 13 deletions(-) diff --git a/CITATION.cff b/CITATION.cff index e6d79e2530d..a18999e8f2d 100644 --- a/CITATION.cff +++ b/CITATION.cff @@ -40,14 +40,14 @@ authors: family-names: Yao affiliation: Nanyang Technological University, Singapore orcid: https://orcid.org/0000-0001-7036-4238 +- given-names: Jing-Hui + family-names: Tong + affiliation: National Taiwan Normal University, Taiwan + orcid: https://orcid.org/0009-0002-7195-3071 - given-names: Yohai family-names: Magen affiliation: Tel Aviv University, Israel orcid: https://orcid.org/0000-0002-4892-4013 -- given-names: Tong - family-names: Jing-Hui - affiliation: National Taiwan Normal University, Taiwan - orcid: https://orcid.org/0009-0002-7195-3071 - given-names: Kathryn family-names: Materna affiliation: US Geological Survey, USA @@ -76,9 +76,9 @@ authors: family-names: Wessel affiliation: University of Hawaiʻi at Mānoa, USA orcid: https://orcid.org/0000-0001-5708-7336 -date-released: 2024-09-05 -doi: 10.5281/zenodo.13679420 +date-released: 2024-12-31 +doi: 10.5281/zenodo.14535921 license: BSD-3-Clause repository-code: https://github.com/GenericMappingTools/pygmt type: software -version: 0.13.0 +version: 0.14.0 diff --git a/README.md b/README.md index 44f4065aeb7..edcf9959769 100644 --- a/README.md +++ b/README.md @@ -132,7 +132,7 @@ research using the following BibTeX: ``` @software{ - pygmt_2024_13679420, + pygmt_2024_14535921, author = {Tian, Dongdong and Uieda, Leonardo and Leong, Wei Ji and @@ -142,8 +142,8 @@ research using the following BibTeX: Jones, Max and Toney, Liam and Yao, Jiayuan and - Magen, Yohai and Tong, Jing-Hui and + Magen, Yohai and Materna, Kathryn and Belem, Andre and Newton, Tyler and @@ -152,12 +152,12 @@ research using the following BibTeX: Quinn, Jamie and Wessel, Paul}, title = {{PyGMT: A Python interface for the Generic Mapping Tools}}, - month = sep, + month = dec, year = 2024, publisher = {Zenodo}, - version = {0.13.0}, - doi = {10.5281/zenodo.13679420}, - url = {https://doi.org/10.5281/zenodo.13679420} + version = {0.14.0}, + doi = {10.5281/zenodo.14535921}, + url = {https://doi.org/10.5281/zenodo.14535921} } ``` diff --git a/doc/_static/version_switch.js b/doc/_static/version_switch.js index acedd8c1d4c..5450cb9d6af 100644 --- a/doc/_static/version_switch.js +++ b/doc/_static/version_switch.js @@ -12,6 +12,7 @@ var all_versions = { 'latest': 'latest', 'dev': 'dev', + 'v0.14.0': 'v0.14.0', 'v0.13.0': 'v0.13.0', 'v0.12.0': 'v0.12.0', 'v0.11.0': 'v0.11.0', diff --git a/doc/changes.md b/doc/changes.md index 2efe67a7f1d..21250127374 100644 --- a/doc/changes.md +++ b/doc/changes.md @@ -1,5 +1,107 @@ # Changelog +## Release v0.14.0 (2024/12/31) + +[![Digital Object Identifier for PyGMT v0.14.0](https://zenodo.org/badge/DOI/10.5281/zenodo.14535921.svg)](https://doi.org/10.5281/zenodo.14535921) + +### Highlights + +* 🎉 **Fourteenth minor release of PyGMT** 🎉 +* Bump minimum supported version to GMT>=6.4.0 ([#3450](https://github.com/GenericMappingTools/pygmt/pull/3450)) +* Two new plotting methods and six new functions to access more GMT remote datasets +* PyArrow as an optional dependency and improved support of PyArrow data types ([#3592](https://github.com/GenericMappingTools/pygmt/pull/3592)) + +### New Features + +* Add Figure.hlines for plotting horizontal lines ([#923](https://github.com/GenericMappingTools/pygmt/pull/923)) +* Add Figure.vlines for plotting vertical lines ([#3726](https://github.com/GenericMappingTools/pygmt/pull/3726)) +* Add load_black_marble to load "Black Marble" dataset ([#3469](https://github.com/GenericMappingTools/pygmt/pull/3469)) +* Add load_blue_marble to load "Blue Marble" dataset ([#2235](https://github.com/GenericMappingTools/pygmt/pull/2235)) +* Add load_earth_deflection to load "IGPP Earth east-west and north-south deflection" datasets ([#3728](https://github.com/GenericMappingTools/pygmt/pull/3728)) +* Add load_earth_dist to load "GSHHG Earth distance to shoreline" dataset ([#3706](https://github.com/GenericMappingTools/pygmt/pull/3706)) +* Add load_earth_mean_dynamic_topography to load "CNES Earth Mean Dynamic Topography" dataset ([#3718](https://github.com/GenericMappingTools/pygmt/pull/3718)) +* Add load_earth_mean_sea_surface to load "CNES Earth Mean Sea Surface" dataset ([#3717](https://github.com/GenericMappingTools/pygmt/pull/3717)) +* load_earth_free_air_anomaly: Add "uncertainty" parameter to load the "IGPP Earth free-air anomaly uncertainty" dataset ([#3727](https://github.com/GenericMappingTools/pygmt/pull/3727)) + + +### Enhancements + +* Figure.plot: Add the "symbol" parameter to support plotting data points with varying symbols ([#1117](https://github.com/GenericMappingTools/pygmt/pull/1117)) +* Figure.plot3d: Add the "symbol" parameter to support plotting data points with varying symbols ([#3559](https://github.com/GenericMappingTools/pygmt/pull/3559)) +* Figure.legend: Support passing a StringIO object as the legend specification ([#3438](https://github.com/GenericMappingTools/pygmt/pull/3438)) +* load_tile_map: Add parameter "crs" to set the CRS of the returned dataarray ([#3554](https://github.com/GenericMappingTools/pygmt/pull/3554)) +* PyArrow: Support pyarrow arrays with string/large_string/string_view types ([#3619](https://github.com/GenericMappingTools/pygmt/pull/3619)) +* Support 1-D/2-D numpy arrays with longlong and ulonglong dtype ([#3566](https://github.com/GenericMappingTools/pygmt/pull/3566)) +* GMT_IMAGE: Implement the to_dataarray method for 3-band images ([#3128](https://github.com/GenericMappingTools/pygmt/pull/3128)) +* Ensure non-ASCII characters are typeset correctly even if PS_CHAR_ENCODING is not "ISOLatin1+" ([#3611](https://github.com/GenericMappingTools/pygmt/pull/3611)) +* Add enums GridRegistration and GridType for grid registration and type ([#3693](https://github.com/GenericMappingTools/pygmt/pull/3693)) + +### Deprecations + +* SPEC 0: Bump minimum supported versions to Python 3.11, NumPy 1.25, pandas>=2.0 and xarray>=2023.04 ([#3460](https://github.com/GenericMappingTools/pygmt/pull/3460), [#3606](https://github.com/GenericMappingTools/pygmt/pull/3606), [#3697](https://github.com/GenericMappingTools/pygmt/pull/3697)) +* clib.Session.virtualfile_from_vectors: Now takes a sequence of vectors as its single argument (Passing multiple arguments will be unsupported in v0.16.0) ([#3522](https://github.com/GenericMappingTools/pygmt/pull/3522)) +* Remove the deprecated build_arg_string function (deprecated since v0.12.0) ([#3427](https://github.com/GenericMappingTools/pygmt/pull/3427)) +* Figure.grdcontour: Remove the deprecated syntax for the 'annotation' parameter (deprecated since v0.12.0) ([#3428](https://github.com/GenericMappingTools/pygmt/pull/3428)) + +### Bug Fixes + +* launch_external_viewer: Use full path when opening the file in a web browser ([#3647](https://github.com/GenericMappingTools/pygmt/pull/3647)) +* PyArrow: Map date32[day]/date64[ms] dtypes in pandas objects to np.datetime64 with correct date/time units ([#3617](https://github.com/GenericMappingTools/pygmt/pull/3617)) +* clib.session: Add the GMT_SESSION_NOGDALCLOSE flag to keep GDAL open ([#3672](https://github.com/GenericMappingTools/pygmt/pull/3672)) +* Set the "Conventions" attribute to "CF-1.7" for netCDF grids only ([#3463](https://github.com/GenericMappingTools/pygmt/pull/3463)) +* Fix the conversion error for pandas.Series with missing values in pandas<=2.1 ([#3505](https://github.com/GenericMappingTools/pygmt/pull/3505), [#3596](https://github.com/GenericMappingTools/pygmt/pull/3596)) +* GeoPandas: Explicitly convert columns with overflow integers to avoid OverflowError with fiona 1.10 ([#3455](https://github.com/GenericMappingTools/pygmt/pull/3455)) +* Figure.plot/Figure.plot3d: Improve the check of the "style" parameter for "v" or "V" ([#3603](https://github.com/GenericMappingTools/pygmt/pull/3603)) +* Correctly reserve the grid data dtype by converting ctypes array to numpy array with np.ctypeslib.as_array ([#3446](https://github.com/GenericMappingTools/pygmt/pull/3446)) +* **Breaking**: Figure.text: Fix typesetting of integers when mixed with floating-point values ([#3493](https://github.com/GenericMappingTools/pygmt/pull/3493)) + +### Documentation + +* Add basic tutorial "Plotting polygons" ([#3593](https://github.com/GenericMappingTools/pygmt/pull/3593)) +* Update the gallery example for plotting lines with LineString/MultiLineString geometry ([#3711](https://github.com/GenericMappingTools/pygmt/pull/3711)) +* Add the PyGMT ecosystem page ([#3475](https://github.com/GenericMappingTools/pygmt/pull/3475)) +* Document the support policy for optional packages ([#3616](https://github.com/GenericMappingTools/pygmt/pull/3616)) +* Document the environment variables that can affect the behavior of PyGMT ([#3432](https://github.com/GenericMappingTools/pygmt/pull/3432)) +* Document the built-in patterns in the Technical Reference section ([#3466](https://github.com/GenericMappingTools/pygmt/pull/3466)) +* Document Continuous Benchmarking in Maintainers Guides ([#3631](https://github.com/GenericMappingTools/pygmt/pull/3631)) +* Add instructions for installing optional dependencies ([#3506](https://github.com/GenericMappingTools/pygmt/pull/3506)) +* Update "PyData Ecosystem" to "Scientific Python Ecosystem" ([#3447](https://github.com/GenericMappingTools/pygmt/pull/3447)) +* Figure.savefig: Clarify that the "transparent" parameter also works for the PNG file associated with the KML format ([#3579](https://github.com/GenericMappingTools/pygmt/pull/3579)) +* Add the PyGMT talk at AGU24 to the "Overview" section ([#3685](https://github.com/GenericMappingTools/pygmt/pull/3685)) +* Add the GMT/PyGMT pre-conference workshop at AGU24 to the "External resources" section ([#3689](https://github.com/GenericMappingTools/pygmt/pull/3689)) +* Add TODO comments in the maintainers guides and update the release checklist ([#3724](https://github.com/GenericMappingTools/pygmt/pull/3724)) + +### Maintenance + +* **Breaking**: data_kind: data is None and required now returns the "empty" kind ([#3482](https://github.com/GenericMappingTools/pygmt/pull/3482)) +* **Breaking**: data_kind: Now "matrix" represents a 2-D numpy array and unrecognized data types fall back to "vectors" ([#3351](https://github.com/GenericMappingTools/pygmt/pull/3351)) +* Add Support for Python 3.13 ([#3490](https://github.com/GenericMappingTools/pygmt/pull/3490)) +* Add the Session.virtualfile_from_stringio method to allow StringIO input for certain functions/methods ([#3326](https://github.com/GenericMappingTools/pygmt/pull/3326)) +* Add "geodatasets" as a dependency for docs and update the choropleth example ([#3719](https://github.com/GenericMappingTools/pygmt/pull/3719)) +* PyArrow: Check compatibility of pyarrow.array with string type ([#2933](https://github.com/GenericMappingTools/pygmt/pull/2933)) +* Rename sphinx-gallery's README.txt to GALLERY_HEADER.rst and require Sphinx-Gallery>=0.17.0 ([#3348](https://github.com/GenericMappingTools/pygmt/pull/3348)) +* clib.conversion: Remove the as_c_contiguous function and use np.ascontiguousarray instead ([#3492](https://github.com/GenericMappingTools/pygmt/pull/3492)) +* Use TODO comments to track deprecations and workarounds ([#3722](https://github.com/GenericMappingTools/pygmt/pull/3722)) +* Move Figure.psconvert into a separate file ([#3553](https://github.com/GenericMappingTools/pygmt/pull/3553)) +* Improve the data type checking for 2-D arrays passed to the GMT C API ([#3563](https://github.com/GenericMappingTools/pygmt/pull/3563)) +* Enable ruff's TD (flake8-todos), COM (flake8-commas), TRY (tryceratops), and EM (flake8-errmsg) rules ([#3723](https://github.com/GenericMappingTools/pygmt/pull/3723), [#3531](https://github.com/GenericMappingTools/pygmt/pull/3531), [#3665](https://github.com/GenericMappingTools/pygmt/pull/3665), [#3661](https://github.com/GenericMappingTools/pygmt/pull/3661)) +* CI: Install pyarrow-core instead of pyarrow from conda-forge ([#3698](https://github.com/GenericMappingTools/pygmt/pull/3698)) +* CI: Ensure no hyphens in Python file and directory names in the "Style Checks" workflow ([#3703](https://github.com/GenericMappingTools/pygmt/pull/3703)) +* Bump to ruff>=0.8.0 and rename rule TCH to TC ([#3662](https://github.com/GenericMappingTools/pygmt/pull/3662)) +* Bump to Ghostscript 10.04.0 ([#3443](https://github.com/GenericMappingTools/pygmt/pull/3443)) +* Add enums GridFormat for GMT grid format ID ([#3449](https://github.com/GenericMappingTools/pygmt/pull/3449)) + +**Full Changelog**: + +### Contributors + +* [Dongdong Tian](https://github.com/seisman) +* [Yvonne Fröhlich](https://github.com/yvonnefroehlich) +* [Wei Ji Leong](https://github.com/weiji14) +* [Michael Grund](https://github.com/michaelgrund) +* [Will Schlitzer](https://github.com/willschlitzer) +* [Jiayuan Yao](https://github.com/core-man) + ## Release v0.13.0 (2024/09/05) [![Digital Object Identifier for PyGMT v0.13.0](https://zenodo.org/badge/DOI/10.5281/zenodo.13679420.svg)](https://doi.org/10.5281/zenodo.13679420) diff --git a/doc/minversions.md b/doc/minversions.md index 1d348ec6922..b10150b5ce1 100644 --- a/doc/minversions.md +++ b/doc/minversions.md @@ -41,6 +41,7 @@ compatibility reasons. | PyGMT Version | GMT | Python | NumPy | pandas | Xarray | |---|---|---|---|---|---| | [Dev][]* [] | {{ requires.gmt }} | {{ requires.python }} | {{ requires.numpy }} | {{ requires.pandas }} | {{ requires.xarray }} | +| [] | >=6.4.0 | >=3.11 | >=1.25 | >=2.0 | >=2023.04 | | [] | >=6.3.0 | >=3.10 | >=1.24 | >=1.5 | >=2022.09 | | [] | >=6.3.0 | >=3.10 | >=1.23 | >=1.5 | >=2022.06 | | [] | >=6.3.0 | >=3.9 | >=1.23 | | | From b43ac2244abb3aec2256b3ce8b83da7e5dc2cca7 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Wed, 1 Jan 2025 09:33:19 +0800 Subject: [PATCH 28/55] Build(deps): Bump actions/upload-artifact from 4.4.3 to 4.5.0 (#3734) Bumps [actions/upload-artifact](https://github.com/actions/upload-artifact) from 4.4.3 to 4.5.0. - [Release notes](https://github.com/actions/upload-artifact/releases) - [Commits](https://github.com/actions/upload-artifact/compare/v4.4.3...v4.5.0) --- updated-dependencies: - dependency-name: actions/upload-artifact dependency-type: direct:production update-type: version-update:semver-minor ... Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- .github/workflows/cache_data.yaml | 2 +- .github/workflows/ci_tests.yaml | 2 +- .github/workflows/ci_tests_dev.yaml | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/.github/workflows/cache_data.yaml b/.github/workflows/cache_data.yaml index 7e12513b0e9..a005ba5e87b 100644 --- a/.github/workflows/cache_data.yaml +++ b/.github/workflows/cache_data.yaml @@ -76,7 +76,7 @@ jobs: # Upload the downloaded files as artifacts to GitHub - name: Upload artifacts to GitHub - uses: actions/upload-artifact@v4.4.3 + uses: actions/upload-artifact@v4.5.0 with: name: gmt-cache include-hidden-files: true diff --git a/.github/workflows/ci_tests.yaml b/.github/workflows/ci_tests.yaml index c1012487d22..2d9091ab384 100644 --- a/.github/workflows/ci_tests.yaml +++ b/.github/workflows/ci_tests.yaml @@ -179,7 +179,7 @@ jobs: # Upload diff images on test failure - name: Upload diff images if any test fails - uses: actions/upload-artifact@v4.4.3 + uses: actions/upload-artifact@v4.5.0 if: failure() with: name: artifact-${{ runner.os }}-${{ matrix.python-version }} diff --git a/.github/workflows/ci_tests_dev.yaml b/.github/workflows/ci_tests_dev.yaml index d8259827f1d..6a026a5099f 100644 --- a/.github/workflows/ci_tests_dev.yaml +++ b/.github/workflows/ci_tests_dev.yaml @@ -187,7 +187,7 @@ jobs: # Upload diff images on test failure - name: Upload diff images if any test fails - uses: actions/upload-artifact@v4.4.3 + uses: actions/upload-artifact@v4.5.0 if: ${{ failure() }} with: name: artifact-GMT-${{ matrix.gmt_git_ref }}-${{ runner.os }} From d54e6e1f14d85ece2232cb32072ed38c5ee801e8 Mon Sep 17 00:00:00 2001 From: Dongdong Tian Date: Thu, 2 Jan 2025 18:40:04 +0800 Subject: [PATCH 29/55] Update License year to 2025 (#3737) --- LICENSE.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) 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, From e3a3682dafb440a6856dce6b8df3682710654665 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Fri, 3 Jan 2025 11:08:17 +1300 Subject: [PATCH 30/55] Build(deps): Bump astral-sh/setup-uv from 4.2.0 to 5.1.0 (#3735) * Build(deps): Bump astral-sh/setup-uv from 4.2.0 to 5.1.0 Bumps [astral-sh/setup-uv](https://github.com/astral-sh/setup-uv) from 4.2.0 to 5.1.0. - [Release notes](https://github.com/astral-sh/setup-uv/releases) - [Commits](https://github.com/astral-sh/setup-uv/compare/v4.2.0...v5.1.0) --- updated-dependencies: - dependency-name: astral-sh/setup-uv dependency-type: direct:production update-type: version-update:semver-major ... Signed-off-by: dependabot[bot] * Try the venv auto-activate feature in v5.0.0 * Put `uv run dvc pull` command on one line --------- Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> Co-authored-by: Dongdong Tian Co-authored-by: Wei Ji <23487320+weiji14@users.noreply.github.com> --- .github/workflows/ci_tests.yaml | 10 ++++------ 1 file changed, 4 insertions(+), 6 deletions(-) diff --git a/.github/workflows/ci_tests.yaml b/.github/workflows/ci_tests.yaml index 2d9091ab384..e155d7bd95c 100644 --- a/.github/workflows/ci_tests.yaml +++ b/.github/workflows/ci_tests.yaml @@ -154,20 +154,18 @@ jobs: GH_TOKEN: ${{ github.token }} - name: Install uv - uses: astral-sh/setup-uv@v4.2.0 + uses: astral-sh/setup-uv@v5.1.0 + with: + python-version: ${{ matrix.python-version }} - name: Install dvc run: | - uv venv - source .venv/bin/activate uv pip install dvc uv pip list # Pull baseline image data from dvc remote (DAGsHub) - name: Pull baseline image data from dvc remote - run: | - source .venv/bin/activate - uv run dvc pull --no-run-cache --verbose && ls -lhR pygmt/tests/baseline/ + run: uv run dvc pull --no-run-cache --verbose && ls -lhR pygmt/tests/baseline/ # Install the package that we want to test - name: Install the package From b1c984ad0f29f49eda961bcbe843b30fbc475e00 Mon Sep 17 00:00:00 2001 From: Dongdong Tian Date: Fri, 3 Jan 2025 08:29:40 +0800 Subject: [PATCH 31/55] Fix an image in README.md (broken on PyPI) and rewrap to 88 characters (#3740) --- README.md | 75 +++++++++++++++++++++++++++++-------------------------- 1 file changed, 40 insertions(+), 35 deletions(-) diff --git a/README.md b/README.md index edcf9959769..ba4e47d8fdb 100644 --- a/README.md +++ b/README.md @@ -22,22 +22,22 @@ ## Why PyGMT? -A beautiful map is worth a thousand words. To truly understand how powerful PyGMT is, play with it online on -[Binder](https://github.com/GenericMappingTools/try-gmt)! For a quicker introduction, check out our -[3 minute overview](https://youtu.be/4iPnITXrxVU)! +A beautiful map is worth a thousand words. To truly understand how powerful PyGMT is, +play with it online on [Binder](https://github.com/GenericMappingTools/try-gmt)! For a +quicker introduction, check out our [3 minute overview](https://youtu.be/4iPnITXrxVU)! -Afterwards, feel free to look at our [Tutorials](https://www.pygmt.org/latest/tutorials), visit the -[Gallery](https://www.pygmt.org/latest/gallery), and check out some +Afterwards, feel free to look at our [Tutorials](https://www.pygmt.org/latest/tutorials), +visit the [Gallery](https://www.pygmt.org/latest/gallery), and check out some [external PyGMT examples](https://www.pygmt.org/latest/external_resources.html)! -![Quick Introduction to PyGMT YouTube Video](doc/_static/scipy2022-youtube-thumbnail.jpg) +![Quick Introduction to PyGMT YouTube Video](https://raw.githubusercontent.com/GenericMappingTools/pygmt/refs/heads/main/doc/_static/scipy2022-youtube-thumbnail.jpg) ## About -PyGMT is a library for processing geospatial and geophysical data and making publication-quality -maps and figures. It provides a Pythonic interface for the -[Generic Mapping Tools (GMT)](https://github.com/GenericMappingTools/gmt), a command-line program -widely used across the Earth, Ocean, and Planetary sciences and beyond. +PyGMT is a library for processing geospatial and geophysical data and making +publication-quality maps and figures. It provides a Pythonic interface for the +[Generic Mapping Tools (GMT)](https://github.com/GenericMappingTools/gmt), a command-line +program widely used across the Earth, Ocean, and Planetary sciences and beyond. ## Project goals @@ -45,8 +45,9 @@ widely used across the Earth, Ocean, and Planetary sciences and beyond. - Build a Pythonic API for GMT. - Interface with the GMT C API directly using ctypes (no system calls). - Support for rich display in the Jupyter notebook. -- Integration with the [scientific Python ecosystem](https://scientific-python.org/): `numpy.ndarray` or - `pandas.DataFrame` for data tables, `xarray.DataArray` for grids, and `geopandas.GeoDataFrame` for geographical data. +- Integration with the [scientific Python ecosystem](https://scientific-python.org/): + `numpy.ndarray` or `pandas.DataFrame` for data tables, `xarray.DataArray` for grids, + and `geopandas.GeoDataFrame` for geographical data. ## Quickstart @@ -69,7 +70,8 @@ For other ways to install `pygmt`, see the [full installation instructions](http ### Getting started As a starting point, you can open a [Python interpreter](https://docs.python.org/3/tutorial/interpreter.html) -or a [Jupyter notebook](https://docs.jupyter.org/en/latest/running.html), and try the following example: +or a [Jupyter notebook](https://docs.jupyter.org/en/latest/running.html), and try the +following example: ``` python import pygmt @@ -79,18 +81,18 @@ fig.text(position="MC", text="PyGMT", font="80p,Helvetica-Bold,red@75") fig.show() ``` -You should see a global map with land and water masses colored in tan and lightblue, respectively. On top, -there should be the semi-transparent text "PyGMT". For more examples, please have a look at the -[Gallery](https://www.pygmt.org/latest/gallery/index.html) and +You should see a global map with land and water masses colored in tan and lightblue, +respectively. On top, there should be the semi-transparent text "PyGMT". For more examples, +please have a look at the [Gallery](https://www.pygmt.org/latest/gallery/index.html) and [Tutorials](https://www.pygmt.org/latest/tutorials/index.html). ## Contacting us - Most discussion happens [on GitHub](https://github.com/GenericMappingTools/pygmt). - Feel free to [open an issue](https://github.com/GenericMappingTools/pygmt/issues/new) or comment on any open - issue or pull request. -- We have a [Discourse forum](https://forum.generic-mapping-tools.org/c/questions/pygmt-q-a) where you can ask - questions and leave comments. + Feel free to [open an issue](https://github.com/GenericMappingTools/pygmt/issues/new) + or comment on any open issue or pull request. +- We have a [Discourse forum](https://forum.generic-mapping-tools.org/c/questions/pygmt-q-a) + where you can ask questions and leave comments. ## Contributing @@ -109,26 +111,29 @@ 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{ @@ -162,10 +167,10 @@ research using the following BibTeX: ``` 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 From 4d60eac33d86ce922fda80c38755953fbf9393ab Mon Sep 17 00:00:00 2001 From: Dongdong Tian Date: Fri, 3 Jan 2025 08:31:07 +0800 Subject: [PATCH 32/55] clib.Session: Remove deprecated virtualfile_from_data method, use virtualfile_in instead (Deprecated since v0.13.0) (#3739) --- doc/api/index.rst | 1 - pygmt/clib/session.py | 37 ------------------------- pygmt/tests/test_clib_virtualfile_in.py | 32 --------------------- 3 files changed, 70 deletions(-) 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/pygmt/clib/session.py b/pygmt/clib/session.py index 52f710ec398..b01b279241b 100644 --- a/pygmt/clib/session.py +++ b/pygmt/clib/session.py @@ -1920,43 +1920,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/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 From 4b9314a5da7ef00b0d848ef09fe590d71ad38e90 Mon Sep 17 00:00:00 2001 From: Dongdong Tian Date: Fri, 3 Jan 2025 08:31:36 +0800 Subject: [PATCH 33/55] clib.Session: Remove deprecated open_virtual_file method, use open_virtualfile instead (Deprecated since v0.11.0) (#3738) --- pygmt/clib/session.py | 18 --------------- pygmt/tests/test_clib_virtualfiles.py | 32 --------------------------- 2 files changed, 50 deletions(-) diff --git a/pygmt/clib/session.py b/pygmt/clib/session.py index b01b279241b..f35161b7b02 100644 --- a/pygmt/clib/session.py +++ b/pygmt/clib/session.py @@ -1389,24 +1389,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 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 From af66e2ab85da2f0a1c7b7f2d64f0927ddf5d6cd4 Mon Sep 17 00:00:00 2001 From: Dongdong Tian Date: Mon, 6 Jan 2025 00:05:44 +0800 Subject: [PATCH 34/55] Add the missing separators in changelog (#3745) --- doc/changes.md | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/doc/changes.md b/doc/changes.md index 21250127374..8e7142d2fd8 100644 --- a/doc/changes.md +++ b/doc/changes.md @@ -102,6 +102,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 +282,8 @@ * [Michael Grund](https://github.com/michaelgrund) * [Wei Ji Leong](https://github.com/weiji14) +--- + ## Release v0.11.0 (2024/02/01) [![Digital Object Identifier for PyGMT v0.11.0](https://zenodo.org/badge/DOI/10.5281/zenodo.10578540.svg)](https://doi.org/10.5281/zenodo.10578540) From 7c38ce75df74695e9add69fc97c38a9c7215f354 Mon Sep 17 00:00:00 2001 From: Dongdong Tian Date: Mon, 6 Jan 2025 08:28:20 +0800 Subject: [PATCH 35/55] Use well-known labels in project URLs following PEP753 (#3743) --- pyproject.toml | 9 +++++---- 1 file changed, 5 insertions(+), 4 deletions(-) diff --git a/pyproject.toml b/pyproject.toml index 2150af08d7b..9af670df32c 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"] From 4c03bab6fbc90f7354fad28d663fd98f9df6c268 Mon Sep 17 00:00:00 2001 From: Dongdong Tian Date: Mon, 6 Jan 2025 22:57:33 +0800 Subject: [PATCH 36/55] Update the release checklist post v0.14.0 release (#3692) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Co-authored-by: Wei Ji <23487320+weiji14@users.noreply.github.com> Co-authored-by: Michael Grund <23025878+michaelgrund@users.noreply.github.com> Co-authored-by: Yvonne Fröhlich <94163266+yvonnefroehlich@users.noreply.github.com> --- .github/ISSUE_TEMPLATE/4-release_checklist.md | 18 +++++++----------- 1 file changed, 7 insertions(+), 11 deletions(-) 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 --- From e0f8e84b623149dac9a0bfe2cbf62b72cd4a4941 Mon Sep 17 00:00:00 2001 From: Dongdong Tian Date: Tue, 7 Jan 2025 07:53:54 +0800 Subject: [PATCH 37/55] Figure.plot/Figure.plot3d: Improve the docstrings for straight_line (#3720) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Co-authored-by: Yvonne Fröhlich <94163266+yvonnefroehlich@users.noreply.github.com> --- pygmt/src/plot.py | 49 +++++++++++++++++++++++++++++++++------------ pygmt/src/plot3d.py | 42 +++++++++++++++++++++++++++----------- 2 files changed, 66 insertions(+), 25 deletions(-) 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) From 448e44c2a7f6d78202666488ee58f8c839a82930 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Yvonne=20Fr=C3=B6hlich?= <94163266+yvonnefroehlich@users.noreply.github.com> Date: Tue, 7 Jan 2025 09:03:49 +0100 Subject: [PATCH 38/55] DOC/Gallery example "Choropleth map": Fix typo (#3751) --- examples/gallery/maps/choropleth_map.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/examples/gallery/maps/choropleth_map.py b/examples/gallery/maps/choropleth_map.py index 19376f3c61c..b4b3bf54e69 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 From aeaf33fc352561cdb4881804aa0ec01efd9e91cf Mon Sep 17 00:00:00 2001 From: Dongdong Tian Date: Wed, 8 Jan 2025 08:35:25 +0800 Subject: [PATCH 39/55] _load_remote_dataset: Add the "kind" attribute to explicitly specify if data is a grid or image (#3688) --- pygmt/datasets/load_remote_dataset.py | 36 +++++++++++++++++++++++---- 1 file changed, 31 insertions(+), 5 deletions(-) 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 From a9d26a8248b2579f0f1a889d6d3258137fe7c0e4 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Wed, 8 Jan 2025 08:36:33 +0800 Subject: [PATCH 40/55] Build(deps): Bump lycheeverse/lychee-action from 2.1.0 to 2.2.0 (#3753) Bumps [lycheeverse/lychee-action](https://github.com/lycheeverse/lychee-action) from 2.1.0 to 2.2.0. - [Release notes](https://github.com/lycheeverse/lychee-action/releases) - [Commits](https://github.com/lycheeverse/lychee-action/compare/v2.1.0...v2.2.0) --- updated-dependencies: - dependency-name: lycheeverse/lychee-action dependency-type: direct:production update-type: version-update:semver-minor ... Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- .github/workflows/check-links.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) 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 From 1d50ffb9f62f97fa861755e7b937c3d363bf4511 Mon Sep 17 00:00:00 2001 From: Dongdong Tian Date: Wed, 8 Jan 2025 21:24:38 +0800 Subject: [PATCH 41/55] TYP: Add return type hints for functions that return None (#3754) --- pygmt/_show_versions.py | 2 +- pygmt/clib/session.py | 18 ++++++++++++------ pygmt/figure.py | 12 ++++++------ pygmt/helpers/caching.py | 2 +- pygmt/helpers/tempfile.py | 2 +- pygmt/helpers/utils.py | 4 ++-- pygmt/session_management.py | 4 ++-- 7 files changed, 25 insertions(+), 19 deletions(-) diff --git a/pygmt/_show_versions.py b/pygmt/_show_versions.py index e529f053e46..d15bf0799c6 100644 --- a/pygmt/_show_versions.py +++ b/pygmt/_show_versions.py @@ -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/session.py b/pygmt/clib/session.py index f35161b7b02..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. 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/tempfile.py b/pygmt/helpers/tempfile.py index 6d8dfd74fec..6995be1db98 100644 --- a/pygmt/helpers/tempfile.py +++ b/pygmt/helpers/tempfile.py @@ -59,7 +59,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..b53942818c5 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. 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`. From 0ba87ba369466de605c39a020d7564be1bc3ef1f Mon Sep 17 00:00:00 2001 From: Dongdong Tian Date: Wed, 8 Jan 2025 21:33:21 +0800 Subject: [PATCH 42/55] CI: Update the doc deploy script to generate CNAME and index.html (#3749) --- .github/workflows/ci_docs.yml | 9 +++++++-- 1 file changed, 7 insertions(+), 2 deletions(-) diff --git a/.github/workflows/ci_docs.yml b/.github/workflows/ci_docs.yml index a74d4fbfcd5..06c7debb426 100644 --- a/.github/workflows/ci_docs.yml +++ b/.github/workflows/ci_docs.yml @@ -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}:" From 664a9eda6fa03adc24b9a1752c601c438528e5e3 Mon Sep 17 00:00:00 2001 From: Dongdong Tian Date: Wed, 8 Jan 2025 21:43:31 +0800 Subject: [PATCH 43/55] ruff: Ignore the B018 rule (useless expressions) in examples (#3750) --- examples/gallery/images/rgb_image.py | 2 +- examples/gallery/lines/linestrings.py | 2 +- examples/gallery/maps/choropleth_map.py | 2 +- examples/tutorials/basics/plot.py | 6 +++--- pyproject.toml | 5 ++++- 5 files changed, 10 insertions(+), 7 deletions(-) diff --git a/examples/gallery/images/rgb_image.py b/examples/gallery/images/rgb_image.py index 56d7b9d2f70..8afb2c93e25 100644 --- a/examples/gallery/images/rgb_image.py +++ b/examples/gallery/images/rgb_image.py @@ -28,7 +28,7 @@ # Subset to area of Lāhainā in EPSG:32604 coordinates image = img.rio.clip_box(minx=738000, maxx=755000, miny=2300000, maxy=2318000) image = image.load() # Force loading the DataArray into memory -image # noqa: B018 +image # %% # Plot the RGB imagery: diff --git a/examples/gallery/lines/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/maps/choropleth_map.py b/examples/gallery/maps/choropleth_map.py index b4b3bf54e69..f1cce8c3014 100644 --- a/examples/gallery/maps/choropleth_map.py +++ b/examples/gallery/maps/choropleth_map.py @@ -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/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/pyproject.toml b/pyproject.toml index 9af670df32c..61c6a541fef 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -160,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 From c7198e6f2d38edca746b87886f2e7c0933d3f2c9 Mon Sep 17 00:00:00 2001 From: Dongdong Tian Date: Thu, 9 Jan 2025 08:20:52 +0800 Subject: [PATCH 44/55] DOC: Make bullet point lists consistent in docstrings (":" and lower-case) Part II (#3752) --- pygmt/helpers/decorators.py | 70 ++++++++++++++++++------------------- pygmt/src/grdfilter.py | 14 ++++---- pygmt/src/grdgradient.py | 14 ++++---- pygmt/src/grdview.py | 10 +++--- pygmt/src/sphdistance.py | 6 ++-- pygmt/src/x2sys_cross.py | 6 ++-- pygmt/src/x2sys_init.py | 14 ++++---- 7 files changed, 67 insertions(+), 67 deletions(-) diff --git a/pygmt/helpers/decorators.py b/pygmt/helpers/decorators.py index a22d49334af..08822d42a86 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 + - **x**\|\ **X**: define a gap when there is a large enough change in the x coordinates (upper case to use projected coordinates). - - **y**\|\ **Y** - define a gap when there is a large enough + - **y**\|\ **Y**: define a gap when there is a large enough change in the y coordinates (upper case to use projected coordinates). - - **d**\|\ **D** - define a gap when there is a large enough + - **d**\|\ **D**: define a gap when there is a large enough distance between coordinates (upper case 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/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/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/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)]. From d33e5ac5286e260db534c1c8925ee544ecfa4a9e Mon Sep 17 00:00:00 2001 From: Michael Grund <23025878+michaelgrund@users.noreply.github.com> Date: Thu, 9 Jan 2025 01:54:14 +0100 Subject: [PATCH 45/55] DOC: Make usage of "lowercase" and "uppercase" consistent across all docs (#3756) --- doc/techref/patterns.md | 2 +- examples/gallery/basemaps/double_y_axes.py | 2 +- examples/gallery/embellishments/scalebar.py | 5 +++-- examples/gallery/images/cross_section.py | 2 +- examples/gallery/lines/decorated_lines.py | 2 +- examples/gallery/lines/quoted_lines.py | 2 +- examples/gallery/symbols/multi_parameter_symbols.py | 2 +- examples/projections/cyl/cyl_oblique_mercator.py | 2 +- examples/projections/nongeo/cartesian_linear.py | 2 +- examples/projections/nongeo/cartesian_logarithmic.py | 2 +- examples/projections/nongeo/cartesian_power.py | 2 +- examples/projections/nongeo/polar.py | 2 +- examples/tutorials/basics/frames.py | 4 ++-- pygmt/helpers/decorators.py | 6 +++--- pygmt/src/filter1d.py | 2 +- pygmt/src/project.py | 2 +- pygmt/src/text.py | 8 ++++---- 17 files changed, 25 insertions(+), 24 deletions(-) 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/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/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/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/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/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/pygmt/helpers/decorators.py b/pygmt/helpers/decorators.py index 08822d42a86..3c4f9dd5510 100644 --- a/pygmt/helpers/decorators.py +++ b/pygmt/helpers/decorators.py @@ -119,13 +119,13 @@ criteria. - **x**\|\ **X**: define a gap when there is a large enough - change in the x coordinates (upper case to use projected + 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 + 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 + distance between coordinates (uppercase to use projected coordinates). - **z**: define a gap when there is a large enough change in the z data. Use **+c**\ *col* to change the z data column 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/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/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 From 0bf733f54ca6f2e387954be2cbc146f7ca59739d Mon Sep 17 00:00:00 2001 From: Dongdong Tian Date: Thu, 9 Jan 2025 08:55:21 +0800 Subject: [PATCH 46/55] clib.conversion._to_numpy: Add tests for numpy array with np.datetime64 dtypes (#3687) --- pygmt/tests/test_clib_to_numpy.py | 30 ++++++++++++++++++++++++++++++ 1 file changed, 30 insertions(+) diff --git a/pygmt/tests/test_clib_to_numpy.py b/pygmt/tests/test_clib_to_numpy.py index c3bc413a0b1..79858bc04d4 100644 --- a/pygmt/tests/test_clib_to_numpy.py +++ b/pygmt/tests/test_clib_to_numpy.py @@ -153,6 +153,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. # From 9e912baec890b1af009c0eac2b344229a9285e36 Mon Sep 17 00:00:00 2001 From: Dongdong Tian Date: Thu, 9 Jan 2025 10:38:40 +0800 Subject: [PATCH 47/55] clib.converison._to_numpy: Add tests for pandas.Series with datetime dtypes (#3670) --- pygmt/clib/conversion.py | 11 +++ pygmt/tests/test_clib_to_numpy.py | 107 ++++++++++++++++++++++++++++++ 2 files changed, 118 insertions(+) diff --git a/pygmt/clib/conversion.py b/pygmt/clib/conversion.py index c54923705dc..52eec0d2479 100644 --- a/pygmt/clib/conversion.py +++ b/pygmt/clib/conversion.py @@ -192,6 +192,17 @@ 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_ array can be converted to np.str_. diff --git a/pygmt/tests/test_clib_to_numpy.py b/pygmt/tests/test_clib_to_numpy.py index 79858bc04d4..31b6c2421e8 100644 --- a/pygmt/tests/test_clib_to_numpy.py +++ b/pygmt/tests/test_clib_to_numpy.py @@ -365,6 +365,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. # From 504138458b9fde3d26f45abdce7b617c49c13e41 Mon Sep 17 00:00:00 2001 From: Wei Ji <23487320+weiji14@users.noreply.github.com> Date: Thu, 9 Jan 2025 17:37:33 +1300 Subject: [PATCH 48/55] CI: Separate jobs for publishing to TestPyPI and PyPI (#3742) --- .github/workflows/publish-to-pypi.yml | 58 ++++++++++++++++++++++----- 1 file changed, 49 insertions(+), 9 deletions(-) diff --git a/.github/workflows/publish-to-pypi.yml b/.github/workflows/publish-to-pypi.yml index 2dbc12cbef1..15c66e1842b 100644 --- a/.github/workflows/publish-to-pypi.yml +++ b/.github/workflows/publish-to-pypi.yml @@ -35,13 +35,9 @@ on: # - main jobs: - publish-pypi: - name: Publish to PyPI + build: + name: Build distribution 📦 runs-on: ubuntu-latest - permissions: - # This permission is mandatory for OIDC publishing - id-token: write - if: github.repository == 'GenericMappingTools/pygmt' steps: - name: Checkout @@ -49,6 +45,7 @@ jobs: with: # fetch all history so that setuptools-scm works fetch-depth: 0 + persist-credentials: false - name: Set up Python uses: actions/setup-python@v5.3.0 @@ -74,11 +71,54 @@ jobs: echo "Generated files:" ls -lh dist/ - - name: Publish to Test PyPI + - name: Store the distribution packages + uses: actions/upload-artifact@v4.5.0 + with: + name: python-package-distributions + path: dist/ + + publish-to-testpypi: + name: Publish Python 🐍 distribution 📦 to TestPyPI + if: github.repository == 'GenericMappingTools/pygmt' + needs: + - build + runs-on: ubuntu-latest + environment: + name: testpypi + url: https://test.pypi.org/project/pygmt + permissions: + id-token: write # IMPORTANT: mandatory for trusted OIDC publishing + + steps: + - name: Download all the dists + uses: actions/download-artifact@v4.1.8 + with: + name: python-package-distributions + path: dist/ + + - name: Publish distribution 📦 to TestPyPI uses: pypa/gh-action-pypi-publish@v1.12.3 with: repository-url: https://test.pypi.org/legacy/ - - name: Publish to PyPI - if: startsWith(github.ref, 'refs/tags') + publish-pypi: + name: Publish Python 🐍 distribution 📦 to PyPI + if: github.repository == 'GenericMappingTools/pygmt' && startsWith(github.ref, 'refs/tags/') + needs: + - build + runs-on: ubuntu-latest + environment: + name: pypi + url: https://pypi.org/project/pygmt/ + permissions: + id-token: write # IMPORTANT: mandatory for trusted OIDC publishing + + steps: + - name: Download all the dists + uses: actions/download-artifact@v4.1.8 + with: + name: python-package-distributions + path: dist/ + + - name: Publish distribution 📦 to PyPI uses: pypa/gh-action-pypi-publish@v1.12.3 From 753a06ed6b3485af95dfff833bebfc3fc55493f2 Mon Sep 17 00:00:00 2001 From: Dongdong Tian Date: Fri, 10 Jan 2025 17:46:59 +0800 Subject: [PATCH 49/55] Bump to ruff 0.9.0, apply ruff 2025 style, and ignore A005 (stdlib-module-shadowing) violations (#3763) * Ignore A005 errors * Apply ruff styling changes in 2025 * Bump to ruff 0.9.0 --- doc/conf.py | 6 ++---- environment.yml | 2 +- .../tutorials/advanced/cartesian_histograms.py | 4 ++-- pygmt/_show_versions.py | 2 +- pygmt/encodings.py | 1 + pygmt/helpers/tempfile.py | 1 + pygmt/helpers/utils.py | 5 ++--- pygmt/io.py | 1 + pygmt/src/grd2xyz.py | 3 +-- pygmt/src/select.py | 1 + .../test_clib_virtualfile_from_stringio.py | 17 +++-------------- pygmt/tests/test_grdview.py | 2 +- pygmt/tests/test_info.py | 10 ++-------- 13 files changed, 19 insertions(+), 36 deletions(-) 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/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/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/pygmt/_show_versions.py b/pygmt/_show_versions.py index d15bf0799c6..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 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/helpers/tempfile.py b/pygmt/helpers/tempfile.py index 6995be1db98..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. """ diff --git a/pygmt/helpers/utils.py b/pygmt/helpers/utils.py index b53942818c5..e32f5bbe03f 100644 --- a/pygmt/helpers/utils.py +++ b/pygmt/helpers/utils.py @@ -574,9 +574,8 @@ def launch_external_viewer(fname: str, waiting: float = 0) -> None: } 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/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/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/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_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 From 2bd524eb1e3780e598b3e8789a11be36042ee335 Mon Sep 17 00:00:00 2001 From: Dongdong Tian Date: Sat, 11 Jan 2025 13:58:20 +0800 Subject: [PATCH 50/55] Simplify doc/Makefile with sphinx-build make-mode (#3761) --- doc/Makefile | 18 ++++++++---------- 1 file changed, 8 insertions(+), 10 deletions(-) diff --git a/doc/Makefile b/doc/Makefile index b82215c44c4..5656fe80ab6 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) -D plot_gallery=0 -M html $(SPHINXOPTS) "$(SOURCEDIR)" "$(BUILDDIR)" $(SPHINXOPTS) @echo @echo "Build finished. The HTML pages are in $(BUILDDIR)/html." From 77a2d5929dcbad02c8b10e40f3920f5f3131344d Mon Sep 17 00:00:00 2001 From: Dongdong Tian Date: Sun, 12 Jan 2025 14:05:24 +0800 Subject: [PATCH 51/55] Fix the 'html-noplot' make target for docs (#3766) --- doc/Makefile | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/doc/Makefile b/doc/Makefile index 5656fe80ab6..04b1c1ab549 100644 --- a/doc/Makefile +++ b/doc/Makefile @@ -39,7 +39,7 @@ html-noplot: api @echo @echo "Building HTML files without example plots." @echo - $(SPHINXBUILD) -D plot_gallery=0 -M html $(SPHINXOPTS) "$(SOURCEDIR)" "$(BUILDDIR)" $(SPHINXOPTS) + $(SPHINXBUILD) -M html "$(SOURCEDIR)" "$(BUILDDIR)" $(SPHINXOPTS) -D plot_gallery=0 @echo @echo "Build finished. The HTML pages are in $(BUILDDIR)/html." From 8b9ec5a6cea7b1352b52783534fe76bc9f99dd77 Mon Sep 17 00:00:00 2001 From: Dongdong Tian Date: Mon, 13 Jan 2025 08:11:09 +0800 Subject: [PATCH 52/55] Fix the bug of converting Python sequence of datetime-like objects (#3760) Convert unrecognized objects to datetime before converting to string dtype --- pygmt/clib/conversion.py | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/pygmt/clib/conversion.py b/pygmt/clib/conversion.py index 52eec0d2479..7823aa32103 100644 --- a/pygmt/clib/conversion.py +++ b/pygmt/clib/conversion.py @@ -205,6 +205,11 @@ def _to_numpy(data: Any) -> np.ndarray: 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): From 900e8d4f3cda1f29eb0a5c6d7cabe7cc6ae1f655 Mon Sep 17 00:00:00 2001 From: Dongdong Tian Date: Mon, 13 Jan 2025 08:22:32 +0800 Subject: [PATCH 53/55] clib.conversion._to_numpy: Add tests for Python sequence of datetime-like objects (#3758) Co-authored-by: Wei Ji <23487320+weiji14@users.noreply.github.com> --- .../tests/baseline/test_plot_datetime.png.dvc | 4 +- pygmt/tests/test_clib_to_numpy.py | 77 +++++++++++++++++-- pygmt/tests/test_plot.py | 7 +- 3 files changed, 80 insertions(+), 8 deletions(-) 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 31b6c2421e8..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. # @@ -603,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) @@ -649,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_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 From d5e775016f70c0f0949947dd457178542abe0a35 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Wed, 15 Jan 2025 09:46:49 +1300 Subject: [PATCH 54/55] Build(deps): Bump mamba-org/setup-micromamba from 2.0.3 to 2.0.4 (#3769) Bumps [mamba-org/setup-micromamba](https://github.com/mamba-org/setup-micromamba) from 2.0.3 to 2.0.4. - [Release notes](https://github.com/mamba-org/setup-micromamba/releases) - [Commits](https://github.com/mamba-org/setup-micromamba/compare/v2.0.3...v2.0.4) --- updated-dependencies: - dependency-name: mamba-org/setup-micromamba dependency-type: direct:production update-type: version-update:semver-patch ... Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- .github/workflows/benchmarks.yml | 2 +- .github/workflows/cache_data.yaml | 2 +- .github/workflows/ci_docs.yml | 2 +- .github/workflows/ci_doctests.yaml | 2 +- .github/workflows/ci_tests.yaml | 2 +- .github/workflows/ci_tests_dev.yaml | 2 +- .github/workflows/ci_tests_legacy.yaml | 2 +- 7 files changed, 7 insertions(+), 7 deletions(-) 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..0ce6ac025de 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: | diff --git a/.github/workflows/ci_docs.yml b/.github/workflows/ci_docs.yml index 06c7debb426..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: | 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 e155d7bd95c..2fad4df7a51 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: | diff --git a/.github/workflows/ci_tests_dev.yaml b/.github/workflows/ci_tests_dev.yaml index 6a026a5099f..c222de5d5a6 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: | diff --git a/.github/workflows/ci_tests_legacy.yaml b/.github/workflows/ci_tests_legacy.yaml index 1af5e00cd38..0c4ae574235 100644 --- a/.github/workflows/ci_tests_legacy.yaml +++ b/.github/workflows/ci_tests_legacy.yaml @@ -51,7 +51,7 @@ jobs: # Install Micromamba with conda-forge dependencies - name: Setup Micromamba - uses: mamba-org/setup-micromamba@v2.0.3 + uses: mamba-org/setup-micromamba@v2.0.4 with: environment-name: pygmt condarc: | From 540bba1db9d34191c880ccdd1289d7fbcb1264c8 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Wed, 15 Jan 2025 09:48:29 +1300 Subject: [PATCH 55/55] Build(deps): Bump actions/upload-artifact from 4.5.0 to 4.6.0 (#3770) Bumps [actions/upload-artifact](https://github.com/actions/upload-artifact) from 4.5.0 to 4.6.0. - [Release notes](https://github.com/actions/upload-artifact/releases) - [Commits](https://github.com/actions/upload-artifact/compare/v4.5.0...v4.6.0) --- updated-dependencies: - dependency-name: actions/upload-artifact dependency-type: direct:production update-type: version-update:semver-minor ... Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- .github/workflows/cache_data.yaml | 2 +- .github/workflows/ci_tests.yaml | 2 +- .github/workflows/ci_tests_dev.yaml | 2 +- .github/workflows/publish-to-pypi.yml | 2 +- 4 files changed, 4 insertions(+), 4 deletions(-) diff --git a/.github/workflows/cache_data.yaml b/.github/workflows/cache_data.yaml index 0ce6ac025de..46c37211424 100644 --- a/.github/workflows/cache_data.yaml +++ b/.github/workflows/cache_data.yaml @@ -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/ci_tests.yaml b/.github/workflows/ci_tests.yaml index 2fad4df7a51..44416d210e0 100644 --- a/.github/workflows/ci_tests.yaml +++ b/.github/workflows/ci_tests.yaml @@ -177,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 }} diff --git a/.github/workflows/ci_tests_dev.yaml b/.github/workflows/ci_tests_dev.yaml index c222de5d5a6..6932a0d520b 100644 --- a/.github/workflows/ci_tests_dev.yaml +++ b/.github/workflows/ci_tests_dev.yaml @@ -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/publish-to-pypi.yml b/.github/workflows/publish-to-pypi.yml index 15c66e1842b..917353ee85a 100644 --- a/.github/workflows/publish-to-pypi.yml +++ b/.github/workflows/publish-to-pypi.yml @@ -72,7 +72,7 @@ jobs: ls -lh dist/ - name: Store the distribution packages - uses: actions/upload-artifact@v4.5.0 + uses: actions/upload-artifact@v4.6.0 with: name: python-package-distributions path: dist/