diff --git a/.github/ISSUE_TEMPLATE/bug_report.md b/.github/ISSUE_TEMPLATE/bug_report.md index ed1bdeaaa3f..4325de1fb8c 100644 --- a/.github/ISSUE_TEMPLATE/bug_report.md +++ b/.github/ISSUE_TEMPLATE/bug_report.md @@ -1,32 +1,32 @@ ---- -name: Bug report -about: Report a problem/bug to help us improve -title: '' -labels: bug -assignees: '' - ---- - -**Description of the problem** - - - -**Full code that generated the error** - -```python -PASTE CODE HERE -``` - -**Full error message** - -``` -PASTE ERROR MESSAGE HERE -``` - -**System information** - -Please paste the output of `python -c "import pygmt; pygmt.show_versions()"`: - -``` -PASTE THE OUTPUT HERE -``` +--- +name: Bug report +about: Report a problem/bug to help us improve +title: '' +labels: bug +assignees: '' + +--- + +**Description of the problem** + + + +**Full code that generated the error** + +```python +PASTE CODE HERE +``` + +**Full error message** + +``` +PASTE ERROR MESSAGE HERE +``` + +**System information** + +Please paste the output of `python -c "import pygmt; pygmt.show_versions()"`: + +``` +PASTE THE OUTPUT HERE +``` diff --git a/.github/ISSUE_TEMPLATE/config.yml b/.github/ISSUE_TEMPLATE/config.yml index 13087693b57..d324bea7da4 100644 --- a/.github/ISSUE_TEMPLATE/config.yml +++ b/.github/ISSUE_TEMPLATE/config.yml @@ -1,5 +1,5 @@ -blank_issues_enabled: true -contact_links: - - name: GMT Community Forum - url: https://forum.generic-mapping-tools.org/c/questions/pygmt-q-a - about: Please ask questions here or find answers to common problems. +blank_issues_enabled: true +contact_links: + - name: GMT Community Forum + url: https://forum.generic-mapping-tools.org/c/questions/pygmt-q-a + about: Please ask questions here or find answers to common problems. diff --git a/.github/ISSUE_TEMPLATE/feature_request.md b/.github/ISSUE_TEMPLATE/feature_request.md index fd67e0b225a..f9c4e8dcd79 100644 --- a/.github/ISSUE_TEMPLATE/feature_request.md +++ b/.github/ISSUE_TEMPLATE/feature_request.md @@ -1,19 +1,19 @@ ---- -name: Feature request -about: Request the addition of a new feature/functionality -title: '' -labels: feature request -assignees: '' - ---- - -**Description of the desired feature** - - - - - - -**Are you willing to help implement and maintain this feature?** Yes/No - - +--- +name: Feature request +about: Request the addition of a new feature/functionality +title: '' +labels: feature request +assignees: '' + +--- + +**Description of the desired feature** + + + + + + +**Are you willing to help implement and maintain this feature?** Yes/No + + diff --git a/.github/ISSUE_TEMPLATE/release_checklist.md b/.github/ISSUE_TEMPLATE/release_checklist.md index f3479658403..7bcdd8f3bf0 100644 --- a/.github/ISSUE_TEMPLATE/release_checklist.md +++ b/.github/ISSUE_TEMPLATE/release_checklist.md @@ -1,44 +1,44 @@ ---- -name: PyGMT release checklist -about: Checklist for a new PyGMT release. -title: Release PyGMT vX.Y.Z -labels: maintenance -assignees: '' - ---- - -**Release**: [v0.x.x](https://github.com/GenericMappingTools/pygmt/milestones/?) -**Scheduled Date**: YYYY/MM/DD -**Pull request due date**: YYYY/MM/DD - -**Priority PRs/issues to complete prior to release** -- [ ] Wrap X () -- [ ] Wrap Y () - -**Before release**: -- [ ] Reserve a DOI on [Zenodo](https://zenodo.org) by clicking on "New Version" -- [ ] Finish up 'Changelog entry for v0.x.x' Pull Request: - - [ ] Add a new entry in `doc/_static/version_switch.js` for documentation switcher - - [ ] Update citation information https://github.com/GenericMappingTools/pygmt#citing-pygmt - - [ ] Add the documentation link https://github.com/GenericMappingTools/pygmt#documentation-for-other-versions - - [ ] Add compatibility information https://github.com/GenericMappingTools/pygmt#compatibility-with-python-and-gmt-versions - - [ ] Copy draft changelog from Release Drafter and edit it to look nice - -**Release**: -- [ ] At the [PyGMT release page on GitHub](https://github.com/GenericMappingTools/pygmt/releases): - - [ ] 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 -- [ ] Manually upload the pygmt-vX.Y.Z.zip file to https://zenodo.org/deposit, ensure that it is filed under the correct reserved DOI - -**After release**: -- [ ] Update conda-forge [pygmt-feedstock](https://github.com/conda-forge/pygmt-feedstock) [Usually done automatically by conda-forge's bot] -- [ ] Bump PyGMT version on https://github.com/GenericMappingTools/try-gmt -- [ ] Announce the release on: - - [ ] GMT [forum](https://forum.generic-mapping-tools.org/c/news/) - - [ ] [Major/Minor releases only] GMT [website](https://github.com/GenericMappingTools/website) (News) - - [ ] [ResearchGate](https://www.researchgate.net/project/PyGMT-A-Python-interface-for-the-Generic-Mapping-Tools) - ---- - -- [ ] Party :tada: (don't tick before all other checkboxes are ticked!) +--- +name: PyGMT release checklist +about: Checklist for a new PyGMT release. +title: Release PyGMT vX.Y.Z +labels: maintenance +assignees: '' + +--- + +**Release**: [v0.x.x](https://github.com/GenericMappingTools/pygmt/milestones/?) +**Scheduled Date**: YYYY/MM/DD +**Pull request due date**: YYYY/MM/DD + +**Priority PRs/issues to complete prior to release** +- [ ] Wrap X () +- [ ] Wrap Y () + +**Before release**: +- [ ] Reserve a DOI on [Zenodo](https://zenodo.org) by clicking on "New Version" +- [ ] Finish up 'Changelog entry for v0.x.x' Pull Request: + - [ ] Add a new entry in `doc/_static/version_switch.js` for documentation switcher + - [ ] Update citation information https://github.com/GenericMappingTools/pygmt#citing-pygmt + - [ ] Add the documentation link https://github.com/GenericMappingTools/pygmt#documentation-for-other-versions + - [ ] Add compatibility information https://github.com/GenericMappingTools/pygmt#compatibility-with-python-and-gmt-versions + - [ ] Copy draft changelog from Release Drafter and edit it to look nice + +**Release**: +- [ ] At the [PyGMT release page on GitHub](https://github.com/GenericMappingTools/pygmt/releases): + - [ ] 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 +- [ ] Manually upload the pygmt-vX.Y.Z.zip file to https://zenodo.org/deposit, ensure that it is filed under the correct reserved DOI + +**After release**: +- [ ] Update conda-forge [pygmt-feedstock](https://github.com/conda-forge/pygmt-feedstock) [Usually done automatically by conda-forge's bot] +- [ ] Bump PyGMT version on https://github.com/GenericMappingTools/try-gmt +- [ ] Announce the release on: + - [ ] GMT [forum](https://forum.generic-mapping-tools.org/c/news/) + - [ ] [Major/Minor releases only] GMT [website](https://github.com/GenericMappingTools/website) (News) + - [ ] [ResearchGate](https://www.researchgate.net/project/PyGMT-A-Python-interface-for-the-Generic-Mapping-Tools) + +--- + +- [ ] Party :tada: (don't tick before all other checkboxes are ticked!) diff --git a/.github/PULL_REQUEST_TEMPLATE.md b/.github/PULL_REQUEST_TEMPLATE.md index 8e3982647e5..150c79b2528 100644 --- a/.github/PULL_REQUEST_TEMPLATE.md +++ b/.github/PULL_REQUEST_TEMPLATE.md @@ -1,25 +1,25 @@ -**Description of proposed changes** - - - - - - -Fixes # - - -**Reminders** - -- [ ] Run `make format` and `make check` to make sure the code follows the style guide. -- [ ] Add tests for new features or tests that would have caught the bug that you're fixing. -- [ ] Add new public functions/methods/classes to `doc/api/index.rst`. -- [ ] Write detailed docstrings for all functions/methods. -- [ ] If adding new functionality, add an example to docstrings or tutorials. - -**Slash Commands** - -You can write slash commands (`/command`) in the first line of a comment to perform -specific operations. Supported slash commands are: - -- `/format`: automatically format and lint the code -- `/test-gmt-dev`: run full tests on the latest GMT development version +**Description of proposed changes** + + + + + + +Fixes # + + +**Reminders** + +- [ ] Run `make format` and `make check` to make sure the code follows the style guide. +- [ ] Add tests for new features or tests that would have caught the bug that you're fixing. +- [ ] Add new public functions/methods/classes to `doc/api/index.rst`. +- [ ] Write detailed docstrings for all functions/methods. +- [ ] If adding new functionality, add an example to docstrings or tutorials. + +**Slash Commands** + +You can write slash commands (`/command`) in the first line of a comment to perform +specific operations. Supported slash commands are: + +- `/format`: automatically format and lint the code +- `/test-gmt-dev`: run full tests on the latest GMT development version diff --git a/.github/codecov.yml b/.github/codecov.yml index 5f6138a7852..0a0dbb12e4e 100644 --- a/.github/codecov.yml +++ b/.github/codecov.yml @@ -1,14 +1,14 @@ -codecov: - notify: - require_ci_to_pass: no - -coverage: - status: - patch: - default: - target: '90' - if_no_uploads: error - if_not_found: success - if_ci_failed: failure - -comment: off +codecov: + notify: + require_ci_to_pass: no + +coverage: + status: + patch: + default: + target: '90' + if_no_uploads: error + if_not_found: success + if_ci_failed: failure + +comment: off diff --git a/.github/dependabot.yml b/.github/dependabot.yml index 639bdce3e44..feaa507c6f2 100644 --- a/.github/dependabot.yml +++ b/.github/dependabot.yml @@ -1,17 +1,17 @@ -# Set update schedule for GitHub Actions - -version: 2 -updates: - - - package-ecosystem: "github-actions" - directory: "/" - schedule: - # Check for updates to GitHub Actions every weekday - interval: "weekly" - day: "tuesday" - # Allow up to 2 open pull requests at a time - open-pull-requests-limit: 2 - # Specify labels for pull requests - labels: - - "maintenance" - - "skip-changelog" +# Set update schedule for GitHub Actions + +version: 2 +updates: + + - package-ecosystem: "github-actions" + directory: "/" + schedule: + # Check for updates to GitHub Actions every weekday + interval: "weekly" + day: "tuesday" + # Allow up to 2 open pull requests at a time + open-pull-requests-limit: 2 + # Specify labels for pull requests + labels: + - "maintenance" + - "skip-changelog" diff --git a/.github/release-drafter.yml b/.github/release-drafter.yml index bbdbfb68b74..995a1c9e9d8 100644 --- a/.github/release-drafter.yml +++ b/.github/release-drafter.yml @@ -1,38 +1,38 @@ -name-template: 'v$RESOLVED_VERSION' -tag-template: 'v$RESOLVED_VERSION' -version-resolver: - minor: - labels: - - 'feature' - default: patch -categories: - - title: 'New Features' - label: 'feature' - - title: 'Enhancements' - label: 'enhancement' - - title: 'Deprecations' - label: 'deprecation' - - title: 'Bug Fixes' - label: 'bug' - - title: 'Documentation' - label: 'documentation' - - title: 'Maintenance' - label: 'maintenance' -exclude-labels: - - 'skip-changelog' -category-template: '### $TITLE' -change-template: '* $TITLE ([#$NUMBER]($URL))' -template: | - ## Release v$NEXT_PATCH_VERSION (20YY/MM/DD) - - [![Digital Object Identifier for PyGMT v$NEXT_PATCH_VERSION](https://zenodo.org/badge/DOI/10.5281/zenodo.3781524.svg)](https://doi.org/10.5281/zenodo.3781524) - - ### Highlights - - * - - $CHANGES - - ### Contributors - - $CONTRIBUTORS +name-template: 'v$RESOLVED_VERSION' +tag-template: 'v$RESOLVED_VERSION' +version-resolver: + minor: + labels: + - 'feature' + default: patch +categories: + - title: 'New Features' + label: 'feature' + - title: 'Enhancements' + label: 'enhancement' + - title: 'Deprecations' + label: 'deprecation' + - title: 'Bug Fixes' + label: 'bug' + - title: 'Documentation' + label: 'documentation' + - title: 'Maintenance' + label: 'maintenance' +exclude-labels: + - 'skip-changelog' +category-template: '### $TITLE' +change-template: '* $TITLE ([#$NUMBER]($URL))' +template: | + ## Release v$NEXT_PATCH_VERSION (20YY/MM/DD) + + [![Digital Object Identifier for PyGMT v$NEXT_PATCH_VERSION](https://zenodo.org/badge/DOI/10.5281/zenodo.3781524.svg)](https://doi.org/10.5281/zenodo.3781524) + + ### Highlights + + * + + $CHANGES + + ### Contributors + + $CONTRIBUTORS diff --git a/.github/workflows/cache_data.yaml b/.github/workflows/cache_data.yaml index 259c1003ff3..432a8f45c4e 100644 --- a/.github/workflows/cache_data.yaml +++ b/.github/workflows/cache_data.yaml @@ -1,53 +1,53 @@ -# This workflow gets and uploads the GMT data artifacts used in the PyGMT CI tests -name: Cache data - -on: - # Uncomment the 'pull_request' line below to manually re-cache data artifacts - # pull_request: - # Schedule runs on 12 noon every Sunday - schedule: - - cron: '0 12 * * 0' - -jobs: - gmt_cache: - name: Cache GMT artifacts - runs-on: macOS-latest - - steps: - # Setup Miniconda - - name: Setup Miniconda - uses: conda-incubator/setup-miniconda@v2.0.1 - with: - channels: conda-forge - miniconda-version: "latest" - - # Install GMT - - name: Install GMT - shell: bash -l {0} - run: conda install -c conda-forge gmt=6.1.1 - - # Download remote files - - name: Download remote data - shell: bash -l {0} - run: | - gmt which -Ga @earth_relief_10m_p @earth_relief_10m_g \ - @earth_relief_30m_p @earth_relief_30m_g \ - @earth_relief_01d_p @earth_relief_01d_g \ - @earth_relief_05m_p @earth_relief_05m_g - # Download one tile of the 03s srtm data. - # @N35E135.earth_relief_03s_g.nc is for internal use only. - # The naming scheme may change. - # DO NOT USE IT IN SCRIPTS. - gmt which -Ga @N35E135.earth_relief_03s_g.nc - gmt which -Ga @ridge.txt @Table_5_11.txt @test.dat.nc \ - @tut_bathy.nc @tut_quakes.ngdc @tut_ship.xyz \ - @usgs_quakes_22.txt - - # Upload the downloaded files as artifacts to GitHub - - name: Upload artifacts to GitHub - uses: actions/upload-artifact@v2 - with: - name: gmt-cache - path: | - ~/.gmt/cache - ~/.gmt/server +# This workflow gets and uploads the GMT data artifacts used in the PyGMT CI tests +name: Cache data + +on: + # Uncomment the 'pull_request' line below to manually re-cache data artifacts + # pull_request: + # Schedule runs on 12 noon every Sunday + schedule: + - cron: '0 12 * * 0' + +jobs: + gmt_cache: + name: Cache GMT artifacts + runs-on: macOS-latest + + steps: + # Setup Miniconda + - name: Setup Miniconda + uses: conda-incubator/setup-miniconda@v2.0.1 + with: + channels: conda-forge + miniconda-version: "latest" + + # Install GMT + - name: Install GMT + shell: bash -l {0} + run: conda install -c conda-forge gmt=6.1.1 + + # Download remote files + - name: Download remote data + shell: bash -l {0} + run: | + gmt which -Ga @earth_relief_10m_p @earth_relief_10m_g \ + @earth_relief_30m_p @earth_relief_30m_g \ + @earth_relief_01d_p @earth_relief_01d_g \ + @earth_relief_05m_p @earth_relief_05m_g + # Download one tile of the 03s srtm data. + # @N35E135.earth_relief_03s_g.nc is for internal use only. + # The naming scheme may change. + # DO NOT USE IT IN SCRIPTS. + gmt which -Ga @N35E135.earth_relief_03s_g.nc + gmt which -Ga @ridge.txt @Table_5_11.txt @test.dat.nc \ + @tut_bathy.nc @tut_quakes.ngdc @tut_ship.xyz \ + @usgs_quakes_22.txt + + # Upload the downloaded files as artifacts to GitHub + - name: Upload artifacts to GitHub + uses: actions/upload-artifact@v2 + with: + name: gmt-cache + path: | + ~/.gmt/cache + ~/.gmt/server diff --git a/.github/workflows/check-links.yml b/.github/workflows/check-links.yml index ebbc9d11279..c9cbcc55cad 100644 --- a/.github/workflows/check-links.yml +++ b/.github/workflows/check-links.yml @@ -1,55 +1,55 @@ -# This workflow checks the links in plaintext and HTML files -name: Check Links - -on: - # Uncomment the 'pull_request' line below to trigger the workflow in PR - # pull_request: - # Schedule runs on 12 noon every Sunday - schedule: - - cron: '0 12 * * 0' - -jobs: - check_links: - name: Check Links - runs-on: ubuntu-latest - - steps: - - name: Checkout the repository - uses: actions/checkout@v2.3.4 - with: - path: repository - - - name: Checkout the documentation - uses: actions/checkout@v2.3.4 - with: - ref: gh-pages - path: documentation - - - name: Link Checker - uses: lycheeverse/lychee-action@v1.0.4 - with: - # 429: Too many requests - args: > - --accept 429 - --exclude "^https://zenodo.org/badge/DOI/$" - --exclude "^https://github.com/GenericMappingTools/pygmt/pull/[0-9]*$" - --exclude "^https://github.com/GenericMappingTools/pygmt/issues/[0-9]*$" - --exclude "^https://www.generic-mapping-tools.org/_static/gmt-logo.png/$" - --exclude "^https://www.generic-mapping-tools.org/_static/gmt-logo.png/n/n$" - --exclude "^``@ridge.txt$" - --exclude "^git" - --exclude "^file://" - --exclude "^https://docs.generic-mapping-tools.org/latest/%s$" - --verbose - "repository/**/*.rst" - "repository/**/*.md" - "repository/**/*.py" - "documentation/dev/**/*.html" - env: - GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} - - - name: Create Issue From File - uses: peter-evans/create-issue-from-file@v2.3.2 - with: - title: Link Checker Report - content-filepath: ./lychee/out.md +# This workflow checks the links in plaintext and HTML files +name: Check Links + +on: + # Uncomment the 'pull_request' line below to trigger the workflow in PR + # pull_request: + # Schedule runs on 12 noon every Sunday + schedule: + - cron: '0 12 * * 0' + +jobs: + check_links: + name: Check Links + runs-on: ubuntu-latest + + steps: + - name: Checkout the repository + uses: actions/checkout@v2.3.4 + with: + path: repository + + - name: Checkout the documentation + uses: actions/checkout@v2.3.4 + with: + ref: gh-pages + path: documentation + + - name: Link Checker + uses: lycheeverse/lychee-action@v1.0.4 + with: + # 429: Too many requests + args: > + --accept 429 + --exclude "^https://zenodo.org/badge/DOI/$" + --exclude "^https://github.com/GenericMappingTools/pygmt/pull/[0-9]*$" + --exclude "^https://github.com/GenericMappingTools/pygmt/issues/[0-9]*$" + --exclude "^https://www.generic-mapping-tools.org/_static/gmt-logo.png/$" + --exclude "^https://www.generic-mapping-tools.org/_static/gmt-logo.png/n/n$" + --exclude "^``@ridge.txt$" + --exclude "^git" + --exclude "^file://" + --exclude "^https://docs.generic-mapping-tools.org/latest/%s$" + --verbose + "repository/**/*.rst" + "repository/**/*.md" + "repository/**/*.py" + "documentation/dev/**/*.html" + env: + GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} + + - name: Create Issue From File + uses: peter-evans/create-issue-from-file@v2.3.2 + with: + title: Link Checker Report + content-filepath: ./lychee/out.md diff --git a/.github/workflows/ci_tests.yaml b/.github/workflows/ci_tests.yaml index b0c6341e340..791ebfbbedb 100644 --- a/.github/workflows/ci_tests.yaml +++ b/.github/workflows/ci_tests.yaml @@ -1,198 +1,198 @@ -# This workflow installs PyGMT dependencies, build documentation and run tests -# For more information see: https://help.github.com/actions/language-and-framework-guides/using-python-with-github-actions - -name: Tests - -on: - push: - branches: [ master ] - pull_request: - types: [opened, reopened, synchronize, ready_for_review] - paths-ignore: - - 'doc/**' - - '*.md' - - '*.json' - - 'README.rst' - - 'LICENSE.txt' - release: - types: - - published - # Schedule daily tests - schedule: - - cron: '0 0 * * *' - -jobs: - test: - name: ${{ matrix.os }} - Python ${{ matrix.python-version }} - runs-on: ${{ matrix.os }} - strategy: - fail-fast: false - matrix: - python-version: [3.7, 3.8, 3.9] - os: [ubuntu-latest, macOS-latest, windows-latest] - # Is it a draft Pull Request (true or false)? - isDraft: - - ${{ github.event.pull_request.draft }} - # Only run one job (Ubuntu + Python 3.9) for draft PRs - exclude: - - os: macOS-latest - isDraft: true - - os: windows-latest - isDraft: true - - os: ubuntu-latest - python-version: 3.7 - isDraft: true - - os: ubuntu-latest - python-version: 3.8 - isDraft: true - - # environmental variables used in coverage - env: - OS: ${{ matrix.os }} - PYTHON: ${{ matrix.python-version }} - - steps: - # Cancel previous runs that are not completed - - name: Cancel Previous Runs - uses: styfle/cancel-workflow-action@0.8.0 - with: - access_token: ${{ github.token }} - - # Checkout current git repository - - name: Checkout - uses: actions/checkout@v2.3.4 - with: - # fecth all history so that setuptools-scm works - fetch-depth: 0 - - # Setup Miniconda - - name: Setup Miniconda - uses: conda-incubator/setup-miniconda@v2.0.1 - with: - activate-environment: pygmt - python-version: ${{ matrix.python-version }} - channels: conda-forge - miniconda-version: "latest" - - # Install GMT and other required dependencies from conda-forge - - name: Install dependencies - shell: bash -l {0} - run: conda env update --file environment.yml - - # Show installed pkg information for postmortem diagnostic - - name: List installed packages - shell: bash -l {0} - run: conda list - - # Download cached remote files (artifacts) from GitHub - - name: Download remote data from GitHub - uses: dawidd6/action-download-artifact@v2.11.1 - with: - workflow: cache_data.yaml - workflow_conclusion: success - name: gmt-cache - path: .gmt - - # Move downloaded files to ~/.gmt directory and list them - - name: Move and list downloaded remote files - shell: bash -l {0} - run: | - mkdir -p ~/.gmt - mv .gmt/* ~/.gmt - # Change modification times of the two files, so GMT won't refresh it - touch ~/.gmt/server/gmt_data_server.txt ~/.gmt/server/gmt_hash_server.txt - ls -lhR ~/.gmt - - # Install the package that we want to test - - name: Install the package - shell: bash -l {0} - run: | - python setup.py sdist --formats=zip - pip install dist/* - - # Run the tests - - name: Test with pytest - shell: bash -l {0} - run: make test PYTEST_EXTRA="-r P" - - # Upload diff images on test failure - - name: Upload diff images if any test fails - uses: actions/upload-artifact@v2 - if: ${{ failure() }} - with: - name: artifact-${{ runner.os }}-${{ matrix.python-version }} - path: tmp-test-dir-with-unique-name - - # Build the documentation - - name: Build the documentation - shell: bash -l {0} - run: make -C doc clean all - - # Upload coverage to Codecov - - name: Upload coverage to Codecov - uses: codecov/codecov-action@v1.2.1 - with: - file: ./coverage.xml # optional - env_vars: OS,PYTHON - fail_ci_if_error: true - - - name: Checkout the gh-pages branch - uses: actions/checkout@28c7f3d2b5162b5ddd3dfd9a45aa55eaf396478b - with: - ref: gh-pages - # Checkout to this folder instead of the current one - path: deploy - # Download the entire history - fetch-depth: 0 - if: (github.event_name == 'release' || github.event_name == 'push') && (matrix.os == 'ubuntu-latest') && (matrix.python-version == '3.9') - - - name: Push the built HTML to gh-pages - run: | - # Detect if this is a release or from the master branch - if [[ "${GITHUB_EVENT_NAME}" == "release" ]]; then - # Get the tag name without the "refs/tags/" part - version="${GITHUB_REF#refs/*/}" - else - version=dev - fi - echo "Deploying version: $version" - # Make the new commit message. Needs to happen before cd into deploy - # 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 - touch .nojekyll - # 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}:" - cp -Rvf ../doc/_build/html/ ${version}/ - # If this is a new release, update the link from /latest to it - if [[ "${version}" != "dev" ]]; then - echo -e "\nSetup link from ${version} to 'latest'." - rm -f latest - ln -sf ${version} latest - fi - # Stage the commit - git add -A . - echo -e "\nChanges to be applied:" - git status - # Configure git to be the GitHub Actions account - git config user.email "github-actions[bot]@users.noreply.github.com" - git config user.name "github-actions[bot]" - # If this is a dev build and the last commit was from a dev build - # (detect if "dev" was in the previous commit message), reuse the - # same commit - if [[ "${version}" == "dev" && `git log -1 --format='%s'` == *"dev"* ]]; then - echo -e "\nAmending last commit:" - git commit --amend --reset-author -m "$message" - else - echo -e "\nMaking a new commit:" - git commit -m "$message" - fi - # Make the push quiet just in case there is anything that could leak - # sensitive information. - echo -e "\nPushing changes to gh-pages." - git push -fq origin gh-pages 2>&1 >/dev/null - echo -e "\nFinished uploading generated files." - if: (github.event_name == 'release' || github.event_name == 'push') && (matrix.os == 'ubuntu-latest') && (matrix.python-version == '3.9') +# This workflow installs PyGMT dependencies, build documentation and run tests +# For more information see: https://help.github.com/actions/language-and-framework-guides/using-python-with-github-actions + +name: Tests + +on: + push: + branches: [ master ] + pull_request: + types: [opened, reopened, synchronize, ready_for_review] + paths-ignore: + - 'doc/**' + - '*.md' + - '*.json' + - 'README.rst' + - 'LICENSE.txt' + release: + types: + - published + # Schedule daily tests + schedule: + - cron: '0 0 * * *' + +jobs: + test: + name: ${{ matrix.os }} - Python ${{ matrix.python-version }} + runs-on: ${{ matrix.os }} + strategy: + fail-fast: false + matrix: + python-version: [3.7, 3.8, 3.9] + os: [ubuntu-latest, macOS-latest, windows-latest] + # Is it a draft Pull Request (true or false)? + isDraft: + - ${{ github.event.pull_request.draft }} + # Only run one job (Ubuntu + Python 3.9) for draft PRs + exclude: + - os: macOS-latest + isDraft: true + - os: windows-latest + isDraft: true + - os: ubuntu-latest + python-version: 3.7 + isDraft: true + - os: ubuntu-latest + python-version: 3.8 + isDraft: true + + # environmental variables used in coverage + env: + OS: ${{ matrix.os }} + PYTHON: ${{ matrix.python-version }} + + steps: + # Cancel previous runs that are not completed + - name: Cancel Previous Runs + uses: styfle/cancel-workflow-action@0.8.0 + with: + access_token: ${{ github.token }} + + # Checkout current git repository + - name: Checkout + uses: actions/checkout@v2.3.4 + with: + # fecth all history so that setuptools-scm works + fetch-depth: 0 + + # Setup Miniconda + - name: Setup Miniconda + uses: conda-incubator/setup-miniconda@v2.0.1 + with: + activate-environment: pygmt + python-version: ${{ matrix.python-version }} + channels: conda-forge + miniconda-version: "latest" + + # Install GMT and other required dependencies from conda-forge + - name: Install dependencies + shell: bash -l {0} + run: conda env update --file environment.yml + + # Show installed pkg information for postmortem diagnostic + - name: List installed packages + shell: bash -l {0} + run: conda list + + # Download cached remote files (artifacts) from GitHub + - name: Download remote data from GitHub + uses: dawidd6/action-download-artifact@v2.11.1 + with: + workflow: cache_data.yaml + workflow_conclusion: success + name: gmt-cache + path: .gmt + + # Move downloaded files to ~/.gmt directory and list them + - name: Move and list downloaded remote files + shell: bash -l {0} + run: | + mkdir -p ~/.gmt + mv .gmt/* ~/.gmt + # Change modification times of the two files, so GMT won't refresh it + touch ~/.gmt/server/gmt_data_server.txt ~/.gmt/server/gmt_hash_server.txt + ls -lhR ~/.gmt + + # Install the package that we want to test + - name: Install the package + shell: bash -l {0} + run: | + python setup.py sdist --formats=zip + pip install dist/* + + # Run the tests + - name: Test with pytest + shell: bash -l {0} + run: make test PYTEST_EXTRA="-r P" + + # Upload diff images on test failure + - name: Upload diff images if any test fails + uses: actions/upload-artifact@v2 + if: ${{ failure() }} + with: + name: artifact-${{ runner.os }}-${{ matrix.python-version }} + path: tmp-test-dir-with-unique-name + + # Build the documentation + - name: Build the documentation + shell: bash -l {0} + run: make -C doc clean all + + # Upload coverage to Codecov + - name: Upload coverage to Codecov + uses: codecov/codecov-action@v1.2.1 + with: + file: ./coverage.xml # optional + env_vars: OS,PYTHON + fail_ci_if_error: true + + - name: Checkout the gh-pages branch + uses: actions/checkout@28c7f3d2b5162b5ddd3dfd9a45aa55eaf396478b + with: + ref: gh-pages + # Checkout to this folder instead of the current one + path: deploy + # Download the entire history + fetch-depth: 0 + if: (github.event_name == 'release' || github.event_name == 'push') && (matrix.os == 'ubuntu-latest') && (matrix.python-version == '3.9') + + - name: Push the built HTML to gh-pages + run: | + # Detect if this is a release or from the master branch + if [[ "${GITHUB_EVENT_NAME}" == "release" ]]; then + # Get the tag name without the "refs/tags/" part + version="${GITHUB_REF#refs/*/}" + else + version=dev + fi + echo "Deploying version: $version" + # Make the new commit message. Needs to happen before cd into deploy + # 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 + touch .nojekyll + # 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}:" + cp -Rvf ../doc/_build/html/ ${version}/ + # If this is a new release, update the link from /latest to it + if [[ "${version}" != "dev" ]]; then + echo -e "\nSetup link from ${version} to 'latest'." + rm -f latest + ln -sf ${version} latest + fi + # Stage the commit + git add -A . + echo -e "\nChanges to be applied:" + git status + # Configure git to be the GitHub Actions account + git config user.email "github-actions[bot]@users.noreply.github.com" + git config user.name "github-actions[bot]" + # If this is a dev build and the last commit was from a dev build + # (detect if "dev" was in the previous commit message), reuse the + # same commit + if [[ "${version}" == "dev" && `git log -1 --format='%s'` == *"dev"* ]]; then + echo -e "\nAmending last commit:" + git commit --amend --reset-author -m "$message" + else + echo -e "\nMaking a new commit:" + git commit -m "$message" + fi + # Make the push quiet just in case there is anything that could leak + # sensitive information. + echo -e "\nPushing changes to gh-pages." + git push -fq origin gh-pages 2>&1 >/dev/null + echo -e "\nFinished uploading generated files." + if: (github.event_name == 'release' || github.event_name == 'push') && (matrix.os == 'ubuntu-latest') && (matrix.python-version == '3.9') diff --git a/.github/workflows/ci_tests_dev.yaml b/.github/workflows/ci_tests_dev.yaml index 53c797847bb..ce78f986a90 100644 --- a/.github/workflows/ci_tests_dev.yaml +++ b/.github/workflows/ci_tests_dev.yaml @@ -1,154 +1,154 @@ -# This workflow installs PyGMT dependencies, builds documentation and runs tests on GMT dev version -# For more information see: https://help.github.com/actions/language-and-framework-guides/using-python-with-github-actions - -name: GMT Dev Tests - -on: - # push: - # branches: [ master ] - pull_request: - types: [ready_for_review] - paths-ignore: - - 'doc/**' - - '*.md' - - '*.json' - - 'README.rst' - - 'LICENSE.txt' - repository_dispatch: - types: [test-gmt-dev-command] - # Schedule daily tests - schedule: - - cron: '0 0 * * *' - -jobs: - test_gmt_master: - name: ${{ matrix.os }} - GMT ${{ matrix.gmt_git_ref }} - runs-on: ${{ matrix.os }} - strategy: - fail-fast: false - matrix: - python-version: [3.9] - os: [ubuntu-20.04, macOS-10.15, windows-latest] - gmt_git_ref: [master] - defaults: - run: - shell: bash -l {0} - - steps: - # Cancel previous runs that are not completed - - name: Cancel Previous Runs - uses: styfle/cancel-workflow-action@0.8.0 - with: - access_token: ${{ github.token }} - - # Checkout current git repository - - name: Checkout - uses: actions/checkout@v2.3.4 - if: github.event_name != 'repository_dispatch' - with: - # fecth all history so that setuptools-scm works - fetch-depth: 0 - - # Generate token from GenericMappingTools bot - - name: Generate token from GenericMappingTools bot - uses: tibdex/github-app-token@v1 - if: github.event_name == 'repository_dispatch' - id: generate-token - with: - app_id: ${{ secrets.APP_ID }} - private_key: ${{ secrets.APP_PRIVATE_KEY }} - - # Checkout the pull request branch - - name: Checkout - uses: actions/checkout@v2 - if: github.event_name == 'repository_dispatch' - with: - token: ${{ steps.generate-token.outputs.token }} - repository: ${{ github.event.client_payload.pull_request.head.repo.full_name }} - ref: ${{ github.event.client_payload.pull_request.head.ref }} - # fecth all history so that setuptools-scm works - fetch-depth: 0 - - # Setup Miniconda - - name: Setup Miniconda - uses: conda-incubator/setup-miniconda@v2.0.1 - with: - python-version: ${{ matrix.python-version }} - channels: conda-forge - miniconda-version: "latest" - - # Install build dependencies from conda-forge - - name: Install build dependencies - run: | - conda install ninja cmake libblas libcblas liblapack fftw gdal ghostscript \ - libnetcdf hdf5 zlib curl pcre ipython pytest pytest-cov pytest-mpl - - # Build and install latest GMT from GitHub - - name: Install GMT ${{ matrix.gmt_git_ref }} branch (Linux/macOS) - run: curl https://raw.githubusercontent.com/GenericMappingTools/gmt/master/ci/build-gmt.sh | bash - env: - GMT_GIT_REF: ${{ matrix.gmt_git_ref }} - GMT_INSTALL_DIR: ${{ github.workspace }}/gmt-install-dir - if: runner.os != 'Windows' - - - name: Install GMT dev version from conda-forge (Windows) - run: conda install -c conda-forge/label/dev gmt - if: runner.os == 'Windows' - - # Download cached remote files (artifacts) from GitHub - - name: Download remote data from GitHub - uses: dawidd6/action-download-artifact@v2.11.1 - with: - workflow: cache_data.yaml - workflow_conclusion: success - name: gmt-cache - path: .gmt - - # Move downloaded files to ~/.gmt directory and list them - - name: Move and list downloaded remote files - shell: bash -l {0} - run: | - mkdir -p ~/.gmt - mv .gmt/* ~/.gmt - # Change modification times of the two files, so GMT won't refresh it - touch ~/.gmt/server/gmt_data_server.txt ~/.gmt/server/gmt_hash_server.txt - ls -lhR ~/.gmt - - # Install the package that we want to test - - name: Install the package - run: | - python setup.py sdist --formats=zip - pip install dist/* - - - name: Add GMT's bin to PATH (Linux/macOS) - run: echo ${GITHUB_WORKSPACE}/gmt-install-dir/bin >> $GITHUB_PATH - if: runner.os != 'Windows' - - # Run the tests - - name: Test with pytest (Linux/macOS) - run: make test PYTEST_EXTRA="-r P" - env: - GMT_LIBRARY_PATH: ${{ github.workspace }}/gmt-install-dir/lib - if: runner.os != 'Windows' - - # Run the tests - - name: Test with pytest (Windows) - run: make test PYTEST_EXTRA="-r P" - if: runner.os == 'Windows' - - # Upload diff images on test failure - - name: Upload diff images if any test fails - uses: actions/upload-artifact@v2 - if: ${{ failure() }} - with: - name: artifact-GMT-${{ matrix.gmt_git_ref }}-${{ runner.os }} - path: tmp-test-dir-with-unique-name - - - name: Add reaction - uses: peter-evans/create-or-update-comment@v1 - if: github.event_name == 'repository_dispatch' - with: - token: ${{ steps.generate-token.outputs.token }} - repository: ${{ github.event.client_payload.github.payload.repository.full_name }} - comment-id: ${{ github.event.client_payload.github.payload.comment.id }} - reaction-type: hooray +# This workflow installs PyGMT dependencies, builds documentation and runs tests on GMT dev version +# For more information see: https://help.github.com/actions/language-and-framework-guides/using-python-with-github-actions + +name: GMT Dev Tests + +on: + # push: + # branches: [ master ] + pull_request: + types: [ready_for_review] + paths-ignore: + - 'doc/**' + - '*.md' + - '*.json' + - 'README.rst' + - 'LICENSE.txt' + repository_dispatch: + types: [test-gmt-dev-command] + # Schedule daily tests + schedule: + - cron: '0 0 * * *' + +jobs: + test_gmt_master: + name: ${{ matrix.os }} - GMT ${{ matrix.gmt_git_ref }} + runs-on: ${{ matrix.os }} + strategy: + fail-fast: false + matrix: + python-version: [3.9] + os: [ubuntu-20.04, macOS-10.15, windows-latest] + gmt_git_ref: [master] + defaults: + run: + shell: bash -l {0} + + steps: + # Cancel previous runs that are not completed + - name: Cancel Previous Runs + uses: styfle/cancel-workflow-action@0.8.0 + with: + access_token: ${{ github.token }} + + # Checkout current git repository + - name: Checkout + uses: actions/checkout@v2.3.4 + if: github.event_name != 'repository_dispatch' + with: + # fecth all history so that setuptools-scm works + fetch-depth: 0 + + # Generate token from GenericMappingTools bot + - name: Generate token from GenericMappingTools bot + uses: tibdex/github-app-token@v1 + if: github.event_name == 'repository_dispatch' + id: generate-token + with: + app_id: ${{ secrets.APP_ID }} + private_key: ${{ secrets.APP_PRIVATE_KEY }} + + # Checkout the pull request branch + - name: Checkout + uses: actions/checkout@v2 + if: github.event_name == 'repository_dispatch' + with: + token: ${{ steps.generate-token.outputs.token }} + repository: ${{ github.event.client_payload.pull_request.head.repo.full_name }} + ref: ${{ github.event.client_payload.pull_request.head.ref }} + # fecth all history so that setuptools-scm works + fetch-depth: 0 + + # Setup Miniconda + - name: Setup Miniconda + uses: conda-incubator/setup-miniconda@v2.0.1 + with: + python-version: ${{ matrix.python-version }} + channels: conda-forge + miniconda-version: "latest" + + # Install build dependencies from conda-forge + - name: Install build dependencies + run: | + conda install ninja cmake libblas libcblas liblapack fftw gdal ghostscript \ + libnetcdf hdf5 zlib curl pcre ipython pytest pytest-cov pytest-mpl + + # Build and install latest GMT from GitHub + - name: Install GMT ${{ matrix.gmt_git_ref }} branch (Linux/macOS) + run: curl https://raw.githubusercontent.com/GenericMappingTools/gmt/master/ci/build-gmt.sh | bash + env: + GMT_GIT_REF: ${{ matrix.gmt_git_ref }} + GMT_INSTALL_DIR: ${{ github.workspace }}/gmt-install-dir + if: runner.os != 'Windows' + + - name: Install GMT dev version from conda-forge (Windows) + run: conda install -c conda-forge/label/dev gmt + if: runner.os == 'Windows' + + # Download cached remote files (artifacts) from GitHub + - name: Download remote data from GitHub + uses: dawidd6/action-download-artifact@v2.11.1 + with: + workflow: cache_data.yaml + workflow_conclusion: success + name: gmt-cache + path: .gmt + + # Move downloaded files to ~/.gmt directory and list them + - name: Move and list downloaded remote files + shell: bash -l {0} + run: | + mkdir -p ~/.gmt + mv .gmt/* ~/.gmt + # Change modification times of the two files, so GMT won't refresh it + touch ~/.gmt/server/gmt_data_server.txt ~/.gmt/server/gmt_hash_server.txt + ls -lhR ~/.gmt + + # Install the package that we want to test + - name: Install the package + run: | + python setup.py sdist --formats=zip + pip install dist/* + + - name: Add GMT's bin to PATH (Linux/macOS) + run: echo ${GITHUB_WORKSPACE}/gmt-install-dir/bin >> $GITHUB_PATH + if: runner.os != 'Windows' + + # Run the tests + - name: Test with pytest (Linux/macOS) + run: make test PYTEST_EXTRA="-r P" + env: + GMT_LIBRARY_PATH: ${{ github.workspace }}/gmt-install-dir/lib + if: runner.os != 'Windows' + + # Run the tests + - name: Test with pytest (Windows) + run: make test PYTEST_EXTRA="-r P" + if: runner.os == 'Windows' + + # Upload diff images on test failure + - name: Upload diff images if any test fails + uses: actions/upload-artifact@v2 + if: ${{ failure() }} + with: + name: artifact-GMT-${{ matrix.gmt_git_ref }}-${{ runner.os }} + path: tmp-test-dir-with-unique-name + + - name: Add reaction + uses: peter-evans/create-or-update-comment@v1 + if: github.event_name == 'repository_dispatch' + with: + token: ${{ steps.generate-token.outputs.token }} + repository: ${{ github.event.client_payload.github.payload.repository.full_name }} + comment-id: ${{ github.event.client_payload.github.payload.comment.id }} + reaction-type: hooray diff --git a/.github/workflows/format-command.yml b/.github/workflows/format-command.yml index e9ec1b82233..3ea09dbf455 100644 --- a/.github/workflows/format-command.yml +++ b/.github/workflows/format-command.yml @@ -1,51 +1,51 @@ -name: format-command -on: - repository_dispatch: - types: [format-command] -jobs: - format: - runs-on: ubuntu-latest - steps: - # Generate token from GenericMappingTools bot - - uses: tibdex/github-app-token@v1 - id: generate-token - with: - app_id: ${{ secrets.APP_ID }} - private_key: ${{ secrets.APP_PRIVATE_KEY }} - - # Checkout the pull request branch - - uses: actions/checkout@v2 - with: - token: ${{ steps.generate-token.outputs.token }} - repository: ${{ github.event.client_payload.pull_request.head.repo.full_name }} - ref: ${{ github.event.client_payload.pull_request.head.ref }} - - # Setup Python environment - - uses: actions/setup-python@v2.2.1 - - # Install formatting tools - - name: Install formatting tools - run: | - pip install black blackdoc docformatter flake8 isort - sudo apt-get install dos2unix - - # Run "make format" and commit the change to the PR branch - - name: Commit to the PR branch if any changes - run: | - make format - find . -type f -not -path '*/\.git/*' -exec grep -Iq . {} \; -exec dos2unix {} \; - find . -type f -not -path '*/\.git/*' -exec grep -Iq . {} \; -exec chmod 644 {} \; - if [[ $(git ls-files -m) ]]; then - git config --global user.name 'actions-bot' - git config --global user.email '58130806+actions-bot@users.noreply.github.com' - git commit -am "[format-command] fixes" - git push - fi - - - name: Add reaction - uses: peter-evans/create-or-update-comment@v1 - with: - token: ${{ steps.generate-token.outputs.token }} - repository: ${{ github.event.client_payload.github.payload.repository.full_name }} - comment-id: ${{ github.event.client_payload.github.payload.comment.id }} - reaction-type: hooray +name: format-command +on: + repository_dispatch: + types: [format-command] +jobs: + format: + runs-on: ubuntu-latest + steps: + # Generate token from GenericMappingTools bot + - uses: tibdex/github-app-token@v1 + id: generate-token + with: + app_id: ${{ secrets.APP_ID }} + private_key: ${{ secrets.APP_PRIVATE_KEY }} + + # Checkout the pull request branch + - uses: actions/checkout@v2 + with: + token: ${{ steps.generate-token.outputs.token }} + repository: ${{ github.event.client_payload.pull_request.head.repo.full_name }} + ref: ${{ github.event.client_payload.pull_request.head.ref }} + + # Setup Python environment + - uses: actions/setup-python@v2.2.1 + + # Install formatting tools + - name: Install formatting tools + run: | + pip install black blackdoc docformatter flake8 isort + sudo apt-get install dos2unix + + # Run "make format" and commit the change to the PR branch + - name: Commit to the PR branch if any changes + run: | + make format + find . -type f -not -path '*/\.git/*' -exec grep -Iq . {} \; -exec dos2unix {} \; + find . -type f -not -path '*/\.git/*' -exec grep -Iq . {} \; -exec chmod 644 {} \; + if [[ $(git ls-files -m) ]]; then + git config --global user.name 'actions-bot' + git config --global user.email '58130806+actions-bot@users.noreply.github.com' + git commit -am "[format-command] fixes" + git push + fi + + - name: Add reaction + uses: peter-evans/create-or-update-comment@v1 + with: + token: ${{ steps.generate-token.outputs.token }} + repository: ${{ github.event.client_payload.github.payload.repository.full_name }} + comment-id: ${{ github.event.client_payload.github.payload.comment.id }} + reaction-type: hooray diff --git a/.github/workflows/publish-to-pypi.yml b/.github/workflows/publish-to-pypi.yml index d65461db1d0..e23f5197341 100644 --- a/.github/workflows/publish-to-pypi.yml +++ b/.github/workflows/publish-to-pypi.yml @@ -1,65 +1,65 @@ -# Publish archives to PyPI and TestPyPI using GitHub Actions - -name: Publish to PyPI - -# Only run for pushes to the master branch and releases. -on: - push: - branches: - - master - release: - types: - - published - # Runs for pull requests should be disabled other than for testing purposes - #pull_request: - # branches: - # - master - -jobs: - publish-pypi: - name: Publish to PyPI - runs-on: ubuntu-latest - if: github.repository == 'GenericMappingTools/pygmt' - - steps: - - name: Checkout - uses: actions/checkout@v2.3.4 - with: - # fetch all history so that setuptools-scm works - fetch-depth: 0 - - - name: Set up Python - uses: actions/setup-python@v2.2.1 - with: - python-version: 3.9 - - - name: Install dependencies - run: python -m pip install setuptools wheel - - # This step is only necessary for testing purposes and for TestPyPI - - name: Fix up version string for TestPyPI - if: ${{ !startsWith(github.ref, 'refs/tags') }} - run: | - # Change setuptools-scm local_scheme to "no-local-version" so the - # local part of the version isn't included, making the version string - # compatible with PyPI. - sed --in-place "s/node-and-date/no-local-version/g" setup.py - - - name: Build source and wheel distributions - run: | - make package - echo "" - echo "Generated files:" - ls -lh dist/ - - - name: Publish to Test PyPI - uses: pypa/gh-action-pypi-publish@v1.4.2 - with: - password: ${{ secrets.TEST_PYPI_API_TOKEN }} - repository_url: https://test.pypi.org/legacy/ - - - name: Publish to PyPI - if: startsWith(github.ref, 'refs/tags') - uses: pypa/gh-action-pypi-publish@v1.4.2 - with: - password: ${{ secrets.PYPI_API_TOKEN }} +# Publish archives to PyPI and TestPyPI using GitHub Actions + +name: Publish to PyPI + +# Only run for pushes to the master branch and releases. +on: + push: + branches: + - master + release: + types: + - published + # Runs for pull requests should be disabled other than for testing purposes + #pull_request: + # branches: + # - master + +jobs: + publish-pypi: + name: Publish to PyPI + runs-on: ubuntu-latest + if: github.repository == 'GenericMappingTools/pygmt' + + steps: + - name: Checkout + uses: actions/checkout@v2.3.4 + with: + # fetch all history so that setuptools-scm works + fetch-depth: 0 + + - name: Set up Python + uses: actions/setup-python@v2.2.1 + with: + python-version: 3.9 + + - name: Install dependencies + run: python -m pip install setuptools wheel + + # This step is only necessary for testing purposes and for TestPyPI + - name: Fix up version string for TestPyPI + if: ${{ !startsWith(github.ref, 'refs/tags') }} + run: | + # Change setuptools-scm local_scheme to "no-local-version" so the + # local part of the version isn't included, making the version string + # compatible with PyPI. + sed --in-place "s/node-and-date/no-local-version/g" setup.py + + - name: Build source and wheel distributions + run: | + make package + echo "" + echo "Generated files:" + ls -lh dist/ + + - name: Publish to Test PyPI + uses: pypa/gh-action-pypi-publish@v1.4.2 + with: + password: ${{ secrets.TEST_PYPI_API_TOKEN }} + repository_url: https://test.pypi.org/legacy/ + + - name: Publish to PyPI + if: startsWith(github.ref, 'refs/tags') + uses: pypa/gh-action-pypi-publish@v1.4.2 + with: + password: ${{ secrets.PYPI_API_TOKEN }} diff --git a/.github/workflows/release-drafter.yml b/.github/workflows/release-drafter.yml index 9a2fd735f7a..f94cf602b4e 100644 --- a/.github/workflows/release-drafter.yml +++ b/.github/workflows/release-drafter.yml @@ -1,19 +1,19 @@ -name: Release Drafter - -on: - push: - # branches to consider in the event; optional, defaults to all - branches: - - master - -jobs: - update_release_draft: - runs-on: ubuntu-latest - steps: - # Drafts your next Release notes as Pull Requests are merged into "master" - - uses: release-drafter/release-drafter@v5.14.0 - with: - # (Optional) specify config name to use, relative to .github/. Default: release-drafter.yml - config-name: release-drafter.yml - env: - GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} +name: Release Drafter + +on: + push: + # branches to consider in the event; optional, defaults to all + branches: + - master + +jobs: + update_release_draft: + runs-on: ubuntu-latest + steps: + # Drafts your next Release notes as Pull Requests are merged into "master" + - uses: release-drafter/release-drafter@v5.14.0 + with: + # (Optional) specify config name to use, relative to .github/. Default: release-drafter.yml + config-name: release-drafter.yml + env: + GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} diff --git a/.github/workflows/slash-command-dispatch.yml b/.github/workflows/slash-command-dispatch.yml index 918597888e1..254b52cb821 100644 --- a/.github/workflows/slash-command-dispatch.yml +++ b/.github/workflows/slash-command-dispatch.yml @@ -1,26 +1,26 @@ -name: Slash Command Dispatch -on: - issue_comment: - types: [created] - # Add "edited" type for test purposes. Where possible, avoid using to prevent processing unnecessary events. - # types: [created, edited] -jobs: - slashCommandDispatch: - runs-on: ubuntu-latest - steps: - # Generate token from GenericMappingTools bot - - uses: tibdex/github-app-token@v1 - id: generate-token - with: - app_id: ${{ secrets.APP_ID }} - private_key: ${{ secrets.APP_PRIVATE_KEY }} - - - name: Slash Command Dispatch - uses: peter-evans/slash-command-dispatch@v2 - with: - token: ${{ steps.generate-token.outputs.token }} - commands: | - format - test-gmt-dev - issue-type: pull-request - permission: none +name: Slash Command Dispatch +on: + issue_comment: + types: [created] + # Add "edited" type for test purposes. Where possible, avoid using to prevent processing unnecessary events. + # types: [created, edited] +jobs: + slashCommandDispatch: + runs-on: ubuntu-latest + steps: + # Generate token from GenericMappingTools bot + - uses: tibdex/github-app-token@v1 + id: generate-token + with: + app_id: ${{ secrets.APP_ID }} + private_key: ${{ secrets.APP_PRIVATE_KEY }} + + - name: Slash Command Dispatch + uses: peter-evans/slash-command-dispatch@v2 + with: + token: ${{ steps.generate-token.outputs.token }} + commands: | + format + test-gmt-dev + issue-type: pull-request + permission: none diff --git a/.github/workflows/style_checks.yaml b/.github/workflows/style_checks.yaml index 3ad07f0e10a..a6824719432 100644 --- a/.github/workflows/style_checks.yaml +++ b/.github/workflows/style_checks.yaml @@ -1,42 +1,42 @@ -name: Style Checks - -on: - push: - branches: [ master ] - pull_request: - # Schedule daily tests - schedule: - - cron: '0 0 * * *' - -jobs: - style_check: - name: Style Checks - runs-on: ubuntu-latest - - steps: - # Checkout current git repository - - name: Checkout - uses: actions/checkout@v2.3.4 - - # Setup Miniconda - - name: Set up Python - uses: actions/setup-python@v2.2.1 - with: - python-version: 3.9 - - - name: Install packages - run: | - pip install black blackdoc docformatter flake8 pylint isort - sudo apt-get install dos2unix - - - name: Formatting check (black, blackdoc, docformatter, flake8 and isort) - run: make check - - - name: Linting (pylint) - run: make lint - - - name: Ensure files use UNIX line breaks and have 644 permission - run: | - find . -type f -not -path '*/\.git/*' -exec grep -Iq . {} \; -exec dos2unix --quiet {} \; - find . -type f -not -path '*/\.git/*' -exec grep -Iq . {} \; -exec chmod 644 {} \; - if [[ $(git ls-files -m) ]]; then git --no-pager diff HEAD; exit 1; fi +name: Style Checks + +on: + push: + branches: [ master ] + pull_request: + # Schedule daily tests + schedule: + - cron: '0 0 * * *' + +jobs: + style_check: + name: Style Checks + runs-on: ubuntu-latest + + steps: + # Checkout current git repository + - name: Checkout + uses: actions/checkout@v2.3.4 + + # Setup Miniconda + - name: Set up Python + uses: actions/setup-python@v2.2.1 + with: + python-version: 3.9 + + - name: Install packages + run: | + pip install black blackdoc docformatter flake8 pylint isort + sudo apt-get install dos2unix + + - name: Formatting check (black, blackdoc, docformatter, flake8 and isort) + run: make check + + - name: Linting (pylint) + run: make lint + + - name: Ensure files use UNIX line breaks and have 644 permission + run: | + find . -type f -not -path '*/\.git/*' -exec grep -Iq . {} \; -exec dos2unix --quiet {} \; + find . -type f -not -path '*/\.git/*' -exec grep -Iq . {} \; -exec chmod 644 {} \; + if [[ $(git ls-files -m) ]]; then git --no-pager diff HEAD; exit 1; fi diff --git a/.gitignore b/.gitignore index 7e3800a3b1c..99f0eeff565 100644 --- a/.gitignore +++ b/.gitignore @@ -1,46 +1,46 @@ -# Byte-compiled / optimized / DLL files -*.py[cd] - -# C extensions -*.so - -# Distribution / packaging -build/ -dist/ -*.egg -*.egg-info/ -.eggs/ -MANIFEST - -# Unit test / coverage reports -.cache -.coverage -coverage.xml -htmlcov/ -.pytest_cache/ -results/ -result_images/ -tmp-test-dir-with-unique-name/ - -# Sphinx documentation -doc/api/generated/ -doc/_build/ -doc/gallery/ -doc/projections/ -doc/tutorials/ - -# Jupyter Notebook -.ipynb_checkpoints/ - -# Visual Studio Code -.vscode/ - -# Environments -.env - -# Backup copies / swap files -*~ -.*.swp - -# macOS -.DS_Store +# Byte-compiled / optimized / DLL files +*.py[cd] + +# C extensions +*.so + +# Distribution / packaging +build/ +dist/ +*.egg +*.egg-info/ +.eggs/ +MANIFEST + +# Unit test / coverage reports +.cache +.coverage +coverage.xml +htmlcov/ +.pytest_cache/ +results/ +result_images/ +tmp-test-dir-with-unique-name/ + +# Sphinx documentation +doc/api/generated/ +doc/_build/ +doc/gallery/ +doc/projections/ +doc/tutorials/ + +# Jupyter Notebook +.ipynb_checkpoints/ + +# Visual Studio Code +.vscode/ + +# Environments +.env + +# Backup copies / swap files +*~ +.*.swp + +# macOS +.DS_Store diff --git a/.pylintrc b/.pylintrc index 056f4ded418..c2f1c7be17c 100644 --- a/.pylintrc +++ b/.pylintrc @@ -1,611 +1,611 @@ -[MASTER] - -# A comma-separated list of package or module names from where C extensions may -# be loaded. Extensions are loading into the active Python interpreter and may -# run arbitrary code. -extension-pkg-whitelist= - -# Specify a score threshold to be exceeded before program exits with error. -fail-under=10 - -# Add files or directories to the blacklist. They should be base names, not -# paths. -ignore=CVS - -# Add files or directories matching the regex patterns to the blacklist. The -# regex matches against base names, not paths. -ignore-patterns= - -# Python code to execute, usually for sys.path manipulation such as -# pygtk.require(). -#init-hook= - -# Use multiple processes to speed up Pylint. Specifying 0 will auto-detect the -# number of processors available to use. -jobs=4 - -# Control the amount of potential inferred values when inferring a single -# object. This can help the performance when dealing with large functions or -# complex, nested conditions. -limit-inference-results=100 - -# List of plugins (as comma separated values of python module names) to load, -# usually to register additional checkers. -load-plugins= - -# Pickle collected data for later comparisons. -persistent=yes - -# When enabled, pylint would attempt to guess common misconfiguration and emit -# user-friendly hints instead of false-positive error messages. -suggestion-mode=yes - -# Allow loading of arbitrary C extensions. Extensions are imported into the -# active Python interpreter and may run arbitrary code. -unsafe-load-any-extension=no - - -[MESSAGES CONTROL] - -# Only show warnings with the listed confidence levels. Leave empty to show -# all. Valid levels: HIGH, INFERENCE, INFERENCE_FAILURE, UNDEFINED. -confidence= - -# Disable the message, report, category or checker with the given id(s). You -# can either give multiple identifiers separated by comma (,) or put this -# option multiple times (only on the command line, not in the configuration -# file where it should appear only once). You can also use "--disable=all" to -# disable everything first and then reenable specific checks. For example, if -# you want to run only the similarities checker, you can use "--disable=all -# --enable=similarities". If you want to run only the classes checker, but have -# no Warning level messages displayed, use "--disable=all --enable=classes -# --disable=W". -disable=print-statement, - parameter-unpacking, - unpacking-in-except, - old-raise-syntax, - backtick, - long-suffix, - old-ne-operator, - old-octal-literal, - import-star-module-level, - non-ascii-bytes-literal, - raw-checker-failed, - bad-inline-option, - locally-disabled, - file-ignored, - suppressed-message, - useless-suppression, - deprecated-pragma, - use-symbolic-message-instead, - apply-builtin, - basestring-builtin, - buffer-builtin, - cmp-builtin, - coerce-builtin, - execfile-builtin, - file-builtin, - long-builtin, - raw_input-builtin, - reduce-builtin, - standarderror-builtin, - unicode-builtin, - xrange-builtin, - coerce-method, - delslice-method, - getslice-method, - setslice-method, - no-absolute-import, - old-division, - dict-iter-method, - dict-view-method, - next-method-called, - metaclass-assignment, - indexing-exception, - raising-string, - reload-builtin, - oct-method, - hex-method, - nonzero-method, - cmp-method, - input-builtin, - round-builtin, - intern-builtin, - unichr-builtin, - map-builtin-not-iterating, - zip-builtin-not-iterating, - range-builtin-not-iterating, - filter-builtin-not-iterating, - using-cmp-argument, - eq-without-hash, - div-method, - idiv-method, - rdiv-method, - exception-message-attribute, - invalid-str-codec, - sys-max-int, - bad-python3-import, - deprecated-string-function, - deprecated-str-translate-call, - deprecated-itertools-function, - deprecated-types-field, - next-method-defined, - dict-items-not-iterating, - dict-keys-not-iterating, - dict-values-not-iterating, - deprecated-operator-function, - deprecated-urllib-function, - xreadlines-attribute, - deprecated-sys-function, - exception-escape, - comprehension-escape, - attribute-defined-outside-init, - bad-continuation, - import-error, - duplicate-code - -# Enable the message, report, category or checker with the given id(s). You can -# either give multiple identifier separated by comma (,) or put this option -# multiple time (only on the command line, not in the configuration file where -# it should appear only once). See also the "--disable" option for examples. -enable=c-extension-no-member - - -[REPORTS] - -# Python expression which should return a score less than or equal to 10. You -# have access to the variables 'error', 'warning', 'refactor', and 'convention' -# which contain the number of messages in each category, as well as 'statement' -# which is the total number of statements analyzed. This score is used by the -# global evaluation report (RP0004). -evaluation=10.0 - ((float(5 * error + warning + refactor + convention) / statement) * 10) - -# Template used to display messages. This is a python new-style format string -# used to format the message information. See doc for all details. -#msg-template= - -# Set the output format. Available formats are text, parseable, colorized, json -# and msvs (visual studio). You can also give a reporter class, e.g. -# mypackage.mymodule.MyReporterClass. -output-format=text - -# Tells whether to display a full report or only the messages. -reports=no - -# Activate the evaluation score. -score=yes - - -[REFACTORING] - -# Maximum number of nested blocks for function / method body -max-nested-blocks=5 - -# Complete name of functions that never returns. When checking for -# inconsistent-return-statements if a never returning function is called then -# it will be considered as an explicit return statement and no message will be -# printed. -never-returning-functions=sys.exit - - -[VARIABLES] - -# List of additional names supposed to be defined in builtins. Remember that -# you should avoid defining new builtins when possible. -additional-builtins= - -# Tells whether unused global variables should be treated as a violation. -allow-global-unused-variables=yes - -# List of strings which can identify a callback function by name. A callback -# name must start or end with one of those strings. -callbacks=cb_, - _cb - -# A regular expression matching the name of dummy variables (i.e. expected to -# not be used). -dummy-variables-rgx=_+$|(_[a-zA-Z0-9_]*[a-zA-Z0-9]+?$)|dummy|^ignored_|^unused_ - -# Argument names that match this expression will be ignored. Default to name -# with leading underscore. -ignored-argument-names=_.*|^ignored_|^unused_ - -# Tells whether we should check for unused import in __init__ files. -init-import=no - -# List of qualified module names which can have objects that can redefine -# builtins. -redefining-builtins-modules=six.moves,past.builtins,future.builtins,builtins,io - - -[LOGGING] - -# The type of string formatting that logging methods do. `old` means using % -# formatting, `new` is for `{}` formatting. -logging-format-style=old - -# Logging modules to check that the string format arguments are in logging -# function parameter format. -logging-modules=logging - - -[SPELLING] - -# Limits count of emitted suggestions for spelling mistakes. -max-spelling-suggestions=4 - -# Spelling dictionary name. Available dictionaries: none. To make it work, -# install the python-enchant package. -spelling-dict= - -# List of comma separated words that should not be checked. -spelling-ignore-words= - -# A path to a file that contains the private dictionary; one word per line. -spelling-private-dict-file= - -# Tells whether to store unknown words to the private dictionary (see the -# --spelling-private-dict-file option) instead of raising a message. -spelling-store-unknown-words=no - - -[SIMILARITIES] - -# Ignore comments when computing similarities. -ignore-comments=yes - -# Ignore docstrings when computing similarities. -ignore-docstrings=yes - -# Ignore imports when computing similarities. -ignore-imports=no - -# Minimum lines number of a similarity. -min-similarity-lines=4 - - -[MISCELLANEOUS] - -# List of note tags to take in consideration, separated by a comma. -notes=FIXME, - XXX, - TODO - -# Regular expression of note tags to take in consideration. -#notes-rgx= - - -[FORMAT] - -# Expected format of line ending, e.g. empty (any line ending), LF or CRLF. -expected-line-ending-format= - -# Regexp for a line that is allowed to be longer than the limit. -ignore-long-lines=^\s*(# )??$ - -# Number of spaces of indent required inside a hanging or continued line. -indent-after-paren=4 - -# String used as indentation unit. This is usually " " (4 spaces) or "\t" (1 -# tab). -indent-string=' ' - -# Maximum number of characters on a single line. -max-line-length=100 - -# Maximum number of lines in a module. -max-module-lines=3000 - -# List of optional constructs for which whitespace checking is disabled. `dict- -# separator` is used to allow tabulation in dicts, etc.: {1 : 1,\n222: 2}. -# `trailing-comma` allows a space between comma and closing bracket: (a, ). -# `empty-line` allows space-only lines. -no-space-check=trailing-comma, - dict-separator - -# Allow the body of a class to be on the same line as the declaration if body -# contains single statement. -single-line-class-stmt=no - -# Allow the body of an if to be on the same line as the test if there is no -# else. -single-line-if-stmt=no - - -[TYPECHECK] - -# List of decorators that produce context managers, such as -# contextlib.contextmanager. Add to this list to register other decorators that -# produce valid context managers. -contextmanager-decorators=contextlib.contextmanager - -# List of members which are set dynamically and missed by pylint inference -# system, and so shouldn't trigger E1101 when accessed. Python regular -# expressions are accepted. -generated-members= - -# Tells whether missing members accessed in mixin class should be ignored. A -# mixin class is detected if its name ends with "mixin" (case insensitive). -ignore-mixin-members=yes - -# Tells whether to warn about missing members when the owner of the attribute -# is inferred to be None. -ignore-none=yes - -# This flag controls whether pylint should warn about no-member and similar -# checks whenever an opaque object is returned when inferring. The inference -# can return multiple potential results while evaluating a Python object, but -# some branches might not be evaluated, which results in partial inference. In -# that case, it might be useful to still emit no-member and other checks for -# the rest of the inferred objects. -ignore-on-opaque-inference=yes - -# List of class names for which member attributes should not be checked (useful -# for classes with dynamically set attributes). This supports the use of -# qualified names. -ignored-classes=optparse.Values,thread._local,_thread._local - -# List of module names for which member attributes should not be checked -# (useful for modules/projects where namespaces are manipulated during runtime -# and thus existing member attributes cannot be deduced by static analysis). It -# supports qualified module names, as well as Unix pattern matching. -ignored-modules= - -# Show a hint with possible names when a member name was not found. The aspect -# of finding the hint is based on edit distance. -missing-member-hint=yes - -# The minimum edit distance a name should have in order to be considered a -# similar match for a missing member name. -missing-member-hint-distance=1 - -# The total number of similar names that should be taken in consideration when -# showing a hint for a missing member. -missing-member-max-choices=1 - -# List of decorators that change the signature of a decorated function. -signature-mutators= - - -[STRING] - -# This flag controls whether inconsistent-quotes generates a warning when the -# character used as a quote delimiter is used inconsistently within a module. -check-quote-consistency=no - -# This flag controls whether the implicit-str-concat should generate a warning -# on implicit string concatenation in sequences defined over several lines. -check-str-concat-over-line-jumps=no - - -[BASIC] - -# Naming style matching correct argument names. -argument-naming-style=snake_case - -# Regular expression matching correct argument names. Overrides argument- -# naming-style. -#argument-rgx= - -# Naming style matching correct attribute names. -attr-naming-style=snake_case - -# Regular expression matching correct attribute names. Overrides attr-naming- -# style. -#attr-rgx= - -# Bad variable names which should always be refused, separated by a comma. -bad-names=foo, - bar, - baz, - toto, - tutu, - tata - -# Bad variable names regexes, separated by a comma. If names match any regex, -# they will always be refused -bad-names-rgxs= - -# Naming style matching correct class attribute names. -class-attribute-naming-style=any - -# Regular expression matching correct class attribute names. Overrides class- -# attribute-naming-style. -#class-attribute-rgx= - -# Naming style matching correct class names. -class-naming-style=PascalCase - -# Regular expression matching correct class names. Overrides class-naming- -# style. -#class-rgx= - -# Naming style matching correct constant names. -const-naming-style=UPPER_CASE - -# Regular expression matching correct constant names. Overrides const-naming- -# style. -#const-rgx= - -# Minimum line length for functions/classes that require docstrings, shorter -# ones are exempt. -docstring-min-length=-1 - -# Naming style matching correct function names. -function-naming-style=snake_case - -# Regular expression matching correct function names. Overrides function- -# naming-style. -#function-rgx= - -# Good variable names which should always be accepted, separated by a comma. -good-names=i, - j, - k, - ex, - Run, - _, - w, - e, - s, - n, - x, - y, - z - -# Good variable names regexes, separated by a comma. If names match any regex, -# they will always be accepted -good-names-rgxs= - -# Include a hint for the correct naming format with invalid-name. -include-naming-hint=no - -# Naming style matching correct inline iteration names. -inlinevar-naming-style=any - -# Regular expression matching correct inline iteration names. Overrides -# inlinevar-naming-style. -#inlinevar-rgx= - -# Naming style matching correct method names. -method-naming-style=snake_case - -# Regular expression matching correct method names. Overrides method-naming- -# style. -#method-rgx= - -# Naming style matching correct module names. -module-naming-style=snake_case - -# Regular expression matching correct module names. Overrides module-naming- -# style. -#module-rgx= - -# Colon-delimited sets of names that determine each other's naming style when -# the name regexes allow several styles. -name-group= - -# Regular expression which should only match function or class names that do -# not require a docstring. -no-docstring-rgx=^_ - -# List of decorators that produce properties, such as abc.abstractproperty. Add -# to this list to register other decorators that produce valid properties. -# These decorators are taken in consideration only for invalid-name. -property-classes=abc.abstractproperty - -# Naming style matching correct variable names. -variable-naming-style=snake_case - -# Regular expression matching correct variable names. Overrides variable- -# naming-style. -#variable-rgx= - - -[DESIGN] - -# Maximum number of arguments for function / method. -max-args=10 - -# Maximum number of attributes for a class (see R0902). -max-attributes=10 - -# Maximum number of boolean expressions in an if statement (see R0916). -max-bool-expr=5 - -# Maximum number of branch for function / method body. -max-branches=12 - -# Maximum number of locals for function / method body. -max-locals=15 - -# Maximum number of parents for a class (see R0901). -max-parents=7 - -# Maximum number of public methods for a class (see R0904). -max-public-methods=20 - -# Maximum number of return / yield for function / method body. -max-returns=6 - -# Maximum number of statements in function / method body. -max-statements=50 - -# Minimum number of public methods for a class (see R0903). -min-public-methods=2 - - -[IMPORTS] - -# List of modules that can be imported at any level, not just the top level -# one. -allow-any-import-level=pygmt.clib.Session, - sys, - platform, - importlib, - subprocess, - pytest - -# Allow wildcard imports from modules that define __all__. -allow-wildcard-with-all=no - -# Analyse import fallback blocks. This can be used to support both Python 2 and -# 3 compatible code, which means that the block might have code that exists -# only in one or another interpreter, leading to false positives when analysed. -analyse-fallback-blocks=no - -# Deprecated modules which should not be used, separated by a comma. -deprecated-modules=optparse,tkinter.tix - -# Create a graph of external dependencies in the given file (report RP0402 must -# not be disabled). -ext-import-graph= - -# Create a graph of every (i.e. internal and external) dependencies in the -# given file (report RP0402 must not be disabled). -import-graph= - -# Create a graph of internal dependencies in the given file (report RP0402 must -# not be disabled). -int-import-graph= - -# Force import order to recognize a module as part of the standard -# compatibility libraries. -known-standard-library= - -# Force import order to recognize a module as part of a third party library. -known-third-party=enchant - -# Couples of modules and preferred modules, separated by a comma. -preferred-modules= - - -[CLASSES] - -# List of method names used to declare (i.e. assign) instance attributes. -defining-attr-methods=__init__, - __new__, - setUp, - __post_init__ - -# List of member names, which should be excluded from the protected access -# warning. -exclude-protected=_asdict, - _fields, - _replace, - _source, - _make - -# List of valid names for the first argument in a class method. -valid-classmethod-first-arg=cls - -# List of valid names for the first argument in a metaclass class method. -valid-metaclass-classmethod-first-arg=cls - - -[EXCEPTIONS] - -# Exceptions that will emit a warning when being caught. Defaults to -# "BaseException, Exception". -overgeneral-exceptions=BaseException, - Exception +[MASTER] + +# A comma-separated list of package or module names from where C extensions may +# be loaded. Extensions are loading into the active Python interpreter and may +# run arbitrary code. +extension-pkg-whitelist= + +# Specify a score threshold to be exceeded before program exits with error. +fail-under=10 + +# Add files or directories to the blacklist. They should be base names, not +# paths. +ignore=CVS + +# Add files or directories matching the regex patterns to the blacklist. The +# regex matches against base names, not paths. +ignore-patterns= + +# Python code to execute, usually for sys.path manipulation such as +# pygtk.require(). +#init-hook= + +# Use multiple processes to speed up Pylint. Specifying 0 will auto-detect the +# number of processors available to use. +jobs=4 + +# Control the amount of potential inferred values when inferring a single +# object. This can help the performance when dealing with large functions or +# complex, nested conditions. +limit-inference-results=100 + +# List of plugins (as comma separated values of python module names) to load, +# usually to register additional checkers. +load-plugins= + +# Pickle collected data for later comparisons. +persistent=yes + +# When enabled, pylint would attempt to guess common misconfiguration and emit +# user-friendly hints instead of false-positive error messages. +suggestion-mode=yes + +# Allow loading of arbitrary C extensions. Extensions are imported into the +# active Python interpreter and may run arbitrary code. +unsafe-load-any-extension=no + + +[MESSAGES CONTROL] + +# Only show warnings with the listed confidence levels. Leave empty to show +# all. Valid levels: HIGH, INFERENCE, INFERENCE_FAILURE, UNDEFINED. +confidence= + +# Disable the message, report, category or checker with the given id(s). You +# can either give multiple identifiers separated by comma (,) or put this +# option multiple times (only on the command line, not in the configuration +# file where it should appear only once). You can also use "--disable=all" to +# disable everything first and then reenable specific checks. For example, if +# you want to run only the similarities checker, you can use "--disable=all +# --enable=similarities". If you want to run only the classes checker, but have +# no Warning level messages displayed, use "--disable=all --enable=classes +# --disable=W". +disable=print-statement, + parameter-unpacking, + unpacking-in-except, + old-raise-syntax, + backtick, + long-suffix, + old-ne-operator, + old-octal-literal, + import-star-module-level, + non-ascii-bytes-literal, + raw-checker-failed, + bad-inline-option, + locally-disabled, + file-ignored, + suppressed-message, + useless-suppression, + deprecated-pragma, + use-symbolic-message-instead, + apply-builtin, + basestring-builtin, + buffer-builtin, + cmp-builtin, + coerce-builtin, + execfile-builtin, + file-builtin, + long-builtin, + raw_input-builtin, + reduce-builtin, + standarderror-builtin, + unicode-builtin, + xrange-builtin, + coerce-method, + delslice-method, + getslice-method, + setslice-method, + no-absolute-import, + old-division, + dict-iter-method, + dict-view-method, + next-method-called, + metaclass-assignment, + indexing-exception, + raising-string, + reload-builtin, + oct-method, + hex-method, + nonzero-method, + cmp-method, + input-builtin, + round-builtin, + intern-builtin, + unichr-builtin, + map-builtin-not-iterating, + zip-builtin-not-iterating, + range-builtin-not-iterating, + filter-builtin-not-iterating, + using-cmp-argument, + eq-without-hash, + div-method, + idiv-method, + rdiv-method, + exception-message-attribute, + invalid-str-codec, + sys-max-int, + bad-python3-import, + deprecated-string-function, + deprecated-str-translate-call, + deprecated-itertools-function, + deprecated-types-field, + next-method-defined, + dict-items-not-iterating, + dict-keys-not-iterating, + dict-values-not-iterating, + deprecated-operator-function, + deprecated-urllib-function, + xreadlines-attribute, + deprecated-sys-function, + exception-escape, + comprehension-escape, + attribute-defined-outside-init, + bad-continuation, + import-error, + duplicate-code + +# Enable the message, report, category or checker with the given id(s). You can +# either give multiple identifier separated by comma (,) or put this option +# multiple time (only on the command line, not in the configuration file where +# it should appear only once). See also the "--disable" option for examples. +enable=c-extension-no-member + + +[REPORTS] + +# Python expression which should return a score less than or equal to 10. You +# have access to the variables 'error', 'warning', 'refactor', and 'convention' +# which contain the number of messages in each category, as well as 'statement' +# which is the total number of statements analyzed. This score is used by the +# global evaluation report (RP0004). +evaluation=10.0 - ((float(5 * error + warning + refactor + convention) / statement) * 10) + +# Template used to display messages. This is a python new-style format string +# used to format the message information. See doc for all details. +#msg-template= + +# Set the output format. Available formats are text, parseable, colorized, json +# and msvs (visual studio). You can also give a reporter class, e.g. +# mypackage.mymodule.MyReporterClass. +output-format=text + +# Tells whether to display a full report or only the messages. +reports=no + +# Activate the evaluation score. +score=yes + + +[REFACTORING] + +# Maximum number of nested blocks for function / method body +max-nested-blocks=5 + +# Complete name of functions that never returns. When checking for +# inconsistent-return-statements if a never returning function is called then +# it will be considered as an explicit return statement and no message will be +# printed. +never-returning-functions=sys.exit + + +[VARIABLES] + +# List of additional names supposed to be defined in builtins. Remember that +# you should avoid defining new builtins when possible. +additional-builtins= + +# Tells whether unused global variables should be treated as a violation. +allow-global-unused-variables=yes + +# List of strings which can identify a callback function by name. A callback +# name must start or end with one of those strings. +callbacks=cb_, + _cb + +# A regular expression matching the name of dummy variables (i.e. expected to +# not be used). +dummy-variables-rgx=_+$|(_[a-zA-Z0-9_]*[a-zA-Z0-9]+?$)|dummy|^ignored_|^unused_ + +# Argument names that match this expression will be ignored. Default to name +# with leading underscore. +ignored-argument-names=_.*|^ignored_|^unused_ + +# Tells whether we should check for unused import in __init__ files. +init-import=no + +# List of qualified module names which can have objects that can redefine +# builtins. +redefining-builtins-modules=six.moves,past.builtins,future.builtins,builtins,io + + +[LOGGING] + +# The type of string formatting that logging methods do. `old` means using % +# formatting, `new` is for `{}` formatting. +logging-format-style=old + +# Logging modules to check that the string format arguments are in logging +# function parameter format. +logging-modules=logging + + +[SPELLING] + +# Limits count of emitted suggestions for spelling mistakes. +max-spelling-suggestions=4 + +# Spelling dictionary name. Available dictionaries: none. To make it work, +# install the python-enchant package. +spelling-dict= + +# List of comma separated words that should not be checked. +spelling-ignore-words= + +# A path to a file that contains the private dictionary; one word per line. +spelling-private-dict-file= + +# Tells whether to store unknown words to the private dictionary (see the +# --spelling-private-dict-file option) instead of raising a message. +spelling-store-unknown-words=no + + +[SIMILARITIES] + +# Ignore comments when computing similarities. +ignore-comments=yes + +# Ignore docstrings when computing similarities. +ignore-docstrings=yes + +# Ignore imports when computing similarities. +ignore-imports=no + +# Minimum lines number of a similarity. +min-similarity-lines=4 + + +[MISCELLANEOUS] + +# List of note tags to take in consideration, separated by a comma. +notes=FIXME, + XXX, + TODO + +# Regular expression of note tags to take in consideration. +#notes-rgx= + + +[FORMAT] + +# Expected format of line ending, e.g. empty (any line ending), LF or CRLF. +expected-line-ending-format= + +# Regexp for a line that is allowed to be longer than the limit. +ignore-long-lines=^\s*(# )??$ + +# Number of spaces of indent required inside a hanging or continued line. +indent-after-paren=4 + +# String used as indentation unit. This is usually " " (4 spaces) or "\t" (1 +# tab). +indent-string=' ' + +# Maximum number of characters on a single line. +max-line-length=100 + +# Maximum number of lines in a module. +max-module-lines=3000 + +# List of optional constructs for which whitespace checking is disabled. `dict- +# separator` is used to allow tabulation in dicts, etc.: {1 : 1,\n222: 2}. +# `trailing-comma` allows a space between comma and closing bracket: (a, ). +# `empty-line` allows space-only lines. +no-space-check=trailing-comma, + dict-separator + +# Allow the body of a class to be on the same line as the declaration if body +# contains single statement. +single-line-class-stmt=no + +# Allow the body of an if to be on the same line as the test if there is no +# else. +single-line-if-stmt=no + + +[TYPECHECK] + +# List of decorators that produce context managers, such as +# contextlib.contextmanager. Add to this list to register other decorators that +# produce valid context managers. +contextmanager-decorators=contextlib.contextmanager + +# List of members which are set dynamically and missed by pylint inference +# system, and so shouldn't trigger E1101 when accessed. Python regular +# expressions are accepted. +generated-members= + +# Tells whether missing members accessed in mixin class should be ignored. A +# mixin class is detected if its name ends with "mixin" (case insensitive). +ignore-mixin-members=yes + +# Tells whether to warn about missing members when the owner of the attribute +# is inferred to be None. +ignore-none=yes + +# This flag controls whether pylint should warn about no-member and similar +# checks whenever an opaque object is returned when inferring. The inference +# can return multiple potential results while evaluating a Python object, but +# some branches might not be evaluated, which results in partial inference. In +# that case, it might be useful to still emit no-member and other checks for +# the rest of the inferred objects. +ignore-on-opaque-inference=yes + +# List of class names for which member attributes should not be checked (useful +# for classes with dynamically set attributes). This supports the use of +# qualified names. +ignored-classes=optparse.Values,thread._local,_thread._local + +# List of module names for which member attributes should not be checked +# (useful for modules/projects where namespaces are manipulated during runtime +# and thus existing member attributes cannot be deduced by static analysis). It +# supports qualified module names, as well as Unix pattern matching. +ignored-modules= + +# Show a hint with possible names when a member name was not found. The aspect +# of finding the hint is based on edit distance. +missing-member-hint=yes + +# The minimum edit distance a name should have in order to be considered a +# similar match for a missing member name. +missing-member-hint-distance=1 + +# The total number of similar names that should be taken in consideration when +# showing a hint for a missing member. +missing-member-max-choices=1 + +# List of decorators that change the signature of a decorated function. +signature-mutators= + + +[STRING] + +# This flag controls whether inconsistent-quotes generates a warning when the +# character used as a quote delimiter is used inconsistently within a module. +check-quote-consistency=no + +# This flag controls whether the implicit-str-concat should generate a warning +# on implicit string concatenation in sequences defined over several lines. +check-str-concat-over-line-jumps=no + + +[BASIC] + +# Naming style matching correct argument names. +argument-naming-style=snake_case + +# Regular expression matching correct argument names. Overrides argument- +# naming-style. +#argument-rgx= + +# Naming style matching correct attribute names. +attr-naming-style=snake_case + +# Regular expression matching correct attribute names. Overrides attr-naming- +# style. +#attr-rgx= + +# Bad variable names which should always be refused, separated by a comma. +bad-names=foo, + bar, + baz, + toto, + tutu, + tata + +# Bad variable names regexes, separated by a comma. If names match any regex, +# they will always be refused +bad-names-rgxs= + +# Naming style matching correct class attribute names. +class-attribute-naming-style=any + +# Regular expression matching correct class attribute names. Overrides class- +# attribute-naming-style. +#class-attribute-rgx= + +# Naming style matching correct class names. +class-naming-style=PascalCase + +# Regular expression matching correct class names. Overrides class-naming- +# style. +#class-rgx= + +# Naming style matching correct constant names. +const-naming-style=UPPER_CASE + +# Regular expression matching correct constant names. Overrides const-naming- +# style. +#const-rgx= + +# Minimum line length for functions/classes that require docstrings, shorter +# ones are exempt. +docstring-min-length=-1 + +# Naming style matching correct function names. +function-naming-style=snake_case + +# Regular expression matching correct function names. Overrides function- +# naming-style. +#function-rgx= + +# Good variable names which should always be accepted, separated by a comma. +good-names=i, + j, + k, + ex, + Run, + _, + w, + e, + s, + n, + x, + y, + z + +# Good variable names regexes, separated by a comma. If names match any regex, +# they will always be accepted +good-names-rgxs= + +# Include a hint for the correct naming format with invalid-name. +include-naming-hint=no + +# Naming style matching correct inline iteration names. +inlinevar-naming-style=any + +# Regular expression matching correct inline iteration names. Overrides +# inlinevar-naming-style. +#inlinevar-rgx= + +# Naming style matching correct method names. +method-naming-style=snake_case + +# Regular expression matching correct method names. Overrides method-naming- +# style. +#method-rgx= + +# Naming style matching correct module names. +module-naming-style=snake_case + +# Regular expression matching correct module names. Overrides module-naming- +# style. +#module-rgx= + +# Colon-delimited sets of names that determine each other's naming style when +# the name regexes allow several styles. +name-group= + +# Regular expression which should only match function or class names that do +# not require a docstring. +no-docstring-rgx=^_ + +# List of decorators that produce properties, such as abc.abstractproperty. Add +# to this list to register other decorators that produce valid properties. +# These decorators are taken in consideration only for invalid-name. +property-classes=abc.abstractproperty + +# Naming style matching correct variable names. +variable-naming-style=snake_case + +# Regular expression matching correct variable names. Overrides variable- +# naming-style. +#variable-rgx= + + +[DESIGN] + +# Maximum number of arguments for function / method. +max-args=10 + +# Maximum number of attributes for a class (see R0902). +max-attributes=10 + +# Maximum number of boolean expressions in an if statement (see R0916). +max-bool-expr=5 + +# Maximum number of branch for function / method body. +max-branches=12 + +# Maximum number of locals for function / method body. +max-locals=15 + +# Maximum number of parents for a class (see R0901). +max-parents=7 + +# Maximum number of public methods for a class (see R0904). +max-public-methods=20 + +# Maximum number of return / yield for function / method body. +max-returns=6 + +# Maximum number of statements in function / method body. +max-statements=50 + +# Minimum number of public methods for a class (see R0903). +min-public-methods=2 + + +[IMPORTS] + +# List of modules that can be imported at any level, not just the top level +# one. +allow-any-import-level=pygmt.clib.Session, + sys, + platform, + importlib, + subprocess, + pytest + +# Allow wildcard imports from modules that define __all__. +allow-wildcard-with-all=no + +# Analyse import fallback blocks. This can be used to support both Python 2 and +# 3 compatible code, which means that the block might have code that exists +# only in one or another interpreter, leading to false positives when analysed. +analyse-fallback-blocks=no + +# Deprecated modules which should not be used, separated by a comma. +deprecated-modules=optparse,tkinter.tix + +# Create a graph of external dependencies in the given file (report RP0402 must +# not be disabled). +ext-import-graph= + +# Create a graph of every (i.e. internal and external) dependencies in the +# given file (report RP0402 must not be disabled). +import-graph= + +# Create a graph of internal dependencies in the given file (report RP0402 must +# not be disabled). +int-import-graph= + +# Force import order to recognize a module as part of the standard +# compatibility libraries. +known-standard-library= + +# Force import order to recognize a module as part of a third party library. +known-third-party=enchant + +# Couples of modules and preferred modules, separated by a comma. +preferred-modules= + + +[CLASSES] + +# List of method names used to declare (i.e. assign) instance attributes. +defining-attr-methods=__init__, + __new__, + setUp, + __post_init__ + +# List of member names, which should be excluded from the protected access +# warning. +exclude-protected=_asdict, + _fields, + _replace, + _source, + _make + +# List of valid names for the first argument in a class method. +valid-classmethod-first-arg=cls + +# List of valid names for the first argument in a metaclass class method. +valid-metaclass-classmethod-first-arg=cls + + +[EXCEPTIONS] + +# Exceptions that will emit a warning when being caught. Defaults to +# "BaseException, Exception". +overgeneral-exceptions=BaseException, + Exception diff --git a/AUTHORS.md b/AUTHORS.md index b90ba33acb2..cd91b083c40 100644 --- a/AUTHORS.md +++ b/AUTHORS.md @@ -1,20 +1,20 @@ -# Project Contributors - -This project was started in 2017 by [Leonardo Uieda](http://www.leouieda.com) -during [an NSF funded postdoc](http://www.leouieda.com/blog/hawaii-gmt-postdoc.html) -with [Paul Wessel](http://www.soest.hawaii.edu/wessel) at the University of Hawaii at -Manoa. - -The following people have contributed code and/or documentation to the project -(alphabetical by name) and are considered to be "PyGMT Developers": - -* [Dongdong Tian](https://seisman.info/) | [0000-0001-7967-1197](https://orcid.org/0000-0001-7967-1197) | Michigan State University -* [Jiayuan Yao](https://github.com/core-man) | [0000-0001-7036-4238](https://orcid.org/0000-0001-7036-4238) | Nanyang Technological University -* [Leonardo Uieda](http://www.leouieda.com/) | [0000-0001-6123-9515](https://orcid.org/0000-0001-6123-9515) | University of Liverpool -* [Liam Toney](https://liam.earth/) | [0000-0003-0167-9433](https://orcid.org/0000-0003-0167-9433) | University of Alaska Fairbanks -* [Malte Ziebarth](https://github.com/mjziebarth) | [0000-0002-5190-4478](https://orcid.org/0000-0002-5190-4478) | GFZ German Research Centre for Geosciences -* [Meghan Jones](https://github.com/meghanrjones) | [0000-0003-0180-8928](https://orcid.org/0000-0003-0180-8928) | University of Hawai'i at Mānoa -* [Michael Grund](https://github.com/michaelgrund) | [0000-0001-8759-2018](https://orcid.org/0000-0001-8759-2018) | Innoplexia GmbH -* [Tyler Newton](http://www.tnewton.com/) | [0000-0002-1560-6553](https://orcid.org/0000-0002-1560-6553) | University of Oregon -* [Wei Ji Leong](https://github.com/weiji14) | [0000-0003-2354-1988](https://orcid.org/0000-0003-2354-1988) | Victoria University of Wellington -* [William Schlitzer](https://github.com/willschlitzer) | [0000-0002-5843-2282](https://orcid.org/0000-0002-5843-2282) | Unaffiliated +# Project Contributors + +This project was started in 2017 by [Leonardo Uieda](http://www.leouieda.com) +during [an NSF funded postdoc](http://www.leouieda.com/blog/hawaii-gmt-postdoc.html) +with [Paul Wessel](http://www.soest.hawaii.edu/wessel) at the University of Hawaii at +Manoa. + +The following people have contributed code and/or documentation to the project +(alphabetical by name) and are considered to be "PyGMT Developers": + +* [Dongdong Tian](https://seisman.info/) | [0000-0001-7967-1197](https://orcid.org/0000-0001-7967-1197) | Michigan State University +* [Jiayuan Yao](https://github.com/core-man) | [0000-0001-7036-4238](https://orcid.org/0000-0001-7036-4238) | Nanyang Technological University +* [Leonardo Uieda](http://www.leouieda.com/) | [0000-0001-6123-9515](https://orcid.org/0000-0001-6123-9515) | University of Liverpool +* [Liam Toney](https://liam.earth/) | [0000-0003-0167-9433](https://orcid.org/0000-0003-0167-9433) | University of Alaska Fairbanks +* [Malte Ziebarth](https://github.com/mjziebarth) | [0000-0002-5190-4478](https://orcid.org/0000-0002-5190-4478) | GFZ German Research Centre for Geosciences +* [Meghan Jones](https://github.com/meghanrjones) | [0000-0003-0180-8928](https://orcid.org/0000-0003-0180-8928) | University of Hawai'i at Mānoa +* [Michael Grund](https://github.com/michaelgrund) | [0000-0001-8759-2018](https://orcid.org/0000-0001-8759-2018) | Innoplexia GmbH +* [Tyler Newton](http://www.tnewton.com/) | [0000-0002-1560-6553](https://orcid.org/0000-0002-1560-6553) | University of Oregon +* [Wei Ji Leong](https://github.com/weiji14) | [0000-0003-2354-1988](https://orcid.org/0000-0003-2354-1988) | Victoria University of Wellington +* [William Schlitzer](https://github.com/willschlitzer) | [0000-0002-5843-2282](https://orcid.org/0000-0002-5843-2282) | Unaffiliated diff --git a/AUTHORSHIP.md b/AUTHORSHIP.md index 058dfb6f176..738ddaa0479 100644 --- a/AUTHORSHIP.md +++ b/AUTHORSHIP.md @@ -1,78 +1,78 @@ -# Authorship guidelines for academic papers and software archives - -First of all, we are deeply thankful to everyone who has helped make PyGMT -what it is today. Our goal for this document is to establish guidelines -for giving credit to contributors for their work. -To do so, we will attempt to define: - -- Fair and diverse ways of providing recognition for contributors' efforts. -- Define _contributions_ in a broad way: writing code and/or documentation, - providing ideas, fostering the community, etc. - -The following are the ways in which individuals who have contributed will be -recognized. - -> **Note**: These policies are not set in stone and may be changed to -> accommodate the growth of the project or the preferences of the community. - -## The `AUTHORS.md` file - -Anyone who has contributed a pull request to the project is welcome to add -themselves to the `AUTHORS.md` file. This file lives in the repository and is -packaged with distributions. This is an optional process. - -## Changelog for each release - -Every time we make a release, everyone who has made a commit to the repository -since the previous release will be mentioned in the changelog entry. If their -full name is available on GitHub, we will use it. Otherwise, we will use the -GitHub handle. This is a way of saying "Thank you". - -## Authorship on Zenodo archives of releases - -Anyone who has contributed to the repository (i.e., appears on `git log`) will -be invited to be an author on the Zenodo archive of new releases. - -To be included as an author, you *must* add the following to the `AUTHORS.md` -file of the repository: - -1. Full name (and a link to your website or GitHub page) -2. [ORCID](https://orcid.org) (optional) -3. Affiliation (if omitted, we will use "Unaffiliated") - -The order of authors will be defined by the number of commits to the repository -(`git shortlog -sne`). The order can also be changed on a case-by-case basis. - -If you have contributed and do not wish to be included in Zenodo archives, -there are a few options: - -1. Don't add yourself to `AUTHORS.md` -2. Remove yourself from `AUTHORS.md` -3. Indicate next to your name on `AUTHORS.md` that you do not wish to be - included with something like `(not included in Zenodo)`. - -## Scientific publications (papers) - -We aim to write academic papers for most of our software packages. Ideally, we -will publish updated papers for major changes or significant new components of -the package. - -To be included as an author on the paper, you *must* satisfy the following -criteria: - -1. Have made multiple and regular contributions to the repository, or the GMT - repository, in numerous facets, such as wrapping functions, testing, and/or - writing documentation. -2. Have made non-coding contributions, including project administration and - decision making. -3. Have participated in the writing and reviewing of the paper. -4. Add your full name, affiliation, and (optionally) ORCID to the paper. These - can be submitted on pull requests to the corresponding paper repository. -5. Write and/or read and review the manuscript in a timely manner and provide - comments on the paper (even if it's just an "OK", but preferably more). - -The order of authors will be defined by the number of commits made since the -previous major release that has an associated paper (`git shortlog -vX.0.0...HEAD -sne`). The order of any author who hasn't made any commits will -be decided by all authors. The order can also be changed on a case-by-case -basis. +# Authorship guidelines for academic papers and software archives + +First of all, we are deeply thankful to everyone who has helped make PyGMT +what it is today. Our goal for this document is to establish guidelines +for giving credit to contributors for their work. +To do so, we will attempt to define: + +- Fair and diverse ways of providing recognition for contributors' efforts. +- Define _contributions_ in a broad way: writing code and/or documentation, + providing ideas, fostering the community, etc. + +The following are the ways in which individuals who have contributed will be +recognized. + +> **Note**: These policies are not set in stone and may be changed to +> accommodate the growth of the project or the preferences of the community. + +## The `AUTHORS.md` file + +Anyone who has contributed a pull request to the project is welcome to add +themselves to the `AUTHORS.md` file. This file lives in the repository and is +packaged with distributions. This is an optional process. + +## Changelog for each release + +Every time we make a release, everyone who has made a commit to the repository +since the previous release will be mentioned in the changelog entry. If their +full name is available on GitHub, we will use it. Otherwise, we will use the +GitHub handle. This is a way of saying "Thank you". + +## Authorship on Zenodo archives of releases + +Anyone who has contributed to the repository (i.e., appears on `git log`) will +be invited to be an author on the Zenodo archive of new releases. + +To be included as an author, you *must* add the following to the `AUTHORS.md` +file of the repository: + +1. Full name (and a link to your website or GitHub page) +2. [ORCID](https://orcid.org) (optional) +3. Affiliation (if omitted, we will use "Unaffiliated") + +The order of authors will be defined by the number of commits to the repository +(`git shortlog -sne`). The order can also be changed on a case-by-case basis. + +If you have contributed and do not wish to be included in Zenodo archives, +there are a few options: + +1. Don't add yourself to `AUTHORS.md` +2. Remove yourself from `AUTHORS.md` +3. Indicate next to your name on `AUTHORS.md` that you do not wish to be + included with something like `(not included in Zenodo)`. + +## Scientific publications (papers) + +We aim to write academic papers for most of our software packages. Ideally, we +will publish updated papers for major changes or significant new components of +the package. + +To be included as an author on the paper, you *must* satisfy the following +criteria: + +1. Have made multiple and regular contributions to the repository, or the GMT + repository, in numerous facets, such as wrapping functions, testing, and/or + writing documentation. +2. Have made non-coding contributions, including project administration and + decision making. +3. Have participated in the writing and reviewing of the paper. +4. Add your full name, affiliation, and (optionally) ORCID to the paper. These + can be submitted on pull requests to the corresponding paper repository. +5. Write and/or read and review the manuscript in a timely manner and provide + comments on the paper (even if it's just an "OK", but preferably more). + +The order of authors will be defined by the number of commits made since the +previous major release that has an associated paper (`git shortlog +vX.0.0...HEAD -sne`). The order of any author who hasn't made any commits will +be decided by all authors. The order can also be changed on a case-by-case +basis. diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index 68cdd71426a..e73c2f706d8 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -1,503 +1,503 @@ -# Contributing Guidelines - -:tada: **First off, thank you for considering contributing to our project!** :tada: - -This is a community-driven project, so it's people like you that make it useful and -successful. -These are some of the many ways to contribute: - -* :bug: Submitting bug reports and feature requests -* :memo: Writing tutorials or examples -* :mag: Fixing typos and improving to the documentation -* :bulb: Writing code for everyone to use - -If you get stuck at any point you can create an issue on GitHub (look for the *Issues* -tab in the repository) or contact us at one of the other channels mentioned below. - -For more information on contributing to open source projects, -[GitHub's own guide](https://guides.github.com/activities/contributing-to-open-source/) -is a great starting point if you are new to version control. -Also, checkout the -[Zen of Scientific Software Maintenance](https://jrleeman.github.io/ScientificSoftwareMaintenance/) -for some guiding principles on how to create high quality scientific software -contributions. - - -## Ground Rules - -The goal is to maintain a diverse community that's pleasant for everyone. -**Please be considerate and respectful of others**. -Everyone must abide by our [Code of Conduct](CODE_OF_CONDUCT.md) and we encourage all to -read it carefully. - - -## Contents - -* [What Can I Do?](#what-can-i-do) -* [How Can I Talk to You?](#how-can-i-talk-to-you) -* [Reporting a Bug](#reporting-a-bug) -* [Editing the Documentation](#editing-the-documentation) - - [Gallery plots](#gallery-plots) - - [Tutorials](#tutorials) - - [Example code standards](#example-code-standards) -* [Contributing Code](#contributing-code) - - [General guidelines](#general-guidelines) - - [Setting up your environment](#setting-up-your-environment) - - [Code style](#code-style) - - [Testing your code](#testing-your-code) - - [Testing plots](#testing-plots) - - [Documentation](#documentation) - - [Code Review](#code-review) - - -## What Can I Do? - -* Tackle any issue that you wish! Some issues are labeled as **"good first issues"** to - indicate that they are beginner friendly, meaning that they don't require extensive - knowledge of the project. -* Make a tutorial or example of how to do something. -* Provide feedback about how we can improve the project or about your particular use - case. -* Contribute code you already have. It doesn't need to be perfect! We will help you - clean things up, test it, etc. - - -## How Can I Talk to You? - -Discussion often happens in the issues and pull requests. -In addition, there is a -[Discourse forum](https://forum.generic-mapping-tools.org/c/questions/pygmt-q-a) for -the project where you can ask questions. - - -## Reporting a Bug - -Find the *Issues* tab on the top of the GitHub repository and click *New Issue*. -You'll be prompted to choose between different types of issue, like bug reports and -feature requests. -Choose the one that best matches your need. -The Issue will be populated with one of our templates. -**Please try to fillout the template with as much detail as you can**. -Remember: the more information we have, the easier it will be for us to solve your -problem. - - -## Editing the Documentation - -If you're browsing the documentation and notice a typo or something that could be -improved, please consider letting us know by [creating an issue](#reporting-a-bug) or -submitting a fix (even better :star2:). - -You can submit fixes to the documentation pages completely online without having to -download and install anything: - -* On each documentation page, there should be an "Improve This Page" link at the very - top. -* Click on that link to open the respective source file (usually an `.rst` file in the - `doc` folder) on GitHub for editing online (you'll need a GitHub account). -* Make your desired changes. -* When you're done, scroll to the bottom of the page. -* Fill out the two fields under "Commit changes": the first is a short title describing - your fixes; the second is a more detailed description of the changes. Try to be as - detailed as possible and describe *why* you changed something. -* Click on the "Commit changes" button to open a - [pull request (see below)](#pull-requests). -* We'll review your changes and then merge them in if everything is OK. -* Done :tada::beer: - -Alternatively, you can make the changes offline to the files in the `doc` folder or the -example scripts. See [Contributing Code](#contributing-code) for instructions. - -### Gallery plots - -The gallery and tutorials are managed by -[sphinx-gallery](https://sphinx-gallery.readthedocs.io/). -The source files for the example gallery are `.py` scripts in `examples/gallery/` that -generate one or more figures. They are executed automatically by sphinx-gallery when the -documentation is built. The output is gathered and assembled into the gallery. - -You can **add a new** plot by placing a new `.py` file in one of the folders inside the -`examples/gallery` folder of the repository. See the other examples to get an idea for the -format. - -General guidelines for making a good gallery plot: - -* Examples should highlight a single feature/command. Good: *how to add a label to - a colorbar*. Bad: *how to add a label to the colorbar and use two different CPTs and - use subplots*. -* Try to make the example as simple as possible. Good: use only commands that are - required to show the feature you want to highlight. Bad: use advanced/complex Python - features to make the code smaller. -* Use a sample dataset from `pygmt.datasets` if you need to plot data. If a suitable - dataset isn't available, open an issue requesting one and we'll work together to add - it. -* Add comments to explain things are aren't obvious from reading the code. Good: *Use a - Mercator projection and make the plot 15 centimeters wide*. Bad: *Draw coastlines and - plot the data*. -* Describe the feature that you're showcasing and link to other relevant parts of the - documentation. -* SI units should be used in the example code for gallery plots. - -### Tutorials - -The tutorials (the User Guide in the docs) are also built by sphinx-gallery from the -`.py` files in the `examples/tutorials` folder of the repository. To add a new tutorial: - -* Include a `.py` file in the `examples/tutorials` folder on the base of the repository. -* Write the tutorial in "notebook" style with code mixed with paragraphs explaining what - is being done. See the other tutorials for the format. -* Include the tutorial in the table of contents of the documentation (side bar). Do this - by adding a line to the User Guide `toc` directive in `doc/index.rst`. Notice that the - file included is the `.rst` generated by sphinx-gallery. -* Choose the most representative figure as the thumbnail figure by adding a comment line - `# sphinx_gallery_thumbnail_number = ` to any place (usually at the top) - in the tutorial. The *fig_number* starts from 1. - -Guidelines for a good tutorial: - -* Each tutorial should focus on a particular set of tasks that a user might want to - accomplish: plotting grids, interpolation, configuring the frame, projections, etc. -* The tutorial code should be as simple as possible. Avoid using advanced/complex Python - features or abbreviations. -* Explain the options and features in as much detail as possible. The gallery has - concise examples while the tutorials are detailed and full of text. -* SI units should be used in the example code for tutorial plots. - -Note that the `Figure.show()` function needs to be called for a plot to be inserted into -the documentation. - -### Example code standards - -When editing documentation, use the following standards to demonstrate the example code: - -1. Python arguments, such as import statements, Boolean expressions, and function - arguments should be wrapped as ``code`` by using \`\` on both sides of the code. - Example: \`\`import pygmt\`\` results in ``import pygmt`` - -2. Literal GMT arguments should be **bold** by wrapping the arguments with \*\* - (two asterisks) on both sides. The argument description should be in *italicized* - with \* (single asterisk) on both sides. - Example: `**+l**\ *label*` results in **+l***label* - -3. Optional arguments are placed wrapped with [ ] (square brackets). -4. Arguments that are mutually exclusive are separated with a | (bar) to denote "or". - -## Contributing Code - -**Is this your first contribution?** -Please take a look at these resources to learn about git and pull requests (don't -hesitate to [ask questions](#how-can-i-talk-to-you)): - -* [How to Contribute to Open Source](https://opensource.guide/how-to-contribute/). -* Aaron Meurer's [tutorial on the git workflow](http://www.asmeurer.com/git-workflow/) -* [How to Contribute to an Open Source Project on GitHub](https://egghead.io/courses/how-to-contribute-to-an-open-source-project-on-github) - -### General guidelines - -We follow the [git pull request workflow](http://www.asmeurer.com/git-workflow/) to -make changes to our codebase. -Every change made goes through a pull request, even our own, so that our -[continuous integration](https://en.wikipedia.org/wiki/Continuous_integration) services -have a change to check that the code is up to standards and passes all our tests. -This way, the *master* branch is always stable. - -General guidelines for pull requests (PRs): - -* **Open an issue first** describing what you want to do. If there is already an issue - that matches your PR, leave a comment there instead to let us know what you plan to - do. -* Each pull request should consist of a **small** and logical collection of changes. -* Larger changes should be broken down into smaller components and integrated - separately. -* Bug fixes should be submitted in separate PRs. -* 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. -* Do not commit changes to files that are irrelevant to your feature or bugfix (eg: - `.gitignore`, IDE project files, etc). -* Write descriptive commit messages. Chris Beams has written a - [guide](https://chris.beams.io/posts/git-commit/) on how to write good commit - messages. -* Be willing to accept criticism and work on improving your code; we don't want to break - other users' code, so care must be taken not to introduce bugs. -* Be aware that the pull request review process is not immediate, and is generally - proportional to the size of the pull request. - -### Setting up your environment - -We highly recommend using [Anaconda](https://www.anaconda.com/download/) and the `conda` -package manager to install and manage your Python packages. -It will make your life a lot easier! - -The repository includes a conda environment file `environment.yml` with the -specification for all development requirements to build and test the project. -Once you have forked and clone the repository to your local machine, you use this file -to create an isolated environment on which you can work. -Run the following on the base of the repository: - -```bash -conda env create -``` - -Before building and testing the project, you have to activate the environment: - -```bash -conda activate pygmt -``` - -You'll need to do this every time you start a new terminal. - -See the [`environment.yml`](environment.yml) file for the list of dependencies and the -environment name. - -We have a [`Makefile`](Makefile) that provides commands for installing, running the -tests and coverage analysis, running linters, etc. -If you don't want to use `make`, open the `Makefile` and copy the commands you want to -run. - -To install the current source code into your testing environment, run: - -```bash -make install -``` - -This installs your project in *editable* mode, meaning that changes made to the source -code will be available when you import the package (even if you're on a different -directory). - -### Code style - -We use some tools: - -- [Black](https://github.com/psf/black) -- [blackdoc](https://github.com/keewis/blackdoc) -- [docformatter](https://github.com/myint/docformatter) -- [isort](https://pycqa.github.io/isort/) - -to format the code so we don't have to think about it. -Black and blackdoc loosely follows the [PEP8](http://pep8.org) guide but with a few differences. -Regardless, you won't have to worry about formatting the code yourself. -Before committing, run it to automatically format your code: - -```bash -make format -``` - -For consistency, we also use UNIX-style line endings (`\n`) and file permission -644 (`-rw-r--r--`) throughout the whole project. -Don't worry if you forget to do it. Our continuous integration systems will -warn us and you can make a new commit with the formatted code. -Even better, you can just write `/format` in the first line of any comment in a -Pull Request to lint the code automatically. - -We also use [flake8](http://flake8.pycqa.org/en/latest/) and -[pylint](https://www.pylint.org/) to check the quality of the code and quickly catch -common errors. -The [`Makefile`](Makefile) contains rules for running both checks: - -```bash -make check # Runs black, blackdoc, docformatter, flake8 and isort (in check mode) -make lint # Runs pylint, which is a bit slower -``` - -#### Docstrings - -**All docstrings** should follow the -[numpy style guide](https://numpydoc.readthedocs.io/en/latest/format.html#docstring-standard). -All functions/classes/methods should have docstrings with a full description of all -arguments and return values. - -While the maximum line length for code is automatically set by *Black*, docstrings -must be formatted manually. To play nicely with Jupyter and IPython, **keep docstrings -limited to 79 characters** per line. - -### Testing your code - -Automated testing helps ensure that our code is as free of bugs as it can be. -It also lets us know immediately if a change we make breaks any other part of the code. - -All of our test code and data are stored in the `tests` subpackage. -We use the [pytest](https://pytest.org/) framework to run the test suite. - -Please write tests for your code so that we can be sure that it won't break any of the -existing functionality. -Tests also help us be confident that we won't break your code in the future. - -When writing tests, don't test everything that the GMT function already tests, such as -the every unique combination arguments. An exception to this would be the most popular -modules, such as `plot` and `basemap`. The highest priority for tests should be the -Python-specific code, such as numpy, pandas, and xarray objects and the virtualfile -mechanism. - -If you're **new to testing**, see existing test files for examples of things to do. -**Don't let the tests keep you from submitting your contribution!** -If you're not sure how to do this or are having trouble, submit your pull request -anyway. -We will help you create the tests and sort out any kind of problem during code review. - -Run the tests and calculate test coverage using: - - make test - -The coverage report will let you know which lines of code are touched by the tests. -If all the tests pass, you can view the coverage reports by opening `htmlcov/index.html` -in your browser. **Strive to get 100% coverage for the lines you changed.** -It's OK if you can't or don't know how to test something. -Leave a comment in the PR and we'll help you out. - -You can also run tests in just one test script using: - - pytest pygmt/tests/NAME_OF_TEST_FILE.py - -or run tests which contain names that match a specific keyword expression: - - pytest -k KEYWORD pygmt/tests - -### Testing plots - -Writing an image-based test is only slightly more difficult than a simple test. -The main consideration is that you must specify the "baseline" or reference -image, and compare it with a "generated" or test image. This is handled using -the *decorator* functions `@check_figures_equal` and -`@pytest.mark.mpl_image_compare` whose usage are further described below. - -#### Using check_figures_equal - -This approach draws the same figure using two different methods (the reference -method and the tested method), and checks that both of them are the same. -It takes two `pygmt.Figure` objects ('fig_ref' and 'fig_test'), generates a png -image, and checks for the Root Mean Square (RMS) error between the two. -Here's an example: - -```python -@check_figures_equal() -def test_my_plotting_case(): - "Test that my plotting function works" - fig_ref, fig_test = Figure(), Figure() - fig_ref.grdimage("@earth_relief_01d_g", projection="W120/15c", cmap="geo") - fig_test.grdimage(grid, projection="W120/15c", cmap="geo") - return fig_ref, fig_test -``` - -Note: This is the recommended way to test plots whenever possible, such as when -we want to compare a reference GMT plot created from NetCDF files with one -generated by PyGMT that passes through several layers of virtualfile machinery. -Using this method will help save space in the git repository by not having to -store baseline images as with the other method below. - -#### Using mpl_image_compare - -This method uses the [pytest-mpl](https://github.com/matplotlib/pytest-mpl) -plug-in to test plot generating code. -Every time the tests are run, `pytest-mpl` compares the generated plots with known -correct ones stored in `pygmt/tests/baseline`. -If your test created a `pygmt.Figure` object, you can test it by adding a *decorator* and -returning the `pygmt.Figure` object: - -```python -@pytest.mark.mpl_image_compare -def test_my_plotting_case(): - "Test that my plotting function works" - fig = Figure() - fig.basemap(region=[0, 360, -90, 90], projection='W7i', frame=True) - return fig -``` - -Your test function **must** return the `pygmt.Figure` object and you can only -test one figure per function. - -Before you can run your test, you'll need to generate a *baseline* (a correct -version) of your plot. -Run the following from the repository root: - -```bash -pytest --mpl-generate-path=baseline pygmt/tests/NAME_OF_TEST_FILE.py -``` - -This will create a `baseline` folder with all the plots generated in your test -file. -Visually inspect the one corresponding to your test function. -If it's correct, copy it (and only it) to `pygmt/tests/baseline`. -When you run `make test` the next time, your test should be executed and -passing. - -Don't forget to commit the baseline image as well. - -### Documentation - -Most documentation sources are in the `doc` folder. -We use [sphinx](http://www.sphinx-doc.org/) to build the web pages from these sources. -To build the HTML files: - -```bash -cd doc -make all -``` - -This will build the HTML files in `doc/_build/html`. -Open `doc/_build/html/index.html` in your browser to view the pages. - -The API reference is manually assembled in `doc/api/index.rst`. -The *autodoc* sphinx extension will automatically create pages for each -function/class/module listed there. - -You can reference functions, classes, methods, and modules from anywhere -(including docstrings) using: - -- :func:\`package.module.function\` -- :class:\`package.module.class\` -- :meth:\`package.module.method\` -- :mod:\`package.module\` - -An example would be to use -:meth:\`pygmt.Figure.grdview\` to link -to https://www.pygmt.org/latest/api/generated/pygmt.Figure.grdview.html. -PyGMT documentation that is not a class, method, -or module can be linked with :doc:\`Any Link Text \`. -For example, :doc:\`Install instructions \\` links -to https://www.pygmt.org/latest/install.html. - -Linking to the GMT documentation and GMT configuration parameters can be done using: - -- :gmt-docs:\`page_name.html\` -- :gmt-term:\`GMT_PARAMETER\` - -An example would be using -:gmt-docs:\`makecpt.html\` to link to -https://docs.generic-mapping-tools.org/latest/makecpt.html. -For GMT configuration parameters, an example is -:gmt-term:\`COLOR_FOREGROUND\` to link to -https://docs.generic-mapping-tools.org/latest/gmt.conf.html#term-COLOR_FOREGROUND. - -Sphinx will create a link to the automatically generated page for that -function/class/module. - -**All docstrings** should follow the -[numpy style guide](https://numpydoc.readthedocs.io/en/latest/format.html). -All functions/classes/methods should have docstrings with a full description of all -arguments and return values. - -### Code Review - -After you've submitted a pull request, you should expect to hear at least a comment -within a couple of days. -We may suggest some changes or improvements or alternatives. - -Some things that will increase the chance that your pull request is accepted quickly: - -* Write a good and detailed description of what the PR does. -* Write tests for the code you wrote/modified. -* Readable code is better than clever code (even with comments). -* Write documentation for your code (docstrings) and leave comments explaining the - *reason* behind non-obvious things. -* Include an example of new features in the gallery or tutorials. -* Follow the [PEP8](http://pep8.org) style guide for code and the - [numpy guide](https://numpydoc.readthedocs.io/en/latest/format.html) - for documentation. - -Pull requests will automatically have tests run by GitHub Actions. -This includes running both the unit tests as well as code linters. -GitHub will show the status of these checks on the pull request. -Try to get them all passing (green). -If you have any trouble, leave a comment in the PR or -[get in touch](#how-can-i-talk-to-you). +# Contributing Guidelines + +:tada: **First off, thank you for considering contributing to our project!** :tada: + +This is a community-driven project, so it's people like you that make it useful and +successful. +These are some of the many ways to contribute: + +* :bug: Submitting bug reports and feature requests +* :memo: Writing tutorials or examples +* :mag: Fixing typos and improving to the documentation +* :bulb: Writing code for everyone to use + +If you get stuck at any point you can create an issue on GitHub (look for the *Issues* +tab in the repository) or contact us at one of the other channels mentioned below. + +For more information on contributing to open source projects, +[GitHub's own guide](https://guides.github.com/activities/contributing-to-open-source/) +is a great starting point if you are new to version control. +Also, checkout the +[Zen of Scientific Software Maintenance](https://jrleeman.github.io/ScientificSoftwareMaintenance/) +for some guiding principles on how to create high quality scientific software +contributions. + + +## Ground Rules + +The goal is to maintain a diverse community that's pleasant for everyone. +**Please be considerate and respectful of others**. +Everyone must abide by our [Code of Conduct](CODE_OF_CONDUCT.md) and we encourage all to +read it carefully. + + +## Contents + +* [What Can I Do?](#what-can-i-do) +* [How Can I Talk to You?](#how-can-i-talk-to-you) +* [Reporting a Bug](#reporting-a-bug) +* [Editing the Documentation](#editing-the-documentation) + - [Gallery plots](#gallery-plots) + - [Tutorials](#tutorials) + - [Example code standards](#example-code-standards) +* [Contributing Code](#contributing-code) + - [General guidelines](#general-guidelines) + - [Setting up your environment](#setting-up-your-environment) + - [Code style](#code-style) + - [Testing your code](#testing-your-code) + - [Testing plots](#testing-plots) + - [Documentation](#documentation) + - [Code Review](#code-review) + + +## What Can I Do? + +* Tackle any issue that you wish! Some issues are labeled as **"good first issues"** to + indicate that they are beginner friendly, meaning that they don't require extensive + knowledge of the project. +* Make a tutorial or example of how to do something. +* Provide feedback about how we can improve the project or about your particular use + case. +* Contribute code you already have. It doesn't need to be perfect! We will help you + clean things up, test it, etc. + + +## How Can I Talk to You? + +Discussion often happens in the issues and pull requests. +In addition, there is a +[Discourse forum](https://forum.generic-mapping-tools.org/c/questions/pygmt-q-a) for +the project where you can ask questions. + + +## Reporting a Bug + +Find the *Issues* tab on the top of the GitHub repository and click *New Issue*. +You'll be prompted to choose between different types of issue, like bug reports and +feature requests. +Choose the one that best matches your need. +The Issue will be populated with one of our templates. +**Please try to fillout the template with as much detail as you can**. +Remember: the more information we have, the easier it will be for us to solve your +problem. + + +## Editing the Documentation + +If you're browsing the documentation and notice a typo or something that could be +improved, please consider letting us know by [creating an issue](#reporting-a-bug) or +submitting a fix (even better :star2:). + +You can submit fixes to the documentation pages completely online without having to +download and install anything: + +* On each documentation page, there should be an "Improve This Page" link at the very + top. +* Click on that link to open the respective source file (usually an `.rst` file in the + `doc` folder) on GitHub for editing online (you'll need a GitHub account). +* Make your desired changes. +* When you're done, scroll to the bottom of the page. +* Fill out the two fields under "Commit changes": the first is a short title describing + your fixes; the second is a more detailed description of the changes. Try to be as + detailed as possible and describe *why* you changed something. +* Click on the "Commit changes" button to open a + [pull request (see below)](#pull-requests). +* We'll review your changes and then merge them in if everything is OK. +* Done :tada::beer: + +Alternatively, you can make the changes offline to the files in the `doc` folder or the +example scripts. See [Contributing Code](#contributing-code) for instructions. + +### Gallery plots + +The gallery and tutorials are managed by +[sphinx-gallery](https://sphinx-gallery.readthedocs.io/). +The source files for the example gallery are `.py` scripts in `examples/gallery/` that +generate one or more figures. They are executed automatically by sphinx-gallery when the +documentation is built. The output is gathered and assembled into the gallery. + +You can **add a new** plot by placing a new `.py` file in one of the folders inside the +`examples/gallery` folder of the repository. See the other examples to get an idea for the +format. + +General guidelines for making a good gallery plot: + +* Examples should highlight a single feature/command. Good: *how to add a label to + a colorbar*. Bad: *how to add a label to the colorbar and use two different CPTs and + use subplots*. +* Try to make the example as simple as possible. Good: use only commands that are + required to show the feature you want to highlight. Bad: use advanced/complex Python + features to make the code smaller. +* Use a sample dataset from `pygmt.datasets` if you need to plot data. If a suitable + dataset isn't available, open an issue requesting one and we'll work together to add + it. +* Add comments to explain things are aren't obvious from reading the code. Good: *Use a + Mercator projection and make the plot 15 centimeters wide*. Bad: *Draw coastlines and + plot the data*. +* Describe the feature that you're showcasing and link to other relevant parts of the + documentation. +* SI units should be used in the example code for gallery plots. + +### Tutorials + +The tutorials (the User Guide in the docs) are also built by sphinx-gallery from the +`.py` files in the `examples/tutorials` folder of the repository. To add a new tutorial: + +* Include a `.py` file in the `examples/tutorials` folder on the base of the repository. +* Write the tutorial in "notebook" style with code mixed with paragraphs explaining what + is being done. See the other tutorials for the format. +* Include the tutorial in the table of contents of the documentation (side bar). Do this + by adding a line to the User Guide `toc` directive in `doc/index.rst`. Notice that the + file included is the `.rst` generated by sphinx-gallery. +* Choose the most representative figure as the thumbnail figure by adding a comment line + `# sphinx_gallery_thumbnail_number = ` to any place (usually at the top) + in the tutorial. The *fig_number* starts from 1. + +Guidelines for a good tutorial: + +* Each tutorial should focus on a particular set of tasks that a user might want to + accomplish: plotting grids, interpolation, configuring the frame, projections, etc. +* The tutorial code should be as simple as possible. Avoid using advanced/complex Python + features or abbreviations. +* Explain the options and features in as much detail as possible. The gallery has + concise examples while the tutorials are detailed and full of text. +* SI units should be used in the example code for tutorial plots. + +Note that the `Figure.show()` function needs to be called for a plot to be inserted into +the documentation. + +### Example code standards + +When editing documentation, use the following standards to demonstrate the example code: + +1. Python arguments, such as import statements, Boolean expressions, and function + arguments should be wrapped as ``code`` by using \`\` on both sides of the code. + Example: \`\`import pygmt\`\` results in ``import pygmt`` + +2. Literal GMT arguments should be **bold** by wrapping the arguments with \*\* + (two asterisks) on both sides. The argument description should be in *italicized* + with \* (single asterisk) on both sides. + Example: `**+l**\ *label*` results in **+l***label* + +3. Optional arguments are placed wrapped with [ ] (square brackets). +4. Arguments that are mutually exclusive are separated with a | (bar) to denote "or". + +## Contributing Code + +**Is this your first contribution?** +Please take a look at these resources to learn about git and pull requests (don't +hesitate to [ask questions](#how-can-i-talk-to-you)): + +* [How to Contribute to Open Source](https://opensource.guide/how-to-contribute/). +* Aaron Meurer's [tutorial on the git workflow](http://www.asmeurer.com/git-workflow/) +* [How to Contribute to an Open Source Project on GitHub](https://egghead.io/courses/how-to-contribute-to-an-open-source-project-on-github) + +### General guidelines + +We follow the [git pull request workflow](http://www.asmeurer.com/git-workflow/) to +make changes to our codebase. +Every change made goes through a pull request, even our own, so that our +[continuous integration](https://en.wikipedia.org/wiki/Continuous_integration) services +have a change to check that the code is up to standards and passes all our tests. +This way, the *master* branch is always stable. + +General guidelines for pull requests (PRs): + +* **Open an issue first** describing what you want to do. If there is already an issue + that matches your PR, leave a comment there instead to let us know what you plan to + do. +* Each pull request should consist of a **small** and logical collection of changes. +* Larger changes should be broken down into smaller components and integrated + separately. +* Bug fixes should be submitted in separate PRs. +* 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. +* Do not commit changes to files that are irrelevant to your feature or bugfix (eg: + `.gitignore`, IDE project files, etc). +* Write descriptive commit messages. Chris Beams has written a + [guide](https://chris.beams.io/posts/git-commit/) on how to write good commit + messages. +* Be willing to accept criticism and work on improving your code; we don't want to break + other users' code, so care must be taken not to introduce bugs. +* Be aware that the pull request review process is not immediate, and is generally + proportional to the size of the pull request. + +### Setting up your environment + +We highly recommend using [Anaconda](https://www.anaconda.com/download/) and the `conda` +package manager to install and manage your Python packages. +It will make your life a lot easier! + +The repository includes a conda environment file `environment.yml` with the +specification for all development requirements to build and test the project. +Once you have forked and clone the repository to your local machine, you use this file +to create an isolated environment on which you can work. +Run the following on the base of the repository: + +```bash +conda env create +``` + +Before building and testing the project, you have to activate the environment: + +```bash +conda activate pygmt +``` + +You'll need to do this every time you start a new terminal. + +See the [`environment.yml`](environment.yml) file for the list of dependencies and the +environment name. + +We have a [`Makefile`](Makefile) that provides commands for installing, running the +tests and coverage analysis, running linters, etc. +If you don't want to use `make`, open the `Makefile` and copy the commands you want to +run. + +To install the current source code into your testing environment, run: + +```bash +make install +``` + +This installs your project in *editable* mode, meaning that changes made to the source +code will be available when you import the package (even if you're on a different +directory). + +### Code style + +We use some tools: + +- [Black](https://github.com/psf/black) +- [blackdoc](https://github.com/keewis/blackdoc) +- [docformatter](https://github.com/myint/docformatter) +- [isort](https://pycqa.github.io/isort/) + +to format the code so we don't have to think about it. +Black and blackdoc loosely follows the [PEP8](http://pep8.org) guide but with a few differences. +Regardless, you won't have to worry about formatting the code yourself. +Before committing, run it to automatically format your code: + +```bash +make format +``` + +For consistency, we also use UNIX-style line endings (`\n`) and file permission +644 (`-rw-r--r--`) throughout the whole project. +Don't worry if you forget to do it. Our continuous integration systems will +warn us and you can make a new commit with the formatted code. +Even better, you can just write `/format` in the first line of any comment in a +Pull Request to lint the code automatically. + +We also use [flake8](http://flake8.pycqa.org/en/latest/) and +[pylint](https://www.pylint.org/) to check the quality of the code and quickly catch +common errors. +The [`Makefile`](Makefile) contains rules for running both checks: + +```bash +make check # Runs black, blackdoc, docformatter, flake8 and isort (in check mode) +make lint # Runs pylint, which is a bit slower +``` + +#### Docstrings + +**All docstrings** should follow the +[numpy style guide](https://numpydoc.readthedocs.io/en/latest/format.html#docstring-standard). +All functions/classes/methods should have docstrings with a full description of all +arguments and return values. + +While the maximum line length for code is automatically set by *Black*, docstrings +must be formatted manually. To play nicely with Jupyter and IPython, **keep docstrings +limited to 79 characters** per line. + +### Testing your code + +Automated testing helps ensure that our code is as free of bugs as it can be. +It also lets us know immediately if a change we make breaks any other part of the code. + +All of our test code and data are stored in the `tests` subpackage. +We use the [pytest](https://pytest.org/) framework to run the test suite. + +Please write tests for your code so that we can be sure that it won't break any of the +existing functionality. +Tests also help us be confident that we won't break your code in the future. + +When writing tests, don't test everything that the GMT function already tests, such as +the every unique combination arguments. An exception to this would be the most popular +modules, such as `plot` and `basemap`. The highest priority for tests should be the +Python-specific code, such as numpy, pandas, and xarray objects and the virtualfile +mechanism. + +If you're **new to testing**, see existing test files for examples of things to do. +**Don't let the tests keep you from submitting your contribution!** +If you're not sure how to do this or are having trouble, submit your pull request +anyway. +We will help you create the tests and sort out any kind of problem during code review. + +Run the tests and calculate test coverage using: + + make test + +The coverage report will let you know which lines of code are touched by the tests. +If all the tests pass, you can view the coverage reports by opening `htmlcov/index.html` +in your browser. **Strive to get 100% coverage for the lines you changed.** +It's OK if you can't or don't know how to test something. +Leave a comment in the PR and we'll help you out. + +You can also run tests in just one test script using: + + pytest pygmt/tests/NAME_OF_TEST_FILE.py + +or run tests which contain names that match a specific keyword expression: + + pytest -k KEYWORD pygmt/tests + +### Testing plots + +Writing an image-based test is only slightly more difficult than a simple test. +The main consideration is that you must specify the "baseline" or reference +image, and compare it with a "generated" or test image. This is handled using +the *decorator* functions `@check_figures_equal` and +`@pytest.mark.mpl_image_compare` whose usage are further described below. + +#### Using check_figures_equal + +This approach draws the same figure using two different methods (the reference +method and the tested method), and checks that both of them are the same. +It takes two `pygmt.Figure` objects ('fig_ref' and 'fig_test'), generates a png +image, and checks for the Root Mean Square (RMS) error between the two. +Here's an example: + +```python +@check_figures_equal() +def test_my_plotting_case(): + "Test that my plotting function works" + fig_ref, fig_test = Figure(), Figure() + fig_ref.grdimage("@earth_relief_01d_g", projection="W120/15c", cmap="geo") + fig_test.grdimage(grid, projection="W120/15c", cmap="geo") + return fig_ref, fig_test +``` + +Note: This is the recommended way to test plots whenever possible, such as when +we want to compare a reference GMT plot created from NetCDF files with one +generated by PyGMT that passes through several layers of virtualfile machinery. +Using this method will help save space in the git repository by not having to +store baseline images as with the other method below. + +#### Using mpl_image_compare + +This method uses the [pytest-mpl](https://github.com/matplotlib/pytest-mpl) +plug-in to test plot generating code. +Every time the tests are run, `pytest-mpl` compares the generated plots with known +correct ones stored in `pygmt/tests/baseline`. +If your test created a `pygmt.Figure` object, you can test it by adding a *decorator* and +returning the `pygmt.Figure` object: + +```python +@pytest.mark.mpl_image_compare +def test_my_plotting_case(): + "Test that my plotting function works" + fig = Figure() + fig.basemap(region=[0, 360, -90, 90], projection='W7i', frame=True) + return fig +``` + +Your test function **must** return the `pygmt.Figure` object and you can only +test one figure per function. + +Before you can run your test, you'll need to generate a *baseline* (a correct +version) of your plot. +Run the following from the repository root: + +```bash +pytest --mpl-generate-path=baseline pygmt/tests/NAME_OF_TEST_FILE.py +``` + +This will create a `baseline` folder with all the plots generated in your test +file. +Visually inspect the one corresponding to your test function. +If it's correct, copy it (and only it) to `pygmt/tests/baseline`. +When you run `make test` the next time, your test should be executed and +passing. + +Don't forget to commit the baseline image as well. + +### Documentation + +Most documentation sources are in the `doc` folder. +We use [sphinx](http://www.sphinx-doc.org/) to build the web pages from these sources. +To build the HTML files: + +```bash +cd doc +make all +``` + +This will build the HTML files in `doc/_build/html`. +Open `doc/_build/html/index.html` in your browser to view the pages. + +The API reference is manually assembled in `doc/api/index.rst`. +The *autodoc* sphinx extension will automatically create pages for each +function/class/module listed there. + +You can reference functions, classes, methods, and modules from anywhere +(including docstrings) using: + +- :func:\`package.module.function\` +- :class:\`package.module.class\` +- :meth:\`package.module.method\` +- :mod:\`package.module\` + +An example would be to use +:meth:\`pygmt.Figure.grdview\` to link +to https://www.pygmt.org/latest/api/generated/pygmt.Figure.grdview.html. +PyGMT documentation that is not a class, method, +or module can be linked with :doc:\`Any Link Text \`. +For example, :doc:\`Install instructions \\` links +to https://www.pygmt.org/latest/install.html. + +Linking to the GMT documentation and GMT configuration parameters can be done using: + +- :gmt-docs:\`page_name.html\` +- :gmt-term:\`GMT_PARAMETER\` + +An example would be using +:gmt-docs:\`makecpt.html\` to link to +https://docs.generic-mapping-tools.org/latest/makecpt.html. +For GMT configuration parameters, an example is +:gmt-term:\`COLOR_FOREGROUND\` to link to +https://docs.generic-mapping-tools.org/latest/gmt.conf.html#term-COLOR_FOREGROUND. + +Sphinx will create a link to the automatically generated page for that +function/class/module. + +**All docstrings** should follow the +[numpy style guide](https://numpydoc.readthedocs.io/en/latest/format.html). +All functions/classes/methods should have docstrings with a full description of all +arguments and return values. + +### Code Review + +After you've submitted a pull request, you should expect to hear at least a comment +within a couple of days. +We may suggest some changes or improvements or alternatives. + +Some things that will increase the chance that your pull request is accepted quickly: + +* Write a good and detailed description of what the PR does. +* Write tests for the code you wrote/modified. +* Readable code is better than clever code (even with comments). +* Write documentation for your code (docstrings) and leave comments explaining the + *reason* behind non-obvious things. +* Include an example of new features in the gallery or tutorials. +* Follow the [PEP8](http://pep8.org) style guide for code and the + [numpy guide](https://numpydoc.readthedocs.io/en/latest/format.html) + for documentation. + +Pull requests will automatically have tests run by GitHub Actions. +This includes running both the unit tests as well as code linters. +GitHub will show the status of these checks on the pull request. +Try to get them all passing (green). +If you have any trouble, leave a comment in the PR or +[get in touch](#how-can-i-talk-to-you). diff --git a/LICENSE.txt b/LICENSE.txt index bd155e59d1a..a7b897659a6 100644 --- a/LICENSE.txt +++ b/LICENSE.txt @@ -1,25 +1,25 @@ -Copyright (c) 2017-2021 The PyGMT Developers -All rights reserved. - -Redistribution and use in source and binary forms, with or without modification, -are permitted provided that the following conditions are met: - -* Redistributions of source code must retain the above copyright notice, - this list of conditions and the following disclaimer. -* Redistributions in binary form must reproduce the above copyright notice, - this list of conditions and the following disclaimer in the documentation - and/or other materials provided with the distribution. -* Neither the name of the PyGMT Developers nor the names of any contributors - may be used to endorse or promote products derived from this software - without specific prior written permission. - -THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND -ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED -WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE -DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR -ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES -(INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; -LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON -ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT -(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS -SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +Copyright (c) 2017-2021 The PyGMT Developers +All rights reserved. + +Redistribution and use in source and binary forms, with or without modification, +are permitted provided that the following conditions are met: + +* Redistributions of source code must retain the above copyright notice, + this list of conditions and the following disclaimer. +* Redistributions in binary form must reproduce the above copyright notice, + this list of conditions and the following disclaimer in the documentation + and/or other materials provided with the distribution. +* Neither the name of the PyGMT Developers nor the names of any contributors + may be used to endorse or promote products derived from this software + without specific prior written permission. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND +ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED +WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE +DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR +ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES +(INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; +LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON +ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS +SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. diff --git a/MAINTENANCE.md b/MAINTENANCE.md index d3ca9ce79d3..9cf03380d47 100644 --- a/MAINTENANCE.md +++ b/MAINTENANCE.md @@ -1,222 +1,222 @@ -# Maintainers Guide - -This page contains instructions for project maintainers about how our setup works, -making releases, creating packages, etc. - -If you want to make a contribution to the project, see the -[Contributing Guide](CONTRIBUTING.md) instead. - -## Table of Contents -* [Onboarding Access Checklist](#onboarding-access-checklist) -* [Branches](#branches) -* [Reviewing and Merging Pull Requests](#reviewing-and-merging-pull-requests) -* [Continuous Integration](#continuous-integration) - - [Github Actions](#github-actions) -* [Continuous Documentation](#continuous-documentation) -* [Making a Release](#making-a-release) - - [Updating the Changelog](#updating-the-changelog) - - [Check the README Syntax](#check-the-readme-syntax) - - [Pushing to PyPI and Updating the Documentation](#pushing-to-pypi-and-updating-the-documentation) - - [Archiving on Zenodo](#archiving-on-zenodo) - - [Updating the Conda Package](#updating-the-conda-package) - -## Onboarding Access Checklist - -- [ ] Added to [python-maintainers](https://github.com/orgs/GenericMappingTools/teams/python-maintainers) team in the [GenericMappingTools](https://github.com/orgs/GenericMappingTools/teams/) organization on GitHub (gives 'maintain' permissions) -- [ ] Added as moderator on [GMT forum](https://forum.generic-mapping-tools.org) (to see mod-only discussions) -- [ ] Added as member on the [PyGMT devs Slack channel](https://pygmtdevs.slack.com) (for casual conversations) -- [ ] Added as maintainer on [PyPI](https://pypi.org/project/pygmt/) and [Test PyPI](https://test.pypi.org/project/pygmt) [optional] -- [ ] Added as member on [HackMD](https://hackmd.io/@pygmt) [optional] - -## Branches - -* *master*: Always tested and ready to become a new version. Don't push directly to this - branch. Make a new branch and submit a pull request instead. -* *gh-pages*: Holds the HTML documentation and is served by GitHub. Pages for the master - branch are in the `dev` folder. Pages for each release are in their own folders. - **Automatically updated by GitHub Actions** so you shouldn't have to make commits here. - - -## Reviewing and merging pull requests - -A few guidelines for reviewing: - -* Always **be polite** and give constructive feedback. -* Welcome new users and thank them for their time, even if we don't plan on merging the - PR. -* Don't be harsh with code style or performance. If the code is bad, either (1) merge - the pull request and open a new one fixing the code and pinging the original submitter - (2) comment on the PR detailing how the code could be improved. Both ways are focused - on showing the contributor **how to write good code**, not shaming them. - -Pull requests should be **squash merged**. -This means that all commits will be collapsed into one. -The main advantages of this are: - -* Eliminates experimental commits or commits to undo previous changes. -* Makes sure every commit on master passes the tests and has a defined purpose. -* The maintainer writes the final commit message, so we can make sure it's good and - descriptive. - - -## Continuous Integration - -We use GitHub Actions continuous integration (CI) services to -build and test the project on Linux, macOS and Windows. -They rely on the `environment.yml` file to install required dependencies using -conda and the `Makefile` to run the tests and checks. - -### GitHub Actions - -There are 8 configuration files located in `.github/workflows`: - -1. `style_checks.yaml` (Code lint and style checks) - - This is run on every commit to the *master* and Pull Request branches. - It is also scheduled to run daily on the *master* branch. - -2. `ci_tests.yaml` (Tests on Linux/macOS/Windows) - - This is run on every commit to the *master* and Pull Request branches. - It is also scheduled to run daily on the *master* branch. - In draft Pull Requests, only one job (Ubuntu + Python latest) - is triggered to save on Continuous Integration resources. - - On the *master* branch, the workflow also handles the documentation - deployment: - - * Updating the development documentation by pushing the built HTML pages - from the *master* branch onto the `dev` folder of the *gh-pages* branch. - * Updating the `latest` documentation link to the new release. - -3. `ci_tests_dev.yaml` (GMT Dev Tests on Linux/macOS/Windows). - - This is triggered when a PR is marked as "ready for review", or using the - slash command `/test-gmt-dev`. It is also scheduled to run daily on the - *master* branch. - -4. `cache_data.yaml` (Caches GMT remote data files needed for GitHub Actions CI) - - This is scheduled to run every Sunday at 12:00 (UTC). - If new remote files are needed urgently, maintainers can manually uncomment - the 'pull_request:' line in that `cache_data.yaml` file to refresh the cache. - -5. `publish-to-pypi.yml` (Publish wheels to PyPI and TestPyPI) - - This workflow is run to publish wheels to PyPI and TestPyPI (for testing only). - Archives will be pushed to TestPyPI on every commit to the *master* branch - and tagged releases, and to PyPI for tagged releases only. - -6. `release-drafter.yml` (Drafts the next release notes) - - This workflow is run to update the next releases notes as pull requests are - merged into master. - -7. `check-links.yml` (Check links in the repository and website) - - This workflow is run weekly to check all external links in plaintext and - HTML files. It will create an issue if broken links are found. - -8. `format-command.yml` (Format the codes using slash command) - - This workflow is triggered in a PR if the slash command `/format` is used. - -## Continuous Documentation - -We use the [Vercel for GitHub](https://github.com/apps/vercel) App to preview changes -made to our documentation website every time we make a commit in a pull request. -The service has a configuration file `vercel.json`, with a list of options to -change the default behaviour at https://vercel.com/docs/configuration. -The actual script `package.json` is used by Vercel to install the necessary packages, -build the documentation, copy the files to a 'public' folder and deploy that to the web, -see https://vercel.com/docs/build-step. - -## Making a Release - -We try to automate the release process as much as possible. -GitHub Actions workflow handles publishing new releases to PyPI and updating the documentation. -The version number is set automatically using setuptools_scm based information -obtained from git. -There are a few steps that still must be done manually, though. - -### Updating the changelog - -The Release Drafter GitHub Action will automatically keep a draft changelog at -https://github.com/GenericMappingTools/pygmt/releases, adding a new entry -every time a Pull Request (with a proper label) is merged into the master branch. -This release drafter tool has two configuration files, one for the GitHub Action -at .github/workflows/release-drafter.yml, and one for the changelog template -at .github/release-drafter.yml. Configuration settings can be found at -https://github.com/release-drafter/release-drafter. - -The drafted release notes are not perfect, so we will need to tidy it prior to -publishing the actual release notes at https://www.pygmt.org/latest/changes.html. - -1. Go to https://github.com/GenericMappingTools/pygmt/releases and click on the - 'Edit' button next to the current draft release note. Copy the text of the - automatically drafted release notes under the 'Write' tab to - `doc/changes.md`. Add a section separator `---` between the new and old - changelog sections. -2. Update the DOI badge in the changelog. Remember to replace the DOI number - inside the badge url. - - ``` - [![Digital Object Identifier for PyGMT vX.Y.Z](https://zenodo.org/badge/DOI/10.5281/zenodo..svg)](https://doi.org/10.5281/zenodo.) - ``` -3. Open a new Pull Request using the title 'Changelog entry for vX.Y.Z' with - the updated release notes, so that other people can help to review and - collaborate on the changelog curation process described next. -4. Edit the change list to remove any trivial changes (updates to the README, - typo fixes, CI configuration, etc). -5. Edit the list of people who contributed to the release, linking to their - GitHub account. Sort their names by the number of commits made since the - last release (e.g. use `` git shortlog HEAD...v0.1.2 -sne ``). -6. Update `README.rst` with new information on the new release version, namely - the BibTeX citation, a vX.Y.Z documentation link, and compatibility with - Python and GMT versions. - -### Check the README syntax - -GitHub is a bit forgiving when it comes to the RST syntax in the README but PyPI is not. -So slightly broken RST can cause the PyPI page to not render the correct content. Check -using the `rst2html.py` script that comes with docutils: - -``` -python setup.py --long-description | rst2html.py --no-raw > index.html -``` - -Open `index.html` and check for any flaws or error messages. - -### Pushing to PyPI and updating the documentation - -After the changelog is updated, making a release can be done by going to -https://github.com/GenericMappingTools/pygmt/releases, editing the draft release, -and clicking on publish. A git tag will also be created, make sure that this -tag is a proper version number (following [Semantic Versioning](https://semver.org/)) -with a leading `v`. E.g. `v0.2.1`. - -Once the release/tag is created, this should trigger GitHub Actions to do all the work for us. -A new source distribution will be uploaded to PyPI, a new folder with the documentation -HTML will be pushed to *gh-pages*, and the `latest` link will be updated to point to -this new folder. - -### Archiving on Zenodo - -Grab a zip file from the GitHub release and upload to Zenodo using the previously -reserved DOI. - -### Updating the conda package - -When a new version is released on PyPI, conda-forge's bot automatically creates version -updates for the feedstock. In most cases, the maintainers can simply merge that PR. - -If changes need to be done manually, you can: - -1. Fork the [pygmt feedstock repository](https://github.com/conda-forge/pygmt-feedstock) if - you haven't already. If you have a fork, update it. -2. Update the version number and sha256 hash on `recipe/meta.yaml`. You can get the hash - from the PyPI "Download files" section. -3. Add or remove any new dependencies (most are probably only `run` dependencies). -4. Make a new branch, commit, and push the changes **to your personal fork**. -5. Create a PR against the original feedstock master. -6. Once the CI tests pass, merge the PR or ask a maintainer to do so. +# Maintainers Guide + +This page contains instructions for project maintainers about how our setup works, +making releases, creating packages, etc. + +If you want to make a contribution to the project, see the +[Contributing Guide](CONTRIBUTING.md) instead. + +## Table of Contents +* [Onboarding Access Checklist](#onboarding-access-checklist) +* [Branches](#branches) +* [Reviewing and Merging Pull Requests](#reviewing-and-merging-pull-requests) +* [Continuous Integration](#continuous-integration) + - [Github Actions](#github-actions) +* [Continuous Documentation](#continuous-documentation) +* [Making a Release](#making-a-release) + - [Updating the Changelog](#updating-the-changelog) + - [Check the README Syntax](#check-the-readme-syntax) + - [Pushing to PyPI and Updating the Documentation](#pushing-to-pypi-and-updating-the-documentation) + - [Archiving on Zenodo](#archiving-on-zenodo) + - [Updating the Conda Package](#updating-the-conda-package) + +## Onboarding Access Checklist + +- [ ] Added to [python-maintainers](https://github.com/orgs/GenericMappingTools/teams/python-maintainers) team in the [GenericMappingTools](https://github.com/orgs/GenericMappingTools/teams/) organization on GitHub (gives 'maintain' permissions) +- [ ] Added as moderator on [GMT forum](https://forum.generic-mapping-tools.org) (to see mod-only discussions) +- [ ] Added as member on the [PyGMT devs Slack channel](https://pygmtdevs.slack.com) (for casual conversations) +- [ ] Added as maintainer on [PyPI](https://pypi.org/project/pygmt/) and [Test PyPI](https://test.pypi.org/project/pygmt) [optional] +- [ ] Added as member on [HackMD](https://hackmd.io/@pygmt) [optional] + +## Branches + +* *master*: Always tested and ready to become a new version. Don't push directly to this + branch. Make a new branch and submit a pull request instead. +* *gh-pages*: Holds the HTML documentation and is served by GitHub. Pages for the master + branch are in the `dev` folder. Pages for each release are in their own folders. + **Automatically updated by GitHub Actions** so you shouldn't have to make commits here. + + +## Reviewing and merging pull requests + +A few guidelines for reviewing: + +* Always **be polite** and give constructive feedback. +* Welcome new users and thank them for their time, even if we don't plan on merging the + PR. +* Don't be harsh with code style or performance. If the code is bad, either (1) merge + the pull request and open a new one fixing the code and pinging the original submitter + (2) comment on the PR detailing how the code could be improved. Both ways are focused + on showing the contributor **how to write good code**, not shaming them. + +Pull requests should be **squash merged**. +This means that all commits will be collapsed into one. +The main advantages of this are: + +* Eliminates experimental commits or commits to undo previous changes. +* Makes sure every commit on master passes the tests and has a defined purpose. +* The maintainer writes the final commit message, so we can make sure it's good and + descriptive. + + +## Continuous Integration + +We use GitHub Actions continuous integration (CI) services to +build and test the project on Linux, macOS and Windows. +They rely on the `environment.yml` file to install required dependencies using +conda and the `Makefile` to run the tests and checks. + +### GitHub Actions + +There are 8 configuration files located in `.github/workflows`: + +1. `style_checks.yaml` (Code lint and style checks) + + This is run on every commit to the *master* and Pull Request branches. + It is also scheduled to run daily on the *master* branch. + +2. `ci_tests.yaml` (Tests on Linux/macOS/Windows) + + This is run on every commit to the *master* and Pull Request branches. + It is also scheduled to run daily on the *master* branch. + In draft Pull Requests, only one job (Ubuntu + Python latest) + is triggered to save on Continuous Integration resources. + + On the *master* branch, the workflow also handles the documentation + deployment: + + * Updating the development documentation by pushing the built HTML pages + from the *master* branch onto the `dev` folder of the *gh-pages* branch. + * Updating the `latest` documentation link to the new release. + +3. `ci_tests_dev.yaml` (GMT Dev Tests on Linux/macOS/Windows). + + This is triggered when a PR is marked as "ready for review", or using the + slash command `/test-gmt-dev`. It is also scheduled to run daily on the + *master* branch. + +4. `cache_data.yaml` (Caches GMT remote data files needed for GitHub Actions CI) + + This is scheduled to run every Sunday at 12:00 (UTC). + If new remote files are needed urgently, maintainers can manually uncomment + the 'pull_request:' line in that `cache_data.yaml` file to refresh the cache. + +5. `publish-to-pypi.yml` (Publish wheels to PyPI and TestPyPI) + + This workflow is run to publish wheels to PyPI and TestPyPI (for testing only). + Archives will be pushed to TestPyPI on every commit to the *master* branch + and tagged releases, and to PyPI for tagged releases only. + +6. `release-drafter.yml` (Drafts the next release notes) + + This workflow is run to update the next releases notes as pull requests are + merged into master. + +7. `check-links.yml` (Check links in the repository and website) + + This workflow is run weekly to check all external links in plaintext and + HTML files. It will create an issue if broken links are found. + +8. `format-command.yml` (Format the codes using slash command) + + This workflow is triggered in a PR if the slash command `/format` is used. + +## Continuous Documentation + +We use the [Vercel for GitHub](https://github.com/apps/vercel) App to preview changes +made to our documentation website every time we make a commit in a pull request. +The service has a configuration file `vercel.json`, with a list of options to +change the default behaviour at https://vercel.com/docs/configuration. +The actual script `package.json` is used by Vercel to install the necessary packages, +build the documentation, copy the files to a 'public' folder and deploy that to the web, +see https://vercel.com/docs/build-step. + +## Making a Release + +We try to automate the release process as much as possible. +GitHub Actions workflow handles publishing new releases to PyPI and updating the documentation. +The version number is set automatically using setuptools_scm based information +obtained from git. +There are a few steps that still must be done manually, though. + +### Updating the changelog + +The Release Drafter GitHub Action will automatically keep a draft changelog at +https://github.com/GenericMappingTools/pygmt/releases, adding a new entry +every time a Pull Request (with a proper label) is merged into the master branch. +This release drafter tool has two configuration files, one for the GitHub Action +at .github/workflows/release-drafter.yml, and one for the changelog template +at .github/release-drafter.yml. Configuration settings can be found at +https://github.com/release-drafter/release-drafter. + +The drafted release notes are not perfect, so we will need to tidy it prior to +publishing the actual release notes at https://www.pygmt.org/latest/changes.html. + +1. Go to https://github.com/GenericMappingTools/pygmt/releases and click on the + 'Edit' button next to the current draft release note. Copy the text of the + automatically drafted release notes under the 'Write' tab to + `doc/changes.md`. Add a section separator `---` between the new and old + changelog sections. +2. Update the DOI badge in the changelog. Remember to replace the DOI number + inside the badge url. + + ``` + [![Digital Object Identifier for PyGMT vX.Y.Z](https://zenodo.org/badge/DOI/10.5281/zenodo..svg)](https://doi.org/10.5281/zenodo.) + ``` +3. Open a new Pull Request using the title 'Changelog entry for vX.Y.Z' with + the updated release notes, so that other people can help to review and + collaborate on the changelog curation process described next. +4. Edit the change list to remove any trivial changes (updates to the README, + typo fixes, CI configuration, etc). +5. Edit the list of people who contributed to the release, linking to their + GitHub account. Sort their names by the number of commits made since the + last release (e.g. use `` git shortlog HEAD...v0.1.2 -sne ``). +6. Update `README.rst` with new information on the new release version, namely + the BibTeX citation, a vX.Y.Z documentation link, and compatibility with + Python and GMT versions. + +### Check the README syntax + +GitHub is a bit forgiving when it comes to the RST syntax in the README but PyPI is not. +So slightly broken RST can cause the PyPI page to not render the correct content. Check +using the `rst2html.py` script that comes with docutils: + +``` +python setup.py --long-description | rst2html.py --no-raw > index.html +``` + +Open `index.html` and check for any flaws or error messages. + +### Pushing to PyPI and updating the documentation + +After the changelog is updated, making a release can be done by going to +https://github.com/GenericMappingTools/pygmt/releases, editing the draft release, +and clicking on publish. A git tag will also be created, make sure that this +tag is a proper version number (following [Semantic Versioning](https://semver.org/)) +with a leading `v`. E.g. `v0.2.1`. + +Once the release/tag is created, this should trigger GitHub Actions to do all the work for us. +A new source distribution will be uploaded to PyPI, a new folder with the documentation +HTML will be pushed to *gh-pages*, and the `latest` link will be updated to point to +this new folder. + +### Archiving on Zenodo + +Grab a zip file from the GitHub release and upload to Zenodo using the previously +reserved DOI. + +### Updating the conda package + +When a new version is released on PyPI, conda-forge's bot automatically creates version +updates for the feedstock. In most cases, the maintainers can simply merge that PR. + +If changes need to be done manually, you can: + +1. Fork the [pygmt feedstock repository](https://github.com/conda-forge/pygmt-feedstock) if + you haven't already. If you have a fork, update it. +2. Update the version number and sha256 hash on `recipe/meta.yaml`. You can get the hash + from the PyPI "Download files" section. +3. Add or remove any new dependencies (most are probably only `run` dependencies). +4. Make a new branch, commit, and push the changes **to your personal fork**. +5. Create a PR against the original feedstock master. +6. Once the CI tests pass, merge the PR or ask a maintainer to do so. diff --git a/Makefile b/Makefile index e5cae667ecd..dac2bb37298 100644 --- a/Makefile +++ b/Makefile @@ -1,71 +1,71 @@ -# Build, package, test, and clean -PROJECT=pygmt -TESTDIR=tmp-test-dir-with-unique-name -PYTEST_COV_ARGS=--cov=$(PROJECT) --cov-config=../pyproject.toml \ - --cov-report=term-missing --cov-report=xml --cov-report=html \ - --pyargs ${PYTEST_EXTRA} -BLACK_FILES=$(PROJECT) setup.py doc/conf.py examples -BLACKDOC_OPTIONS=--line-length 79 -DOCFORMATTER_FILES=$(PROJECT) setup.py doc/conf.py examples -DOCFORMATTER_OPTIONS=--recursive --pre-summary-newline --make-summary-multi-line --wrap-summaries 79 --wrap-descriptions 79 -FLAKE8_FILES=$(PROJECT) setup.py doc/conf.py -LINT_FILES=$(PROJECT) setup.py doc/conf.py - -help: - @echo "Commands:" - @echo "" - @echo " install install in editable mode" - @echo " package build source and wheel distributions" - @echo " test run the test suite (including doctests) and report coverage" - @echo " format run black, blackdoc, docformatter and isort to automatically format the code" - @echo " check run code style and quality checks (black, blackdoc, docformatter, flake8 and isort)" - @echo " lint run pylint for a deeper (and slower) quality check" - @echo " clean clean up build and generated files" - @echo " distclean clean up build and generated files, including project metadata files" - @echo "" - -install: - pip install --no-deps -e . - -package: - python setup.py sdist bdist_wheel - -test: - # Run a tmp folder to make sure the tests are run on the installed version - mkdir -p $(TESTDIR) - @echo "" - @cd $(TESTDIR); python -c "import $(PROJECT); $(PROJECT).show_versions()" - @echo "" - cd $(TESTDIR); pytest $(PYTEST_COV_ARGS) $(PROJECT) - cp $(TESTDIR)/coverage.xml . - cp -r $(TESTDIR)/htmlcov . - rm -r $(TESTDIR) - -format: - isort . - docformatter --in-place $(DOCFORMATTER_OPTIONS) $(DOCFORMATTER_FILES) - black $(BLACK_FILES) - blackdoc $(BLACKDOC_OPTIONS) $(BLACK_FILES) - -check: - isort . --check - docformatter --check $(DOCFORMATTER_OPTIONS) $(DOCFORMATTER_FILES) - black --check $(BLACK_FILES) - blackdoc --check $(BLACKDOC_OPTIONS) $(BLACK_FILES) - flake8 $(FLAKE8_FILES) - -lint: - pylint $(LINT_FILES) - -clean: - find . -name "*.pyc" -exec rm -v {} + - find . -name "*~" -exec rm -v {} + - find . -type d -name "__pycache__" -exec rm -rv {} + - rm -rvf build dist .eggs MANIFEST .coverage .cache .pytest_cache htmlcov coverage.xml - rm -rvf $(TESTDIR) - rm -rvf baseline - rm -rvf result_images - rm -rvf results - -distclean: clean - rm -rvf *.egg-info +# Build, package, test, and clean +PROJECT=pygmt +TESTDIR=tmp-test-dir-with-unique-name +PYTEST_COV_ARGS=--cov=$(PROJECT) --cov-config=../pyproject.toml \ + --cov-report=term-missing --cov-report=xml --cov-report=html \ + --pyargs ${PYTEST_EXTRA} +BLACK_FILES=$(PROJECT) setup.py doc/conf.py examples +BLACKDOC_OPTIONS=--line-length 79 +DOCFORMATTER_FILES=$(PROJECT) setup.py doc/conf.py examples +DOCFORMATTER_OPTIONS=--recursive --pre-summary-newline --make-summary-multi-line --wrap-summaries 79 --wrap-descriptions 79 +FLAKE8_FILES=$(PROJECT) setup.py doc/conf.py +LINT_FILES=$(PROJECT) setup.py doc/conf.py + +help: + @echo "Commands:" + @echo "" + @echo " install install in editable mode" + @echo " package build source and wheel distributions" + @echo " test run the test suite (including doctests) and report coverage" + @echo " format run black, blackdoc, docformatter and isort to automatically format the code" + @echo " check run code style and quality checks (black, blackdoc, docformatter, flake8 and isort)" + @echo " lint run pylint for a deeper (and slower) quality check" + @echo " clean clean up build and generated files" + @echo " distclean clean up build and generated files, including project metadata files" + @echo "" + +install: + pip install --no-deps -e . + +package: + python setup.py sdist bdist_wheel + +test: + # Run a tmp folder to make sure the tests are run on the installed version + mkdir -p $(TESTDIR) + @echo "" + @cd $(TESTDIR); python -c "import $(PROJECT); $(PROJECT).show_versions()" + @echo "" + cd $(TESTDIR); pytest $(PYTEST_COV_ARGS) $(PROJECT) + cp $(TESTDIR)/coverage.xml . + cp -r $(TESTDIR)/htmlcov . + rm -r $(TESTDIR) + +format: + isort . + docformatter --in-place $(DOCFORMATTER_OPTIONS) $(DOCFORMATTER_FILES) + black $(BLACK_FILES) + blackdoc $(BLACKDOC_OPTIONS) $(BLACK_FILES) + +check: + isort . --check + docformatter --check $(DOCFORMATTER_OPTIONS) $(DOCFORMATTER_FILES) + black --check $(BLACK_FILES) + blackdoc --check $(BLACKDOC_OPTIONS) $(BLACK_FILES) + flake8 $(FLAKE8_FILES) + +lint: + pylint $(LINT_FILES) + +clean: + find . -name "*.pyc" -exec rm -v {} + + find . -name "*~" -exec rm -v {} + + find . -type d -name "__pycache__" -exec rm -rv {} + + rm -rvf build dist .eggs MANIFEST .coverage .cache .pytest_cache htmlcov coverage.xml + rm -rvf $(TESTDIR) + rm -rvf baseline + rm -rvf result_images + rm -rvf results + +distclean: clean + rm -rvf *.egg-info diff --git a/README.rst b/README.rst index c82ffecd5a0..f8fd846aaf1 100644 --- a/README.rst +++ b/README.rst @@ -1,239 +1,239 @@ -PyGMT -===== - - A Python interface for the Generic Mapping Tools - -`Documentation (development version) `__ | -`Contact `__ | -`Try Online `__ - -.. image:: http://img.shields.io/pypi/v/pygmt.svg?style=flat-square - :alt: Latest version on PyPI - :target: https://pypi.python.org/pypi/pygmt -.. image:: https://github.com/GenericMappingTools/pygmt/workflows/Tests/badge.svg - :alt: GitHub Actions Tests status - :target: https://github.com/GenericMappingTools/pygmt/actions/workflows/ci_tests.yaml -.. image:: https://github.com/GenericMappingTools/pygmt/workflows/GMT%20Dev%20Tests/badge.svg - :alt: GitHub Actions GMT Dev Tests status - :target: https://github.com/GenericMappingTools/pygmt/actions/workflows/ci_tests_dev.yaml -.. image:: https://img.shields.io/codecov/c/github/GenericMappingTools/pygmt/master.svg?style=flat-square - :alt: Test coverage status - :target: https://codecov.io/gh/GenericMappingTools/pygmt -.. image:: https://img.shields.io/pypi/pyversions/pygmt.svg?style=flat-square - :alt: Compatible Python versions. - :target: https://pypi.python.org/pypi/pygmt -.. image:: https://img.shields.io/discourse/status?label=forum&server=https%3A%2F%2Fforum.generic-mapping-tools.org%2F&style=flat-square - :alt: Discourse forum - :target: https://forum.generic-mapping-tools.org -.. image:: https://zenodo.org/badge/DOI/10.5281/3781524.svg - :alt: Digital Object Identifier for the Zenodo archive - :target: https://doi.org/10.5281/zenodo.3781524 -.. image:: https://img.shields.io/badge/Contributor%20Covenant-v2.0%20adopted-ff69b4.svg - :alt: Contributor Code of Conduct - :target: CODE_OF_CONDUCT.md - -.. placeholder-for-doc-index - - -Why PyGMT? ----------- - -A beautiful map is worth a thousand words. -To truly understand how powerful PyGMT is, play with it online on `Binder `__! -But if you need some convincing first, watch this `1 hour introduction `__ to PyGMT! - -Afterwards, feel free to look at our `Tutorials `__ -or visit the `PyGMT Gallery `__. - -.. image:: https://user-images.githubusercontent.com/23487320/95393255-c0b72e80-0956-11eb-9471-24429461802b.png - :alt: Remote Online Sessions for Emerging Seismologists (ROSES): Unit 8 - PyGMT - :align: center - :target: https://www.youtube.com/watch?v=SSIGJEe0BIk - - -Disclaimer ----------- - -🚨 **This package is still undergoing rapid development.** 🚨 - -All of the API (functions/classes/interfaces) is subject to change until we reach v1.0.0 -as per the `semantic versioning specification `__. -There may be non-backward compatible changes as we experiment with new design ideas and -implement new features. **This is not a finished product, use with caution.** - -We welcome any feedback and ideas! -Let us know by submitting -`issues on GitHub `__ -or by posting on our `Discourse forum `__. - -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) `__, a -command-line program widely used in the Earth Sciences. - -We rely heavily on new features that have been implemented in GMT 6.0. In particular, -a new *modern execution mode* that greatly simplifies figure creation. **These features -are not available in the 5.4 version of GMT**. - - -Project goals -------------- - -* Make GMT more accessible to new users. -* 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 `PyData `__ ecosystem: - ``numpy.ndarray`` or ``pandas.DataFrame`` for data tables and - ``xarray.DataArray`` for grids. - - -Contacting Us -------------- - -* Most discussion happens `on GitHub - `__. Feel free to `open an issue - `__ or comment on any - open issue or pull request. -* We have a `Discourse forum - `__ where you can ask - questions and leave comments. - - -Contributing ------------- - -Code of conduct -+++++++++++++++ - -Please note that this project is released with a `Contributor Code of Conduct -`__. -By participating in this project you agree to abide by its terms. - -Contributing Guidelines -+++++++++++++++++++++++ - -Please read our `Contributing Guide -`__ to -see how you can help and give feedback. - -Imposter syndrome disclaimer -++++++++++++++++++++++++++++ - -**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? - -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. - -*This disclaimer was adapted from the* -`MetPy project `__. - - -Citing PyGMT ------------- - -PyGMT is a community developed project. See the -`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: - -.. code-block:: - - @software{pygmt_2021_4522136, - author = {Uieda, Leonardo and - Tian, Dongdong and - Leong, Wei Ji and - Toney, Liam and - Schlitzer, William and - Grund, Michael and - Newton, Tyler and - Ziebarth, Malte and - Jones, Meghan and - Wessel, Paul}, - title = {{PyGMT: A Python interface for the Generic Mapping Tools}}, - month = feb, - year = 2021, - publisher = {Zenodo}, - version = {v0.3.0}, - doi = {10.5281/zenodo.4522136}, - url = {https://doi.org/10.5281/zenodo.4522136} - } - -To cite a specific version of PyGMT, go to our Zenodo page at -https://doi.org/10.5281/zenodo.3781524 and use the "Export to BibTeX" function there. -It is also strongly recommended to cite the -`GMT6 paper `__ (which PyGMT wraps around). -Note that some modules like ``surface`` and ``x2sys`` also have their dedicated citation. -Further information for all these can be found at https://www.generic-mapping-tools.org/cite. - - -License -------- - -PyGMT is free software: you can redistribute it and/or modify it under the terms of -the **BSD 3-clause License**. A copy of this license is provided in -`LICENSE.txt `__. - - -Support -------- - -The development of PyGMT has been supported by NSF grants -`OCE-1558403 `__ and -`EAR-1948603 `__. - - -Related projects ----------------- - -* `GMT.jl `__: A Julia wrapper for GMT. -* `gmtmex `__: A Matlab/Octave wrapper - for GMT. - -Other Python wrappers for GMT: - -* `gmtpy `__ by `Sebastian Heimann `__ -* `pygmt `__ by `Ian Rose `__ -* `PyGMT `__ by `Magnus Hagdorn `__ - - -Documentation for other versions --------------------------------- - -* `Development `__ (reflects the *master* branch on - GitHub) -* `Latest release `__ -* `v0.3.0 `__ -* `v0.2.1 `__ -* `v0.2.0 `__ -* `v0.1.2 `__ -* `v0.1.1 `__ -* `v0.1.0 `__ -* `v0.0.1a0 `__ - -Compatibility with Python and GMT versions ------------------------------------------- - -======= ========== ========= -PyGMT GMT Python -======= ========== ========= -0.3.0 >=6.1.1 >=3.7 -0.2.1 >=6.1.1 >=3.6 -0.2.0 >=6.1.1 3.6 - 3.8 -0.1.x >=6.0.0 3.6 - 3.8 -======= ========== ========= +PyGMT +===== + + A Python interface for the Generic Mapping Tools + +`Documentation (development version) `__ | +`Contact `__ | +`Try Online `__ + +.. image:: http://img.shields.io/pypi/v/pygmt.svg?style=flat-square + :alt: Latest version on PyPI + :target: https://pypi.python.org/pypi/pygmt +.. image:: https://github.com/GenericMappingTools/pygmt/workflows/Tests/badge.svg + :alt: GitHub Actions Tests status + :target: https://github.com/GenericMappingTools/pygmt/actions/workflows/ci_tests.yaml +.. image:: https://github.com/GenericMappingTools/pygmt/workflows/GMT%20Dev%20Tests/badge.svg + :alt: GitHub Actions GMT Dev Tests status + :target: https://github.com/GenericMappingTools/pygmt/actions/workflows/ci_tests_dev.yaml +.. image:: https://img.shields.io/codecov/c/github/GenericMappingTools/pygmt/master.svg?style=flat-square + :alt: Test coverage status + :target: https://codecov.io/gh/GenericMappingTools/pygmt +.. image:: https://img.shields.io/pypi/pyversions/pygmt.svg?style=flat-square + :alt: Compatible Python versions. + :target: https://pypi.python.org/pypi/pygmt +.. image:: https://img.shields.io/discourse/status?label=forum&server=https%3A%2F%2Fforum.generic-mapping-tools.org%2F&style=flat-square + :alt: Discourse forum + :target: https://forum.generic-mapping-tools.org +.. image:: https://zenodo.org/badge/DOI/10.5281/3781524.svg + :alt: Digital Object Identifier for the Zenodo archive + :target: https://doi.org/10.5281/zenodo.3781524 +.. image:: https://img.shields.io/badge/Contributor%20Covenant-v2.0%20adopted-ff69b4.svg + :alt: Contributor Code of Conduct + :target: CODE_OF_CONDUCT.md + +.. placeholder-for-doc-index + + +Why PyGMT? +---------- + +A beautiful map is worth a thousand words. +To truly understand how powerful PyGMT is, play with it online on `Binder `__! +But if you need some convincing first, watch this `1 hour introduction `__ to PyGMT! + +Afterwards, feel free to look at our `Tutorials `__ +or visit the `PyGMT Gallery `__. + +.. image:: https://user-images.githubusercontent.com/23487320/95393255-c0b72e80-0956-11eb-9471-24429461802b.png + :alt: Remote Online Sessions for Emerging Seismologists (ROSES): Unit 8 - PyGMT + :align: center + :target: https://www.youtube.com/watch?v=SSIGJEe0BIk + + +Disclaimer +---------- + +🚨 **This package is still undergoing rapid development.** 🚨 + +All of the API (functions/classes/interfaces) is subject to change until we reach v1.0.0 +as per the `semantic versioning specification `__. +There may be non-backward compatible changes as we experiment with new design ideas and +implement new features. **This is not a finished product, use with caution.** + +We welcome any feedback and ideas! +Let us know by submitting +`issues on GitHub `__ +or by posting on our `Discourse forum `__. + +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) `__, a +command-line program widely used in the Earth Sciences. + +We rely heavily on new features that have been implemented in GMT 6.0. In particular, +a new *modern execution mode* that greatly simplifies figure creation. **These features +are not available in the 5.4 version of GMT**. + + +Project goals +------------- + +* Make GMT more accessible to new users. +* 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 `PyData `__ ecosystem: + ``numpy.ndarray`` or ``pandas.DataFrame`` for data tables and + ``xarray.DataArray`` for grids. + + +Contacting Us +------------- + +* Most discussion happens `on GitHub + `__. Feel free to `open an issue + `__ or comment on any + open issue or pull request. +* We have a `Discourse forum + `__ where you can ask + questions and leave comments. + + +Contributing +------------ + +Code of conduct ++++++++++++++++ + +Please note that this project is released with a `Contributor Code of Conduct +`__. +By participating in this project you agree to abide by its terms. + +Contributing Guidelines ++++++++++++++++++++++++ + +Please read our `Contributing Guide +`__ to +see how you can help and give feedback. + +Imposter syndrome disclaimer +++++++++++++++++++++++++++++ + +**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? + +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. + +*This disclaimer was adapted from the* +`MetPy project `__. + + +Citing PyGMT +------------ + +PyGMT is a community developed project. See the +`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: + +.. code-block:: + + @software{pygmt_2021_4522136, + author = {Uieda, Leonardo and + Tian, Dongdong and + Leong, Wei Ji and + Toney, Liam and + Schlitzer, William and + Grund, Michael and + Newton, Tyler and + Ziebarth, Malte and + Jones, Meghan and + Wessel, Paul}, + title = {{PyGMT: A Python interface for the Generic Mapping Tools}}, + month = feb, + year = 2021, + publisher = {Zenodo}, + version = {v0.3.0}, + doi = {10.5281/zenodo.4522136}, + url = {https://doi.org/10.5281/zenodo.4522136} + } + +To cite a specific version of PyGMT, go to our Zenodo page at +https://doi.org/10.5281/zenodo.3781524 and use the "Export to BibTeX" function there. +It is also strongly recommended to cite the +`GMT6 paper `__ (which PyGMT wraps around). +Note that some modules like ``surface`` and ``x2sys`` also have their dedicated citation. +Further information for all these can be found at https://www.generic-mapping-tools.org/cite. + + +License +------- + +PyGMT is free software: you can redistribute it and/or modify it under the terms of +the **BSD 3-clause License**. A copy of this license is provided in +`LICENSE.txt `__. + + +Support +------- + +The development of PyGMT has been supported by NSF grants +`OCE-1558403 `__ and +`EAR-1948603 `__. + + +Related projects +---------------- + +* `GMT.jl `__: A Julia wrapper for GMT. +* `gmtmex `__: A Matlab/Octave wrapper + for GMT. + +Other Python wrappers for GMT: + +* `gmtpy `__ by `Sebastian Heimann `__ +* `pygmt `__ by `Ian Rose `__ +* `PyGMT `__ by `Magnus Hagdorn `__ + + +Documentation for other versions +-------------------------------- + +* `Development `__ (reflects the *master* branch on + GitHub) +* `Latest release `__ +* `v0.3.0 `__ +* `v0.2.1 `__ +* `v0.2.0 `__ +* `v0.1.2 `__ +* `v0.1.1 `__ +* `v0.1.0 `__ +* `v0.0.1a0 `__ + +Compatibility with Python and GMT versions +------------------------------------------ + +======= ========== ========= +PyGMT GMT Python +======= ========== ========= +0.3.0 >=6.1.1 >=3.7 +0.2.1 >=6.1.1 >=3.6 +0.2.0 >=6.1.1 3.6 - 3.8 +0.1.x >=6.0.0 3.6 - 3.8 +======= ========== ========= diff --git a/doc/_static/style.css b/doc/_static/style.css index 2cec2bff641..51c8d3465b2 100644 --- a/doc/_static/style.css +++ b/doc/_static/style.css @@ -1,169 +1,169 @@ -/* To stick the footer to the bottom of the page */ -html { -} - -body { - font-family: "Atkinson Hyperlegible", sans-serif; - font-size: 1.05em; -} - -h1, h2, h3, h4 { - font-weight: normal; - font-family: "Atkinson Hyperlegible", sans-serif; -} - -h1 { - font-size: 200%; -} - -p { - font-size: 1.05em; -} - -.gmtplot-output { - width: 100%; - overflow: auto; -} - -.gmtplot-output img { - max-width: 700px; - width: 100%; -} - -.gmtplot-output pre { - color: #888888; -} - -.gmtplot-output-figure { - margin-bottom: 50px; -} - -.rst-content img { - max-width: 80%; -} - -.sidebar-title { - margin-top: 10px; - margin-bottom: 0px; -} - -.banner { - padding-bottom: 60px; - text-align: center; -} - -.banner h1 { - margin-top: 40px; - font-size: 4em; -} - -.api-module { - margin-bottom: 80px; -} - -.youtube-embed { - max-width: 600px; - margin-bottom: 24px; -} - -.video-container { - position:relative; - padding-bottom:56.25%; - padding-top:30px; - height:0; - overflow:hidden; -} - -.video-container iframe, .video-container object, .video-container embed { - position:absolute; - top:0; - left:0; - width:100%; - height:100%; -} - -/*.wy-menu-vertical header, .wy-menu-vertical p.caption {*/ - /*font-size: 90%;*/ - /*font-weight: bold;*/ - /*color: #eeeeee;*/ - /*letter-spacing: 0.12em;*/ -/*}*/ - -.wy-nav-content { - max-width: 1000px; -} - -/* Remove the padding from the Parameters table */ -.rst-content table.field-list .field-name { - padding-left: 0px; -} - -/* Lign up the Parameters section with the descriptions */ -.rst-content table.field-list td { - padding-top: 8px; -} - -.rst-content .highlight > pre { - font-size: 14px; -} - -.source-link { - float: right; -} - -/* Don't let the edit and notebook download links disappear on mobile. */ -@media screen and (max-width: 480px) { - .wy-breadcrumbs li.source-link { - float:none; - display: block; - margin-top: 20px; - } -} - -/* Style for the copy button */ -/* Safe to remove for sphinx-copybutton>0.3.1 - * https://github.com/executablebooks/sphinx-copybutton/pull/107 - */ -a.copybtn { - position: absolute; - top: .2em; - right: .2em; - width: 1.3em; - height: 1.3em; - opacity: .3; - transition: opacity 0.5s; - border: none; - user-select: none; -} - -/* Atkinson Hyperlegible regular */ -@font-face { - font-family: "Atkinson Hyperlegible"; - src: url("./fonts/Atkinson-Hyperlegible-Regular-102a.woff2") format('woff2'); - font-weight: normal; - font-style: normal; -} - -/* Atkinson Hyperlegible bold */ -@font-face { - font-family: "Atkinson Hyperlegible"; - src: url("./fonts/Atkinson-Hyperlegible-Bold-102a.woff2") format('woff2'); - font-weight: bold; - font-style: normal; -} - -/* Atkinson Hyperlegible italic */ -@font-face { - font-family: "Atkinson Hyperlegible"; - src: url("./fonts/Atkinson-Hyperlegible-Italic-102a.woff2") format('woff2'); - font-weight: normal; - font-style: italic; -} - -/* Atkinson Hyperlegible bold italic */ -@font-face { - font-family: "Atkinson Hyperlegible"; - src: url("./fonts/Atkinson-Hyperlegible-BoldItalic-102a.woff2") format('woff2'); - font-weight: bold; - font-style: italic; -} +/* To stick the footer to the bottom of the page */ +html { +} + +body { + font-family: "Atkinson Hyperlegible", sans-serif; + font-size: 1.05em; +} + +h1, h2, h3, h4 { + font-weight: normal; + font-family: "Atkinson Hyperlegible", sans-serif; +} + +h1 { + font-size: 200%; +} + +p { + font-size: 1.05em; +} + +.gmtplot-output { + width: 100%; + overflow: auto; +} + +.gmtplot-output img { + max-width: 700px; + width: 100%; +} + +.gmtplot-output pre { + color: #888888; +} + +.gmtplot-output-figure { + margin-bottom: 50px; +} + +.rst-content img { + max-width: 80%; +} + +.sidebar-title { + margin-top: 10px; + margin-bottom: 0px; +} + +.banner { + padding-bottom: 60px; + text-align: center; +} + +.banner h1 { + margin-top: 40px; + font-size: 4em; +} + +.api-module { + margin-bottom: 80px; +} + +.youtube-embed { + max-width: 600px; + margin-bottom: 24px; +} + +.video-container { + position:relative; + padding-bottom:56.25%; + padding-top:30px; + height:0; + overflow:hidden; +} + +.video-container iframe, .video-container object, .video-container embed { + position:absolute; + top:0; + left:0; + width:100%; + height:100%; +} + +/*.wy-menu-vertical header, .wy-menu-vertical p.caption {*/ + /*font-size: 90%;*/ + /*font-weight: bold;*/ + /*color: #eeeeee;*/ + /*letter-spacing: 0.12em;*/ +/*}*/ + +.wy-nav-content { + max-width: 1000px; +} + +/* Remove the padding from the Parameters table */ +.rst-content table.field-list .field-name { + padding-left: 0px; +} + +/* Lign up the Parameters section with the descriptions */ +.rst-content table.field-list td { + padding-top: 8px; +} + +.rst-content .highlight > pre { + font-size: 14px; +} + +.source-link { + float: right; +} + +/* Don't let the edit and notebook download links disappear on mobile. */ +@media screen and (max-width: 480px) { + .wy-breadcrumbs li.source-link { + float:none; + display: block; + margin-top: 20px; + } +} + +/* Style for the copy button */ +/* Safe to remove for sphinx-copybutton>0.3.1 + * https://github.com/executablebooks/sphinx-copybutton/pull/107 + */ +a.copybtn { + position: absolute; + top: .2em; + right: .2em; + width: 1.3em; + height: 1.3em; + opacity: .3; + transition: opacity 0.5s; + border: none; + user-select: none; +} + +/* Atkinson Hyperlegible regular */ +@font-face { + font-family: "Atkinson Hyperlegible"; + src: url("./fonts/Atkinson-Hyperlegible-Regular-102a.woff2") format('woff2'); + font-weight: normal; + font-style: normal; +} + +/* Atkinson Hyperlegible bold */ +@font-face { + font-family: "Atkinson Hyperlegible"; + src: url("./fonts/Atkinson-Hyperlegible-Bold-102a.woff2") format('woff2'); + font-weight: bold; + font-style: normal; +} + +/* Atkinson Hyperlegible italic */ +@font-face { + font-family: "Atkinson Hyperlegible"; + src: url("./fonts/Atkinson-Hyperlegible-Italic-102a.woff2") format('woff2'); + font-weight: normal; + font-style: italic; +} + +/* Atkinson Hyperlegible bold italic */ +@font-face { + font-family: "Atkinson Hyperlegible"; + src: url("./fonts/Atkinson-Hyperlegible-BoldItalic-102a.woff2") format('woff2'); + font-weight: bold; + font-style: italic; +} diff --git a/doc/_static/version_switch.js b/doc/_static/version_switch.js index 0d3df020b4c..2bf592b5c89 100644 --- a/doc/_static/version_switch.js +++ b/doc/_static/version_switch.js @@ -1,81 +1,81 @@ -// Copyright 2013 PSF. Licensed under the PYTHON SOFTWARE FOUNDATION LICENSE VERSION 2 -// File originates from the cpython source found in Doc/tools/sphinxext/static/version_switch.js - -(function() { - 'use strict'; - - var doc_url = "www.pygmt.org"; - //var doc_url = "0.0.0.0:8000"; // for local testing only - var url_re = new RegExp(doc_url + "\\/(dev|latest|(v\\d+\\.\\d+\\.\\d+))\\/"); - // List all versions. - // Add one entry "version: title" for any minor releases - var all_versions = { - 'latest': 'latest', - 'dev': 'dev', - 'v0.3.0': 'v0.3.0', - 'v0.2.1': 'v0.2.1', - 'v0.2.0': 'v0.2.0', - 'v0.1.2': 'v0.1.2', - 'v0.1.1': 'v0.1.1', - 'v0.1.0': 'v0.1.0', - '0.0.1a0': 'v0.0.1a0', - }; - - function build_select(current_version, current_release) { - var buf = [''); - return buf.join(''); - } - - function patch_url(url, new_version) { - return url.replace(url_re, doc_url + '/' + new_version + '/'); - } - - function on_switch() { - var selected = $(this).children('option:selected').attr('value'); - - var url = window.location.href, - new_url = patch_url(url, selected); - - if (new_url != url) { - // check beforehand if url exists, else redirect to version's start page - $.ajax({ - url: new_url, - success: function() { - window.location.href = new_url; - }, - error: function() { - window.location.href = 'http://' + doc_url + '/' + selected; - } - }); - } - } - - $(document).ready(function() { - var match = url_re.exec(window.location.href); - if (match) { - var release = DOCUMENTATION_OPTIONS.VERSION; - var version = match[1]; - var select = build_select(version, release); - $('.version_switch_note').html('Or, select a version from the drop-down menu above.'); - $('.version').html(select); - $('.version select').bind('change', on_switch); - } - }); -})(); +// Copyright 2013 PSF. Licensed under the PYTHON SOFTWARE FOUNDATION LICENSE VERSION 2 +// File originates from the cpython source found in Doc/tools/sphinxext/static/version_switch.js + +(function() { + 'use strict'; + + var doc_url = "www.pygmt.org"; + //var doc_url = "0.0.0.0:8000"; // for local testing only + var url_re = new RegExp(doc_url + "\\/(dev|latest|(v\\d+\\.\\d+\\.\\d+))\\/"); + // List all versions. + // Add one entry "version: title" for any minor releases + var all_versions = { + 'latest': 'latest', + 'dev': 'dev', + 'v0.3.0': 'v0.3.0', + 'v0.2.1': 'v0.2.1', + 'v0.2.0': 'v0.2.0', + 'v0.1.2': 'v0.1.2', + 'v0.1.1': 'v0.1.1', + 'v0.1.0': 'v0.1.0', + '0.0.1a0': 'v0.0.1a0', + }; + + function build_select(current_version, current_release) { + var buf = [''); + return buf.join(''); + } + + function patch_url(url, new_version) { + return url.replace(url_re, doc_url + '/' + new_version + '/'); + } + + function on_switch() { + var selected = $(this).children('option:selected').attr('value'); + + var url = window.location.href, + new_url = patch_url(url, selected); + + if (new_url != url) { + // check beforehand if url exists, else redirect to version's start page + $.ajax({ + url: new_url, + success: function() { + window.location.href = new_url; + }, + error: function() { + window.location.href = 'http://' + doc_url + '/' + selected; + } + }); + } + } + + $(document).ready(function() { + var match = url_re.exec(window.location.href); + if (match) { + var release = DOCUMENTATION_OPTIONS.VERSION; + var version = match[1]; + var select = build_select(version, release); + $('.version_switch_note').html('Or, select a version from the drop-down menu above.'); + $('.version').html(select); + $('.version select').bind('change', on_switch); + } + }); +})(); diff --git a/doc/_templates/breadcrumbs.html b/doc/_templates/breadcrumbs.html index e585169259b..26ec57270bd 100644 --- a/doc/_templates/breadcrumbs.html +++ b/doc/_templates/breadcrumbs.html @@ -1,29 +1,29 @@ -{# Extend the RTD template to include "Edit on GitHub" #} -{% extends "!breadcrumbs.html" %} - - -{% block breadcrumbs_aside %} - -{% endblock %} +{# Extend the RTD template to include "Edit on GitHub" #} +{% extends "!breadcrumbs.html" %} + + +{% block breadcrumbs_aside %} + +{% endblock %} diff --git a/doc/api/index.rst b/doc/api/index.rst index 58a41dd8cec..359099c1379 100644 --- a/doc/api/index.rst +++ b/doc/api/index.rst @@ -1,220 +1,220 @@ -.. _api: - -API Reference -============= - -.. automodule:: pygmt - -.. currentmodule:: pygmt - -Plotting --------- - -All plotting is handled through the :class:`pygmt.Figure` class and its methods. - -.. autosummary:: - :toctree: generated - - Figure - -Plotting data and laying out the map: - -.. autosummary:: - :toctree: generated - - Figure.basemap - Figure.coast - Figure.colorbar - Figure.contour - Figure.grdcontour - Figure.grdimage - Figure.grdview - Figure.image - Figure.inset - Figure.legend - Figure.logo - Figure.meca - Figure.plot - Figure.plot3d - Figure.set_panel - Figure.shift_origin - Figure.subplot - Figure.text - -Color palette table generation: - -.. autosummary:: - :toctree: generated - - grd2cpt - makecpt - -Saving and displaying the figure: - -.. autosummary:: - :toctree: generated - - Figure.savefig - Figure.show - Figure.psconvert - - -Data Processing ---------------- - -Operations on tabular data: - -.. autosummary:: - :toctree: generated - - blockmedian - surface - -Operations on grids: - -.. autosummary:: - :toctree: generated - - grdcut - grdfilter - grdtrack - -Crossover analysis with x2sys: - -.. autosummary:: - :toctree: generated - - x2sys_init - x2sys_cross - -GMT Defaults ------------- - -Operations on GMT defaults: - -.. autosummary:: - :toctree: generated - - config - -Metadata --------- - -Getting metadata from tabular or grid data: - -.. autosummary:: - :toctree: generated - - GMTDataArrayAccessor - info - grdinfo - - -Miscellaneous -------------- - -.. autosummary:: - :toctree: generated - - which - test - print_clib_info - show_versions - - -.. automodule:: pygmt.datasets - -.. currentmodule:: pygmt - -Datasets --------- - -PyGMT provides access to GMT's datasets through the :mod:`pygmt.datasets` package. -These functions will download the datasets automatically the first time they are used -and store them in the GMT cache folder. - -.. autosummary:: - :toctree: generated - - datasets.load_earth_relief - datasets.load_japan_quakes - datasets.load_ocean_ridge_points - datasets.load_sample_bathymetry - datasets.load_usgs_quakes - -.. automodule:: pygmt.exceptions - -.. currentmodule:: pygmt - -Exceptions ----------- - -All custom exceptions are derived from :class:`pygmt.exceptions.GMTError`. - -.. autosummary:: - :toctree: generated - - exceptions.GMTError - exceptions.GMTInvalidInput - exceptions.GMTVersionError - exceptions.GMTOSError - exceptions.GMTCLibError - exceptions.GMTCLibNoSessionError - exceptions.GMTCLibNotFoundError - - -.. automodule:: pygmt.clib - -.. currentmodule:: pygmt - -GMT C API ---------- - -The :mod:`pygmt.clib` package is a wrapper for the GMT C API built using :mod:`ctypes`. -Most calls to the C API happen through the :class:`pygmt.clib.Session` class. - -.. autosummary:: - :toctree: generated - - clib.Session - -`GMT modules `__ are executed through -the :meth:`~pygmt.clib.Session.call_module` method: - -.. autosummary:: - :toctree: generated - - clib.Session.call_module - -Passing memory blocks between Python data objects (e.g. :class:`numpy.ndarray`, -:class:`pandas.Series`, :class:`xarray.DataArray`, etc) and GMT happens through -*virtual files*. These methods are context managers that automate the -conversion of Python variables to GMT virtual files: - -.. autosummary:: - :toctree: generated - - clib.Session.virtualfile_from_data - clib.Session.virtualfile_from_matrix - clib.Session.virtualfile_from_vectors - clib.Session.virtualfile_from_grid - - -Low level access (these are mostly used by the :mod:`pygmt.clib` package): - -.. autosummary:: - :toctree: generated - - clib.Session.create - clib.Session.destroy - clib.Session.__getitem__ - clib.Session.__enter__ - clib.Session.__exit__ - clib.Session.get_default - clib.Session.create_data - clib.Session.put_matrix - clib.Session.put_vector - clib.Session.write_data - clib.Session.open_virtual_file - clib.Session.extract_region - clib.Session.get_libgmt_func +.. _api: + +API Reference +============= + +.. automodule:: pygmt + +.. currentmodule:: pygmt + +Plotting +-------- + +All plotting is handled through the :class:`pygmt.Figure` class and its methods. + +.. autosummary:: + :toctree: generated + + Figure + +Plotting data and laying out the map: + +.. autosummary:: + :toctree: generated + + Figure.basemap + Figure.coast + Figure.colorbar + Figure.contour + Figure.grdcontour + Figure.grdimage + Figure.grdview + Figure.image + Figure.inset + Figure.legend + Figure.logo + Figure.meca + Figure.plot + Figure.plot3d + Figure.set_panel + Figure.shift_origin + Figure.subplot + Figure.text + +Color palette table generation: + +.. autosummary:: + :toctree: generated + + grd2cpt + makecpt + +Saving and displaying the figure: + +.. autosummary:: + :toctree: generated + + Figure.savefig + Figure.show + Figure.psconvert + + +Data Processing +--------------- + +Operations on tabular data: + +.. autosummary:: + :toctree: generated + + blockmedian + surface + +Operations on grids: + +.. autosummary:: + :toctree: generated + + grdcut + grdfilter + grdtrack + +Crossover analysis with x2sys: + +.. autosummary:: + :toctree: generated + + x2sys_init + x2sys_cross + +GMT Defaults +------------ + +Operations on GMT defaults: + +.. autosummary:: + :toctree: generated + + config + +Metadata +-------- + +Getting metadata from tabular or grid data: + +.. autosummary:: + :toctree: generated + + GMTDataArrayAccessor + info + grdinfo + + +Miscellaneous +------------- + +.. autosummary:: + :toctree: generated + + which + test + print_clib_info + show_versions + + +.. automodule:: pygmt.datasets + +.. currentmodule:: pygmt + +Datasets +-------- + +PyGMT provides access to GMT's datasets through the :mod:`pygmt.datasets` package. +These functions will download the datasets automatically the first time they are used +and store them in the GMT cache folder. + +.. autosummary:: + :toctree: generated + + datasets.load_earth_relief + datasets.load_japan_quakes + datasets.load_ocean_ridge_points + datasets.load_sample_bathymetry + datasets.load_usgs_quakes + +.. automodule:: pygmt.exceptions + +.. currentmodule:: pygmt + +Exceptions +---------- + +All custom exceptions are derived from :class:`pygmt.exceptions.GMTError`. + +.. autosummary:: + :toctree: generated + + exceptions.GMTError + exceptions.GMTInvalidInput + exceptions.GMTVersionError + exceptions.GMTOSError + exceptions.GMTCLibError + exceptions.GMTCLibNoSessionError + exceptions.GMTCLibNotFoundError + + +.. automodule:: pygmt.clib + +.. currentmodule:: pygmt + +GMT C API +--------- + +The :mod:`pygmt.clib` package is a wrapper for the GMT C API built using :mod:`ctypes`. +Most calls to the C API happen through the :class:`pygmt.clib.Session` class. + +.. autosummary:: + :toctree: generated + + clib.Session + +`GMT modules `__ are executed through +the :meth:`~pygmt.clib.Session.call_module` method: + +.. autosummary:: + :toctree: generated + + clib.Session.call_module + +Passing memory blocks between Python data objects (e.g. :class:`numpy.ndarray`, +:class:`pandas.Series`, :class:`xarray.DataArray`, etc) and GMT happens through +*virtual files*. These methods are context managers that automate the +conversion of Python variables to GMT virtual files: + +.. autosummary:: + :toctree: generated + + clib.Session.virtualfile_from_data + clib.Session.virtualfile_from_matrix + clib.Session.virtualfile_from_vectors + clib.Session.virtualfile_from_grid + + +Low level access (these are mostly used by the :mod:`pygmt.clib` package): + +.. autosummary:: + :toctree: generated + + clib.Session.create + clib.Session.destroy + clib.Session.__getitem__ + clib.Session.__enter__ + clib.Session.__exit__ + clib.Session.get_default + clib.Session.create_data + clib.Session.put_matrix + clib.Session.put_vector + clib.Session.write_data + clib.Session.open_virtual_file + clib.Session.extract_region + clib.Session.get_libgmt_func diff --git a/doc/changes.md b/doc/changes.md index 12c8d4eff3b..c267d404f76 100644 --- a/doc/changes.md +++ b/doc/changes.md @@ -1,426 +1,426 @@ -# Changelog - -## Release v0.3.0 (2021/02/15) - -[![Digital Object Identifier for PyGMT v0.3.0](https://zenodo.org/badge/DOI/10.5281/zenodo.4522136.svg)](https://doi.org/10.5281/zenodo.4522136) - -### Highlights - -* 🎉 **Third minor release of PyGMT** 🎉 -* Wrap inset ([#788](https://github.com/GenericMappingTools/pygmt/pull/788)) for making overview maps and subplot ([#822](https://github.com/GenericMappingTools/pygmt/pull/822)) for multi-panel figures -* Apply standardized formatting conventions ([#775](https://github.com/GenericMappingTools/pygmt/pull/775)) across most documentation pages -* Drop Python 3.6 support ([#699](https://github.com/GenericMappingTools/pygmt/pull/699)) so PyGMT now requires Python 3.7 or newer - -### New Features - -* Wrap grd2cpt ([#803](https://github.com/GenericMappingTools/pygmt/pull/803)) -* Let Figure.text support record-by-record transparency ([#716](https://github.com/GenericMappingTools/pygmt/pull/716)) -* Provide basic support for FreeBSD ([#700](https://github.com/GenericMappingTools/pygmt/pull/700), [#878](https://github.com/GenericMappingTools/pygmt/pull/878)) - -### Enhancements - -* Let load_earth_relief support the 'region' parameter for all resolutions ([#873](https://github.com/GenericMappingTools/pygmt/pull/873)) -* Improve how PyGMT finds the GMT library ([#702](https://github.com/GenericMappingTools/pygmt/pull/702)) -* Add common alias panel (-c) to all plotting functions ([#853](https://github.com/GenericMappingTools/pygmt/pull/853)) -* Add aliases dcw ([#765](https://github.com/GenericMappingTools/pygmt/pull/765)) and lakes ([#781](https://github.com/GenericMappingTools/pygmt/pull/781)) to Figure.coast -* Add alias shading to Figure.colorbar ([#752](https://github.com/GenericMappingTools/pygmt/pull/752)) -* Add alias annotation (A) to Figure.contour ([#883](https://github.com/GenericMappingTools/pygmt/pull/883)) -* Wrap Figure.grdinfo aliases ([#799](https://github.com/GenericMappingTools/pygmt/pull/799)) -* Add aliases frame and cmap to Figure.colorbar ([#709](https://github.com/GenericMappingTools/pygmt/pull/709)) -* Add alias frame to Figure.grdview ([#707](https://github.com/GenericMappingTools/pygmt/pull/707)) -* Improve the error message when PyGMT fails to load the GMT library ([#814](https://github.com/GenericMappingTools/pygmt/pull/814)) -* Add GMTInvalidInput error to Figure.coast ([#787](https://github.com/GenericMappingTools/pygmt/pull/787)) - -### Documentation - -* Add authorship policy ([#726](https://github.com/GenericMappingTools/pygmt/pull/726)) -* Update PyGMT development installation instructions ([#865](https://github.com/GenericMappingTools/pygmt/pull/865)) -* Add a tutorial for adding a map title ([#720](https://github.com/GenericMappingTools/pygmt/pull/720)) -* Add a tutorial for plotting Earth relief ([#712](https://github.com/GenericMappingTools/pygmt/pull/712)) -* Add a tutorial for 3D perspective image ([#743](https://github.com/GenericMappingTools/pygmt/pull/743)) -* Add a tutorial for contour maps ([#705](https://github.com/GenericMappingTools/pygmt/pull/705)) -* Add a tutorial for plotting lines ([#741](https://github.com/GenericMappingTools/pygmt/pull/741)) -* Add a tutorial for the region argument ([#800](https://github.com/GenericMappingTools/pygmt/pull/800)) -* Add a gallery example for datetime inputs ([#779](https://github.com/GenericMappingTools/pygmt/pull/779)) -* Add a gallery example for Figure.logo ([#823](https://github.com/GenericMappingTools/pygmt/pull/823)) -* Add a gallery example for plotting multi-parameter symbols ([#772](https://github.com/GenericMappingTools/pygmt/pull/772)) -* Add a gallery example for Figure.image ([#777](https://github.com/GenericMappingTools/pygmt/pull/777)) -* Add a gallery example for setting line colors with a custom CPT ([#774](https://github.com/GenericMappingTools/pygmt/pull/774)) -* Add more gallery examples for projections ([#761](https://github.com/GenericMappingTools/pygmt/pull/761), [#721](https://github.com/GenericMappingTools/pygmt/pull/721), [#757](https://github.com/GenericMappingTools/pygmt/pull/757), [#723](https://github.com/GenericMappingTools/pygmt/pull/723), [#762](https://github.com/GenericMappingTools/pygmt/pull/762), [#742](https://github.com/GenericMappingTools/pygmt/pull/742), [#728](https://github.com/GenericMappingTools/pygmt/pull/728), [#727](https://github.com/GenericMappingTools/pygmt/pull/727)) -* Update the docstrings in the plotting modules ([#881](https://github.com/GenericMappingTools/pygmt/pull/881)) -* Update the docstrings in the non-plotting modules ([#882](https://github.com/GenericMappingTools/pygmt/pull/882)) -* Update Figure.coast docstrings ([#798](https://github.com/GenericMappingTools/pygmt/pull/798)) -* Update the docstrings of common aliases ([#862](https://github.com/GenericMappingTools/pygmt/pull/862)) -* Add sphinx-copybutton extension to easily copy codes ([#838](https://github.com/GenericMappingTools/pygmt/pull/838)) -* Choose the best figures in tutorials for thumbnails ([#826](https://github.com/GenericMappingTools/pygmt/pull/826)) -* Update axis label explanation in frames tutorial ([#820](https://github.com/GenericMappingTools/pygmt/pull/820)) -* Add guidelines for types of tests to write ([#796](https://github.com/GenericMappingTools/pygmt/pull/796)) -* Recommend using SI units in documentation ([#795](https://github.com/GenericMappingTools/pygmt/pull/795)) -* Add a table for compatibility of PyGMT with Python and GMT ([#763](https://github.com/GenericMappingTools/pygmt/pull/763)) -* Add description for the "columns" arguments ([#766](https://github.com/GenericMappingTools/pygmt/pull/766)) -* Add a table of the available projections ([#753](https://github.com/GenericMappingTools/pygmt/pull/753)) -* Add projection description for Lambert Azimuthal Equal-Area ([#760](https://github.com/GenericMappingTools/pygmt/pull/760)) -* Change text when GMTInvalidInput error is raised for basemap ([#729](https://github.com/GenericMappingTools/pygmt/pull/729)) - -### Bug Fixes - -* Fix a bug of Figure.text when "text" is a non-string array ([#724](https://github.com/GenericMappingTools/pygmt/pull/724)) -* Fix the error message when IPython is not available ([#701](https://github.com/GenericMappingTools/pygmt/pull/701)) - -### Maintenance - -* Add dependabot to keep GitHub Actions up to date ([#861](https://github.com/GenericMappingTools/pygmt/pull/861)) -* Skip workflows in PRs if only non-source-code files are changed ([#839](https://github.com/GenericMappingTools/pygmt/pull/839)) -* Add slash command '/test-gmt-dev' to test GMT dev version ([#831](https://github.com/GenericMappingTools/pygmt/pull/831)) -* Check files for UNIX-style line breaks and 644 permission ([#736](https://github.com/GenericMappingTools/pygmt/pull/736)) -* Rename vercel configuration file from now.json to vercel.json ([#738](https://github.com/GenericMappingTools/pygmt/pull/738)) -* Add a CI job testing GMT master branch on Windows ([#756](https://github.com/GenericMappingTools/pygmt/pull/756)) -* Migrate documentation deployment from Travis CI to GitHub Actions ([#713](https://github.com/GenericMappingTools/pygmt/pull/713)) -* Move Figure.meca into a standalone module ([#686](https://github.com/GenericMappingTools/pygmt/pull/686)) -* Move plotting functions to separate modules ([#808](https://github.com/GenericMappingTools/pygmt/pull/808)) -* Move non-plotting modules to separate modules ([#832](https://github.com/GenericMappingTools/pygmt/pull/832)) -* Add isort to sort imports alphabetically ([#745](https://github.com/GenericMappingTools/pygmt/pull/745)) -* Convert relative imports to absolute imports ([#754](https://github.com/GenericMappingTools/pygmt/pull/754)) -* Switch from versioneer to setuptools-scm ([#695](https://github.com/GenericMappingTools/pygmt/pull/695)) -* Add docformatter to format plain text in docstrings ([#642](https://github.com/GenericMappingTools/pygmt/pull/642)) -* Migrate pytest configurations to pyproject.toml ([#725](https://github.com/GenericMappingTools/pygmt/pull/725)) -* Migrate coverage configurations to pyproject.toml ([#667](https://github.com/GenericMappingTools/pygmt/pull/667)) -* Show test execution times in pytest ([#835](https://github.com/GenericMappingTools/pygmt/pull/835)) -* Add tests for grdfilter ([#809](https://github.com/GenericMappingTools/pygmt/pull/809)) -* Add tests for GMTInvalidInput of Figure.savefig and Figure.show ([#810](https://github.com/GenericMappingTools/pygmt/pull/810)) -* Add args_in_kwargs function ([#791](https://github.com/GenericMappingTools/pygmt/pull/791)) -* Add a Makefile target 'distclean' for deleting project metadata files ([#744](https://github.com/GenericMappingTools/pygmt/pull/744)) -* Add a test for Figure.basemap map_scale ([#739](https://github.com/GenericMappingTools/pygmt/pull/739)) -* Use args_in_kwargs for Figure.basemap error raising ([#797](https://github.com/GenericMappingTools/pygmt/pull/797)) - -### Contributors - -* [Will Schlitzer](https://github.com/willschlitzer) -* [Dongdong Tian](https://github.com/seisman) -* [Wei Ji Leong](https://github.com/weiji14) -* [Michael Grund](https://github.com/michaelgrund) -* [Liam Toney](https://github.com/liamtoney) -* [Meghan Jones](https://github.com/meghanrjones) - ----- - -## Release v0.2.1 (2020/11/14) - -[![Digital Object Identifier for PyGMT v0.2.1](https://zenodo.org/badge/DOI/10.5281/zenodo.4253459.svg)](https://doi.org/10.5281/zenodo.4253459) - -### Highlights - -* 🎉 **Patch release with more tutorials and gallery examples!** 🎉 -* 🐍 Support Python 3.9 ([#689](https://github.com/GenericMappingTools/pygmt/pull/689)) -* 📹 Add [Liam](https://github.com/liamtoney)'s [ROSES 2020 PyGMT talk](https://www.youtube.com/watch?v=SSIGJEe0BIk) ([#643](https://github.com/GenericMappingTools/pygmt/pull/643)) - -### New Features - -* Wrap plot3d ([#471](https://github.com/GenericMappingTools/pygmt/pull/471)) -* Wrap grdfilter ([#616](https://github.com/GenericMappingTools/pygmt/pull/616)) - -### Enhancements - -* Allow np.object dtypes into virtualfile_from_vectors ([#684](https://github.com/GenericMappingTools/pygmt/pull/684)) -* Let plot() accept record-by-record transparency ([#626](https://github.com/GenericMappingTools/pygmt/pull/626)) -* Refactor info to allow datetime inputs from xarray.Dataset and pandas.DataFrame tables ([#619](https://github.com/GenericMappingTools/pygmt/pull/619)) - -### Tutorials & Gallery - -* Add tutorial for pygmt.config ([#482](https://github.com/GenericMappingTools/pygmt/pull/482)) -* Add an example for different line styles ([#604](https://github.com/GenericMappingTools/pygmt/pull/604), [#664](https://github.com/GenericMappingTools/pygmt/pull/664)) -* Add a gallery example for varying transparent points ([#654](https://github.com/GenericMappingTools/pygmt/pull/654)) -* Add tutorial for pygmt.Figure.text ([#480](https://github.com/GenericMappingTools/pygmt/pull/480)) -* Add an example for scatter plots with auto legends ([#607](https://github.com/GenericMappingTools/pygmt/pull/607)) -* Improve colorbar gallery example ([#596](https://github.com/GenericMappingTools/pygmt/pull/596)) - -### Documentation Improvements - -* doc: Fix the description of grdcontour -G option ([#681](https://github.com/GenericMappingTools/pygmt/pull/681)) -* Refresh Code of Conduct from v1.4 to v2.0 ([#673](https://github.com/GenericMappingTools/pygmt/pull/673)) -* Add PyGMT Zenodo BibTeX entry to main README.md ([#678](https://github.com/GenericMappingTools/pygmt/pull/678)) -* Complete most of documentation for makecpt ([#676](https://github.com/GenericMappingTools/pygmt/pull/676)) -* Complete documentation for plot ([#666](https://github.com/GenericMappingTools/pygmt/pull/666)) -* Add "no_clip" to plot, text, contour and meca ([#661](https://github.com/GenericMappingTools/pygmt/pull/661)) -* Add common alias "verbose" (V) to all functions ([#662](https://github.com/GenericMappingTools/pygmt/pull/662)) -* Improve documentation of Figure.logo() ([#651](https://github.com/GenericMappingTools/pygmt/pull/651)) -* Add mini-galleries for methods and functions ([#648](https://github.com/GenericMappingTools/pygmt/pull/648)) -* Complete documentation of grdimage ([#620](https://github.com/GenericMappingTools/pygmt/pull/620)) -* Add common alias perspective (p) for plotting 3D illustrations ([#627](https://github.com/GenericMappingTools/pygmt/pull/627)) -* Add common aliases xshift (X) and yshift (Y) ([#624](https://github.com/GenericMappingTools/pygmt/pull/624)) -* Add common alias cores (x) for grdimage and other multi-threaded modules ([#625](https://github.com/GenericMappingTools/pygmt/pull/625)) -* Enable switching different versions of documentation ([#621](https://github.com/GenericMappingTools/pygmt/pull/621)) -* Add common alias transparency (-t) to all plotting functions ([#614](https://github.com/GenericMappingTools/pygmt/pull/614)) - -### Bug Fixes - -* Disallow passing arguments like -XNone to GMT ([#639](https://github.com/GenericMappingTools/pygmt/pull/639)) - -### Maintenance - -* Migrate PyPI release to GitHub Actions ([#679](https://github.com/GenericMappingTools/pygmt/pull/679)) -* Upload artifacts showing diff images on test failure ([#675](https://github.com/GenericMappingTools/pygmt/pull/675)) -* Add slash command "/format" to automatically format PRs ([#646](https://github.com/GenericMappingTools/pygmt/pull/646)) -* Add instructions to run specific tests ([#660](https://github.com/GenericMappingTools/pygmt/pull/660)) -* Add more tests for xarray grid shading ([#650](https://github.com/GenericMappingTools/pygmt/pull/650)) -* Refactor xfail tests to avoid storing baseline images ([#603](https://github.com/GenericMappingTools/pygmt/pull/603)) -* Add blackdoc to format Python codes in docstrings ([#641](https://github.com/GenericMappingTools/pygmt/pull/641)) -* Check and lint sphinx configuration file doc/conf.py ([#630](https://github.com/GenericMappingTools/pygmt/pull/630)) -* Improve Makefile to clean ``__pycache__`` directory recursively ([#611](https://github.com/GenericMappingTools/pygmt/pull/611)) -* Update release process and checklist template ([#602](https://github.com/GenericMappingTools/pygmt/pull/602)) - -### Contributors - -* [Dongdong Tian](https://github.com/seisman) -* [Wei Ji Leong](https://github.com/weiji14) -* [Conor Bacon](https://github.com/hemmelig) -* [carocamargo](https://github.com/carocamargo) - ----- - -## Release v0.2.0 (2020/09/12) - -[![Digital Object Identifier for PyGMT v0.2.0](https://zenodo.org/badge/DOI/10.5281/zenodo.4025418.svg)](https://doi.org/10.5281/zenodo.4025418) - -### Highlights - -* 🎉 **Second minor release of PyGMT** 🎉 -* Minimum required GMT version is now 6.1.1 or newer ([#577](https://github.com/GenericMappingTools/pygmt/pull/577)) -* Plotting xarray grids using grdimage and grdview should not crash anymore and works for most cases ([#560](https://github.com/GenericMappingTools/pygmt/pull/560)) -* Easier time-series plots with support for datetime-like inputs to plot ([#464](https://github.com/GenericMappingTools/pygmt/pull/464)) and the region argument ([#562](https://github.com/GenericMappingTools/pygmt/pull/562)) - -### New Features - -* Wrap GMT_Put_Strings to pass str columns into GMT C API directly ([#520](https://github.com/GenericMappingTools/pygmt/pull/520)) -* Wrap meca ([#516](https://github.com/GenericMappingTools/pygmt/pull/516)) -* Wrap x2sys_init and x2sys_cross ([#546](https://github.com/GenericMappingTools/pygmt/pull/546)) -* Let grdcut() accept xarray.DataArray as input ([#541](https://github.com/GenericMappingTools/pygmt/pull/541)) -* Initialize a GMTDataArrayAccessor ([#500](https://github.com/GenericMappingTools/pygmt/pull/500)) - -### Enhancements - -* Allow passing in pandas dataframes to x2sys_cross ([#591](https://github.com/GenericMappingTools/pygmt/pull/591)) -* Sensible array outputs for pygmt info ([#575](https://github.com/GenericMappingTools/pygmt/pull/575)) -* Allow pandas.DataFrame table and 1D/2D numpy array inputs into pygmt.info ([#574](https://github.com/GenericMappingTools/pygmt/pull/574)) -* Add auto-legend feature to grdcontour and contour ([#568](https://github.com/GenericMappingTools/pygmt/pull/568)) -* Add common alias verbose (V) ([#550](https://github.com/GenericMappingTools/pygmt/pull/550)) -* Let load_earth_relief() support all resolutions and optional subregion ([#542](https://github.com/GenericMappingTools/pygmt/pull/542)) -* Allow load_earth_relief() to load pixel or gridline registered data ([#509](https://github.com/GenericMappingTools/pygmt/pull/509)) - -### Documentation - -* Link to try-gmt binder repository ([#598](https://github.com/GenericMappingTools/pygmt/pull/598)) -* Improve docstring of data_kind() to include xarray grid ([#588](https://github.com/GenericMappingTools/pygmt/pull/588)) -* Improve the documentation of Figure.shift_origin() ([#536](https://github.com/GenericMappingTools/pygmt/pull/536)) -* Add shading to grdview gallery example ([#506](https://github.com/GenericMappingTools/pygmt/pull/506)) - -### Bug Fixes - -* Ensure surface and grdcut loads GMTDataArray accessor info into xarray ([#539](https://github.com/GenericMappingTools/pygmt/pull/539)) -* Raise an error if short- and long-form arguments coexist ([#537](https://github.com/GenericMappingTools/pygmt/pull/537)) -* Fix the grdtrack example to avoid crashes on macOS ([#531](https://github.com/GenericMappingTools/pygmt/pull/531)) -* Properly allow for either pixel or gridline registered grids ([#476](https://github.com/GenericMappingTools/pygmt/pull/476)) - -### Maintenance - -* Add a test for xarray shading ([#581](https://github.com/GenericMappingTools/pygmt/pull/581)) -* Remove expected failures on grdview tests ([#589](https://github.com/GenericMappingTools/pygmt/pull/589)) -* Redesign check_figures_equal testing function to be more explicit ([#590](https://github.com/GenericMappingTools/pygmt/pull/590)) -* Cut Windows CI build time in half to 15 min ([#586](https://github.com/GenericMappingTools/pygmt/pull/586)) -* Add a test for Session.write_data() writing netCDF grids ([#583](https://github.com/GenericMappingTools/pygmt/pull/583)) -* Add a test to make sure shift_origin does not crash ([#580](https://github.com/GenericMappingTools/pygmt/pull/580)) -* Add testing.check_figures_equal to avoid storing baseline images ([#555](https://github.com/GenericMappingTools/pygmt/pull/555)) -* Eliminate unnecessary jobs from Travis CI ([#567](https://github.com/GenericMappingTools/pygmt/pull/567)) and Azure Pipelines ([#513](https://github.com/GenericMappingTools/pygmt/pull/513)) -* Improve the workflow to test both GMT master ([#485](https://github.com/GenericMappingTools/pygmt/pull/485)) and 6.1 branches ([#554](https://github.com/GenericMappingTools/pygmt/pull/554)) -* Automatically cancel in-progress CI runs of old commits ([#544](https://github.com/GenericMappingTools/pygmt/pull/544)) -* Remove the Stickler CI configuration file ([#538](https://github.com/GenericMappingTools/pygmt/pull/538)), run style checks using GitHub Actions ([#519](https://github.com/GenericMappingTools/pygmt/pull/519)) -* Cache GMT remote data as artifacts on GitHub ([#530](https://github.com/GenericMappingTools/pygmt/pull/530)) -* Let pytest generate both HTML and XML coverage reports ([#512](https://github.com/GenericMappingTools/pygmt/pull/512)) -* Run Continuous Integration tests on GitHub Actions ([#475](https://github.com/GenericMappingTools/pygmt/pull/475)) - -### Contributors - -* [Dongdong Tian](https://github.com/seisman) -* [Wei Ji Leong](https://github.com/weiji14) -* [Tyler Newton](https://github.com/tjnewton) -* [Liam Toney](https://github.com/liamtoney) - ----- - -## Release v0.1.2 (2020/07/07) - -[![Digital Object Identifier for PyGMT v0.1.2](https://zenodo.org/badge/DOI/10.5281/zenodo.3930577.svg)](https://doi.org/10.5281/zenodo.3930577) - -### Highlights - -* Patch release in preparation for the SciPy 2020 sprint session -* Last version to support GMT 6.0, future PyGMT versions will require GMT 6.1 or newer - -### New Features - -* Wrap grdcut ([#492](https://github.com/GenericMappingTools/pygmt/pull/492)) -* Add show_versions() function for printing debugging information used in issue reports ([#466](https://github.com/GenericMappingTools/pygmt/pull/466)) - -### Enhancements - -* Change load_earth_relief()'s default resolution to 01d ([#488](https://github.com/GenericMappingTools/pygmt/pull/488)) -* Enhance text with extra functionality and aliases ([#481](https://github.com/GenericMappingTools/pygmt/pull/481)) - -### Documentation - -* Add gallery example for grdview ([#502](https://github.com/GenericMappingTools/pygmt/pull/502)) -* Turn all short aliases into long form ([#474](https://github.com/GenericMappingTools/pygmt/pull/474)) -* Update the plotting example using the colormap generated by pygmt.makecpt ([#472](https://github.com/GenericMappingTools/pygmt/pull/472)) -* Add instructions to view the test coverage reports locally ([#468](https://github.com/GenericMappingTools/pygmt/pull/468)) -* Update the instructions for testing pygmt install ([#459](https://github.com/GenericMappingTools/pygmt/pull/459)) - -### Bug Fixes - -* Fix a bug when passing data to GMT in Session.open_virtual_file() ([#490](https://github.com/GenericMappingTools/pygmt/pull/490)) - -### Maintenance - -* Temporarily expect failures for some grdcontour and grdview tests ([#503](https://github.com/GenericMappingTools/pygmt/pull/503)) -* Fix several failures due to updates of earth relief data ([#498](https://github.com/GenericMappingTools/pygmt/pull/498)) -* Unpin pylint version and fix some lint warnings ([#484](https://github.com/GenericMappingTools/pygmt/pull/484)) -* Separate tests of gmtinfo and grdinfo ([#461](https://github.com/GenericMappingTools/pygmt/pull/461)) -* Fix the test for GMT_COMPATIBILITY=6 ([#454](https://github.com/GenericMappingTools/pygmt/pull/454)) -* Update baseline images for updates of earth relief data ([#452](https://github.com/GenericMappingTools/pygmt/pull/452)) -* Simplify PyGMT Release process ([#446](https://github.com/GenericMappingTools/pygmt/pull/446)) - -### Contributors - -* [Dongdong Tian](https://github.com/seisman) -* [Wei Ji Leong](https://github.com/weiji14) -* [Liam Toney](https://github.com/liamtoney) - ----- - -## Release v0.1.1 (2020/05/22) - -[![Digital Object Identifier for PyGMT v0.1.1](https://zenodo.org/badge/DOI/10.5281/zenodo.3837197.svg)](https://doi.org/10.5281/zenodo.3837197) - -### Highlights - -* 🏁Windows users rejoice, this bugfix release is for you!🏁 -* Let PyGMT work with the conda GMT package on Windows ([#434](https://github.com/GenericMappingTools/pygmt/pull/434)) - -### Enhancements - -* Handle setting special parameters without default settings for config ([#411](https://github.com/GenericMappingTools/pygmt/pull/411)) - -### Documentation - -* Update install instructions ([#430](https://github.com/GenericMappingTools/pygmt/pull/430)) -* Add PyGMT AGU 2019 poster to website ([#425](https://github.com/GenericMappingTools/pygmt/pull/425)) -* Redirect www.pygmt.org to latest, instead of dev ([#423](https://github.com/GenericMappingTools/pygmt/pull/423)) - -### Bug Fixes - -* Set GMT_COMPATIBILITY to 6 when pygmt session starts ([#432](https://github.com/GenericMappingTools/pygmt/pull/432)) -* Improve how PyGMT finds the GMT library ([#440](https://github.com/GenericMappingTools/pygmt/pull/440)) - -### Maintenance - -* Finalize fixes on Windows test suite for v0.1.1 ([#441](https://github.com/GenericMappingTools/pygmt/pull/441)) -* Cache test data on Azure Pipelines ([#438](https://github.com/GenericMappingTools/pygmt/pull/438)) - -### Contributors - -* [Dongdong Tian](https://github.com/seisman) -* [Wei Ji Leong](https://github.com/weiji14) -* [Jason K. Moore](https://github.com/moorepants) - ----- - -## Release v0.1.0 (2020/05/03) - -[![Digital Object Identifier for PyGMT v0.1.0](https://zenodo.org/badge/DOI/10.5281/zenodo.3782862.svg)](https://doi.org/10.5281/zenodo.3782862) - -### Highlights - -* 🎉 **First official release of PyGMT** 🎉 -* Python 3.8 is now supported ([#398](https://github.com/GenericMappingTools/pygmt/pull/398)) -* PyGMT now uses the stable version of GMT 6.0.0 by default ([#363](https://github.com/GenericMappingTools/pygmt/pull/363)) -* Use sphinx-gallery to manage examples and tutorials ([#268](https://github.com/GenericMappingTools/pygmt/pull/268)) - -### New Features - -* Wrap blockmedian ([#349](https://github.com/GenericMappingTools/pygmt/pull/349)) -* Add pygmt.config() to change gmt defaults locally and globally ([#293](https://github.com/GenericMappingTools/pygmt/pull/293)) -* Wrap grdview ([#330](https://github.com/GenericMappingTools/pygmt/pull/330)) -* Wrap grdtrack ([#308](https://github.com/GenericMappingTools/pygmt/pull/308)) -* Wrap colorbar ([#332](https://github.com/GenericMappingTools/pygmt/pull/332)) -* Wrap text ([#321](https://github.com/GenericMappingTools/pygmt/pull/321)) -* Wrap legend ([#333](https://github.com/GenericMappingTools/pygmt/pull/333)) -* Wrap makecpt ([#329](https://github.com/GenericMappingTools/pygmt/pull/329)) -* Add a new method to shift plot origins ([#289](https://github.com/GenericMappingTools/pygmt/pull/289)) - -### Enhancements - -* Allow text accepting "frame" as an argument ([#385](https://github.com/GenericMappingTools/pygmt/pull/385)) -* Allow for grids with negative lat/lon increments ([#369](https://github.com/GenericMappingTools/pygmt/pull/369)) -* Allow passing in list to 'region' argument in surface ([#378](https://github.com/GenericMappingTools/pygmt/pull/378)) -* Allow passing in scalar number to x and y in plot ([#376](https://github.com/GenericMappingTools/pygmt/pull/376)) -* Implement default position/box for legend ([#359](https://github.com/GenericMappingTools/pygmt/pull/359)) -* Add sequence_space converter in kwargs_to_string ([#325](https://github.com/GenericMappingTools/pygmt/pull/325)) - -### Documentation - -* Update PyPI install instructions and API disclaimer message ([#421](https://github.com/GenericMappingTools/pygmt/pull/421)) -* Fix the link to GMT documentation ([#419](https://github.com/GenericMappingTools/pygmt/pull/419)) -* Use napoleon instead of numpydoc with sphinx ([#383](https://github.com/GenericMappingTools/pygmt/pull/383)) -* Document using a list for repeated arguments ([#361](https://github.com/GenericMappingTools/pygmt/pull/361)) -* Add legend gallery entry ([#358](https://github.com/GenericMappingTools/pygmt/pull/358)) -* Update instructions to set GMT_LIBRARY_PATH ([#324](https://github.com/GenericMappingTools/pygmt/pull/324)) -* Fix the link to the GMT homepage ([#331](https://github.com/GenericMappingTools/pygmt/pull/331)) -* Split projections gallery by projection types ([#318](https://github.com/GenericMappingTools/pygmt/pull/318)) -* Fix the link to GMT/Matlab API in the README ([#297](https://github.com/GenericMappingTools/pygmt/pull/297)) -* Use shinx extlinks for linking GMT docs ([#294](https://github.com/GenericMappingTools/pygmt/pull/294)) -* Comment about country code in projection examples ([#290](https://github.com/GenericMappingTools/pygmt/pull/290)) -* Add an overview page listing presentations ([#286](https://github.com/GenericMappingTools/pygmt/pull/286)) - -### Bug Fixes - -* Let surface return xr.DataArray instead of xr.Dataset ([#408](https://github.com/GenericMappingTools/pygmt/pull/408)) -* Update GMT constant GMT_STR16 to GMT_VF_LEN for GMT API change in 6.1.0 ([#397](https://github.com/GenericMappingTools/pygmt/pull/397)) -* Properly trigger pytest matplotlib image comparison ([#352](https://github.com/GenericMappingTools/pygmt/pull/352)) -* Use uuid.uuid4 to generate unique names ([#274](https://github.com/GenericMappingTools/pygmt/pull/274)) - -### Maintenance - -* Quickfix Zeit Now miniconda installer link to anaconda.com ([#413](https://github.com/GenericMappingTools/pygmt/pull/413)) -* Fix GitHub Pages deployment from Travis ([#410](https://github.com/GenericMappingTools/pygmt/pull/410)) -* Update and clean TravisCI configuration ([#404](https://github.com/GenericMappingTools/pygmt/pull/404)) -* Quickfix min elevation for new SRTM15+V2.1 earth relief grids ([#401](https://github.com/GenericMappingTools/pygmt/pull/401)) -* Wrap docstrings to 79 chars and check with flake8 ([#384](https://github.com/GenericMappingTools/pygmt/pull/384)) -* Update continuous integration scripts to 1.2.0 ([#355](https://github.com/GenericMappingTools/pygmt/pull/355)) -* Use Zeit Now to deploy doc builds from PRs ([#344](https://github.com/GenericMappingTools/pygmt/pull/344)) -* Move gmt from requirements.txt to CI scripts instead ([#343](https://github.com/GenericMappingTools/pygmt/pull/343)) -* Change py.test to pytest ([#338](https://github.com/GenericMappingTools/pygmt/pull/338)) -* Add Google Analytics to measure site visitors ([#314](https://github.com/GenericMappingTools/pygmt/pull/314)) -* Register mpl_image_compare marker to remove PytestUnknownMarkWarning ([#323](https://github.com/GenericMappingTools/pygmt/pull/323)) -* Disable Windows CI builds before PR [#313](https://github.com/GenericMappingTools/pygmt/pull/313) is merged ([#320](https://github.com/GenericMappingTools/pygmt/pull/320)) -* Enable Mac and Windows CI on Azure Pipelines ([#312](https://github.com/GenericMappingTools/pygmt/pull/312)) -* Fixes for using GMT 6.0.0rc1 ([#311](https://github.com/GenericMappingTools/pygmt/pull/311)) -* Assign authorship to "The PyGMT Developers" ([#284](https://github.com/GenericMappingTools/pygmt/pull/284)) - -### Deprecations - -* Remove mention of gitter.im ([#405](https://github.com/GenericMappingTools/pygmt/pull/405)) -* Remove portrait (-P) from common options ([#339](https://github.com/GenericMappingTools/pygmt/pull/339)) -* Remove require.js since WorldWind was dropped ([#278](https://github.com/GenericMappingTools/pygmt/pull/278)) -* Remove Web WorldWind support ([#275](https://github.com/GenericMappingTools/pygmt/pull/275)) - -### Contributors - -* [Dongdong Tian](https://github.com/seisman) -* [Wei Ji Leong](https://github.com/weiji14) -* [Leonardo Uieda](https://github.com/leouieda) -* [Liam Toney](https://github.com/liamtoney) -* [Brook Tozer](https://github.com/btozer) -* [Claudio Satriano](https://github.com/claudiodsf) -* [Cody Woodson](https://github.com/Dovacody) -* [Mark Wieczorek](https://github.com/MarkWieczorek) -* [Philipp Loose](https://github.com/phloose) -* [Kathryn Materna](https://github.com/kmaterna) +# Changelog + +## Release v0.3.0 (2021/02/15) + +[![Digital Object Identifier for PyGMT v0.3.0](https://zenodo.org/badge/DOI/10.5281/zenodo.4522136.svg)](https://doi.org/10.5281/zenodo.4522136) + +### Highlights + +* 🎉 **Third minor release of PyGMT** 🎉 +* Wrap inset ([#788](https://github.com/GenericMappingTools/pygmt/pull/788)) for making overview maps and subplot ([#822](https://github.com/GenericMappingTools/pygmt/pull/822)) for multi-panel figures +* Apply standardized formatting conventions ([#775](https://github.com/GenericMappingTools/pygmt/pull/775)) across most documentation pages +* Drop Python 3.6 support ([#699](https://github.com/GenericMappingTools/pygmt/pull/699)) so PyGMT now requires Python 3.7 or newer + +### New Features + +* Wrap grd2cpt ([#803](https://github.com/GenericMappingTools/pygmt/pull/803)) +* Let Figure.text support record-by-record transparency ([#716](https://github.com/GenericMappingTools/pygmt/pull/716)) +* Provide basic support for FreeBSD ([#700](https://github.com/GenericMappingTools/pygmt/pull/700), [#878](https://github.com/GenericMappingTools/pygmt/pull/878)) + +### Enhancements + +* Let load_earth_relief support the 'region' parameter for all resolutions ([#873](https://github.com/GenericMappingTools/pygmt/pull/873)) +* Improve how PyGMT finds the GMT library ([#702](https://github.com/GenericMappingTools/pygmt/pull/702)) +* Add common alias panel (-c) to all plotting functions ([#853](https://github.com/GenericMappingTools/pygmt/pull/853)) +* Add aliases dcw ([#765](https://github.com/GenericMappingTools/pygmt/pull/765)) and lakes ([#781](https://github.com/GenericMappingTools/pygmt/pull/781)) to Figure.coast +* Add alias shading to Figure.colorbar ([#752](https://github.com/GenericMappingTools/pygmt/pull/752)) +* Add alias annotation (A) to Figure.contour ([#883](https://github.com/GenericMappingTools/pygmt/pull/883)) +* Wrap Figure.grdinfo aliases ([#799](https://github.com/GenericMappingTools/pygmt/pull/799)) +* Add aliases frame and cmap to Figure.colorbar ([#709](https://github.com/GenericMappingTools/pygmt/pull/709)) +* Add alias frame to Figure.grdview ([#707](https://github.com/GenericMappingTools/pygmt/pull/707)) +* Improve the error message when PyGMT fails to load the GMT library ([#814](https://github.com/GenericMappingTools/pygmt/pull/814)) +* Add GMTInvalidInput error to Figure.coast ([#787](https://github.com/GenericMappingTools/pygmt/pull/787)) + +### Documentation + +* Add authorship policy ([#726](https://github.com/GenericMappingTools/pygmt/pull/726)) +* Update PyGMT development installation instructions ([#865](https://github.com/GenericMappingTools/pygmt/pull/865)) +* Add a tutorial for adding a map title ([#720](https://github.com/GenericMappingTools/pygmt/pull/720)) +* Add a tutorial for plotting Earth relief ([#712](https://github.com/GenericMappingTools/pygmt/pull/712)) +* Add a tutorial for 3D perspective image ([#743](https://github.com/GenericMappingTools/pygmt/pull/743)) +* Add a tutorial for contour maps ([#705](https://github.com/GenericMappingTools/pygmt/pull/705)) +* Add a tutorial for plotting lines ([#741](https://github.com/GenericMappingTools/pygmt/pull/741)) +* Add a tutorial for the region argument ([#800](https://github.com/GenericMappingTools/pygmt/pull/800)) +* Add a gallery example for datetime inputs ([#779](https://github.com/GenericMappingTools/pygmt/pull/779)) +* Add a gallery example for Figure.logo ([#823](https://github.com/GenericMappingTools/pygmt/pull/823)) +* Add a gallery example for plotting multi-parameter symbols ([#772](https://github.com/GenericMappingTools/pygmt/pull/772)) +* Add a gallery example for Figure.image ([#777](https://github.com/GenericMappingTools/pygmt/pull/777)) +* Add a gallery example for setting line colors with a custom CPT ([#774](https://github.com/GenericMappingTools/pygmt/pull/774)) +* Add more gallery examples for projections ([#761](https://github.com/GenericMappingTools/pygmt/pull/761), [#721](https://github.com/GenericMappingTools/pygmt/pull/721), [#757](https://github.com/GenericMappingTools/pygmt/pull/757), [#723](https://github.com/GenericMappingTools/pygmt/pull/723), [#762](https://github.com/GenericMappingTools/pygmt/pull/762), [#742](https://github.com/GenericMappingTools/pygmt/pull/742), [#728](https://github.com/GenericMappingTools/pygmt/pull/728), [#727](https://github.com/GenericMappingTools/pygmt/pull/727)) +* Update the docstrings in the plotting modules ([#881](https://github.com/GenericMappingTools/pygmt/pull/881)) +* Update the docstrings in the non-plotting modules ([#882](https://github.com/GenericMappingTools/pygmt/pull/882)) +* Update Figure.coast docstrings ([#798](https://github.com/GenericMappingTools/pygmt/pull/798)) +* Update the docstrings of common aliases ([#862](https://github.com/GenericMappingTools/pygmt/pull/862)) +* Add sphinx-copybutton extension to easily copy codes ([#838](https://github.com/GenericMappingTools/pygmt/pull/838)) +* Choose the best figures in tutorials for thumbnails ([#826](https://github.com/GenericMappingTools/pygmt/pull/826)) +* Update axis label explanation in frames tutorial ([#820](https://github.com/GenericMappingTools/pygmt/pull/820)) +* Add guidelines for types of tests to write ([#796](https://github.com/GenericMappingTools/pygmt/pull/796)) +* Recommend using SI units in documentation ([#795](https://github.com/GenericMappingTools/pygmt/pull/795)) +* Add a table for compatibility of PyGMT with Python and GMT ([#763](https://github.com/GenericMappingTools/pygmt/pull/763)) +* Add description for the "columns" arguments ([#766](https://github.com/GenericMappingTools/pygmt/pull/766)) +* Add a table of the available projections ([#753](https://github.com/GenericMappingTools/pygmt/pull/753)) +* Add projection description for Lambert Azimuthal Equal-Area ([#760](https://github.com/GenericMappingTools/pygmt/pull/760)) +* Change text when GMTInvalidInput error is raised for basemap ([#729](https://github.com/GenericMappingTools/pygmt/pull/729)) + +### Bug Fixes + +* Fix a bug of Figure.text when "text" is a non-string array ([#724](https://github.com/GenericMappingTools/pygmt/pull/724)) +* Fix the error message when IPython is not available ([#701](https://github.com/GenericMappingTools/pygmt/pull/701)) + +### Maintenance + +* Add dependabot to keep GitHub Actions up to date ([#861](https://github.com/GenericMappingTools/pygmt/pull/861)) +* Skip workflows in PRs if only non-source-code files are changed ([#839](https://github.com/GenericMappingTools/pygmt/pull/839)) +* Add slash command '/test-gmt-dev' to test GMT dev version ([#831](https://github.com/GenericMappingTools/pygmt/pull/831)) +* Check files for UNIX-style line breaks and 644 permission ([#736](https://github.com/GenericMappingTools/pygmt/pull/736)) +* Rename vercel configuration file from now.json to vercel.json ([#738](https://github.com/GenericMappingTools/pygmt/pull/738)) +* Add a CI job testing GMT master branch on Windows ([#756](https://github.com/GenericMappingTools/pygmt/pull/756)) +* Migrate documentation deployment from Travis CI to GitHub Actions ([#713](https://github.com/GenericMappingTools/pygmt/pull/713)) +* Move Figure.meca into a standalone module ([#686](https://github.com/GenericMappingTools/pygmt/pull/686)) +* Move plotting functions to separate modules ([#808](https://github.com/GenericMappingTools/pygmt/pull/808)) +* Move non-plotting modules to separate modules ([#832](https://github.com/GenericMappingTools/pygmt/pull/832)) +* Add isort to sort imports alphabetically ([#745](https://github.com/GenericMappingTools/pygmt/pull/745)) +* Convert relative imports to absolute imports ([#754](https://github.com/GenericMappingTools/pygmt/pull/754)) +* Switch from versioneer to setuptools-scm ([#695](https://github.com/GenericMappingTools/pygmt/pull/695)) +* Add docformatter to format plain text in docstrings ([#642](https://github.com/GenericMappingTools/pygmt/pull/642)) +* Migrate pytest configurations to pyproject.toml ([#725](https://github.com/GenericMappingTools/pygmt/pull/725)) +* Migrate coverage configurations to pyproject.toml ([#667](https://github.com/GenericMappingTools/pygmt/pull/667)) +* Show test execution times in pytest ([#835](https://github.com/GenericMappingTools/pygmt/pull/835)) +* Add tests for grdfilter ([#809](https://github.com/GenericMappingTools/pygmt/pull/809)) +* Add tests for GMTInvalidInput of Figure.savefig and Figure.show ([#810](https://github.com/GenericMappingTools/pygmt/pull/810)) +* Add args_in_kwargs function ([#791](https://github.com/GenericMappingTools/pygmt/pull/791)) +* Add a Makefile target 'distclean' for deleting project metadata files ([#744](https://github.com/GenericMappingTools/pygmt/pull/744)) +* Add a test for Figure.basemap map_scale ([#739](https://github.com/GenericMappingTools/pygmt/pull/739)) +* Use args_in_kwargs for Figure.basemap error raising ([#797](https://github.com/GenericMappingTools/pygmt/pull/797)) + +### Contributors + +* [Will Schlitzer](https://github.com/willschlitzer) +* [Dongdong Tian](https://github.com/seisman) +* [Wei Ji Leong](https://github.com/weiji14) +* [Michael Grund](https://github.com/michaelgrund) +* [Liam Toney](https://github.com/liamtoney) +* [Meghan Jones](https://github.com/meghanrjones) + +---- + +## Release v0.2.1 (2020/11/14) + +[![Digital Object Identifier for PyGMT v0.2.1](https://zenodo.org/badge/DOI/10.5281/zenodo.4253459.svg)](https://doi.org/10.5281/zenodo.4253459) + +### Highlights + +* 🎉 **Patch release with more tutorials and gallery examples!** 🎉 +* 🐍 Support Python 3.9 ([#689](https://github.com/GenericMappingTools/pygmt/pull/689)) +* 📹 Add [Liam](https://github.com/liamtoney)'s [ROSES 2020 PyGMT talk](https://www.youtube.com/watch?v=SSIGJEe0BIk) ([#643](https://github.com/GenericMappingTools/pygmt/pull/643)) + +### New Features + +* Wrap plot3d ([#471](https://github.com/GenericMappingTools/pygmt/pull/471)) +* Wrap grdfilter ([#616](https://github.com/GenericMappingTools/pygmt/pull/616)) + +### Enhancements + +* Allow np.object dtypes into virtualfile_from_vectors ([#684](https://github.com/GenericMappingTools/pygmt/pull/684)) +* Let plot() accept record-by-record transparency ([#626](https://github.com/GenericMappingTools/pygmt/pull/626)) +* Refactor info to allow datetime inputs from xarray.Dataset and pandas.DataFrame tables ([#619](https://github.com/GenericMappingTools/pygmt/pull/619)) + +### Tutorials & Gallery + +* Add tutorial for pygmt.config ([#482](https://github.com/GenericMappingTools/pygmt/pull/482)) +* Add an example for different line styles ([#604](https://github.com/GenericMappingTools/pygmt/pull/604), [#664](https://github.com/GenericMappingTools/pygmt/pull/664)) +* Add a gallery example for varying transparent points ([#654](https://github.com/GenericMappingTools/pygmt/pull/654)) +* Add tutorial for pygmt.Figure.text ([#480](https://github.com/GenericMappingTools/pygmt/pull/480)) +* Add an example for scatter plots with auto legends ([#607](https://github.com/GenericMappingTools/pygmt/pull/607)) +* Improve colorbar gallery example ([#596](https://github.com/GenericMappingTools/pygmt/pull/596)) + +### Documentation Improvements + +* doc: Fix the description of grdcontour -G option ([#681](https://github.com/GenericMappingTools/pygmt/pull/681)) +* Refresh Code of Conduct from v1.4 to v2.0 ([#673](https://github.com/GenericMappingTools/pygmt/pull/673)) +* Add PyGMT Zenodo BibTeX entry to main README.md ([#678](https://github.com/GenericMappingTools/pygmt/pull/678)) +* Complete most of documentation for makecpt ([#676](https://github.com/GenericMappingTools/pygmt/pull/676)) +* Complete documentation for plot ([#666](https://github.com/GenericMappingTools/pygmt/pull/666)) +* Add "no_clip" to plot, text, contour and meca ([#661](https://github.com/GenericMappingTools/pygmt/pull/661)) +* Add common alias "verbose" (V) to all functions ([#662](https://github.com/GenericMappingTools/pygmt/pull/662)) +* Improve documentation of Figure.logo() ([#651](https://github.com/GenericMappingTools/pygmt/pull/651)) +* Add mini-galleries for methods and functions ([#648](https://github.com/GenericMappingTools/pygmt/pull/648)) +* Complete documentation of grdimage ([#620](https://github.com/GenericMappingTools/pygmt/pull/620)) +* Add common alias perspective (p) for plotting 3D illustrations ([#627](https://github.com/GenericMappingTools/pygmt/pull/627)) +* Add common aliases xshift (X) and yshift (Y) ([#624](https://github.com/GenericMappingTools/pygmt/pull/624)) +* Add common alias cores (x) for grdimage and other multi-threaded modules ([#625](https://github.com/GenericMappingTools/pygmt/pull/625)) +* Enable switching different versions of documentation ([#621](https://github.com/GenericMappingTools/pygmt/pull/621)) +* Add common alias transparency (-t) to all plotting functions ([#614](https://github.com/GenericMappingTools/pygmt/pull/614)) + +### Bug Fixes + +* Disallow passing arguments like -XNone to GMT ([#639](https://github.com/GenericMappingTools/pygmt/pull/639)) + +### Maintenance + +* Migrate PyPI release to GitHub Actions ([#679](https://github.com/GenericMappingTools/pygmt/pull/679)) +* Upload artifacts showing diff images on test failure ([#675](https://github.com/GenericMappingTools/pygmt/pull/675)) +* Add slash command "/format" to automatically format PRs ([#646](https://github.com/GenericMappingTools/pygmt/pull/646)) +* Add instructions to run specific tests ([#660](https://github.com/GenericMappingTools/pygmt/pull/660)) +* Add more tests for xarray grid shading ([#650](https://github.com/GenericMappingTools/pygmt/pull/650)) +* Refactor xfail tests to avoid storing baseline images ([#603](https://github.com/GenericMappingTools/pygmt/pull/603)) +* Add blackdoc to format Python codes in docstrings ([#641](https://github.com/GenericMappingTools/pygmt/pull/641)) +* Check and lint sphinx configuration file doc/conf.py ([#630](https://github.com/GenericMappingTools/pygmt/pull/630)) +* Improve Makefile to clean ``__pycache__`` directory recursively ([#611](https://github.com/GenericMappingTools/pygmt/pull/611)) +* Update release process and checklist template ([#602](https://github.com/GenericMappingTools/pygmt/pull/602)) + +### Contributors + +* [Dongdong Tian](https://github.com/seisman) +* [Wei Ji Leong](https://github.com/weiji14) +* [Conor Bacon](https://github.com/hemmelig) +* [carocamargo](https://github.com/carocamargo) + +---- + +## Release v0.2.0 (2020/09/12) + +[![Digital Object Identifier for PyGMT v0.2.0](https://zenodo.org/badge/DOI/10.5281/zenodo.4025418.svg)](https://doi.org/10.5281/zenodo.4025418) + +### Highlights + +* 🎉 **Second minor release of PyGMT** 🎉 +* Minimum required GMT version is now 6.1.1 or newer ([#577](https://github.com/GenericMappingTools/pygmt/pull/577)) +* Plotting xarray grids using grdimage and grdview should not crash anymore and works for most cases ([#560](https://github.com/GenericMappingTools/pygmt/pull/560)) +* Easier time-series plots with support for datetime-like inputs to plot ([#464](https://github.com/GenericMappingTools/pygmt/pull/464)) and the region argument ([#562](https://github.com/GenericMappingTools/pygmt/pull/562)) + +### New Features + +* Wrap GMT_Put_Strings to pass str columns into GMT C API directly ([#520](https://github.com/GenericMappingTools/pygmt/pull/520)) +* Wrap meca ([#516](https://github.com/GenericMappingTools/pygmt/pull/516)) +* Wrap x2sys_init and x2sys_cross ([#546](https://github.com/GenericMappingTools/pygmt/pull/546)) +* Let grdcut() accept xarray.DataArray as input ([#541](https://github.com/GenericMappingTools/pygmt/pull/541)) +* Initialize a GMTDataArrayAccessor ([#500](https://github.com/GenericMappingTools/pygmt/pull/500)) + +### Enhancements + +* Allow passing in pandas dataframes to x2sys_cross ([#591](https://github.com/GenericMappingTools/pygmt/pull/591)) +* Sensible array outputs for pygmt info ([#575](https://github.com/GenericMappingTools/pygmt/pull/575)) +* Allow pandas.DataFrame table and 1D/2D numpy array inputs into pygmt.info ([#574](https://github.com/GenericMappingTools/pygmt/pull/574)) +* Add auto-legend feature to grdcontour and contour ([#568](https://github.com/GenericMappingTools/pygmt/pull/568)) +* Add common alias verbose (V) ([#550](https://github.com/GenericMappingTools/pygmt/pull/550)) +* Let load_earth_relief() support all resolutions and optional subregion ([#542](https://github.com/GenericMappingTools/pygmt/pull/542)) +* Allow load_earth_relief() to load pixel or gridline registered data ([#509](https://github.com/GenericMappingTools/pygmt/pull/509)) + +### Documentation + +* Link to try-gmt binder repository ([#598](https://github.com/GenericMappingTools/pygmt/pull/598)) +* Improve docstring of data_kind() to include xarray grid ([#588](https://github.com/GenericMappingTools/pygmt/pull/588)) +* Improve the documentation of Figure.shift_origin() ([#536](https://github.com/GenericMappingTools/pygmt/pull/536)) +* Add shading to grdview gallery example ([#506](https://github.com/GenericMappingTools/pygmt/pull/506)) + +### Bug Fixes + +* Ensure surface and grdcut loads GMTDataArray accessor info into xarray ([#539](https://github.com/GenericMappingTools/pygmt/pull/539)) +* Raise an error if short- and long-form arguments coexist ([#537](https://github.com/GenericMappingTools/pygmt/pull/537)) +* Fix the grdtrack example to avoid crashes on macOS ([#531](https://github.com/GenericMappingTools/pygmt/pull/531)) +* Properly allow for either pixel or gridline registered grids ([#476](https://github.com/GenericMappingTools/pygmt/pull/476)) + +### Maintenance + +* Add a test for xarray shading ([#581](https://github.com/GenericMappingTools/pygmt/pull/581)) +* Remove expected failures on grdview tests ([#589](https://github.com/GenericMappingTools/pygmt/pull/589)) +* Redesign check_figures_equal testing function to be more explicit ([#590](https://github.com/GenericMappingTools/pygmt/pull/590)) +* Cut Windows CI build time in half to 15 min ([#586](https://github.com/GenericMappingTools/pygmt/pull/586)) +* Add a test for Session.write_data() writing netCDF grids ([#583](https://github.com/GenericMappingTools/pygmt/pull/583)) +* Add a test to make sure shift_origin does not crash ([#580](https://github.com/GenericMappingTools/pygmt/pull/580)) +* Add testing.check_figures_equal to avoid storing baseline images ([#555](https://github.com/GenericMappingTools/pygmt/pull/555)) +* Eliminate unnecessary jobs from Travis CI ([#567](https://github.com/GenericMappingTools/pygmt/pull/567)) and Azure Pipelines ([#513](https://github.com/GenericMappingTools/pygmt/pull/513)) +* Improve the workflow to test both GMT master ([#485](https://github.com/GenericMappingTools/pygmt/pull/485)) and 6.1 branches ([#554](https://github.com/GenericMappingTools/pygmt/pull/554)) +* Automatically cancel in-progress CI runs of old commits ([#544](https://github.com/GenericMappingTools/pygmt/pull/544)) +* Remove the Stickler CI configuration file ([#538](https://github.com/GenericMappingTools/pygmt/pull/538)), run style checks using GitHub Actions ([#519](https://github.com/GenericMappingTools/pygmt/pull/519)) +* Cache GMT remote data as artifacts on GitHub ([#530](https://github.com/GenericMappingTools/pygmt/pull/530)) +* Let pytest generate both HTML and XML coverage reports ([#512](https://github.com/GenericMappingTools/pygmt/pull/512)) +* Run Continuous Integration tests on GitHub Actions ([#475](https://github.com/GenericMappingTools/pygmt/pull/475)) + +### Contributors + +* [Dongdong Tian](https://github.com/seisman) +* [Wei Ji Leong](https://github.com/weiji14) +* [Tyler Newton](https://github.com/tjnewton) +* [Liam Toney](https://github.com/liamtoney) + +---- + +## Release v0.1.2 (2020/07/07) + +[![Digital Object Identifier for PyGMT v0.1.2](https://zenodo.org/badge/DOI/10.5281/zenodo.3930577.svg)](https://doi.org/10.5281/zenodo.3930577) + +### Highlights + +* Patch release in preparation for the SciPy 2020 sprint session +* Last version to support GMT 6.0, future PyGMT versions will require GMT 6.1 or newer + +### New Features + +* Wrap grdcut ([#492](https://github.com/GenericMappingTools/pygmt/pull/492)) +* Add show_versions() function for printing debugging information used in issue reports ([#466](https://github.com/GenericMappingTools/pygmt/pull/466)) + +### Enhancements + +* Change load_earth_relief()'s default resolution to 01d ([#488](https://github.com/GenericMappingTools/pygmt/pull/488)) +* Enhance text with extra functionality and aliases ([#481](https://github.com/GenericMappingTools/pygmt/pull/481)) + +### Documentation + +* Add gallery example for grdview ([#502](https://github.com/GenericMappingTools/pygmt/pull/502)) +* Turn all short aliases into long form ([#474](https://github.com/GenericMappingTools/pygmt/pull/474)) +* Update the plotting example using the colormap generated by pygmt.makecpt ([#472](https://github.com/GenericMappingTools/pygmt/pull/472)) +* Add instructions to view the test coverage reports locally ([#468](https://github.com/GenericMappingTools/pygmt/pull/468)) +* Update the instructions for testing pygmt install ([#459](https://github.com/GenericMappingTools/pygmt/pull/459)) + +### Bug Fixes + +* Fix a bug when passing data to GMT in Session.open_virtual_file() ([#490](https://github.com/GenericMappingTools/pygmt/pull/490)) + +### Maintenance + +* Temporarily expect failures for some grdcontour and grdview tests ([#503](https://github.com/GenericMappingTools/pygmt/pull/503)) +* Fix several failures due to updates of earth relief data ([#498](https://github.com/GenericMappingTools/pygmt/pull/498)) +* Unpin pylint version and fix some lint warnings ([#484](https://github.com/GenericMappingTools/pygmt/pull/484)) +* Separate tests of gmtinfo and grdinfo ([#461](https://github.com/GenericMappingTools/pygmt/pull/461)) +* Fix the test for GMT_COMPATIBILITY=6 ([#454](https://github.com/GenericMappingTools/pygmt/pull/454)) +* Update baseline images for updates of earth relief data ([#452](https://github.com/GenericMappingTools/pygmt/pull/452)) +* Simplify PyGMT Release process ([#446](https://github.com/GenericMappingTools/pygmt/pull/446)) + +### Contributors + +* [Dongdong Tian](https://github.com/seisman) +* [Wei Ji Leong](https://github.com/weiji14) +* [Liam Toney](https://github.com/liamtoney) + +---- + +## Release v0.1.1 (2020/05/22) + +[![Digital Object Identifier for PyGMT v0.1.1](https://zenodo.org/badge/DOI/10.5281/zenodo.3837197.svg)](https://doi.org/10.5281/zenodo.3837197) + +### Highlights + +* 🏁Windows users rejoice, this bugfix release is for you!🏁 +* Let PyGMT work with the conda GMT package on Windows ([#434](https://github.com/GenericMappingTools/pygmt/pull/434)) + +### Enhancements + +* Handle setting special parameters without default settings for config ([#411](https://github.com/GenericMappingTools/pygmt/pull/411)) + +### Documentation + +* Update install instructions ([#430](https://github.com/GenericMappingTools/pygmt/pull/430)) +* Add PyGMT AGU 2019 poster to website ([#425](https://github.com/GenericMappingTools/pygmt/pull/425)) +* Redirect www.pygmt.org to latest, instead of dev ([#423](https://github.com/GenericMappingTools/pygmt/pull/423)) + +### Bug Fixes + +* Set GMT_COMPATIBILITY to 6 when pygmt session starts ([#432](https://github.com/GenericMappingTools/pygmt/pull/432)) +* Improve how PyGMT finds the GMT library ([#440](https://github.com/GenericMappingTools/pygmt/pull/440)) + +### Maintenance + +* Finalize fixes on Windows test suite for v0.1.1 ([#441](https://github.com/GenericMappingTools/pygmt/pull/441)) +* Cache test data on Azure Pipelines ([#438](https://github.com/GenericMappingTools/pygmt/pull/438)) + +### Contributors + +* [Dongdong Tian](https://github.com/seisman) +* [Wei Ji Leong](https://github.com/weiji14) +* [Jason K. Moore](https://github.com/moorepants) + +---- + +## Release v0.1.0 (2020/05/03) + +[![Digital Object Identifier for PyGMT v0.1.0](https://zenodo.org/badge/DOI/10.5281/zenodo.3782862.svg)](https://doi.org/10.5281/zenodo.3782862) + +### Highlights + +* 🎉 **First official release of PyGMT** 🎉 +* Python 3.8 is now supported ([#398](https://github.com/GenericMappingTools/pygmt/pull/398)) +* PyGMT now uses the stable version of GMT 6.0.0 by default ([#363](https://github.com/GenericMappingTools/pygmt/pull/363)) +* Use sphinx-gallery to manage examples and tutorials ([#268](https://github.com/GenericMappingTools/pygmt/pull/268)) + +### New Features + +* Wrap blockmedian ([#349](https://github.com/GenericMappingTools/pygmt/pull/349)) +* Add pygmt.config() to change gmt defaults locally and globally ([#293](https://github.com/GenericMappingTools/pygmt/pull/293)) +* Wrap grdview ([#330](https://github.com/GenericMappingTools/pygmt/pull/330)) +* Wrap grdtrack ([#308](https://github.com/GenericMappingTools/pygmt/pull/308)) +* Wrap colorbar ([#332](https://github.com/GenericMappingTools/pygmt/pull/332)) +* Wrap text ([#321](https://github.com/GenericMappingTools/pygmt/pull/321)) +* Wrap legend ([#333](https://github.com/GenericMappingTools/pygmt/pull/333)) +* Wrap makecpt ([#329](https://github.com/GenericMappingTools/pygmt/pull/329)) +* Add a new method to shift plot origins ([#289](https://github.com/GenericMappingTools/pygmt/pull/289)) + +### Enhancements + +* Allow text accepting "frame" as an argument ([#385](https://github.com/GenericMappingTools/pygmt/pull/385)) +* Allow for grids with negative lat/lon increments ([#369](https://github.com/GenericMappingTools/pygmt/pull/369)) +* Allow passing in list to 'region' argument in surface ([#378](https://github.com/GenericMappingTools/pygmt/pull/378)) +* Allow passing in scalar number to x and y in plot ([#376](https://github.com/GenericMappingTools/pygmt/pull/376)) +* Implement default position/box for legend ([#359](https://github.com/GenericMappingTools/pygmt/pull/359)) +* Add sequence_space converter in kwargs_to_string ([#325](https://github.com/GenericMappingTools/pygmt/pull/325)) + +### Documentation + +* Update PyPI install instructions and API disclaimer message ([#421](https://github.com/GenericMappingTools/pygmt/pull/421)) +* Fix the link to GMT documentation ([#419](https://github.com/GenericMappingTools/pygmt/pull/419)) +* Use napoleon instead of numpydoc with sphinx ([#383](https://github.com/GenericMappingTools/pygmt/pull/383)) +* Document using a list for repeated arguments ([#361](https://github.com/GenericMappingTools/pygmt/pull/361)) +* Add legend gallery entry ([#358](https://github.com/GenericMappingTools/pygmt/pull/358)) +* Update instructions to set GMT_LIBRARY_PATH ([#324](https://github.com/GenericMappingTools/pygmt/pull/324)) +* Fix the link to the GMT homepage ([#331](https://github.com/GenericMappingTools/pygmt/pull/331)) +* Split projections gallery by projection types ([#318](https://github.com/GenericMappingTools/pygmt/pull/318)) +* Fix the link to GMT/Matlab API in the README ([#297](https://github.com/GenericMappingTools/pygmt/pull/297)) +* Use shinx extlinks for linking GMT docs ([#294](https://github.com/GenericMappingTools/pygmt/pull/294)) +* Comment about country code in projection examples ([#290](https://github.com/GenericMappingTools/pygmt/pull/290)) +* Add an overview page listing presentations ([#286](https://github.com/GenericMappingTools/pygmt/pull/286)) + +### Bug Fixes + +* Let surface return xr.DataArray instead of xr.Dataset ([#408](https://github.com/GenericMappingTools/pygmt/pull/408)) +* Update GMT constant GMT_STR16 to GMT_VF_LEN for GMT API change in 6.1.0 ([#397](https://github.com/GenericMappingTools/pygmt/pull/397)) +* Properly trigger pytest matplotlib image comparison ([#352](https://github.com/GenericMappingTools/pygmt/pull/352)) +* Use uuid.uuid4 to generate unique names ([#274](https://github.com/GenericMappingTools/pygmt/pull/274)) + +### Maintenance + +* Quickfix Zeit Now miniconda installer link to anaconda.com ([#413](https://github.com/GenericMappingTools/pygmt/pull/413)) +* Fix GitHub Pages deployment from Travis ([#410](https://github.com/GenericMappingTools/pygmt/pull/410)) +* Update and clean TravisCI configuration ([#404](https://github.com/GenericMappingTools/pygmt/pull/404)) +* Quickfix min elevation for new SRTM15+V2.1 earth relief grids ([#401](https://github.com/GenericMappingTools/pygmt/pull/401)) +* Wrap docstrings to 79 chars and check with flake8 ([#384](https://github.com/GenericMappingTools/pygmt/pull/384)) +* Update continuous integration scripts to 1.2.0 ([#355](https://github.com/GenericMappingTools/pygmt/pull/355)) +* Use Zeit Now to deploy doc builds from PRs ([#344](https://github.com/GenericMappingTools/pygmt/pull/344)) +* Move gmt from requirements.txt to CI scripts instead ([#343](https://github.com/GenericMappingTools/pygmt/pull/343)) +* Change py.test to pytest ([#338](https://github.com/GenericMappingTools/pygmt/pull/338)) +* Add Google Analytics to measure site visitors ([#314](https://github.com/GenericMappingTools/pygmt/pull/314)) +* Register mpl_image_compare marker to remove PytestUnknownMarkWarning ([#323](https://github.com/GenericMappingTools/pygmt/pull/323)) +* Disable Windows CI builds before PR [#313](https://github.com/GenericMappingTools/pygmt/pull/313) is merged ([#320](https://github.com/GenericMappingTools/pygmt/pull/320)) +* Enable Mac and Windows CI on Azure Pipelines ([#312](https://github.com/GenericMappingTools/pygmt/pull/312)) +* Fixes for using GMT 6.0.0rc1 ([#311](https://github.com/GenericMappingTools/pygmt/pull/311)) +* Assign authorship to "The PyGMT Developers" ([#284](https://github.com/GenericMappingTools/pygmt/pull/284)) + +### Deprecations + +* Remove mention of gitter.im ([#405](https://github.com/GenericMappingTools/pygmt/pull/405)) +* Remove portrait (-P) from common options ([#339](https://github.com/GenericMappingTools/pygmt/pull/339)) +* Remove require.js since WorldWind was dropped ([#278](https://github.com/GenericMappingTools/pygmt/pull/278)) +* Remove Web WorldWind support ([#275](https://github.com/GenericMappingTools/pygmt/pull/275)) + +### Contributors + +* [Dongdong Tian](https://github.com/seisman) +* [Wei Ji Leong](https://github.com/weiji14) +* [Leonardo Uieda](https://github.com/leouieda) +* [Liam Toney](https://github.com/liamtoney) +* [Brook Tozer](https://github.com/btozer) +* [Claudio Satriano](https://github.com/claudiodsf) +* [Cody Woodson](https://github.com/Dovacody) +* [Mark Wieczorek](https://github.com/MarkWieczorek) +* [Philipp Loose](https://github.com/phloose) +* [Kathryn Materna](https://github.com/kmaterna) diff --git a/doc/conf.py b/doc/conf.py index 9583ed7f8a4..e35574b5310 100644 --- a/doc/conf.py +++ b/doc/conf.py @@ -1,183 +1,183 @@ -# -*- coding: utf-8 -*- -""" -Sphinx documentation configuration file. -""" -# pylint: disable=invalid-name - -import datetime - -# isort: off -from sphinx_gallery.sorting import ( # pylint: disable=no-name-in-module - ExplicitOrder, - FileNameSortKey, -) -from pygmt import __commit__, __version__ -from pygmt.sphinx_gallery import PyGMTScraper - -# isort: on - -extensions = [ - "myst_parser", - "sphinx.ext.autodoc", - "sphinx.ext.autosummary", - "sphinx.ext.coverage", - "sphinx.ext.mathjax", - "sphinx.ext.doctest", - "sphinx.ext.viewcode", - "sphinx.ext.extlinks", - "sphinx.ext.intersphinx", - "sphinx.ext.napoleon", - "sphinx_gallery.gen_gallery", - "sphinx_copybutton", -] - -# Autosummary pages will be generated by sphinx-autogen instead of sphinx-build -autosummary_generate = [] - -# Make the list of returns arguments and attributes render the same as the -# parameters list -napoleon_use_rtype = False -napoleon_use_ivar = True - -# configure links to GMT docs -extlinks = { - "gmt-docs": ("https://docs.generic-mapping-tools.org/latest/%s", None), - "gmt-term": ("https://docs.generic-mapping-tools.org/latest/gmt.conf#term-%s", ""), -} - -# intersphinx configuration -intersphinx_mapping = { - "python": ("https://docs.python.org/3/", None), - "numpy": ("https://numpy.org/doc/stable/", None), - "pandas": ("https://pandas.pydata.org/pandas-docs/stable/", None), - "xarray": ("https://xarray.pydata.org/en/stable/", None), -} - -# options for sphinx-copybutton -# https://sphinx-copybutton.readthedocs.io -copybutton_prompt_text = r">>> |\.\.\. " -copybutton_prompt_is_regexp = True -copybutton_only_copy_prompt_lines = True -copybutton_remove_prompts = True - -sphinx_gallery_conf = { - # path to your examples scripts - "examples_dirs": [ - "../examples/gallery", - "../examples/tutorials", - "../examples/projections", - ], - # path where to save gallery generated examples - "gallery_dirs": ["gallery", "tutorials", "projections"], - "subsection_order": ExplicitOrder( - [ - "../examples/gallery/line", - "../examples/gallery/coast", - "../examples/gallery/plot", - "../examples/gallery/grid", - "../examples/projections/azim", - "../examples/projections/conic", - "../examples/projections/cyl", - "../examples/projections/misc", - "../examples/projections/nongeo", - "../examples/projections/table", - ] - ), - # Patter to search for example files - "filename_pattern": r"\.py", - # Remove the "Download all examples" button from the top level gallery - "download_all_examples": False, - # Sort gallery example by file name instead of number of lines (default) - "within_subsection_order": FileNameSortKey, - # directory where function granular galleries are stored - "backreferences_dir": "api/generated/backreferences", - # Modules for which function level galleries are created. In - # this case sphinx_gallery and numpy in a tuple of strings. - "doc_module": "pygmt", - # Insert links to documentation of objects in the examples - "reference_url": {"pygmt": None}, - "image_scrapers": (PyGMTScraper(),), - # Removes configuration comments from scripts - "remove_config_comments": True, -} - -# Sphinx project configuration -templates_path = ["_templates"] -exclude_patterns = ["_build", "**.ipynb_checkpoints"] -source_suffix = ".rst" -needs_sphinx = "1.8" -# The encoding of source files. -source_encoding = "utf-8-sig" -master_doc = "index" - -# General information about the project -year = datetime.date.today().year -project = "PyGMT" -copyright = f"2017-{year}, The PyGMT Developers." # pylint: disable=redefined-builtin -if len(__version__.split("+")) > 1 or __version__ == "unknown": - version = "dev" -else: - version = __version__ -release = __version__ - -# These enable substitutions using |variable| in the rst files -rst_epilog = """ -.. |year| replace:: {year} -""".format( - year=year -) - -html_last_updated_fmt = "%b %d, %Y" -html_title = "PyGMT" -html_short_title = "PyGMT" -html_logo = "" -html_favicon = "_static/favicon.png" -html_static_path = ["_static"] -html_css_files = ["style.css"] -html_extra_path = [] -pygments_style = "default" -add_function_parentheses = False -html_show_sourcelink = False -html_show_sphinx = False -html_show_copyright = True - -# Theme config -html_theme = "sphinx_rtd_theme" -html_theme_options = {} -repository = "GenericMappingTools/pygmt" -repository_url = "https://github.com/GenericMappingTools/pygmt" -commit_link = f'{ __commit__[:8] }' -html_context = { - "menu_links": [ - ( - ' Contributing', - f"{repository_url}/blob/master/CONTRIBUTING.md", - ), - ( - ' Code of Conduct', - f"{repository_url}/blob/master/CODE_OF_CONDUCT.md", - ), - ( - ' License', - f"{repository_url}/blob/master/LICENSE.txt", - ), - ( - ' Contact', - "https://forum.generic-mapping-tools.org", - ), - ( - ' Source Code', - repository_url, - ), - ], - # Custom variables to enable "Improve this page"" and "Download notebook" - # links - "doc_path": "doc", - "galleries": sphinx_gallery_conf["gallery_dirs"], - "gallery_dir": dict( - zip(sphinx_gallery_conf["gallery_dirs"], sphinx_gallery_conf["examples_dirs"]) - ), - "github_repo": repository, - "github_version": "master", - "commit": commit_link, -} +# -*- coding: utf-8 -*- +""" +Sphinx documentation configuration file. +""" +# pylint: disable=invalid-name + +import datetime + +# isort: off +from sphinx_gallery.sorting import ( # pylint: disable=no-name-in-module + ExplicitOrder, + FileNameSortKey, +) +from pygmt import __commit__, __version__ +from pygmt.sphinx_gallery import PyGMTScraper + +# isort: on + +extensions = [ + "myst_parser", + "sphinx.ext.autodoc", + "sphinx.ext.autosummary", + "sphinx.ext.coverage", + "sphinx.ext.mathjax", + "sphinx.ext.doctest", + "sphinx.ext.viewcode", + "sphinx.ext.extlinks", + "sphinx.ext.intersphinx", + "sphinx.ext.napoleon", + "sphinx_gallery.gen_gallery", + "sphinx_copybutton", +] + +# Autosummary pages will be generated by sphinx-autogen instead of sphinx-build +autosummary_generate = [] + +# Make the list of returns arguments and attributes render the same as the +# parameters list +napoleon_use_rtype = False +napoleon_use_ivar = True + +# configure links to GMT docs +extlinks = { + "gmt-docs": ("https://docs.generic-mapping-tools.org/latest/%s", None), + "gmt-term": ("https://docs.generic-mapping-tools.org/latest/gmt.conf#term-%s", ""), +} + +# intersphinx configuration +intersphinx_mapping = { + "python": ("https://docs.python.org/3/", None), + "numpy": ("https://numpy.org/doc/stable/", None), + "pandas": ("https://pandas.pydata.org/pandas-docs/stable/", None), + "xarray": ("https://xarray.pydata.org/en/stable/", None), +} + +# options for sphinx-copybutton +# https://sphinx-copybutton.readthedocs.io +copybutton_prompt_text = r">>> |\.\.\. " +copybutton_prompt_is_regexp = True +copybutton_only_copy_prompt_lines = True +copybutton_remove_prompts = True + +sphinx_gallery_conf = { + # path to your examples scripts + "examples_dirs": [ + "../examples/gallery", + "../examples/tutorials", + "../examples/projections", + ], + # path where to save gallery generated examples + "gallery_dirs": ["gallery", "tutorials", "projections"], + "subsection_order": ExplicitOrder( + [ + "../examples/gallery/line", + "../examples/gallery/coast", + "../examples/gallery/plot", + "../examples/gallery/grid", + "../examples/projections/azim", + "../examples/projections/conic", + "../examples/projections/cyl", + "../examples/projections/misc", + "../examples/projections/nongeo", + "../examples/projections/table", + ] + ), + # Patter to search for example files + "filename_pattern": r"\.py", + # Remove the "Download all examples" button from the top level gallery + "download_all_examples": False, + # Sort gallery example by file name instead of number of lines (default) + "within_subsection_order": FileNameSortKey, + # directory where function granular galleries are stored + "backreferences_dir": "api/generated/backreferences", + # Modules for which function level galleries are created. In + # this case sphinx_gallery and numpy in a tuple of strings. + "doc_module": "pygmt", + # Insert links to documentation of objects in the examples + "reference_url": {"pygmt": None}, + "image_scrapers": (PyGMTScraper(),), + # Removes configuration comments from scripts + "remove_config_comments": True, +} + +# Sphinx project configuration +templates_path = ["_templates"] +exclude_patterns = ["_build", "**.ipynb_checkpoints"] +source_suffix = ".rst" +needs_sphinx = "1.8" +# The encoding of source files. +source_encoding = "utf-8-sig" +master_doc = "index" + +# General information about the project +year = datetime.date.today().year +project = "PyGMT" +copyright = f"2017-{year}, The PyGMT Developers." # pylint: disable=redefined-builtin +if len(__version__.split("+")) > 1 or __version__ == "unknown": + version = "dev" +else: + version = __version__ +release = __version__ + +# These enable substitutions using |variable| in the rst files +rst_epilog = """ +.. |year| replace:: {year} +""".format( + year=year +) + +html_last_updated_fmt = "%b %d, %Y" +html_title = "PyGMT" +html_short_title = "PyGMT" +html_logo = "" +html_favicon = "_static/favicon.png" +html_static_path = ["_static"] +html_css_files = ["style.css"] +html_extra_path = [] +pygments_style = "default" +add_function_parentheses = False +html_show_sourcelink = False +html_show_sphinx = False +html_show_copyright = True + +# Theme config +html_theme = "sphinx_rtd_theme" +html_theme_options = {} +repository = "GenericMappingTools/pygmt" +repository_url = "https://github.com/GenericMappingTools/pygmt" +commit_link = f'{ __commit__[:8] }' +html_context = { + "menu_links": [ + ( + ' Contributing', + f"{repository_url}/blob/master/CONTRIBUTING.md", + ), + ( + ' Code of Conduct', + f"{repository_url}/blob/master/CODE_OF_CONDUCT.md", + ), + ( + ' License', + f"{repository_url}/blob/master/LICENSE.txt", + ), + ( + ' Contact', + "https://forum.generic-mapping-tools.org", + ), + ( + ' Source Code', + repository_url, + ), + ], + # Custom variables to enable "Improve this page"" and "Download notebook" + # links + "doc_path": "doc", + "galleries": sphinx_gallery_conf["gallery_dirs"], + "gallery_dir": dict( + zip(sphinx_gallery_conf["gallery_dirs"], sphinx_gallery_conf["examples_dirs"]) + ), + "github_repo": repository, + "github_version": "master", + "commit": commit_link, +} diff --git a/doc/index.rst b/doc/index.rst index 7dcc11b8953..9ff95477f7e 100644 --- a/doc/index.rst +++ b/doc/index.rst @@ -1,51 +1,51 @@ -.. title:: Home - -.. raw:: html - - - -.. include:: ../README.rst - :start-after: placeholder-for-doc-index - -.. toctree:: - :maxdepth: 2 - :hidden: - :caption: Getting Started - - overview.rst - install.rst - tutorials/first-figure.rst - gallery/index.rst - -.. toctree:: - :maxdepth: 2 - :hidden: - :caption: User Guide - - tutorials/frames.rst - projections/index.rst - tutorials/coastlines.rst - tutorials/regions.rst - tutorials/plot.rst - tutorials/lines.rst - tutorials/text.rst - tutorials/contour-map.rst - tutorials/earth-relief.rst - tutorials/3d-perspective-image.rst - tutorials/inset.rst - tutorials/subplots.rst - tutorials/configuration.rst - -.. toctree:: - :maxdepth: 2 - :hidden: - :caption: Reference documentation - - api/index.rst - changes.rst +.. title:: Home + +.. raw:: html + + + +.. include:: ../README.rst + :start-after: placeholder-for-doc-index + +.. toctree:: + :maxdepth: 2 + :hidden: + :caption: Getting Started + + overview.rst + install.rst + tutorials/first-figure.rst + gallery/index.rst + +.. toctree:: + :maxdepth: 2 + :hidden: + :caption: User Guide + + tutorials/frames.rst + projections/index.rst + tutorials/coastlines.rst + tutorials/regions.rst + tutorials/plot.rst + tutorials/lines.rst + tutorials/text.rst + tutorials/contour-map.rst + tutorials/earth-relief.rst + tutorials/3d-perspective-image.rst + tutorials/inset.rst + tutorials/subplots.rst + tutorials/configuration.rst + +.. toctree:: + :maxdepth: 2 + :hidden: + :caption: Reference documentation + + api/index.rst + changes.rst diff --git a/doc/install.rst b/doc/install.rst index 33b8cf695f1..0fa66438902 100644 --- a/doc/install.rst +++ b/doc/install.rst @@ -1,208 +1,208 @@ -.. _install: - -Installing -========== - -.. note:: - - 🚨 **This package is in the early stages of design and implementation.** 🚨 - - We welcome any feedback and ideas! - Let us know by submitting - `issues on GitHub `__ - or by posting on our `Discourse forum - `__. - - -Quickstart ----------- - -The fastest way to install PyGMT is with the -`conda `__ -package manager which takes care of setting up a virtual environment, as well -as the installation of GMT and all the dependencies PyGMT depends on:: - - conda create --name pygmt --channel conda-forge pygmt - -To activate the virtual environment, you can do:: - - conda activate pygmt - -After this, check that everything works by running the following in a Python -interpreter (e.g. in a Jupyter notebook):: - - import pygmt - pygmt.show_versions() - -You are now ready to make you first figure! -Start by looking at the tutorials on our sidebar, good luck! - -.. note:: - - The sections below provide more detailed, step by step instructions to - installing and testing PyGMT for those who may have a slightly different - setup. - -Which Python? -------------- - -PyGMT is tested to run on **Python 3.7 or greater**. Older Python versions may -work, but there is no guarantee that PyGMT will behave as expected. - -We recommend using the `Anaconda `__ -Python distribution to ensure you have all dependencies installed and the -``conda`` package manager available. -Installing Anaconda does not require administrative rights to your computer and -doesn't interfere with any other Python installations in your system. - - -Which GMT? ----------- - -PyGMT requires Generic Mapping Tools (GMT) version 6 as a minimum, which is the -latest released version that can be found at -the `GMT official site `__. -We need the latest GMT (>=6.1.1) since there are many changes being made to GMT -itself in response to the development of PyGMT, mainly the new -`modern execution mode `__. - -Compiled conda packages of GMT for Linux, macOS and Windows are provided -through `conda-forge `__. -Advanced users can also -`build GMT from source `__ -instead, which is not so recommended but we would love to get feedback from -anyone who tries. - -We recommend following the instructions further on to install GMT 6. - -Dependencies ------------- - -PyGMT requires the following libraries: - -* `numpy `__ -* `pandas `__ -* `xarray `__ -* `netCDF4 `__ -* `packaging `__ - -The following are optional (but recommended) dependencies: - -* `IPython `__: For embedding the figures in Jupyter - notebooks. - - -Installing GMT and other dependencies -------------------------------------- - -Before installing PyGMT, we must install GMT itself along with the other -dependencies. The easiest way to do this is via the ``conda`` package manager. -We recommend working in an isolated -`conda environment `__ -to avoid issues with conflicting versions of dependencies. - -First, we must configure conda to get packages from the -`conda-forge channel `__:: - - conda config --prepend channels conda-forge - -Now we can create a new conda environment with Python and all our dependencies -installed (we'll call it ``pygmt`` but feel free to change it to whatever you -want):: - - conda create --name pygmt python=3.9 pip numpy pandas xarray netcdf4 packaging gmt - -Activate the environment by running the following (**do not forget this step!**):: - - conda activate pygmt - -From now on, all commands will take place inside the conda virtual environment -called 'pygmt' and won't affect your default 'base' installation. - - -Installing PyGMT ----------------- - -Now that you have GMT installed and your conda virtual environment activated, -you can install PyGMT using any of the following methods: - -Using conda (recommended) -~~~~~~~~~~~~~~~~~~~~~~~~~ - -This installs the latest stable release of PyGMT from -`conda-forge `__:: - - conda install pygmt - -Using pip -~~~~~~~~~ - -This installs the latest stable release from -`PyPI `__:: - - pip install pygmt - -Alternatively, you can install the latest development version from -`TestPyPI `__:: - - pip install --pre --index-url https://test.pypi.org/simple/ --extra-index-url https://pypi.org/simple pygmt - -or from PyGMT's `GitHub repository `__ -(slower as it downloads the whole archive):: - - pip install git+https://github.com/GenericMappingTools/pygmt.git#egg=pygmt - -Any of the above methods (conda/pip) should allow you to use the ``pygmt`` -library from Python. - - -Testing your install --------------------- - -Quick check -~~~~~~~~~~~ - -To ensure that PyGMT and its depedencies are installed correctly, run the -following in your Python interpreter:: - - import pygmt - pygmt.show_versions() - -Or run this in the command line:: - - python -c "import pygmt; pygmt.show_versions()" - - -Full test (optional) -~~~~~~~~~~~~~~~~~~~~ - -PyGMT ships with a full test suite. -You can run our tests after you install it but you will need a few extra -dependencies as well (be sure to have your conda environment activated):: - - conda install pytest pytest-mpl ipython - -Test your installation by running the following inside a Python interpreter:: - - import pygmt - pygmt.show_versions() - pygmt.test() - - -Finding the GMT shared library ------------------------------- - -Sometimes, PyGMT will be unable to find the correct version of the GMT shared -library. -This can happen if you have multiple versions of GMT installed. - -You can tell PyGMT exactly where to look for ``libgmt`` by setting the -``GMT_LIBRARY_PATH`` environment variable. -This should be set to the directory where ``libgmt.so``, ``libgmt.dylib`` or -``gmt.dll`` can be found for Linux, macOS and Windows respectively. -e.g. on a command line, run:: - - # Linux/macOS - export GMT_LIBRARY_PATH=$HOME/anaconda3/envs/pygmt/lib - # Windows - set "GMT_LIBRARY_PATH=C:\Users\USERNAME\Anaconda3\envs\pygmt\Library\bin\" +.. _install: + +Installing +========== + +.. note:: + + 🚨 **This package is in the early stages of design and implementation.** 🚨 + + We welcome any feedback and ideas! + Let us know by submitting + `issues on GitHub `__ + or by posting on our `Discourse forum + `__. + + +Quickstart +---------- + +The fastest way to install PyGMT is with the +`conda `__ +package manager which takes care of setting up a virtual environment, as well +as the installation of GMT and all the dependencies PyGMT depends on:: + + conda create --name pygmt --channel conda-forge pygmt + +To activate the virtual environment, you can do:: + + conda activate pygmt + +After this, check that everything works by running the following in a Python +interpreter (e.g. in a Jupyter notebook):: + + import pygmt + pygmt.show_versions() + +You are now ready to make you first figure! +Start by looking at the tutorials on our sidebar, good luck! + +.. note:: + + The sections below provide more detailed, step by step instructions to + installing and testing PyGMT for those who may have a slightly different + setup. + +Which Python? +------------- + +PyGMT is tested to run on **Python 3.7 or greater**. Older Python versions may +work, but there is no guarantee that PyGMT will behave as expected. + +We recommend using the `Anaconda `__ +Python distribution to ensure you have all dependencies installed and the +``conda`` package manager available. +Installing Anaconda does not require administrative rights to your computer and +doesn't interfere with any other Python installations in your system. + + +Which GMT? +---------- + +PyGMT requires Generic Mapping Tools (GMT) version 6 as a minimum, which is the +latest released version that can be found at +the `GMT official site `__. +We need the latest GMT (>=6.1.1) since there are many changes being made to GMT +itself in response to the development of PyGMT, mainly the new +`modern execution mode `__. + +Compiled conda packages of GMT for Linux, macOS and Windows are provided +through `conda-forge `__. +Advanced users can also +`build GMT from source `__ +instead, which is not so recommended but we would love to get feedback from +anyone who tries. + +We recommend following the instructions further on to install GMT 6. + +Dependencies +------------ + +PyGMT requires the following libraries: + +* `numpy `__ +* `pandas `__ +* `xarray `__ +* `netCDF4 `__ +* `packaging `__ + +The following are optional (but recommended) dependencies: + +* `IPython `__: For embedding the figures in Jupyter + notebooks. + + +Installing GMT and other dependencies +------------------------------------- + +Before installing PyGMT, we must install GMT itself along with the other +dependencies. The easiest way to do this is via the ``conda`` package manager. +We recommend working in an isolated +`conda environment `__ +to avoid issues with conflicting versions of dependencies. + +First, we must configure conda to get packages from the +`conda-forge channel `__:: + + conda config --prepend channels conda-forge + +Now we can create a new conda environment with Python and all our dependencies +installed (we'll call it ``pygmt`` but feel free to change it to whatever you +want):: + + conda create --name pygmt python=3.9 pip numpy pandas xarray netcdf4 packaging gmt + +Activate the environment by running the following (**do not forget this step!**):: + + conda activate pygmt + +From now on, all commands will take place inside the conda virtual environment +called 'pygmt' and won't affect your default 'base' installation. + + +Installing PyGMT +---------------- + +Now that you have GMT installed and your conda virtual environment activated, +you can install PyGMT using any of the following methods: + +Using conda (recommended) +~~~~~~~~~~~~~~~~~~~~~~~~~ + +This installs the latest stable release of PyGMT from +`conda-forge `__:: + + conda install pygmt + +Using pip +~~~~~~~~~ + +This installs the latest stable release from +`PyPI `__:: + + pip install pygmt + +Alternatively, you can install the latest development version from +`TestPyPI `__:: + + pip install --pre --index-url https://test.pypi.org/simple/ --extra-index-url https://pypi.org/simple pygmt + +or from PyGMT's `GitHub repository `__ +(slower as it downloads the whole archive):: + + pip install git+https://github.com/GenericMappingTools/pygmt.git#egg=pygmt + +Any of the above methods (conda/pip) should allow you to use the ``pygmt`` +library from Python. + + +Testing your install +-------------------- + +Quick check +~~~~~~~~~~~ + +To ensure that PyGMT and its depedencies are installed correctly, run the +following in your Python interpreter:: + + import pygmt + pygmt.show_versions() + +Or run this in the command line:: + + python -c "import pygmt; pygmt.show_versions()" + + +Full test (optional) +~~~~~~~~~~~~~~~~~~~~ + +PyGMT ships with a full test suite. +You can run our tests after you install it but you will need a few extra +dependencies as well (be sure to have your conda environment activated):: + + conda install pytest pytest-mpl ipython + +Test your installation by running the following inside a Python interpreter:: + + import pygmt + pygmt.show_versions() + pygmt.test() + + +Finding the GMT shared library +------------------------------ + +Sometimes, PyGMT will be unable to find the correct version of the GMT shared +library. +This can happen if you have multiple versions of GMT installed. + +You can tell PyGMT exactly where to look for ``libgmt`` by setting the +``GMT_LIBRARY_PATH`` environment variable. +This should be set to the directory where ``libgmt.so``, ``libgmt.dylib`` or +``gmt.dll`` can be found for Linux, macOS and Windows respectively. +e.g. on a command line, run:: + + # Linux/macOS + export GMT_LIBRARY_PATH=$HOME/anaconda3/envs/pygmt/lib + # Windows + set "GMT_LIBRARY_PATH=C:\Users\USERNAME\Anaconda3\envs\pygmt\Library\bin\" diff --git a/doc/overview.rst b/doc/overview.rst index 9075e06971b..bb154f1d1b2 100644 --- a/doc/overview.rst +++ b/doc/overview.rst @@ -1,102 +1,102 @@ -Overview -======== - -About ------ - -PyGMT is a Python wrapper for the -`Generic Mapping Tools (GMT) `__, a -command-line program widely used in the Earth Sciences. -It provides capabilities for processing spatial data (gridding, filtering, masking, -FFTs, etc) and making high quality plots and maps. - -PyGMT is different from Python libraries like -`Bokeh `__ -and `Matplotlib `__, which have a larger focus on interactivity -and allowing different backends. -GMT uses the `PostScript `__ format to -generate high quality (static) vector graphics for publications, posters, talks, etc. -It is memory efficient and very fast. -The PostScript figures can be converted to other formats like PDF, PNG, and JPG for use -on the web and elsewhere. -In fact, PyGMT users will usually not have any contact with the original PostScript -files and get only the more convenient formats like PDF and PNG. - -The project was started in 2017 by `Leonardo Uieda `__ -and `Paul Wessel `__ (the co-creator and main -developer of GMT) at the University of Hawaii at Manoa. -The development of PyGMT has been supported by NSF grants -`OCE-1558403 `__ and -`EAR-1948603 `__. - -Presentations -------------- - -These are conference presentations about the development of PyGMT (previously -"GMT/Python"): - -* "Remote Online Sessions for Emerging Seismologists (ROSES): Unit 8 - PyGMT". - 2020. - Liam Toney. - Presented at *ROSES 2020*. - url: https://www.iris.edu/hq/inclass/lesson/728 - -.. figure:: https://img.youtube.com/vi/SSIGJEe0BIk/maxresdefault.jpg - :target: https://www.youtube.com/watch?v=SSIGJEe0BIk - :align: center - :alt: ROSES 2020 youtube video - -* "PyGMT: Accessing the Generic Mapping Tools from Python". - 2019. - Leonardo Uieda and Paul Wessel. - Presented at *AGU 2019*. - doi:`10.6084/m9.figshare.11320280 `__ - -.. figure:: _static/agu2019-poster.jpg - :target: https://doi.org/10.6084/m9.figshare.11320280 - :align: center - :alt: AGU 2019 poster on figshare - -* "Building an object-oriented Python interface for the Generic Mapping Tools". - 2018. - Leonardo Uieda and Paul Wessel. - Presented at *SciPy 2018*. - doi:`10.6084/m9.figshare.6814052 `__ - -.. figure:: _static/scipy2018-youtube-thumbnail.png - :target: https://www.youtube.com/watch?v=6wMtfZXfTRM - :align: center - :alt: SciPy youtube video - -* "Integrating the Generic Mapping Tools with the Scientific Python Ecosystem". - 2018. - Leonardo Uieda and Paul Wessel. - Presented at *AOGS Annual Meeting 2018*. - doi:`10.6084/m9.figshare.6399944 `__ - -.. figure:: _static/aogs2018-poster.jpg - :target: https://doi.org/10.6084/m9.figshare.6399944 - :align: center - :alt: AOGS poster on figshare - -* "Bringing the Generic Mapping Tools to Python". - 2017. - Leonardo Uieda and Paul Wessel. - Presented at *SciPy 2017*. - doi:`10.6084/m9.figshare.7635833 `__ - -.. figure:: _static/scipy2017-youtube-thumbnail.png - :target: https://www.youtube.com/watch?v=93M4How7R24 - :align: center - :alt: SciPy youtube video - -* "A modern Python interface for the Generic Mapping Tools". - 2017. - Leonardo Uieda and Paul Wessel. - Presented at *AGU 2017*. - doi:`10.6084/m9.figshare.5662411 `__ - -.. figure:: _static/agu2017-poster.jpg - :target: https://doi.org/10.6084/m9.figshare.5662411 - :align: center - :alt: AGU 2017 poster on figshare +Overview +======== + +About +----- + +PyGMT is a Python wrapper for the +`Generic Mapping Tools (GMT) `__, a +command-line program widely used in the Earth Sciences. +It provides capabilities for processing spatial data (gridding, filtering, masking, +FFTs, etc) and making high quality plots and maps. + +PyGMT is different from Python libraries like +`Bokeh `__ +and `Matplotlib `__, which have a larger focus on interactivity +and allowing different backends. +GMT uses the `PostScript `__ format to +generate high quality (static) vector graphics for publications, posters, talks, etc. +It is memory efficient and very fast. +The PostScript figures can be converted to other formats like PDF, PNG, and JPG for use +on the web and elsewhere. +In fact, PyGMT users will usually not have any contact with the original PostScript +files and get only the more convenient formats like PDF and PNG. + +The project was started in 2017 by `Leonardo Uieda `__ +and `Paul Wessel `__ (the co-creator and main +developer of GMT) at the University of Hawaii at Manoa. +The development of PyGMT has been supported by NSF grants +`OCE-1558403 `__ and +`EAR-1948603 `__. + +Presentations +------------- + +These are conference presentations about the development of PyGMT (previously +"GMT/Python"): + +* "Remote Online Sessions for Emerging Seismologists (ROSES): Unit 8 - PyGMT". + 2020. + Liam Toney. + Presented at *ROSES 2020*. + url: https://www.iris.edu/hq/inclass/lesson/728 + +.. figure:: https://img.youtube.com/vi/SSIGJEe0BIk/maxresdefault.jpg + :target: https://www.youtube.com/watch?v=SSIGJEe0BIk + :align: center + :alt: ROSES 2020 youtube video + +* "PyGMT: Accessing the Generic Mapping Tools from Python". + 2019. + Leonardo Uieda and Paul Wessel. + Presented at *AGU 2019*. + doi:`10.6084/m9.figshare.11320280 `__ + +.. figure:: _static/agu2019-poster.jpg + :target: https://doi.org/10.6084/m9.figshare.11320280 + :align: center + :alt: AGU 2019 poster on figshare + +* "Building an object-oriented Python interface for the Generic Mapping Tools". + 2018. + Leonardo Uieda and Paul Wessel. + Presented at *SciPy 2018*. + doi:`10.6084/m9.figshare.6814052 `__ + +.. figure:: _static/scipy2018-youtube-thumbnail.png + :target: https://www.youtube.com/watch?v=6wMtfZXfTRM + :align: center + :alt: SciPy youtube video + +* "Integrating the Generic Mapping Tools with the Scientific Python Ecosystem". + 2018. + Leonardo Uieda and Paul Wessel. + Presented at *AOGS Annual Meeting 2018*. + doi:`10.6084/m9.figshare.6399944 `__ + +.. figure:: _static/aogs2018-poster.jpg + :target: https://doi.org/10.6084/m9.figshare.6399944 + :align: center + :alt: AOGS poster on figshare + +* "Bringing the Generic Mapping Tools to Python". + 2017. + Leonardo Uieda and Paul Wessel. + Presented at *SciPy 2017*. + doi:`10.6084/m9.figshare.7635833 `__ + +.. figure:: _static/scipy2017-youtube-thumbnail.png + :target: https://www.youtube.com/watch?v=93M4How7R24 + :align: center + :alt: SciPy youtube video + +* "A modern Python interface for the Generic Mapping Tools". + 2017. + Leonardo Uieda and Paul Wessel. + Presented at *AGU 2017*. + doi:`10.6084/m9.figshare.5662411 `__ + +.. figure:: _static/agu2017-poster.jpg + :target: https://doi.org/10.6084/m9.figshare.5662411 + :align: center + :alt: AGU 2017 poster on figshare diff --git a/environment.yml b/environment.yml index caccb437f6e..a6ac97e6197 100644 --- a/environment.yml +++ b/environment.yml @@ -1,34 +1,34 @@ -name: pygmt -channels: - - conda-forge - - defaults -dependencies: - # Required dependencies - - pip - - gmt=6.1.1 - - numpy - - pandas - - xarray - - netCDF4 - - packaging - # Development dependencies - - black - - blackdoc - - codecov - - coverage[toml] - - docformatter - - flake8 - - ipython - - isort>=5 - - jupyter - - make - - matplotlib - - myst-parser - - pylint - - pytest-cov - - pytest-mpl - - pytest>=6.0 - - sphinx - - sphinx-copybutton - - sphinx-gallery - - sphinx_rtd_theme=0.4.3 +name: pygmt +channels: + - conda-forge + - defaults +dependencies: + # Required dependencies + - pip + - gmt=6.1.1 + - numpy + - pandas + - xarray + - netCDF4 + - packaging + # Development dependencies + - black + - blackdoc + - codecov + - coverage[toml] + - docformatter + - flake8 + - ipython + - isort>=5 + - jupyter + - make + - matplotlib + - myst-parser + - pylint + - pytest-cov + - pytest-mpl + - pytest>=6.0 + - sphinx + - sphinx-copybutton + - sphinx-gallery + - sphinx_rtd_theme=0.4.3 diff --git a/examples/gallery/coast/borders.py b/examples/gallery/coast/borders.py index 86453fc1f5a..2ab865c605f 100644 --- a/examples/gallery/coast/borders.py +++ b/examples/gallery/coast/borders.py @@ -1,25 +1,25 @@ -""" -Political Boundaries --------------------- - -The ``borders`` parameter of :meth:`pygmt.Figure.coast` specifies levels of political -boundaries to plot and the pen used to draw them. Choose from the list of boundaries -below: - -* **1** = National boundaries -* **2** = State boundaries within the Americas -* **3** = Marine boundaries -* **a** = All boundaries (1-3) - -For example, to draw national boundaries with 1p thickness black lines use -``borders="1/1p,black"``. You can draw multiple boundaries by passing in a list to -``borders``. -""" -import pygmt - -fig = pygmt.Figure() -# Make a Sinusoidal projection map of the Americas with automatic annotations, ticks and gridlines -fig.basemap(region=[-150, -30, -60, 60], projection="I-90/15c", frame="afg") -# Plot each level of the boundaries dataset with a different color. -fig.coast(borders=["1/0.5p,black", "2/0.5p,red", "3/0.5p,blue"], land="gray") -fig.show() +""" +Political Boundaries +-------------------- + +The ``borders`` parameter of :meth:`pygmt.Figure.coast` specifies levels of political +boundaries to plot and the pen used to draw them. Choose from the list of boundaries +below: + +* **1** = National boundaries +* **2** = State boundaries within the Americas +* **3** = Marine boundaries +* **a** = All boundaries (1-3) + +For example, to draw national boundaries with 1p thickness black lines use +``borders="1/1p,black"``. You can draw multiple boundaries by passing in a list to +``borders``. +""" +import pygmt + +fig = pygmt.Figure() +# Make a Sinusoidal projection map of the Americas with automatic annotations, ticks and gridlines +fig.basemap(region=[-150, -30, -60, 60], projection="I-90/15c", frame="afg") +# Plot each level of the boundaries dataset with a different color. +fig.coast(borders=["1/0.5p,black", "2/0.5p,red", "3/0.5p,blue"], land="gray") +fig.show() diff --git a/examples/gallery/coast/land_and_water.py b/examples/gallery/coast/land_and_water.py index 3ee3a012088..e0c6c2c3aab 100644 --- a/examples/gallery/coast/land_and_water.py +++ b/examples/gallery/coast/land_and_water.py @@ -1,16 +1,16 @@ -""" -Color land and water --------------------- - -The ``land`` and ``water`` parameters of :meth:`pygmt.Figure.coast` specify a color to -fill in the land and water masses, respectively. You can use standard GMT color names or -give a hex value (like ``#333333``). -""" -import pygmt - -fig = pygmt.Figure() -# Make a global Mollweide map with automatic ticks -fig.basemap(region="g", projection="W15c", frame=True) -# Plot the land as light gray, and the water as sky blue -fig.coast(land="#666666", water="skyblue") -fig.show() +""" +Color land and water +-------------------- + +The ``land`` and ``water`` parameters of :meth:`pygmt.Figure.coast` specify a color to +fill in the land and water masses, respectively. You can use standard GMT color names or +give a hex value (like ``#333333``). +""" +import pygmt + +fig = pygmt.Figure() +# Make a global Mollweide map with automatic ticks +fig.basemap(region="g", projection="W15c", frame=True) +# Plot the land as light gray, and the water as sky blue +fig.coast(land="#666666", water="skyblue") +fig.show() diff --git a/examples/gallery/coast/shorelines.py b/examples/gallery/coast/shorelines.py index 8d51ddebb06..b109ab0590b 100644 --- a/examples/gallery/coast/shorelines.py +++ b/examples/gallery/coast/shorelines.py @@ -1,14 +1,14 @@ -""" -Shorelines ----------- - -Use :meth:`pygmt.Figure.coast` to display shorelines as black lines. -""" -import pygmt - -fig = pygmt.Figure() -# Make a global Mollweide map with automatic ticks -fig.basemap(region="g", projection="W15c", frame=True) -# Display the shorelines as black lines with 0.5 point thickness -fig.coast(shorelines="0.5p,black") -fig.show() +""" +Shorelines +---------- + +Use :meth:`pygmt.Figure.coast` to display shorelines as black lines. +""" +import pygmt + +fig = pygmt.Figure() +# Make a global Mollweide map with automatic ticks +fig.basemap(region="g", projection="W15c", frame=True) +# Display the shorelines as black lines with 0.5 point thickness +fig.coast(shorelines="0.5p,black") +fig.show() diff --git a/examples/gallery/grid/grdview_surface.py b/examples/gallery/grid/grdview_surface.py index 760f30499e6..005a71429ff 100644 --- a/examples/gallery/grid/grdview_surface.py +++ b/examples/gallery/grid/grdview_surface.py @@ -1,53 +1,53 @@ -""" -Plotting a surface ------------------- - -The :meth:`pygmt.Figure.grdview()` method can plot 3-D surfaces with ``surftype="s"``. Here, -we supply the data as an :class:`xarray.DataArray` with the coordinate vectors ``x`` and -``y`` defined. Note that the ``perspective`` parameter here controls the azimuth and -elevation angle of the view. We provide a list of two arguments to ``frame`` - the -first argument specifies the :math:`x`- and :math:`y`:-axes frame attributes and the -second argument, prepended with ``"z"``, specifies the :math:`z`-axis frame attributes. -Specifying the same scale for the ``projection`` and ``zcale`` parameters ensures equal -axis scaling. The ``shading`` parameter specifies illumination; here we choose an azimuth of -45° with ``shading="+a45"``. -""" - -import numpy as np -import pygmt -import xarray as xr - - -# Define an interesting function of two variables, see: -# https://en.wikipedia.org/wiki/Ackley_function -def ackley(x, y): - return ( - -20 * np.exp(-0.2 * np.sqrt(0.5 * (x ** 2 + y ** 2))) - - np.exp(0.5 * (np.cos(2 * np.pi * x) + np.cos(2 * np.pi * y))) - + np.exp(1) - + 20 - ) - - -# Create gridded data -INC = 0.05 -x = np.arange(-5, 5 + INC, INC) -y = np.arange(-5, 5 + INC, INC) -data = xr.DataArray(ackley(*np.meshgrid(x, y)), coords=(x, y)) - -fig = pygmt.Figure() - -# Plot grid as a 3-D surface -SCALE = 0.5 # in centimeter -fig.grdview( - data, - frame=["a5f1", "za5f1"], - projection=f"x{SCALE}c", - zscale=f"{SCALE}c", - surftype="s", - cmap="roma", - perspective=[135, 30], # Azimuth southeast (135°), at elevation 30° - shading="+a45", -) - -fig.show() +""" +Plotting a surface +------------------ + +The :meth:`pygmt.Figure.grdview()` method can plot 3-D surfaces with ``surftype="s"``. Here, +we supply the data as an :class:`xarray.DataArray` with the coordinate vectors ``x`` and +``y`` defined. Note that the ``perspective`` parameter here controls the azimuth and +elevation angle of the view. We provide a list of two arguments to ``frame`` - the +first argument specifies the :math:`x`- and :math:`y`:-axes frame attributes and the +second argument, prepended with ``"z"``, specifies the :math:`z`-axis frame attributes. +Specifying the same scale for the ``projection`` and ``zcale`` parameters ensures equal +axis scaling. The ``shading`` parameter specifies illumination; here we choose an azimuth of +45° with ``shading="+a45"``. +""" + +import numpy as np +import pygmt +import xarray as xr + + +# Define an interesting function of two variables, see: +# https://en.wikipedia.org/wiki/Ackley_function +def ackley(x, y): + return ( + -20 * np.exp(-0.2 * np.sqrt(0.5 * (x ** 2 + y ** 2))) + - np.exp(0.5 * (np.cos(2 * np.pi * x) + np.cos(2 * np.pi * y))) + + np.exp(1) + + 20 + ) + + +# Create gridded data +INC = 0.05 +x = np.arange(-5, 5 + INC, INC) +y = np.arange(-5, 5 + INC, INC) +data = xr.DataArray(ackley(*np.meshgrid(x, y)), coords=(x, y)) + +fig = pygmt.Figure() + +# Plot grid as a 3-D surface +SCALE = 0.5 # in centimeter +fig.grdview( + data, + frame=["a5f1", "za5f1"], + projection=f"x{SCALE}c", + zscale=f"{SCALE}c", + surftype="s", + cmap="roma", + perspective=[135, 30], # Azimuth southeast (135°), at elevation 30° + shading="+a45", +) + +fig.show() diff --git a/examples/gallery/grid/track_sampling.py b/examples/gallery/grid/track_sampling.py index 02ba34a418c..a0dd1b72a06 100644 --- a/examples/gallery/grid/track_sampling.py +++ b/examples/gallery/grid/track_sampling.py @@ -1,39 +1,39 @@ -""" -Sampling along tracks ---------------------- - -The :func:`pygmt.grdtrack` function samples a raster grid's value along specified -points. We will need to input a 2D raster to ``grid`` which can be an -:class:`xarray.DataArray`. The argument passed to the ``points`` parameter can be a -:class:`pandas.DataFrame` table where the first two columns are x and y (or longitude -and latitude). Note also that there is a ``newcolname`` parameter that will be used to -name the new column of values sampled from the grid. - -Alternatively, a NetCDF file path can be passed to ``grid``. An ASCII file path can -also be accepted for ``points``. To save an output ASCII file, a file name argument -needs to be passed to the ``outfile`` parameter. -""" - -import pygmt - -# Load sample grid and point datasets -grid = pygmt.datasets.load_earth_relief() -points = pygmt.datasets.load_ocean_ridge_points() -# Sample the bathymetry along the world's ocean ridges at specified track points -track = pygmt.grdtrack(points=points, grid=grid, newcolname="bathymetry") - -fig = pygmt.Figure() -# Plot the earth relief grid on Cylindrical Stereographic projection, masking land areas -fig.basemap(region="g", frame=True, projection="Cyl_stere/150/-20/15c") -fig.grdimage(grid=grid, cmap="gray") -fig.coast(land="#666666") -# Plot using circles (c) of 0.15 cm, the sampled bathymetry points -# Points are colored using elevation values (normalized for visual purposes) -fig.plot( - x=track.longitude, - y=track.latitude, - style="c0.15c", - cmap="terra", - color=(track.bathymetry - track.bathymetry.mean()) / track.bathymetry.std(), -) -fig.show() +""" +Sampling along tracks +--------------------- + +The :func:`pygmt.grdtrack` function samples a raster grid's value along specified +points. We will need to input a 2D raster to ``grid`` which can be an +:class:`xarray.DataArray`. The argument passed to the ``points`` parameter can be a +:class:`pandas.DataFrame` table where the first two columns are x and y (or longitude +and latitude). Note also that there is a ``newcolname`` parameter that will be used to +name the new column of values sampled from the grid. + +Alternatively, a NetCDF file path can be passed to ``grid``. An ASCII file path can +also be accepted for ``points``. To save an output ASCII file, a file name argument +needs to be passed to the ``outfile`` parameter. +""" + +import pygmt + +# Load sample grid and point datasets +grid = pygmt.datasets.load_earth_relief() +points = pygmt.datasets.load_ocean_ridge_points() +# Sample the bathymetry along the world's ocean ridges at specified track points +track = pygmt.grdtrack(points=points, grid=grid, newcolname="bathymetry") + +fig = pygmt.Figure() +# Plot the earth relief grid on Cylindrical Stereographic projection, masking land areas +fig.basemap(region="g", frame=True, projection="Cyl_stere/150/-20/15c") +fig.grdimage(grid=grid, cmap="gray") +fig.coast(land="#666666") +# Plot using circles (c) of 0.15 cm, the sampled bathymetry points +# Points are colored using elevation values (normalized for visual purposes) +fig.plot( + x=track.longitude, + y=track.latitude, + style="c0.15c", + cmap="terra", + color=(track.bathymetry - track.bathymetry.mean()) / track.bathymetry.std(), +) +fig.show() diff --git a/examples/gallery/line/line-custom-cpt.py b/examples/gallery/line/line-custom-cpt.py index 4406c69f697..bc4a36795cc 100644 --- a/examples/gallery/line/line-custom-cpt.py +++ b/examples/gallery/line/line-custom-cpt.py @@ -1,33 +1,33 @@ -""" -Line colors with a custom CPT ------------------------------ - -The color of the lines made by :meth:`pygmt.Figure.plot` can be set according to a -custom CPT and assigned with the ``pen`` parameter. - -The custom CPT can be used by setting the plot command's ``cmap`` parameter to -``True``. The ``zvalue`` parameter sets the z-value (color) to be used from the custom -CPT, and the line color is set as the z-value by using **+z** when setting the ``pen`` -color. - -""" - -import numpy as np -import pygmt - -# Create a list of values between 20 and 30 with at 0.2 intervals -x = np.arange(start=20, stop=30, step=0.2) - -fig = pygmt.Figure() -fig.basemap(frame=["WSne", "af"], region=[20, 30, -10, 10]) - -# Create a custom CPT with the batlow CPT and 10 discrete z-values (colors) -pygmt.makecpt(cmap="batlow", series=[0, 10, 1]) - -# Plot 10 lines and set a different z-value for each line -for zvalue in range(0, 10): - y = zvalue * np.sin(x) - fig.plot(x=x, y=y, cmap=True, zvalue=zvalue, pen="thick,+z,-") -# Color bar to show the custom CPT and the associated z-values -fig.colorbar() -fig.show() +""" +Line colors with a custom CPT +----------------------------- + +The color of the lines made by :meth:`pygmt.Figure.plot` can be set according to a +custom CPT and assigned with the ``pen`` parameter. + +The custom CPT can be used by setting the plot command's ``cmap`` parameter to +``True``. The ``zvalue`` parameter sets the z-value (color) to be used from the custom +CPT, and the line color is set as the z-value by using **+z** when setting the ``pen`` +color. + +""" + +import numpy as np +import pygmt + +# Create a list of values between 20 and 30 with at 0.2 intervals +x = np.arange(start=20, stop=30, step=0.2) + +fig = pygmt.Figure() +fig.basemap(frame=["WSne", "af"], region=[20, 30, -10, 10]) + +# Create a custom CPT with the batlow CPT and 10 discrete z-values (colors) +pygmt.makecpt(cmap="batlow", series=[0, 10, 1]) + +# Plot 10 lines and set a different z-value for each line +for zvalue in range(0, 10): + y = zvalue * np.sin(x) + fig.plot(x=x, y=y, cmap=True, zvalue=zvalue, pen="thick,+z,-") +# Color bar to show the custom CPT and the associated z-values +fig.colorbar() +fig.show() diff --git a/examples/gallery/line/linestyles.py b/examples/gallery/line/linestyles.py index 625be8c18f2..ef9da710167 100644 --- a/examples/gallery/line/linestyles.py +++ b/examples/gallery/line/linestyles.py @@ -1,55 +1,55 @@ -""" -Line styles ------------ - -The :meth:`pygmt.Figure.plot` method can plot lines in different styles. -The default line style is a 0.25-point wide, black, solid line, and can be -customized with the ``pen`` parameter. - -A *pen* in GMT has three attributes: *width*, *color*, and *style*. -The *style* attribute controls the appearance of the line. -Giving “dotted” or “.” yields a dotted line, whereas a dashed pen is requested -with “dashed” or “-”. Also combinations of dots and dashes, like “.-” for a -dot-dashed line, are allowed. - -For more advanced *pen* attributes, see the GMT cookbook -:gmt-docs:`cookbook/features.html#wpen-attrib`. - -""" - -import numpy as np -import pygmt - -# Generate a two-point line for plotting -x = np.array([0, 7]) -y = np.array([9, 9]) - -fig = pygmt.Figure() -fig.basemap(region=[0, 10, 0, 10], projection="X15c/8c", frame='+t"Line Styles"') - -# Plot the line using the default line style -fig.plot(x=x, y=y) -fig.text(x=x[-1], y=y[-1], text="solid (default)", justify="ML", offset="0.2c/0c") - -# Plot the line using different line styles -for linestyle in [ - "1p,red,-", # dashed line - "1p,blue,.", # dotted line - "1p,lightblue,-.", # dash-dotted line - "2p,blue,..-", # dot-dot-dashed line - "2p,tomato,--.", # dash-dash-dotted line - "2p,tomato,4_2:2p", # A pattern of 4-point-long line segment and 2-point-gap between segment -]: - y -= 1 # Move the current line down - fig.plot(x=x, y=y, pen=linestyle) - fig.text(x=x[-1], y=y[-1], text=linestyle, justify="ML", offset="0.2c/0c") - -# Plot the line like a railway track (black/white). -# The trick here is plotting the same line twice but with different line styles -y -= 1 # move the current line down -fig.plot(x=x, y=y, pen="5p,black") -fig.plot(x=x, y=y, pen="4p,white,20p_20p") -fig.text(x=x[-1], y=y[-1], text="5p,black", justify="ML", offset="0.2c/0.2c") -fig.text(x=x[-1], y=y[-1], text="4p,white,20p_20p", justify="ML", offset="0.2c/-0.2c") - -fig.show() +""" +Line styles +----------- + +The :meth:`pygmt.Figure.plot` method can plot lines in different styles. +The default line style is a 0.25-point wide, black, solid line, and can be +customized with the ``pen`` parameter. + +A *pen* in GMT has three attributes: *width*, *color*, and *style*. +The *style* attribute controls the appearance of the line. +Giving “dotted” or “.” yields a dotted line, whereas a dashed pen is requested +with “dashed” or “-”. Also combinations of dots and dashes, like “.-” for a +dot-dashed line, are allowed. + +For more advanced *pen* attributes, see the GMT cookbook +:gmt-docs:`cookbook/features.html#wpen-attrib`. + +""" + +import numpy as np +import pygmt + +# Generate a two-point line for plotting +x = np.array([0, 7]) +y = np.array([9, 9]) + +fig = pygmt.Figure() +fig.basemap(region=[0, 10, 0, 10], projection="X15c/8c", frame='+t"Line Styles"') + +# Plot the line using the default line style +fig.plot(x=x, y=y) +fig.text(x=x[-1], y=y[-1], text="solid (default)", justify="ML", offset="0.2c/0c") + +# Plot the line using different line styles +for linestyle in [ + "1p,red,-", # dashed line + "1p,blue,.", # dotted line + "1p,lightblue,-.", # dash-dotted line + "2p,blue,..-", # dot-dot-dashed line + "2p,tomato,--.", # dash-dash-dotted line + "2p,tomato,4_2:2p", # A pattern of 4-point-long line segment and 2-point-gap between segment +]: + y -= 1 # Move the current line down + fig.plot(x=x, y=y, pen=linestyle) + fig.text(x=x[-1], y=y[-1], text=linestyle, justify="ML", offset="0.2c/0c") + +# Plot the line like a railway track (black/white). +# The trick here is plotting the same line twice but with different line styles +y -= 1 # move the current line down +fig.plot(x=x, y=y, pen="5p,black") +fig.plot(x=x, y=y, pen="4p,white,20p_20p") +fig.text(x=x[-1], y=y[-1], text="5p,black", justify="ML", offset="0.2c/0.2c") +fig.text(x=x[-1], y=y[-1], text="4p,white,20p_20p", justify="ML", offset="0.2c/-0.2c") + +fig.show() diff --git a/examples/gallery/line/vector-heads-tails.py b/examples/gallery/line/vector-heads-tails.py index 366ee58beda..e00281f482f 100644 --- a/examples/gallery/line/vector-heads-tails.py +++ b/examples/gallery/line/vector-heads-tails.py @@ -1,82 +1,82 @@ -""" -Vector heads and tails ----------------------- - -Many modules in PyGMT allow plotting vectors with individual -heads and tails. For this purpose, several modifiers may be appended to -the corresponding vector-producing parameters for specifying the placement -of vector heads and tails, their shapes, and the justification of the vector. - -To place a vector head at the beginning of the vector path -simply append **+b** to the vector-producing option (use **+e** to place -one at the end). Optionally, append **t** for a terminal line, **c** for a -circle, **a** for arrow (default), **i** for tail, **A** for plain open -arrow, and **I** for plain open tail. Further append **l** or **r** (e.g. -``+bar``) to only draw the left or right half-sides of the selected head/tail -(default is both sides) or use **+l** or **+r** to apply simultaneously to both -sides. In this context left and right refer to the side of the vector line -when viewed from the beginning point to the end point of a line segment. -The angle of the vector head apex can be set using **+a**\ *angle* -(default is 30). The shape of the vector head can be adjusted using -**+h**\ *shape* (e.g. ``+h0.5``). - -For further modifiers see the *Vector Attributes* subsection of the -corresponding module. - -In the following we use the :meth:`pygmt.Figure.plot` method to plot vectors -with individual heads and tails. We must specify the modifiers (together with -the vector type, here ``v``, see also -:doc:`Vector types documentation `) -by passing the corresponding shortcuts to the ``style`` parameter. - -""" - -import pygmt - -fig = pygmt.Figure() -fig.basemap( - region=[0, 10, 0, 15], projection="X15c/10c", frame='+t"Vector heads and tails"' -) - -x = 1 -y = 14 -angle = 0 # in degrees, measured counter-clockwise from horizontal -length = 7 - -for vecstyle in [ - # vector without head and tail (line) - "v0c", - # plain open arrow at beginning and end, angle of the vector head apex is set to 50 - "v0.6c+bA+eA+a50", - # plain open tail at beginning and end - "v0.4c+bI+eI", - # terminal line at beginning and end, angle of vector head apex is set to 80 - "v0.3c+bt+et+a80", - # arrow head at end - "v0.6c+e", - # circle at beginning and arrow head at end - "v0.6c+bc+ea", - # terminal line at beginning and arrow head at end - "v0.6c+bt+ea", - # arrow head at end, shape of vector head is set to 0.5 - "v1c+e+h0.5", - # modified arrow heads at beginning and end - "v1c+b+e+h0.5", - # tail at beginning and arrow with modified vector head at end - "v1c+bi+ea+h0.5", - # half-sided arrow head (right side) at beginning and arrow at the end - "v1c+bar+ea+h0.8", - # half-sided arrow heads at beginning (right side) and end (left side) - "v1c+bar+eal+h0.5", - # half-sided tail at beginning and arrow at end (right side for both) - "v1c+bi+ea+r+h0.5+a45", -]: - fig.plot( - x=x, y=y, style=vecstyle, direction=([angle], [length]), pen="2p", color="red3" - ) - fig.text( - x=6, y=y, text=vecstyle, font="Courier-Bold", justify="ML", offset="0.2c/0c" - ) - y -= 1 # move the next vector down - -fig.show() +""" +Vector heads and tails +---------------------- + +Many modules in PyGMT allow plotting vectors with individual +heads and tails. For this purpose, several modifiers may be appended to +the corresponding vector-producing parameters for specifying the placement +of vector heads and tails, their shapes, and the justification of the vector. + +To place a vector head at the beginning of the vector path +simply append **+b** to the vector-producing option (use **+e** to place +one at the end). Optionally, append **t** for a terminal line, **c** for a +circle, **a** for arrow (default), **i** for tail, **A** for plain open +arrow, and **I** for plain open tail. Further append **l** or **r** (e.g. +``+bar``) to only draw the left or right half-sides of the selected head/tail +(default is both sides) or use **+l** or **+r** to apply simultaneously to both +sides. In this context left and right refer to the side of the vector line +when viewed from the beginning point to the end point of a line segment. +The angle of the vector head apex can be set using **+a**\ *angle* +(default is 30). The shape of the vector head can be adjusted using +**+h**\ *shape* (e.g. ``+h0.5``). + +For further modifiers see the *Vector Attributes* subsection of the +corresponding module. + +In the following we use the :meth:`pygmt.Figure.plot` method to plot vectors +with individual heads and tails. We must specify the modifiers (together with +the vector type, here ``v``, see also +:doc:`Vector types documentation `) +by passing the corresponding shortcuts to the ``style`` parameter. + +""" + +import pygmt + +fig = pygmt.Figure() +fig.basemap( + region=[0, 10, 0, 15], projection="X15c/10c", frame='+t"Vector heads and tails"' +) + +x = 1 +y = 14 +angle = 0 # in degrees, measured counter-clockwise from horizontal +length = 7 + +for vecstyle in [ + # vector without head and tail (line) + "v0c", + # plain open arrow at beginning and end, angle of the vector head apex is set to 50 + "v0.6c+bA+eA+a50", + # plain open tail at beginning and end + "v0.4c+bI+eI", + # terminal line at beginning and end, angle of vector head apex is set to 80 + "v0.3c+bt+et+a80", + # arrow head at end + "v0.6c+e", + # circle at beginning and arrow head at end + "v0.6c+bc+ea", + # terminal line at beginning and arrow head at end + "v0.6c+bt+ea", + # arrow head at end, shape of vector head is set to 0.5 + "v1c+e+h0.5", + # modified arrow heads at beginning and end + "v1c+b+e+h0.5", + # tail at beginning and arrow with modified vector head at end + "v1c+bi+ea+h0.5", + # half-sided arrow head (right side) at beginning and arrow at the end + "v1c+bar+ea+h0.8", + # half-sided arrow heads at beginning (right side) and end (left side) + "v1c+bar+eal+h0.5", + # half-sided tail at beginning and arrow at end (right side for both) + "v1c+bi+ea+r+h0.5+a45", +]: + fig.plot( + x=x, y=y, style=vecstyle, direction=([angle], [length]), pen="2p", color="red3" + ) + fig.text( + x=6, y=y, text=vecstyle, font="Courier-Bold", justify="ML", offset="0.2c/0c" + ) + y -= 1 # move the next vector down + +fig.show() diff --git a/examples/gallery/line/vectors.py b/examples/gallery/line/vectors.py index 480cd9cb89b..20d612804ed 100644 --- a/examples/gallery/line/vectors.py +++ b/examples/gallery/line/vectors.py @@ -1,64 +1,64 @@ -""" -Cartesian, circular, and geographic vectors -------------------------------------------- - -The :meth:`pygmt.Figure.plot` method can plot Cartesian, circular, and geographic vectors. -The ``style`` parameter controls vector attributes. See also -:doc:`Vector attributes documentation `. - -""" -import numpy as np -import pygmt - -# create a plot with coast, Mercator projection (M) over the continental US -fig = pygmt.Figure() -fig.coast( - region=[-127, -64, 24, 53], - projection="M15c", - frame=True, - borders=1, - area_thresh=4000, - shorelines="0.25p,black", -) - - -# Left: plot 12 Cartesian vectors with different lengths -x = np.linspace(-116, -116, 12) # x vector coordinates -y = np.linspace(33.5, 42.5, 12) # y vector coordinates -direction = np.zeros(x.shape) # direction of vectors -length = np.linspace(0.5, 2.4, 12) # length of vectors -# Cartesian vectors (v) with red pen and fill (+g, +p), vector head at end (+e), -# and 40 degree angle (+a) with no indentation for vector head (+h) -style = "v0.2c+e+a40+gred+h0+p1p,red" -fig.plot(x=x, y=y, style=style, pen="1p,red", direction=[direction, length]) -fig.text(text="CARTESIAN", x=-112, y=44.2, font="13p,Helvetica-Bold,red", fill="white") - - -# Middle: plot 7 math angle arcs with different radii -num = 7 -x = np.full(num, -95) # x coordinates of the center -y = np.full(num, 37) # y coordinates of the center -radius = 1.8 - 0.2 * np.arange(0, num) # radius -startdir = np.full(num, 90) # start direction in degrees -stopdir = 180 + 40 * np.arange(0, num) # stop direction in degrees -# data for circular vectors -data = np.column_stack([x, y, radius, startdir, stopdir]) -arcstyle = "m0.5c+ea" # Circular vector (m) with an arrow at end -fig.plot(data=data, style=arcstyle, color="red3", pen="1.5p,black") -fig.text(text="CIRCULAR", x=-95, y=44.2, font="13p,Helvetica-Bold,black", fill="white") - - -# Right: plot geographic vectors using endpoints -NYC = [-74.0060, 40.7128] -CHI = [-87.6298, 41.8781] -SEA = [-122.3321, 47.6062] -NO = [-90.0715, 29.9511] -# `=` means geographic vectors. -# With the modifier '+s', the input data should contain coordinates of start and end points -style = "=0.5c+s+e+a30+gblue+h0.5+p1p,blue" -data = np.array([NYC + CHI, NYC + SEA, NYC + NO]) -fig.plot(data=data, style=style, pen="1.0p,blue") -fig.text( - text="GEOGRAPHIC", x=-74.5, y=44.2, font="13p,Helvetica-Bold,blue", fill="white" -) -fig.show() +""" +Cartesian, circular, and geographic vectors +------------------------------------------- + +The :meth:`pygmt.Figure.plot` method can plot Cartesian, circular, and geographic vectors. +The ``style`` parameter controls vector attributes. See also +:doc:`Vector attributes documentation `. + +""" +import numpy as np +import pygmt + +# create a plot with coast, Mercator projection (M) over the continental US +fig = pygmt.Figure() +fig.coast( + region=[-127, -64, 24, 53], + projection="M15c", + frame=True, + borders=1, + area_thresh=4000, + shorelines="0.25p,black", +) + + +# Left: plot 12 Cartesian vectors with different lengths +x = np.linspace(-116, -116, 12) # x vector coordinates +y = np.linspace(33.5, 42.5, 12) # y vector coordinates +direction = np.zeros(x.shape) # direction of vectors +length = np.linspace(0.5, 2.4, 12) # length of vectors +# Cartesian vectors (v) with red pen and fill (+g, +p), vector head at end (+e), +# and 40 degree angle (+a) with no indentation for vector head (+h) +style = "v0.2c+e+a40+gred+h0+p1p,red" +fig.plot(x=x, y=y, style=style, pen="1p,red", direction=[direction, length]) +fig.text(text="CARTESIAN", x=-112, y=44.2, font="13p,Helvetica-Bold,red", fill="white") + + +# Middle: plot 7 math angle arcs with different radii +num = 7 +x = np.full(num, -95) # x coordinates of the center +y = np.full(num, 37) # y coordinates of the center +radius = 1.8 - 0.2 * np.arange(0, num) # radius +startdir = np.full(num, 90) # start direction in degrees +stopdir = 180 + 40 * np.arange(0, num) # stop direction in degrees +# data for circular vectors +data = np.column_stack([x, y, radius, startdir, stopdir]) +arcstyle = "m0.5c+ea" # Circular vector (m) with an arrow at end +fig.plot(data=data, style=arcstyle, color="red3", pen="1.5p,black") +fig.text(text="CIRCULAR", x=-95, y=44.2, font="13p,Helvetica-Bold,black", fill="white") + + +# Right: plot geographic vectors using endpoints +NYC = [-74.0060, 40.7128] +CHI = [-87.6298, 41.8781] +SEA = [-122.3321, 47.6062] +NO = [-90.0715, 29.9511] +# `=` means geographic vectors. +# With the modifier '+s', the input data should contain coordinates of start and end points +style = "=0.5c+s+e+a30+gblue+h0.5+p1p,blue" +data = np.array([NYC + CHI, NYC + SEA, NYC + NO]) +fig.plot(data=data, style=style, pen="1.0p,blue") +fig.text( + text="GEOGRAPHIC", x=-74.5, y=44.2, font="13p,Helvetica-Bold,blue", fill="white" +) +fig.show() diff --git a/examples/gallery/plot/colorbar.py b/examples/gallery/plot/colorbar.py index eb9994191ab..bbc4639f881 100644 --- a/examples/gallery/plot/colorbar.py +++ b/examples/gallery/plot/colorbar.py @@ -1,58 +1,58 @@ -""" -Colorbar --------- - -The :meth:`pygmt.Figure.colorbar` method creates a color scalebar. We must -specify the colormap via the ``cmap`` parameter, and optionally set the -placement via the ``position`` parameter. The full list of color palette tables -can be found at :gmt-docs:`cookbook/cpts.html`. You can set the ``position`` of -the colorbar using the following options: - -- **j/J**: justified inside/outside the map frame using any 2 character combination - of vertical (**T**\ op, **M**\ iddle, **B**\ ottom) and horizontal (**L**\ eft, - **C**\ enter, **R**\ ight) alignment codes, e.g. ``position="jTR"`` for top - right. -- **g**: using map coordinates, e.g. ``position="g170/-45"`` for longitude 170E, - latitude 45S. -- **x**: using paper coordinates, e.g. ``position="x5c/7c"`` for 5cm,7cm from anchor - point. -- **n**: using normalized (0-1) coordinates, e.g. ``position="n0.4/0.8"``. - -Note that the anchor point defaults to the bottom left (**BL**). Append ``+h`` to -``position`` to get a horizontal colorbar instead of a vertical one. -""" -import pygmt - -fig = pygmt.Figure() -fig.basemap(region=[0, 3, 6, 9], projection="x3c", frame=["af", 'WSne+t"Colorbars"']) - -## Create a colorbar designed for seismic tomography - roma -# Colorbar is placed at bottom center (BC) by default if no position is given -fig.colorbar(cmap="roma", frame=["x+lVelocity", "y+lm/s"]) - -## Create a colorbar showing the scientific rainbow - batlow -fig.colorbar( - cmap="batlow", - # Colorbar positioned at map coordinates (g) longitude/latitude 0.3/8.7, - # with a length/width (+w) of 4cm by 0.5cm, and plotted horizontally (+h) - position="g0.3/8.7+w4c/0.5c+h", - box=True, - frame=["x+lTemperature", r"y+l\260C"], - scale=100, -) - -## Create a colorbar suitable for surface topography - oleron -fig.colorbar( - cmap="oleron", - # Colorbar position justified outside map frame (J) at Middle Right (MR), - # offset (+o) by 1cm horizontally and 0cm vertically from anchor point, - # with a length/width (+w) of 7cm by 0.5cm and a box for NaN values (+n) - position="JMR+o1c/0c+w7c/0.5c+n+mc", - # Note that the label 'Elevation' is moved to the opposite side and plotted - # vertically as a column of text using '+mc' in the position parameter - # above - frame=["x+lElevation", "y+lm"], - scale=10, -) - -fig.show() +""" +Colorbar +-------- + +The :meth:`pygmt.Figure.colorbar` method creates a color scalebar. We must +specify the colormap via the ``cmap`` parameter, and optionally set the +placement via the ``position`` parameter. The full list of color palette tables +can be found at :gmt-docs:`cookbook/cpts.html`. You can set the ``position`` of +the colorbar using the following options: + +- **j/J**: justified inside/outside the map frame using any 2 character combination + of vertical (**T**\ op, **M**\ iddle, **B**\ ottom) and horizontal (**L**\ eft, + **C**\ enter, **R**\ ight) alignment codes, e.g. ``position="jTR"`` for top + right. +- **g**: using map coordinates, e.g. ``position="g170/-45"`` for longitude 170E, + latitude 45S. +- **x**: using paper coordinates, e.g. ``position="x5c/7c"`` for 5cm,7cm from anchor + point. +- **n**: using normalized (0-1) coordinates, e.g. ``position="n0.4/0.8"``. + +Note that the anchor point defaults to the bottom left (**BL**). Append ``+h`` to +``position`` to get a horizontal colorbar instead of a vertical one. +""" +import pygmt + +fig = pygmt.Figure() +fig.basemap(region=[0, 3, 6, 9], projection="x3c", frame=["af", 'WSne+t"Colorbars"']) + +## Create a colorbar designed for seismic tomography - roma +# Colorbar is placed at bottom center (BC) by default if no position is given +fig.colorbar(cmap="roma", frame=["x+lVelocity", "y+lm/s"]) + +## Create a colorbar showing the scientific rainbow - batlow +fig.colorbar( + cmap="batlow", + # Colorbar positioned at map coordinates (g) longitude/latitude 0.3/8.7, + # with a length/width (+w) of 4cm by 0.5cm, and plotted horizontally (+h) + position="g0.3/8.7+w4c/0.5c+h", + box=True, + frame=["x+lTemperature", r"y+l\260C"], + scale=100, +) + +## Create a colorbar suitable for surface topography - oleron +fig.colorbar( + cmap="oleron", + # Colorbar position justified outside map frame (J) at Middle Right (MR), + # offset (+o) by 1cm horizontally and 0cm vertically from anchor point, + # with a length/width (+w) of 7cm by 0.5cm and a box for NaN values (+n) + position="JMR+o1c/0c+w7c/0.5c+n+mc", + # Note that the label 'Elevation' is moved to the opposite side and plotted + # vertically as a column of text using '+mc' in the position parameter + # above + frame=["x+lElevation", "y+lm"], + scale=10, +) + +fig.show() diff --git a/examples/gallery/plot/datetime-inputs.py b/examples/gallery/plot/datetime-inputs.py index 4851b337484..621e9017ebb 100644 --- a/examples/gallery/plot/datetime-inputs.py +++ b/examples/gallery/plot/datetime-inputs.py @@ -1,65 +1,65 @@ -""" -Datetime inputs ---------------- - -Datetime inputs of the following types are supported in PyGMT: - -- :class:`numpy.datetime64` -- :class:`pandas.DatetimeIndex` -- :class:`xarray.DataArray`: datetimes included in a *xarray.DataArray* -- raw datetime strings in `ISO format `__ - (e.g. ``"YYYY-MM-DD"``, ``"YYYY-MM-DDTHH"``, and ``"YYYY-MM-DDTHH:MM:SS"``) -- Python built-in :class:`datetime.datetime` and :class:`datetime.date` - -We can pass datetime inputs based on one of the types listed above directly to -the ``x`` and ``y`` parameters of e.g. the :meth:`pygmt.Figure.plot` method. - -The ``region`` parameter has to include the :math:`x` and :math:`y` axis limits -in the form [*date_min*, *date_max*, *ymin*, *ymax*]. Here *date_min* and -*date_max* can be directly defined as datetime input. - -""" - -import datetime - -import numpy as np -import pandas as pd -import pygmt -import xarray as xr - -fig = pygmt.Figure() - -# create a basemap with limits of 2010-01-01 to 2020-06-01 on the x axis and -# 0 to 10 on the y axis -fig.basemap( - projection="X15c/5c", - region=[datetime.date(2010, 1, 1), datetime.date(2020, 6, 1), 0, 10], - frame=["WSen", "af"], -) - -# numpy.datetime64 types -x = np.array(["2010-06-01", "2011-06-01T12", "2012-01-01T12:34:56"], dtype="datetime64") -y = [1, 2, 3] -fig.plot(x, y, style="c0.4c", pen="1p", color="red3") - -# pandas.DatetimeIndex -x = pd.date_range("2013", periods=3, freq="YS") -y = [4, 5, 6] -fig.plot(x, y, style="t0.4c", pen="1p", color="gold") - -# xarray.DataArray -x = xr.DataArray(data=pd.date_range(start="2015-03", periods=3, freq="QS")) -y = [7.5, 6, 4.5] -fig.plot(x, y, style="s0.4c", pen="1p") - -# raw datetime strings -x = ["2016-02-01", "2016-06-04T14", "2016-10-04T00:00:15"] -y = [7, 8, 9] -fig.plot(x, y, style="a0.4c", pen="1p", color="dodgerblue") - -# the Python built-in datetime and date -x = [datetime.date(2018, 1, 1), datetime.datetime(2019, 6, 1, 20, 5, 45)] -y = [6.5, 4.5] -fig.plot(x, y, style="i0.4c", pen="1p", color="seagreen") - -fig.show() +""" +Datetime inputs +--------------- + +Datetime inputs of the following types are supported in PyGMT: + +- :class:`numpy.datetime64` +- :class:`pandas.DatetimeIndex` +- :class:`xarray.DataArray`: datetimes included in a *xarray.DataArray* +- raw datetime strings in `ISO format `__ + (e.g. ``"YYYY-MM-DD"``, ``"YYYY-MM-DDTHH"``, and ``"YYYY-MM-DDTHH:MM:SS"``) +- Python built-in :class:`datetime.datetime` and :class:`datetime.date` + +We can pass datetime inputs based on one of the types listed above directly to +the ``x`` and ``y`` parameters of e.g. the :meth:`pygmt.Figure.plot` method. + +The ``region`` parameter has to include the :math:`x` and :math:`y` axis limits +in the form [*date_min*, *date_max*, *ymin*, *ymax*]. Here *date_min* and +*date_max* can be directly defined as datetime input. + +""" + +import datetime + +import numpy as np +import pandas as pd +import pygmt +import xarray as xr + +fig = pygmt.Figure() + +# create a basemap with limits of 2010-01-01 to 2020-06-01 on the x axis and +# 0 to 10 on the y axis +fig.basemap( + projection="X15c/5c", + region=[datetime.date(2010, 1, 1), datetime.date(2020, 6, 1), 0, 10], + frame=["WSen", "af"], +) + +# numpy.datetime64 types +x = np.array(["2010-06-01", "2011-06-01T12", "2012-01-01T12:34:56"], dtype="datetime64") +y = [1, 2, 3] +fig.plot(x, y, style="c0.4c", pen="1p", color="red3") + +# pandas.DatetimeIndex +x = pd.date_range("2013", periods=3, freq="YS") +y = [4, 5, 6] +fig.plot(x, y, style="t0.4c", pen="1p", color="gold") + +# xarray.DataArray +x = xr.DataArray(data=pd.date_range(start="2015-03", periods=3, freq="QS")) +y = [7.5, 6, 4.5] +fig.plot(x, y, style="s0.4c", pen="1p") + +# raw datetime strings +x = ["2016-02-01", "2016-06-04T14", "2016-10-04T00:00:15"] +y = [7, 8, 9] +fig.plot(x, y, style="a0.4c", pen="1p", color="dodgerblue") + +# the Python built-in datetime and date +x = [datetime.date(2018, 1, 1), datetime.datetime(2019, 6, 1, 20, 5, 45)] +y = [6.5, 4.5] +fig.plot(x, y, style="i0.4c", pen="1p", color="seagreen") + +fig.show() diff --git a/examples/gallery/plot/image.py b/examples/gallery/plot/image.py index aa5984d7b1e..cd03ca98bbd 100644 --- a/examples/gallery/plot/image.py +++ b/examples/gallery/plot/image.py @@ -1,31 +1,31 @@ -""" -Images or EPS files on maps ---------------------------- -The :meth:`pygmt.Figure.image` method can be used to read and -place a raster image file or an Encapsulated PostScript file -on a map. We must specify the file as *str* via the ``imagefile`` -parameter or simply use the filename as the first argument. You can -also use a full URL pointing to your desired image. The ``position`` -parameter allows us to set a reference point on the map for the image. - -""" -import os - -import pygmt - -fig = pygmt.Figure() - -fig.basemap(region=[0, 2, 0, 2], projection="X6c", frame=True) - -# place and center the GMT logo from the GMT website to the position 1/1 -# on a basemap and draw a rectangular border around the image -fig.image( - imagefile="https://www.generic-mapping-tools.org/_static/gmt-logo.png", - position="g1/1+w3c+jCM", - box=True, -) - -# clean up the downloaded image in the current directory -os.remove("gmt-logo.png") - -fig.show() +""" +Images or EPS files on maps +--------------------------- +The :meth:`pygmt.Figure.image` method can be used to read and +place a raster image file or an Encapsulated PostScript file +on a map. We must specify the file as *str* via the ``imagefile`` +parameter or simply use the filename as the first argument. You can +also use a full URL pointing to your desired image. The ``position`` +parameter allows us to set a reference point on the map for the image. + +""" +import os + +import pygmt + +fig = pygmt.Figure() + +fig.basemap(region=[0, 2, 0, 2], projection="X6c", frame=True) + +# place and center the GMT logo from the GMT website to the position 1/1 +# on a basemap and draw a rectangular border around the image +fig.image( + imagefile="https://www.generic-mapping-tools.org/_static/gmt-logo.png", + position="g1/1+w3c+jCM", + box=True, +) + +# clean up the downloaded image in the current directory +os.remove("gmt-logo.png") + +fig.show() diff --git a/examples/gallery/plot/inset.py b/examples/gallery/plot/inset.py index 8f549b00f49..b774b4a8ba4 100644 --- a/examples/gallery/plot/inset.py +++ b/examples/gallery/plot/inset.py @@ -1,25 +1,25 @@ -""" -Inset ------ - -The :meth:`pygmt.Figure.inset` method adds an inset figure inside a larger -figure. The function is called using a ``with`` statement, and its position, -box, offset, and margin parameters are set. Within the ``with`` statement, -PyGMT plotting functions can be called that add to the inset figure. -""" -import pygmt - -fig = pygmt.Figure() -# Create the primary figure, setting the region to Madagascar, the land color to -# "brown", the water to "lightblue", the shorelines width to "thin", and adding a frame -fig.coast(region="MG+r2", land="brown", water="lightblue", shorelines="thin", frame="a") -# Create an inset, setting the position to top left, the width to 3.5 centimeters, and -# the x- and y-offsets to 0.2 centimeters. The margin is set to 0, and the border is "green". -with fig.inset(position="jTL+w3.5c+o0.2c", margin=0, box="+pgreen"): - # Create a figure in the inset using coast. This example uses the azimuthal - # orthogonal projection centered at 47E, 20S. The land is set to "gray" and - # Madagascar is highlighted in "red". - fig.coast( - region="g", projection="G47/-20/3.5c", land="gray", water="white", dcw="MG+gred" - ) -fig.show() +""" +Inset +----- + +The :meth:`pygmt.Figure.inset` method adds an inset figure inside a larger +figure. The function is called using a ``with`` statement, and its position, +box, offset, and margin parameters are set. Within the ``with`` statement, +PyGMT plotting functions can be called that add to the inset figure. +""" +import pygmt + +fig = pygmt.Figure() +# Create the primary figure, setting the region to Madagascar, the land color to +# "brown", the water to "lightblue", the shorelines width to "thin", and adding a frame +fig.coast(region="MG+r2", land="brown", water="lightblue", shorelines="thin", frame="a") +# Create an inset, setting the position to top left, the width to 3.5 centimeters, and +# the x- and y-offsets to 0.2 centimeters. The margin is set to 0, and the border is "green". +with fig.inset(position="jTL+w3.5c+o0.2c", margin=0, box="+pgreen"): + # Create a figure in the inset using coast. This example uses the azimuthal + # orthogonal projection centered at 47E, 20S. The land is set to "gray" and + # Madagascar is highlighted in "red". + fig.coast( + region="g", projection="G47/-20/3.5c", land="gray", water="white", dcw="MG+gred" + ) +fig.show() diff --git a/examples/gallery/plot/legend.py b/examples/gallery/plot/legend.py index 65f8c615b00..106a10c5494 100644 --- a/examples/gallery/plot/legend.py +++ b/examples/gallery/plot/legend.py @@ -1,27 +1,27 @@ -""" -Legend ------- - -The :meth:`pygmt.Figure.legend` method can automatically create a legend for -symbols plotted using :meth:`pygmt.Figure.plot`. Legend entries are only -created when the ``label`` parameter is used. -""" -import pygmt - -fig = pygmt.Figure() - -fig.basemap(projection="x1i", region=[0, 7, 3, 7], frame=True) - -fig.plot( - data="@Table_5_11.txt", - style="c0.15i", - color="lightgreen", - pen="faint", - label="Apples", -) -fig.plot(data="@Table_5_11.txt", pen="1.5p,gray", label='"My lines"') -fig.plot(data="@Table_5_11.txt", style="t0.15i", color="orange", label="Oranges") - -fig.legend(position="JTR+jTR+o0.2c", box=True) - -fig.show() +""" +Legend +------ + +The :meth:`pygmt.Figure.legend` method can automatically create a legend for +symbols plotted using :meth:`pygmt.Figure.plot`. Legend entries are only +created when the ``label`` parameter is used. +""" +import pygmt + +fig = pygmt.Figure() + +fig.basemap(projection="x1i", region=[0, 7, 3, 7], frame=True) + +fig.plot( + data="@Table_5_11.txt", + style="c0.15i", + color="lightgreen", + pen="faint", + label="Apples", +) +fig.plot(data="@Table_5_11.txt", pen="1.5p,gray", label='"My lines"') +fig.plot(data="@Table_5_11.txt", style="t0.15i", color="orange", label="Oranges") + +fig.legend(position="JTR+jTR+o0.2c", box=True) + +fig.show() diff --git a/examples/gallery/plot/logo.py b/examples/gallery/plot/logo.py index e9edc4512fe..075ee333ba2 100644 --- a/examples/gallery/plot/logo.py +++ b/examples/gallery/plot/logo.py @@ -1,18 +1,18 @@ -""" -Logo ----- - -The :meth:`pygmt.Figure.logo` method allows to place the GMT logo on a map. -""" - -import pygmt - -fig = pygmt.Figure() -fig.basemap(region=[0, 10, 0, 2], projection="X6c", frame=True) - -# add the GMT logo in the Top Right corner of the current map, -# scaled up to be 3 cm wide and offset by 0.3 cm in X direction -# and 0.6 cm in Y direction. -fig.logo(position="jTR+o0.3c/0.6c+w3c") - -fig.show() +""" +Logo +---- + +The :meth:`pygmt.Figure.logo` method allows to place the GMT logo on a map. +""" + +import pygmt + +fig = pygmt.Figure() +fig.basemap(region=[0, 10, 0, 2], projection="X6c", frame=True) + +# add the GMT logo in the Top Right corner of the current map, +# scaled up to be 3 cm wide and offset by 0.3 cm in X direction +# and 0.6 cm in Y direction. +fig.logo(position="jTR+o0.3c/0.6c+w3c") + +fig.show() diff --git a/examples/gallery/plot/meca.py b/examples/gallery/plot/meca.py index 3b555569499..285bd663caf 100644 --- a/examples/gallery/plot/meca.py +++ b/examples/gallery/plot/meca.py @@ -1,32 +1,32 @@ -""" -Focal mechanisms ----------------- - -The :meth:`pygmt.Figure.meca` method can plot focal mechanisms, or beachballs. -We can specify the focal mechanism nodal planes or moment tensor components as -a dict using the ``spec`` parameter (or they can be specified as a 1d or 2d array, -or within a specified file). The size of plotted beachballs can be specified -using the ``scale`` parameter. -""" - -import pygmt - -fig = pygmt.Figure() - -# generate a basemap near Washington state showing coastlines, land, and water -fig.coast( - region=[-125, -122, 47, 49], - projection="M6c", - land="grey", - water="lightblue", - shorelines=True, - frame="a", -) - -# store focal mechanisms parameters in a dict -focal_mechanism = dict(strike=330, dip=30, rake=90, magnitude=3) - -# pass the focal mechanism data to meca in addition to the scale and event location -fig.meca(focal_mechanism, scale="1c", longitude=-124.3, latitude=48.1, depth=12.0) - -fig.show() +""" +Focal mechanisms +---------------- + +The :meth:`pygmt.Figure.meca` method can plot focal mechanisms, or beachballs. +We can specify the focal mechanism nodal planes or moment tensor components as +a dict using the ``spec`` parameter (or they can be specified as a 1d or 2d array, +or within a specified file). The size of plotted beachballs can be specified +using the ``scale`` parameter. +""" + +import pygmt + +fig = pygmt.Figure() + +# generate a basemap near Washington state showing coastlines, land, and water +fig.coast( + region=[-125, -122, 47, 49], + projection="M6c", + land="grey", + water="lightblue", + shorelines=True, + frame="a", +) + +# store focal mechanisms parameters in a dict +focal_mechanism = dict(strike=330, dip=30, rake=90, magnitude=3) + +# pass the focal mechanism data to meca in addition to the scale and event location +fig.meca(focal_mechanism, scale="1c", longitude=-124.3, latitude=48.1, depth=12.0) + +fig.show() diff --git a/examples/gallery/plot/multi-parameter-symbols.py b/examples/gallery/plot/multi-parameter-symbols.py index f4a1171daed..7af29803a62 100644 --- a/examples/gallery/plot/multi-parameter-symbols.py +++ b/examples/gallery/plot/multi-parameter-symbols.py @@ -1,62 +1,62 @@ -""" -Multi-parameter symbols -------------------------- - -The :meth:`pygmt.Figure.plot` method can plot individual multi-parameter symbols by passing -the corresponding shortcuts listed below to the ``style`` parameter. Additionally, we must define -the required parameters in a 2d list or numpy array (``[[parameters]]`` for a single symbol -or ``[[parameters_1],[parameters_2],[parameters_i]]`` for several ones) or use an -appropriately formatted input file and pass it to ``data``. - -The following symbols are available: - -- **e**: ellipse, ``[[lon, lat, direction, major_axis, minor_axis]]`` -- **j**: rotated rectangle, ``[[lon, lat, direction, width, height]]`` -- **r**: rectangle, ``[[lon, lat, width, height]]`` -- **R**: rounded rectangle, ``[[lon, lat, width, height, radius]]`` -- **w**: pie wedge, ``[[lon, lat, radius, startdir, stopdir]]``, the last two arguments are - directions given in degrees counter-clockwise from horizontal - -Upper-case versions **E**, **J**, and **W** are similar to **e**, **j** and **w** but expect geographic -azimuths and distances. - -""" - -import numpy as np -import pygmt - -fig = pygmt.Figure() - -fig.basemap(region=[0, 6, 0, 2], projection="x3c", frame=True) - -################### -# ELLIPSE -data = np.array([[0.5, 1, 45, 3, 1]]) - -fig.plot(data=data, style="e", color="orange", pen="2p,black") - -################### -# ROTATED RECTANGLE -data = np.array([[1.5, 1, 120, 5, 0.5]]) - -fig.plot(data=data, style="j", color="red3", pen="2p,black") - -################### -# RECTANGLE -data = np.array([[3, 1, 4, 1.5]]) - -fig.plot(data=data, style="r", color="dodgerblue", pen="2p,black") - -################### -# ROUNDED RECTANGLE -data = np.array([[4.5, 1, 1.25, 4, 0.5]]) - -fig.plot(data=data, style="R", color="seagreen", pen="2p,black") - -################### -# PIE WEDGE -data = np.array([[5.5, 1, 2.5, 45, 330]]) - -fig.plot(data=data, style="w", color="lightgray", pen="2p,black") - -fig.show() +""" +Multi-parameter symbols +------------------------- + +The :meth:`pygmt.Figure.plot` method can plot individual multi-parameter symbols by passing +the corresponding shortcuts listed below to the ``style`` parameter. Additionally, we must define +the required parameters in a 2d list or numpy array (``[[parameters]]`` for a single symbol +or ``[[parameters_1],[parameters_2],[parameters_i]]`` for several ones) or use an +appropriately formatted input file and pass it to ``data``. + +The following symbols are available: + +- **e**: ellipse, ``[[lon, lat, direction, major_axis, minor_axis]]`` +- **j**: rotated rectangle, ``[[lon, lat, direction, width, height]]`` +- **r**: rectangle, ``[[lon, lat, width, height]]`` +- **R**: rounded rectangle, ``[[lon, lat, width, height, radius]]`` +- **w**: pie wedge, ``[[lon, lat, radius, startdir, stopdir]]``, the last two arguments are + directions given in degrees counter-clockwise from horizontal + +Upper-case versions **E**, **J**, and **W** are similar to **e**, **j** and **w** but expect geographic +azimuths and distances. + +""" + +import numpy as np +import pygmt + +fig = pygmt.Figure() + +fig.basemap(region=[0, 6, 0, 2], projection="x3c", frame=True) + +################### +# ELLIPSE +data = np.array([[0.5, 1, 45, 3, 1]]) + +fig.plot(data=data, style="e", color="orange", pen="2p,black") + +################### +# ROTATED RECTANGLE +data = np.array([[1.5, 1, 120, 5, 0.5]]) + +fig.plot(data=data, style="j", color="red3", pen="2p,black") + +################### +# RECTANGLE +data = np.array([[3, 1, 4, 1.5]]) + +fig.plot(data=data, style="r", color="dodgerblue", pen="2p,black") + +################### +# ROUNDED RECTANGLE +data = np.array([[4.5, 1, 1.25, 4, 0.5]]) + +fig.plot(data=data, style="R", color="seagreen", pen="2p,black") + +################### +# PIE WEDGE +data = np.array([[5.5, 1, 2.5, 45, 330]]) + +fig.plot(data=data, style="w", color="lightgray", pen="2p,black") + +fig.show() diff --git a/examples/gallery/plot/points-categorial.py b/examples/gallery/plot/points-categorial.py new file mode 100644 index 00000000000..901fa4a3321 --- /dev/null +++ b/examples/gallery/plot/points-categorial.py @@ -0,0 +1,49 @@ +""" +Color points by categories +--------------------------- + +The :meth:`pygmt.Figure.plot` method can be used to plot symbols which are +color-coded by categories. +""" + +import numpy as np +import pandas as pd +import pygmt + +# Load sample iris data, and convert 'species' column to categorical dtype +df = pd.read_csv("https://github.com/mwaskom/seaborn-data/raw/master/iris.csv") +df["species"] = df.species.astype(dtype="category") + +# Use pygmt.info to get region bounds (xmin, xmax, ymin, ymax) +# The below example will return a numpy array like [2. 4.4 4.3 7.9] +region = pygmt.info( + table=df[["sepal_width", "sepal_length"]], # x and y columns + per_column=True, # report output as a numpy array +) + +# Make our 2D categorial scatter plot, coloring each of the 3 species differently +fig = pygmt.Figure() + +# Generate basemap of 10cm x 10cm size +fig.basemap( + region=region, + projection="X10c/10c", + frame=['xafg+l"Sepal Width"', 'yafg+l"Sepal Length"', "WSen"], +) + +# Define colormap to use for three categories +pygmt.makecpt(cmap="inferno", color_model="+c", series=(0, 3, 1)) + +fig.plot( + x=df.sepal_width, # Use one feature as x data input + y=df.sepal_length, # Use another feature as y data input + sizes=df.petal_width + / df.petal_length, # Vary each symbol size according to the ratio of the two remaining features + color=df.species.cat.codes.astype(int), # Points colored by categorical number code + cmap=True, # Use colormap created by makecpt + no_clip=True, # Do not clip symbols that fall exactly on the map bounds + style="cc", # Use circles as symbols with size in centimeter units + transparency=40, # Set transparency level for all symbols to deal with overplotting +) + +fig.show() diff --git a/examples/gallery/plot/points-transparency.py b/examples/gallery/plot/points-transparency.py index f6b0d5bef04..70f96f64e72 100644 --- a/examples/gallery/plot/points-transparency.py +++ b/examples/gallery/plot/points-transparency.py @@ -1,25 +1,25 @@ -""" -Points with varying transparency --------------------------------- - -Points can be plotted with different transparency levels by passing in an array -argument to the ``transparency`` parameter of :meth:`pygmt.Figure.plot`. -""" - -import numpy as np -import pygmt - -# prepare the input x and y data -x = np.arange(0, 105, 5) -y = np.ones(x.size) -# transparency level in percentage from 0 (i.e., opaque) to 100 -transparency = x - -fig = pygmt.Figure() -fig.basemap( - region=[-5, 105, 0, 2], - frame=['xaf+l"Transparency level"+u%', "WSrt"], - projection="X15c/6c", -) -fig.plot(x=x, y=y, style="c0.6c", color="blue", pen="1p,red", transparency=transparency) -fig.show() +""" +Points with varying transparency +-------------------------------- + +Points can be plotted with different transparency levels by passing in an array +argument to the ``transparency`` parameter of :meth:`pygmt.Figure.plot`. +""" + +import numpy as np +import pygmt + +# prepare the input x and y data +x = np.arange(0, 105, 5) +y = np.ones(x.size) +# transparency level in percentage from 0 (i.e., opaque) to 100 +transparency = x + +fig = pygmt.Figure() +fig.basemap( + region=[-5, 105, 0, 2], + frame=['xaf+l"Transparency level"+u%', "WSrt"], + projection="X15c/6c", +) +fig.plot(x=x, y=y, style="c0.6c", color="blue", pen="1p,red", transparency=transparency) +fig.show() diff --git a/examples/gallery/plot/points.py b/examples/gallery/plot/points.py index be159269414..5fc007b9b8c 100644 --- a/examples/gallery/plot/points.py +++ b/examples/gallery/plot/points.py @@ -1,22 +1,22 @@ -""" -Points ------- - -The :meth:`pygmt.Figure.plot` method can plot points. The plot symbol and size -is set with the ``style`` parameter. -""" -import numpy as np -import pygmt - -# Generate a random set of points to plot -np.random.seed(42) -region = [150, 240, -10, 60] -x = np.random.uniform(region[0], region[1], 100) -y = np.random.uniform(region[2], region[3], 100) - -fig = pygmt.Figure() -# Create a 15x15 cm basemap with a Cartesian projection (X) using the data region -fig.basemap(region=region, projection="X15c", frame=True) -# Plot using triangles (i) of 0.5 cm -fig.plot(x, y, style="i0.5c", color="black") -fig.show() +""" +Points +------ + +The :meth:`pygmt.Figure.plot` method can plot points. The plot symbol and size +is set with the ``style`` parameter. +""" +import numpy as np +import pygmt + +# Generate a random set of points to plot +np.random.seed(42) +region = [150, 240, -10, 60] +x = np.random.uniform(region[0], region[1], 100) +y = np.random.uniform(region[2], region[3], 100) + +fig = pygmt.Figure() +# Create a 15x15 cm basemap with a Cartesian projection (X) using the data region +fig.basemap(region=region, projection="X15c", frame=True) +# Plot using triangles (i) of 0.5 cm +fig.plot(x, y, style="i0.5c", color="black") +fig.show() diff --git a/examples/gallery/plot/scatter3d.py b/examples/gallery/plot/scatter3d.py index a573005cc88..54a8cd4138c 100644 --- a/examples/gallery/plot/scatter3d.py +++ b/examples/gallery/plot/scatter3d.py @@ -1,53 +1,53 @@ -""" -3D Scatter plots ----------------- - -The :meth:`pygmt.Figure.plot3d` method can be used to plot symbols in 3D. -In the example below, we show how the -`Iris flower dataset `__ -can be visualized using a perspective 3-dimensional plot. The ``region`` -parameter has to include the :math:`x`, :math:`y`, :math:`z` axis limits in the -form of (xmin, xmax, ymin, ymax, zmin, zmax), which can be done automatically -using :meth:`pygmt.info`. To plot the z-axis frame, set ``frame`` as a -minimum to something like ``frame=["WsNeZ", "zaf"]``. Use ``perspective`` to -control the azimuth and elevation angle of the view, and ``zscale`` to adjust -the vertical exaggeration factor. -""" - -import pandas as pd -import pygmt - -# Load sample iris data, and convert 'species' column to categorical dtype -df = pd.read_csv("https://github.com/mwaskom/seaborn-data/raw/master/iris.csv") -df["species"] = df.species.astype(dtype="category") - -# Use pygmt.info to get region bounds (xmin, xmax, ymin, ymax, zmin, zmax) -# The below example will return a numpy array like [0., 3., 4., 8., 1., 7.] -region = pygmt.info( - table=df[["petal_width", "sepal_length", "petal_length"]], # x, y, z columns - per_column=True, # report output as a numpy array - spacing="1/2/0.5", # rounds x, y and z intervals by 1, 2 and 0.5 respectively -) - -# Make our 3D scatter plot, coloring each of the 3 species differently -fig = pygmt.Figure() -pygmt.makecpt(cmap="cubhelix", color_model="+c", series=(0, 3, 1)) -fig.plot3d( - x=df.petal_width, - y=df.sepal_length, - z=df.petal_length, - sizes=0.1 * df.sepal_width, # Vary each symbol size according to a data column - color=df.species.cat.codes.astype(int), # Points colored by categorical number code - cmap=True, # Use colormap created by makecpt - region=region, # (xmin, xmax, ymin, ymax, zmin, zmax) - frame=[ - "WsNeZ3", # z axis label positioned on 3rd corner - 'xafg+l"Petal Width"', - 'yafg+l"Sepal Length"', - 'zafg+l"Petal Length"', - ], - style="uc", # 3D cUbe, with size in centimeter units - perspective=[315, 25], # Azimuth NorthWest (315°), at elevation 25° - zscale=1.5, # Vertical exaggeration factor -) -fig.show() +""" +3D Scatter plots +---------------- + +The :meth:`pygmt.Figure.plot3d` method can be used to plot symbols in 3D. +In the example below, we show how the +`Iris flower dataset `__ +can be visualized using a perspective 3-dimensional plot. The ``region`` +parameter has to include the :math:`x`, :math:`y`, :math:`z` axis limits in the +form of (xmin, xmax, ymin, ymax, zmin, zmax), which can be done automatically +using :meth:`pygmt.info`. To plot the z-axis frame, set ``frame`` as a +minimum to something like ``frame=["WsNeZ", "zaf"]``. Use ``perspective`` to +control the azimuth and elevation angle of the view, and ``zscale`` to adjust +the vertical exaggeration factor. +""" + +import pandas as pd +import pygmt + +# Load sample iris data, and convert 'species' column to categorical dtype +df = pd.read_csv("https://github.com/mwaskom/seaborn-data/raw/master/iris.csv") +df["species"] = df.species.astype(dtype="category") + +# Use pygmt.info to get region bounds (xmin, xmax, ymin, ymax, zmin, zmax) +# The below example will return a numpy array like [0., 3., 4., 8., 1., 7.] +region = pygmt.info( + table=df[["petal_width", "sepal_length", "petal_length"]], # x, y, z columns + per_column=True, # report output as a numpy array + spacing="1/2/0.5", # rounds x, y and z intervals by 1, 2 and 0.5 respectively +) + +# Make our 3D scatter plot, coloring each of the 3 species differently +fig = pygmt.Figure() +pygmt.makecpt(cmap="cubhelix", color_model="+c", series=(0, 3, 1)) +fig.plot3d( + x=df.petal_width, + y=df.sepal_length, + z=df.petal_length, + sizes=0.1 * df.sepal_width, # Vary each symbol size according to a data column + color=df.species.cat.codes.astype(int), # Points colored by categorical number code + cmap=True, # Use colormap created by makecpt + region=region, # (xmin, xmax, ymin, ymax, zmin, zmax) + frame=[ + "WsNeZ3", # z axis label positioned on 3rd corner + 'xafg+l"Petal Width"', + 'yafg+l"Sepal Length"', + 'zafg+l"Petal Length"', + ], + style="uc", # 3D cUbe, with size in centimeter units + perspective=[315, 25], # Azimuth NorthWest (315°), at elevation 25° + zscale=1.5, # Vertical exaggeration factor +) +fig.show() diff --git a/examples/projections/README.txt b/examples/projections/README.txt index 52797009bf8..bc11fd38228 100644 --- a/examples/projections/README.txt +++ b/examples/projections/README.txt @@ -1,10 +1,10 @@ -Projections -=========== - -PyGMT support many map projections. Use the ``projection`` parameter to specify which -one you want to use in all plotting modules. The projection is specified by a one -letter code along with (sometimes optional) reference longitude and latitude and the -width of the map (for example, **A**\ *lon0/lat0*\ [*/horizon*\ ]\ */width*). The map -height is determined based on the region and projection. - -These are all the available projections: +Projections +=========== + +PyGMT support many map projections. Use the ``projection`` parameter to specify which +one you want to use in all plotting modules. The projection is specified by a one +letter code along with (sometimes optional) reference longitude and latitude and the +width of the map (for example, **A**\ *lon0/lat0*\ [*/horizon*\ ]\ */width*). The map +height is determined based on the region and projection. + +These are all the available projections: diff --git a/examples/projections/azim/azim_equidistant.py b/examples/projections/azim/azim_equidistant.py index adb4b24e5e2..15e69aec13b 100644 --- a/examples/projections/azim/azim_equidistant.py +++ b/examples/projections/azim/azim_equidistant.py @@ -1,23 +1,23 @@ -r""" -Azimuthal Equidistant -===================== - -The main advantage of this projection is that distances from the projection -center are displayed in correct proportions. Also directions measured from the -projection center are correct. It is very useful for a global view on locations -that lie within a certain distance or for comparing distances of different -locations relative to the projection center. - -**e**\ *lon0/lat0*\ [*/horizon*]\ */scale* or -**E**\ *lon0/lat0*\ [*/horizon*]\ */width* - -The projection type is set with **e** or **E**. *lon0/lat0* specifies the projection -center, and the optional parameter *horizon* specifies the maximum distance to the -projection center (i.e. the visibile portion of the rest of the world map) in -degrees <= 180° (default 180°). The size of the figure is set by *scale* or *width*. -""" -import pygmt - -fig = pygmt.Figure() -fig.coast(projection="E-100/40/15c", region="g", frame="g", land="gray") -fig.show() +r""" +Azimuthal Equidistant +===================== + +The main advantage of this projection is that distances from the projection +center are displayed in correct proportions. Also directions measured from the +projection center are correct. It is very useful for a global view on locations +that lie within a certain distance or for comparing distances of different +locations relative to the projection center. + +**e**\ *lon0/lat0*\ [*/horizon*]\ */scale* or +**E**\ *lon0/lat0*\ [*/horizon*]\ */width* + +The projection type is set with **e** or **E**. *lon0/lat0* specifies the projection +center, and the optional parameter *horizon* specifies the maximum distance to the +projection center (i.e. the visibile portion of the rest of the world map) in +degrees <= 180° (default 180°). The size of the figure is set by *scale* or *width*. +""" +import pygmt + +fig = pygmt.Figure() +fig.coast(projection="E-100/40/15c", region="g", frame="g", land="gray") +fig.show() diff --git a/examples/projections/azim/azim_general_perspective.py b/examples/projections/azim/azim_general_perspective.py index 341568f9e72..35ac334b388 100644 --- a/examples/projections/azim/azim_general_perspective.py +++ b/examples/projections/azim/azim_general_perspective.py @@ -1,36 +1,36 @@ -r""" -General Perspective -=================== - -The general perspective projection imitates the view of the Earth from a finite -point in space. In a full view of the earth one third of its surface area can -be seen. - -**g**\ *lon0/lat0*\ */altitude*\ */azimuth*\ */tilt*\ */twist*\ */Width*\ */Height*\ */scale* -or **G**\ *lon0/lat0*\ */altitude*\ */azimuth*\ */tilt*\ */twist*\ */Width*\ */Height*\ */width* - -The projection type is set with **g** or **G**. -*lon0/lat0* specifies the projection center and *altitude* sets the height -in km of the viewpoint above local sea level (If altitude is less than 10, -then it is the distance from the center of the earth to the viewpoint in earth -radii). With *azimuth* the direction (in degrees) in which you are looking is -specified, measured clockwise from north. *tilt* is given in degrees and is the -viewing angle relative to zenith. A tilt of 0° is looking straight down, 60° is -looking 30° above horizon. *twist* is the clockwise rotation of the image (in -degrees). *Width* and *Height* describe the viewport angle in degrees, and *scale* -or *width* determine the size of the figure. - -The example shows the coast of Northern Europe viewed from 250 km above sea -level looking 30° from north at a tilt of 45°. The height and width of the -viewing angle is both 60°, which imitates viewing with naked eye. -""" -import pygmt - -fig = pygmt.Figure() -fig.coast( - projection="G4/52/250/30/45/0/60/60/12c", - region="g", - frame=["x10g10", "y5g5"], - land="gray", -) -fig.show() +r""" +General Perspective +=================== + +The general perspective projection imitates the view of the Earth from a finite +point in space. In a full view of the earth one third of its surface area can +be seen. + +**g**\ *lon0/lat0*\ */altitude*\ */azimuth*\ */tilt*\ */twist*\ */Width*\ */Height*\ */scale* +or **G**\ *lon0/lat0*\ */altitude*\ */azimuth*\ */tilt*\ */twist*\ */Width*\ */Height*\ */width* + +The projection type is set with **g** or **G**. +*lon0/lat0* specifies the projection center and *altitude* sets the height +in km of the viewpoint above local sea level (If altitude is less than 10, +then it is the distance from the center of the earth to the viewpoint in earth +radii). With *azimuth* the direction (in degrees) in which you are looking is +specified, measured clockwise from north. *tilt* is given in degrees and is the +viewing angle relative to zenith. A tilt of 0° is looking straight down, 60° is +looking 30° above horizon. *twist* is the clockwise rotation of the image (in +degrees). *Width* and *Height* describe the viewport angle in degrees, and *scale* +or *width* determine the size of the figure. + +The example shows the coast of Northern Europe viewed from 250 km above sea +level looking 30° from north at a tilt of 45°. The height and width of the +viewing angle is both 60°, which imitates viewing with naked eye. +""" +import pygmt + +fig = pygmt.Figure() +fig.coast( + projection="G4/52/250/30/45/0/60/60/12c", + region="g", + frame=["x10g10", "y5g5"], + land="gray", +) +fig.show() diff --git a/examples/projections/azim/azim_general_stereographic.py b/examples/projections/azim/azim_general_stereographic.py index e66aeacb2c1..0c82bf69d84 100644 --- a/examples/projections/azim/azim_general_stereographic.py +++ b/examples/projections/azim/azim_general_stereographic.py @@ -1,24 +1,24 @@ -r""" -General Stereographic -===================== - -This map projection is a conformal, azimuthal projection. It is mainly used with -a projection center in one of the poles. Then meridians appear as straight lines -and cross latitudes at a right angle. Unlike the azimuthal equidistant projection, -the distances in this projection are not displayed in correct proportions. -It is often used as a hemisphere map like the Lambert Azimuthal Equal Area -projection. - -**s**\ *lon0/lat0*\ [*/horizon*]\ */scale* -or **S**\ *lon0/lat0*\ [*/horizon*\]\ */width* - -The projection type is set with **s** or **S**. *lon0/lat0* specifies the -projection center, the optional *horizon* parameter specifies the maximum distance from -projection center (in degrees, < 180, default 90), and the *scale* or *width* sets the -size of the figure. -""" -import pygmt - -fig = pygmt.Figure() -fig.coast(region=[4, 14, 52, 57], projection="S0/90/12c", frame="ag", land="gray") -fig.show() +r""" +General Stereographic +===================== + +This map projection is a conformal, azimuthal projection. It is mainly used with +a projection center in one of the poles. Then meridians appear as straight lines +and cross latitudes at a right angle. Unlike the azimuthal equidistant projection, +the distances in this projection are not displayed in correct proportions. +It is often used as a hemisphere map like the Lambert Azimuthal Equal Area +projection. + +**s**\ *lon0/lat0*\ [*/horizon*]\ */scale* +or **S**\ *lon0/lat0*\ [*/horizon*\]\ */width* + +The projection type is set with **s** or **S**. *lon0/lat0* specifies the +projection center, the optional *horizon* parameter specifies the maximum distance from +projection center (in degrees, < 180, default 90), and the *scale* or *width* sets the +size of the figure. +""" +import pygmt + +fig = pygmt.Figure() +fig.coast(region=[4, 14, 52, 57], projection="S0/90/12c", frame="ag", land="gray") +fig.show() diff --git a/examples/projections/azim/azim_gnomonic.py b/examples/projections/azim/azim_gnomonic.py index 7f781f48a60..153ef58a6e5 100644 --- a/examples/projections/azim/azim_gnomonic.py +++ b/examples/projections/azim/azim_gnomonic.py @@ -1,25 +1,25 @@ -r""" -Gnomonic -======== - -The point of perspective of the gnomonic projection lies at the center of the -earth. As a consequence great circles (orthodromes) on the surface of the Earth -are displayed as straight lines, which makes it suitable for distance estimation -for navigational purposes. It is neither conformal nor equal-area and the -distortion increases greatly with distance to the projection center. It follows -that the scope of application is restricted to a small area around the -projection center (at a maximum of 60°). - -**f**\ *lon0/lat0*\ [*/horizon*\ ]\ */scale* -or **F**\ *lon0/lat0*\ [*/horizon*\ ]\ */width* - -**f** or **F** specifies the projection type, *lon0/lat0* specifies the projection -center, the optional parameter *horizon* specifies the maximum distance from projection -center (in degrees, < 90, default 60), and *scale* or *width* sets the size of the -figure. -""" -import pygmt - -fig = pygmt.Figure() -fig.coast(projection="F-90/15/12c", region="g", frame="20g20", land="gray") -fig.show() +r""" +Gnomonic +======== + +The point of perspective of the gnomonic projection lies at the center of the +earth. As a consequence great circles (orthodromes) on the surface of the Earth +are displayed as straight lines, which makes it suitable for distance estimation +for navigational purposes. It is neither conformal nor equal-area and the +distortion increases greatly with distance to the projection center. It follows +that the scope of application is restricted to a small area around the +projection center (at a maximum of 60°). + +**f**\ *lon0/lat0*\ [*/horizon*\ ]\ */scale* +or **F**\ *lon0/lat0*\ [*/horizon*\ ]\ */width* + +**f** or **F** specifies the projection type, *lon0/lat0* specifies the projection +center, the optional parameter *horizon* specifies the maximum distance from projection +center (in degrees, < 90, default 60), and *scale* or *width* sets the size of the +figure. +""" +import pygmt + +fig = pygmt.Figure() +fig.coast(projection="F-90/15/12c", region="g", frame="20g20", land="gray") +fig.show() diff --git a/examples/projections/azim/azim_lambert.py b/examples/projections/azim/azim_lambert.py index 3ec0424bc51..dcded97aede 100644 --- a/examples/projections/azim/azim_lambert.py +++ b/examples/projections/azim/azim_lambert.py @@ -1,21 +1,21 @@ -r""" -Lambert Azimuthal Equal Area -============================ - -This projection was developed by Johann Heinrich Lambert in 1772 and is typically used -for mapping large regions like continents and hemispheres. It is an azimuthal, -equal-area projection, but is not perspective. Distortion is zero at the center of the -projection, and increases radially away from this point. - -**a**\ *lon0/lat0*\ [*/horizon*\ ]\ */scale* -or **A**\ *lon0/lat0*\ [*/horizon*\ ]\ */width* - -**a** or **A** specifies the projection type, and *lon0/lat0* specifies the projection -center, *horizon* specifies the maximum distance from projection center (in degrees, -<= 180, default 90), and *scale* or *width* sets the size of the figure. -""" -import pygmt - -fig = pygmt.Figure() -fig.coast(region="g", frame="afg", land="gray", projection="A30/-20/60/12c") -fig.show() +r""" +Lambert Azimuthal Equal Area +============================ + +This projection was developed by Johann Heinrich Lambert in 1772 and is typically used +for mapping large regions like continents and hemispheres. It is an azimuthal, +equal-area projection, but is not perspective. Distortion is zero at the center of the +projection, and increases radially away from this point. + +**a**\ *lon0/lat0*\ [*/horizon*\ ]\ */scale* +or **A**\ *lon0/lat0*\ [*/horizon*\ ]\ */width* + +**a** or **A** specifies the projection type, and *lon0/lat0* specifies the projection +center, *horizon* specifies the maximum distance from projection center (in degrees, +<= 180, default 90), and *scale* or *width* sets the size of the figure. +""" +import pygmt + +fig = pygmt.Figure() +fig.coast(region="g", frame="afg", land="gray", projection="A30/-20/60/12c") +fig.show() diff --git a/examples/projections/azim/azim_orthographic.py b/examples/projections/azim/azim_orthographic.py index a9c9d5558d7..bc1cc93eb5d 100644 --- a/examples/projections/azim/azim_orthographic.py +++ b/examples/projections/azim/azim_orthographic.py @@ -1,22 +1,22 @@ -r""" -Orthographic -============ - -This is a perspective projection like the general perspective, -but with the difference that the point of perspective lies in infinite distance. -It is therefore often used to give the appearance of a globe viewed from outer -space, were one hemisphere can be seen as a whole. It is neither conformal nor -equal-area and the distortion increases near the edges. - -**g**\ *lon0/lat0*\ [*/horizon*\ ]\ */scale* -or **G**\ *lon0/lat0*\ [*/horizon*\ ]\ */width* - -**g** or **G** specifies the projection type, *lon0/lat0* specifies the projection -center, the optional parameter *horizon* specifies the maximum distance from projection -center (in degrees, <= 90, default 90), and *scale* and *width* set the figure size. -""" -import pygmt - -fig = pygmt.Figure() -fig.coast(projection="G10/52/12c", region="g", frame="g", land="gray") -fig.show() +r""" +Orthographic +============ + +This is a perspective projection like the general perspective, +but with the difference that the point of perspective lies in infinite distance. +It is therefore often used to give the appearance of a globe viewed from outer +space, were one hemisphere can be seen as a whole. It is neither conformal nor +equal-area and the distortion increases near the edges. + +**g**\ *lon0/lat0*\ [*/horizon*\ ]\ */scale* +or **G**\ *lon0/lat0*\ [*/horizon*\ ]\ */width* + +**g** or **G** specifies the projection type, *lon0/lat0* specifies the projection +center, the optional parameter *horizon* specifies the maximum distance from projection +center (in degrees, <= 90, default 90), and *scale* and *width* set the figure size. +""" +import pygmt + +fig = pygmt.Figure() +fig.coast(projection="G10/52/12c", region="g", frame="g", land="gray") +fig.show() diff --git a/examples/projections/conic/conic_albers.py b/examples/projections/conic/conic_albers.py index f64c9c6a2e7..aaa871d480a 100644 --- a/examples/projections/conic/conic_albers.py +++ b/examples/projections/conic/conic_albers.py @@ -1,28 +1,28 @@ -r""" -Albers Conic Equal Area -======================= - -This projection, developed by Heinrich C. Albers in 1805, is predominantly used to map -regions of large east-west extent, in particular the United States. It is a conic, -equal-area projection, in which parallels are unequally spaced arcs of concentric -circles, more closely spaced at the north and south edges of the map. Meridians, on the -other hand, are equally spaced radii about a common center, and cut the parallels at -right angles. Distortion in scale and shape vanishes along the two standard parallels. -Between them, the scale along parallels is too small; beyond them it is too large. -The opposite is true for the scale along meridians. - -**b**\ *lon0/lat0*\ /\ *lat1/lat2*\ */scale* -or **B**\ *lon0/lat0*\ /\ *lat1/lat2*\ */width* - -The projection is set with **b** or **B**. The projection center is set by *lon0/lat0* -and two standard parallels for the map are set with *lat1/lat2*. The figure size is set -with *scale* or *width*. -""" -import pygmt - -fig = pygmt.Figure() -# Use the ISO country code for Brazil and add a padding of 2 degrees (+R2) -fig.coast( - projection="B-55/-15/-25/0/12c", region="BR+R2", frame="afg", land="gray", borders=1 -) -fig.show() +r""" +Albers Conic Equal Area +======================= + +This projection, developed by Heinrich C. Albers in 1805, is predominantly used to map +regions of large east-west extent, in particular the United States. It is a conic, +equal-area projection, in which parallels are unequally spaced arcs of concentric +circles, more closely spaced at the north and south edges of the map. Meridians, on the +other hand, are equally spaced radii about a common center, and cut the parallels at +right angles. Distortion in scale and shape vanishes along the two standard parallels. +Between them, the scale along parallels is too small; beyond them it is too large. +The opposite is true for the scale along meridians. + +**b**\ *lon0/lat0*\ /\ *lat1/lat2*\ */scale* +or **B**\ *lon0/lat0*\ /\ *lat1/lat2*\ */width* + +The projection is set with **b** or **B**. The projection center is set by *lon0/lat0* +and two standard parallels for the map are set with *lat1/lat2*. The figure size is set +with *scale* or *width*. +""" +import pygmt + +fig = pygmt.Figure() +# Use the ISO country code for Brazil and add a padding of 2 degrees (+R2) +fig.coast( + projection="B-55/-15/-25/0/12c", region="BR+R2", frame="afg", land="gray", borders=1 +) +fig.show() diff --git a/examples/projections/conic/conic_equidistant.py b/examples/projections/conic/conic_equidistant.py index 8faef867828..812433af6bf 100644 --- a/examples/projections/conic/conic_equidistant.py +++ b/examples/projections/conic/conic_equidistant.py @@ -1,28 +1,28 @@ -r""" -Equidistant conic -================= - -The equidistant conic projection was described by the Greek philosopher Claudius -Ptolemy about A.D. 150. It is neither conformal or equal-area, but serves as a -compromise between them. The scale is true along all meridians and the -standard parallels. - -**d**\ *lon0/lat0*\ /\ *lat1/lat2*\ */scale* -or **D**\ *lon0/lat0*\ /\ *lat1/lat2*\ */width* - -The projection is set with **d** or **D**. The projection center is set by *lon0/lat0* -and two standard parallels for the map are set with *lat1/lat2*. The figure size is set -with *scale* or *width*. -""" -import pygmt - -fig = pygmt.Figure() -fig.coast( - shorelines="1/0.5p", - region=[-88, -70, 18, 24], - projection="D-79/21/19/23/12c", - land="lightgreen", - water="lightblue", - frame="afg", -) -fig.show() +r""" +Equidistant conic +================= + +The equidistant conic projection was described by the Greek philosopher Claudius +Ptolemy about A.D. 150. It is neither conformal or equal-area, but serves as a +compromise between them. The scale is true along all meridians and the +standard parallels. + +**d**\ *lon0/lat0*\ /\ *lat1/lat2*\ */scale* +or **D**\ *lon0/lat0*\ /\ *lat1/lat2*\ */width* + +The projection is set with **d** or **D**. The projection center is set by *lon0/lat0* +and two standard parallels for the map are set with *lat1/lat2*. The figure size is set +with *scale* or *width*. +""" +import pygmt + +fig = pygmt.Figure() +fig.coast( + shorelines="1/0.5p", + region=[-88, -70, 18, 24], + projection="D-79/21/19/23/12c", + land="lightgreen", + water="lightblue", + frame="afg", +) +fig.show() diff --git a/examples/projections/conic/conic_lambert.py b/examples/projections/conic/conic_lambert.py index 9230591af69..22aeb82e82c 100644 --- a/examples/projections/conic/conic_lambert.py +++ b/examples/projections/conic/conic_lambert.py @@ -1,31 +1,31 @@ -r""" -Lambert Conic Conformal Projection -================================== - -This conic projection was designed by the Alsatian mathematician Johann Heinrich -Lambert (1772) and has been used extensively for mapping of regions with predominantly -east-west orientation, just like the Albers projection. Unlike the Albers projection, -Lambert’s conformal projection is not equal-area. The parallels are arcs of circles -with a common origin, and meridians are the equally spaced radii of these circles. As -with Albers projection, it is only the two standard parallels that are distortion-free. - -**l**\ *lon0/lat0*\ /\ *lat1/lat2*\ */scale* -or **L**\ *lon0/lat0*\ /\ *lat1/lat2*\ */width* - -The projection is set with **l** or **L**. The projection center is set by *lon0/lat0* -and two standard parallels for the map are set with *lat1/lat2*. The figure size is set -with *scale* or *width*. -""" -import pygmt - -fig = pygmt.Figure() -fig.coast( - shorelines="1/0.5p", - region=[-130, -70, 24, 52], - projection="L-100/35/33/45/12c", - land="gray", - borders=["1/thick,black", "2/thin,black"], - frame="afg", -) - -fig.show() +r""" +Lambert Conic Conformal Projection +================================== + +This conic projection was designed by the Alsatian mathematician Johann Heinrich +Lambert (1772) and has been used extensively for mapping of regions with predominantly +east-west orientation, just like the Albers projection. Unlike the Albers projection, +Lambert’s conformal projection is not equal-area. The parallels are arcs of circles +with a common origin, and meridians are the equally spaced radii of these circles. As +with Albers projection, it is only the two standard parallels that are distortion-free. + +**l**\ *lon0/lat0*\ /\ *lat1/lat2*\ */scale* +or **L**\ *lon0/lat0*\ /\ *lat1/lat2*\ */width* + +The projection is set with **l** or **L**. The projection center is set by *lon0/lat0* +and two standard parallels for the map are set with *lat1/lat2*. The figure size is set +with *scale* or *width*. +""" +import pygmt + +fig = pygmt.Figure() +fig.coast( + shorelines="1/0.5p", + region=[-130, -70, 24, 52], + projection="L-100/35/33/45/12c", + land="gray", + borders=["1/thick,black", "2/thin,black"], + frame="afg", +) + +fig.show() diff --git a/examples/projections/conic/polyconic.py b/examples/projections/conic/polyconic.py index c6808e66be7..62687d468bb 100644 --- a/examples/projections/conic/polyconic.py +++ b/examples/projections/conic/polyconic.py @@ -1,38 +1,38 @@ -r""" -Polyconic Projection -==================== - -The polyconic projection, in Europe usually referred to as the American polyconic -projection, was introduced shortly before 1820 by the Swiss-American cartographer -Ferdinand Rodulph Hassler (1770–1843). As head of the Survey of the Coast, he was -looking for a projection that would give the least distortion for mapping the coast of -the United States. The projection acquired its name from the construction of each -parallel, which is achieved by projecting the parallel onto the cone while it is rolled -around the globe, along the central meridian, tangent to that parallel. As a -consequence, the projection involves many cones rather than a single one used in -regular conic projections. - -The polyconic projection is neither equal-area, nor conformal. It is true to scale -without distortion along the central meridian. Each parallel is true to scale as well, -but the meridians are not as they get further away from the central meridian. As a -consequence, no parallel is standard because conformity is lost with the lengthening of -the meridians. - -**poly**\ */scale* or **Poly**\ */width* - -The projection is set with **poly** or **Poly**. The figure size is set -with *scale* or *width*. -""" -import pygmt - -fig = pygmt.Figure() -fig.coast( - shorelines="1/0.5p", - region=[-180, -20, 0, 90], - projection="Poly/12c", - land="gray", - borders="1/thick,black", - frame="afg10", -) - -fig.show() +r""" +Polyconic Projection +==================== + +The polyconic projection, in Europe usually referred to as the American polyconic +projection, was introduced shortly before 1820 by the Swiss-American cartographer +Ferdinand Rodulph Hassler (1770–1843). As head of the Survey of the Coast, he was +looking for a projection that would give the least distortion for mapping the coast of +the United States. The projection acquired its name from the construction of each +parallel, which is achieved by projecting the parallel onto the cone while it is rolled +around the globe, along the central meridian, tangent to that parallel. As a +consequence, the projection involves many cones rather than a single one used in +regular conic projections. + +The polyconic projection is neither equal-area, nor conformal. It is true to scale +without distortion along the central meridian. Each parallel is true to scale as well, +but the meridians are not as they get further away from the central meridian. As a +consequence, no parallel is standard because conformity is lost with the lengthening of +the meridians. + +**poly**\ */scale* or **Poly**\ */width* + +The projection is set with **poly** or **Poly**. The figure size is set +with *scale* or *width*. +""" +import pygmt + +fig = pygmt.Figure() +fig.coast( + shorelines="1/0.5p", + region=[-180, -20, 0, 90], + projection="Poly/12c", + land="gray", + borders="1/thick,black", + frame="afg10", +) + +fig.show() diff --git a/examples/projections/cyl/cyl_cassini.py b/examples/projections/cyl/cyl_cassini.py index 98a18a121bb..7901a85239e 100644 --- a/examples/projections/cyl/cyl_cassini.py +++ b/examples/projections/cyl/cyl_cassini.py @@ -1,24 +1,24 @@ -""" -Cassini Cylindrical -============================ - -This cylindrical projection was developed in 1745 by César-François Cassini de Thury -for the survey of France. It is occasionally called Cassini-Soldner since the latter -provided the more accurate mathematical analysis that led to the development of the -ellipsoidal formulae. The projection is neither conformal nor equal-area, and behaves -as a compromise between the two end-members. The distortion is zero along the central -meridian. It is best suited for mapping regions of north-south extent. The central -meridian, each meridian 90° away, and equator are straight lines; all other meridians -and parallels are complex curves. - -**c**\ *lon0/lat0*\ */scale* or **C**\ *lon0/lat0*\ */width* - -The projection is set with **c** or **C**. The projection center is set by *lon0/lat0*, -and the figure size is set with *scale* or *width*. -""" -import pygmt - -fig = pygmt.Figure() -# Use the ISO code for Madagascar (MG) and pad it by 2 degrees (+R2) -fig.coast(projection="C47/-19/12c", region="MG+R2", frame="afg", land="gray", borders=1) -fig.show() +""" +Cassini Cylindrical +============================ + +This cylindrical projection was developed in 1745 by César-François Cassini de Thury +for the survey of France. It is occasionally called Cassini-Soldner since the latter +provided the more accurate mathematical analysis that led to the development of the +ellipsoidal formulae. The projection is neither conformal nor equal-area, and behaves +as a compromise between the two end-members. The distortion is zero along the central +meridian. It is best suited for mapping regions of north-south extent. The central +meridian, each meridian 90° away, and equator are straight lines; all other meridians +and parallels are complex curves. + +**c**\ *lon0/lat0*\ */scale* or **C**\ *lon0/lat0*\ */width* + +The projection is set with **c** or **C**. The projection center is set by *lon0/lat0*, +and the figure size is set with *scale* or *width*. +""" +import pygmt + +fig = pygmt.Figure() +# Use the ISO code for Madagascar (MG) and pad it by 2 degrees (+R2) +fig.coast(projection="C47/-19/12c", region="MG+R2", frame="afg", land="gray", borders=1) +fig.show() diff --git a/examples/projections/cyl/cyl_equal_area.py b/examples/projections/cyl/cyl_equal_area.py index d2e33563756..5fbe638d239 100644 --- a/examples/projections/cyl/cyl_equal_area.py +++ b/examples/projections/cyl/cyl_equal_area.py @@ -1,25 +1,25 @@ -""" -Cylindrical equal-area -====================== - -This cylindrical projection is actually several projections, depending on what -latitude is selected as the standard parallel. However, they are all equal area and -hence non-conformal. All meridians and parallels are straight lines. - -**y**\ *lon0/lat0*\ */scale* or **Y**\ *lon0/lat0*\ */width* - -The projection is set with **y** or **Y**. The projection center is set by *lon0/lat0*, -and the figure size is set with *scale* or *width*. -""" -import pygmt - -fig = pygmt.Figure() -# Use region "d" to specify global region (-180/180/-90/90) -fig.coast( - region="d", - projection="Y35/30/12c", - water="dodgerblue", - shorelines="thinnest", - frame="afg", -) -fig.show() +""" +Cylindrical equal-area +====================== + +This cylindrical projection is actually several projections, depending on what +latitude is selected as the standard parallel. However, they are all equal area and +hence non-conformal. All meridians and parallels are straight lines. + +**y**\ *lon0/lat0*\ */scale* or **Y**\ *lon0/lat0*\ */width* + +The projection is set with **y** or **Y**. The projection center is set by *lon0/lat0*, +and the figure size is set with *scale* or *width*. +""" +import pygmt + +fig = pygmt.Figure() +# Use region "d" to specify global region (-180/180/-90/90) +fig.coast( + region="d", + projection="Y35/30/12c", + water="dodgerblue", + shorelines="thinnest", + frame="afg", +) +fig.show() diff --git a/examples/projections/cyl/cyl_equidistant.py b/examples/projections/cyl/cyl_equidistant.py index e3b0f2e60ae..090b9f8c0ce 100644 --- a/examples/projections/cyl/cyl_equidistant.py +++ b/examples/projections/cyl/cyl_equidistant.py @@ -1,25 +1,25 @@ -""" -Cylindrical equidistant -======================= - -This simple cylindrical projection is really a linear scaling of longitudes and -latitudes. The most common form is the Plate Carrée projection, where the scaling of -longitudes and latitudes is the same. All meridians and parallels are straight lines. - -**q**\ *scale* or **Q**\ *width* - -The projection is set with **q** or **Q**, and the figure size is set -with *scale* or *width*. -""" -import pygmt - -fig = pygmt.Figure() -# Use region "d" to specify global region (-180/180/-90/90) -fig.coast( - region="d", - projection="Q12c", - land="tan4", - water="lightcyan", - frame="afg", -) -fig.show() +""" +Cylindrical equidistant +======================= + +This simple cylindrical projection is really a linear scaling of longitudes and +latitudes. The most common form is the Plate Carrée projection, where the scaling of +longitudes and latitudes is the same. All meridians and parallels are straight lines. + +**q**\ *scale* or **Q**\ *width* + +The projection is set with **q** or **Q**, and the figure size is set +with *scale* or *width*. +""" +import pygmt + +fig = pygmt.Figure() +# Use region "d" to specify global region (-180/180/-90/90) +fig.coast( + region="d", + projection="Q12c", + land="tan4", + water="lightcyan", + frame="afg", +) +fig.show() diff --git a/examples/projections/cyl/cyl_mercator.py b/examples/projections/cyl/cyl_mercator.py index 7c6cf4337d7..2a5057c1227 100644 --- a/examples/projections/cyl/cyl_mercator.py +++ b/examples/projections/cyl/cyl_mercator.py @@ -1,25 +1,25 @@ -""" -Mercator -======== - -The Mercator projection takes its name from the Flemish cartographer Gheert Cremer, -better known as Gerardus Mercator, who presented it in 1569. The projection is a -cylindrical and conformal, with no distortion along the equator. A major navigational -feature of the projection is that a line of constant azimuth is straight. Such a line -is called a rhumb line or loxodrome. Thus, to sail from one point to another one only -had to connect the points with a straight line, determine the azimuth of the line, and -keep this constant course for the entire voyage. The Mercator projection has been used -extensively for world maps in which the distortion towards the polar regions grows -rather large. - -**m**\ [*lon0[/lat0]*]\ */scale* or **M**\ [*lon0*][*/lat0*]\ */width* - -The projection is set with **m** or **M**. The central meridian is set with the -optional *lon0* and the standard parallel is set with the optional *lat0*. -The figure size is set with *scale* or *width*. -""" -import pygmt - -fig = pygmt.Figure() -fig.coast(region=[0, 360, -80, 80], frame="afg", land="red", projection="M0/0/12c") -fig.show() +""" +Mercator +======== + +The Mercator projection takes its name from the Flemish cartographer Gheert Cremer, +better known as Gerardus Mercator, who presented it in 1569. The projection is a +cylindrical and conformal, with no distortion along the equator. A major navigational +feature of the projection is that a line of constant azimuth is straight. Such a line +is called a rhumb line or loxodrome. Thus, to sail from one point to another one only +had to connect the points with a straight line, determine the azimuth of the line, and +keep this constant course for the entire voyage. The Mercator projection has been used +extensively for world maps in which the distortion towards the polar regions grows +rather large. + +**m**\ [*lon0[/lat0]*]\ */scale* or **M**\ [*lon0*][*/lat0*]\ */width* + +The projection is set with **m** or **M**. The central meridian is set with the +optional *lon0* and the standard parallel is set with the optional *lat0*. +The figure size is set with *scale* or *width*. +""" +import pygmt + +fig = pygmt.Figure() +fig.coast(region=[0, 360, -80, 80], frame="afg", land="red", projection="M0/0/12c") +fig.show() diff --git a/examples/projections/cyl/cyl_miller.py b/examples/projections/cyl/cyl_miller.py index a5196a8e03b..60fb419a94f 100644 --- a/examples/projections/cyl/cyl_miller.py +++ b/examples/projections/cyl/cyl_miller.py @@ -1,28 +1,28 @@ -""" -Miller cylindrical -================== - -This cylindrical projection, presented by Osborn Maitland Miller of the American -Geographic Society in 1942, is neither equal nor conformal. All meridians and -parallels are straight lines. The projection was designed to be a compromise between -Mercator and other cylindrical projections. Specifically, Miller spaced the parallels -by using Mercator’s formula with 0.8 times the actual latitude, thus avoiding the -singular poles; the result was then divided by 0.8. - -**j**\ [*lon0/*]\ */scale* or **J**\ [*lon0/*]\ */width* - -The projection is set with **j** or **J**. The central meridian is set by the -optional *lon0*, and the figure size is set with *scale* or *width*. -""" -import pygmt - -fig = pygmt.Figure() -fig.coast( - region=[-180, 180, -80, 80], - projection="J-65/12c", - land="khaki", - water="azure", - shorelines="thinnest", - frame="afg", -) -fig.show() +""" +Miller cylindrical +================== + +This cylindrical projection, presented by Osborn Maitland Miller of the American +Geographic Society in 1942, is neither equal nor conformal. All meridians and +parallels are straight lines. The projection was designed to be a compromise between +Mercator and other cylindrical projections. Specifically, Miller spaced the parallels +by using Mercator’s formula with 0.8 times the actual latitude, thus avoiding the +singular poles; the result was then divided by 0.8. + +**j**\ [*lon0/*]\ */scale* or **J**\ [*lon0/*]\ */width* + +The projection is set with **j** or **J**. The central meridian is set by the +optional *lon0*, and the figure size is set with *scale* or *width*. +""" +import pygmt + +fig = pygmt.Figure() +fig.coast( + region=[-180, 180, -80, 80], + projection="J-65/12c", + land="khaki", + water="azure", + shorelines="thinnest", + frame="afg", +) +fig.show() diff --git a/examples/projections/cyl/cyl_stereographic.py b/examples/projections/cyl/cyl_stereographic.py index 5fd8e8a6a83..ec4f4186749 100644 --- a/examples/projections/cyl/cyl_stereographic.py +++ b/examples/projections/cyl/cyl_stereographic.py @@ -1,32 +1,32 @@ -""" -Cylindrical Stereographic -========================= - -The cylindrical stereographic projections are certainly not as notable as other -cylindrical projections, but are still used because of their relative simplicity and -their ability to overcome some of the downsides of other cylindrical projections, like -extreme distortions of the higher latitudes. The stereographic projections are -perspective projections, projecting the sphere onto a cylinder in the direction of the -antipodal point on the equator. The cylinder crosses the sphere at two standard -parallels, equidistant from the equator. - -**cyl_stere/**\ [*lon0/*]\ [*lat0/*]\ *scale* -or **Cyl_stere/**\ [*lon0/*]\ [*lat0/*]\ *width* - -The projection is set with **cyl_stere** or **Cyl_stere**. The central meridian is set -by the optional *lon0*, and the figure size is set with *scale* or *width*. - -The standard parallel is typically one of these (but can be any value): - -* 66.159467 - Miller's modified Gall -* 55 - Kamenetskiy's First -* 45 - Gall's Stereographic -* 30 - Bolshoi Sovietskii Atlas Mira or Kamenetskiy's Second -* 0 - Braun's Cylindrical - -""" -import pygmt - -fig = pygmt.Figure() -fig.coast(region="g", frame="afg", land="gray", projection="Cyl_stere/30/-20/12c") -fig.show() +""" +Cylindrical Stereographic +========================= + +The cylindrical stereographic projections are certainly not as notable as other +cylindrical projections, but are still used because of their relative simplicity and +their ability to overcome some of the downsides of other cylindrical projections, like +extreme distortions of the higher latitudes. The stereographic projections are +perspective projections, projecting the sphere onto a cylinder in the direction of the +antipodal point on the equator. The cylinder crosses the sphere at two standard +parallels, equidistant from the equator. + +**cyl_stere/**\ [*lon0/*]\ [*lat0/*]\ *scale* +or **Cyl_stere/**\ [*lon0/*]\ [*lat0/*]\ *width* + +The projection is set with **cyl_stere** or **Cyl_stere**. The central meridian is set +by the optional *lon0*, and the figure size is set with *scale* or *width*. + +The standard parallel is typically one of these (but can be any value): + +* 66.159467 - Miller's modified Gall +* 55 - Kamenetskiy's First +* 45 - Gall's Stereographic +* 30 - Bolshoi Sovietskii Atlas Mira or Kamenetskiy's Second +* 0 - Braun's Cylindrical + +""" +import pygmt + +fig = pygmt.Figure() +fig.coast(region="g", frame="afg", land="gray", projection="Cyl_stere/30/-20/12c") +fig.show() diff --git a/examples/projections/cyl/cyl_transverse_mercator.py b/examples/projections/cyl/cyl_transverse_mercator.py index 50f1648fec2..fcf3d974795 100644 --- a/examples/projections/cyl/cyl_transverse_mercator.py +++ b/examples/projections/cyl/cyl_transverse_mercator.py @@ -1,28 +1,28 @@ -""" -Transverse Mercator -=================== - -The transverse Mercator was invented by Johann Heinrich Lambert in 1772. In this -projection the cylinder touches a meridian along which there is no distortion. The -distortion increases away from the central meridian and goes to infinity at 90° from -center. The central meridian, each meridian 90° away from the center, and equator are -straight lines; other parallels and meridians are complex curves. - -**t**\ *lon0/*\ [*lat0/*\ ]\ *scale* or **T**\ *lon0/*\ [*lat0/*\ ]\ *width* - -The projection is set with **t** or **T**. The central meridian is set -by *lon0*, the latitude of the origin is set by the optional *lat0*, and the figure -size is set with *scale* or *width*. -""" -import pygmt - -fig = pygmt.Figure() -fig.coast( - region=[20, 50, 30, 45], - projection="T35/12c", - land="lightbrown", - water="seashell", - shorelines="thinnest", - frame="afg", -) -fig.show() +""" +Transverse Mercator +=================== + +The transverse Mercator was invented by Johann Heinrich Lambert in 1772. In this +projection the cylinder touches a meridian along which there is no distortion. The +distortion increases away from the central meridian and goes to infinity at 90° from +center. The central meridian, each meridian 90° away from the center, and equator are +straight lines; other parallels and meridians are complex curves. + +**t**\ *lon0/*\ [*lat0/*\ ]\ *scale* or **T**\ *lon0/*\ [*lat0/*\ ]\ *width* + +The projection is set with **t** or **T**. The central meridian is set +by *lon0*, the latitude of the origin is set by the optional *lat0*, and the figure +size is set with *scale* or *width*. +""" +import pygmt + +fig = pygmt.Figure() +fig.coast( + region=[20, 50, 30, 45], + projection="T35/12c", + land="lightbrown", + water="seashell", + shorelines="thinnest", + frame="afg", +) +fig.show() diff --git a/examples/projections/cyl/cyl_universal_transverse_mercator.py b/examples/projections/cyl/cyl_universal_transverse_mercator.py index bc8a8d4ef95..0bf7cf6ce5e 100644 --- a/examples/projections/cyl/cyl_universal_transverse_mercator.py +++ b/examples/projections/cyl/cyl_universal_transverse_mercator.py @@ -1,34 +1,34 @@ -""" -Universal Transverse Mercator -============================= - -A particular subset of the transverse Mercator is the Universal Transverse Mercator -(UTM) which was adopted by the US Army for large-scale military maps. Here, the globe -is divided into 60 zones between 84°S and 84°N, most of which are 6 wide. Each of these -UTM zones have their unique central meridian. Furthermore, each zone is divided into -latitude bands but these are not needed to specify the projection for most cases. - -In order to minimize the distortion in any given zone, a scale factor of 0.9996 has -been factored into the formulae. This makes the UTM projection a secant projection and -not a tangent projection like the transverse Mercator above. The scale only varies by -1 part in 1,000 from true scale at equator. The ellipsoidal projection expressions are -accurate for map areas that extend less than 10 away from the central meridian. - -**u**\ *zone/scale* or **U**\ *zone/width* - -the projection is set with **u** or **U**. *zone* sets the zone for the figure, and -the figure size is set wtih *scale* or *width*. -""" -import pygmt - -fig = pygmt.Figure() -# UTM Zone is set to 52R -fig.coast( - region=[127.5, 128.5, 26, 27], - projection="U52R/12c", - land="lightgreen", - water="lightblue", - shorelines="thinnest", - frame="afg", -) -fig.show() +""" +Universal Transverse Mercator +============================= + +A particular subset of the transverse Mercator is the Universal Transverse Mercator +(UTM) which was adopted by the US Army for large-scale military maps. Here, the globe +is divided into 60 zones between 84°S and 84°N, most of which are 6 wide. Each of these +UTM zones have their unique central meridian. Furthermore, each zone is divided into +latitude bands but these are not needed to specify the projection for most cases. + +In order to minimize the distortion in any given zone, a scale factor of 0.9996 has +been factored into the formulae. This makes the UTM projection a secant projection and +not a tangent projection like the transverse Mercator above. The scale only varies by +1 part in 1,000 from true scale at equator. The ellipsoidal projection expressions are +accurate for map areas that extend less than 10 away from the central meridian. + +**u**\ *zone/scale* or **U**\ *zone/width* + +the projection is set with **u** or **U**. *zone* sets the zone for the figure, and +the figure size is set wtih *scale* or *width*. +""" +import pygmt + +fig = pygmt.Figure() +# UTM Zone is set to 52R +fig.coast( + region=[127.5, 128.5, 26, 27], + projection="U52R/12c", + land="lightgreen", + water="lightblue", + shorelines="thinnest", + frame="afg", +) +fig.show() diff --git a/examples/projections/misc/misc_eckertIV.py b/examples/projections/misc/misc_eckertIV.py index 595fa48a51e..de466b8d1e7 100644 --- a/examples/projections/misc/misc_eckertIV.py +++ b/examples/projections/misc/misc_eckertIV.py @@ -1,20 +1,20 @@ -""" -Eckert IV -========= - -The Eckert IV projection, presented by the German cartographer Max -Eckert-Greiffendorff in 1906, is a pseudo-cylindrical equal-area projection. Central -meridian and all parallels are straight lines; other meridians are equally spaced -elliptical arcs. The scale is true along latitude 40°30’. - -**kf**\ [*lon0/*]\ *scale* or **Kf**\ [*lon0/*]\ *width* - -The projection is set with **kf** or **Kf**. The central meridian is set with the -optional *lon0*, and the figure size is set with *scale* or *width*. -""" -import pygmt - -fig = pygmt.Figure() -# Use region "d" to specify global region (-180/180/-90/90) -fig.coast(region="d", projection="Kf12c", land="ivory", water="bisque4", frame="afg") -fig.show() +""" +Eckert IV +========= + +The Eckert IV projection, presented by the German cartographer Max +Eckert-Greiffendorff in 1906, is a pseudo-cylindrical equal-area projection. Central +meridian and all parallels are straight lines; other meridians are equally spaced +elliptical arcs. The scale is true along latitude 40°30’. + +**kf**\ [*lon0/*]\ *scale* or **Kf**\ [*lon0/*]\ *width* + +The projection is set with **kf** or **Kf**. The central meridian is set with the +optional *lon0*, and the figure size is set with *scale* or *width*. +""" +import pygmt + +fig = pygmt.Figure() +# Use region "d" to specify global region (-180/180/-90/90) +fig.coast(region="d", projection="Kf12c", land="ivory", water="bisque4", frame="afg") +fig.show() diff --git a/examples/projections/misc/misc_eckertVI.py b/examples/projections/misc/misc_eckertVI.py index cb93a153f07..8b48c5f1b74 100644 --- a/examples/projections/misc/misc_eckertVI.py +++ b/examples/projections/misc/misc_eckertVI.py @@ -1,21 +1,21 @@ -""" -Eckert VI -========= - -The Eckert VI projections, presented by the German cartographer -Max Eckert-Greiffendorff in 1906, is a pseudo-cylindrical equal-area projection. -Central meridian and all parallels are straight lines; other meridians are equally -spaced sinusoids. The scale is true along latitude 49°16’. - - -**ks**\ [*lon0/*]\ *scale* or **Ks**\ [*lon0/*]\ *width* - -The projection is set with **ks** or **Ks**. The central meridian is set with the -optional *lon0*, and the figure size is set with *scale* or *width*. -""" -import pygmt - -fig = pygmt.Figure() -# Use region "d" to specify global region (-180/180/-90/90) -fig.coast(region="d", projection="Ks12c", land="ivory", water="bisque4", frame="afg") -fig.show() +""" +Eckert VI +========= + +The Eckert VI projections, presented by the German cartographer +Max Eckert-Greiffendorff in 1906, is a pseudo-cylindrical equal-area projection. +Central meridian and all parallels are straight lines; other meridians are equally +spaced sinusoids. The scale is true along latitude 49°16’. + + +**ks**\ [*lon0/*]\ *scale* or **Ks**\ [*lon0/*]\ *width* + +The projection is set with **ks** or **Ks**. The central meridian is set with the +optional *lon0*, and the figure size is set with *scale* or *width*. +""" +import pygmt + +fig = pygmt.Figure() +# Use region "d" to specify global region (-180/180/-90/90) +fig.coast(region="d", projection="Ks12c", land="ivory", water="bisque4", frame="afg") +fig.show() diff --git a/examples/projections/misc/misc_hammer.py b/examples/projections/misc/misc_hammer.py index 31edadcffda..54e4ecbb4a5 100644 --- a/examples/projections/misc/misc_hammer.py +++ b/examples/projections/misc/misc_hammer.py @@ -1,20 +1,20 @@ -""" -Hammer -====== - -The equal-area Hammer projection, first presented by the German mathematician -Ernst von Hammer in 1892, is also known as Hammer-Aitoff (the Aitoff projection looks -similar, but is not equal-area). The border is an ellipse, equator and central -meridian are straight lines, while other parallels and meridians are complex curves. - -**h**\ [*lon0/*]\ *scale* or **H**\ [*lon0/*]\ *width* - -The projection is set with **h** or **H**. The central meridian is set with the -optional *lon0*, and the figure size is set with *scale* or *width*. -""" -import pygmt - -fig = pygmt.Figure() -# Use region "d" to specify global region (-180/180/-90/90) -fig.coast(region="d", projection="H12c", land="black", water="cornsilk", frame="afg") -fig.show() +""" +Hammer +====== + +The equal-area Hammer projection, first presented by the German mathematician +Ernst von Hammer in 1892, is also known as Hammer-Aitoff (the Aitoff projection looks +similar, but is not equal-area). The border is an ellipse, equator and central +meridian are straight lines, while other parallels and meridians are complex curves. + +**h**\ [*lon0/*]\ *scale* or **H**\ [*lon0/*]\ *width* + +The projection is set with **h** or **H**. The central meridian is set with the +optional *lon0*, and the figure size is set with *scale* or *width*. +""" +import pygmt + +fig = pygmt.Figure() +# Use region "d" to specify global region (-180/180/-90/90) +fig.coast(region="d", projection="H12c", land="black", water="cornsilk", frame="afg") +fig.show() diff --git a/examples/projections/misc/misc_mollweide.py b/examples/projections/misc/misc_mollweide.py index 5fe04dcbd7d..769a6cad9dc 100644 --- a/examples/projections/misc/misc_mollweide.py +++ b/examples/projections/misc/misc_mollweide.py @@ -1,22 +1,22 @@ -""" -Mollweide -========= - -This pseudo-cylindrical, equal-area projection was developed by the German -mathematician and astronomer Karl Brandan Mollweide in 1805. Parallels are unequally -spaced straight lines with the meridians being equally spaced elliptical arcs. The -scale is only true along latitudes 40°44’ north and south. The projection is used -mainly for global maps showing data distributions. It is occasionally referenced under -the name homalographic projection. - -**w**\ [*lon0/*]\ *scale* or **W**\ [*lon0/*]\ *width* - -The projection is set with **w** or **W**. The central meridian is set with the -optional *lon0*, and the figure size is set with *scale* or *width*. -""" -import pygmt - -fig = pygmt.Figure() -# Use region "d" to specify global region (-180/180/-90/90) -fig.coast(region="d", projection="W12c", land="tomato1", water="skyblue", frame="ag") -fig.show() +""" +Mollweide +========= + +This pseudo-cylindrical, equal-area projection was developed by the German +mathematician and astronomer Karl Brandan Mollweide in 1805. Parallels are unequally +spaced straight lines with the meridians being equally spaced elliptical arcs. The +scale is only true along latitudes 40°44’ north and south. The projection is used +mainly for global maps showing data distributions. It is occasionally referenced under +the name homalographic projection. + +**w**\ [*lon0/*]\ *scale* or **W**\ [*lon0/*]\ *width* + +The projection is set with **w** or **W**. The central meridian is set with the +optional *lon0*, and the figure size is set with *scale* or *width*. +""" +import pygmt + +fig = pygmt.Figure() +# Use region "d" to specify global region (-180/180/-90/90) +fig.coast(region="d", projection="W12c", land="tomato1", water="skyblue", frame="ag") +fig.show() diff --git a/examples/projections/misc/misc_robinson.py b/examples/projections/misc/misc_robinson.py index f2c6b0b0136..58499f4e798 100644 --- a/examples/projections/misc/misc_robinson.py +++ b/examples/projections/misc/misc_robinson.py @@ -1,23 +1,23 @@ -""" -Robinson -======== - -The Robinson projection, presented by the American geographer and cartographer -Arthur H. Robinson in 1963, is a modified cylindrical projection that is neither -conformal nor equal-area. Central meridian and all parallels are straight lines; other -meridians are curved. It uses lookup tables rather than analytic expressions to make -the world map “look” right 22. The scale is true along latitudes 38. The projection was -originally developed for use by Rand McNally and is currently used by the -National Geographic Society. - -**n**\ [*lon0/*]\ *scale* or **N**\ [*lon0/*]\ *width* - -The projection is set with **n** or **N**. The central meridian is set with the -optional *lon0*, and the figure size is set with *scale* or *width*. -""" -import pygmt - -fig = pygmt.Figure() -# Use region "d" to specify global region (-180/180/-90/90) -fig.coast(region="d", projection="N12c", land="goldenrod", water="snow2", frame="afg") -fig.show() +""" +Robinson +======== + +The Robinson projection, presented by the American geographer and cartographer +Arthur H. Robinson in 1963, is a modified cylindrical projection that is neither +conformal nor equal-area. Central meridian and all parallels are straight lines; other +meridians are curved. It uses lookup tables rather than analytic expressions to make +the world map “look” right 22. The scale is true along latitudes 38. The projection was +originally developed for use by Rand McNally and is currently used by the +National Geographic Society. + +**n**\ [*lon0/*]\ *scale* or **N**\ [*lon0/*]\ *width* + +The projection is set with **n** or **N**. The central meridian is set with the +optional *lon0*, and the figure size is set with *scale* or *width*. +""" +import pygmt + +fig = pygmt.Figure() +# Use region "d" to specify global region (-180/180/-90/90) +fig.coast(region="d", projection="N12c", land="goldenrod", water="snow2", frame="afg") +fig.show() diff --git a/examples/projections/misc/misc_sinusoidal.py b/examples/projections/misc/misc_sinusoidal.py index 93bac1a9938..6e8291b304a 100644 --- a/examples/projections/misc/misc_sinusoidal.py +++ b/examples/projections/misc/misc_sinusoidal.py @@ -1,21 +1,21 @@ -""" -Sinusoidal -========== - -The sinusoidal projection is one of the oldest known projections, is equal-area, and -has been used since the mid-16th century. It has also been called the -“Equal-area Mercator” projection. The central meridian is a straight line; all other -meridians are sinusoidal curves. Parallels are all equally spaced straight lines, with -scale being true along all parallels (and central meridian). - -**i**\ [*lon0/*]\ *scale* or **I**\ [*lon0/*]\ *width* - -The projection is set with **i** or **I**. The central meridian is set with the -optional *lon0*, and the figure size is set with *scale* or *width*. -""" -import pygmt - -fig = pygmt.Figure() -# Use region "d" to specify global region (-180/180/-90/90) -fig.coast(region="d", projection="I12c", land="coral4", water="azure3", frame="afg") -fig.show() +""" +Sinusoidal +========== + +The sinusoidal projection is one of the oldest known projections, is equal-area, and +has been used since the mid-16th century. It has also been called the +“Equal-area Mercator” projection. The central meridian is a straight line; all other +meridians are sinusoidal curves. Parallels are all equally spaced straight lines, with +scale being true along all parallels (and central meridian). + +**i**\ [*lon0/*]\ *scale* or **I**\ [*lon0/*]\ *width* + +The projection is set with **i** or **I**. The central meridian is set with the +optional *lon0*, and the figure size is set with *scale* or *width*. +""" +import pygmt + +fig = pygmt.Figure() +# Use region "d" to specify global region (-180/180/-90/90) +fig.coast(region="d", projection="I12c", land="coral4", water="azure3", frame="afg") +fig.show() diff --git a/examples/projections/misc/misc_van_der_grinten.py b/examples/projections/misc/misc_van_der_grinten.py index a297b950c39..13abcb9c7ae 100644 --- a/examples/projections/misc/misc_van_der_grinten.py +++ b/examples/projections/misc/misc_van_der_grinten.py @@ -1,20 +1,20 @@ -""" -Van der Grinten -=============== - -The Van der Grinten projection, presented by Alphons J. van der Grinten in 1904, is -neither equal-area nor conformal. Central meridian and Equator are straight lines; -other meridians are arcs of circles. The scale is true along the Equator only. Its -main use is to show the entire world enclosed in a circle. - -**v**\ [*lon0/*]\ *scale* or **V**\ [*lon0/*]\ *width* - -The projection is set with **v** or **V**. The central meridian is set with the -optional *lon0*, and the figure size is set with *scale* or *width*. -""" -import pygmt - -fig = pygmt.Figure() -# Use region "d" to specify global region (-180/180/-90/90) -fig.coast(region="d", projection="V12c", land="gray", water="cornsilk", frame="afg") -fig.show() +""" +Van der Grinten +=============== + +The Van der Grinten projection, presented by Alphons J. van der Grinten in 1904, is +neither equal-area nor conformal. Central meridian and Equator are straight lines; +other meridians are arcs of circles. The scale is true along the Equator only. Its +main use is to show the entire world enclosed in a circle. + +**v**\ [*lon0/*]\ *scale* or **V**\ [*lon0/*]\ *width* + +The projection is set with **v** or **V**. The central meridian is set with the +optional *lon0*, and the figure size is set with *scale* or *width*. +""" +import pygmt + +fig = pygmt.Figure() +# Use region "d" to specify global region (-180/180/-90/90) +fig.coast(region="d", projection="V12c", land="gray", water="cornsilk", frame="afg") +fig.show() diff --git a/examples/projections/misc/misc_winkel_tripel.py b/examples/projections/misc/misc_winkel_tripel.py index adbc238172f..c43322a8647 100644 --- a/examples/projections/misc/misc_winkel_tripel.py +++ b/examples/projections/misc/misc_winkel_tripel.py @@ -1,29 +1,29 @@ -""" -Winkel Tripel -============= - -In 1921, the German mathematician Oswald Winkel a projection that was to strike a -compromise between the properties of three elements (area, angle and distance). The -German word “tripel” refers to this junction of where each of these elements are least -distorted when plotting global maps. The projection was popularized when Bartholomew -and Son started to use it in its world-renowned “The Times Atlas of the World” in the -mid-20th century. In 1998, the National Geographic Society made the Winkel Tripel as -its map projection of choice for global maps. - -Naturally, this projection is neither conformal, nor equal-area. Central meridian and -equator are straight lines; other parallels and meridians are curved. The projection is -obtained by averaging the coordinates of the Equidistant Cylindrical and Aitoff -(not Hammer-Aitoff) projections. The poles map into straight lines 0.4 times the -length of equator. - -**r**\ [*lon0/*]\ *scale* or **R**\ [*lon0/*]\ *width* - -The projection is set with **r** or **R**. The central meridian is set with the -optional *lon0*, and the figure size is set with *scale* or *width*. -""" -import pygmt - -fig = pygmt.Figure() -# Use region "d" to specify global region (-180/180/-90/90) -fig.coast(region="d", projection="R12c", land="burlywood4", water="wheat1", frame="afg") -fig.show() +""" +Winkel Tripel +============= + +In 1921, the German mathematician Oswald Winkel a projection that was to strike a +compromise between the properties of three elements (area, angle and distance). The +German word “tripel” refers to this junction of where each of these elements are least +distorted when plotting global maps. The projection was popularized when Bartholomew +and Son started to use it in its world-renowned “The Times Atlas of the World” in the +mid-20th century. In 1998, the National Geographic Society made the Winkel Tripel as +its map projection of choice for global maps. + +Naturally, this projection is neither conformal, nor equal-area. Central meridian and +equator are straight lines; other parallels and meridians are curved. The projection is +obtained by averaging the coordinates of the Equidistant Cylindrical and Aitoff +(not Hammer-Aitoff) projections. The poles map into straight lines 0.4 times the +length of equator. + +**r**\ [*lon0/*]\ *scale* or **R**\ [*lon0/*]\ *width* + +The projection is set with **r** or **R**. The central meridian is set with the +optional *lon0*, and the figure size is set with *scale* or *width*. +""" +import pygmt + +fig = pygmt.Figure() +# Use region "d" to specify global region (-180/180/-90/90) +fig.coast(region="d", projection="R12c", land="burlywood4", water="wheat1", frame="afg") +fig.show() diff --git a/examples/projections/nongeo/cartesian_linear.py b/examples/projections/nongeo/cartesian_linear.py index dd41ffcf878..def158dc200 100644 --- a/examples/projections/nongeo/cartesian_linear.py +++ b/examples/projections/nongeo/cartesian_linear.py @@ -1,21 +1,21 @@ -""" -Cartesian linear -================ - -**X**\ *width*/[*height*]: Give the *width* of the figure and the optional *height*. -""" -import pygmt - -fig = pygmt.Figure() -fig.plot( - # The x and y parameters determine the coordinates of lines - x=[3, 9, 2], - y=[4, 9, 37], - pen="3p,red", - # ``region`` sets the x and y ranges or the Cartesian figure. - region=[0, 10, 0, 50], - projection="X15c/10c", - # ``WSne`` is passed to ``frame`` to put axis labels only on the left and bottom axes. - frame=["af", "WSne"], -) -fig.show() +""" +Cartesian linear +================ + +**X**\ *width*/[*height*]: Give the *width* of the figure and the optional *height*. +""" +import pygmt + +fig = pygmt.Figure() +fig.plot( + # The x and y parameters determine the coordinates of lines + x=[3, 9, 2], + y=[4, 9, 37], + pen="3p,red", + # ``region`` sets the x and y ranges or the Cartesian figure. + region=[0, 10, 0, 50], + projection="X15c/10c", + # ``WSne`` is passed to ``frame`` to put axis labels only on the left and bottom axes. + frame=["af", "WSne"], +) +fig.show() diff --git a/examples/projections/nongeo/cartesian_logarithmic.py b/examples/projections/nongeo/cartesian_logarithmic.py index bae435cf6d4..91edd5bc946 100644 --- a/examples/projections/nongeo/cartesian_logarithmic.py +++ b/examples/projections/nongeo/cartesian_logarithmic.py @@ -1,37 +1,37 @@ -""" -Cartesian logarithmic -===================== - -**X**\ *width*\ [**l**]/[*height*\ [**l**]]: Give the *width* of the figure and -the optional *height*. The axis or axes with a logarithmic transformation requires -**l** after its size argument. -""" -import numpy as np -import pygmt - -# Create a list of x values 0-100 -xline = np.arange(0, 101) -# Create a list of y-values that are the square root of the x-values -yline = xline ** 0.5 -# Create a list of x values for every 10 in 0-100 -xpoints = np.arange(0, 101, 10) -# Create a list of y-values that are the square root of the x-values -ypoints = xpoints ** 0.5 - -fig = pygmt.Figure() -fig.plot( - region=[1, 100, 0, 10], - # Set a logarithmic transformation on the x-axis - projection="X15cl/10c", - # Set the figures frame, color, and gridlines - frame=["WSne+gbisque", "x2g3", "ya2f1g2"], - x=xline, - y=yline, - # Set the line thickness to *1p*, the color to *blue*, and the style to *dash* - pen="1p,blue,-", -) -# Plot square root values as points on the line -# Style of points is 0.3 cm square, color is *red* with a *black* outline -# Points are not clipped if they go off the figure -fig.plot(x=xpoints, y=ypoints, style="s0.3c", color="red", no_clip=True, pen="black") -fig.show() +""" +Cartesian logarithmic +===================== + +**X**\ *width*\ [**l**]/[*height*\ [**l**]]: Give the *width* of the figure and +the optional *height*. The axis or axes with a logarithmic transformation requires +**l** after its size argument. +""" +import numpy as np +import pygmt + +# Create a list of x values 0-100 +xline = np.arange(0, 101) +# Create a list of y-values that are the square root of the x-values +yline = xline ** 0.5 +# Create a list of x values for every 10 in 0-100 +xpoints = np.arange(0, 101, 10) +# Create a list of y-values that are the square root of the x-values +ypoints = xpoints ** 0.5 + +fig = pygmt.Figure() +fig.plot( + region=[1, 100, 0, 10], + # Set a logarithmic transformation on the x-axis + projection="X15cl/10c", + # Set the figures frame, color, and gridlines + frame=["WSne+gbisque", "x2g3", "ya2f1g2"], + x=xline, + y=yline, + # Set the line thickness to *1p*, the color to *blue*, and the style to *dash* + pen="1p,blue,-", +) +# Plot square root values as points on the line +# Style of points is 0.3 cm square, color is *red* with a *black* outline +# Points are not clipped if they go off the figure +fig.plot(x=xpoints, y=ypoints, style="s0.3c", color="red", no_clip=True, pen="black") +fig.show() diff --git a/examples/projections/nongeo/polar.py b/examples/projections/nongeo/polar.py index 6c139a33f33..995d625fc37 100644 --- a/examples/projections/nongeo/polar.py +++ b/examples/projections/nongeo/polar.py @@ -1,22 +1,22 @@ -""" -Polar -===== - -**P**\ *width*: Give the *width* of the figure. - -""" -import pygmt - -fig = pygmt.Figure() -fig.plot( - # x inputs are the theta values for a polar plot. - x=[180, 120, 270, 60, 0], - # y inputs are the radius values for a polar plot. - y=[15, 35, 15, 35, 15], - pen="2p,blue", - # The region values are theta-min/theta-max/radius-min/radius-max. - region=[0, 360, 0, 40], - projection="P15c", - frame=["afg"], -) -fig.show() +""" +Polar +===== + +**P**\ *width*: Give the *width* of the figure. + +""" +import pygmt + +fig = pygmt.Figure() +fig.plot( + # x inputs are the theta values for a polar plot. + x=[180, 120, 270, 60, 0], + # y inputs are the radius values for a polar plot. + y=[15, 35, 15, 35, 15], + pen="2p,blue", + # The region values are theta-min/theta-max/radius-min/radius-max. + region=[0, 360, 0, 40], + projection="P15c", + frame=["afg"], +) +fig.show() diff --git a/examples/projections/table/README.txt b/examples/projections/table/README.txt index fa905bbb839..316c332e7b7 100644 --- a/examples/projections/table/README.txt +++ b/examples/projections/table/README.txt @@ -1,82 +1,82 @@ -Projection Table ----------------- - -The below table shows the projection codes for the 31 GMT projections. - -.. Substitution definitions: -.. |lon0| replace:: lon\ :sub:`0` -.. |lat0| replace:: lat\ :sub:`0` -.. |lon1| replace:: lon\ :sub:`1` -.. |lat1| replace:: lat\ :sub:`1` -.. |lat2| replace:: lat\ :sub:`2` -.. |lonp| replace:: lon\ :sub:`p` -.. |latp| replace:: lat\ :sub:`p` - -.. list-table:: - :widths: 20 28 - :header-rows: 1 - - * - PyGMT Projection Argument - - Projection Name - * - **A**\ |lon0|/|lat0|\ [/\ *horizon*]/\ *width* - - Lambert azimuthal equal area - * - **B**\ |lon0|/|lat0|/|lat1|/|lat2|/\ *width* - - Albers conic equal area - * - **C**\ |lon0|/|lat0|/\ *width* - - Cassini cylindrical - * - **Cyl_stere/**\ [|lon0|\ [/|lat0|/]]\ *width* - - Cylindrical stereographic - * - **D**\ |lon0|/|lat0|/|lat1|/|lat2|/\ *width* - - Equidistant conic - * - **E**\ |lon0|/|lat0|\ [/\ *horizon*]/\ *width* - - Azimuthal equidistant - * - **F**\ |lon0|/|lat0|\ [/\ *horizon*]/\ *width* - - Azimuthal gnomonic - * - **G**\ |lon0|/|lat0|\ [/\ *horizon*]/\ *width* - - Azimuthal orthographic - * - **G**\ |lon0|/|lat0|/\ *alt*/*azim*/*tilt*/*twist*/*W*/*H*/*width* - - General perspective - * - **H**\ [|lon0|/]\ *width* - - Hammer equal area - * - **I**\ [|lon0|/]\ *width* - - Sinusoidal equal area - * - **J**\ [|lon0|/]\ *width* - - Miller cylindrical - * - **Kf**\ [|lon0|/]\ *width* - - Eckert IV equal area - * - **Ks**\ [|lon0|/]\ *width* - - Eckert VI equal area - * - **L**\ |lon0|/|lat0|/|lat1|/|lat2|/\ *width* - - Lambert conic conformal - * - **M**\ [|lon0|\ [/|lat0|]/]\ *width* - - Mercator cylindrical - * - **N**\ [|lon0|/]\ *width* - - Robinson - * - **Oa**\ |lon0|/|lat0|/\ *azim*/*width*\ [**+v**] - - Oblique Mercator, 1: origin and azim - * - **Ob**\ |lon0|/|lat0|/|lon1|/|lat1|/\ *width*\ [**+v**] - - Oblique Mercator, 2: two points - * - **Oc**\ |lon0|/|lat0|/|lonp|/|latp|/\ *width*\ [**+v**] - - Oblique Mercator, 3: origin and pole - * - **P**\ *width*\ [**+a**]\ [**+f**\ [**e**\|\ **p**\|\ *radius*]][**+r**\ *offset*][**+t**\ *origin*][**+z**\ [**p**\|\ *radius*]] - - Polar [azimuthal] (:math:`\theta, r`) (or cylindrical) - * - **Poly**\ [|lon0|\ [/|lat0|]/]\ *width* - - Polyconic - * - **Q**\ [|lon0|\ [/|lat0|/]]\ *width* - - Equidistant cylindrical - * - **R**\ [|lon0|/]\ *width* - - Winkel Tripel - * - **S**\ |lon0|/|lat0|\ [/\ *horizon*]/\ *width* - - General stereographic - * - **T**\ [|lon0|\ [/|lat0|]/]\ *width* - - Transverse Mercator - * - **U**\ *zone*/*width* - - Universal Transverse Mercator (UTM) - * - **V**\ [|lon0|/]\ *width* - - Van der Grinten - * - **W**\ [|lon0|/]\ *width* - - Mollweide - * - **X**\ *width*\ [**l**\|\ **p**\ *exp*\|\ **T**\|\ **t**][/\ *height*\ [**l**\|\ **p**\ *exp*\|\ **T**\|\ **t**]][**d**] - - Linear, log\ :math:`_{10}`, :math:`x^a-y^b`, and time - * - **Y**\ |lon0|/|lat0|/\ *width* - - Cylindrical equal area +Projection Table +---------------- + +The below table shows the projection codes for the 31 GMT projections. + +.. Substitution definitions: +.. |lon0| replace:: lon\ :sub:`0` +.. |lat0| replace:: lat\ :sub:`0` +.. |lon1| replace:: lon\ :sub:`1` +.. |lat1| replace:: lat\ :sub:`1` +.. |lat2| replace:: lat\ :sub:`2` +.. |lonp| replace:: lon\ :sub:`p` +.. |latp| replace:: lat\ :sub:`p` + +.. list-table:: + :widths: 20 28 + :header-rows: 1 + + * - PyGMT Projection Argument + - Projection Name + * - **A**\ |lon0|/|lat0|\ [/\ *horizon*]/\ *width* + - Lambert azimuthal equal area + * - **B**\ |lon0|/|lat0|/|lat1|/|lat2|/\ *width* + - Albers conic equal area + * - **C**\ |lon0|/|lat0|/\ *width* + - Cassini cylindrical + * - **Cyl_stere/**\ [|lon0|\ [/|lat0|/]]\ *width* + - Cylindrical stereographic + * - **D**\ |lon0|/|lat0|/|lat1|/|lat2|/\ *width* + - Equidistant conic + * - **E**\ |lon0|/|lat0|\ [/\ *horizon*]/\ *width* + - Azimuthal equidistant + * - **F**\ |lon0|/|lat0|\ [/\ *horizon*]/\ *width* + - Azimuthal gnomonic + * - **G**\ |lon0|/|lat0|\ [/\ *horizon*]/\ *width* + - Azimuthal orthographic + * - **G**\ |lon0|/|lat0|/\ *alt*/*azim*/*tilt*/*twist*/*W*/*H*/*width* + - General perspective + * - **H**\ [|lon0|/]\ *width* + - Hammer equal area + * - **I**\ [|lon0|/]\ *width* + - Sinusoidal equal area + * - **J**\ [|lon0|/]\ *width* + - Miller cylindrical + * - **Kf**\ [|lon0|/]\ *width* + - Eckert IV equal area + * - **Ks**\ [|lon0|/]\ *width* + - Eckert VI equal area + * - **L**\ |lon0|/|lat0|/|lat1|/|lat2|/\ *width* + - Lambert conic conformal + * - **M**\ [|lon0|\ [/|lat0|]/]\ *width* + - Mercator cylindrical + * - **N**\ [|lon0|/]\ *width* + - Robinson + * - **Oa**\ |lon0|/|lat0|/\ *azim*/*width*\ [**+v**] + - Oblique Mercator, 1: origin and azim + * - **Ob**\ |lon0|/|lat0|/|lon1|/|lat1|/\ *width*\ [**+v**] + - Oblique Mercator, 2: two points + * - **Oc**\ |lon0|/|lat0|/|lonp|/|latp|/\ *width*\ [**+v**] + - Oblique Mercator, 3: origin and pole + * - **P**\ *width*\ [**+a**]\ [**+f**\ [**e**\|\ **p**\|\ *radius*]][**+r**\ *offset*][**+t**\ *origin*][**+z**\ [**p**\|\ *radius*]] + - Polar [azimuthal] (:math:`\theta, r`) (or cylindrical) + * - **Poly**\ [|lon0|\ [/|lat0|]/]\ *width* + - Polyconic + * - **Q**\ [|lon0|\ [/|lat0|/]]\ *width* + - Equidistant cylindrical + * - **R**\ [|lon0|/]\ *width* + - Winkel Tripel + * - **S**\ |lon0|/|lat0|\ [/\ *horizon*]/\ *width* + - General stereographic + * - **T**\ [|lon0|\ [/|lat0|]/]\ *width* + - Transverse Mercator + * - **U**\ *zone*/*width* + - Universal Transverse Mercator (UTM) + * - **V**\ [|lon0|/]\ *width* + - Van der Grinten + * - **W**\ [|lon0|/]\ *width* + - Mollweide + * - **X**\ *width*\ [**l**\|\ **p**\ *exp*\|\ **T**\|\ **t**][/\ *height*\ [**l**\|\ **p**\ *exp*\|\ **T**\|\ **t**]][**d**] + - Linear, log\ :math:`_{10}`, :math:`x^a-y^b`, and time + * - **Y**\ |lon0|/|lat0|/\ *width* + - Cylindrical equal area diff --git a/examples/tutorials/3d-perspective-image.py b/examples/tutorials/3d-perspective-image.py index 61236cca7be..8d49e896b06 100644 --- a/examples/tutorials/3d-perspective-image.py +++ b/examples/tutorials/3d-perspective-image.py @@ -1,104 +1,104 @@ -""" -Creating a 3D perspective image -=============================== - -Create 3-D perspective image or surface mesh from a grid -using :meth:`pygmt.Figure.grdview`. - -.. note:: - - This tutorial assumes the use of a Python notebook, such as IPython or Jupyter Notebook. - To see the figures while using a Python script instead, use - ``fig.show(method="external")`` to display the figure in the default PDF viewer. - - To save the figure, use ``fig.savefig("figname.pdf")`` where ``"figname.pdf"`` - is the desired name and file extension for the saved figure. -""" -# sphinx_gallery_thumbnail_number = 4 - -import pygmt - -# Load sample earth relief data -grid = pygmt.datasets.load_earth_relief(resolution="10m", region=[-108, -103, 35, 40]) - -######################################################################################## -# The :meth:`pygmt.Figure.grdview` method takes the ``grid`` input. -# The ``perspective`` parameter changes the azimuth and elevation of the viewpoint; the -# default is [180, 90], which is looking directly down on the figure and north is "up". -# The ``zsize`` parameter sets how tall the three-dimensional portion appears. -# -# The default grid surface type is *mesh plot*. - -fig = pygmt.Figure() -fig.grdview( - grid=grid, - # Sets the view azimuth as 130 degrees, and the view elevation as 30 degrees - perspective=[130, 30], - # Sets the x- and y-axis labels, and annotates the west, south, and east axes - frame=["xa", "ya", "WSnE"], - # Sets a Mercator projection on a 15-centimeter figure - projection="M15c", - # Sets the height of the three-dimensional relief at 1.5 centimeters - zsize="1.5c", -) -fig.show() - -######################################################################################## -# The grid surface type can be set with the ``surftype`` parameter. -# The default CPT is *turbo* and can be customized with the ``cmap`` parameter. - -fig = pygmt.Figure() -fig.grdview( - grid=grid, - perspective=[130, 30], - frame=["xa", "yaf", "WSnE"], - projection="M15c", - zsize="1.5c", - # Set the surftype to "surface" - surftype="s", - # Set the CPT to "geo" - cmap="geo", -) -fig.show() - -######################################################################################## -# The ``plane`` parameter sets the elevation and color of a plane that provides a fill -# below the surface relief. - -fig = pygmt.Figure() -fig.grdview( - grid=grid, - perspective=[130, 30], - frame=["xa", "yaf", "WSnE"], - projection="M15c", - zsize="1.5c", - surftype="s", - cmap="geo", - # Set the plane elevation to 1,000 meters and make the fill "gray" - plane="1000+ggray", -) -fig.show() - -######################################################################################## -# The ``perspective`` azimuth can be changed to set the direction that is "up" -# in the figure. The ``contourpen`` parameter sets the pen used to draw contour lines -# on the surface. :meth:`pygmt.Figure.colorbar` can be used to add a color bar to the -# figure. The ``cmap`` parameter does not need to be passed again. To keep the color -# bar's alignment similar to the figure, use ``True`` as the ``perspective`` parameter. - -fig = pygmt.Figure() -fig.grdview( - grid=grid, - # Set the azimuth to -130 (230) degrees and the elevation to 30 degrees - perspective=[-130, 30], - frame=["xaf", "yaf", "WSnE"], - projection="M15c", - zsize="1.5c", - surftype="s", - cmap="geo", - plane="1000+ggrey", - # Set the contour pen thickness to "0.1p" - contourpen="0.1p", -) -fig.colorbar(perspective=True, frame=["a500", "x+lElevation", "y+lm"]) -fig.show() +""" +Creating a 3D perspective image +=============================== + +Create 3-D perspective image or surface mesh from a grid +using :meth:`pygmt.Figure.grdview`. + +.. note:: + + This tutorial assumes the use of a Python notebook, such as IPython or Jupyter Notebook. + To see the figures while using a Python script instead, use + ``fig.show(method="external")`` to display the figure in the default PDF viewer. + + To save the figure, use ``fig.savefig("figname.pdf")`` where ``"figname.pdf"`` + is the desired name and file extension for the saved figure. +""" +# sphinx_gallery_thumbnail_number = 4 + +import pygmt + +# Load sample earth relief data +grid = pygmt.datasets.load_earth_relief(resolution="10m", region=[-108, -103, 35, 40]) + +######################################################################################## +# The :meth:`pygmt.Figure.grdview` method takes the ``grid`` input. +# The ``perspective`` parameter changes the azimuth and elevation of the viewpoint; the +# default is [180, 90], which is looking directly down on the figure and north is "up". +# The ``zsize`` parameter sets how tall the three-dimensional portion appears. +# +# The default grid surface type is *mesh plot*. + +fig = pygmt.Figure() +fig.grdview( + grid=grid, + # Sets the view azimuth as 130 degrees, and the view elevation as 30 degrees + perspective=[130, 30], + # Sets the x- and y-axis labels, and annotates the west, south, and east axes + frame=["xa", "ya", "WSnE"], + # Sets a Mercator projection on a 15-centimeter figure + projection="M15c", + # Sets the height of the three-dimensional relief at 1.5 centimeters + zsize="1.5c", +) +fig.show() + +######################################################################################## +# The grid surface type can be set with the ``surftype`` parameter. +# The default CPT is *turbo* and can be customized with the ``cmap`` parameter. + +fig = pygmt.Figure() +fig.grdview( + grid=grid, + perspective=[130, 30], + frame=["xa", "yaf", "WSnE"], + projection="M15c", + zsize="1.5c", + # Set the surftype to "surface" + surftype="s", + # Set the CPT to "geo" + cmap="geo", +) +fig.show() + +######################################################################################## +# The ``plane`` parameter sets the elevation and color of a plane that provides a fill +# below the surface relief. + +fig = pygmt.Figure() +fig.grdview( + grid=grid, + perspective=[130, 30], + frame=["xa", "yaf", "WSnE"], + projection="M15c", + zsize="1.5c", + surftype="s", + cmap="geo", + # Set the plane elevation to 1,000 meters and make the fill "gray" + plane="1000+ggray", +) +fig.show() + +######################################################################################## +# The ``perspective`` azimuth can be changed to set the direction that is "up" +# in the figure. The ``contourpen`` parameter sets the pen used to draw contour lines +# on the surface. :meth:`pygmt.Figure.colorbar` can be used to add a color bar to the +# figure. The ``cmap`` parameter does not need to be passed again. To keep the color +# bar's alignment similar to the figure, use ``True`` as the ``perspective`` parameter. + +fig = pygmt.Figure() +fig.grdview( + grid=grid, + # Set the azimuth to -130 (230) degrees and the elevation to 30 degrees + perspective=[-130, 30], + frame=["xaf", "yaf", "WSnE"], + projection="M15c", + zsize="1.5c", + surftype="s", + cmap="geo", + plane="1000+ggrey", + # Set the contour pen thickness to "0.1p" + contourpen="0.1p", +) +fig.colorbar(perspective=True, frame=["a500", "x+lElevation", "y+lm"]) +fig.show() diff --git a/examples/tutorials/README.txt b/examples/tutorials/README.txt index 0c7e28c3b35..fac42503601 100644 --- a/examples/tutorials/README.txt +++ b/examples/tutorials/README.txt @@ -1,2 +1,2 @@ -Tutorials -========= +Tutorials +========= diff --git a/examples/tutorials/coastlines.py b/examples/tutorials/coastlines.py index e5043c7b3fc..be5c5ff87a4 100644 --- a/examples/tutorials/coastlines.py +++ b/examples/tutorials/coastlines.py @@ -1,88 +1,88 @@ -""" -Coastlines and borders -====================== - -Plotting coastlines and borders is handled by :meth:`pygmt.Figure.coast`. - -.. note:: - - This tutorial assumes the use of a Python notebook, such as IPython or Jupyter Notebook. - To see the figures while using a Python script instead, use - ``fig.show(method="external")`` to display the figure in the default PDF viewer. - - To save the figure, use ``fig.savefig("figname.pdf")`` where ``"figname.pdf"`` - is the desired name and file extension for the saved figure. -""" -# sphinx_gallery_thumbnail_number = 5 - -import pygmt - -######################################################################################## -# Shorelines -# ---------- -# -# Use the ``shorelines`` argument to plot only the shorelines: - -fig = pygmt.Figure() -fig.basemap(region="g", projection="W15c", frame=True) -fig.coast(shorelines=True) -fig.show() - -######################################################################################## -# The shorelines are divided in 4 levels: -# -# 1. coastline -# 2. lakeshore -# 3. island-in-lake shore -# 4. lake-in-island-in-lake shore -# -# You can specify which level you want to plot by passing the level number and a GMT pen -# configuration. For example, to plot just the coastlines with 0.5 thickness and black -# lines: - -fig = pygmt.Figure() -fig.basemap(region="g", projection="W15c", frame=True) -fig.coast(shorelines="1/0.5p,black") -fig.show() - -######################################################################################## -# You can specify multiple levels (with their own pens) by passing a list to -# ``shorelines``: - -fig = pygmt.Figure() -fig.basemap(region="g", projection="W15c", frame=True) -fig.coast(shorelines=["1/1p,black", "2/0.5p,red"]) -fig.show() - -######################################################################################## -# Resolutions -# ----------- -# -# The coastline database comes with 5 resolutions. The resolution drops by 80% between -# levels: -# -# 1. ``"c"``: crude -# 2. ``"l"``: low (default) -# 3. ``"i"``: intermediate -# 4. ``"h"``: high -# 5. ``"f"``: full - -oahu = [-158.3, -157.6, 21.2, 21.8] -fig = pygmt.Figure() -for res in ["c", "l", "i", "h", "f"]: - fig.coast(resolution=res, shorelines="1p", region=oahu, projection="M5c") - fig.shift_origin(xshift="5c") -fig.show() - -######################################################################################## -# Land and water -# -------------- -# -# Use the ``land`` and ``water`` arguments to specify a fill color for land and water -# bodies. The colors can be given by name or hex codes (like the ones used in HTML and -# CSS): - -fig = pygmt.Figure() -fig.basemap(region="g", projection="W10i", frame=True) -fig.coast(land="#666666", water="skyblue") -fig.show() +""" +Coastlines and borders +====================== + +Plotting coastlines and borders is handled by :meth:`pygmt.Figure.coast`. + +.. note:: + + This tutorial assumes the use of a Python notebook, such as IPython or Jupyter Notebook. + To see the figures while using a Python script instead, use + ``fig.show(method="external")`` to display the figure in the default PDF viewer. + + To save the figure, use ``fig.savefig("figname.pdf")`` where ``"figname.pdf"`` + is the desired name and file extension for the saved figure. +""" +# sphinx_gallery_thumbnail_number = 5 + +import pygmt + +######################################################################################## +# Shorelines +# ---------- +# +# Use the ``shorelines`` argument to plot only the shorelines: + +fig = pygmt.Figure() +fig.basemap(region="g", projection="W15c", frame=True) +fig.coast(shorelines=True) +fig.show() + +######################################################################################## +# The shorelines are divided in 4 levels: +# +# 1. coastline +# 2. lakeshore +# 3. island-in-lake shore +# 4. lake-in-island-in-lake shore +# +# You can specify which level you want to plot by passing the level number and a GMT pen +# configuration. For example, to plot just the coastlines with 0.5 thickness and black +# lines: + +fig = pygmt.Figure() +fig.basemap(region="g", projection="W15c", frame=True) +fig.coast(shorelines="1/0.5p,black") +fig.show() + +######################################################################################## +# You can specify multiple levels (with their own pens) by passing a list to +# ``shorelines``: + +fig = pygmt.Figure() +fig.basemap(region="g", projection="W15c", frame=True) +fig.coast(shorelines=["1/1p,black", "2/0.5p,red"]) +fig.show() + +######################################################################################## +# Resolutions +# ----------- +# +# The coastline database comes with 5 resolutions. The resolution drops by 80% between +# levels: +# +# 1. ``"c"``: crude +# 2. ``"l"``: low (default) +# 3. ``"i"``: intermediate +# 4. ``"h"``: high +# 5. ``"f"``: full + +oahu = [-158.3, -157.6, 21.2, 21.8] +fig = pygmt.Figure() +for res in ["c", "l", "i", "h", "f"]: + fig.coast(resolution=res, shorelines="1p", region=oahu, projection="M5c") + fig.shift_origin(xshift="5c") +fig.show() + +######################################################################################## +# Land and water +# -------------- +# +# Use the ``land`` and ``water`` arguments to specify a fill color for land and water +# bodies. The colors can be given by name or hex codes (like the ones used in HTML and +# CSS): + +fig = pygmt.Figure() +fig.basemap(region="g", projection="W10i", frame=True) +fig.coast(land="#666666", water="skyblue") +fig.show() diff --git a/examples/tutorials/configuration.py b/examples/tutorials/configuration.py index acbaa1bd0aa..21d416f1755 100644 --- a/examples/tutorials/configuration.py +++ b/examples/tutorials/configuration.py @@ -1,86 +1,86 @@ -""" -Configuring PyGMT defaults -========================== - -Default GMT parameters can be set globally or locally using :class:`pygmt.config`. - -.. note:: - - This tutorial assumes the use of a Python notebook, such as IPython or Jupyter Notebook. - To see the figures while using a Python script instead, use - ``fig.show(method="external")`` to display the figure in the default PDF viewer. - - To save the figure, use ``fig.savefig("figname.pdf")`` where ``"figname.pdf"`` - is the desired name and file extension for the saved figure. -""" -# sphinx_gallery_thumbnail_number = 3 - -import pygmt - -######################################################################################## -# Configuring default GMT parameters -# ---------------------------------- -# -# Users can override default parameters either temporarily (locally) or permanently -# (globally) using :meth:`pygmt.config`. The full list of default parameters that can be -# changed can be found at :gmt-docs:`gmt.conf.html`. -# -# We demonstrate the usage of :meth:`pygmt.config` by configuring a map plot. - -# Start with a basic figure with the default style -fig = pygmt.Figure() -fig.basemap(region=[115, 119.5, 4, 7.5], projection="M10c", frame=True) -fig.coast(land="black", water="skyblue") - -fig.show() - -######################################################################################## -# Globally overriding defaults -# ---------------------------- -# -# The ``MAP_FRAME_TYPE`` parameter specifies the style of map frame to use, of which there -# are 5 options: ``fancy`` (default, seen above), ``fancy+``, ``plain``, ``graph`` -# (which does not apply to geographical maps) and ``inside``. -# -# The ``FORMAT_GEO_MAP`` parameter controls the format of geographical tick annotations. -# The default uses degrees and minutes. Here we specify the ticks to be a decimal number -# of degrees. - -fig = pygmt.Figure() - -# Configuration for the 'current figure'. -pygmt.config(MAP_FRAME_TYPE="plain") -pygmt.config(FORMAT_GEO_MAP="ddd.xx") - -fig.basemap(region=[115, 119.5, 4, 7.5], projection="M10c", frame=True) -fig.coast(land="black", water="skyblue") - -fig.show() - -######################################################################################## -# Locally overriding defaults -# --------------------------- -# -# It is also possible to temporarily override the default parameters, which is very -# useful for limiting the scope of changes to a particular plot. :class:`pygmt.config` is -# implemented as a context manager, which handles the setup and teardown of a GMT -# session. Python users are likely familiar with the ``with open(...) as file:`` snippet, -# which returns a ``file`` context manager. In this way, it can be used to override a parameter -# for a single command, or a sequence of commands. An application of :class:`pygmt.config` -# as a context manager is shown below: - -fig = pygmt.Figure() - -# This will have a fancy+ frame -with pygmt.config(MAP_FRAME_TYPE="fancy+"): - fig.basemap(region=[115, 119.5, 4, 7.5], projection="M10c", frame=True) -fig.coast(land="black", water="skyblue") - -# Shift plot origin down by 10cm to plot another map -fig.shift_origin(yshift="-10c") - -# This figure retains the default "fancy" frame -fig.basemap(region=[115, 119.5, 4, 7.5], projection="M10c", frame=True) -fig.coast(land="black", water="skyblue") - -fig.show() +""" +Configuring PyGMT defaults +========================== + +Default GMT parameters can be set globally or locally using :class:`pygmt.config`. + +.. note:: + + This tutorial assumes the use of a Python notebook, such as IPython or Jupyter Notebook. + To see the figures while using a Python script instead, use + ``fig.show(method="external")`` to display the figure in the default PDF viewer. + + To save the figure, use ``fig.savefig("figname.pdf")`` where ``"figname.pdf"`` + is the desired name and file extension for the saved figure. +""" +# sphinx_gallery_thumbnail_number = 3 + +import pygmt + +######################################################################################## +# Configuring default GMT parameters +# ---------------------------------- +# +# Users can override default parameters either temporarily (locally) or permanently +# (globally) using :meth:`pygmt.config`. The full list of default parameters that can be +# changed can be found at :gmt-docs:`gmt.conf.html`. +# +# We demonstrate the usage of :meth:`pygmt.config` by configuring a map plot. + +# Start with a basic figure with the default style +fig = pygmt.Figure() +fig.basemap(region=[115, 119.5, 4, 7.5], projection="M10c", frame=True) +fig.coast(land="black", water="skyblue") + +fig.show() + +######################################################################################## +# Globally overriding defaults +# ---------------------------- +# +# The ``MAP_FRAME_TYPE`` parameter specifies the style of map frame to use, of which there +# are 5 options: ``fancy`` (default, seen above), ``fancy+``, ``plain``, ``graph`` +# (which does not apply to geographical maps) and ``inside``. +# +# The ``FORMAT_GEO_MAP`` parameter controls the format of geographical tick annotations. +# The default uses degrees and minutes. Here we specify the ticks to be a decimal number +# of degrees. + +fig = pygmt.Figure() + +# Configuration for the 'current figure'. +pygmt.config(MAP_FRAME_TYPE="plain") +pygmt.config(FORMAT_GEO_MAP="ddd.xx") + +fig.basemap(region=[115, 119.5, 4, 7.5], projection="M10c", frame=True) +fig.coast(land="black", water="skyblue") + +fig.show() + +######################################################################################## +# Locally overriding defaults +# --------------------------- +# +# It is also possible to temporarily override the default parameters, which is very +# useful for limiting the scope of changes to a particular plot. :class:`pygmt.config` is +# implemented as a context manager, which handles the setup and teardown of a GMT +# session. Python users are likely familiar with the ``with open(...) as file:`` snippet, +# which returns a ``file`` context manager. In this way, it can be used to override a parameter +# for a single command, or a sequence of commands. An application of :class:`pygmt.config` +# as a context manager is shown below: + +fig = pygmt.Figure() + +# This will have a fancy+ frame +with pygmt.config(MAP_FRAME_TYPE="fancy+"): + fig.basemap(region=[115, 119.5, 4, 7.5], projection="M10c", frame=True) +fig.coast(land="black", water="skyblue") + +# Shift plot origin down by 10cm to plot another map +fig.shift_origin(yshift="-10c") + +# This figure retains the default "fancy" frame +fig.basemap(region=[115, 119.5, 4, 7.5], projection="M10c", frame=True) +fig.coast(land="black", water="skyblue") + +fig.show() diff --git a/examples/tutorials/contour-map.py b/examples/tutorials/contour-map.py index e13c1aa6efa..bb68db8d9ee 100644 --- a/examples/tutorials/contour-map.py +++ b/examples/tutorials/contour-map.py @@ -1,113 +1,113 @@ -""" -Creating a map with contour lines -================================= - -Plotting a contour map is handled by :meth:`pygmt.Figure.grdcontour`. - -.. note:: - - This tutorial assumes the use of a Python notebook, such as IPython or Jupyter Notebook. - To see the figures while using a Python script instead, use - ``fig.show(method="external")`` to display the figure in the default PDF viewer. - - To save the figure, use ``fig.savefig("figname.pdf")`` where ``"figname.pdf"`` - is the desired name and file extension for the saved figure. -""" -# sphinx_gallery_thumbnail_number = 5 - -import pygmt - -# Load sample earth relief data -grid = pygmt.datasets.load_earth_relief(resolution="05m", region=[-92.5, -82.5, -3, 7]) - -######################################################################################## -# Create contour plot -# ------------------- -# -# The :meth:`pygmt.Figure.grdcontour` method takes the grid input. -# It plots annotated contour lines, which are thicker and have the -# elevation/depth written on them, and unannotated contour lines. -# In the example below, the default contour line intervals are 500 meters, -# with an annotated contour line every 1000 meters. -# By default, it plots the map with the -# equidistant cylindrical projection and with no frame. - -fig = pygmt.Figure() -fig.grdcontour(grid=grid) -fig.show() - -######################################################################################## -# Contour line settings -# --------------------- -# -# Use the ``annotation`` and ``interval`` arguments to adjust contour line intervals. -# In the example below, there are contour intervals every 250 meters and -# annotated contour lines every 1,000 meters. - -fig = pygmt.Figure() -fig.grdcontour( - annotation=1000, - interval=250, - grid=grid, -) -fig.show() - -######################################################################################## -# Contour limits -# -------------- -# -# The ``limit`` argument sets the minimum and maximum values for the contour lines. -# The argument takes the low and high values, -# and is either a list (as below) or a string ``limit="-4000/-2000"``. - -fig = pygmt.Figure() -fig.grdcontour( - annotation=1000, - interval=250, - grid=grid, - limit=[-4000, -2000], -) -fig.show() - -######################################################################################## -# Map settings -# ------------ -# -# The :meth:`pygmt.Figure.grdcontour` method accepts additional arguments, -# including setting the projection and frame. - -fig = pygmt.Figure() -fig.grdcontour( - annotation=1000, - interval=250, - grid=grid, - limit=[-4000, -2000], - projection="M10c", - frame=True, -) -fig.show() - -######################################################################################## -# Adding a colormap -# ----------------- -# -# The :meth:`pygmt.Figure.grdimage` method can be used to add a -# colormap to the contour map. It must be called prior to -# :meth:`pygmt.Figure.grdcontour` to keep the contour lines visible on the final map. -# If the ``projection`` argument is specified in the :meth:`pygmt.Figure.grdimage` -# method, it does not need to be repeated in the :meth:`pygmt.Figure.grdcontour` method. - -fig = pygmt.Figure() -fig.grdimage( - grid=grid, - cmap="haxby", - projection="M10c", - frame=True, -) -fig.grdcontour( - annotation=1000, - interval=250, - grid=grid, - limit=[-4000, -2000], -) -fig.show() +""" +Creating a map with contour lines +================================= + +Plotting a contour map is handled by :meth:`pygmt.Figure.grdcontour`. + +.. note:: + + This tutorial assumes the use of a Python notebook, such as IPython or Jupyter Notebook. + To see the figures while using a Python script instead, use + ``fig.show(method="external")`` to display the figure in the default PDF viewer. + + To save the figure, use ``fig.savefig("figname.pdf")`` where ``"figname.pdf"`` + is the desired name and file extension for the saved figure. +""" +# sphinx_gallery_thumbnail_number = 5 + +import pygmt + +# Load sample earth relief data +grid = pygmt.datasets.load_earth_relief(resolution="05m", region=[-92.5, -82.5, -3, 7]) + +######################################################################################## +# Create contour plot +# ------------------- +# +# The :meth:`pygmt.Figure.grdcontour` method takes the grid input. +# It plots annotated contour lines, which are thicker and have the +# elevation/depth written on them, and unannotated contour lines. +# In the example below, the default contour line intervals are 500 meters, +# with an annotated contour line every 1000 meters. +# By default, it plots the map with the +# equidistant cylindrical projection and with no frame. + +fig = pygmt.Figure() +fig.grdcontour(grid=grid) +fig.show() + +######################################################################################## +# Contour line settings +# --------------------- +# +# Use the ``annotation`` and ``interval`` arguments to adjust contour line intervals. +# In the example below, there are contour intervals every 250 meters and +# annotated contour lines every 1,000 meters. + +fig = pygmt.Figure() +fig.grdcontour( + annotation=1000, + interval=250, + grid=grid, +) +fig.show() + +######################################################################################## +# Contour limits +# -------------- +# +# The ``limit`` argument sets the minimum and maximum values for the contour lines. +# The argument takes the low and high values, +# and is either a list (as below) or a string ``limit="-4000/-2000"``. + +fig = pygmt.Figure() +fig.grdcontour( + annotation=1000, + interval=250, + grid=grid, + limit=[-4000, -2000], +) +fig.show() + +######################################################################################## +# Map settings +# ------------ +# +# The :meth:`pygmt.Figure.grdcontour` method accepts additional arguments, +# including setting the projection and frame. + +fig = pygmt.Figure() +fig.grdcontour( + annotation=1000, + interval=250, + grid=grid, + limit=[-4000, -2000], + projection="M10c", + frame=True, +) +fig.show() + +######################################################################################## +# Adding a colormap +# ----------------- +# +# The :meth:`pygmt.Figure.grdimage` method can be used to add a +# colormap to the contour map. It must be called prior to +# :meth:`pygmt.Figure.grdcontour` to keep the contour lines visible on the final map. +# If the ``projection`` argument is specified in the :meth:`pygmt.Figure.grdimage` +# method, it does not need to be repeated in the :meth:`pygmt.Figure.grdcontour` method. + +fig = pygmt.Figure() +fig.grdimage( + grid=grid, + cmap="haxby", + projection="M10c", + frame=True, +) +fig.grdcontour( + annotation=1000, + interval=250, + grid=grid, + limit=[-4000, -2000], +) +fig.show() diff --git a/examples/tutorials/earth-relief.py b/examples/tutorials/earth-relief.py index f7dc1aaf6b6..999b6311d1f 100644 --- a/examples/tutorials/earth-relief.py +++ b/examples/tutorials/earth-relief.py @@ -1,111 +1,111 @@ -""" -Plotting Earth relief -===================== - -Plotting a map of Earth relief can use the data accessed by the -:meth:`pygmt.datasets.load_earth_relief` method. The data can then be plotted using the -:meth:`pygmt.Figure.grdimage` method. - -.. note:: - - This tutorial assumes the use of a Python notebook, such as IPython or Jupyter Notebook. - To see the figures while using a Python script instead, use - ``fig.show(method="external")`` to display the figure in the default PDF viewer. - - To save the figure, use ``fig.savefig("figname.pdf")`` where ``"figname.pdf"`` - is the desired name and file extension for the saved figure. -""" -# sphinx_gallery_thumbnail_number = 5 - -import pygmt - -######################################################################################## -# Load sample Earth relief data for the entire globe at a resolution of 1 arc degree. -# The other available resolutions are show -# at :gmt-docs:`datasets/remote-data.html#global-earth-relief-grids`. -grid = pygmt.datasets.load_earth_relief(resolution="01d") - -######################################################################################## -# Create a plot -# ------------- -# -# The :meth:`pygmt.Figure.grdimage` method takes the ``grid`` input to -# create a figure. It creates and applies a color palette to the figure based upon the -# z-values of the data. By default, it plots the map with the *turbo* CPT, an -# equidistant cylindrical projection, and with no frame. - -fig = pygmt.Figure() -fig.grdimage(grid=grid) -fig.show() - -######################################################################################## -# -# :meth:`pygmt.Figure.grdimage` can take the optional argument ``projection`` for the -# map. In the example below, the ``projection`` is set as ``R12c`` for 12 centimeter -# figure with a Winkel Tripel projection. For a list of available projections, -# see :gmt-docs:`cookbook/map-projections.html`. - -fig = pygmt.Figure() -fig.grdimage(grid=grid, projection="R12c") -fig.show() - -######################################################################################## -# Set a color map -# --------------- -# -# :meth:`pygmt.Figure.grdimage` takes the ``cmap`` argument to set the CPT of the -# figure. Examples of common CPTs for Earth relief are shown below. -# A full list of CPTs can be found at :gmt-docs:`cookbook/cpts.html`. - -######################################################################################## -# -# Using the *geo* CPT: - -fig = pygmt.Figure() -fig.grdimage(grid=grid, projection="R12c", cmap="geo") -fig.show() - -######################################################################################## -# -# Using the *relief* CPT: - -fig = pygmt.Figure() -fig.grdimage(grid=grid, projection="R12c", cmap="relief") -fig.show() - -######################################################################################## -# Add a color bar -# --------------- -# -# The :meth:`pygmt.Figure.colorbar` method displays the CPT and the associated Z-values -# of the figure, and by default uses the same CPT set by the ``cmap`` argument -# for :meth:`pygmt.Figure.grdimage`. The ``frame`` argument for -# :meth:`pygmt.Figure.colorbar` can be used to set the axis intervals and labels. A -# list is used to pass multiple arguments to ``frame``. In the example below, -# ``a2500`` sets the axis interval to 2,500, ``x+lElevation`` sets the x-axis -# label, and ``y+lm`` sets the y-axis label. - -fig = pygmt.Figure() -fig.grdimage(grid=grid, projection="R12c", cmap="geo") -fig.colorbar(frame=["a2500", "x+lElevation", "y+lm"]) -fig.show() - -######################################################################################## -# Create a region map -# ------------------- -# -# In addition to providing global data, the ``region`` argument for -# :meth:`pygmt.datasets.load_earth_relief` can be used to provide data for a specific -# area. The ``region`` argument is required for resolutions at 5 arc minutes or higher, -# and accepts a list (as in the example below) or a string. The geographic ranges are -# passed as *xmin*/*xmax*/*ymin*/*ymax*. -# -# The example below uses data with a 10 arc minute resolution, and plots it on a -# 15 centimeter figure with a Mercator projection and a CPT set to *geo*. -# ``frame="a"`` is used to add a frame to the figure. - -grid = pygmt.datasets.load_earth_relief(resolution="10m", region=[-14, 30, 35, 60]) -fig = pygmt.Figure() -fig.grdimage(grid=grid, projection="M15c", frame="a", cmap="geo") -fig.colorbar(frame=["a1000", "x+lElevation", "y+lm"]) -fig.show() +""" +Plotting Earth relief +===================== + +Plotting a map of Earth relief can use the data accessed by the +:meth:`pygmt.datasets.load_earth_relief` method. The data can then be plotted using the +:meth:`pygmt.Figure.grdimage` method. + +.. note:: + + This tutorial assumes the use of a Python notebook, such as IPython or Jupyter Notebook. + To see the figures while using a Python script instead, use + ``fig.show(method="external")`` to display the figure in the default PDF viewer. + + To save the figure, use ``fig.savefig("figname.pdf")`` where ``"figname.pdf"`` + is the desired name and file extension for the saved figure. +""" +# sphinx_gallery_thumbnail_number = 5 + +import pygmt + +######################################################################################## +# Load sample Earth relief data for the entire globe at a resolution of 1 arc degree. +# The other available resolutions are show +# at :gmt-docs:`datasets/remote-data.html#global-earth-relief-grids`. +grid = pygmt.datasets.load_earth_relief(resolution="01d") + +######################################################################################## +# Create a plot +# ------------- +# +# The :meth:`pygmt.Figure.grdimage` method takes the ``grid`` input to +# create a figure. It creates and applies a color palette to the figure based upon the +# z-values of the data. By default, it plots the map with the *turbo* CPT, an +# equidistant cylindrical projection, and with no frame. + +fig = pygmt.Figure() +fig.grdimage(grid=grid) +fig.show() + +######################################################################################## +# +# :meth:`pygmt.Figure.grdimage` can take the optional argument ``projection`` for the +# map. In the example below, the ``projection`` is set as ``R12c`` for 12 centimeter +# figure with a Winkel Tripel projection. For a list of available projections, +# see :gmt-docs:`cookbook/map-projections.html`. + +fig = pygmt.Figure() +fig.grdimage(grid=grid, projection="R12c") +fig.show() + +######################################################################################## +# Set a color map +# --------------- +# +# :meth:`pygmt.Figure.grdimage` takes the ``cmap`` argument to set the CPT of the +# figure. Examples of common CPTs for Earth relief are shown below. +# A full list of CPTs can be found at :gmt-docs:`cookbook/cpts.html`. + +######################################################################################## +# +# Using the *geo* CPT: + +fig = pygmt.Figure() +fig.grdimage(grid=grid, projection="R12c", cmap="geo") +fig.show() + +######################################################################################## +# +# Using the *relief* CPT: + +fig = pygmt.Figure() +fig.grdimage(grid=grid, projection="R12c", cmap="relief") +fig.show() + +######################################################################################## +# Add a color bar +# --------------- +# +# The :meth:`pygmt.Figure.colorbar` method displays the CPT and the associated Z-values +# of the figure, and by default uses the same CPT set by the ``cmap`` argument +# for :meth:`pygmt.Figure.grdimage`. The ``frame`` argument for +# :meth:`pygmt.Figure.colorbar` can be used to set the axis intervals and labels. A +# list is used to pass multiple arguments to ``frame``. In the example below, +# ``a2500`` sets the axis interval to 2,500, ``x+lElevation`` sets the x-axis +# label, and ``y+lm`` sets the y-axis label. + +fig = pygmt.Figure() +fig.grdimage(grid=grid, projection="R12c", cmap="geo") +fig.colorbar(frame=["a2500", "x+lElevation", "y+lm"]) +fig.show() + +######################################################################################## +# Create a region map +# ------------------- +# +# In addition to providing global data, the ``region`` argument for +# :meth:`pygmt.datasets.load_earth_relief` can be used to provide data for a specific +# area. The ``region`` argument is required for resolutions at 5 arc minutes or higher, +# and accepts a list (as in the example below) or a string. The geographic ranges are +# passed as *xmin*/*xmax*/*ymin*/*ymax*. +# +# The example below uses data with a 10 arc minute resolution, and plots it on a +# 15 centimeter figure with a Mercator projection and a CPT set to *geo*. +# ``frame="a"`` is used to add a frame to the figure. + +grid = pygmt.datasets.load_earth_relief(resolution="10m", region=[-14, 30, 35, 60]) +fig = pygmt.Figure() +fig.grdimage(grid=grid, projection="M15c", frame="a", cmap="geo") +fig.colorbar(frame=["a1000", "x+lElevation", "y+lm"]) +fig.show() diff --git a/examples/tutorials/first-figure.py b/examples/tutorials/first-figure.py index 17380d1139b..ac33a507e5d 100644 --- a/examples/tutorials/first-figure.py +++ b/examples/tutorials/first-figure.py @@ -1,93 +1,93 @@ -""" -Making your first figure -======================== - -Welcome to PyGMT! Here we'll cover some of basic concepts, like creating simple figures -and naming conventions. - -.. note:: - - This tutorial assumes the use of a Python notebook, such as IPython or Jupyter Notebook. - To see the figures while using a Python script instead, use - ``fig.show(method="external")`` to display the figure in the default PDF viewer. - - To save the figure, use ``fig.savefig("figname.pdf")`` where ``"figname.pdf"`` - is the desired name and file extension for the saved figure. -""" - -######################################################################################## -# Loading the library -# ------------------- -# -# All modules and figure generation is accessible from the :mod:`pygmt` top level -# package: - -import pygmt - -######################################################################################## -# Creating figures -# ---------------- -# -# All figure generation in PyGMT is handled by the :class:`pygmt.Figure` class. -# Start a new figure by creating an instance of this class: - -fig = pygmt.Figure() - -######################################################################################## -# Add elements to the figure using its methods. For example, let's start a map with an -# automatic frame and ticks around a given longitude and latitude bound, set the -# projection to Mercator (``M``), and the map width to 8 inches: - -fig.basemap(region=[-90, -70, 0, 20], projection="M8i", frame=True) - -######################################################################################## -# Now we can add coastlines using :meth:`pygmt.Figure.coast` to this map using the -# default resolution, line width, and color: - -fig.coast(shorelines=True) - -######################################################################################## -# To see the figure, call :meth:`pygmt.Figure.show`: - -fig.show() - -######################################################################################## -# You can also set the map region, projection, and frame type directly in other methods -# without calling :meth:`gmt.Figure.basemap`: - -fig = pygmt.Figure() -fig.coast(shorelines=True, region=[-90, -70, 0, 20], projection="M8i", frame=True) -fig.show() - -######################################################################################## -# Saving figures -# -------------- -# -# Use the method :meth:`pygmt.Figure.savefig` to save your figure to a file. The figure -# format is inferred from the extension. -# -# .. code:: python -# -# fig.savefig("central-america-shorelines.png") -# -# Note for experienced GMT users -# ------------------------------ -# -# You’ll probably have noticed several things that are different from classic -# command-line GMT. Many of these changes reflect the new GMT modern execution mode that -# are part of GMT 6. A few are PyGMT exclusive (like the ``savefig`` method). -# -# 1. The name of method is ``coast`` instead of ``pscoast``. As a general rule, all -# ``ps*`` modules had their ``ps`` prefix removed. The exceptions are: -# ``psxy`` which is now ``plot``, ``psxyz`` which is now ``plot3d``, and ``psscale`` -# which is now ``colorbar``. -# 2. The parameters don't use the GMT 1-letter syntax (**R**, **J**, **B**, etc). We use longer -# aliases for these parameters and have some Python exclusive names. The mapping -# between the GMT parameters and their Python counterparts should be straight -# forward. -# 3. Parameters like ``region`` can take lists as well as strings like ``1/2/3/4``. -# 4. If a GMT parameter has no options (like ``-B`` instead of ``-Baf``), use a ``True`` -# in Python. An empty string would also be acceptable. For repeated parameters, such -# as ``-B+Loleron -Bxaf -By+lm``, provide a list: ``frame=["+Loleron", "xaf", "y+lm"]``. -# 5. There is no output redirecting to a PostScript file. The figure is generated in the -# background and will only be shown or saved when you ask for it. +""" +Making your first figure +======================== + +Welcome to PyGMT! Here we'll cover some of basic concepts, like creating simple figures +and naming conventions. + +.. note:: + + This tutorial assumes the use of a Python notebook, such as IPython or Jupyter Notebook. + To see the figures while using a Python script instead, use + ``fig.show(method="external")`` to display the figure in the default PDF viewer. + + To save the figure, use ``fig.savefig("figname.pdf")`` where ``"figname.pdf"`` + is the desired name and file extension for the saved figure. +""" + +######################################################################################## +# Loading the library +# ------------------- +# +# All modules and figure generation is accessible from the :mod:`pygmt` top level +# package: + +import pygmt + +######################################################################################## +# Creating figures +# ---------------- +# +# All figure generation in PyGMT is handled by the :class:`pygmt.Figure` class. +# Start a new figure by creating an instance of this class: + +fig = pygmt.Figure() + +######################################################################################## +# Add elements to the figure using its methods. For example, let's start a map with an +# automatic frame and ticks around a given longitude and latitude bound, set the +# projection to Mercator (``M``), and the map width to 8 inches: + +fig.basemap(region=[-90, -70, 0, 20], projection="M8i", frame=True) + +######################################################################################## +# Now we can add coastlines using :meth:`pygmt.Figure.coast` to this map using the +# default resolution, line width, and color: + +fig.coast(shorelines=True) + +######################################################################################## +# To see the figure, call :meth:`pygmt.Figure.show`: + +fig.show() + +######################################################################################## +# You can also set the map region, projection, and frame type directly in other methods +# without calling :meth:`gmt.Figure.basemap`: + +fig = pygmt.Figure() +fig.coast(shorelines=True, region=[-90, -70, 0, 20], projection="M8i", frame=True) +fig.show() + +######################################################################################## +# Saving figures +# -------------- +# +# Use the method :meth:`pygmt.Figure.savefig` to save your figure to a file. The figure +# format is inferred from the extension. +# +# .. code:: python +# +# fig.savefig("central-america-shorelines.png") +# +# Note for experienced GMT users +# ------------------------------ +# +# You’ll probably have noticed several things that are different from classic +# command-line GMT. Many of these changes reflect the new GMT modern execution mode that +# are part of GMT 6. A few are PyGMT exclusive (like the ``savefig`` method). +# +# 1. The name of method is ``coast`` instead of ``pscoast``. As a general rule, all +# ``ps*`` modules had their ``ps`` prefix removed. The exceptions are: +# ``psxy`` which is now ``plot``, ``psxyz`` which is now ``plot3d``, and ``psscale`` +# which is now ``colorbar``. +# 2. The parameters don't use the GMT 1-letter syntax (**R**, **J**, **B**, etc). We use longer +# aliases for these parameters and have some Python exclusive names. The mapping +# between the GMT parameters and their Python counterparts should be straight +# forward. +# 3. Parameters like ``region`` can take lists as well as strings like ``1/2/3/4``. +# 4. If a GMT parameter has no options (like ``-B`` instead of ``-Baf``), use a ``True`` +# in Python. An empty string would also be acceptable. For repeated parameters, such +# as ``-B+Loleron -Bxaf -By+lm``, provide a list: ``frame=["+Loleron", "xaf", "y+lm"]``. +# 5. There is no output redirecting to a PostScript file. The figure is generated in the +# background and will only be shown or saved when you ask for it. diff --git a/examples/tutorials/frames.py b/examples/tutorials/frames.py index 763644fb192..a8784a68c9c 100644 --- a/examples/tutorials/frames.py +++ b/examples/tutorials/frames.py @@ -1,110 +1,110 @@ -""" -Frames, ticks, titles, and labels -================================= - -Setting the style of the map frames, ticks, etc, is handled by the ``frame`` parameter -that all plotting methods of :class:`pygmt.Figure`. - -.. note:: - - This tutorial assumes the use of a Python notebook, such as IPython or Jupyter Notebook. - To see the figures while using a Python script instead, use - ``fig.show(method="external")`` to display the figure in the default PDF viewer. - - To save the figure, use ``fig.savefig("figname.pdf")`` where ``"figname.pdf"`` - is the desired name and file extension for the saved figure. -""" -# sphinx_gallery_thumbnail_number = 4 - -import pygmt - -######################################################################################## -# Plot frame -# ---------- -# -# By default, PyGMT does not add a frame to your plot. For example, we can plot the -# coastlines of the world with a Mercator projection: - -fig = pygmt.Figure() -fig.coast(shorelines="1/0.5p", region=[-180, 180, -60, 60], projection="M25c") -fig.show() - -######################################################################################## -# To add the default GMT frame to the plot, use ``frame="f"`` in -# :meth:`pygmt.Figure.basemap` or any other plotting module: - -fig = pygmt.Figure() -fig.coast(shorelines="1/0.5p", region=[-180, 180, -60, 60], projection="M25c") -fig.basemap(frame="f") -fig.show() - -######################################################################################## -# Ticks and grid lines -# -------------------- -# -# The automatic frame (``frame=True`` or ``frame="a"``) sets the default GMT style frame -# and automatically determines tick labels from the plot region. - -fig = pygmt.Figure() -fig.coast(shorelines="1/0.5p", region=[-180, 180, -60, 60], projection="M25c") -fig.basemap(frame="a") -fig.show() - -######################################################################################## -# Add automatic grid lines to the plot by adding a ``g`` to ``frame``: - -fig = pygmt.Figure() -fig.coast(shorelines="1/0.5p", region=[-180, 180, -60, 60], projection="M25c") -fig.basemap(frame="ag") -fig.show() - -######################################################################################## -# Title -# ----- -# -# The figure title can be set by passing **+t**\ *title* to the ``frame`` parameter of -# :meth:`pygmt.Figure.basemap`. Passing multiple arguments to ``frame`` can be done by -# using a list, as show in the example below. - -fig = pygmt.Figure() -# region="IS" specifies Iceland using the ISO country code -fig.coast(shorelines="1/0.5p", region="IS", projection="M25c") -fig.basemap(frame=["a", "+tIceland"]) -fig.show() - -######################################################################################## -# To use a title with multiple words, the title must be placed inside another set of -# quotation marks. To prevent the quotation marks from appearing in the figure title, -# the ``frame`` parameter can be passed in single quotation marks and the title can be -# passed in double quotation marks. - -fig = pygmt.Figure() -# region="TT" specifies Trinidad and Tobago -fig.coast(shorelines="1/0.5p", region="TT", projection="M25c") -fig.basemap(frame=["a", '+t"Trinidad and Tobago"']) -fig.show() - -######################################################################################## -# Axis labels -# ----------- -# -# Axis labels can be set by passing **x+l**\ *label* (or starting with **y** if -# labeling the y-axis) to the ``frame`` parameter of :meth:`pygmt.Figure.basemap`. -# By default, all 4 map boundaries (or plot axes) are plotted with both tick marks and -# axis labels. The axes are named as **W** (west/left), **S** (south/bottom), -# **N** (north/top), and **E** (east/right) sides of a figure. If an upper-case axis -# name is passed, the axis is plotted with tick marks and axis labels. A lower case -# axis name plots only the axis and tick marks. -# -# The example below uses a Cartesian projection, as GMT does not allow axis labels to -# be set for geographic maps. - -fig = pygmt.Figure() -fig.basemap( - region=[0, 10, 0, 20], - projection="X10c/8c", - # Plot axis, tick marks, and axis labels on the west/left and south/bottom axes - # Plot axis and tick marks on the north/top and east/right axes - frame=["WSne", "x+lx-axis", "y+ly-axis"], -) -fig.show() +""" +Frames, ticks, titles, and labels +================================= + +Setting the style of the map frames, ticks, etc, is handled by the ``frame`` parameter +that all plotting methods of :class:`pygmt.Figure`. + +.. note:: + + This tutorial assumes the use of a Python notebook, such as IPython or Jupyter Notebook. + To see the figures while using a Python script instead, use + ``fig.show(method="external")`` to display the figure in the default PDF viewer. + + To save the figure, use ``fig.savefig("figname.pdf")`` where ``"figname.pdf"`` + is the desired name and file extension for the saved figure. +""" +# sphinx_gallery_thumbnail_number = 4 + +import pygmt + +######################################################################################## +# Plot frame +# ---------- +# +# By default, PyGMT does not add a frame to your plot. For example, we can plot the +# coastlines of the world with a Mercator projection: + +fig = pygmt.Figure() +fig.coast(shorelines="1/0.5p", region=[-180, 180, -60, 60], projection="M25c") +fig.show() + +######################################################################################## +# To add the default GMT frame to the plot, use ``frame="f"`` in +# :meth:`pygmt.Figure.basemap` or any other plotting module: + +fig = pygmt.Figure() +fig.coast(shorelines="1/0.5p", region=[-180, 180, -60, 60], projection="M25c") +fig.basemap(frame="f") +fig.show() + +######################################################################################## +# Ticks and grid lines +# -------------------- +# +# The automatic frame (``frame=True`` or ``frame="a"``) sets the default GMT style frame +# and automatically determines tick labels from the plot region. + +fig = pygmt.Figure() +fig.coast(shorelines="1/0.5p", region=[-180, 180, -60, 60], projection="M25c") +fig.basemap(frame="a") +fig.show() + +######################################################################################## +# Add automatic grid lines to the plot by adding a ``g`` to ``frame``: + +fig = pygmt.Figure() +fig.coast(shorelines="1/0.5p", region=[-180, 180, -60, 60], projection="M25c") +fig.basemap(frame="ag") +fig.show() + +######################################################################################## +# Title +# ----- +# +# The figure title can be set by passing **+t**\ *title* to the ``frame`` parameter of +# :meth:`pygmt.Figure.basemap`. Passing multiple arguments to ``frame`` can be done by +# using a list, as show in the example below. + +fig = pygmt.Figure() +# region="IS" specifies Iceland using the ISO country code +fig.coast(shorelines="1/0.5p", region="IS", projection="M25c") +fig.basemap(frame=["a", "+tIceland"]) +fig.show() + +######################################################################################## +# To use a title with multiple words, the title must be placed inside another set of +# quotation marks. To prevent the quotation marks from appearing in the figure title, +# the ``frame`` parameter can be passed in single quotation marks and the title can be +# passed in double quotation marks. + +fig = pygmt.Figure() +# region="TT" specifies Trinidad and Tobago +fig.coast(shorelines="1/0.5p", region="TT", projection="M25c") +fig.basemap(frame=["a", '+t"Trinidad and Tobago"']) +fig.show() + +######################################################################################## +# Axis labels +# ----------- +# +# Axis labels can be set by passing **x+l**\ *label* (or starting with **y** if +# labeling the y-axis) to the ``frame`` parameter of :meth:`pygmt.Figure.basemap`. +# By default, all 4 map boundaries (or plot axes) are plotted with both tick marks and +# axis labels. The axes are named as **W** (west/left), **S** (south/bottom), +# **N** (north/top), and **E** (east/right) sides of a figure. If an upper-case axis +# name is passed, the axis is plotted with tick marks and axis labels. A lower case +# axis name plots only the axis and tick marks. +# +# The example below uses a Cartesian projection, as GMT does not allow axis labels to +# be set for geographic maps. + +fig = pygmt.Figure() +fig.basemap( + region=[0, 10, 0, 20], + projection="X10c/8c", + # Plot axis, tick marks, and axis labels on the west/left and south/bottom axes + # Plot axis and tick marks on the north/top and east/right axes + frame=["WSne", "x+lx-axis", "y+ly-axis"], +) +fig.show() diff --git a/examples/tutorials/inset.py b/examples/tutorials/inset.py index 9d892e5755b..ca509dd333b 100644 --- a/examples/tutorials/inset.py +++ b/examples/tutorials/inset.py @@ -1,119 +1,119 @@ -""" -Adding an inset to the figure -============================= - -To plot an inset figure inside another larger figure, we can use the -:meth:`pygmt.Figure.inset` method. After a large figure has been created, -call ``inset`` using a ``with`` statement, and new plot elements will be -added to the inset figure instead of the larger figure. - -.. note:: - - This tutorial assumes the use of a Python notebook, such as IPython or Jupyter Notebook. - To see the figures while using a Python script instead, use - ``fig.show(method="external")`` to display the figure in the default PDF viewer. - - To save the figure, use ``fig.savefig("figname.pdf")`` where ``"figname.pdf"`` - is the desired name and file extension for the saved figure. -""" -# sphinx_gallery_thumbnail_number = 4 - -import pygmt - -######################################################################################## -# -# Prior to creating an inset figure, a larger figure must first be plotted. In the -# example below, :meth:`pygmt.Figure.coast` is used to create a map of the US state of -# Massachusetts. - -fig = pygmt.Figure() -fig.coast( - region=[-74, -69.5, 41, 43], # Set bounding box of the large figure - borders="2/thin", # Plot state boundaries with thin lines - shorelines="thin", # Plot coastline with thin lines - projection="M15c", # Set Mercator projection and size of 15 centimeter - land="lightyellow", # Color land areas light yellow - water="lightblue", # Color water areas light blue - frame="a", # Set frame with annotation and major tick spacing -) -fig.show() - -######################################################################################## -# -# The :meth:`pygmt.Figure.inset` method uses a context manager, and is called using a -# ``with`` statement. The ``position`` parameter, including the inset width, is -# required to plot the inset. Using the **j** argument, the location of the inset is -# set to one of the 9 anchors (bottom-middle-top and left-center-right). In the -# example below, ``BL`` sets the inset to the bottom left. The ``box`` parameter can -# set the fill and border of the inset. In the example below, ``+pblack`` sets the -# border color to black and ``+gred`` sets the fill to red. - -fig = pygmt.Figure() -fig.coast( - region=[-74, -69.5, 41, 43], - borders="2/thin", - shorelines="thin", - projection="M15c", - land="lightyellow", - water="lightblue", - frame="a", -) -with fig.inset(position="jBL+w3c", box="+pblack+glightred"): - # pass is used to exit the with statement as no plotting functions are called - pass -fig.show() - -######################################################################################## -# -# When using **j** to set the anchor of the inset, the default location is in -# contact with the nearby axis or axes. The offset of the inset can be set with **+o**, -# followed by the offsets along the x- and y-axis. If only one offset is -# passed, it is applied to both axes. Each offset can have its own unit. In -# the example below, the inset is shifted 0.5 centimeters on the x-axis and -# 0.2 centimeters on the y-axis. - -fig = pygmt.Figure() -fig.coast( - region=[-74, -69.5, 41, 43], - borders="2/thin", - shorelines="thin", - projection="M15c", - land="lightyellow", - water="lightblue", - frame="a", -) -with fig.inset(position="jBL+w3c+o0.5c/0.2c", box="+pblack+glightred"): - pass -fig.show() - -######################################################################################## -# -# Standard plotting functions can be called from within the ``inset`` context manager. -# The example below uses :meth:`pygmt.Figure.coast` to plot a zoomed out map that -# selectively paints the state of Massachusetts to shows its location relative to -# other states. - -fig = pygmt.Figure() -fig.coast( - region=[-74, -69.5, 41, 43], - borders="2/thin", - shorelines="thin", - projection="M15c", - land="lightyellow", - water="lightblue", - frame="a", -) -# This does not include an inset fill as it is covered by the inset figure -with fig.inset(position="jBL+w3c+o0.5c/0.2c", box="+pblack"): - # Use a plotting function to create a figure inside the inset - fig.coast( - region=[-80, -65, 35, 50], - projection="M3c", - land="gray", - borders=[1, 2], - shorelines="1/thin", - water="white", - # Use dcw to selectively highlight an area - dcw="US.MA+gred", - ) -fig.show() +""" +Adding an inset to the figure +============================= + +To plot an inset figure inside another larger figure, we can use the +:meth:`pygmt.Figure.inset` method. After a large figure has been created, +call ``inset`` using a ``with`` statement, and new plot elements will be +added to the inset figure instead of the larger figure. + +.. note:: + + This tutorial assumes the use of a Python notebook, such as IPython or Jupyter Notebook. + To see the figures while using a Python script instead, use + ``fig.show(method="external")`` to display the figure in the default PDF viewer. + + To save the figure, use ``fig.savefig("figname.pdf")`` where ``"figname.pdf"`` + is the desired name and file extension for the saved figure. +""" +# sphinx_gallery_thumbnail_number = 4 + +import pygmt + +######################################################################################## +# +# Prior to creating an inset figure, a larger figure must first be plotted. In the +# example below, :meth:`pygmt.Figure.coast` is used to create a map of the US state of +# Massachusetts. + +fig = pygmt.Figure() +fig.coast( + region=[-74, -69.5, 41, 43], # Set bounding box of the large figure + borders="2/thin", # Plot state boundaries with thin lines + shorelines="thin", # Plot coastline with thin lines + projection="M15c", # Set Mercator projection and size of 15 centimeter + land="lightyellow", # Color land areas light yellow + water="lightblue", # Color water areas light blue + frame="a", # Set frame with annotation and major tick spacing +) +fig.show() + +######################################################################################## +# +# The :meth:`pygmt.Figure.inset` method uses a context manager, and is called using a +# ``with`` statement. The ``position`` parameter, including the inset width, is +# required to plot the inset. Using the **j** argument, the location of the inset is +# set to one of the 9 anchors (bottom-middle-top and left-center-right). In the +# example below, ``BL`` sets the inset to the bottom left. The ``box`` parameter can +# set the fill and border of the inset. In the example below, ``+pblack`` sets the +# border color to black and ``+gred`` sets the fill to red. + +fig = pygmt.Figure() +fig.coast( + region=[-74, -69.5, 41, 43], + borders="2/thin", + shorelines="thin", + projection="M15c", + land="lightyellow", + water="lightblue", + frame="a", +) +with fig.inset(position="jBL+w3c", box="+pblack+glightred"): + # pass is used to exit the with statement as no plotting functions are called + pass +fig.show() + +######################################################################################## +# +# When using **j** to set the anchor of the inset, the default location is in +# contact with the nearby axis or axes. The offset of the inset can be set with **+o**, +# followed by the offsets along the x- and y-axis. If only one offset is +# passed, it is applied to both axes. Each offset can have its own unit. In +# the example below, the inset is shifted 0.5 centimeters on the x-axis and +# 0.2 centimeters on the y-axis. + +fig = pygmt.Figure() +fig.coast( + region=[-74, -69.5, 41, 43], + borders="2/thin", + shorelines="thin", + projection="M15c", + land="lightyellow", + water="lightblue", + frame="a", +) +with fig.inset(position="jBL+w3c+o0.5c/0.2c", box="+pblack+glightred"): + pass +fig.show() + +######################################################################################## +# +# Standard plotting functions can be called from within the ``inset`` context manager. +# The example below uses :meth:`pygmt.Figure.coast` to plot a zoomed out map that +# selectively paints the state of Massachusetts to shows its location relative to +# other states. + +fig = pygmt.Figure() +fig.coast( + region=[-74, -69.5, 41, 43], + borders="2/thin", + shorelines="thin", + projection="M15c", + land="lightyellow", + water="lightblue", + frame="a", +) +# This does not include an inset fill as it is covered by the inset figure +with fig.inset(position="jBL+w3c+o0.5c/0.2c", box="+pblack"): + # Use a plotting function to create a figure inside the inset + fig.coast( + region=[-80, -65, 35, 50], + projection="M3c", + land="gray", + borders=[1, 2], + shorelines="1/thin", + water="white", + # Use dcw to selectively highlight an area + dcw="US.MA+gred", + ) +fig.show() diff --git a/examples/tutorials/lines.py b/examples/tutorials/lines.py index a7340371aee..2183b0464e2 100644 --- a/examples/tutorials/lines.py +++ b/examples/tutorials/lines.py @@ -1,139 +1,139 @@ -""" -Plot lines -========== - -Plotting lines is handled by :meth:`pygmt.Figure.plot`. - -.. note:: - - This tutorial assumes the use of a Python notebook, such as IPython or Jupyter Notebook. - To see the figures while using a Python script instead, use - ``fig.show(method="external")`` to display the figure in the default PDF viewer. - - To save the figure, use ``fig.savefig("figname.pdf")`` where ``"figname.pdf"`` - is the desired name and file extension for the saved figure. -""" -# sphinx_gallery_thumbnail_number = 3 - -import pygmt - -######################################################################################## -# Plot lines -# ---------- -# -# Create a Cartesian figure using ``projection`` parameter and set the axis scales -# using ``region`` (in this case, each axis is 0-10). Pass a list of ``x`` and ``y`` -# values to be plotted as a line. - -fig = pygmt.Figure() -fig.plot( - region=[0, 10, 0, 10], - projection="X25c/20c", - frame="a", - x=[1, 8], - y=[5, 9], - pen="1p,black", -) -fig.show() - -######################################################################################## -# Additional line segments can be added by including additional values for ``x`` -# and ``y``. - -fig = pygmt.Figure() -fig.plot( - region=[0, 10, 0, 10], - projection="X25c/20c", - frame="a", - x=[1, 6, 9], - y=[5, 7, 4], - pen="1p,black", -) -fig.show() - -######################################################################################## -# To plot multiple lines, :meth:`pygmt.Figure.plot` needs to be used for each -# additional line. Arguments such as ``region``, ``projection``, and ``frame`` do -# not need to be repeated in subsequent uses. - -fig = pygmt.Figure() -fig.plot( - region=[0, 10, 0, 10], - projection="X25c/20c", - frame="a", - x=[1, 6, 9], - y=[5, 7, 4], - pen="2p,blue", -) -fig.plot(x=[2, 4, 10], y=[3, 8, 9], pen="2p,red") -fig.show() - -######################################################################################## -# Change line attributes -# ---------------------- -# -# The line attributes can be set by the ``pen`` parameter. ``pen`` takes a string -# argument with the optional values *width*,\ *color*,\ *style*. -# -# In the example below, the pen width is set to ``5p``, and with ``black`` as the -# default color and ``solid`` as the default style. - -fig = pygmt.Figure() -fig.plot( - region=[0, 10, 0, 10], - projection="X25c/20c", - frame="a", - x=[1, 8], - y=[3, 9], - pen="5p", -) -fig.show() - -######################################################################################## -# The line color can be set and is added after the line width to the ``pen`` parameter. -# In the example below, the line color is set to ``red``. - -fig = pygmt.Figure() -fig.plot( - region=[0, 10, 0, 10], - projection="X25c/20c", - frame="a", - x=[1, 8], - y=[3, 9], - pen="5p,red", -) -fig.show() - -######################################################################################## -# The line style can be set and is added after the line width or color to the -# ``pen`` parameter. In the example below, the line style is set to -# ``..-`` (*dot dot dash*), and the default color ``black`` is used. - -fig = pygmt.Figure() -fig.plot( - region=[0, 10, 0, 10], - projection="X25c/20c", - frame="a", - x=[1, 8], - y=[3, 9], - pen="5p,..-", -) -fig.show() - -######################################################################################## -# The line width, color, and style can all be set in the same ``pen`` parameter. In the -# example below, the line width is set to ``7p``, the color is set to ``green``, and the -# line style is ``-.-`` (*dash dot dash*). -# -# For a gallery showing other ``pen`` settings, see :doc:`/gallery/line/linestyles`. - -fig = pygmt.Figure() -fig.plot( - region=[0, 10, 0, 10], - projection="X25c/20c", - frame="a", - x=[1, 8], - y=[3, 9], - pen="7p,green,-.-", -) -fig.show() +""" +Plot lines +========== + +Plotting lines is handled by :meth:`pygmt.Figure.plot`. + +.. note:: + + This tutorial assumes the use of a Python notebook, such as IPython or Jupyter Notebook. + To see the figures while using a Python script instead, use + ``fig.show(method="external")`` to display the figure in the default PDF viewer. + + To save the figure, use ``fig.savefig("figname.pdf")`` where ``"figname.pdf"`` + is the desired name and file extension for the saved figure. +""" +# sphinx_gallery_thumbnail_number = 3 + +import pygmt + +######################################################################################## +# Plot lines +# ---------- +# +# Create a Cartesian figure using ``projection`` parameter and set the axis scales +# using ``region`` (in this case, each axis is 0-10). Pass a list of ``x`` and ``y`` +# values to be plotted as a line. + +fig = pygmt.Figure() +fig.plot( + region=[0, 10, 0, 10], + projection="X25c/20c", + frame="a", + x=[1, 8], + y=[5, 9], + pen="1p,black", +) +fig.show() + +######################################################################################## +# Additional line segments can be added by including additional values for ``x`` +# and ``y``. + +fig = pygmt.Figure() +fig.plot( + region=[0, 10, 0, 10], + projection="X25c/20c", + frame="a", + x=[1, 6, 9], + y=[5, 7, 4], + pen="1p,black", +) +fig.show() + +######################################################################################## +# To plot multiple lines, :meth:`pygmt.Figure.plot` needs to be used for each +# additional line. Arguments such as ``region``, ``projection``, and ``frame`` do +# not need to be repeated in subsequent uses. + +fig = pygmt.Figure() +fig.plot( + region=[0, 10, 0, 10], + projection="X25c/20c", + frame="a", + x=[1, 6, 9], + y=[5, 7, 4], + pen="2p,blue", +) +fig.plot(x=[2, 4, 10], y=[3, 8, 9], pen="2p,red") +fig.show() + +######################################################################################## +# Change line attributes +# ---------------------- +# +# The line attributes can be set by the ``pen`` parameter. ``pen`` takes a string +# argument with the optional values *width*,\ *color*,\ *style*. +# +# In the example below, the pen width is set to ``5p``, and with ``black`` as the +# default color and ``solid`` as the default style. + +fig = pygmt.Figure() +fig.plot( + region=[0, 10, 0, 10], + projection="X25c/20c", + frame="a", + x=[1, 8], + y=[3, 9], + pen="5p", +) +fig.show() + +######################################################################################## +# The line color can be set and is added after the line width to the ``pen`` parameter. +# In the example below, the line color is set to ``red``. + +fig = pygmt.Figure() +fig.plot( + region=[0, 10, 0, 10], + projection="X25c/20c", + frame="a", + x=[1, 8], + y=[3, 9], + pen="5p,red", +) +fig.show() + +######################################################################################## +# The line style can be set and is added after the line width or color to the +# ``pen`` parameter. In the example below, the line style is set to +# ``..-`` (*dot dot dash*), and the default color ``black`` is used. + +fig = pygmt.Figure() +fig.plot( + region=[0, 10, 0, 10], + projection="X25c/20c", + frame="a", + x=[1, 8], + y=[3, 9], + pen="5p,..-", +) +fig.show() + +######################################################################################## +# The line width, color, and style can all be set in the same ``pen`` parameter. In the +# example below, the line width is set to ``7p``, the color is set to ``green``, and the +# line style is ``-.-`` (*dash dot dash*). +# +# For a gallery showing other ``pen`` settings, see :doc:`/gallery/line/linestyles`. + +fig = pygmt.Figure() +fig.plot( + region=[0, 10, 0, 10], + projection="X25c/20c", + frame="a", + x=[1, 8], + y=[3, 9], + pen="7p,green,-.-", +) +fig.show() diff --git a/examples/tutorials/plot.py b/examples/tutorials/plot.py index 52ed3e8f4c1..25f2042ebcb 100644 --- a/examples/tutorials/plot.py +++ b/examples/tutorials/plot.py @@ -1,102 +1,102 @@ -""" -Plotting data points --------------------- - -GMT shines when it comes to plotting data on a map. We can use some sample data that is -packaged with GMT to try this out. PyGMT provides access to these datasets through the -:mod:`pygmt.datasets` package. If you don't have the data files already, they are -automatically downloaded and saved to a cache directory the first time you use them -(usually ``~/.gmt/cache``). - -.. note:: - - This tutorial assumes the use of a Python notebook, such as IPython or Jupyter Notebook. - To see the figures while using a Python script instead, use - ``fig.show(method="external")`` to display the figure in the default PDF viewer. - - To save the figure, use ``fig.savefig("figname.pdf")`` where ``"figname.pdf"`` - is the desired name and file extension for the saved figure. -""" -# sphinx_gallery_thumbnail_number = 3 - -import pygmt - -######################################################################################## -# For example, let's load the sample dataset of tsunami generating earthquakes around -# Japan (:func:`pygmt.datasets.load_japan_quakes`). The data is loaded as a -# :class:`pandas.DataFrame`. - -data = pygmt.datasets.load_japan_quakes() - -# Set the region for the plot to be slightly larger than the data bounds. -region = [ - data.longitude.min() - 1, - data.longitude.max() + 1, - data.latitude.min() - 1, - data.latitude.max() + 1, -] - -print(region) -print(data.head()) - - -######################################################################################## -# We'll use :meth:`pygmt.Figure.plot` method to plot circles on the locations of the -# hypocenters of the earthquakes. - -fig = pygmt.Figure() -fig.basemap(region=region, projection="M15c", frame=True) -fig.coast(land="black", water="skyblue") -fig.plot(x=data.longitude, y=data.latitude, style="c0.3c", color="white", pen="black") -fig.show() - -######################################################################################## -# We used the style ``c0.3c`` which means "circles of 0.3 centimeter size". The ``pen`` -# parameter controls the outline of the symbols and the ``color`` controls the fill. -# -# We can map the size of the circles to the earthquake magnitude by passing an array to -# the ``sizes`` parameter. Because the magnitude is on a logarithmic scale, it helps to -# show the differences by scaling the values using a power law. - -fig = pygmt.Figure() -fig.basemap(region=region, projection="M15c", frame=True) -fig.coast(land="black", water="skyblue") -fig.plot( - x=data.longitude, - y=data.latitude, - sizes=0.02 * (2 ** data.magnitude), - style="cc", - color="white", - pen="black", -) -fig.show() - -######################################################################################## -# Notice that we didn't include the size in the ``style`` argument this time, just the -# symbol ``c`` (circles) and the unit ``c`` (centimeter). So in this case, the sizes -# will be interpreted as being in centimeters. -# -# We can also map the colors of the markers to the depths by passing an array to the -# ``color`` argument and providing a colormap name (``cmap``). We can even use the new -# matplotlib colormap "viridis". Here, we first create a continuous colormap -# ranging from the minimum depth to the maximum depth of the earthquakes -# using :func:`pygmt.makecpt`, then set ``cmap=True`` in :func:`pygmt.Figure.plot` -# to use the colormap. At the end of the plot, we also plot a colorbar showing -# the colormap used in the plot. -# - -fig = pygmt.Figure() -fig.basemap(region=region, projection="M15c", frame=True) -fig.coast(land="black", water="skyblue") -pygmt.makecpt(cmap="viridis", series=[data.depth_km.min(), data.depth_km.max()]) -fig.plot( - x=data.longitude, - y=data.latitude, - sizes=0.02 * 2 ** data.magnitude, - color=data.depth_km, - cmap=True, - style="cc", - pen="black", -) -fig.colorbar(frame='af+l"Depth (km)"') -fig.show() +""" +Plotting data points +-------------------- + +GMT shines when it comes to plotting data on a map. We can use some sample data that is +packaged with GMT to try this out. PyGMT provides access to these datasets through the +:mod:`pygmt.datasets` package. If you don't have the data files already, they are +automatically downloaded and saved to a cache directory the first time you use them +(usually ``~/.gmt/cache``). + +.. note:: + + This tutorial assumes the use of a Python notebook, such as IPython or Jupyter Notebook. + To see the figures while using a Python script instead, use + ``fig.show(method="external")`` to display the figure in the default PDF viewer. + + To save the figure, use ``fig.savefig("figname.pdf")`` where ``"figname.pdf"`` + is the desired name and file extension for the saved figure. +""" +# sphinx_gallery_thumbnail_number = 3 + +import pygmt + +######################################################################################## +# For example, let's load the sample dataset of tsunami generating earthquakes around +# Japan (:func:`pygmt.datasets.load_japan_quakes`). The data is loaded as a +# :class:`pandas.DataFrame`. + +data = pygmt.datasets.load_japan_quakes() + +# Set the region for the plot to be slightly larger than the data bounds. +region = [ + data.longitude.min() - 1, + data.longitude.max() + 1, + data.latitude.min() - 1, + data.latitude.max() + 1, +] + +print(region) +print(data.head()) + + +######################################################################################## +# We'll use :meth:`pygmt.Figure.plot` method to plot circles on the locations of the +# hypocenters of the earthquakes. + +fig = pygmt.Figure() +fig.basemap(region=region, projection="M15c", frame=True) +fig.coast(land="black", water="skyblue") +fig.plot(x=data.longitude, y=data.latitude, style="c0.3c", color="white", pen="black") +fig.show() + +######################################################################################## +# We used the style ``c0.3c`` which means "circles of 0.3 centimeter size". The ``pen`` +# parameter controls the outline of the symbols and the ``color`` controls the fill. +# +# We can map the size of the circles to the earthquake magnitude by passing an array to +# the ``sizes`` parameter. Because the magnitude is on a logarithmic scale, it helps to +# show the differences by scaling the values using a power law. + +fig = pygmt.Figure() +fig.basemap(region=region, projection="M15c", frame=True) +fig.coast(land="black", water="skyblue") +fig.plot( + x=data.longitude, + y=data.latitude, + sizes=0.02 * (2 ** data.magnitude), + style="cc", + color="white", + pen="black", +) +fig.show() + +######################################################################################## +# Notice that we didn't include the size in the ``style`` argument this time, just the +# symbol ``c`` (circles) and the unit ``c`` (centimeter). So in this case, the sizes +# will be interpreted as being in centimeters. +# +# We can also map the colors of the markers to the depths by passing an array to the +# ``color`` argument and providing a colormap name (``cmap``). We can even use the new +# matplotlib colormap "viridis". Here, we first create a continuous colormap +# ranging from the minimum depth to the maximum depth of the earthquakes +# using :func:`pygmt.makecpt`, then set ``cmap=True`` in :func:`pygmt.Figure.plot` +# to use the colormap. At the end of the plot, we also plot a colorbar showing +# the colormap used in the plot. +# + +fig = pygmt.Figure() +fig.basemap(region=region, projection="M15c", frame=True) +fig.coast(land="black", water="skyblue") +pygmt.makecpt(cmap="viridis", series=[data.depth_km.min(), data.depth_km.max()]) +fig.plot( + x=data.longitude, + y=data.latitude, + sizes=0.02 * 2 ** data.magnitude, + color=data.depth_km, + cmap=True, + style="cc", + pen="black", +) +fig.colorbar(frame='af+l"Depth (km)"') +fig.show() diff --git a/examples/tutorials/regions.py b/examples/tutorials/regions.py index 34f3587a377..34df02a0993 100644 --- a/examples/tutorials/regions.py +++ b/examples/tutorials/regions.py @@ -1,244 +1,244 @@ -""" -Set the region -============== - -Many of the plotting functions take the ``region`` parameter, which sets -the area that will be shown in the figure. This tutorial covers the different types of -inputs that it can accept. - -.. note:: - - This tutorial assumes the use of a Python notebook, such as IPython or Jupyter Notebook. - To see the figures while using a Python script instead, use - ``fig.show(method="external")`` to display the figure in the default PDF viewer. - - To save the figure, use ``fig.savefig("figname.pdf")`` where ``"figname.pdf"`` - is the desired name and file extension for the saved figure. -""" - -import pygmt - -######################################################################################## -# Coordinates -# ----------- -# -# A string of coordinates can be passed to ``region``, in the form of -# *xmin*/*xmax*/*ymin*/*ymax*. - -fig = pygmt.Figure() -fig.coast( - # Sets the x-range from 10E to 20E and the y-range to 35N to 45N - region="10/20/35/45", - # Set projection to Mercator, and the figure size to 15 centimeters - projection="M15c", - # Set the color of the land to light gray - land="lightgray", - # Set the color of the water to white - water="white", - # Display the national borders and set the pen thickness to 0.5p - borders="1/0.5p", - # Display the shorelines and set the pen thickness to 0.5p - shorelines="1/0.5p", - # Set the frame to display annotations and gridlines - frame="ag", -) -fig.show() - -######################################################################################## -# -# The coordinates can be passed to ``region`` as a list, in the form -# of [*xmin*,\ *xmax*,\ *ymin*,\ *ymax*]. - -fig = pygmt.Figure() -fig.coast( - # Sets the x-range from 10E to 20E and the y-range to 35N to 45N - region=[10, 20, 35, 45], - projection="M12c", - land="lightgray", - water="white", - borders="1/0.5p", - shorelines="1/0.5p", - frame="ag", -) -fig.show() - -######################################################################################## -# -# Instead of passing axes minima and maxima, the coordinates can be passed for the -# bottom-left and top-right corners. The string format takes the coordinates for the -# bottom-left and top-right coordinates. To specify corner coordinates, append **+r** -# at the end of the ``region`` string. - -fig = pygmt.Figure() -fig.coast( - # Sets the bottom-left corner as 10E, 35N and the top-right corner as 20E, 45N - region="10/35/20/45+r", - projection="M12c", - land="lightgray", - water="white", - borders="1/0.5p", - shorelines="1/0.5p", - frame="ag", -) -fig.show() - -######################################################################################## -# Global regions -# -------------- -# -# In addition to passing coordinates, the argument **d** can be passed to set the -# region to the entire globe. The range is 180W to 180E (-180, 180) and 90S to -# 90N (-90 to 90). With no parameters set for the projection, the figure defaults to be -# centered at the mid-point of both x- and y-axes. Using **d**\ , the figure is -# centered at (0, 0), or the intersection of the equator and prime meridian. - -fig = pygmt.Figure() -fig.coast( - region="d", - projection="Cyl_stere/12c", - land="darkgray", - water="white", - borders="1/0.5p", - shorelines="1/0.5p", - frame="ag", -) -fig.show() - -######################################################################################## -# -# The argument **g** can be passed, which encompasses the entire globe. The range is -# 0E to 360E (0, 360) and 90S to 90N (-90 to 90). With no parameters set for the -# projection, the figure is centered at (180, 0), or the intersection of the equator and -# International Date Line. - -fig = pygmt.Figure() -fig.coast( - region="g", - projection="Cyl_stere/12c", - land="darkgray", - water="white", - borders="1/0.5p", - shorelines="1/0.5p", - frame="ag", -) -fig.show() - -######################################################################################## -# ISO code -# -------- -# -# The ``region`` can be set to include a specific area specified by the two-character -# ISO 3166-1 alpha-2 convention -# (for futher information: https://en.wikipedia.org/wiki/ISO_3166-1_alpha-2). - -fig = pygmt.Figure() -fig.coast( - # Sets the figure region to encompass Japan with the ISO code "JP" - region="JP", - projection="M12c", - land="lightgray", - water="white", - borders="1/0.5p", - shorelines="1/0.5p", - frame="ag", -) -fig.show() - -######################################################################################## -# -# The area encompassed by the ISO code can be expanded by appending **+r**\ *increment* -# to the ISO code. The *increment* unit is in degrees, and if only value is added it -# expands the range of the region in all directions. Using **+r** rounds to the nearest -# increment. - -fig = pygmt.Figure() -fig.coast( - # Expands the region setting outside the range of Japan by 3 degrees in all - # directions - region="JP+r3", - projection="M12c", - land="lightgray", - water="white", - borders="1/0.5p", - shorelines="1/0.5p", - frame="ag", -) -fig.show() - -######################################################################################## -# -# Instead of expanding the range of the plot uniformly in all directions, two values -# can be passed to expand differently on each axis. The format is *xinc*/*yinc*. - -fig = pygmt.Figure() -fig.coast( - # Expands the region setting outside the range of Japan by 3 degrees on the x-axis - # and 5 degrees on the y-axis. - region="JP+r3/5", - projection="M12c", - land="lightgray", - water="white", - borders="1/0.5p", - shorelines="1/0.5p", - frame="ag", -) -fig.show() - -######################################################################################## -# -# Instead of expanding the range of the plot uniformly in all directions, four values -# can be passed to expand differently in each direction. -# The format is *winc*/*einc*/*sinc*/*ninc*, which expands on the west, -# east, south, and north axes. - -fig = pygmt.Figure() -fig.coast( - # Expands the region setting outside the range of Japan by 3 degrees to the west, - # 5 degrees to the east, 7 degrees to the south, and 9 degrees to the north. - region="JP+r3/5/7/9", - projection="M12c", - land="lightgray", - water="white", - borders="1/0.5p", - shorelines="1/0.5p", - frame="ag", -) -fig.show() - -######################################################################################## -# -# The ``region`` increment can be appended with **+R**, which adds the increment -# without rounding. - -fig = pygmt.Figure() -fig.coast( - # Expands the region setting outside the range of Japan by 3 degrees in all - # directions, without rounding to the nearest increment. - region="JP+R3", - projection="M12c", - land="lightgray", - water="white", - borders="1/0.5p", - shorelines="1/0.5p", - frame="ag", -) -fig.show() - -######################################################################################## -# -# The ``region`` increment can be appended with **+e**, which expand the bounding box -# by at least 25% beyond the increment. - -fig = pygmt.Figure() -fig.coast( - # Expands the region setting outside the range of Japan by 3 degrees in all - # directions, without rounding to the nearest increment. - region="JP+e3", - projection="M12c", - land="lightgray", - water="white", - borders="1/0.5p", - shorelines="1/0.5p", - frame="ag", -) -fig.show() +""" +Set the region +============== + +Many of the plotting functions take the ``region`` parameter, which sets +the area that will be shown in the figure. This tutorial covers the different types of +inputs that it can accept. + +.. note:: + + This tutorial assumes the use of a Python notebook, such as IPython or Jupyter Notebook. + To see the figures while using a Python script instead, use + ``fig.show(method="external")`` to display the figure in the default PDF viewer. + + To save the figure, use ``fig.savefig("figname.pdf")`` where ``"figname.pdf"`` + is the desired name and file extension for the saved figure. +""" + +import pygmt + +######################################################################################## +# Coordinates +# ----------- +# +# A string of coordinates can be passed to ``region``, in the form of +# *xmin*/*xmax*/*ymin*/*ymax*. + +fig = pygmt.Figure() +fig.coast( + # Sets the x-range from 10E to 20E and the y-range to 35N to 45N + region="10/20/35/45", + # Set projection to Mercator, and the figure size to 15 centimeters + projection="M15c", + # Set the color of the land to light gray + land="lightgray", + # Set the color of the water to white + water="white", + # Display the national borders and set the pen thickness to 0.5p + borders="1/0.5p", + # Display the shorelines and set the pen thickness to 0.5p + shorelines="1/0.5p", + # Set the frame to display annotations and gridlines + frame="ag", +) +fig.show() + +######################################################################################## +# +# The coordinates can be passed to ``region`` as a list, in the form +# of [*xmin*,\ *xmax*,\ *ymin*,\ *ymax*]. + +fig = pygmt.Figure() +fig.coast( + # Sets the x-range from 10E to 20E and the y-range to 35N to 45N + region=[10, 20, 35, 45], + projection="M12c", + land="lightgray", + water="white", + borders="1/0.5p", + shorelines="1/0.5p", + frame="ag", +) +fig.show() + +######################################################################################## +# +# Instead of passing axes minima and maxima, the coordinates can be passed for the +# bottom-left and top-right corners. The string format takes the coordinates for the +# bottom-left and top-right coordinates. To specify corner coordinates, append **+r** +# at the end of the ``region`` string. + +fig = pygmt.Figure() +fig.coast( + # Sets the bottom-left corner as 10E, 35N and the top-right corner as 20E, 45N + region="10/35/20/45+r", + projection="M12c", + land="lightgray", + water="white", + borders="1/0.5p", + shorelines="1/0.5p", + frame="ag", +) +fig.show() + +######################################################################################## +# Global regions +# -------------- +# +# In addition to passing coordinates, the argument **d** can be passed to set the +# region to the entire globe. The range is 180W to 180E (-180, 180) and 90S to +# 90N (-90 to 90). With no parameters set for the projection, the figure defaults to be +# centered at the mid-point of both x- and y-axes. Using **d**\ , the figure is +# centered at (0, 0), or the intersection of the equator and prime meridian. + +fig = pygmt.Figure() +fig.coast( + region="d", + projection="Cyl_stere/12c", + land="darkgray", + water="white", + borders="1/0.5p", + shorelines="1/0.5p", + frame="ag", +) +fig.show() + +######################################################################################## +# +# The argument **g** can be passed, which encompasses the entire globe. The range is +# 0E to 360E (0, 360) and 90S to 90N (-90 to 90). With no parameters set for the +# projection, the figure is centered at (180, 0), or the intersection of the equator and +# International Date Line. + +fig = pygmt.Figure() +fig.coast( + region="g", + projection="Cyl_stere/12c", + land="darkgray", + water="white", + borders="1/0.5p", + shorelines="1/0.5p", + frame="ag", +) +fig.show() + +######################################################################################## +# ISO code +# -------- +# +# The ``region`` can be set to include a specific area specified by the two-character +# ISO 3166-1 alpha-2 convention +# (for futher information: https://en.wikipedia.org/wiki/ISO_3166-1_alpha-2). + +fig = pygmt.Figure() +fig.coast( + # Sets the figure region to encompass Japan with the ISO code "JP" + region="JP", + projection="M12c", + land="lightgray", + water="white", + borders="1/0.5p", + shorelines="1/0.5p", + frame="ag", +) +fig.show() + +######################################################################################## +# +# The area encompassed by the ISO code can be expanded by appending **+r**\ *increment* +# to the ISO code. The *increment* unit is in degrees, and if only value is added it +# expands the range of the region in all directions. Using **+r** rounds to the nearest +# increment. + +fig = pygmt.Figure() +fig.coast( + # Expands the region setting outside the range of Japan by 3 degrees in all + # directions + region="JP+r3", + projection="M12c", + land="lightgray", + water="white", + borders="1/0.5p", + shorelines="1/0.5p", + frame="ag", +) +fig.show() + +######################################################################################## +# +# Instead of expanding the range of the plot uniformly in all directions, two values +# can be passed to expand differently on each axis. The format is *xinc*/*yinc*. + +fig = pygmt.Figure() +fig.coast( + # Expands the region setting outside the range of Japan by 3 degrees on the x-axis + # and 5 degrees on the y-axis. + region="JP+r3/5", + projection="M12c", + land="lightgray", + water="white", + borders="1/0.5p", + shorelines="1/0.5p", + frame="ag", +) +fig.show() + +######################################################################################## +# +# Instead of expanding the range of the plot uniformly in all directions, four values +# can be passed to expand differently in each direction. +# The format is *winc*/*einc*/*sinc*/*ninc*, which expands on the west, +# east, south, and north axes. + +fig = pygmt.Figure() +fig.coast( + # Expands the region setting outside the range of Japan by 3 degrees to the west, + # 5 degrees to the east, 7 degrees to the south, and 9 degrees to the north. + region="JP+r3/5/7/9", + projection="M12c", + land="lightgray", + water="white", + borders="1/0.5p", + shorelines="1/0.5p", + frame="ag", +) +fig.show() + +######################################################################################## +# +# The ``region`` increment can be appended with **+R**, which adds the increment +# without rounding. + +fig = pygmt.Figure() +fig.coast( + # Expands the region setting outside the range of Japan by 3 degrees in all + # directions, without rounding to the nearest increment. + region="JP+R3", + projection="M12c", + land="lightgray", + water="white", + borders="1/0.5p", + shorelines="1/0.5p", + frame="ag", +) +fig.show() + +######################################################################################## +# +# The ``region`` increment can be appended with **+e**, which expand the bounding box +# by at least 25% beyond the increment. + +fig = pygmt.Figure() +fig.coast( + # Expands the region setting outside the range of Japan by 3 degrees in all + # directions, without rounding to the nearest increment. + region="JP+e3", + projection="M12c", + land="lightgray", + water="white", + borders="1/0.5p", + shorelines="1/0.5p", + frame="ag", +) +fig.show() diff --git a/examples/tutorials/subplots.py b/examples/tutorials/subplots.py index 64c5e04533a..1c999243bf6 100644 --- a/examples/tutorials/subplots.py +++ b/examples/tutorials/subplots.py @@ -1,237 +1,237 @@ -""" -Making subplots -=============== - -When you're preparing a figure for a paper, there will often be times when -you'll need to put many individual plots into one large figure, and label them -'abcd'. These individual plots are called subplots. - -There are two main ways to create subplots in GMT: - -- Use :meth:`pygmt.Figure.shift_origin` to manually move each individual plot - to the right position. -- Use :meth:`pygmt.Figure.subplot` to define the layout of the subplots. - -The first method is easier to use and should handle simple cases involving a -couple of subplots. For more advanced subplot layouts, however, we recommend the -use of :meth:`pygmt.Figure.subplot` which offers finer grained control, and -this is what the tutorial below will cover. -""" -# sphinx_gallery_thumbnail_number = 3 - -import pygmt - -############################################################################### -# -# Let's start by initializing a :class:`pygmt.Figure` instance. - -fig = pygmt.Figure() - -############################################################################### -# Define subplot layout -# --------------------- -# -# The :meth:`pygmt.Figure.subplot` function is used to set up the layout, size, -# and other attributes of the figure. It divides the whole canvas into regular -# grid areas with *n* rows and *m* columns. Each grid area can contain an -# individual subplot. For example: - -############################################################################### -# .. code-block:: default -# -# with fig.subplot(nrows=2, ncols=3, figsize=("15c", "6c"), frame="lrtb"): -# ... - -############################################################################### -# will define our figure to have a 2 row and 3 column grid layout. -# ``figsize=("15c", "6c")`` defines the overall size of the figure to be 15 cm -# wide by 6 cm high. Using ``frame="lrtb"`` allows us to customize the map frame -# for all subplots instead of setting them individually. The figure layout will -# look like the following: - -with fig.subplot(nrows=2, ncols=3, figsize=("15c", "6c"), frame="lrtb"): - for i in range(2): # row number starting from 0 - for j in range(3): # column number starting from 0 - index = i * 3 + j # index number starting from 0 - with fig.set_panel(panel=index): # sets the current panel - fig.text( - position="MC", - text=f"index: {index}; row: {i}, col: {j}", - region=[0, 1, 0, 1], - ) -fig.show() - -############################################################################### -# The :meth:`pygmt.Figure.set_panel` function activates a specified subplot, and -# all subsequent plotting functions will take place in that subplot panel. This -# is similar to matplotlib's ``plt.sca`` method. In order to specify a subplot, -# you will need to provide the identifier for that subplot via the ``panel`` -# parameter. Pass in either the *index* number, or a tuple/list like -# (*row*, *col*) to ``panel``. - -############################################################################### -# .. note:: -# -# The row and column numbering starts from 0. So for a subplot layout with -# N rows and M columns, row numbers will go from 0 to N-1, and column -# numbers will go from 0 to M-1. - -############################################################################### -# For example, to activate the subplot on the top right corner (index: 2) at -# *row*\=0 and *col*\=2, so that all subsequent plotting commands happen -# there, you can use the following command: - -############################################################################### -# .. code-block:: default -# -# with fig.set_panel(panel=[0, 2]): -# ... - -############################################################################### -# Making your first subplot -# ------------------------- -# Next, let's use what we learned above to make a 2 row by 2 column subplot -# figure. We'll also pick up on some new parameters to configure our subplot. - -fig = pygmt.Figure() -with fig.subplot( - nrows=2, - ncols=2, - figsize=("15c", "6c"), - autolabel=True, - frame=["af", "WSne"], - margins=["0.1c", "0.2c"], - title="My Subplot Heading", -): - fig.basemap(region=[0, 10, 0, 10], projection="X?", panel=[0, 0]) - fig.basemap(region=[0, 20, 0, 10], projection="X?", panel=[0, 1]) - fig.basemap(region=[0, 10, 0, 20], projection="X?", panel=[1, 0]) - fig.basemap(region=[0, 20, 0, 20], projection="X?", panel=[1, 1]) -fig.show() - -############################################################################### -# In this example, we define a 2-row, 2-column (2x2) subplot layout using -# :meth:`pygmt.Figure.subplot`. The overall figure dimensions is set to be -# 15 cm wide and 6 cm high (``figsize=["15c", "6c"]``). In addition, we use -# some optional parameters to fine-tune some details of the figure creation: -# -# - ``autolabel=True``: Each subplot is automatically labelled abcd -# - ``margins=["0.1c", "0.2c"]``: adjusts the space between adjacent subplots. -# In this case, it is set as 0.1 cm in the X direction and 0.2 cm in the Y -# direction. -# - ``title="My Subplot Heading"``: adds a title on top of the whole figure. -# -# Notice that each subplot was set to use a linear projection ``"X?"``. -# Usually, we need to specify the width and height of the map frame, but it is -# also possible to use a question mark ``"?"`` to let GMT decide automatically -# on what is the most appropriate width/height for the each subplot's map -# frame. - -############################################################################### -# .. tip:: -# -# In the above example, we used the following commands to activate the -# four subplots explicitly one after another:: -# -# fig.basemap(..., panel=[0, 0]) -# fig.basemap(..., panel=[0, 1]) -# fig.basemap(..., panel=[1, 0]) -# fig.basemap(..., panel=[1, 1]) -# -# In fact, we can just use ``fig.basemap(..., panel=True)`` without -# specifying any subplot index number, and GMT will automatically activate -# the next subplot panel. - -############################################################################### -# .. note:: -# -# All plotting functions (e.g. :meth:`pygmt.Figure.coast`, -# :meth:`pygmt.Figure.text`, etc) are able to use ``panel`` parameter when -# in subplot mode. Once a panel is activated using ``panel`` or -# :meth:`pygmt.Figure.set_panel`, subsequent plotting commands that don't -# set a ``panel`` will have their elements added to the same panel as -# before. - -############################################################################### -# Shared X and Y axis labels -# -------------------------- -# In the example above with the four subplots, the two subplots for each row -# have the same Y-axis range, and the two subplots for each column have the -# same X-axis range. You can use the ``sharex``/``sharey`` parameters to set a -# common X and/or Y axis between subplots. - -fig = pygmt.Figure() -with fig.subplot( - nrows=2, - ncols=2, - figsize=("15c", "6c"), # width of 15 cm, height of 6 cm - autolabel=True, - margins=["0.3c", "0.2c"], # horizontal 0.3 cm and vertical 0.2 cm margins - title="My Subplot Heading", - sharex="b", # shared x-axis on the bottom side - sharey="l", # shared y-axis on the left side - frame="WSrt", -): - fig.basemap(region=[0, 10, 0, 10], projection="X?", panel=True) - fig.basemap(region=[0, 20, 0, 10], projection="X?", panel=True) - fig.basemap(region=[0, 10, 0, 20], projection="X?", panel=True) - fig.basemap(region=[0, 20, 0, 20], projection="X?", panel=True) -fig.show() - -############################################################################### -# ``sharex="b"`` indicates that subplots in a column will share the x-axis, and -# only the **b**\ ottom axis is displayed. ``sharey="l"`` indicates that -# subplots within a row will share the y-axis, and only the **l**\ eft axis is -# displayed. -# -# Of course, instead of using the ``sharex``/``sharey`` option, you can also -# set a different ``frame`` for each subplot to control the axis properties -# individually for each subplot. - -############################################################################### -# Advanced subplot layouts -# ------------------------ -# -# Nested subplot are currently not supported. If you want to create more -# complex subplot layouts, some manual adjustments are needed. -# -# The following example draws three subplots in a 2-row, 2-column layout, with -# the first subplot occupying the first row. - -fig = pygmt.Figure() -# Bottom row, two subplots -with fig.subplot(nrows=1, ncols=2, figsize=("15c", "3c"), autolabel="b)"): - fig.basemap( - region=[0, 5, 0, 5], projection="X?", frame=["af", "WSne"], panel=[0, 0] - ) - fig.basemap( - region=[0, 5, 0, 5], projection="X?", frame=["af", "WSne"], panel=[0, 1] - ) -# Move plot origin by 1 cm above the height of the entire figure -fig.shift_origin(yshift="h+1c") -# Top row, one subplot -with fig.subplot(nrows=1, ncols=1, figsize=("15c", "3c"), autolabel="a)"): - fig.basemap( - region=[0, 10, 0, 10], projection="X?", frame=["af", "WSne"], panel=[0, 0] - ) - fig.text(text="TEXT", x=5, y=5) - -fig.show() - -############################################################################### -# -# We start by drawing the bottom two subplots, setting ``autolabel="b)"`` so -# that the subplots are labelled 'b)' and 'c)'. Next, we use -# :meth:`pygmt.Figure.shift_origin` to move the plot origin 1 cm above the -# **h**\ eight of the entire figure that is currently plotted (i.e. the bottom -# row subplots). A single subplot is then plotted on the top row. You may need -# to adjust the ``yshift`` parameter to make your plot look nice. This top row -# uses ``autolabel="a)"``, and we also plotted some text inside. Note that -# ``projection="X?"`` was used to let GMT automatically determine the size of -# the subplot according to the size of the subplot area. - -############################################################################### -# You can also manually override the ``autolabel`` for each subplot using for -# example, ``fig.set_panel(..., fixedlabel="b) Panel 2")`` which would allow -# you to manually label a single subplot as you wish. This can be useful for -# adding a more descriptive subtitle to individual subplots. +""" +Making subplots +=============== + +When you're preparing a figure for a paper, there will often be times when +you'll need to put many individual plots into one large figure, and label them +'abcd'. These individual plots are called subplots. + +There are two main ways to create subplots in GMT: + +- Use :meth:`pygmt.Figure.shift_origin` to manually move each individual plot + to the right position. +- Use :meth:`pygmt.Figure.subplot` to define the layout of the subplots. + +The first method is easier to use and should handle simple cases involving a +couple of subplots. For more advanced subplot layouts, however, we recommend the +use of :meth:`pygmt.Figure.subplot` which offers finer grained control, and +this is what the tutorial below will cover. +""" +# sphinx_gallery_thumbnail_number = 3 + +import pygmt + +############################################################################### +# +# Let's start by initializing a :class:`pygmt.Figure` instance. + +fig = pygmt.Figure() + +############################################################################### +# Define subplot layout +# --------------------- +# +# The :meth:`pygmt.Figure.subplot` function is used to set up the layout, size, +# and other attributes of the figure. It divides the whole canvas into regular +# grid areas with *n* rows and *m* columns. Each grid area can contain an +# individual subplot. For example: + +############################################################################### +# .. code-block:: default +# +# with fig.subplot(nrows=2, ncols=3, figsize=("15c", "6c"), frame="lrtb"): +# ... + +############################################################################### +# will define our figure to have a 2 row and 3 column grid layout. +# ``figsize=("15c", "6c")`` defines the overall size of the figure to be 15 cm +# wide by 6 cm high. Using ``frame="lrtb"`` allows us to customize the map frame +# for all subplots instead of setting them individually. The figure layout will +# look like the following: + +with fig.subplot(nrows=2, ncols=3, figsize=("15c", "6c"), frame="lrtb"): + for i in range(2): # row number starting from 0 + for j in range(3): # column number starting from 0 + index = i * 3 + j # index number starting from 0 + with fig.set_panel(panel=index): # sets the current panel + fig.text( + position="MC", + text=f"index: {index}; row: {i}, col: {j}", + region=[0, 1, 0, 1], + ) +fig.show() + +############################################################################### +# The :meth:`pygmt.Figure.set_panel` function activates a specified subplot, and +# all subsequent plotting functions will take place in that subplot panel. This +# is similar to matplotlib's ``plt.sca`` method. In order to specify a subplot, +# you will need to provide the identifier for that subplot via the ``panel`` +# parameter. Pass in either the *index* number, or a tuple/list like +# (*row*, *col*) to ``panel``. + +############################################################################### +# .. note:: +# +# The row and column numbering starts from 0. So for a subplot layout with +# N rows and M columns, row numbers will go from 0 to N-1, and column +# numbers will go from 0 to M-1. + +############################################################################### +# For example, to activate the subplot on the top right corner (index: 2) at +# *row*\=0 and *col*\=2, so that all subsequent plotting commands happen +# there, you can use the following command: + +############################################################################### +# .. code-block:: default +# +# with fig.set_panel(panel=[0, 2]): +# ... + +############################################################################### +# Making your first subplot +# ------------------------- +# Next, let's use what we learned above to make a 2 row by 2 column subplot +# figure. We'll also pick up on some new parameters to configure our subplot. + +fig = pygmt.Figure() +with fig.subplot( + nrows=2, + ncols=2, + figsize=("15c", "6c"), + autolabel=True, + frame=["af", "WSne"], + margins=["0.1c", "0.2c"], + title="My Subplot Heading", +): + fig.basemap(region=[0, 10, 0, 10], projection="X?", panel=[0, 0]) + fig.basemap(region=[0, 20, 0, 10], projection="X?", panel=[0, 1]) + fig.basemap(region=[0, 10, 0, 20], projection="X?", panel=[1, 0]) + fig.basemap(region=[0, 20, 0, 20], projection="X?", panel=[1, 1]) +fig.show() + +############################################################################### +# In this example, we define a 2-row, 2-column (2x2) subplot layout using +# :meth:`pygmt.Figure.subplot`. The overall figure dimensions is set to be +# 15 cm wide and 6 cm high (``figsize=["15c", "6c"]``). In addition, we use +# some optional parameters to fine-tune some details of the figure creation: +# +# - ``autolabel=True``: Each subplot is automatically labelled abcd +# - ``margins=["0.1c", "0.2c"]``: adjusts the space between adjacent subplots. +# In this case, it is set as 0.1 cm in the X direction and 0.2 cm in the Y +# direction. +# - ``title="My Subplot Heading"``: adds a title on top of the whole figure. +# +# Notice that each subplot was set to use a linear projection ``"X?"``. +# Usually, we need to specify the width and height of the map frame, but it is +# also possible to use a question mark ``"?"`` to let GMT decide automatically +# on what is the most appropriate width/height for the each subplot's map +# frame. + +############################################################################### +# .. tip:: +# +# In the above example, we used the following commands to activate the +# four subplots explicitly one after another:: +# +# fig.basemap(..., panel=[0, 0]) +# fig.basemap(..., panel=[0, 1]) +# fig.basemap(..., panel=[1, 0]) +# fig.basemap(..., panel=[1, 1]) +# +# In fact, we can just use ``fig.basemap(..., panel=True)`` without +# specifying any subplot index number, and GMT will automatically activate +# the next subplot panel. + +############################################################################### +# .. note:: +# +# All plotting functions (e.g. :meth:`pygmt.Figure.coast`, +# :meth:`pygmt.Figure.text`, etc) are able to use ``panel`` parameter when +# in subplot mode. Once a panel is activated using ``panel`` or +# :meth:`pygmt.Figure.set_panel`, subsequent plotting commands that don't +# set a ``panel`` will have their elements added to the same panel as +# before. + +############################################################################### +# Shared X and Y axis labels +# -------------------------- +# In the example above with the four subplots, the two subplots for each row +# have the same Y-axis range, and the two subplots for each column have the +# same X-axis range. You can use the ``sharex``/``sharey`` parameters to set a +# common X and/or Y axis between subplots. + +fig = pygmt.Figure() +with fig.subplot( + nrows=2, + ncols=2, + figsize=("15c", "6c"), # width of 15 cm, height of 6 cm + autolabel=True, + margins=["0.3c", "0.2c"], # horizontal 0.3 cm and vertical 0.2 cm margins + title="My Subplot Heading", + sharex="b", # shared x-axis on the bottom side + sharey="l", # shared y-axis on the left side + frame="WSrt", +): + fig.basemap(region=[0, 10, 0, 10], projection="X?", panel=True) + fig.basemap(region=[0, 20, 0, 10], projection="X?", panel=True) + fig.basemap(region=[0, 10, 0, 20], projection="X?", panel=True) + fig.basemap(region=[0, 20, 0, 20], projection="X?", panel=True) +fig.show() + +############################################################################### +# ``sharex="b"`` indicates that subplots in a column will share the x-axis, and +# only the **b**\ ottom axis is displayed. ``sharey="l"`` indicates that +# subplots within a row will share the y-axis, and only the **l**\ eft axis is +# displayed. +# +# Of course, instead of using the ``sharex``/``sharey`` option, you can also +# set a different ``frame`` for each subplot to control the axis properties +# individually for each subplot. + +############################################################################### +# Advanced subplot layouts +# ------------------------ +# +# Nested subplot are currently not supported. If you want to create more +# complex subplot layouts, some manual adjustments are needed. +# +# The following example draws three subplots in a 2-row, 2-column layout, with +# the first subplot occupying the first row. + +fig = pygmt.Figure() +# Bottom row, two subplots +with fig.subplot(nrows=1, ncols=2, figsize=("15c", "3c"), autolabel="b)"): + fig.basemap( + region=[0, 5, 0, 5], projection="X?", frame=["af", "WSne"], panel=[0, 0] + ) + fig.basemap( + region=[0, 5, 0, 5], projection="X?", frame=["af", "WSne"], panel=[0, 1] + ) +# Move plot origin by 1 cm above the height of the entire figure +fig.shift_origin(yshift="h+1c") +# Top row, one subplot +with fig.subplot(nrows=1, ncols=1, figsize=("15c", "3c"), autolabel="a)"): + fig.basemap( + region=[0, 10, 0, 10], projection="X?", frame=["af", "WSne"], panel=[0, 0] + ) + fig.text(text="TEXT", x=5, y=5) + +fig.show() + +############################################################################### +# +# We start by drawing the bottom two subplots, setting ``autolabel="b)"`` so +# that the subplots are labelled 'b)' and 'c)'. Next, we use +# :meth:`pygmt.Figure.shift_origin` to move the plot origin 1 cm above the +# **h**\ eight of the entire figure that is currently plotted (i.e. the bottom +# row subplots). A single subplot is then plotted on the top row. You may need +# to adjust the ``yshift`` parameter to make your plot look nice. This top row +# uses ``autolabel="a)"``, and we also plotted some text inside. Note that +# ``projection="X?"`` was used to let GMT automatically determine the size of +# the subplot according to the size of the subplot area. + +############################################################################### +# You can also manually override the ``autolabel`` for each subplot using for +# example, ``fig.set_panel(..., fixedlabel="b) Panel 2")`` which would allow +# you to manually label a single subplot as you wish. This can be useful for +# adding a more descriptive subtitle to individual subplots. diff --git a/examples/tutorials/text.py b/examples/tutorials/text.py index 8474ffbbb3f..2603ec66142 100644 --- a/examples/tutorials/text.py +++ b/examples/tutorials/text.py @@ -1,155 +1,155 @@ -""" -Plotting text -============= - -It is often useful to add annotations to a map plot. This is handled by -:meth:`pygmt.Figure.text`. - -.. note:: - - This tutorial assumes the use of a Python notebook, such as IPython or Jupyter Notebook. - To see the figures while using a Python script instead, use - ``fig.show(method="external")`` to display the figure in the default PDF viewer. - - To save the figure, use ``fig.savefig("figname.pdf")`` where ``"figname.pdf"`` - is the desired name and file extension for the saved figure. -""" -# sphinx_gallery_thumbnail_number = 3 - -import os - -import pygmt - -############################################################################### -# Basic map annotation -# -------------------- -# -# Text annotations can be added to a map using the :meth:`pygmt.Figure.text` -# method of the :class:`pygmt.Figure` class. -# -# Here we create a simple map and add an annotation using the ``text``, ``x``, -# and ``y`` parameters to specify the annotation text and position in the -# projection frame. ``text`` accepts *str* types, while ``x``, and ``y`` -# accepts either *int* or *float* numbers, or a list/array of numbers. - -fig = pygmt.Figure() -with pygmt.config(MAP_FRAME_TYPE="plain"): - fig.basemap(region=[108, 120, -5, 8], projection="M20c", frame="a") -fig.coast(land="black", water="skyblue") - -# Plotting text annotations using single elements -fig.text(text="SOUTH CHINA SEA", x=112, y=6) - -# Plotting text annotations using lists of elements -fig.text(text=["CELEBES SEA", "JAVA SEA"], x=[119, 112], y=[3.25, -4.6]) - -fig.show() - -############################################################################### -# Changing font style -# ------------------- -# The size, family/weight, and color of an annotation can be specified using -# the ``font`` parameter. -# -# A list of all recognised fonts can be found at -# :gmt-docs:`cookbook/postscript-fonts.html`, including details of how to use -# non-default fonts. - -fig = pygmt.Figure() -with pygmt.config(MAP_FRAME_TYPE="plain"): - fig.basemap(region=[108, 120, -5, 8], projection="M20c", frame="a") -fig.coast(land="black", water="skyblue") - -# Customising the font style -fig.text(text="BORNEO", x=114.0, y=0.5, font="22p,Helvetica-Bold,white") - -fig.show() - -############################################################################### -# Plotting from a text file -# ------------------------- -# -# It is also possible to add annotations from a file containing ``x``, ``y``, and -# ``text`` fields. Here we give a complete example. - -fig = pygmt.Figure() -with pygmt.config(MAP_FRAME_TYPE="plain"): - fig.basemap(region=[108, 120, -5, 8], projection="M20c", frame="a") -fig.coast(land="black", water="skyblue") - -# Create space-delimited file -with open("examples.txt", "w") as f: - f.write("114 0.5 0 22p,Helvetica-Bold,white CM BORNEO\n") - f.write("119 3.25 0 12p,Helvetica-Bold,black CM CELEBES SEA\n") - f.write("112 -4.6 0 12p,Helvetica-Bold,black CM JAVA SEA\n") - f.write("112 6 40 12p,Helvetica-Bold,black CM SOUTH CHINA SEA\n") - f.write("119.12 7.25 -40 12p,Helvetica-Bold,black CM SULU SEA\n") - f.write("118.4 -1 65 12p,Helvetica-Bold,black CM MAKASSAR STRAIT\n") - -# Plot region names / sea names from a text file, where -# the longitude (x) and latitude (y) coordinates are in the first two columns. -# Setting angle/font/justify to ``True`` will indicate that those columns are -# present in the text file too (Note: must be in that order!). -# Finally, the text to be printed will be in the last column -fig.text(textfiles="examples.txt", angle=True, font=True, justify=True) - -# Cleanups -os.remove("examples.txt") - -fig.show() - -############################################################################### -# ``justify`` parameter -# --------------------- -# -# ``justify`` is used to define the anchor point for the bounding box for text -# being added to a plot. The following code segment demonstrates the -# positioning of the anchor point relative to the text. -# -# The anchor is specified with a two letter (order independent) code, chosen -# from: -# -# * Vertical anchor: **T**\(op), **M**\(iddle), **B**\(ottom) -# * Horizontal anchor: **L**\(eft), **C**\(entre), **R**\(ight) - -fig = pygmt.Figure() -fig.basemap(region=[0, 3, 0, 3], projection="X10c", frame=["WSne", "af0.5g"]) -for position in ("TL", "TC", "TR", "ML", "MC", "MR", "BL", "BC", "BR"): - fig.text( - text=position, - position=position, - font="28p,Helvetica-Bold,black", - justify=position, - ) -fig.show() - -############################################################################### -# ``angle`` parameter -# ------------------- -# ``angle`` is an optional parameter used to specify the clockwise rotation of -# the text from the horizontal. - -fig = pygmt.Figure() -fig.basemap(region=[0, 4, 0, 4], projection="X5c", frame="WSen") -for i in range(0, 360, 30): - fig.text(text=f"` {i}@.", x=2, y=2, justify="LM", angle=i) -fig.show() - -############################################################################### -# ``fill`` parameter -# ------------------ -# -# ``fill`` is used to set the fill color of the area surrounding the text. - -fig = pygmt.Figure() -fig.basemap(region=[0, 1, 0, 1], projection="X5c", frame="WSen") -fig.text(text="Green", x=0.5, y=0.5, fill="green") -fig.show() - -############################################################################### -# Advanced configuration -# ---------------------- -# -# For crafting more advanced styles, be sure to check out the GMT documentation -# at :gmt-docs:`text.html` and also the cookbook at -# :gmt-docs:`cookbook/features.html#placement-of-text`. Good luck! +""" +Plotting text +============= + +It is often useful to add annotations to a map plot. This is handled by +:meth:`pygmt.Figure.text`. + +.. note:: + + This tutorial assumes the use of a Python notebook, such as IPython or Jupyter Notebook. + To see the figures while using a Python script instead, use + ``fig.show(method="external")`` to display the figure in the default PDF viewer. + + To save the figure, use ``fig.savefig("figname.pdf")`` where ``"figname.pdf"`` + is the desired name and file extension for the saved figure. +""" +# sphinx_gallery_thumbnail_number = 3 + +import os + +import pygmt + +############################################################################### +# Basic map annotation +# -------------------- +# +# Text annotations can be added to a map using the :meth:`pygmt.Figure.text` +# method of the :class:`pygmt.Figure` class. +# +# Here we create a simple map and add an annotation using the ``text``, ``x``, +# and ``y`` parameters to specify the annotation text and position in the +# projection frame. ``text`` accepts *str* types, while ``x``, and ``y`` +# accepts either *int* or *float* numbers, or a list/array of numbers. + +fig = pygmt.Figure() +with pygmt.config(MAP_FRAME_TYPE="plain"): + fig.basemap(region=[108, 120, -5, 8], projection="M20c", frame="a") +fig.coast(land="black", water="skyblue") + +# Plotting text annotations using single elements +fig.text(text="SOUTH CHINA SEA", x=112, y=6) + +# Plotting text annotations using lists of elements +fig.text(text=["CELEBES SEA", "JAVA SEA"], x=[119, 112], y=[3.25, -4.6]) + +fig.show() + +############################################################################### +# Changing font style +# ------------------- +# The size, family/weight, and color of an annotation can be specified using +# the ``font`` parameter. +# +# A list of all recognised fonts can be found at +# :gmt-docs:`cookbook/postscript-fonts.html`, including details of how to use +# non-default fonts. + +fig = pygmt.Figure() +with pygmt.config(MAP_FRAME_TYPE="plain"): + fig.basemap(region=[108, 120, -5, 8], projection="M20c", frame="a") +fig.coast(land="black", water="skyblue") + +# Customising the font style +fig.text(text="BORNEO", x=114.0, y=0.5, font="22p,Helvetica-Bold,white") + +fig.show() + +############################################################################### +# Plotting from a text file +# ------------------------- +# +# It is also possible to add annotations from a file containing ``x``, ``y``, and +# ``text`` fields. Here we give a complete example. + +fig = pygmt.Figure() +with pygmt.config(MAP_FRAME_TYPE="plain"): + fig.basemap(region=[108, 120, -5, 8], projection="M20c", frame="a") +fig.coast(land="black", water="skyblue") + +# Create space-delimited file +with open("examples.txt", "w") as f: + f.write("114 0.5 0 22p,Helvetica-Bold,white CM BORNEO\n") + f.write("119 3.25 0 12p,Helvetica-Bold,black CM CELEBES SEA\n") + f.write("112 -4.6 0 12p,Helvetica-Bold,black CM JAVA SEA\n") + f.write("112 6 40 12p,Helvetica-Bold,black CM SOUTH CHINA SEA\n") + f.write("119.12 7.25 -40 12p,Helvetica-Bold,black CM SULU SEA\n") + f.write("118.4 -1 65 12p,Helvetica-Bold,black CM MAKASSAR STRAIT\n") + +# Plot region names / sea names from a text file, where +# the longitude (x) and latitude (y) coordinates are in the first two columns. +# Setting angle/font/justify to ``True`` will indicate that those columns are +# present in the text file too (Note: must be in that order!). +# Finally, the text to be printed will be in the last column +fig.text(textfiles="examples.txt", angle=True, font=True, justify=True) + +# Cleanups +os.remove("examples.txt") + +fig.show() + +############################################################################### +# ``justify`` parameter +# --------------------- +# +# ``justify`` is used to define the anchor point for the bounding box for text +# being added to a plot. The following code segment demonstrates the +# positioning of the anchor point relative to the text. +# +# The anchor is specified with a two letter (order independent) code, chosen +# from: +# +# * Vertical anchor: **T**\(op), **M**\(iddle), **B**\(ottom) +# * Horizontal anchor: **L**\(eft), **C**\(entre), **R**\(ight) + +fig = pygmt.Figure() +fig.basemap(region=[0, 3, 0, 3], projection="X10c", frame=["WSne", "af0.5g"]) +for position in ("TL", "TC", "TR", "ML", "MC", "MR", "BL", "BC", "BR"): + fig.text( + text=position, + position=position, + font="28p,Helvetica-Bold,black", + justify=position, + ) +fig.show() + +############################################################################### +# ``angle`` parameter +# ------------------- +# ``angle`` is an optional parameter used to specify the clockwise rotation of +# the text from the horizontal. + +fig = pygmt.Figure() +fig.basemap(region=[0, 4, 0, 4], projection="X5c", frame="WSen") +for i in range(0, 360, 30): + fig.text(text=f"` {i}@.", x=2, y=2, justify="LM", angle=i) +fig.show() + +############################################################################### +# ``fill`` parameter +# ------------------ +# +# ``fill`` is used to set the fill color of the area surrounding the text. + +fig = pygmt.Figure() +fig.basemap(region=[0, 1, 0, 1], projection="X5c", frame="WSen") +fig.text(text="Green", x=0.5, y=0.5, fill="green") +fig.show() + +############################################################################### +# Advanced configuration +# ---------------------- +# +# For crafting more advanced styles, be sure to check out the GMT documentation +# at :gmt-docs:`text.html` and also the cookbook at +# :gmt-docs:`cookbook/features.html#placement-of-text`. Good luck! diff --git a/package.json b/package.json index b9b42077037..3f69cdb5764 100644 --- a/package.json +++ b/package.json @@ -1,8 +1,8 @@ -{ - "scripts": { - "build:miniconda": "curl -o ~/miniconda.sh https://repo.anaconda.com/miniconda/Miniconda3-latest-Linux-x86_64.sh && bash ~/miniconda.sh -b -p $HOME/miniconda", - "build:pygmt": "conda install mamba -c conda-forge -y && mamba env create -f environment.yml && source activate pygmt && make install", - "build:docs": "source activate pygmt && cd doc && make all && mv _build/html ../public", - "build": "export PATH=$HOME/miniconda/bin:$PATH && npm run build:miniconda && npm run build:pygmt && npm run build:docs" - } -} +{ + "scripts": { + "build:miniconda": "curl -o ~/miniconda.sh https://repo.anaconda.com/miniconda/Miniconda3-latest-Linux-x86_64.sh && bash ~/miniconda.sh -b -p $HOME/miniconda", + "build:pygmt": "conda install mamba -c conda-forge -y && mamba env create -f environment.yml && source activate pygmt && make install", + "build:docs": "source activate pygmt && cd doc && make all && mv _build/html ../public", + "build": "export PATH=$HOME/miniconda/bin:$PATH && npm run build:miniconda && npm run build:pygmt && npm run build:docs" + } +} diff --git a/pygmt/__init__.py b/pygmt/__init__.py index 8c29a5c0b10..f604f4f3249 100644 --- a/pygmt/__init__.py +++ b/pygmt/__init__.py @@ -1,201 +1,201 @@ -# pylint: disable=missing-docstring -# -# The main API for PyGMT. -# -# All of PyGMT is operated on a "modern mode session" (new to GMT6). When you -# import the pygmt library, a new session will be started automatically. The -# session will be closed when the current Python process terminates. Thus, the -# Python API does not expose the `gmt begin` and `gmt end` commands. - -import atexit as _atexit - -from pkg_resources import get_distribution - -# Import modules to make the high-level GMT Python API -from pygmt import datasets -from pygmt.figure import Figure -from pygmt.modules import GMTDataArrayAccessor, config -from pygmt.session_management import begin as _begin -from pygmt.session_management import end as _end -from pygmt.src import ( - blockmedian, - grd2cpt, - grdcut, - grdfilter, - grdinfo, - grdtrack, - info, - makecpt, - surface, - which, - x2sys_cross, - x2sys_init, -) - -# Get semantic version through setuptools-scm -__version__ = f'v{get_distribution("pygmt").version}' # e.g. v0.1.2.dev3+g0ab3cd78 -__commit__ = __version__.split("+g")[-1] # 0ab3cd78 - -# Start our global modern mode session -_begin() -# Tell Python to run _end when shutting down -_atexit.register(_end) - - -def print_clib_info(): - """ - Print information about the GMT shared library that we can find. - - Includes the GMT version, default values for parameters, the path to the - ``libgmt`` shared library, and GMT directories. - """ - from pygmt.clib import Session - - lines = ["GMT library information:"] - with Session() as ses: - for key in sorted(ses.info): - lines.append(" {}: {}".format(key, ses.info[key])) - print("\n".join(lines)) - - -def show_versions(): - """ - Prints various dependency versions useful when submitting bug reports. This - includes information about: - - - PyGMT itself - - System information (Python version, Operating System) - - Core dependency versions (Numpy, Pandas, Xarray, etc) - - GMT library information - """ - - import importlib - import platform - import subprocess - import sys - - def _get_module_version(modname): - """ - Get version information of a Python module. - """ - try: - if modname in sys.modules: - module = sys.modules[modname] - else: - module = importlib.import_module(modname) - - try: - return module.__version__ - except AttributeError: - return module.version - except ImportError: - return None - - def _get_ghostscript_version(): - """ - Get ghostscript version. - """ - os_name = sys.platform - if os_name.startswith(("linux", "freebsd", "darwin")): - cmds = ["gs"] - elif os_name == "win32": - cmds = ["gswin64c.exe", "gswin32c.exe"] - else: - return None - - for gs_cmd in cmds: - try: - version = subprocess.check_output( - [gs_cmd, "--version"], universal_newlines=True - ).strip() - return version - except FileNotFoundError: - continue - return None - - def _get_gmt_version(): - """ - Get GMT version. - """ - try: - version = subprocess.check_output( - ["gmt", "--version"], universal_newlines=True - ).strip() - return version - except FileNotFoundError: - return None - - sys_info = { - "python": sys.version.replace("\n", " "), - "executable": sys.executable, - "machine": platform.platform(), - } - - deps = ["numpy", "pandas", "xarray", "netCDF4", "packaging"] - - print("PyGMT information:") - print(f" version: {__version__}") - - print("System information:") - for key, val in sys_info.items(): - print(f" {key}: {val}") - - print("Dependency information:") - for modname in deps: - print(f" {modname}: {_get_module_version(modname)}") - print(f" ghostscript: {_get_ghostscript_version()}") - print(f" gmt: {_get_gmt_version()}") - - print_clib_info() - - -def test(doctest=True, verbose=True, coverage=False, figures=True): - """ - Run the test suite. - - Uses `pytest `__ to discover and run the tests. If you - haven't already, you can install it with `conda - `__ or `pip `__. - - Parameters - ---------- - - doctest : bool - If ``True``, will run the doctests as well (code examples that start - with a ``>>>`` in the docs). - verbose : bool - If ``True``, will print extra information during the test run. - coverage : bool - If ``True``, will run test coverage analysis on the code as well. - Requires ``pytest-cov``. - figures : bool - If ``True``, will test generated figures against saved baseline - figures. Requires ``pytest-mpl`` and ``matplotlib``. - - Raises - ------ - - AssertionError - If pytest returns a non-zero error code indicating that some tests have - failed. - """ - import pytest - - show_versions() - - package = __name__ - - args = [] - if verbose: - args.append("-vv") - if coverage: - args.append("--cov={}".format(package)) - args.append("--cov-report=term-missing") - if doctest: - args.append("--doctest-modules") - if figures: - args.append("--mpl") - args.append("--pyargs") - args.append(package) - status = pytest.main(args) - assert status == 0, "Some tests have failed." +# pylint: disable=missing-docstring +# +# The main API for PyGMT. +# +# All of PyGMT is operated on a "modern mode session" (new to GMT6). When you +# import the pygmt library, a new session will be started automatically. The +# session will be closed when the current Python process terminates. Thus, the +# Python API does not expose the `gmt begin` and `gmt end` commands. + +import atexit as _atexit + +from pkg_resources import get_distribution + +# Import modules to make the high-level GMT Python API +from pygmt import datasets +from pygmt.figure import Figure +from pygmt.modules import GMTDataArrayAccessor, config +from pygmt.session_management import begin as _begin +from pygmt.session_management import end as _end +from pygmt.src import ( + blockmedian, + grd2cpt, + grdcut, + grdfilter, + grdinfo, + grdtrack, + info, + makecpt, + surface, + which, + x2sys_cross, + x2sys_init, +) + +# Get semantic version through setuptools-scm +__version__ = f'v{get_distribution("pygmt").version}' # e.g. v0.1.2.dev3+g0ab3cd78 +__commit__ = __version__.split("+g")[-1] # 0ab3cd78 + +# Start our global modern mode session +_begin() +# Tell Python to run _end when shutting down +_atexit.register(_end) + + +def print_clib_info(): + """ + Print information about the GMT shared library that we can find. + + Includes the GMT version, default values for parameters, the path to the + ``libgmt`` shared library, and GMT directories. + """ + from pygmt.clib import Session + + lines = ["GMT library information:"] + with Session() as ses: + for key in sorted(ses.info): + lines.append(" {}: {}".format(key, ses.info[key])) + print("\n".join(lines)) + + +def show_versions(): + """ + Prints various dependency versions useful when submitting bug reports. This + includes information about: + + - PyGMT itself + - System information (Python version, Operating System) + - Core dependency versions (Numpy, Pandas, Xarray, etc) + - GMT library information + """ + + import importlib + import platform + import subprocess + import sys + + def _get_module_version(modname): + """ + Get version information of a Python module. + """ + try: + if modname in sys.modules: + module = sys.modules[modname] + else: + module = importlib.import_module(modname) + + try: + return module.__version__ + except AttributeError: + return module.version + except ImportError: + return None + + def _get_ghostscript_version(): + """ + Get ghostscript version. + """ + os_name = sys.platform + if os_name.startswith(("linux", "freebsd", "darwin")): + cmds = ["gs"] + elif os_name == "win32": + cmds = ["gswin64c.exe", "gswin32c.exe"] + else: + return None + + for gs_cmd in cmds: + try: + version = subprocess.check_output( + [gs_cmd, "--version"], universal_newlines=True + ).strip() + return version + except FileNotFoundError: + continue + return None + + def _get_gmt_version(): + """ + Get GMT version. + """ + try: + version = subprocess.check_output( + ["gmt", "--version"], universal_newlines=True + ).strip() + return version + except FileNotFoundError: + return None + + sys_info = { + "python": sys.version.replace("\n", " "), + "executable": sys.executable, + "machine": platform.platform(), + } + + deps = ["numpy", "pandas", "xarray", "netCDF4", "packaging"] + + print("PyGMT information:") + print(f" version: {__version__}") + + print("System information:") + for key, val in sys_info.items(): + print(f" {key}: {val}") + + print("Dependency information:") + for modname in deps: + print(f" {modname}: {_get_module_version(modname)}") + print(f" ghostscript: {_get_ghostscript_version()}") + print(f" gmt: {_get_gmt_version()}") + + print_clib_info() + + +def test(doctest=True, verbose=True, coverage=False, figures=True): + """ + Run the test suite. + + Uses `pytest `__ to discover and run the tests. If you + haven't already, you can install it with `conda + `__ or `pip `__. + + Parameters + ---------- + + doctest : bool + If ``True``, will run the doctests as well (code examples that start + with a ``>>>`` in the docs). + verbose : bool + If ``True``, will print extra information during the test run. + coverage : bool + If ``True``, will run test coverage analysis on the code as well. + Requires ``pytest-cov``. + figures : bool + If ``True``, will test generated figures against saved baseline + figures. Requires ``pytest-mpl`` and ``matplotlib``. + + Raises + ------ + + AssertionError + If pytest returns a non-zero error code indicating that some tests have + failed. + """ + import pytest + + show_versions() + + package = __name__ + + args = [] + if verbose: + args.append("-vv") + if coverage: + args.append("--cov={}".format(package)) + args.append("--cov-report=term-missing") + if doctest: + args.append("--doctest-modules") + if figures: + args.append("--mpl") + args.append("--pyargs") + args.append(package) + status = pytest.main(args) + assert status == 0, "Some tests have failed." diff --git a/pygmt/clib/conversion.py b/pygmt/clib/conversion.py index 24c6c4470f3..97be8d81b13 100644 --- a/pygmt/clib/conversion.py +++ b/pygmt/clib/conversion.py @@ -1,320 +1,320 @@ -""" -Functions to convert data types into ctypes friendly formats. -""" -import numpy as np -import pandas as pd -from pygmt.exceptions import GMTInvalidInput - - -def dataarray_to_matrix(grid): - """ - Transform an xarray.DataArray into a data 2D array and metadata. - - Use this to extract the underlying numpy array of data and the region and - increment for the grid. - - Only allows grids with two dimensions and constant grid spacing (GMT - doesn't allow variable grid spacing). If the latitude and/or longitude - increments of the input grid are negative, the output matrix will be - sorted by the DataArray coordinates to yield positive increments. - - If the underlying data array is not C contiguous, for example if it's a - slice of a larger grid, a copy will need to be generated. - - Parameters - ---------- - grid : xarray.DataArray - The input grid as a DataArray instance. Information is retrieved from - the coordinate arrays, not from headers. - - Returns - ------- - matrix : 2d-array - The 2D array of data from the grid. - region : list - The West, East, South, North boundaries of the grid. - inc : list - The grid spacing in East-West and North-South, respectively. - - Raises - ------ - GMTInvalidInput - If the grid has more than two dimensions or variable grid spacing. - - Examples - -------- - - >>> from pygmt.datasets import load_earth_relief - >>> # Use the global Earth relief grid with 1 degree spacing - >>> grid = load_earth_relief(resolution="01d") - >>> matrix, region, inc = dataarray_to_matrix(grid) - >>> print(region) - [-180.0, 180.0, -90.0, 90.0] - >>> print(inc) - [1.0, 1.0] - >>> type(matrix) - - >>> print(matrix.shape) - (180, 360) - >>> matrix.flags.c_contiguous - True - >>> # Using a slice of the grid, the matrix will be copied to guarantee - >>> # that it's C-contiguous in memory. The increment should be unchanged. - >>> matrix, region, inc = dataarray_to_matrix(grid[10:41, 30:101]) - >>> matrix.flags.c_contiguous - True - >>> print(matrix.shape) - (31, 71) - >>> print(region) - [-150.0, -79.0, -80.0, -49.0] - >>> print(inc) - [1.0, 1.0] - >>> # but not if only taking every other grid point. - >>> matrix, region, inc = dataarray_to_matrix(grid[10:41:2, 30:101:2]) - >>> matrix.flags.c_contiguous - True - >>> print(matrix.shape) - (16, 36) - >>> print(region) - [-150.5, -78.5, -80.5, -48.5] - >>> print(inc) - [2.0, 2.0] - """ - if len(grid.dims) != 2: - raise GMTInvalidInput( - "Invalid number of grid dimensions '{}'. Must be 2.".format(len(grid.dims)) - ) - # Extract region and inc from the grid - region = [] - inc = [] - # Reverse the dims because it is rows, columns ordered. In geographic - # grids, this would be North-South, East-West. GMT's region and inc are - # East-West, North-South. - for dim in grid.dims[::-1]: - coord = grid.coords[dim].values - coord_incs = coord[1:] - coord[0:-1] - coord_inc = coord_incs[0] - if not np.allclose(coord_incs, coord_inc): - raise GMTInvalidInput( - "Grid appears to have irregular spacing in the '{}' dimension.".format( - dim - ) - ) - region.extend( - [ - coord.min() - coord_inc / 2 * grid.gmt.registration, - coord.max() + coord_inc / 2 * grid.gmt.registration, - ] - ) - inc.append(coord_inc) - - if any(i < 0 for i in inc): # Sort grid when there are negative increments - inc = [abs(i) for i in inc] - grid = grid.sortby(variables=list(grid.dims), ascending=True) - - matrix = as_c_contiguous(grid[::-1].values) - return matrix, region, inc - - -def vectors_to_arrays(vectors): - """ - Convert 1d vectors (lists, arrays or pandas.Series) to C contiguous 1d - arrays. - - Arrays must be in C contiguous order for us to pass their memory pointers - to GMT. If any are not, convert them to C order (which requires copying the - memory). This usually happens when vectors are columns of a 2d array or - have been sliced. - - If a vector is a list or pandas.Series, get the underlying numpy array. - - Parameters - ---------- - vectors : list of lists, 1d arrays or pandas.Series - The vectors that must be converted. - - Returns - ------- - arrays : list of 1d arrays - The converted numpy arrays - - Examples - -------- - - >>> import numpy as np - >>> import pandas as pd - >>> data = np.array([[1, 2], [3, 4], [5, 6]]) - >>> vectors = [data[:, 0], data[:, 1], pd.Series(data=[-1, -2, -3])] - >>> all(i.flags.c_contiguous for i in vectors) - False - >>> all(isinstance(i, np.ndarray) for i in vectors) - False - >>> arrays = vectors_to_arrays(vectors) - >>> all(i.flags.c_contiguous for i in arrays) - True - >>> all(isinstance(i, np.ndarray) for i in arrays) - True - >>> data = [[1, 2], (3, 4), range(5, 7)] - >>> all(isinstance(i, np.ndarray) for i in vectors_to_arrays(data)) - True - """ - arrays = [as_c_contiguous(np.asarray(i)) for i in vectors] - return arrays - - -def as_c_contiguous(array): - """ - Ensure a numpy array is C contiguous in memory. - - If the array is not C contiguous, a copy will be necessary. - - Parameters - ---------- - array : 1d array - The numpy array - - Returns - ------- - array : 1d array - Array is C contiguous order. - - Examples - -------- - - >>> import numpy as np - >>> data = np.array([[1, 2], [3, 4], [5, 6]]) - >>> x = data[:, 0] - >>> x - array([1, 3, 5]) - >>> x.flags.c_contiguous - False - >>> new_x = as_c_contiguous(x) - >>> new_x - array([1, 3, 5]) - >>> new_x.flags.c_contiguous - True - >>> x = np.array([8, 9, 10]) - >>> x.flags.c_contiguous - True - >>> as_c_contiguous(x).flags.c_contiguous - True - """ - if not array.flags.c_contiguous: - return array.copy(order="C") - return array - - -def kwargs_to_ctypes_array(argument, kwargs, dtype): - """ - Convert an iterable argument from kwargs into a ctypes array variable. - - If the argument is not present in kwargs, returns ``None``. - - Parameters - ---------- - argument : str - The name of the argument. - kwargs : dict - Dictionary of keyword arguments. - dtype : ctypes type - The ctypes array type (e.g., ``ctypes.c_double*4``) - - Returns - ------- - ctypes_value : ctypes array or None - - Examples - -------- - - >>> import ctypes as ct - >>> value = kwargs_to_ctypes_array("bla", {"bla": [10, 10]}, ct.c_long * 2) - >>> type(value) - - >>> should_be_none = kwargs_to_ctypes_array( - ... "swallow", {"bla": 1, "foo": [20, 30]}, ct.c_int * 2 - ... ) - >>> print(should_be_none) - None - """ - if argument in kwargs: - return dtype(*kwargs[argument]) - return None - - -def array_to_datetime(array): - """ - Convert an 1d datetime array from various types into pandas.DatetimeIndex - (i.e., numpy.datetime64). - - If the input array is not in legal datetime formats, raise a "ParseError" - exception. - - Parameters - ---------- - array : list or 1d array - The input datetime array in various formats. - - Supported types: - - - str - - numpy.datetime64 - - pandas.DateTimeIndex - - datetime.datetime and datetime.date - - Returns - ------- - array : 1d datetime array in pandas.DatetimeIndex (i.e., numpy.datetime64) - - Examples - -------- - >>> import datetime - >>> # numpy.datetime64 array - >>> x = np.array( - ... ["2010-06-01", "2011-06-01T12", "2012-01-01T12:34:56"], - ... dtype="datetime64", - ... ) - >>> array_to_datetime(x) - DatetimeIndex(['2010-06-01 00:00:00', '2011-06-01 12:00:00', - '2012-01-01 12:34:56'], - dtype='datetime64[ns]', freq=None) - - >>> # pandas.DateTimeIndex array - >>> x = pd.date_range("2013", freq="YS", periods=3) - >>> array_to_datetime(x) # doctest: +NORMALIZE_WHITESPACE - DatetimeIndex(['2013-01-01', '2014-01-01', '2015-01-01'], - dtype='datetime64[ns]', freq='AS-JAN') - - >>> # Python's built-in date and datetime - >>> x = [datetime.date(2018, 1, 1), datetime.datetime(2019, 1, 1)] - >>> array_to_datetime(x) # doctest: +NORMALIZE_WHITESPACE - DatetimeIndex(['2018-01-01', '2019-01-01'], - dtype='datetime64[ns]', freq=None) - - >>> # Raw datetime strings in various format - >>> x = [ - ... "2018", - ... "2018-02", - ... "2018-03-01", - ... "2018-04-01T01:02:03", - ... "5/1/2018", - ... "Jun 05, 2018", - ... "2018/07/02", - ... ] - >>> array_to_datetime(x) - DatetimeIndex(['2018-01-01 00:00:00', '2018-02-01 00:00:00', - '2018-03-01 00:00:00', '2018-04-01 01:02:03', - '2018-05-01 00:00:00', '2018-06-05 00:00:00', - '2018-07-02 00:00:00'], - dtype='datetime64[ns]', freq=None) - - >>> # Mixed datetime types - >>> x = [ - ... "2018-01-01", - ... np.datetime64("2018-01-01"), - ... datetime.datetime(2018, 1, 1), - ... ] - >>> array_to_datetime(x) # doctest: +NORMALIZE_WHITESPACE - DatetimeIndex(['2018-01-01', '2018-01-01', '2018-01-01'], - dtype='datetime64[ns]', freq=None) - """ - return pd.to_datetime(array) +""" +Functions to convert data types into ctypes friendly formats. +""" +import numpy as np +import pandas as pd +from pygmt.exceptions import GMTInvalidInput + + +def dataarray_to_matrix(grid): + """ + Transform an xarray.DataArray into a data 2D array and metadata. + + Use this to extract the underlying numpy array of data and the region and + increment for the grid. + + Only allows grids with two dimensions and constant grid spacing (GMT + doesn't allow variable grid spacing). If the latitude and/or longitude + increments of the input grid are negative, the output matrix will be + sorted by the DataArray coordinates to yield positive increments. + + If the underlying data array is not C contiguous, for example if it's a + slice of a larger grid, a copy will need to be generated. + + Parameters + ---------- + grid : xarray.DataArray + The input grid as a DataArray instance. Information is retrieved from + the coordinate arrays, not from headers. + + Returns + ------- + matrix : 2d-array + The 2D array of data from the grid. + region : list + The West, East, South, North boundaries of the grid. + inc : list + The grid spacing in East-West and North-South, respectively. + + Raises + ------ + GMTInvalidInput + If the grid has more than two dimensions or variable grid spacing. + + Examples + -------- + + >>> from pygmt.datasets import load_earth_relief + >>> # Use the global Earth relief grid with 1 degree spacing + >>> grid = load_earth_relief(resolution="01d") + >>> matrix, region, inc = dataarray_to_matrix(grid) + >>> print(region) + [-180.0, 180.0, -90.0, 90.0] + >>> print(inc) + [1.0, 1.0] + >>> type(matrix) + + >>> print(matrix.shape) + (180, 360) + >>> matrix.flags.c_contiguous + True + >>> # Using a slice of the grid, the matrix will be copied to guarantee + >>> # that it's C-contiguous in memory. The increment should be unchanged. + >>> matrix, region, inc = dataarray_to_matrix(grid[10:41, 30:101]) + >>> matrix.flags.c_contiguous + True + >>> print(matrix.shape) + (31, 71) + >>> print(region) + [-150.0, -79.0, -80.0, -49.0] + >>> print(inc) + [1.0, 1.0] + >>> # but not if only taking every other grid point. + >>> matrix, region, inc = dataarray_to_matrix(grid[10:41:2, 30:101:2]) + >>> matrix.flags.c_contiguous + True + >>> print(matrix.shape) + (16, 36) + >>> print(region) + [-150.5, -78.5, -80.5, -48.5] + >>> print(inc) + [2.0, 2.0] + """ + if len(grid.dims) != 2: + raise GMTInvalidInput( + "Invalid number of grid dimensions '{}'. Must be 2.".format(len(grid.dims)) + ) + # Extract region and inc from the grid + region = [] + inc = [] + # Reverse the dims because it is rows, columns ordered. In geographic + # grids, this would be North-South, East-West. GMT's region and inc are + # East-West, North-South. + for dim in grid.dims[::-1]: + coord = grid.coords[dim].values + coord_incs = coord[1:] - coord[0:-1] + coord_inc = coord_incs[0] + if not np.allclose(coord_incs, coord_inc): + raise GMTInvalidInput( + "Grid appears to have irregular spacing in the '{}' dimension.".format( + dim + ) + ) + region.extend( + [ + coord.min() - coord_inc / 2 * grid.gmt.registration, + coord.max() + coord_inc / 2 * grid.gmt.registration, + ] + ) + inc.append(coord_inc) + + if any(i < 0 for i in inc): # Sort grid when there are negative increments + inc = [abs(i) for i in inc] + grid = grid.sortby(variables=list(grid.dims), ascending=True) + + matrix = as_c_contiguous(grid[::-1].values) + return matrix, region, inc + + +def vectors_to_arrays(vectors): + """ + Convert 1d vectors (lists, arrays or pandas.Series) to C contiguous 1d + arrays. + + Arrays must be in C contiguous order for us to pass their memory pointers + to GMT. If any are not, convert them to C order (which requires copying the + memory). This usually happens when vectors are columns of a 2d array or + have been sliced. + + If a vector is a list or pandas.Series, get the underlying numpy array. + + Parameters + ---------- + vectors : list of lists, 1d arrays or pandas.Series + The vectors that must be converted. + + Returns + ------- + arrays : list of 1d arrays + The converted numpy arrays + + Examples + -------- + + >>> import numpy as np + >>> import pandas as pd + >>> data = np.array([[1, 2], [3, 4], [5, 6]]) + >>> vectors = [data[:, 0], data[:, 1], pd.Series(data=[-1, -2, -3])] + >>> all(i.flags.c_contiguous for i in vectors) + False + >>> all(isinstance(i, np.ndarray) for i in vectors) + False + >>> arrays = vectors_to_arrays(vectors) + >>> all(i.flags.c_contiguous for i in arrays) + True + >>> all(isinstance(i, np.ndarray) for i in arrays) + True + >>> data = [[1, 2], (3, 4), range(5, 7)] + >>> all(isinstance(i, np.ndarray) for i in vectors_to_arrays(data)) + True + """ + arrays = [as_c_contiguous(np.asarray(i)) for i in vectors] + return arrays + + +def as_c_contiguous(array): + """ + Ensure a numpy array is C contiguous in memory. + + If the array is not C contiguous, a copy will be necessary. + + Parameters + ---------- + array : 1d array + The numpy array + + Returns + ------- + array : 1d array + Array is C contiguous order. + + Examples + -------- + + >>> import numpy as np + >>> data = np.array([[1, 2], [3, 4], [5, 6]]) + >>> x = data[:, 0] + >>> x + array([1, 3, 5]) + >>> x.flags.c_contiguous + False + >>> new_x = as_c_contiguous(x) + >>> new_x + array([1, 3, 5]) + >>> new_x.flags.c_contiguous + True + >>> x = np.array([8, 9, 10]) + >>> x.flags.c_contiguous + True + >>> as_c_contiguous(x).flags.c_contiguous + True + """ + if not array.flags.c_contiguous: + return array.copy(order="C") + return array + + +def kwargs_to_ctypes_array(argument, kwargs, dtype): + """ + Convert an iterable argument from kwargs into a ctypes array variable. + + If the argument is not present in kwargs, returns ``None``. + + Parameters + ---------- + argument : str + The name of the argument. + kwargs : dict + Dictionary of keyword arguments. + dtype : ctypes type + The ctypes array type (e.g., ``ctypes.c_double*4``) + + Returns + ------- + ctypes_value : ctypes array or None + + Examples + -------- + + >>> import ctypes as ct + >>> value = kwargs_to_ctypes_array("bla", {"bla": [10, 10]}, ct.c_long * 2) + >>> type(value) + + >>> should_be_none = kwargs_to_ctypes_array( + ... "swallow", {"bla": 1, "foo": [20, 30]}, ct.c_int * 2 + ... ) + >>> print(should_be_none) + None + """ + if argument in kwargs: + return dtype(*kwargs[argument]) + return None + + +def array_to_datetime(array): + """ + Convert an 1d datetime array from various types into pandas.DatetimeIndex + (i.e., numpy.datetime64). + + If the input array is not in legal datetime formats, raise a "ParseError" + exception. + + Parameters + ---------- + array : list or 1d array + The input datetime array in various formats. + + Supported types: + + - str + - numpy.datetime64 + - pandas.DateTimeIndex + - datetime.datetime and datetime.date + + Returns + ------- + array : 1d datetime array in pandas.DatetimeIndex (i.e., numpy.datetime64) + + Examples + -------- + >>> import datetime + >>> # numpy.datetime64 array + >>> x = np.array( + ... ["2010-06-01", "2011-06-01T12", "2012-01-01T12:34:56"], + ... dtype="datetime64", + ... ) + >>> array_to_datetime(x) + DatetimeIndex(['2010-06-01 00:00:00', '2011-06-01 12:00:00', + '2012-01-01 12:34:56'], + dtype='datetime64[ns]', freq=None) + + >>> # pandas.DateTimeIndex array + >>> x = pd.date_range("2013", freq="YS", periods=3) + >>> array_to_datetime(x) # doctest: +NORMALIZE_WHITESPACE + DatetimeIndex(['2013-01-01', '2014-01-01', '2015-01-01'], + dtype='datetime64[ns]', freq='AS-JAN') + + >>> # Python's built-in date and datetime + >>> x = [datetime.date(2018, 1, 1), datetime.datetime(2019, 1, 1)] + >>> array_to_datetime(x) # doctest: +NORMALIZE_WHITESPACE + DatetimeIndex(['2018-01-01', '2019-01-01'], + dtype='datetime64[ns]', freq=None) + + >>> # Raw datetime strings in various format + >>> x = [ + ... "2018", + ... "2018-02", + ... "2018-03-01", + ... "2018-04-01T01:02:03", + ... "5/1/2018", + ... "Jun 05, 2018", + ... "2018/07/02", + ... ] + >>> array_to_datetime(x) + DatetimeIndex(['2018-01-01 00:00:00', '2018-02-01 00:00:00', + '2018-03-01 00:00:00', '2018-04-01 01:02:03', + '2018-05-01 00:00:00', '2018-06-05 00:00:00', + '2018-07-02 00:00:00'], + dtype='datetime64[ns]', freq=None) + + >>> # Mixed datetime types + >>> x = [ + ... "2018-01-01", + ... np.datetime64("2018-01-01"), + ... datetime.datetime(2018, 1, 1), + ... ] + >>> array_to_datetime(x) # doctest: +NORMALIZE_WHITESPACE + DatetimeIndex(['2018-01-01', '2018-01-01', '2018-01-01'], + dtype='datetime64[ns]', freq=None) + """ + return pd.to_datetime(array) diff --git a/pygmt/clib/loading.py b/pygmt/clib/loading.py index 7a9018195f4..42d0ca9921d 100644 --- a/pygmt/clib/loading.py +++ b/pygmt/clib/loading.py @@ -1,166 +1,166 @@ -""" -Utility functions to load libgmt as ctypes.CDLL. - -The path to the shared library can be found automatically by ctypes or set -through the GMT_LIBRARY_PATH environment variable. -""" -import ctypes -import os -import subprocess as sp -import sys -from ctypes.util import find_library -from pathlib import Path - -from pygmt.exceptions import GMTCLibError, GMTCLibNotFoundError, GMTOSError - - -def load_libgmt(): - """ - Find and load ``libgmt`` as a :py:class:`ctypes.CDLL`. - - By default, will look for the shared library in the directory specified by - the environment variable ``GMT_LIBRARY_PATH``. If it's not set, will let - ctypes try to find the library. - - Returns - ------- - :py:class:`ctypes.CDLL` object - The loaded shared library. - - Raises - ------ - GMTCLibNotFoundError - If there was any problem loading the library (couldn't find it or - couldn't access the functions). - """ - lib_fullnames = [] - error = True - for libname in clib_full_names(): - lib_fullnames.append(libname) - try: - libgmt = ctypes.CDLL(libname) - check_libgmt(libgmt) - error = False - break - except OSError as err: - error = err - if error: - raise GMTCLibNotFoundError( - "Error loading the GMT shared library " - f"{', '.join(lib_fullnames)}.\n {error}." - ) - return libgmt - - -def clib_names(os_name): - """ - Return the name of GMT's shared library for the current OS. - - Parameters - ---------- - os_name : str - The operating system name as given by ``sys.platform``. - - Returns - ------- - libnames : list of str - List of possible names of GMT's shared library. - """ - if os_name.startswith("linux"): - libnames = ["libgmt.so"] - elif os_name == "darwin": # Darwin is macOS - libnames = ["libgmt.dylib"] - elif os_name == "win32": - libnames = ["gmt.dll", "gmt_w64.dll", "gmt_w32.dll"] - elif os_name.startswith("freebsd"): # FreeBSD - libnames = ["libgmt.so"] - else: - raise GMTOSError(f'Operating system "{os_name}" not supported.') - return libnames - - -def clib_full_names(env=None): - """ - Return the full path of GMT's shared library for the current OS. - - Parameters - ---------- - env : dict or None - A dictionary containing the environment variables. If ``None``, will - default to ``os.environ``. - - Yields - ------ - lib_fullnames: list of str - List of possible full names of GMT's shared library. - """ - if env is None: - env = os.environ - - libnames = clib_names(os_name=sys.platform) # e.g. libgmt.so, libgmt.dylib, gmt.dll - - # Search for the library in different ways, sorted by priority. - # 1. Search for the library in GMT_LIBRARY_PATH if defined. - libpath = env.get("GMT_LIBRARY_PATH", "") # e.g. $HOME/miniconda/envs/pygmt/lib - if libpath: - for libname in libnames: - libfullpath = Path(libpath) / libname - if libfullpath.exists(): - yield str(libfullpath) - - # 2. Search for the library returned by command "gmt --show-library" - # Use `str(Path(realpath))` to avoid mixture of separators "\\" and "/" - try: - libfullpath = Path( - sp.check_output(["gmt", "--show-library"], encoding="utf-8").rstrip("\n") - ) - assert libfullpath.exists() - yield str(libfullpath) - except (FileNotFoundError, AssertionError, sp.CalledProcessError): - # the 'gmt' executable is not found - # the gmt library is not found - # the 'gmt' executable is broken - pass - - # 3. Search for DLLs in PATH by calling find_library() (Windows only) - if sys.platform == "win32": - for libname in libnames: - libfullpath = find_library(libname) - if libfullpath: - yield libfullpath - - # 4. Search for library names in the system default path - for libname in libnames: - yield libname - - -def check_libgmt(libgmt): - """ - Make sure that libgmt was loaded correctly. - - Checks if it defines some common required functions. - - Does nothing if everything is fine. Raises an exception if any of the - functions are missing. - - Parameters - ---------- - libgmt : :py:class:`ctypes.CDLL` - A shared library loaded using ctypes. - - Raises - ------ - GMTCLibError - """ - # Check if a few of the functions we need are in the library - functions = ["Create_Session", "Get_Enum", "Call_Module", "Destroy_Session"] - for func in functions: - if not hasattr(libgmt, "GMT_" + func): - # pylint: disable=protected-access - msg = ( - f"Error loading '{libgmt._name}'. Couldn't access function GMT_{func}. " - "Ensure that you have installed an up-to-date GMT version 6 library. " - "Please set the environment variable 'GMT_LIBRARY_PATH' to the " - "directory of the GMT 6 library." - ) - raise GMTCLibError(msg) +""" +Utility functions to load libgmt as ctypes.CDLL. + +The path to the shared library can be found automatically by ctypes or set +through the GMT_LIBRARY_PATH environment variable. +""" +import ctypes +import os +import subprocess as sp +import sys +from ctypes.util import find_library +from pathlib import Path + +from pygmt.exceptions import GMTCLibError, GMTCLibNotFoundError, GMTOSError + + +def load_libgmt(): + """ + Find and load ``libgmt`` as a :py:class:`ctypes.CDLL`. + + By default, will look for the shared library in the directory specified by + the environment variable ``GMT_LIBRARY_PATH``. If it's not set, will let + ctypes try to find the library. + + Returns + ------- + :py:class:`ctypes.CDLL` object + The loaded shared library. + + Raises + ------ + GMTCLibNotFoundError + If there was any problem loading the library (couldn't find it or + couldn't access the functions). + """ + lib_fullnames = [] + error = True + for libname in clib_full_names(): + lib_fullnames.append(libname) + try: + libgmt = ctypes.CDLL(libname) + check_libgmt(libgmt) + error = False + break + except OSError as err: + error = err + if error: + raise GMTCLibNotFoundError( + "Error loading the GMT shared library " + f"{', '.join(lib_fullnames)}.\n {error}." + ) + return libgmt + + +def clib_names(os_name): + """ + Return the name of GMT's shared library for the current OS. + + Parameters + ---------- + os_name : str + The operating system name as given by ``sys.platform``. + + Returns + ------- + libnames : list of str + List of possible names of GMT's shared library. + """ + if os_name.startswith("linux"): + libnames = ["libgmt.so"] + elif os_name == "darwin": # Darwin is macOS + libnames = ["libgmt.dylib"] + elif os_name == "win32": + libnames = ["gmt.dll", "gmt_w64.dll", "gmt_w32.dll"] + elif os_name.startswith("freebsd"): # FreeBSD + libnames = ["libgmt.so"] + else: + raise GMTOSError(f'Operating system "{os_name}" not supported.') + return libnames + + +def clib_full_names(env=None): + """ + Return the full path of GMT's shared library for the current OS. + + Parameters + ---------- + env : dict or None + A dictionary containing the environment variables. If ``None``, will + default to ``os.environ``. + + Yields + ------ + lib_fullnames: list of str + List of possible full names of GMT's shared library. + """ + if env is None: + env = os.environ + + libnames = clib_names(os_name=sys.platform) # e.g. libgmt.so, libgmt.dylib, gmt.dll + + # Search for the library in different ways, sorted by priority. + # 1. Search for the library in GMT_LIBRARY_PATH if defined. + libpath = env.get("GMT_LIBRARY_PATH", "") # e.g. $HOME/miniconda/envs/pygmt/lib + if libpath: + for libname in libnames: + libfullpath = Path(libpath) / libname + if libfullpath.exists(): + yield str(libfullpath) + + # 2. Search for the library returned by command "gmt --show-library" + # Use `str(Path(realpath))` to avoid mixture of separators "\\" and "/" + try: + libfullpath = Path( + sp.check_output(["gmt", "--show-library"], encoding="utf-8").rstrip("\n") + ) + assert libfullpath.exists() + yield str(libfullpath) + except (FileNotFoundError, AssertionError, sp.CalledProcessError): + # the 'gmt' executable is not found + # the gmt library is not found + # the 'gmt' executable is broken + pass + + # 3. Search for DLLs in PATH by calling find_library() (Windows only) + if sys.platform == "win32": + for libname in libnames: + libfullpath = find_library(libname) + if libfullpath: + yield libfullpath + + # 4. Search for library names in the system default path + for libname in libnames: + yield libname + + +def check_libgmt(libgmt): + """ + Make sure that libgmt was loaded correctly. + + Checks if it defines some common required functions. + + Does nothing if everything is fine. Raises an exception if any of the + functions are missing. + + Parameters + ---------- + libgmt : :py:class:`ctypes.CDLL` + A shared library loaded using ctypes. + + Raises + ------ + GMTCLibError + """ + # Check if a few of the functions we need are in the library + functions = ["Create_Session", "Get_Enum", "Call_Module", "Destroy_Session"] + for func in functions: + if not hasattr(libgmt, "GMT_" + func): + # pylint: disable=protected-access + msg = ( + f"Error loading '{libgmt._name}'. Couldn't access function GMT_{func}. " + "Ensure that you have installed an up-to-date GMT version 6 library. " + "Please set the environment variable 'GMT_LIBRARY_PATH' to the " + "directory of the GMT 6 library." + ) + raise GMTCLibError(msg) diff --git a/pygmt/clib/session.py b/pygmt/clib/session.py index 64bcd55cf4e..cc907f0e1a6 100644 --- a/pygmt/clib/session.py +++ b/pygmt/clib/session.py @@ -1,1518 +1,1518 @@ -""" -Defines the Session class to create and destroy a GMT API session and provides -access to the API functions. - -Uses ctypes to wrap most of the core functions from the C API. -""" -import ctypes as ctp -import sys -from contextlib import contextmanager - -import numpy as np -import pandas as pd -from packaging.version import Version -from pygmt.clib.conversion import ( - array_to_datetime, - as_c_contiguous, - dataarray_to_matrix, - kwargs_to_ctypes_array, - vectors_to_arrays, -) -from pygmt.clib.loading import load_libgmt -from pygmt.exceptions import ( - GMTCLibError, - GMTCLibNoSessionError, - GMTInvalidInput, - GMTVersionError, -) -from pygmt.helpers import data_kind, dummy_context - -FAMILIES = [ - "GMT_IS_DATASET", - "GMT_IS_GRID", - "GMT_IS_PALETTE", - "GMT_IS_MATRIX", - "GMT_IS_VECTOR", -] - -VIAS = ["GMT_VIA_MATRIX", "GMT_VIA_VECTOR"] - -GEOMETRIES = [ - "GMT_IS_NONE", - "GMT_IS_POINT", - "GMT_IS_LINE", - "GMT_IS_POLYGON", - "GMT_IS_PLP", - "GMT_IS_SURFACE", -] - -METHODS = ["GMT_IS_DUPLICATE", "GMT_IS_REFERENCE"] - -MODES = ["GMT_CONTAINER_ONLY", "GMT_IS_OUTPUT"] - -REGISTRATIONS = ["GMT_GRID_PIXEL_REG", "GMT_GRID_NODE_REG"] - -DTYPES = { - np.float64: "GMT_DOUBLE", - np.float32: "GMT_FLOAT", - np.int64: "GMT_LONG", - np.int32: "GMT_INT", - np.uint64: "GMT_ULONG", - np.uint32: "GMT_UINT", - np.datetime64: "GMT_DATETIME", -} - - -class Session: - """ - A GMT API session where most operations involving the C API happen. - - Works as a context manager (for use in a ``with`` block) to create a GMT C - API session and destroy it in the end to clean up memory. - - Functions of the shared library are exposed as methods of this class. Most - methods MUST be used with an open session (inside a ``with`` block). If - creating GMT data structures to communicate data, put that code inside the - same ``with`` block as the API calls that will use the data. - - By default, will let :mod:`ctypes` try to find the GMT shared library - (``libgmt``). If the environment variable ``GMT_LIBRARY_PATH`` is set, will - look for the shared library in the directory specified by it. - - A ``GMTVersionError`` exception will be raised if the GMT shared library - reports a version older than the required minimum GMT version. - - The ``session_pointer`` attribute holds a ctypes pointer to the currently - open session. - - Raises - ------ - GMTCLibNotFoundError - If there was any problem loading the library (couldn't find it or - couldn't access the functions). - GMTCLibNoSessionError - If you try to call a method outside of a 'with' block. - GMTVersionError - If the minimum required version of GMT is not found. - - Examples - -------- - - >>> from pygmt.datasets import load_earth_relief - >>> from pygmt.helpers import GMTTempFile - >>> grid = load_earth_relief() - >>> type(grid) - - >>> # Create a session and destroy it automatically when exiting the "with" - >>> # block. - >>> with Session() as ses: - ... # Create a virtual file and link to the memory block of the grid. - ... with ses.virtualfile_from_grid(grid) as fin: - ... # Create a temp file to use as output. - ... with GMTTempFile() as fout: - ... # Call the grdinfo module with the virtual file as input - ... # and the temp file as output. - ... ses.call_module( - ... "grdinfo", "{} -C ->{}".format(fin, fout.name) - ... ) - ... # Read the contents of the temp file before it's deleted. - ... print(fout.read().strip()) - ... - -180 180 -90 90 -8182 5651.5 1 1 360 180 1 1 - """ - - # The minimum version of GMT required - required_version = "6.1.1" - - @property - def session_pointer(self): - """ - The :class:`ctypes.c_void_p` pointer to the current open GMT session. - - Raises - ------ - GMTCLibNoSessionError - If trying to access without a currently open GMT session (i.e., - outside of the context manager). - """ - if not hasattr(self, "_session_pointer") or self._session_pointer is None: - raise GMTCLibNoSessionError("No currently open GMT API session.") - return self._session_pointer - - @session_pointer.setter - def session_pointer(self, session): - """ - Set the session void pointer. - """ - self._session_pointer = session - - @property - def info(self): - """ - Dictionary with the GMT version and default paths and parameters. - """ - if not hasattr(self, "_info"): - self._info = { - "version": self.get_default("API_VERSION"), - "padding": self.get_default("API_PAD"), - "binary dir": self.get_default("API_BINDIR"), - "share dir": self.get_default("API_SHAREDIR"), - # This segfaults for some reason - # 'data dir': self.get_default("API_DATADIR"), - "plugin dir": self.get_default("API_PLUGINDIR"), - "library path": self.get_default("API_LIBRARY"), - "cores": self.get_default("API_CORES"), - # API_IMAGE_LAYOUT not defined if GMT is not compiled with GDAL - # "image layout": self.get_default("API_IMAGE_LAYOUT"), - "grid layout": self.get_default("API_GRID_LAYOUT"), - } - return self._info - - def __enter__(self): - """ - Create a GMT API session and check the libgmt version. - - Calls :meth:`pygmt.clib.Session.create`. - - Raises - ------ - GMTVersionError - If the version reported by libgmt is less than - ``Session.required_version``. Will destroy the session before - raising the exception. - """ - self.create("pygmt-session") - # Need to store the version info because 'get_default' won't work after - # the session is destroyed. - version = self.info["version"] - if Version(version) < Version(self.required_version): - self.destroy() - raise GMTVersionError( - "Using an incompatible GMT version {}. Must be equal or newer than {}.".format( - version, self.required_version - ) - ) - return self - - def __exit__(self, exc_type, exc_value, traceback): - """ - Destroy the currently open GMT API session. - - Calls :meth:`pygmt.clib.Session.destroy`. - """ - self.destroy() - - def __getitem__(self, name): - """ - Get the value of a GMT constant (C enum) from gmt_resources.h. - - Used to set configuration values for other API calls. Wraps - ``GMT_Get_Enum``. - - Parameters - ---------- - name : str - The name of the constant (e.g., ``"GMT_SESSION_EXTERNAL"``) - - Returns - ------- - constant : int - Integer value of the constant. Do not rely on this value because it - might change. - - Raises - ------ - GMTCLibError - If the constant doesn't exist. - """ - c_get_enum = self.get_libgmt_func( - "GMT_Get_Enum", argtypes=[ctp.c_void_p, ctp.c_char_p], restype=ctp.c_int - ) - - # The C lib introduced the void API pointer to GMT_Get_Enum so that - # it's consistent with other functions. It doesn't use the pointer so - # we can pass in None (NULL pointer). We can't give it the actual - # pointer because we need to call GMT_Get_Enum when creating a new API - # session pointer (chicken-and-egg type of thing). - session = None - - value = c_get_enum(session, name.encode()) - - if value is None or value == -99999: - raise GMTCLibError(f"Constant '{name}' doesn't exist in libgmt.") - - return value - - def get_libgmt_func(self, name, argtypes=None, restype=None): - """ - Get a ctypes function from the libgmt shared library. - - Assigns the argument and return type conversions for the function. - - Use this method to access a C function from libgmt. - - Parameters - ---------- - name : str - The name of the GMT API function. - argtypes : list - List of ctypes types used to convert the Python input arguments for - the API function. - restype : ctypes type - The ctypes type used to convert the input returned by the function - into a Python type. - - Returns - ------- - function - The GMT API function. - - Examples - -------- - - >>> from ctypes import c_void_p, c_int - >>> with Session() as lib: - ... func = lib.get_libgmt_func( - ... "GMT_Destroy_Session", argtypes=[c_void_p], restype=c_int - ... ) - ... - >>> type(func) - ._FuncPtr'> - """ - if not hasattr(self, "_libgmt"): - self._libgmt = load_libgmt() - function = getattr(self._libgmt, name) - if argtypes is not None: - function.argtypes = argtypes - if restype is not None: - function.restype = restype - return function - - def create(self, name): - """ - Create a new GMT C API session. - - This is required before most other methods of - :class:`pygmt.clib.Session` can be called. - - .. warning:: - - Usage of :class:`pygmt.clib.Session` as a context manager in a - ``with`` block is preferred over calling - :meth:`pygmt.clib.Session.create` and - :meth:`pygmt.clib.Session.destroy` manually. - - Calls ``GMT_Create_Session`` and generates a new ``GMTAPI_CTRL`` - struct, which is a :class:`ctypes.c_void_p` pointer. Sets the - ``session_pointer`` attribute to this pointer. - - Remember to terminate the current session using - :meth:`pygmt.clib.Session.destroy` before creating a new one. - - Parameters - ---------- - name : str - A name for this session. Doesn't really affect the outcome. - """ - try: - # Won't raise an exception if there is a currently open session - self.session_pointer # pylint: disable=pointless-statement - # In this case, fail to create a new session until the old one is - # destroyed - raise GMTCLibError( - "Failed to create a GMT API session: There is a currently open session." - " Must destroy it fist." - ) - # If the exception is raised, this means that there is no open session - # and we're free to create a new one. - except GMTCLibNoSessionError: - pass - - c_create_session = self.get_libgmt_func( - "GMT_Create_Session", - argtypes=[ctp.c_char_p, ctp.c_uint, ctp.c_uint, ctp.c_void_p], - restype=ctp.c_void_p, - ) - - # Capture the output printed by GMT into this list. Will use it later - # to generate error messages for the exceptions raised by API calls. - self._error_log = [] - - @ctp.CFUNCTYPE(ctp.c_int, ctp.c_void_p, ctp.c_char_p) - def print_func(file_pointer, message): # pylint: disable=unused-argument - """ - Callback function that the GMT C API will use to print log and - error messages. - - We'll capture the messages and print them to stderr so that they - will show up on the Jupyter notebook. - """ - message = message.decode().strip() - self._error_log.append(message) - # flush to make sure the messages are printed even if we have a - # crash. - print(message, file=sys.stderr, flush=True) - return 0 - - # Need to store a copy of the function because ctypes doesn't and it - # will be garbage collected otherwise - self._print_callback = print_func - - padding = self["GMT_PAD_DEFAULT"] - session_type = self["GMT_SESSION_EXTERNAL"] - - session = c_create_session(name.encode(), padding, session_type, print_func) - - if session is None: - raise GMTCLibError( - "Failed to create a GMT API session:\n{}".format(self._error_message) - ) - - self.session_pointer = session - - @property - def _error_message(self): - """ - A string with all error messages emitted by the C API. - - Only includes messages with the string ``"[ERROR]"`` in them. - """ - msg = "" - if hasattr(self, "_error_log"): - msg = "\n".join(line for line in self._error_log if "[ERROR]" in line) - return msg - - def destroy(self): - """ - Destroy the currently open GMT API session. - - .. warning:: - - Usage of :class:`pygmt.clib.Session` as a context manager in a - ``with`` block is preferred over calling - :meth:`pygmt.clib.Session.create` and - :meth:`pygmt.clib.Session.destroy` manually. - - Calls ``GMT_Destroy_Session`` to terminate and free the memory of a - registered ``GMTAPI_CTRL`` session (the pointer for this struct is - stored in the ``session_pointer`` attribute). - - Always use this method after you are done using a C API session. The - session needs to be destroyed before creating a new one. Otherwise, - some of the configuration files might be left behind and can influence - subsequent API calls. - - Sets the ``session_pointer`` attribute to ``None``. - """ - c_destroy_session = self.get_libgmt_func( - "GMT_Destroy_Session", argtypes=[ctp.c_void_p], restype=ctp.c_int - ) - - status = c_destroy_session(self.session_pointer) - if status: - raise GMTCLibError( - "Failed to destroy GMT API session:\n{}".format(self._error_message) - ) - - self.session_pointer = None - - def get_default(self, name): - """ - Get the value of a GMT default parameter (library version, paths, etc). - - Possible default parameter names include: - - * ``"API_VERSION"``: The GMT version - * ``"API_PAD"``: The grid padding setting - * ``"API_BINDIR"``: The binary file directory - * ``"API_SHAREDIR"``: The share directory - * ``"API_DATADIR"``: The data directory - * ``"API_PLUGINDIR"``: The plugin directory - * ``"API_LIBRARY"``: The core library path - * ``"API_CORES"``: The number of cores - * ``"API_IMAGE_LAYOUT"``: The image/band layout - * ``"API_GRID_LAYOUT"``: The grid layout - - Parameters - ---------- - name : str - The name of the default parameter (e.g., ``"API_VERSION"``) - - Returns - ------- - value : str - The default value for the parameter. - - Raises - ------ - GMTCLibError - If the parameter doesn't exist. - """ - c_get_default = self.get_libgmt_func( - "GMT_Get_Default", - argtypes=[ctp.c_void_p, ctp.c_char_p, ctp.c_char_p], - restype=ctp.c_int, - ) - - # Make a string buffer to get a return value - value = ctp.create_string_buffer(10000) - - status = c_get_default(self.session_pointer, name.encode(), value) - - if status != 0: - raise GMTCLibError( - "Error getting default value for '{}' (error code {}).".format( - name, status - ) - ) - - return value.value.decode() - - def call_module(self, module, args): - """ - Call a GMT module with the given arguments. - - Makes a call to ``GMT_Call_Module`` from the C API using mode - ``GMT_MODULE_CMD`` (arguments passed as a single string). - - Most interactions with the C API are done through this function. - - Parameters - ---------- - module : str - Module name (``'coast'``, ``'basemap'``, etc). - args : str - String with the command line arguments that will be passed to the - module (for example, ``'-R0/5/0/10 -JM'``). - - Raises - ------ - GMTCLibError - If the returned status code of the function is non-zero. - """ - c_call_module = self.get_libgmt_func( - "GMT_Call_Module", - argtypes=[ctp.c_void_p, ctp.c_char_p, ctp.c_int, ctp.c_void_p], - restype=ctp.c_int, - ) - - mode = self["GMT_MODULE_CMD"] - status = c_call_module( - self.session_pointer, module.encode(), mode, args.encode() - ) - if status != 0: - raise GMTCLibError( - "Module '{}' failed with status code {}:\n{}".format( - module, status, self._error_message - ) - ) - - def create_data(self, family, geometry, mode, **kwargs): - """ - Create an empty GMT data container. - - Parameters - ---------- - family : str - A valid GMT data family name (e.g., ``'GMT_IS_DATASET'``). See the - ``FAMILIES`` attribute for valid names. - geometry : str - A valid GMT data geometry name (e.g., ``'GMT_IS_POINT'``). See the - ``GEOMETRIES`` attribute for valid names. - mode : str - A valid GMT data mode (e.g., ``'GMT_IS_OUTPUT'``). See the - ``MODES`` attribute for valid names. - dim : list of 4 integers - The dimensions of the dataset. See the documentation for the GMT C - API function ``GMT_Create_Data`` (``src/gmt_api.c``) for the full - range of options regarding 'dim'. If ``None``, will pass in the - NULL pointer. - ranges : list of 4 floats - The dataset extent. Also a bit of a complicated argument. See the C - function documentation. It's called ``range`` in the C function but - it would conflict with the Python built-in ``range`` function. - inc : list of 2 floats - The increments between points of the dataset. See the C function - documentation. - registration : str - The node registration (what the coordinates mean). Can be - ``'GMT_GRID_PIXEL_REG'`` or ``'GMT_GRID_NODE_REG'``. Defaults to - ``'GMT_GRID_NODE_REG'``. - pad : int - The grid padding. Defaults to ``GMT_PAD_DEFAULT``. - - Returns - ------- - data_ptr : int - A ctypes pointer (an integer) to the allocated ``GMT_Dataset`` - object. - """ - c_create_data = self.get_libgmt_func( - "GMT_Create_Data", - argtypes=[ - ctp.c_void_p, # API - ctp.c_uint, # family - ctp.c_uint, # geometry - ctp.c_uint, # mode - ctp.POINTER(ctp.c_uint64), # dim - ctp.POINTER(ctp.c_double), # range - ctp.POINTER(ctp.c_double), # inc - ctp.c_uint, # registration - ctp.c_int, # pad - ctp.c_void_p, - ], # data - restype=ctp.c_void_p, - ) - - family_int = self._parse_constant(family, valid=FAMILIES, valid_modifiers=VIAS) - mode_int = self._parse_constant( - mode, - valid=MODES, - valid_modifiers=["GMT_GRID_IS_CARTESIAN", "GMT_GRID_IS_GEO"], - ) - geometry_int = self._parse_constant(geometry, valid=GEOMETRIES) - registration_int = self._parse_constant( - kwargs.get("registration", "GMT_GRID_NODE_REG"), valid=REGISTRATIONS - ) - - # Convert dim, ranges, and inc to ctypes arrays if given (will be None - # if not given to represent NULL pointers) - dim = kwargs_to_ctypes_array("dim", kwargs, ctp.c_uint64 * 4) - ranges = kwargs_to_ctypes_array("ranges", kwargs, ctp.c_double * 4) - inc = kwargs_to_ctypes_array("inc", kwargs, ctp.c_double * 2) - - # Use a NULL pointer (None) for existing data to indicate that the - # container should be created empty. Fill it in later using put_vector - # and put_matrix. - data_ptr = c_create_data( - self.session_pointer, - family_int, - geometry_int, - mode_int, - dim, - ranges, - inc, - registration_int, - self._parse_pad(family, kwargs), - None, - ) - - if data_ptr is None: - raise GMTCLibError("Failed to create an empty GMT data pointer.") - - return data_ptr - - def _parse_pad(self, family, kwargs): - """ - Parse and return an appropriate value for pad if none is given. - - Pad is a bit tricky because, for matrix types, pad control the matrix - ordering (row or column major). Using the default pad will set it to - column major and mess things up with the numpy arrays. - """ - pad = kwargs.get("pad", None) - if pad is None: - if "MATRIX" in family: - pad = 0 - else: - pad = self["GMT_PAD_DEFAULT"] - return pad - - def _parse_constant(self, constant, valid, valid_modifiers=None): - """ - Parse a constant, convert it to an int, and validate it. - - The GMT C API takes certain defined constants, like ``'GMT_IS_GRID'``, - that need to be validated and converted to integer values using - :meth:`pygmt.clib.Session.__getitem__`. - - The constants can also take a modifier by appending another constant - name, e.g. ``'GMT_IS_GRID|GMT_VIA_MATRIX'``. The two parts must be - converted separately and their values are added. - - If valid modifiers are not given, then will assume that modifiers are - not allowed. In this case, will raise a - :class:`pygmt.exceptions.GMTInvalidInput` exception if given a - modifier. - - Parameters - ---------- - constant : str - The name of a valid GMT API constant, with an optional modifier. - valid : list of str - A list of valid values for the constant. Will raise a - :class:`pygmt.exceptions.GMTInvalidInput` exception if the given - value is not on the list. - """ - parts = constant.split("|") - name = parts[0] - nmodifiers = len(parts) - 1 - if nmodifiers > 1: - raise GMTInvalidInput( - "Only one modifier is allowed in constants, {} given: '{}'".format( - nmodifiers, constant - ) - ) - if nmodifiers > 0 and valid_modifiers is None: - raise GMTInvalidInput( - "Constant modifiers not allowed since valid values were not " - + "given: '{}'".format(constant) - ) - if name not in valid: - raise GMTInvalidInput( - "Invalid constant argument '{}'. Must be one of {}.".format( - name, str(valid) - ) - ) - if ( - nmodifiers > 0 - and valid_modifiers is not None - and parts[1] not in valid_modifiers - ): - raise GMTInvalidInput( - "Invalid constant modifier '{}'. Must be one of {}.".format( - parts[1], str(valid_modifiers) - ) - ) - integer_value = sum(self[part] for part in parts) - return integer_value - - def _check_dtype_and_dim(self, array, ndim): - """ - Check that a numpy array has the given dimensions and is a valid data - type. - - Parameters - ---------- - array : numpy array - The array to be tested. - ndim : int - The desired dimension of the array. - - Returns - ------- - gmt_type : int - The GMT constant value representing this data type. - - Raises - ------ - GMTCLibError - If the array has the wrong dimensions or is an unsupported data - type. - - Examples - -------- - - >>> import numpy as np - >>> data = np.array([1, 2, 3], dtype="float64") - >>> with Session() as ses: - ... gmttype = ses._check_dtype_and_dim(data, ndim=1) - ... gmttype == ses["GMT_DOUBLE"] - ... - True - >>> data = np.ones((5, 2), dtype="float32") - >>> with Session() as ses: - ... gmttype = ses._check_dtype_and_dim(data, ndim=2) - ... gmttype == ses["GMT_FLOAT"] - ... - True - """ - # check the array has the given dimension - if array.ndim != ndim: - raise GMTInvalidInput( - "Expected a numpy 1d array, got {}d.".format(array.ndim) - ) - - # check the array has a valid/known data type - if array.dtype.type not in DTYPES: - try: - # Try to convert any unknown numpy data types to np.datetime64 - array = np.asarray(array, dtype=np.datetime64) - except ValueError as e: - raise GMTInvalidInput( - f"Unsupported numpy data type '{array.dtype.type}'." - ) from e - return self[DTYPES[array.dtype.type]] - - def put_vector(self, dataset, column, vector): - """ - Attach a numpy 1D array as a column on a GMT dataset. - - Use this function to attach numpy array data to a GMT dataset and pass - it to GMT modules. Wraps ``GMT_Put_Vector``. - - The dataset must be created by :meth:`pygmt.clib.Session.create_data` - first. Use ``family='GMT_IS_DATASET|GMT_VIA_VECTOR'``. - - Not at all numpy dtypes are supported, only: float64, float32, int64, - int32, uint64, and uint32. - - .. warning:: - The numpy array must be C contiguous in memory. If it comes from a - column slice of a 2d array, for example, you will have to make a - copy. Use :func:`numpy.ascontiguousarray` to make sure your vector - is contiguous (it won't copy if it already is). - - Parameters - ---------- - dataset : :class:`ctypes.c_void_p` - The ctypes void pointer to a ``GMT_Dataset``. Create it with - :meth:`pygmt.clib.Session.create_data`. - column : int - The column number of this vector in the dataset (starting from 0). - vector : numpy 1d-array - The array that will be attached to the dataset. Must be a 1d C - contiguous array. - - Raises - ------ - GMTCLibError - If given invalid input or ``GMT_Put_Vector`` exits with status != - 0. - """ - c_put_vector = self.get_libgmt_func( - "GMT_Put_Vector", - argtypes=[ctp.c_void_p, ctp.c_void_p, ctp.c_uint, ctp.c_uint, ctp.c_void_p], - restype=ctp.c_int, - ) - - gmt_type = self._check_dtype_and_dim(vector, ndim=1) - if gmt_type == self["GMT_DATETIME"]: - vector_pointer = (ctp.c_char_p * len(vector))() - vector_pointer[:] = np.char.encode( - np.datetime_as_string(array_to_datetime(vector)) - ) - else: - vector_pointer = vector.ctypes.data_as(ctp.c_void_p) - status = c_put_vector( - self.session_pointer, dataset, column, gmt_type, vector_pointer - ) - if status != 0: - raise GMTCLibError( - " ".join( - [ - "Failed to put vector of type {}".format(vector.dtype), - "in column {} of dataset.".format(column), - ] - ) - ) - - def put_strings(self, dataset, family, strings): - """ - Attach a numpy 1D array of dtype str as a column on a GMT dataset. - - Use this function to attach string type numpy array data to a GMT - dataset and pass it to GMT modules. Wraps ``GMT_Put_Strings``. - - The dataset must be created by :meth:`pygmt.clib.Session.create_data` - first. - - .. warning:: - The numpy array must be C contiguous in memory. If it comes from a - column slice of a 2d array, for example, you will have to make a - copy. Use :func:`numpy.ascontiguousarray` to make sure your vector - is contiguous (it won't copy if it already is). - - Parameters - ---------- - dataset : :class:`ctypes.c_void_p` - The ctypes void pointer to a ``GMT_Dataset``. Create it with - :meth:`pygmt.clib.Session.create_data`. - family : str - The family type of the dataset. Can be either ``GMT_IS_VECTOR`` or - ``GMT_IS_MATRIX``. - strings : numpy 1d-array - The array that will be attached to the dataset. Must be a 1d C - contiguous array. - - Raises - ------ - GMTCLibError - If given invalid input or ``GMT_Put_Strings`` exits with status != - 0. - """ - c_put_strings = self.get_libgmt_func( - "GMT_Put_Strings", - argtypes=[ - ctp.c_void_p, - ctp.c_uint, - ctp.c_void_p, - ctp.POINTER(ctp.c_char_p), - ], - restype=ctp.c_int, - ) - - strings_pointer = (ctp.c_char_p * len(strings))() - strings_pointer[:] = np.char.encode(strings) - - family_int = self._parse_constant( - family, valid=FAMILIES, valid_modifiers=METHODS - ) - - status = c_put_strings( - self.session_pointer, family_int, dataset, strings_pointer - ) - if status != 0: - raise GMTCLibError( - f"Failed to put strings of type {strings.dtype} into dataset" - ) - - def put_matrix(self, dataset, matrix, pad=0): - """ - Attach a numpy 2D array to a GMT dataset. - - Use this function to attach numpy array data to a GMT dataset and pass - it to GMT modules. Wraps ``GMT_Put_Matrix``. - - The dataset must be created by :meth:`pygmt.clib.Session.create_data` - first. Use ``|GMT_VIA_MATRIX'`` in the family. - - Not at all numpy dtypes are supported, only: float64, float32, int64, - int32, uint64, and uint32. - - .. warning:: - The numpy array must be C contiguous in memory. Use - :func:`numpy.ascontiguousarray` to make sure your vector is - contiguous (it won't copy if it already is). - - Parameters - ---------- - dataset : :class:`ctypes.c_void_p` - The ctypes void pointer to a ``GMT_Dataset``. Create it with - :meth:`pygmt.clib.Session.create_data`. - matrix : numpy 2d-array - The array that will be attached to the dataset. Must be a 2d C - contiguous array. - pad : int - The amount of padding that should be added to the matrix. Use when - creating grids for modules that require padding. - - Raises - ------ - GMTCLibError - If given invalid input or ``GMT_Put_Matrix`` exits with status != - 0. - """ - c_put_matrix = self.get_libgmt_func( - "GMT_Put_Matrix", - argtypes=[ctp.c_void_p, ctp.c_void_p, ctp.c_uint, ctp.c_int, ctp.c_void_p], - restype=ctp.c_int, - ) - - gmt_type = self._check_dtype_and_dim(matrix, ndim=2) - matrix_pointer = matrix.ctypes.data_as(ctp.c_void_p) - status = c_put_matrix( - self.session_pointer, dataset, gmt_type, pad, matrix_pointer - ) - if status != 0: - raise GMTCLibError("Failed to put matrix of type {}.".format(matrix.dtype)) - - def write_data(self, family, geometry, mode, wesn, output, data): - """ - Write a GMT data container to a file. - - The data container should be created by - :meth:`pygmt.clib.Session.create_data`. - - Wraps ``GMT_Write_Data`` but only allows writing to a file. So the - ``method`` argument is omitted. - - Parameters - ---------- - family : str - A valid GMT data family name (e.g., ``'GMT_IS_DATASET'``). See the - ``FAMILIES`` attribute for valid names. Don't use the - ``GMT_VIA_VECTOR`` or ``GMT_VIA_MATRIX`` constructs for this. Use - ``GMT_IS_VECTOR`` and ``GMT_IS_MATRIX`` instead. - geometry : str - A valid GMT data geometry name (e.g., ``'GMT_IS_POINT'``). See the - ``GEOMETRIES`` attribute for valid names. - mode : str - How the data is to be written to the file. This option varies - depending on the given family. See the GMT API documentation for - details. - wesn : list or numpy array - [xmin, xmax, ymin, ymax, zmin, zmax] of the data. Must have 6 - elements. - output : str - The output file name. - data : :class:`ctypes.c_void_p` - Pointer to the data container created by - :meth:`pygmt.clib.Session.create_data`. - - Raises - ------ - GMTCLibError - For invalid input arguments or if the GMT API functions returns a - non-zero status code. - """ - c_write_data = self.get_libgmt_func( - "GMT_Write_Data", - argtypes=[ - ctp.c_void_p, - ctp.c_uint, - ctp.c_uint, - ctp.c_uint, - ctp.c_uint, - ctp.POINTER(ctp.c_double), - ctp.c_char_p, - ctp.c_void_p, - ], - restype=ctp.c_int, - ) - - family_int = self._parse_constant(family, valid=FAMILIES, valid_modifiers=VIAS) - geometry_int = self._parse_constant(geometry, valid=GEOMETRIES) - status = c_write_data( - self.session_pointer, - family_int, - self["GMT_IS_FILE"], - geometry_int, - self[mode], - (ctp.c_double * 6)(*wesn), - output.encode(), - data, - ) - if status != 0: - raise GMTCLibError("Failed to write dataset to '{}'".format(output)) - - @contextmanager - def open_virtual_file(self, family, geometry, direction, data): - """ - Open a GMT Virtual File to pass data to and from a module. - - GMT uses a virtual file scheme to pass in data to API modules. Use it - to pass in your GMT data structure (created using - :meth:`pygmt.clib.Session.create_data`) to a module that expects an - input or output file. - - Use in a ``with`` block. Will automatically close the virtual file when - leaving the ``with`` block. Because of this, no wrapper for - ``GMT_Close_VirtualFile`` is provided. - - Parameters - ---------- - family : str - A valid GMT data family name (e.g., ``'GMT_IS_DATASET'``). Should - be the same as the one you used to create your data structure. - geometry : str - A valid GMT data geometry name (e.g., ``'GMT_IS_POINT'``). Should - be the same as the one you used to create your data structure. - direction : str - Either ``'GMT_IN'`` or ``'GMT_OUT'`` to indicate if passing data to - GMT or getting it out of GMT, respectively. - By default, GMT can modify the data you pass in. Add modifier - ``'GMT_IS_REFERENCE'`` to tell GMT the data are read-only, or - ``'GMT_IS_DUPLICATE'`` to tell GMT to duplicate the data. - data : int - The ctypes void pointer to your GMT data structure. - - Yields - ------ - vfname : str - The name of the virtual file that you can pass to a GMT module. - - Examples - -------- - - >>> from pygmt.helpers import GMTTempFile - >>> import os - >>> import numpy as np - >>> x = np.array([0, 1, 2, 3, 4]) - >>> y = np.array([5, 6, 7, 8, 9]) - >>> with Session() as lib: - ... family = "GMT_IS_DATASET|GMT_VIA_VECTOR" - ... geometry = "GMT_IS_POINT" - ... dataset = lib.create_data( - ... family=family, - ... geometry=geometry, - ... mode="GMT_CONTAINER_ONLY", - ... dim=[2, 5, 1, 0], # columns, lines, segments, type - ... ) - ... lib.put_vector(dataset, column=0, vector=x) - ... lib.put_vector(dataset, column=1, vector=y) - ... # Add the dataset to a virtual file - ... vfargs = (family, geometry, "GMT_IN|GMT_IS_REFERENCE", dataset) - ... with lib.open_virtual_file(*vfargs) as vfile: - ... # Send the output to a temp file so that we can read it - ... with GMTTempFile() as ofile: - ... args = "{} ->{}".format(vfile, ofile.name) - ... lib.call_module("info", args) - ... print(ofile.read().strip()) - ... - : N = 5 <0/4> <5/9> - """ - c_open_virtualfile = self.get_libgmt_func( - "GMT_Open_VirtualFile", - argtypes=[ - ctp.c_void_p, - ctp.c_uint, - ctp.c_uint, - ctp.c_uint, - ctp.c_void_p, - ctp.c_char_p, - ], - restype=ctp.c_int, - ) - - c_close_virtualfile = self.get_libgmt_func( - "GMT_Close_VirtualFile", - argtypes=[ctp.c_void_p, ctp.c_char_p], - restype=ctp.c_int, - ) - - family_int = self._parse_constant(family, valid=FAMILIES, valid_modifiers=VIAS) - geometry_int = self._parse_constant(geometry, valid=GEOMETRIES) - direction_int = self._parse_constant( - direction, valid=["GMT_IN", "GMT_OUT"], valid_modifiers=METHODS - ) - - buff = ctp.create_string_buffer(self["GMT_VF_LEN"]) - - status = c_open_virtualfile( - self.session_pointer, family_int, geometry_int, direction_int, data, buff - ) - - if status != 0: - raise GMTCLibError("Failed to create a virtual file.") - - vfname = buff.value.decode() - - try: - yield vfname - finally: - status = c_close_virtualfile(self.session_pointer, vfname.encode()) - if status != 0: - raise GMTCLibError("Failed to close virtual file '{}'.".format(vfname)) - - @contextmanager - def virtualfile_from_vectors(self, *vectors): - """ - Store 1d arrays as columns of a table inside a virtual file. - - Use the virtual file name to pass in the data in your vectors to a GMT - module. - - Context manager (use in a ``with`` block). Yields the virtual file name - that you can pass as an argument to a GMT module call. Closes the - virtual file upon exit of the ``with`` block. - - Use this instead of creating the data container and virtual file by - hand with :meth:`pygmt.clib.Session.create_data`, - :meth:`pygmt.clib.Session.put_vector`, and - :meth:`pygmt.clib.Session.open_virtual_file`. - - If the arrays are C contiguous blocks of memory, they will be passed - without copying to GMT. If they are not (e.g., they are columns of a 2D - array), they will need to be copied to a contiguous block. - - Parameters - ---------- - vectors : 1d arrays - The vectors that will be included in the array. All must be of the - same size. - - Yields - ------ - fname : str - The name of virtual file. Pass this as a file name argument to a - GMT module. - - Examples - -------- - - >>> from pygmt.helpers import GMTTempFile - >>> import numpy as np - >>> import pandas as pd - >>> x = [1, 2, 3] - >>> y = np.array([4, 5, 6]) - >>> z = pd.Series([7, 8, 9]) - >>> with Session() as ses: - ... with ses.virtualfile_from_vectors(x, y, z) as fin: - ... # Send the output to a file so that we can read it - ... with GMTTempFile() as fout: - ... ses.call_module( - ... "info", "{} ->{}".format(fin, fout.name) - ... ) - ... print(fout.read().strip()) - ... - : N = 3 <1/3> <4/6> <7/9> - """ - # Conversion to a C-contiguous array needs to be done here and not in - # put_vector or put_strings because we need to maintain a reference to - # the copy while it is being used by the C API. Otherwise, the array - # would be garbage collected and the memory freed. Creating it in this - # context manager guarantees that the copy will be around until the - # virtual file is closed. The conversion is implicit in - # vectors_to_arrays. - arrays = vectors_to_arrays(vectors) - - columns = len(arrays) - # Find arrays that are of string dtype from column 3 onwards - # Assumes that first 2 columns contains coordinates like longitude - # latitude, or datetime string types. - for col, array in enumerate(arrays[2:]): - if pd.api.types.is_string_dtype(array.dtype): - columns = col + 2 - break - - rows = len(arrays[0]) - if not all(len(i) == rows for i in arrays): - raise GMTInvalidInput("All arrays must have same size.") - - family = "GMT_IS_DATASET|GMT_VIA_VECTOR" - geometry = "GMT_IS_POINT" - - dataset = self.create_data( - family, geometry, mode="GMT_CONTAINER_ONLY", dim=[columns, rows, 1, 0] - ) - - # Use put_vector for columns with numerical type data - for col, array in enumerate(arrays[:columns]): - self.put_vector(dataset, column=col, vector=array) - - # Use put_strings for last column(s) with string type data - # Have to use modifier "GMT_IS_DUPLICATE" to duplicate the strings - string_arrays = arrays[columns:] - if string_arrays: - if len(string_arrays) == 1: - strings = string_arrays[0] - elif len(string_arrays) > 1: - strings = np.apply_along_axis( - func1d=" ".join, axis=0, arr=string_arrays - ) - strings = np.asanyarray(a=strings, dtype=str) - self.put_strings( - dataset, family="GMT_IS_VECTOR|GMT_IS_DUPLICATE", strings=strings - ) - - with self.open_virtual_file( - family, geometry, "GMT_IN|GMT_IS_REFERENCE", dataset - ) as vfile: - yield vfile - - @contextmanager - def virtualfile_from_matrix(self, matrix): - """ - Store a 2d array as a table inside a virtual file. - - Use the virtual file name to pass in the data in your matrix to a GMT - module. - - Context manager (use in a ``with`` block). Yields the virtual file name - that you can pass as an argument to a GMT module call. Closes the - virtual file upon exit of the ``with`` block. - - The virtual file will contain the array as a ``GMT_MATRIX`` pretending - to be a ``GMT_DATASET``. - - **Not meant for creating ``GMT_GRID``**. The grid requires more - metadata than just the data matrix. Use - :meth:`pygmt.clib.Session.virtualfile_from_grid` instead. - - Use this instead of creating the data container and virtual file by - hand with :meth:`pygmt.clib.Session.create_data`, - :meth:`pygmt.clib.Session.put_matrix`, and - :meth:`pygmt.clib.Session.open_virtual_file` - - The matrix must be C contiguous in memory. If it is not (e.g., it is a - slice of a larger array), the array will be copied to make sure it is. - - Parameters - ---------- - matrix : 2d array - The matrix that will be included in the GMT data container. - - Yields - ------ - fname : str - The name of virtual file. Pass this as a file name argument to a - GMT module. - - Examples - -------- - - >>> from pygmt.helpers import GMTTempFile - >>> import numpy as np - >>> data = np.arange(12).reshape((4, 3)) - >>> print(data) - [[ 0 1 2] - [ 3 4 5] - [ 6 7 8] - [ 9 10 11]] - >>> with Session() as ses: - ... with ses.virtualfile_from_matrix(data) as fin: - ... # Send the output to a file so that we can read it - ... with GMTTempFile() as fout: - ... ses.call_module( - ... "info", "{} ->{}".format(fin, fout.name) - ... ) - ... print(fout.read().strip()) - ... - : N = 4 <0/9> <1/10> <2/11> - """ - # Conversion to a C-contiguous array needs to be done here and not in - # put_matrix because we need to maintain a reference to the copy while - # it is being used by the C API. Otherwise, the array would be garbage - # collected and the memory freed. Creating it in this context manager - # guarantees that the copy will be around until the virtual file is - # closed. - matrix = as_c_contiguous(matrix) - rows, columns = matrix.shape - - family = "GMT_IS_DATASET|GMT_VIA_MATRIX" - geometry = "GMT_IS_POINT" - - dataset = self.create_data( - family, geometry, mode="GMT_CONTAINER_ONLY", dim=[columns, rows, 1, 0] - ) - - self.put_matrix(dataset, matrix) - - with self.open_virtual_file( - family, geometry, "GMT_IN|GMT_IS_REFERENCE", dataset - ) as vfile: - yield vfile - - @contextmanager - def virtualfile_from_grid(self, grid): - """ - Store a grid in a virtual file. - - Use the virtual file name to pass in the data in your grid to a GMT - module. Grids must be :class:`xarray.DataArray` instances. - - Context manager (use in a ``with`` block). Yields the virtual file name - that you can pass as an argument to a GMT module call. Closes the - virtual file upon exit of the ``with`` block. - - The virtual file will contain the grid as a ``GMT_MATRIX`` with extra - metadata. - - Use this instead of creating a data container and virtual file by hand - with :meth:`pygmt.clib.Session.create_data`, - :meth:`pygmt.clib.Session.put_matrix`, and - :meth:`pygmt.clib.Session.open_virtual_file` - - The grid data matrix must be C contiguous in memory. If it is not - (e.g., it is a slice of a larger array), the array will be copied to - make sure it is. - - Parameters - ---------- - grid : :class:`xarray.DataArray` - The grid that will be included in the virtual file. - - Yields - ------ - fname : str - The name of virtual file. Pass this as a file name argument to a - GMT module. - - Examples - -------- - - >>> from pygmt.datasets import load_earth_relief - >>> from pygmt.helpers import GMTTempFile - >>> data = load_earth_relief(resolution="01d") - >>> print(data.shape) - (180, 360) - >>> print(data.lon.values.min(), data.lon.values.max()) - -179.5 179.5 - >>> print(data.lat.values.min(), data.lat.values.max()) - -89.5 89.5 - >>> print(data.values.min(), data.values.max()) - -8182.0 5651.5 - >>> with Session() as ses: - ... with ses.virtualfile_from_grid(data) as fin: - ... # Send the output to a file so that we can read it - ... with GMTTempFile() as fout: - ... args = "{} -L0 -Cn ->{}".format(fin, fout.name) - ... ses.call_module("grdinfo", args) - ... print(fout.read().strip()) - ... - -180 180 -90 90 -8182 5651.5 1 1 360 180 1 1 - >>> # The output is: w e s n z0 z1 dx dy n_columns n_rows reg gtype - """ - _gtype = {0: "GMT_GRID_IS_CARTESIAN", 1: "GMT_GRID_IS_GEO"}[grid.gmt.gtype] - _reg = {0: "GMT_GRID_NODE_REG", 1: "GMT_GRID_PIXEL_REG"}[grid.gmt.registration] - - # Conversion to a C-contiguous array needs to be done here and not in - # put_matrix because we need to maintain a reference to the copy while - # it is being used by the C API. Otherwise, the array would be garbage - # collected and the memory freed. Creating it in this context manager - # guarantees that the copy will be around until the virtual file is - # closed. The conversion is implicit in dataarray_to_matrix. - matrix, region, inc = dataarray_to_matrix(grid) - - family = "GMT_IS_GRID|GMT_VIA_MATRIX" - geometry = "GMT_IS_SURFACE" - gmt_grid = self.create_data( - family, - geometry, - mode=f"GMT_CONTAINER_ONLY|{_gtype}", - ranges=region, - inc=inc, - registration=_reg, - ) - self.put_matrix(gmt_grid, matrix) - args = (family, geometry, "GMT_IN|GMT_IS_REFERENCE", gmt_grid) - with self.open_virtual_file(*args) as vfile: - yield vfile - - def virtualfile_from_data(self, check_kind=None, data=None, x=None, y=None, z=None): - """ - Store any data inside a virtual file. - - This convenience function automatically detects the kind of data passed - into it, and produces a virtualfile that can be passed into GMT later - on. - - Parameters - ---------- - check_kind : str - Used to validate the type of data that can be passed in. Choose - from 'raster', 'vector' or None. Default is None (no validation). - data : str, xarray.DataArray, 2d array, or None - Any raster or vector data format. This could be a file name, a - raster grid, a vector matrix/arrays, or other supported data input. - x/y/z : 1d arrays or None - x, y and z columns as numpy arrays. - - Returns - ------- - file_context : contextlib._GeneratorContextManager - The virtual file stored inside a context manager. Access the file - name of this virtualfile using ``with file_context as fname: ...``. - - Examples - -------- - >>> from pygmt.helpers import GMTTempFile - >>> import xarray as xr - >>> data = xr.Dataset( - ... coords={"index": [0, 1, 2]}, - ... data_vars={ - ... "x": ("index", [9, 8, 7]), - ... "y": ("index", [6, 5, 4]), - ... "z": ("index", [3, 2, 1]), - ... }, - ... ) - >>> with Session() as ses: - ... with ses.virtualfile_from_data( - ... check_kind="vector", data=data - ... ) as fin: - ... # Send the output to a file so that we can read it - ... with GMTTempFile() as fout: - ... ses.call_module("info", f"{fin} ->{fout.name}") - ... print(fout.read().strip()) - ... - : N = 3 <7/9> <4/6> <1/3> - """ - kind = data_kind(data, x, y, z) - - if check_kind == "raster" and kind not in ("file", "grid"): - raise GMTInvalidInput(f"Unrecognized data type for grid: {type(data)}") - if check_kind == "vector" and kind not in ("file", "matrix", "vectors"): - raise GMTInvalidInput(f"Unrecognized data type: {type(data)}") - - # Decide which virtualfile_from_ function to use - _virtualfile_from = { - "file": dummy_context, - "grid": self.virtualfile_from_grid, - # Note: virtualfile_from_matrix is not used because a matrix can be - # converted to vectors instead, and using vectors allows for better - # handling of string type inputs (e.g. for datetime data types) - "matrix": self.virtualfile_from_vectors, - "vectors": self.virtualfile_from_vectors, - }[kind] - - # Ensure the data is an iterable (Python list or tuple) - if kind in ("file", "grid"): - _data = (data,) - elif kind == "vectors": - _data = (x, y, z) - elif kind == "matrix": # turn 2D arrays into list of vectors - try: - # pandas.DataFrame and xarray.Dataset types - _data = [array for _, array in data.items()] - except AttributeError: - # Python lists, tuples, and numpy ndarray types - _data = np.atleast_2d(np.asanyarray(data).T) - - # Finally create the virtualfile from the data, to be passed into GMT - file_context = _virtualfile_from(*_data) - - return file_context - - def extract_region(self): - """ - Extract the WESN bounding box of the currently active figure. - - Retrieves the information from the PostScript file, so it works for - country codes as well. - - Returns - ------- - * wesn : 1d array - A 1D numpy array with the west, east, south, and north dimensions - of the current figure. - - Examples - -------- - - >>> import pygmt - >>> fig = pygmt.Figure() - >>> fig.coast( - ... region=[0, 10, -20, -10], - ... projection="M6i", - ... frame=True, - ... land="black", - ... ) - >>> with Session() as lib: - ... wesn = lib.extract_region() - ... - >>> print(", ".join(["{:.2f}".format(x) for x in wesn])) - 0.00, 10.00, -20.00, -10.00 - - Using ISO country codes for the regions (for example ``'US.HI'`` for - Hawaii): - - >>> fig = pygmt.Figure() - >>> fig.coast( - ... region="US.HI", projection="M6i", frame=True, land="black" - ... ) - >>> with Session() as lib: - ... wesn = lib.extract_region() - ... - >>> print(", ".join(["{:.2f}".format(x) for x in wesn])) - -164.71, -154.81, 18.91, 23.58 - - The country codes can have an extra argument that rounds the region a - multiple of the argument (for example, ``'US.HI+r5'`` will round the - region to multiples of 5): - - >>> fig = pygmt.Figure() - >>> fig.coast( - ... region="US.HI+r5", projection="M6i", frame=True, land="black" - ... ) - >>> with Session() as lib: - ... wesn = lib.extract_region() - ... - >>> print(", ".join(["{:.2f}".format(x) for x in wesn])) - -165.00, -150.00, 15.00, 25.00 - """ - c_extract_region = self.get_libgmt_func( - "GMT_Extract_Region", - argtypes=[ctp.c_void_p, ctp.c_char_p, ctp.POINTER(ctp.c_double)], - restype=ctp.c_int, - ) - - wesn = np.empty(4, dtype=np.float64) - wesn_pointer = wesn.ctypes.data_as(ctp.POINTER(ctp.c_double)) - # The second argument to GMT_Extract_Region is a file pointer to a - # PostScript file. It's only valid in classic mode. Use None to get a - # NULL pointer instead. - status = c_extract_region(self.session_pointer, None, wesn_pointer) - if status != 0: - raise GMTCLibError("Failed to extract region from current figure.") - return wesn +""" +Defines the Session class to create and destroy a GMT API session and provides +access to the API functions. + +Uses ctypes to wrap most of the core functions from the C API. +""" +import ctypes as ctp +import sys +from contextlib import contextmanager + +import numpy as np +import pandas as pd +from packaging.version import Version +from pygmt.clib.conversion import ( + array_to_datetime, + as_c_contiguous, + dataarray_to_matrix, + kwargs_to_ctypes_array, + vectors_to_arrays, +) +from pygmt.clib.loading import load_libgmt +from pygmt.exceptions import ( + GMTCLibError, + GMTCLibNoSessionError, + GMTInvalidInput, + GMTVersionError, +) +from pygmt.helpers import data_kind, dummy_context + +FAMILIES = [ + "GMT_IS_DATASET", + "GMT_IS_GRID", + "GMT_IS_PALETTE", + "GMT_IS_MATRIX", + "GMT_IS_VECTOR", +] + +VIAS = ["GMT_VIA_MATRIX", "GMT_VIA_VECTOR"] + +GEOMETRIES = [ + "GMT_IS_NONE", + "GMT_IS_POINT", + "GMT_IS_LINE", + "GMT_IS_POLYGON", + "GMT_IS_PLP", + "GMT_IS_SURFACE", +] + +METHODS = ["GMT_IS_DUPLICATE", "GMT_IS_REFERENCE"] + +MODES = ["GMT_CONTAINER_ONLY", "GMT_IS_OUTPUT"] + +REGISTRATIONS = ["GMT_GRID_PIXEL_REG", "GMT_GRID_NODE_REG"] + +DTYPES = { + np.float64: "GMT_DOUBLE", + np.float32: "GMT_FLOAT", + np.int64: "GMT_LONG", + np.int32: "GMT_INT", + np.uint64: "GMT_ULONG", + np.uint32: "GMT_UINT", + np.datetime64: "GMT_DATETIME", +} + + +class Session: + """ + A GMT API session where most operations involving the C API happen. + + Works as a context manager (for use in a ``with`` block) to create a GMT C + API session and destroy it in the end to clean up memory. + + Functions of the shared library are exposed as methods of this class. Most + methods MUST be used with an open session (inside a ``with`` block). If + creating GMT data structures to communicate data, put that code inside the + same ``with`` block as the API calls that will use the data. + + By default, will let :mod:`ctypes` try to find the GMT shared library + (``libgmt``). If the environment variable ``GMT_LIBRARY_PATH`` is set, will + look for the shared library in the directory specified by it. + + A ``GMTVersionError`` exception will be raised if the GMT shared library + reports a version older than the required minimum GMT version. + + The ``session_pointer`` attribute holds a ctypes pointer to the currently + open session. + + Raises + ------ + GMTCLibNotFoundError + If there was any problem loading the library (couldn't find it or + couldn't access the functions). + GMTCLibNoSessionError + If you try to call a method outside of a 'with' block. + GMTVersionError + If the minimum required version of GMT is not found. + + Examples + -------- + + >>> from pygmt.datasets import load_earth_relief + >>> from pygmt.helpers import GMTTempFile + >>> grid = load_earth_relief() + >>> type(grid) + + >>> # Create a session and destroy it automatically when exiting the "with" + >>> # block. + >>> with Session() as ses: + ... # Create a virtual file and link to the memory block of the grid. + ... with ses.virtualfile_from_grid(grid) as fin: + ... # Create a temp file to use as output. + ... with GMTTempFile() as fout: + ... # Call the grdinfo module with the virtual file as input + ... # and the temp file as output. + ... ses.call_module( + ... "grdinfo", "{} -C ->{}".format(fin, fout.name) + ... ) + ... # Read the contents of the temp file before it's deleted. + ... print(fout.read().strip()) + ... + -180 180 -90 90 -8182 5651.5 1 1 360 180 1 1 + """ + + # The minimum version of GMT required + required_version = "6.1.1" + + @property + def session_pointer(self): + """ + The :class:`ctypes.c_void_p` pointer to the current open GMT session. + + Raises + ------ + GMTCLibNoSessionError + If trying to access without a currently open GMT session (i.e., + outside of the context manager). + """ + if not hasattr(self, "_session_pointer") or self._session_pointer is None: + raise GMTCLibNoSessionError("No currently open GMT API session.") + return self._session_pointer + + @session_pointer.setter + def session_pointer(self, session): + """ + Set the session void pointer. + """ + self._session_pointer = session + + @property + def info(self): + """ + Dictionary with the GMT version and default paths and parameters. + """ + if not hasattr(self, "_info"): + self._info = { + "version": self.get_default("API_VERSION"), + "padding": self.get_default("API_PAD"), + "binary dir": self.get_default("API_BINDIR"), + "share dir": self.get_default("API_SHAREDIR"), + # This segfaults for some reason + # 'data dir': self.get_default("API_DATADIR"), + "plugin dir": self.get_default("API_PLUGINDIR"), + "library path": self.get_default("API_LIBRARY"), + "cores": self.get_default("API_CORES"), + # API_IMAGE_LAYOUT not defined if GMT is not compiled with GDAL + # "image layout": self.get_default("API_IMAGE_LAYOUT"), + "grid layout": self.get_default("API_GRID_LAYOUT"), + } + return self._info + + def __enter__(self): + """ + Create a GMT API session and check the libgmt version. + + Calls :meth:`pygmt.clib.Session.create`. + + Raises + ------ + GMTVersionError + If the version reported by libgmt is less than + ``Session.required_version``. Will destroy the session before + raising the exception. + """ + self.create("pygmt-session") + # Need to store the version info because 'get_default' won't work after + # the session is destroyed. + version = self.info["version"] + if Version(version) < Version(self.required_version): + self.destroy() + raise GMTVersionError( + "Using an incompatible GMT version {}. Must be equal or newer than {}.".format( + version, self.required_version + ) + ) + return self + + def __exit__(self, exc_type, exc_value, traceback): + """ + Destroy the currently open GMT API session. + + Calls :meth:`pygmt.clib.Session.destroy`. + """ + self.destroy() + + def __getitem__(self, name): + """ + Get the value of a GMT constant (C enum) from gmt_resources.h. + + Used to set configuration values for other API calls. Wraps + ``GMT_Get_Enum``. + + Parameters + ---------- + name : str + The name of the constant (e.g., ``"GMT_SESSION_EXTERNAL"``) + + Returns + ------- + constant : int + Integer value of the constant. Do not rely on this value because it + might change. + + Raises + ------ + GMTCLibError + If the constant doesn't exist. + """ + c_get_enum = self.get_libgmt_func( + "GMT_Get_Enum", argtypes=[ctp.c_void_p, ctp.c_char_p], restype=ctp.c_int + ) + + # The C lib introduced the void API pointer to GMT_Get_Enum so that + # it's consistent with other functions. It doesn't use the pointer so + # we can pass in None (NULL pointer). We can't give it the actual + # pointer because we need to call GMT_Get_Enum when creating a new API + # session pointer (chicken-and-egg type of thing). + session = None + + value = c_get_enum(session, name.encode()) + + if value is None or value == -99999: + raise GMTCLibError(f"Constant '{name}' doesn't exist in libgmt.") + + return value + + def get_libgmt_func(self, name, argtypes=None, restype=None): + """ + Get a ctypes function from the libgmt shared library. + + Assigns the argument and return type conversions for the function. + + Use this method to access a C function from libgmt. + + Parameters + ---------- + name : str + The name of the GMT API function. + argtypes : list + List of ctypes types used to convert the Python input arguments for + the API function. + restype : ctypes type + The ctypes type used to convert the input returned by the function + into a Python type. + + Returns + ------- + function + The GMT API function. + + Examples + -------- + + >>> from ctypes import c_void_p, c_int + >>> with Session() as lib: + ... func = lib.get_libgmt_func( + ... "GMT_Destroy_Session", argtypes=[c_void_p], restype=c_int + ... ) + ... + >>> type(func) + ._FuncPtr'> + """ + if not hasattr(self, "_libgmt"): + self._libgmt = load_libgmt() + function = getattr(self._libgmt, name) + if argtypes is not None: + function.argtypes = argtypes + if restype is not None: + function.restype = restype + return function + + def create(self, name): + """ + Create a new GMT C API session. + + This is required before most other methods of + :class:`pygmt.clib.Session` can be called. + + .. warning:: + + Usage of :class:`pygmt.clib.Session` as a context manager in a + ``with`` block is preferred over calling + :meth:`pygmt.clib.Session.create` and + :meth:`pygmt.clib.Session.destroy` manually. + + Calls ``GMT_Create_Session`` and generates a new ``GMTAPI_CTRL`` + struct, which is a :class:`ctypes.c_void_p` pointer. Sets the + ``session_pointer`` attribute to this pointer. + + Remember to terminate the current session using + :meth:`pygmt.clib.Session.destroy` before creating a new one. + + Parameters + ---------- + name : str + A name for this session. Doesn't really affect the outcome. + """ + try: + # Won't raise an exception if there is a currently open session + self.session_pointer # pylint: disable=pointless-statement + # In this case, fail to create a new session until the old one is + # destroyed + raise GMTCLibError( + "Failed to create a GMT API session: There is a currently open session." + " Must destroy it fist." + ) + # If the exception is raised, this means that there is no open session + # and we're free to create a new one. + except GMTCLibNoSessionError: + pass + + c_create_session = self.get_libgmt_func( + "GMT_Create_Session", + argtypes=[ctp.c_char_p, ctp.c_uint, ctp.c_uint, ctp.c_void_p], + restype=ctp.c_void_p, + ) + + # Capture the output printed by GMT into this list. Will use it later + # to generate error messages for the exceptions raised by API calls. + self._error_log = [] + + @ctp.CFUNCTYPE(ctp.c_int, ctp.c_void_p, ctp.c_char_p) + def print_func(file_pointer, message): # pylint: disable=unused-argument + """ + Callback function that the GMT C API will use to print log and + error messages. + + We'll capture the messages and print them to stderr so that they + will show up on the Jupyter notebook. + """ + message = message.decode().strip() + self._error_log.append(message) + # flush to make sure the messages are printed even if we have a + # crash. + print(message, file=sys.stderr, flush=True) + return 0 + + # Need to store a copy of the function because ctypes doesn't and it + # will be garbage collected otherwise + self._print_callback = print_func + + padding = self["GMT_PAD_DEFAULT"] + session_type = self["GMT_SESSION_EXTERNAL"] + + session = c_create_session(name.encode(), padding, session_type, print_func) + + if session is None: + raise GMTCLibError( + "Failed to create a GMT API session:\n{}".format(self._error_message) + ) + + self.session_pointer = session + + @property + def _error_message(self): + """ + A string with all error messages emitted by the C API. + + Only includes messages with the string ``"[ERROR]"`` in them. + """ + msg = "" + if hasattr(self, "_error_log"): + msg = "\n".join(line for line in self._error_log if "[ERROR]" in line) + return msg + + def destroy(self): + """ + Destroy the currently open GMT API session. + + .. warning:: + + Usage of :class:`pygmt.clib.Session` as a context manager in a + ``with`` block is preferred over calling + :meth:`pygmt.clib.Session.create` and + :meth:`pygmt.clib.Session.destroy` manually. + + Calls ``GMT_Destroy_Session`` to terminate and free the memory of a + registered ``GMTAPI_CTRL`` session (the pointer for this struct is + stored in the ``session_pointer`` attribute). + + Always use this method after you are done using a C API session. The + session needs to be destroyed before creating a new one. Otherwise, + some of the configuration files might be left behind and can influence + subsequent API calls. + + Sets the ``session_pointer`` attribute to ``None``. + """ + c_destroy_session = self.get_libgmt_func( + "GMT_Destroy_Session", argtypes=[ctp.c_void_p], restype=ctp.c_int + ) + + status = c_destroy_session(self.session_pointer) + if status: + raise GMTCLibError( + "Failed to destroy GMT API session:\n{}".format(self._error_message) + ) + + self.session_pointer = None + + def get_default(self, name): + """ + Get the value of a GMT default parameter (library version, paths, etc). + + Possible default parameter names include: + + * ``"API_VERSION"``: The GMT version + * ``"API_PAD"``: The grid padding setting + * ``"API_BINDIR"``: The binary file directory + * ``"API_SHAREDIR"``: The share directory + * ``"API_DATADIR"``: The data directory + * ``"API_PLUGINDIR"``: The plugin directory + * ``"API_LIBRARY"``: The core library path + * ``"API_CORES"``: The number of cores + * ``"API_IMAGE_LAYOUT"``: The image/band layout + * ``"API_GRID_LAYOUT"``: The grid layout + + Parameters + ---------- + name : str + The name of the default parameter (e.g., ``"API_VERSION"``) + + Returns + ------- + value : str + The default value for the parameter. + + Raises + ------ + GMTCLibError + If the parameter doesn't exist. + """ + c_get_default = self.get_libgmt_func( + "GMT_Get_Default", + argtypes=[ctp.c_void_p, ctp.c_char_p, ctp.c_char_p], + restype=ctp.c_int, + ) + + # Make a string buffer to get a return value + value = ctp.create_string_buffer(10000) + + status = c_get_default(self.session_pointer, name.encode(), value) + + if status != 0: + raise GMTCLibError( + "Error getting default value for '{}' (error code {}).".format( + name, status + ) + ) + + return value.value.decode() + + def call_module(self, module, args): + """ + Call a GMT module with the given arguments. + + Makes a call to ``GMT_Call_Module`` from the C API using mode + ``GMT_MODULE_CMD`` (arguments passed as a single string). + + Most interactions with the C API are done through this function. + + Parameters + ---------- + module : str + Module name (``'coast'``, ``'basemap'``, etc). + args : str + String with the command line arguments that will be passed to the + module (for example, ``'-R0/5/0/10 -JM'``). + + Raises + ------ + GMTCLibError + If the returned status code of the function is non-zero. + """ + c_call_module = self.get_libgmt_func( + "GMT_Call_Module", + argtypes=[ctp.c_void_p, ctp.c_char_p, ctp.c_int, ctp.c_void_p], + restype=ctp.c_int, + ) + + mode = self["GMT_MODULE_CMD"] + status = c_call_module( + self.session_pointer, module.encode(), mode, args.encode() + ) + if status != 0: + raise GMTCLibError( + "Module '{}' failed with status code {}:\n{}".format( + module, status, self._error_message + ) + ) + + def create_data(self, family, geometry, mode, **kwargs): + """ + Create an empty GMT data container. + + Parameters + ---------- + family : str + A valid GMT data family name (e.g., ``'GMT_IS_DATASET'``). See the + ``FAMILIES`` attribute for valid names. + geometry : str + A valid GMT data geometry name (e.g., ``'GMT_IS_POINT'``). See the + ``GEOMETRIES`` attribute for valid names. + mode : str + A valid GMT data mode (e.g., ``'GMT_IS_OUTPUT'``). See the + ``MODES`` attribute for valid names. + dim : list of 4 integers + The dimensions of the dataset. See the documentation for the GMT C + API function ``GMT_Create_Data`` (``src/gmt_api.c``) for the full + range of options regarding 'dim'. If ``None``, will pass in the + NULL pointer. + ranges : list of 4 floats + The dataset extent. Also a bit of a complicated argument. See the C + function documentation. It's called ``range`` in the C function but + it would conflict with the Python built-in ``range`` function. + inc : list of 2 floats + The increments between points of the dataset. See the C function + documentation. + registration : str + The node registration (what the coordinates mean). Can be + ``'GMT_GRID_PIXEL_REG'`` or ``'GMT_GRID_NODE_REG'``. Defaults to + ``'GMT_GRID_NODE_REG'``. + pad : int + The grid padding. Defaults to ``GMT_PAD_DEFAULT``. + + Returns + ------- + data_ptr : int + A ctypes pointer (an integer) to the allocated ``GMT_Dataset`` + object. + """ + c_create_data = self.get_libgmt_func( + "GMT_Create_Data", + argtypes=[ + ctp.c_void_p, # API + ctp.c_uint, # family + ctp.c_uint, # geometry + ctp.c_uint, # mode + ctp.POINTER(ctp.c_uint64), # dim + ctp.POINTER(ctp.c_double), # range + ctp.POINTER(ctp.c_double), # inc + ctp.c_uint, # registration + ctp.c_int, # pad + ctp.c_void_p, + ], # data + restype=ctp.c_void_p, + ) + + family_int = self._parse_constant(family, valid=FAMILIES, valid_modifiers=VIAS) + mode_int = self._parse_constant( + mode, + valid=MODES, + valid_modifiers=["GMT_GRID_IS_CARTESIAN", "GMT_GRID_IS_GEO"], + ) + geometry_int = self._parse_constant(geometry, valid=GEOMETRIES) + registration_int = self._parse_constant( + kwargs.get("registration", "GMT_GRID_NODE_REG"), valid=REGISTRATIONS + ) + + # Convert dim, ranges, and inc to ctypes arrays if given (will be None + # if not given to represent NULL pointers) + dim = kwargs_to_ctypes_array("dim", kwargs, ctp.c_uint64 * 4) + ranges = kwargs_to_ctypes_array("ranges", kwargs, ctp.c_double * 4) + inc = kwargs_to_ctypes_array("inc", kwargs, ctp.c_double * 2) + + # Use a NULL pointer (None) for existing data to indicate that the + # container should be created empty. Fill it in later using put_vector + # and put_matrix. + data_ptr = c_create_data( + self.session_pointer, + family_int, + geometry_int, + mode_int, + dim, + ranges, + inc, + registration_int, + self._parse_pad(family, kwargs), + None, + ) + + if data_ptr is None: + raise GMTCLibError("Failed to create an empty GMT data pointer.") + + return data_ptr + + def _parse_pad(self, family, kwargs): + """ + Parse and return an appropriate value for pad if none is given. + + Pad is a bit tricky because, for matrix types, pad control the matrix + ordering (row or column major). Using the default pad will set it to + column major and mess things up with the numpy arrays. + """ + pad = kwargs.get("pad", None) + if pad is None: + if "MATRIX" in family: + pad = 0 + else: + pad = self["GMT_PAD_DEFAULT"] + return pad + + def _parse_constant(self, constant, valid, valid_modifiers=None): + """ + Parse a constant, convert it to an int, and validate it. + + The GMT C API takes certain defined constants, like ``'GMT_IS_GRID'``, + that need to be validated and converted to integer values using + :meth:`pygmt.clib.Session.__getitem__`. + + The constants can also take a modifier by appending another constant + name, e.g. ``'GMT_IS_GRID|GMT_VIA_MATRIX'``. The two parts must be + converted separately and their values are added. + + If valid modifiers are not given, then will assume that modifiers are + not allowed. In this case, will raise a + :class:`pygmt.exceptions.GMTInvalidInput` exception if given a + modifier. + + Parameters + ---------- + constant : str + The name of a valid GMT API constant, with an optional modifier. + valid : list of str + A list of valid values for the constant. Will raise a + :class:`pygmt.exceptions.GMTInvalidInput` exception if the given + value is not on the list. + """ + parts = constant.split("|") + name = parts[0] + nmodifiers = len(parts) - 1 + if nmodifiers > 1: + raise GMTInvalidInput( + "Only one modifier is allowed in constants, {} given: '{}'".format( + nmodifiers, constant + ) + ) + if nmodifiers > 0 and valid_modifiers is None: + raise GMTInvalidInput( + "Constant modifiers not allowed since valid values were not " + + "given: '{}'".format(constant) + ) + if name not in valid: + raise GMTInvalidInput( + "Invalid constant argument '{}'. Must be one of {}.".format( + name, str(valid) + ) + ) + if ( + nmodifiers > 0 + and valid_modifiers is not None + and parts[1] not in valid_modifiers + ): + raise GMTInvalidInput( + "Invalid constant modifier '{}'. Must be one of {}.".format( + parts[1], str(valid_modifiers) + ) + ) + integer_value = sum(self[part] for part in parts) + return integer_value + + def _check_dtype_and_dim(self, array, ndim): + """ + Check that a numpy array has the given dimensions and is a valid data + type. + + Parameters + ---------- + array : numpy array + The array to be tested. + ndim : int + The desired dimension of the array. + + Returns + ------- + gmt_type : int + The GMT constant value representing this data type. + + Raises + ------ + GMTCLibError + If the array has the wrong dimensions or is an unsupported data + type. + + Examples + -------- + + >>> import numpy as np + >>> data = np.array([1, 2, 3], dtype="float64") + >>> with Session() as ses: + ... gmttype = ses._check_dtype_and_dim(data, ndim=1) + ... gmttype == ses["GMT_DOUBLE"] + ... + True + >>> data = np.ones((5, 2), dtype="float32") + >>> with Session() as ses: + ... gmttype = ses._check_dtype_and_dim(data, ndim=2) + ... gmttype == ses["GMT_FLOAT"] + ... + True + """ + # check the array has the given dimension + if array.ndim != ndim: + raise GMTInvalidInput( + "Expected a numpy 1d array, got {}d.".format(array.ndim) + ) + + # check the array has a valid/known data type + if array.dtype.type not in DTYPES: + try: + # Try to convert any unknown numpy data types to np.datetime64 + array = np.asarray(array, dtype=np.datetime64) + except ValueError as e: + raise GMTInvalidInput( + f"Unsupported numpy data type '{array.dtype.type}'." + ) from e + return self[DTYPES[array.dtype.type]] + + def put_vector(self, dataset, column, vector): + """ + Attach a numpy 1D array as a column on a GMT dataset. + + Use this function to attach numpy array data to a GMT dataset and pass + it to GMT modules. Wraps ``GMT_Put_Vector``. + + The dataset must be created by :meth:`pygmt.clib.Session.create_data` + first. Use ``family='GMT_IS_DATASET|GMT_VIA_VECTOR'``. + + Not at all numpy dtypes are supported, only: float64, float32, int64, + int32, uint64, and uint32. + + .. warning:: + The numpy array must be C contiguous in memory. If it comes from a + column slice of a 2d array, for example, you will have to make a + copy. Use :func:`numpy.ascontiguousarray` to make sure your vector + is contiguous (it won't copy if it already is). + + Parameters + ---------- + dataset : :class:`ctypes.c_void_p` + The ctypes void pointer to a ``GMT_Dataset``. Create it with + :meth:`pygmt.clib.Session.create_data`. + column : int + The column number of this vector in the dataset (starting from 0). + vector : numpy 1d-array + The array that will be attached to the dataset. Must be a 1d C + contiguous array. + + Raises + ------ + GMTCLibError + If given invalid input or ``GMT_Put_Vector`` exits with status != + 0. + """ + c_put_vector = self.get_libgmt_func( + "GMT_Put_Vector", + argtypes=[ctp.c_void_p, ctp.c_void_p, ctp.c_uint, ctp.c_uint, ctp.c_void_p], + restype=ctp.c_int, + ) + + gmt_type = self._check_dtype_and_dim(vector, ndim=1) + if gmt_type == self["GMT_DATETIME"]: + vector_pointer = (ctp.c_char_p * len(vector))() + vector_pointer[:] = np.char.encode( + np.datetime_as_string(array_to_datetime(vector)) + ) + else: + vector_pointer = vector.ctypes.data_as(ctp.c_void_p) + status = c_put_vector( + self.session_pointer, dataset, column, gmt_type, vector_pointer + ) + if status != 0: + raise GMTCLibError( + " ".join( + [ + "Failed to put vector of type {}".format(vector.dtype), + "in column {} of dataset.".format(column), + ] + ) + ) + + def put_strings(self, dataset, family, strings): + """ + Attach a numpy 1D array of dtype str as a column on a GMT dataset. + + Use this function to attach string type numpy array data to a GMT + dataset and pass it to GMT modules. Wraps ``GMT_Put_Strings``. + + The dataset must be created by :meth:`pygmt.clib.Session.create_data` + first. + + .. warning:: + The numpy array must be C contiguous in memory. If it comes from a + column slice of a 2d array, for example, you will have to make a + copy. Use :func:`numpy.ascontiguousarray` to make sure your vector + is contiguous (it won't copy if it already is). + + Parameters + ---------- + dataset : :class:`ctypes.c_void_p` + The ctypes void pointer to a ``GMT_Dataset``. Create it with + :meth:`pygmt.clib.Session.create_data`. + family : str + The family type of the dataset. Can be either ``GMT_IS_VECTOR`` or + ``GMT_IS_MATRIX``. + strings : numpy 1d-array + The array that will be attached to the dataset. Must be a 1d C + contiguous array. + + Raises + ------ + GMTCLibError + If given invalid input or ``GMT_Put_Strings`` exits with status != + 0. + """ + c_put_strings = self.get_libgmt_func( + "GMT_Put_Strings", + argtypes=[ + ctp.c_void_p, + ctp.c_uint, + ctp.c_void_p, + ctp.POINTER(ctp.c_char_p), + ], + restype=ctp.c_int, + ) + + strings_pointer = (ctp.c_char_p * len(strings))() + strings_pointer[:] = np.char.encode(strings) + + family_int = self._parse_constant( + family, valid=FAMILIES, valid_modifiers=METHODS + ) + + status = c_put_strings( + self.session_pointer, family_int, dataset, strings_pointer + ) + if status != 0: + raise GMTCLibError( + f"Failed to put strings of type {strings.dtype} into dataset" + ) + + def put_matrix(self, dataset, matrix, pad=0): + """ + Attach a numpy 2D array to a GMT dataset. + + Use this function to attach numpy array data to a GMT dataset and pass + it to GMT modules. Wraps ``GMT_Put_Matrix``. + + The dataset must be created by :meth:`pygmt.clib.Session.create_data` + first. Use ``|GMT_VIA_MATRIX'`` in the family. + + Not at all numpy dtypes are supported, only: float64, float32, int64, + int32, uint64, and uint32. + + .. warning:: + The numpy array must be C contiguous in memory. Use + :func:`numpy.ascontiguousarray` to make sure your vector is + contiguous (it won't copy if it already is). + + Parameters + ---------- + dataset : :class:`ctypes.c_void_p` + The ctypes void pointer to a ``GMT_Dataset``. Create it with + :meth:`pygmt.clib.Session.create_data`. + matrix : numpy 2d-array + The array that will be attached to the dataset. Must be a 2d C + contiguous array. + pad : int + The amount of padding that should be added to the matrix. Use when + creating grids for modules that require padding. + + Raises + ------ + GMTCLibError + If given invalid input or ``GMT_Put_Matrix`` exits with status != + 0. + """ + c_put_matrix = self.get_libgmt_func( + "GMT_Put_Matrix", + argtypes=[ctp.c_void_p, ctp.c_void_p, ctp.c_uint, ctp.c_int, ctp.c_void_p], + restype=ctp.c_int, + ) + + gmt_type = self._check_dtype_and_dim(matrix, ndim=2) + matrix_pointer = matrix.ctypes.data_as(ctp.c_void_p) + status = c_put_matrix( + self.session_pointer, dataset, gmt_type, pad, matrix_pointer + ) + if status != 0: + raise GMTCLibError("Failed to put matrix of type {}.".format(matrix.dtype)) + + def write_data(self, family, geometry, mode, wesn, output, data): + """ + Write a GMT data container to a file. + + The data container should be created by + :meth:`pygmt.clib.Session.create_data`. + + Wraps ``GMT_Write_Data`` but only allows writing to a file. So the + ``method`` argument is omitted. + + Parameters + ---------- + family : str + A valid GMT data family name (e.g., ``'GMT_IS_DATASET'``). See the + ``FAMILIES`` attribute for valid names. Don't use the + ``GMT_VIA_VECTOR`` or ``GMT_VIA_MATRIX`` constructs for this. Use + ``GMT_IS_VECTOR`` and ``GMT_IS_MATRIX`` instead. + geometry : str + A valid GMT data geometry name (e.g., ``'GMT_IS_POINT'``). See the + ``GEOMETRIES`` attribute for valid names. + mode : str + How the data is to be written to the file. This option varies + depending on the given family. See the GMT API documentation for + details. + wesn : list or numpy array + [xmin, xmax, ymin, ymax, zmin, zmax] of the data. Must have 6 + elements. + output : str + The output file name. + data : :class:`ctypes.c_void_p` + Pointer to the data container created by + :meth:`pygmt.clib.Session.create_data`. + + Raises + ------ + GMTCLibError + For invalid input arguments or if the GMT API functions returns a + non-zero status code. + """ + c_write_data = self.get_libgmt_func( + "GMT_Write_Data", + argtypes=[ + ctp.c_void_p, + ctp.c_uint, + ctp.c_uint, + ctp.c_uint, + ctp.c_uint, + ctp.POINTER(ctp.c_double), + ctp.c_char_p, + ctp.c_void_p, + ], + restype=ctp.c_int, + ) + + family_int = self._parse_constant(family, valid=FAMILIES, valid_modifiers=VIAS) + geometry_int = self._parse_constant(geometry, valid=GEOMETRIES) + status = c_write_data( + self.session_pointer, + family_int, + self["GMT_IS_FILE"], + geometry_int, + self[mode], + (ctp.c_double * 6)(*wesn), + output.encode(), + data, + ) + if status != 0: + raise GMTCLibError("Failed to write dataset to '{}'".format(output)) + + @contextmanager + def open_virtual_file(self, family, geometry, direction, data): + """ + Open a GMT Virtual File to pass data to and from a module. + + GMT uses a virtual file scheme to pass in data to API modules. Use it + to pass in your GMT data structure (created using + :meth:`pygmt.clib.Session.create_data`) to a module that expects an + input or output file. + + Use in a ``with`` block. Will automatically close the virtual file when + leaving the ``with`` block. Because of this, no wrapper for + ``GMT_Close_VirtualFile`` is provided. + + Parameters + ---------- + family : str + A valid GMT data family name (e.g., ``'GMT_IS_DATASET'``). Should + be the same as the one you used to create your data structure. + geometry : str + A valid GMT data geometry name (e.g., ``'GMT_IS_POINT'``). Should + be the same as the one you used to create your data structure. + direction : str + Either ``'GMT_IN'`` or ``'GMT_OUT'`` to indicate if passing data to + GMT or getting it out of GMT, respectively. + By default, GMT can modify the data you pass in. Add modifier + ``'GMT_IS_REFERENCE'`` to tell GMT the data are read-only, or + ``'GMT_IS_DUPLICATE'`` to tell GMT to duplicate the data. + data : int + The ctypes void pointer to your GMT data structure. + + Yields + ------ + vfname : str + The name of the virtual file that you can pass to a GMT module. + + Examples + -------- + + >>> from pygmt.helpers import GMTTempFile + >>> import os + >>> import numpy as np + >>> x = np.array([0, 1, 2, 3, 4]) + >>> y = np.array([5, 6, 7, 8, 9]) + >>> with Session() as lib: + ... family = "GMT_IS_DATASET|GMT_VIA_VECTOR" + ... geometry = "GMT_IS_POINT" + ... dataset = lib.create_data( + ... family=family, + ... geometry=geometry, + ... mode="GMT_CONTAINER_ONLY", + ... dim=[2, 5, 1, 0], # columns, lines, segments, type + ... ) + ... lib.put_vector(dataset, column=0, vector=x) + ... lib.put_vector(dataset, column=1, vector=y) + ... # Add the dataset to a virtual file + ... vfargs = (family, geometry, "GMT_IN|GMT_IS_REFERENCE", dataset) + ... with lib.open_virtual_file(*vfargs) as vfile: + ... # Send the output to a temp file so that we can read it + ... with GMTTempFile() as ofile: + ... args = "{} ->{}".format(vfile, ofile.name) + ... lib.call_module("info", args) + ... print(ofile.read().strip()) + ... + : N = 5 <0/4> <5/9> + """ + c_open_virtualfile = self.get_libgmt_func( + "GMT_Open_VirtualFile", + argtypes=[ + ctp.c_void_p, + ctp.c_uint, + ctp.c_uint, + ctp.c_uint, + ctp.c_void_p, + ctp.c_char_p, + ], + restype=ctp.c_int, + ) + + c_close_virtualfile = self.get_libgmt_func( + "GMT_Close_VirtualFile", + argtypes=[ctp.c_void_p, ctp.c_char_p], + restype=ctp.c_int, + ) + + family_int = self._parse_constant(family, valid=FAMILIES, valid_modifiers=VIAS) + geometry_int = self._parse_constant(geometry, valid=GEOMETRIES) + direction_int = self._parse_constant( + direction, valid=["GMT_IN", "GMT_OUT"], valid_modifiers=METHODS + ) + + buff = ctp.create_string_buffer(self["GMT_VF_LEN"]) + + status = c_open_virtualfile( + self.session_pointer, family_int, geometry_int, direction_int, data, buff + ) + + if status != 0: + raise GMTCLibError("Failed to create a virtual file.") + + vfname = buff.value.decode() + + try: + yield vfname + finally: + status = c_close_virtualfile(self.session_pointer, vfname.encode()) + if status != 0: + raise GMTCLibError("Failed to close virtual file '{}'.".format(vfname)) + + @contextmanager + def virtualfile_from_vectors(self, *vectors): + """ + Store 1d arrays as columns of a table inside a virtual file. + + Use the virtual file name to pass in the data in your vectors to a GMT + module. + + Context manager (use in a ``with`` block). Yields the virtual file name + that you can pass as an argument to a GMT module call. Closes the + virtual file upon exit of the ``with`` block. + + Use this instead of creating the data container and virtual file by + hand with :meth:`pygmt.clib.Session.create_data`, + :meth:`pygmt.clib.Session.put_vector`, and + :meth:`pygmt.clib.Session.open_virtual_file`. + + If the arrays are C contiguous blocks of memory, they will be passed + without copying to GMT. If they are not (e.g., they are columns of a 2D + array), they will need to be copied to a contiguous block. + + Parameters + ---------- + vectors : 1d arrays + The vectors that will be included in the array. All must be of the + same size. + + Yields + ------ + fname : str + The name of virtual file. Pass this as a file name argument to a + GMT module. + + Examples + -------- + + >>> from pygmt.helpers import GMTTempFile + >>> import numpy as np + >>> import pandas as pd + >>> x = [1, 2, 3] + >>> y = np.array([4, 5, 6]) + >>> z = pd.Series([7, 8, 9]) + >>> with Session() as ses: + ... with ses.virtualfile_from_vectors(x, y, z) as fin: + ... # Send the output to a file so that we can read it + ... with GMTTempFile() as fout: + ... ses.call_module( + ... "info", "{} ->{}".format(fin, fout.name) + ... ) + ... print(fout.read().strip()) + ... + : N = 3 <1/3> <4/6> <7/9> + """ + # Conversion to a C-contiguous array needs to be done here and not in + # put_vector or put_strings because we need to maintain a reference to + # the copy while it is being used by the C API. Otherwise, the array + # would be garbage collected and the memory freed. Creating it in this + # context manager guarantees that the copy will be around until the + # virtual file is closed. The conversion is implicit in + # vectors_to_arrays. + arrays = vectors_to_arrays(vectors) + + columns = len(arrays) + # Find arrays that are of string dtype from column 3 onwards + # Assumes that first 2 columns contains coordinates like longitude + # latitude, or datetime string types. + for col, array in enumerate(arrays[2:]): + if pd.api.types.is_string_dtype(array.dtype): + columns = col + 2 + break + + rows = len(arrays[0]) + if not all(len(i) == rows for i in arrays): + raise GMTInvalidInput("All arrays must have same size.") + + family = "GMT_IS_DATASET|GMT_VIA_VECTOR" + geometry = "GMT_IS_POINT" + + dataset = self.create_data( + family, geometry, mode="GMT_CONTAINER_ONLY", dim=[columns, rows, 1, 0] + ) + + # Use put_vector for columns with numerical type data + for col, array in enumerate(arrays[:columns]): + self.put_vector(dataset, column=col, vector=array) + + # Use put_strings for last column(s) with string type data + # Have to use modifier "GMT_IS_DUPLICATE" to duplicate the strings + string_arrays = arrays[columns:] + if string_arrays: + if len(string_arrays) == 1: + strings = string_arrays[0] + elif len(string_arrays) > 1: + strings = np.apply_along_axis( + func1d=" ".join, axis=0, arr=string_arrays + ) + strings = np.asanyarray(a=strings, dtype=str) + self.put_strings( + dataset, family="GMT_IS_VECTOR|GMT_IS_DUPLICATE", strings=strings + ) + + with self.open_virtual_file( + family, geometry, "GMT_IN|GMT_IS_REFERENCE", dataset + ) as vfile: + yield vfile + + @contextmanager + def virtualfile_from_matrix(self, matrix): + """ + Store a 2d array as a table inside a virtual file. + + Use the virtual file name to pass in the data in your matrix to a GMT + module. + + Context manager (use in a ``with`` block). Yields the virtual file name + that you can pass as an argument to a GMT module call. Closes the + virtual file upon exit of the ``with`` block. + + The virtual file will contain the array as a ``GMT_MATRIX`` pretending + to be a ``GMT_DATASET``. + + **Not meant for creating ``GMT_GRID``**. The grid requires more + metadata than just the data matrix. Use + :meth:`pygmt.clib.Session.virtualfile_from_grid` instead. + + Use this instead of creating the data container and virtual file by + hand with :meth:`pygmt.clib.Session.create_data`, + :meth:`pygmt.clib.Session.put_matrix`, and + :meth:`pygmt.clib.Session.open_virtual_file` + + The matrix must be C contiguous in memory. If it is not (e.g., it is a + slice of a larger array), the array will be copied to make sure it is. + + Parameters + ---------- + matrix : 2d array + The matrix that will be included in the GMT data container. + + Yields + ------ + fname : str + The name of virtual file. Pass this as a file name argument to a + GMT module. + + Examples + -------- + + >>> from pygmt.helpers import GMTTempFile + >>> import numpy as np + >>> data = np.arange(12).reshape((4, 3)) + >>> print(data) + [[ 0 1 2] + [ 3 4 5] + [ 6 7 8] + [ 9 10 11]] + >>> with Session() as ses: + ... with ses.virtualfile_from_matrix(data) as fin: + ... # Send the output to a file so that we can read it + ... with GMTTempFile() as fout: + ... ses.call_module( + ... "info", "{} ->{}".format(fin, fout.name) + ... ) + ... print(fout.read().strip()) + ... + : N = 4 <0/9> <1/10> <2/11> + """ + # Conversion to a C-contiguous array needs to be done here and not in + # put_matrix because we need to maintain a reference to the copy while + # it is being used by the C API. Otherwise, the array would be garbage + # collected and the memory freed. Creating it in this context manager + # guarantees that the copy will be around until the virtual file is + # closed. + matrix = as_c_contiguous(matrix) + rows, columns = matrix.shape + + family = "GMT_IS_DATASET|GMT_VIA_MATRIX" + geometry = "GMT_IS_POINT" + + dataset = self.create_data( + family, geometry, mode="GMT_CONTAINER_ONLY", dim=[columns, rows, 1, 0] + ) + + self.put_matrix(dataset, matrix) + + with self.open_virtual_file( + family, geometry, "GMT_IN|GMT_IS_REFERENCE", dataset + ) as vfile: + yield vfile + + @contextmanager + def virtualfile_from_grid(self, grid): + """ + Store a grid in a virtual file. + + Use the virtual file name to pass in the data in your grid to a GMT + module. Grids must be :class:`xarray.DataArray` instances. + + Context manager (use in a ``with`` block). Yields the virtual file name + that you can pass as an argument to a GMT module call. Closes the + virtual file upon exit of the ``with`` block. + + The virtual file will contain the grid as a ``GMT_MATRIX`` with extra + metadata. + + Use this instead of creating a data container and virtual file by hand + with :meth:`pygmt.clib.Session.create_data`, + :meth:`pygmt.clib.Session.put_matrix`, and + :meth:`pygmt.clib.Session.open_virtual_file` + + The grid data matrix must be C contiguous in memory. If it is not + (e.g., it is a slice of a larger array), the array will be copied to + make sure it is. + + Parameters + ---------- + grid : :class:`xarray.DataArray` + The grid that will be included in the virtual file. + + Yields + ------ + fname : str + The name of virtual file. Pass this as a file name argument to a + GMT module. + + Examples + -------- + + >>> from pygmt.datasets import load_earth_relief + >>> from pygmt.helpers import GMTTempFile + >>> data = load_earth_relief(resolution="01d") + >>> print(data.shape) + (180, 360) + >>> print(data.lon.values.min(), data.lon.values.max()) + -179.5 179.5 + >>> print(data.lat.values.min(), data.lat.values.max()) + -89.5 89.5 + >>> print(data.values.min(), data.values.max()) + -8182.0 5651.5 + >>> with Session() as ses: + ... with ses.virtualfile_from_grid(data) as fin: + ... # Send the output to a file so that we can read it + ... with GMTTempFile() as fout: + ... args = "{} -L0 -Cn ->{}".format(fin, fout.name) + ... ses.call_module("grdinfo", args) + ... print(fout.read().strip()) + ... + -180 180 -90 90 -8182 5651.5 1 1 360 180 1 1 + >>> # The output is: w e s n z0 z1 dx dy n_columns n_rows reg gtype + """ + _gtype = {0: "GMT_GRID_IS_CARTESIAN", 1: "GMT_GRID_IS_GEO"}[grid.gmt.gtype] + _reg = {0: "GMT_GRID_NODE_REG", 1: "GMT_GRID_PIXEL_REG"}[grid.gmt.registration] + + # Conversion to a C-contiguous array needs to be done here and not in + # put_matrix because we need to maintain a reference to the copy while + # it is being used by the C API. Otherwise, the array would be garbage + # collected and the memory freed. Creating it in this context manager + # guarantees that the copy will be around until the virtual file is + # closed. The conversion is implicit in dataarray_to_matrix. + matrix, region, inc = dataarray_to_matrix(grid) + + family = "GMT_IS_GRID|GMT_VIA_MATRIX" + geometry = "GMT_IS_SURFACE" + gmt_grid = self.create_data( + family, + geometry, + mode=f"GMT_CONTAINER_ONLY|{_gtype}", + ranges=region, + inc=inc, + registration=_reg, + ) + self.put_matrix(gmt_grid, matrix) + args = (family, geometry, "GMT_IN|GMT_IS_REFERENCE", gmt_grid) + with self.open_virtual_file(*args) as vfile: + yield vfile + + def virtualfile_from_data(self, check_kind=None, data=None, x=None, y=None, z=None): + """ + Store any data inside a virtual file. + + This convenience function automatically detects the kind of data passed + into it, and produces a virtualfile that can be passed into GMT later + on. + + Parameters + ---------- + check_kind : str + Used to validate the type of data that can be passed in. Choose + from 'raster', 'vector' or None. Default is None (no validation). + data : str, xarray.DataArray, 2d array, or None + Any raster or vector data format. This could be a file name, a + raster grid, a vector matrix/arrays, or other supported data input. + x/y/z : 1d arrays or None + x, y and z columns as numpy arrays. + + Returns + ------- + file_context : contextlib._GeneratorContextManager + The virtual file stored inside a context manager. Access the file + name of this virtualfile using ``with file_context as fname: ...``. + + Examples + -------- + >>> from pygmt.helpers import GMTTempFile + >>> import xarray as xr + >>> data = xr.Dataset( + ... coords={"index": [0, 1, 2]}, + ... data_vars={ + ... "x": ("index", [9, 8, 7]), + ... "y": ("index", [6, 5, 4]), + ... "z": ("index", [3, 2, 1]), + ... }, + ... ) + >>> with Session() as ses: + ... with ses.virtualfile_from_data( + ... check_kind="vector", data=data + ... ) as fin: + ... # Send the output to a file so that we can read it + ... with GMTTempFile() as fout: + ... ses.call_module("info", f"{fin} ->{fout.name}") + ... print(fout.read().strip()) + ... + : N = 3 <7/9> <4/6> <1/3> + """ + kind = data_kind(data, x, y, z) + + if check_kind == "raster" and kind not in ("file", "grid"): + raise GMTInvalidInput(f"Unrecognized data type for grid: {type(data)}") + if check_kind == "vector" and kind not in ("file", "matrix", "vectors"): + raise GMTInvalidInput(f"Unrecognized data type: {type(data)}") + + # Decide which virtualfile_from_ function to use + _virtualfile_from = { + "file": dummy_context, + "grid": self.virtualfile_from_grid, + # Note: virtualfile_from_matrix is not used because a matrix can be + # converted to vectors instead, and using vectors allows for better + # handling of string type inputs (e.g. for datetime data types) + "matrix": self.virtualfile_from_vectors, + "vectors": self.virtualfile_from_vectors, + }[kind] + + # Ensure the data is an iterable (Python list or tuple) + if kind in ("file", "grid"): + _data = (data,) + elif kind == "vectors": + _data = (x, y, z) + elif kind == "matrix": # turn 2D arrays into list of vectors + try: + # pandas.DataFrame and xarray.Dataset types + _data = [array for _, array in data.items()] + except AttributeError: + # Python lists, tuples, and numpy ndarray types + _data = np.atleast_2d(np.asanyarray(data).T) + + # Finally create the virtualfile from the data, to be passed into GMT + file_context = _virtualfile_from(*_data) + + return file_context + + def extract_region(self): + """ + Extract the WESN bounding box of the currently active figure. + + Retrieves the information from the PostScript file, so it works for + country codes as well. + + Returns + ------- + * wesn : 1d array + A 1D numpy array with the west, east, south, and north dimensions + of the current figure. + + Examples + -------- + + >>> import pygmt + >>> fig = pygmt.Figure() + >>> fig.coast( + ... region=[0, 10, -20, -10], + ... projection="M6i", + ... frame=True, + ... land="black", + ... ) + >>> with Session() as lib: + ... wesn = lib.extract_region() + ... + >>> print(", ".join(["{:.2f}".format(x) for x in wesn])) + 0.00, 10.00, -20.00, -10.00 + + Using ISO country codes for the regions (for example ``'US.HI'`` for + Hawaii): + + >>> fig = pygmt.Figure() + >>> fig.coast( + ... region="US.HI", projection="M6i", frame=True, land="black" + ... ) + >>> with Session() as lib: + ... wesn = lib.extract_region() + ... + >>> print(", ".join(["{:.2f}".format(x) for x in wesn])) + -164.71, -154.81, 18.91, 23.58 + + The country codes can have an extra argument that rounds the region a + multiple of the argument (for example, ``'US.HI+r5'`` will round the + region to multiples of 5): + + >>> fig = pygmt.Figure() + >>> fig.coast( + ... region="US.HI+r5", projection="M6i", frame=True, land="black" + ... ) + >>> with Session() as lib: + ... wesn = lib.extract_region() + ... + >>> print(", ".join(["{:.2f}".format(x) for x in wesn])) + -165.00, -150.00, 15.00, 25.00 + """ + c_extract_region = self.get_libgmt_func( + "GMT_Extract_Region", + argtypes=[ctp.c_void_p, ctp.c_char_p, ctp.POINTER(ctp.c_double)], + restype=ctp.c_int, + ) + + wesn = np.empty(4, dtype=np.float64) + wesn_pointer = wesn.ctypes.data_as(ctp.POINTER(ctp.c_double)) + # The second argument to GMT_Extract_Region is a file pointer to a + # PostScript file. It's only valid in classic mode. Use None to get a + # NULL pointer instead. + status = c_extract_region(self.session_pointer, None, wesn_pointer) + if status != 0: + raise GMTCLibError("Failed to extract region from current figure.") + return wesn diff --git a/pygmt/datasets/earth_relief.py b/pygmt/datasets/earth_relief.py index d57948e957e..7e26305e88a 100644 --- a/pygmt/datasets/earth_relief.py +++ b/pygmt/datasets/earth_relief.py @@ -1,134 +1,134 @@ -""" -Function to download the Earth relief datasets from the GMT data server, and -load as :class:`xarray.DataArray`. - -The grids are available in various resolutions. -""" -import xarray as xr -from pygmt.exceptions import GMTInvalidInput -from pygmt.helpers import kwargs_to_strings -from pygmt.src import grdcut, which - - -@kwargs_to_strings(region="sequence") -def load_earth_relief(resolution="01d", region=None, registration=None): - r""" - Load Earth relief grids (topography and bathymetry) in various resolutions. - - The grids are downloaded to a user data directory - (usually ``~/.gmt/server/earth/earth_relief/``) 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_relief**\_\ *res*\[_\ *reg*] to any grid plotting/processing - function. *res* is the grid resolution (see below), and *reg* is grid - registration type (**p** for pixel registration or **g** for gridline - registration). - - Refer to :gmt-docs:`datasets/remote-data.html#global-earth-relief-grids` - for more details. - - Parameters - ---------- - resolution : str - The grid resolution. The suffix ``d``, ``m`` and ``s`` stand for - arc-degree, arc-minute and arc-second. It can be ``'01d'``, ``'30m'``, - ``'20m'``, ``'15m'``, ``'10m'``, ``'06m'``, ``'05m'``, ``'04m'``, - ``'03m'``, ``'02m'``, ``'01m'``, ``'30s'``, ``'15s'``, ``'03s'``, - or ``'01s'``. - - region : str or list - The subregion of the grid to load, in the forms of a list - [*xmin*, *xmax*, *ymin*, *ymax*] or a string *xmin/xmax/ymin/ymax*. - Required for Earth relief grids with resolutions higher than 5 - arc-minute (i.e., ``05m``). - - registration : str - Grid registration type. Either ``pixel`` for pixel registration or - ``gridline`` for gridline registration. Default is ``None``, where - a pixel-registered grid is returned unless only the - gridline-registered grid is available. - - Returns - ------- - grid : :class:`xarray.DataArray` - The Earth relief grid. Coordinates are latitude and longitude in - degrees. Relief is in meters. - - Notes - ----- - The :class:`xarray.DataArray` grid doesn't support slice operation, for - Earth relief data with resolutions higher than "05m", which are stored as - smaller tiles. - - Examples - -------- - - >>> # load the default grid (pixel-registered 01d grid) - >>> grid = load_earth_relief() - >>> # load the 30m grid with "gridline" registration - >>> grid = load_earth_relief("30m", registration="gridline") - >>> # load high-resolution grid for a specific region - >>> grid = load_earth_relief( - ... "05m", region=[120, 160, 30, 60], registration="gridline" - ... ) - """ - - # earth relief data stored as single grids for low resolutions - non_tiled_resolutions = ["01d", "30m", "20m", "15m", "10m", "06m"] - # earth relief data stored as tiles for high resolutions - tiled_resolutions = ["05m", "04m", "03m", "02m", "01m", "30s", "15s", "03s", "01s"] - - if registration in ("pixel", "gridline", None): - # If None, let GMT decide on Pixel/Gridline type - reg = f"_{registration[0]}" if registration else "" - else: - raise GMTInvalidInput( - f"Invalid grid registration: '{registration}', should be either " - "'pixel', 'gridline' or None. Default is None, where a " - "pixel-registered grid is returned unless only the " - "gridline-registered grid is available." - ) - - if resolution not in non_tiled_resolutions + tiled_resolutions: - raise GMTInvalidInput(f"Invalid Earth relief resolution '{resolution}'.") - - # Check combination of resolution and registeration. - if (resolution == "15s" and registration == "gridline") or ( - resolution in ("03s", "01s") and registration == "pixel" - ): - raise GMTInvalidInput( - f"{registration}-registered Earth relief data for " - f"resolution '{resolution}' is not supported." - ) - - # different ways to load tiled and non-tiled earth relief data - # Known issue: tiled grids don't support slice operation - # See https://github.com/GenericMappingTools/pygmt/issues/524 - if region is None: - if resolution in non_tiled_resolutions: - fname = which(f"@earth_relief_{resolution}{reg}", download="a") - with xr.open_dataarray(fname) as dataarray: - grid = dataarray.load() - _ = grid.gmt # load GMTDataArray accessor information - else: - raise GMTInvalidInput( - f"'region' is required for Earth relief resolution '{resolution}'." - ) - else: - grid = grdcut(f"@earth_relief_{resolution}{reg}", region=region) - - # Add some metadata to the grid - grid.name = "elevation" - grid.attrs["long_name"] = "elevation relative to the geoid" - grid.attrs["units"] = "meters" - grid.attrs["vertical_datum"] = "EMG96" - grid.attrs["horizontal_datum"] = "WGS84" - # Remove the actual range because it gets outdated when indexing the grid, - # which causes problems when exporting it to netCDF for usage on the - # command-line. - grid.attrs.pop("actual_range") - for coord in grid.coords: - grid[coord].attrs.pop("actual_range") - return grid +""" +Function to download the Earth relief datasets from the GMT data server, and +load as :class:`xarray.DataArray`. + +The grids are available in various resolutions. +""" +import xarray as xr +from pygmt.exceptions import GMTInvalidInput +from pygmt.helpers import kwargs_to_strings +from pygmt.src import grdcut, which + + +@kwargs_to_strings(region="sequence") +def load_earth_relief(resolution="01d", region=None, registration=None): + r""" + Load Earth relief grids (topography and bathymetry) in various resolutions. + + The grids are downloaded to a user data directory + (usually ``~/.gmt/server/earth/earth_relief/``) 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_relief**\_\ *res*\[_\ *reg*] to any grid plotting/processing + function. *res* is the grid resolution (see below), and *reg* is grid + registration type (**p** for pixel registration or **g** for gridline + registration). + + Refer to :gmt-docs:`datasets/remote-data.html#global-earth-relief-grids` + for more details. + + Parameters + ---------- + resolution : str + The grid resolution. The suffix ``d``, ``m`` and ``s`` stand for + arc-degree, arc-minute and arc-second. It can be ``'01d'``, ``'30m'``, + ``'20m'``, ``'15m'``, ``'10m'``, ``'06m'``, ``'05m'``, ``'04m'``, + ``'03m'``, ``'02m'``, ``'01m'``, ``'30s'``, ``'15s'``, ``'03s'``, + or ``'01s'``. + + region : str or list + The subregion of the grid to load, in the forms of a list + [*xmin*, *xmax*, *ymin*, *ymax*] or a string *xmin/xmax/ymin/ymax*. + Required for Earth relief grids with resolutions higher than 5 + arc-minute (i.e., ``05m``). + + registration : str + Grid registration type. Either ``pixel`` for pixel registration or + ``gridline`` for gridline registration. Default is ``None``, where + a pixel-registered grid is returned unless only the + gridline-registered grid is available. + + Returns + ------- + grid : :class:`xarray.DataArray` + The Earth relief grid. Coordinates are latitude and longitude in + degrees. Relief is in meters. + + Notes + ----- + The :class:`xarray.DataArray` grid doesn't support slice operation, for + Earth relief data with resolutions higher than "05m", which are stored as + smaller tiles. + + Examples + -------- + + >>> # load the default grid (pixel-registered 01d grid) + >>> grid = load_earth_relief() + >>> # load the 30m grid with "gridline" registration + >>> grid = load_earth_relief("30m", registration="gridline") + >>> # load high-resolution grid for a specific region + >>> grid = load_earth_relief( + ... "05m", region=[120, 160, 30, 60], registration="gridline" + ... ) + """ + + # earth relief data stored as single grids for low resolutions + non_tiled_resolutions = ["01d", "30m", "20m", "15m", "10m", "06m"] + # earth relief data stored as tiles for high resolutions + tiled_resolutions = ["05m", "04m", "03m", "02m", "01m", "30s", "15s", "03s", "01s"] + + if registration in ("pixel", "gridline", None): + # If None, let GMT decide on Pixel/Gridline type + reg = f"_{registration[0]}" if registration else "" + else: + raise GMTInvalidInput( + f"Invalid grid registration: '{registration}', should be either " + "'pixel', 'gridline' or None. Default is None, where a " + "pixel-registered grid is returned unless only the " + "gridline-registered grid is available." + ) + + if resolution not in non_tiled_resolutions + tiled_resolutions: + raise GMTInvalidInput(f"Invalid Earth relief resolution '{resolution}'.") + + # Check combination of resolution and registeration. + if (resolution == "15s" and registration == "gridline") or ( + resolution in ("03s", "01s") and registration == "pixel" + ): + raise GMTInvalidInput( + f"{registration}-registered Earth relief data for " + f"resolution '{resolution}' is not supported." + ) + + # different ways to load tiled and non-tiled earth relief data + # Known issue: tiled grids don't support slice operation + # See https://github.com/GenericMappingTools/pygmt/issues/524 + if region is None: + if resolution in non_tiled_resolutions: + fname = which(f"@earth_relief_{resolution}{reg}", download="a") + with xr.open_dataarray(fname) as dataarray: + grid = dataarray.load() + _ = grid.gmt # load GMTDataArray accessor information + else: + raise GMTInvalidInput( + f"'region' is required for Earth relief resolution '{resolution}'." + ) + else: + grid = grdcut(f"@earth_relief_{resolution}{reg}", region=region) + + # Add some metadata to the grid + grid.name = "elevation" + grid.attrs["long_name"] = "elevation relative to the geoid" + grid.attrs["units"] = "meters" + grid.attrs["vertical_datum"] = "EMG96" + grid.attrs["horizontal_datum"] = "WGS84" + # Remove the actual range because it gets outdated when indexing the grid, + # which causes problems when exporting it to netCDF for usage on the + # command-line. + grid.attrs.pop("actual_range") + for coord in grid.coords: + grid[coord].attrs.pop("actual_range") + return grid diff --git a/pygmt/datasets/tutorial.py b/pygmt/datasets/tutorial.py index 226f0b89ab3..b3535e4cd09 100644 --- a/pygmt/datasets/tutorial.py +++ b/pygmt/datasets/tutorial.py @@ -1,103 +1,103 @@ -""" -Functions to load sample data from the GMT tutorials. -""" -import pandas as pd -from pygmt.src import which - - -def load_japan_quakes(): - """ - Load a table of earthquakes around Japan as a pandas.Dataframe. - - Data is from the NOAA NGDC database. This is the ``@tut_quakes.ngdc`` - dataset used in the GMT tutorials. - - The data are downloaded to a cache directory (usually ``~/.gmt/cache``) the - first time you invoke this function. Afterwards, it will load the data from - the cache. So you'll need an internet connection the first time around. - - Returns - ------- - data : pandas.Dataframe - The data table. Columns are year, month, day, latitude, longitude, - depth (in km), and magnitude of the earthquakes. - """ - fname = which("@tut_quakes.ngdc", download="c") - data = pd.read_csv(fname, header=1, sep=r"\s+") - data.columns = [ - "year", - "month", - "day", - "latitude", - "longitude", - "depth_km", - "magnitude", - ] - return data - - -def load_ocean_ridge_points(): - """ - Load a table of ocean ridge points for the entire world as a - pandas.DataFrame. - - This is the ``@ridge.txt`` dataset used in the GMT tutorials. - - The data are downloaded to a cache directory (usually ``~/.gmt/cache``) the - first time you invoke this function. Afterwards, it will load the data from - the cache. So you'll need an internet connection the first time around. - - Returns - ------- - data : pandas.Dataframe - The data table. Columns are longitude and latitude. - """ - fname = which("@ridge.txt", download="c") - data = pd.read_csv( - fname, sep=r"\s+", names=["longitude", "latitude"], skiprows=1, comment=">" - ) - return data - - -def load_sample_bathymetry(): - """ - Load a table of ship observations of bathymetry off Baja California as a - pandas.DataFrame. - - This is the ``@tut_ship.xyz`` dataset used in the GMT tutorials. - - The data are downloaded to a cache directory (usually ``~/.gmt/cache``) the - first time you invoke this function. Afterwards, it will load the data from - the cache. So you'll need an internet connection the first time around. - - Returns - ------- - data : pandas.Dataframe - The data table. Columns are longitude, latitude, and bathymetry. - """ - fname = which("@tut_ship.xyz", download="c") - data = pd.read_csv( - fname, sep="\t", header=None, names=["longitude", "latitude", "bathymetry"] - ) - return data - - -def load_usgs_quakes(): - """ - Load a table of global earthquakes form the USGS as a pandas.Dataframe. - - This is the ``@usgs_quakes_22.txt`` dataset used in the GMT tutorials. - - The data are downloaded to a cache directory (usually ``~/.gmt/cache``) the - first time you invoke this function. Afterwards, it will load the data from - the cache. So you'll need an internet connection the first time around. - - Returns - ------- - data : pandas.Dataframe - The data table. Use ``print(data.describe())`` to see the available - columns. - """ - fname = which("@usgs_quakes_22.txt", download="c") - data = pd.read_csv(fname) - return data +""" +Functions to load sample data from the GMT tutorials. +""" +import pandas as pd +from pygmt.src import which + + +def load_japan_quakes(): + """ + Load a table of earthquakes around Japan as a pandas.Dataframe. + + Data is from the NOAA NGDC database. This is the ``@tut_quakes.ngdc`` + dataset used in the GMT tutorials. + + The data are downloaded to a cache directory (usually ``~/.gmt/cache``) the + first time you invoke this function. Afterwards, it will load the data from + the cache. So you'll need an internet connection the first time around. + + Returns + ------- + data : pandas.Dataframe + The data table. Columns are year, month, day, latitude, longitude, + depth (in km), and magnitude of the earthquakes. + """ + fname = which("@tut_quakes.ngdc", download="c") + data = pd.read_csv(fname, header=1, sep=r"\s+") + data.columns = [ + "year", + "month", + "day", + "latitude", + "longitude", + "depth_km", + "magnitude", + ] + return data + + +def load_ocean_ridge_points(): + """ + Load a table of ocean ridge points for the entire world as a + pandas.DataFrame. + + This is the ``@ridge.txt`` dataset used in the GMT tutorials. + + The data are downloaded to a cache directory (usually ``~/.gmt/cache``) the + first time you invoke this function. Afterwards, it will load the data from + the cache. So you'll need an internet connection the first time around. + + Returns + ------- + data : pandas.Dataframe + The data table. Columns are longitude and latitude. + """ + fname = which("@ridge.txt", download="c") + data = pd.read_csv( + fname, sep=r"\s+", names=["longitude", "latitude"], skiprows=1, comment=">" + ) + return data + + +def load_sample_bathymetry(): + """ + Load a table of ship observations of bathymetry off Baja California as a + pandas.DataFrame. + + This is the ``@tut_ship.xyz`` dataset used in the GMT tutorials. + + The data are downloaded to a cache directory (usually ``~/.gmt/cache``) the + first time you invoke this function. Afterwards, it will load the data from + the cache. So you'll need an internet connection the first time around. + + Returns + ------- + data : pandas.Dataframe + The data table. Columns are longitude, latitude, and bathymetry. + """ + fname = which("@tut_ship.xyz", download="c") + data = pd.read_csv( + fname, sep="\t", header=None, names=["longitude", "latitude", "bathymetry"] + ) + return data + + +def load_usgs_quakes(): + """ + Load a table of global earthquakes form the USGS as a pandas.Dataframe. + + This is the ``@usgs_quakes_22.txt`` dataset used in the GMT tutorials. + + The data are downloaded to a cache directory (usually ``~/.gmt/cache``) the + first time you invoke this function. Afterwards, it will load the data from + the cache. So you'll need an internet connection the first time around. + + Returns + ------- + data : pandas.Dataframe + The data table. Use ``print(data.describe())`` to see the available + columns. + """ + fname = which("@usgs_quakes_22.txt", download="c") + data = pd.read_csv(fname) + return data diff --git a/pygmt/figure.py b/pygmt/figure.py index acd448b9472..855e34a9235 100644 --- a/pygmt/figure.py +++ b/pygmt/figure.py @@ -1,396 +1,396 @@ -""" -Define the Figure class that handles all plotting. -""" -import base64 -import os -from tempfile import TemporaryDirectory - -try: - from IPython.display import Image -except ImportError: - Image = None - -from pygmt.clib import Session -from pygmt.exceptions import GMTError, GMTInvalidInput -from pygmt.helpers import ( - build_arg_string, - fmt_docstring, - kwargs_to_strings, - launch_external_viewer, - unique_name, - use_alias, -) - -# A registry of all figures that have had "show" called in this session. -# This is needed for the sphinx-gallery scraper in pygmt/sphinx_gallery.py -SHOWED_FIGURES = [] - - -class Figure: - """ - A GMT figure to handle all plotting. - - Use the plotting methods of this class to add elements to the figure. You - can preview the figure using :meth:`pygmt.Figure.show` and save the figure - to a file using :meth:`pygmt.Figure.savefig`. - - Unlike traditional GMT figures, no figure file is generated until you call - :meth:`pygmt.Figure.savefig` or :meth:`pygmt.Figure.psconvert`. - - Examples - -------- - - >>> fig = Figure() - >>> fig.basemap(region=[0, 360, -90, 90], projection="W7i", frame=True) - >>> fig.savefig("my-figure.png") - >>> # Make sure the figure file is generated and clean it up - >>> import os - >>> os.path.exists("my-figure.png") - True - >>> os.remove("my-figure.png") - - The plot region can be specified through ISO country codes (for example, - ``'JP'`` for Japan): - - >>> fig = Figure() - >>> fig.basemap(region="JP", projection="M3i", frame=True) - >>> # The fig.region attribute shows the WESN bounding box for the figure - >>> print(", ".join("{:.2f}".format(i) for i in fig.region)) - 122.94, 145.82, 20.53, 45.52 - """ - - def __init__(self): - self._name = unique_name() - self._preview_dir = TemporaryDirectory(prefix=self._name + "-preview-") - self._activate_figure() - - def __del__(self): - # Clean up the temporary directory that stores the previews - if hasattr(self, "_preview_dir"): - self._preview_dir.cleanup() - - def _activate_figure(self): - """ - Start and/or activate the current figure. - - All plotting commands run afterward will append to this figure. - - Unlike the command-line version (``gmt figure``), this method does not - trigger the generation of a figure file. An explicit call to - :meth:`pygmt.Figure.savefig` or :meth:`pygmt.Figure.psconvert` must be - made in order to get a file. - """ - # Passing format '-' tells pygmt.end to not produce any files. - fmt = "-" - with Session() as lib: - lib.call_module("figure", "{} {}".format(self._name, fmt)) - - def _preprocess(self, **kwargs): - """ - Call the ``figure`` module before each plotting command to ensure we're - plotting to this particular figure. - """ - self._activate_figure() - return kwargs - - @property - def region(self): - """ - The geographic WESN bounding box for the current figure. - """ - self._activate_figure() - with Session() as lib: - wesn = lib.extract_region() - return wesn - - @fmt_docstring - @use_alias( - A="crop", - C="gs_option", - E="dpi", - F="prefix", - I="icc_gray", - T="fmt", - Q="anti_aliasing", - ) - @kwargs_to_strings() - def psconvert(self, **kwargs): - r""" - Convert [E]PS file(s) to other formats. - - Converts one or more PostScript files to other formats (BMP, EPS, JPEG, - PDF, PNG, PPM, SVG, TIFF) using GhostScript. - - If no input files are given, will convert the current active figure - (see :func:`pygmt.figure`). In this case, an output name must be given - using parameter *prefix*. - - Full option list at :gmt-docs:`psconvert.html` - - {aliases} - - Parameters - ---------- - crop : str or bool - Adjust the BoundingBox and HiResBoundingBox to the minimum required - by the image content. Append ``u`` to first remove any GMT-produced - time-stamps. Default is True. - gs_option : str - Specify a single, custom option that will be passed on to - GhostScript as is. - dpi : int - Set raster resolution in dpi. Default = 720 for PDF, 300 for - others. - prefix : str - Force the output file name. By default output names are constructed - using the input names as base, which are appended with an - appropriate extension. Use this option to provide a different name, - but without extension. Extension is still determined automatically. - icc_gray : bool - Enforce gray-shades by using ICC profiles. - anti_aliasing : str - [**g**\|\ **p**\|\ **t**\][**1**\|\ **2**\|\ **4**]. - Set the anti-aliasing options for **g**\ raphics or **t**\ ext. - Append the size of the subsample box (1, 2, or 4) [4]. [Default is - no anti-aliasing (same as bits = 1)]. - fmt : str - Sets the output format, where **b** means BMP, **e** means EPS, - **E** means EPS with PageSize command, **f** means PDF, **F** means - multi-page PDF, **j** means JPEG, **g** means PNG, **G** means - transparent PNG (untouched regions are transparent), **m** means - PPM, **s** means SVG, and **t** means TIFF [default is JPEG]. To - **b**\|\ **j**\|\ **g**\|\ **t**\ , optionally append **+m** in - order to get a monochrome (grayscale) image. The EPS format can be - combined with any of the other formats. For example, **ef** creates - both an EPS and a PDF file. Using **F** creates a multi-page PDF - file from the list of input PS or PDF files. It requires the - ``prefix`` parameter. - """ - kwargs = self._preprocess(**kwargs) - # Default cropping the figure to True - if "A" not in kwargs: - kwargs["A"] = "" - with Session() as lib: - lib.call_module("psconvert", build_arg_string(kwargs)) - - def savefig( - self, fname, transparent=False, crop=True, anti_alias=True, show=False, **kwargs - ): - """ - Save the figure to a file. - - This method implements a matplotlib-like interface for - :meth:`pygmt.Figure.psconvert`. - - Supported formats: PNG (``.png``), JPEG (``.jpg``), PDF (``.pdf``), - BMP (``.bmp``), TIFF (``.tif``), EPS (``.eps``), and KML (``.kml``). - The KML output generates a companion PNG file. - - You can pass in any keyword arguments that - :meth:`pygmt.Figure.psconvert` accepts. - - Parameters - ---------- - fname : str - The desired figure file name, including the extension. See the list - of supported formats and their extensions above. - transparent : bool - If True, will use a transparent background for the figure. Only - valid for PNG format. - crop : bool - If True, will crop the figure canvas (page) to the plot area. - anti_alias: bool - If True, will use anti aliasing when creating raster images (PNG, - JPG, TIFF). More specifically, it passes arguments ``t2`` - and ``g2`` to the ``anti_aliasing`` parameter of - :meth:`pygmt.Figure.psconvert`. Ignored if creating vector - graphics. - show: bool - If True, will open the figure in an external viewer. - dpi : int - Set raster resolution in dpi. Default is 720 for PDF, 300 for - others. - """ - # All supported formats - fmts = dict(png="g", pdf="f", jpg="j", bmp="b", eps="e", tif="t", kml="g") - - prefix, ext = os.path.splitext(fname) - ext = ext[1:] # Remove the . - if ext not in fmts: - raise GMTInvalidInput("Unknown extension '.{}'".format(ext)) - fmt = fmts[ext] - if transparent: - if fmt != "g": - raise GMTInvalidInput( - "Transparency unavailable for '{}', only for png.".format(ext) - ) - fmt = fmt.upper() - if anti_alias: - kwargs["Qt"] = 2 - kwargs["Qg"] = 2 - if ext == "kml": - kwargs["W"] = "+k" - - self.psconvert(prefix=prefix, fmt=fmt, crop=crop, **kwargs) - if show: - launch_external_viewer(fname) - - def show(self, dpi=300, width=500, method="static"): - """ - Display a preview of the figure. - - Inserts the preview in the Jupyter notebook output. You will need to - have IPython installed for this to work. You should have it if you are - using the notebook. - - If ``method='external'``, makes PDF preview instead and opens it in the - default viewer for your operating system (falls back to the default web - browser). Note that the external viewer does not block the current - process, so this won't work in a script. - - Parameters - ---------- - dpi : int - The image resolution (dots per inch). - width : int - Width of the figure shown in the notebook in pixels. Ignored if - ``method='external'``. - method : str - How the figure will be displayed. Options are (1) ``'static'``: PNG - preview (default); (2) ``'external'``: PDF preview in an external - program. - - Returns - ------- - img : IPython.display.Image - Only if ``method != 'external'``. - """ - # Module level variable to know which figures had their show method - # called. Needed for the sphinx-gallery scraper. - SHOWED_FIGURES.append(self) - - if method not in ["static", "external"]: - raise GMTInvalidInput("Invalid show method '{}'.".format(method)) - if method == "external": - pdf = self._preview(fmt="pdf", dpi=dpi, anti_alias=False, as_bytes=False) - launch_external_viewer(pdf) - img = None - elif method == "static": - png = self._preview( - fmt="png", dpi=dpi, anti_alias=True, as_bytes=True, transparent=True - ) - if Image is None: - raise GMTError( - " ".join( - [ - "Cannot find IPython.", - "Make sure you have it installed", - "or use 'method=\"external\"' to open in an external viewer.", - ] - ) - ) - img = Image(data=png, width=width) - return img - - def shift_origin(self, xshift=None, yshift=None): - """ - Shift plot origin in x and/or y directions. - - This method shifts plot origin relative to the current origin by - (*xshift*, *yshift*) and optionally append the length unit (**c**, - **i**, or **p**). - - Prepend **a** to shift the origin back to the original position after - plotting, prepend **c** to center the plot on the center of the paper - (optionally add shift), prepend **f** to shift the origin relative to - the fixed lower left corner of the page, or prepend **r** [Default] to - move the origin relative to its current location. - - Detailed usage at - :gmt-docs:`cookbook/options.html#plot-positioning-and-layout-the-x-y-options` - - Parameters - ---------- - xshift : str - Shift plot origin in x direction. - yshift : str - Shift plot origin in y direction. - """ - self._preprocess() - args = ["-T"] - if xshift: - args.append("-X{}".format(xshift)) - if yshift: - args.append("-Y{}".format(yshift)) - - with Session() as lib: - lib.call_module("plot", " ".join(args)) - - def _preview(self, fmt, dpi, as_bytes=False, **kwargs): - """ - Grab a preview of the figure. - - Parameters - ---------- - fmt : str - The image format. Can be any extension that - :meth:`pygmt.Figure.savefig` recognizes. - dpi : int - The image resolution (dots per inch). - as_bytes : bool - If ``True``, will load the image as a bytes string and return that - instead of the file name. - - Returns - ------- - preview : str or bytes - If ``as_bytes=False``, this is the file name of the preview image - file. Else, it is the file content loaded as a bytes string. - """ - fname = os.path.join(self._preview_dir.name, "{}.{}".format(self._name, fmt)) - self.savefig(fname, dpi=dpi, **kwargs) - if as_bytes: - with open(fname, "rb") as image: - preview = image.read() - return preview - return fname - - def _repr_png_(self): - """ - Show a PNG preview if the object is returned in an interactive shell. - - For the Jupyter notebook or IPython Qt console. - """ - png = self._preview(fmt="png", dpi=70, anti_alias=True, as_bytes=True) - return png - - def _repr_html_(self): - """ - Show the PNG image embedded in HTML with a controlled width. - - Looks better than the raw PNG. - """ - raw_png = self._preview(fmt="png", dpi=300, anti_alias=True, as_bytes=True) - base64_png = base64.encodebytes(raw_png) - html = '' - return html.format(image=base64_png.decode("utf-8"), width=500) - - from pygmt.src import ( # pylint: disable=import-outside-toplevel - basemap, - coast, - colorbar, - contour, - grdcontour, - grdimage, - grdview, - image, - inset, - legend, - logo, - meca, - plot, - plot3d, - set_panel, - subplot, - text, - ) +""" +Define the Figure class that handles all plotting. +""" +import base64 +import os +from tempfile import TemporaryDirectory + +try: + from IPython.display import Image +except ImportError: + Image = None + +from pygmt.clib import Session +from pygmt.exceptions import GMTError, GMTInvalidInput +from pygmt.helpers import ( + build_arg_string, + fmt_docstring, + kwargs_to_strings, + launch_external_viewer, + unique_name, + use_alias, +) + +# A registry of all figures that have had "show" called in this session. +# This is needed for the sphinx-gallery scraper in pygmt/sphinx_gallery.py +SHOWED_FIGURES = [] + + +class Figure: + """ + A GMT figure to handle all plotting. + + Use the plotting methods of this class to add elements to the figure. You + can preview the figure using :meth:`pygmt.Figure.show` and save the figure + to a file using :meth:`pygmt.Figure.savefig`. + + Unlike traditional GMT figures, no figure file is generated until you call + :meth:`pygmt.Figure.savefig` or :meth:`pygmt.Figure.psconvert`. + + Examples + -------- + + >>> fig = Figure() + >>> fig.basemap(region=[0, 360, -90, 90], projection="W7i", frame=True) + >>> fig.savefig("my-figure.png") + >>> # Make sure the figure file is generated and clean it up + >>> import os + >>> os.path.exists("my-figure.png") + True + >>> os.remove("my-figure.png") + + The plot region can be specified through ISO country codes (for example, + ``'JP'`` for Japan): + + >>> fig = Figure() + >>> fig.basemap(region="JP", projection="M3i", frame=True) + >>> # The fig.region attribute shows the WESN bounding box for the figure + >>> print(", ".join("{:.2f}".format(i) for i in fig.region)) + 122.94, 145.82, 20.53, 45.52 + """ + + def __init__(self): + self._name = unique_name() + self._preview_dir = TemporaryDirectory(prefix=self._name + "-preview-") + self._activate_figure() + + def __del__(self): + # Clean up the temporary directory that stores the previews + if hasattr(self, "_preview_dir"): + self._preview_dir.cleanup() + + def _activate_figure(self): + """ + Start and/or activate the current figure. + + All plotting commands run afterward will append to this figure. + + Unlike the command-line version (``gmt figure``), this method does not + trigger the generation of a figure file. An explicit call to + :meth:`pygmt.Figure.savefig` or :meth:`pygmt.Figure.psconvert` must be + made in order to get a file. + """ + # Passing format '-' tells pygmt.end to not produce any files. + fmt = "-" + with Session() as lib: + lib.call_module("figure", "{} {}".format(self._name, fmt)) + + def _preprocess(self, **kwargs): + """ + Call the ``figure`` module before each plotting command to ensure we're + plotting to this particular figure. + """ + self._activate_figure() + return kwargs + + @property + def region(self): + """ + The geographic WESN bounding box for the current figure. + """ + self._activate_figure() + with Session() as lib: + wesn = lib.extract_region() + return wesn + + @fmt_docstring + @use_alias( + A="crop", + C="gs_option", + E="dpi", + F="prefix", + I="icc_gray", + T="fmt", + Q="anti_aliasing", + ) + @kwargs_to_strings() + def psconvert(self, **kwargs): + r""" + Convert [E]PS file(s) to other formats. + + Converts one or more PostScript files to other formats (BMP, EPS, JPEG, + PDF, PNG, PPM, SVG, TIFF) using GhostScript. + + If no input files are given, will convert the current active figure + (see :func:`pygmt.figure`). In this case, an output name must be given + using parameter *prefix*. + + Full option list at :gmt-docs:`psconvert.html` + + {aliases} + + Parameters + ---------- + crop : str or bool + Adjust the BoundingBox and HiResBoundingBox to the minimum required + by the image content. Append ``u`` to first remove any GMT-produced + time-stamps. Default is True. + gs_option : str + Specify a single, custom option that will be passed on to + GhostScript as is. + dpi : int + Set raster resolution in dpi. Default = 720 for PDF, 300 for + others. + prefix : str + Force the output file name. By default output names are constructed + using the input names as base, which are appended with an + appropriate extension. Use this option to provide a different name, + but without extension. Extension is still determined automatically. + icc_gray : bool + Enforce gray-shades by using ICC profiles. + anti_aliasing : str + [**g**\|\ **p**\|\ **t**\][**1**\|\ **2**\|\ **4**]. + Set the anti-aliasing options for **g**\ raphics or **t**\ ext. + Append the size of the subsample box (1, 2, or 4) [4]. [Default is + no anti-aliasing (same as bits = 1)]. + fmt : str + Sets the output format, where **b** means BMP, **e** means EPS, + **E** means EPS with PageSize command, **f** means PDF, **F** means + multi-page PDF, **j** means JPEG, **g** means PNG, **G** means + transparent PNG (untouched regions are transparent), **m** means + PPM, **s** means SVG, and **t** means TIFF [default is JPEG]. To + **b**\|\ **j**\|\ **g**\|\ **t**\ , optionally append **+m** in + order to get a monochrome (grayscale) image. The EPS format can be + combined with any of the other formats. For example, **ef** creates + both an EPS and a PDF file. Using **F** creates a multi-page PDF + file from the list of input PS or PDF files. It requires the + ``prefix`` parameter. + """ + kwargs = self._preprocess(**kwargs) + # Default cropping the figure to True + if "A" not in kwargs: + kwargs["A"] = "" + with Session() as lib: + lib.call_module("psconvert", build_arg_string(kwargs)) + + def savefig( + self, fname, transparent=False, crop=True, anti_alias=True, show=False, **kwargs + ): + """ + Save the figure to a file. + + This method implements a matplotlib-like interface for + :meth:`pygmt.Figure.psconvert`. + + Supported formats: PNG (``.png``), JPEG (``.jpg``), PDF (``.pdf``), + BMP (``.bmp``), TIFF (``.tif``), EPS (``.eps``), and KML (``.kml``). + The KML output generates a companion PNG file. + + You can pass in any keyword arguments that + :meth:`pygmt.Figure.psconvert` accepts. + + Parameters + ---------- + fname : str + The desired figure file name, including the extension. See the list + of supported formats and their extensions above. + transparent : bool + If True, will use a transparent background for the figure. Only + valid for PNG format. + crop : bool + If True, will crop the figure canvas (page) to the plot area. + anti_alias: bool + If True, will use anti aliasing when creating raster images (PNG, + JPG, TIFF). More specifically, it passes arguments ``t2`` + and ``g2`` to the ``anti_aliasing`` parameter of + :meth:`pygmt.Figure.psconvert`. Ignored if creating vector + graphics. + show: bool + If True, will open the figure in an external viewer. + dpi : int + Set raster resolution in dpi. Default is 720 for PDF, 300 for + others. + """ + # All supported formats + fmts = dict(png="g", pdf="f", jpg="j", bmp="b", eps="e", tif="t", kml="g") + + prefix, ext = os.path.splitext(fname) + ext = ext[1:] # Remove the . + if ext not in fmts: + raise GMTInvalidInput("Unknown extension '.{}'".format(ext)) + fmt = fmts[ext] + if transparent: + if fmt != "g": + raise GMTInvalidInput( + "Transparency unavailable for '{}', only for png.".format(ext) + ) + fmt = fmt.upper() + if anti_alias: + kwargs["Qt"] = 2 + kwargs["Qg"] = 2 + if ext == "kml": + kwargs["W"] = "+k" + + self.psconvert(prefix=prefix, fmt=fmt, crop=crop, **kwargs) + if show: + launch_external_viewer(fname) + + def show(self, dpi=300, width=500, method="static"): + """ + Display a preview of the figure. + + Inserts the preview in the Jupyter notebook output. You will need to + have IPython installed for this to work. You should have it if you are + using the notebook. + + If ``method='external'``, makes PDF preview instead and opens it in the + default viewer for your operating system (falls back to the default web + browser). Note that the external viewer does not block the current + process, so this won't work in a script. + + Parameters + ---------- + dpi : int + The image resolution (dots per inch). + width : int + Width of the figure shown in the notebook in pixels. Ignored if + ``method='external'``. + method : str + How the figure will be displayed. Options are (1) ``'static'``: PNG + preview (default); (2) ``'external'``: PDF preview in an external + program. + + Returns + ------- + img : IPython.display.Image + Only if ``method != 'external'``. + """ + # Module level variable to know which figures had their show method + # called. Needed for the sphinx-gallery scraper. + SHOWED_FIGURES.append(self) + + if method not in ["static", "external"]: + raise GMTInvalidInput("Invalid show method '{}'.".format(method)) + if method == "external": + pdf = self._preview(fmt="pdf", dpi=dpi, anti_alias=False, as_bytes=False) + launch_external_viewer(pdf) + img = None + elif method == "static": + png = self._preview( + fmt="png", dpi=dpi, anti_alias=True, as_bytes=True, transparent=True + ) + if Image is None: + raise GMTError( + " ".join( + [ + "Cannot find IPython.", + "Make sure you have it installed", + "or use 'method=\"external\"' to open in an external viewer.", + ] + ) + ) + img = Image(data=png, width=width) + return img + + def shift_origin(self, xshift=None, yshift=None): + """ + Shift plot origin in x and/or y directions. + + This method shifts plot origin relative to the current origin by + (*xshift*, *yshift*) and optionally append the length unit (**c**, + **i**, or **p**). + + Prepend **a** to shift the origin back to the original position after + plotting, prepend **c** to center the plot on the center of the paper + (optionally add shift), prepend **f** to shift the origin relative to + the fixed lower left corner of the page, or prepend **r** [Default] to + move the origin relative to its current location. + + Detailed usage at + :gmt-docs:`cookbook/options.html#plot-positioning-and-layout-the-x-y-options` + + Parameters + ---------- + xshift : str + Shift plot origin in x direction. + yshift : str + Shift plot origin in y direction. + """ + self._preprocess() + args = ["-T"] + if xshift: + args.append("-X{}".format(xshift)) + if yshift: + args.append("-Y{}".format(yshift)) + + with Session() as lib: + lib.call_module("plot", " ".join(args)) + + def _preview(self, fmt, dpi, as_bytes=False, **kwargs): + """ + Grab a preview of the figure. + + Parameters + ---------- + fmt : str + The image format. Can be any extension that + :meth:`pygmt.Figure.savefig` recognizes. + dpi : int + The image resolution (dots per inch). + as_bytes : bool + If ``True``, will load the image as a bytes string and return that + instead of the file name. + + Returns + ------- + preview : str or bytes + If ``as_bytes=False``, this is the file name of the preview image + file. Else, it is the file content loaded as a bytes string. + """ + fname = os.path.join(self._preview_dir.name, "{}.{}".format(self._name, fmt)) + self.savefig(fname, dpi=dpi, **kwargs) + if as_bytes: + with open(fname, "rb") as image: + preview = image.read() + return preview + return fname + + def _repr_png_(self): + """ + Show a PNG preview if the object is returned in an interactive shell. + + For the Jupyter notebook or IPython Qt console. + """ + png = self._preview(fmt="png", dpi=70, anti_alias=True, as_bytes=True) + return png + + def _repr_html_(self): + """ + Show the PNG image embedded in HTML with a controlled width. + + Looks better than the raw PNG. + """ + raw_png = self._preview(fmt="png", dpi=300, anti_alias=True, as_bytes=True) + base64_png = base64.encodebytes(raw_png) + html = '' + return html.format(image=base64_png.decode("utf-8"), width=500) + + from pygmt.src import ( # pylint: disable=import-outside-toplevel + basemap, + coast, + colorbar, + contour, + grdcontour, + grdimage, + grdview, + image, + inset, + legend, + logo, + meca, + plot, + plot3d, + set_panel, + subplot, + text, + ) diff --git a/pygmt/helpers/__init__.py b/pygmt/helpers/__init__.py index 84aaad0fb9a..6cca7eaac70 100644 --- a/pygmt/helpers/__init__.py +++ b/pygmt/helpers/__init__.py @@ -1,13 +1,13 @@ -""" -Functions, classes, decorators, and context managers to help wrap GMT modules. -""" -from pygmt.helpers.decorators import fmt_docstring, kwargs_to_strings, use_alias -from pygmt.helpers.tempfile import GMTTempFile, unique_name -from pygmt.helpers.utils import ( - args_in_kwargs, - build_arg_string, - data_kind, - dummy_context, - is_nonstr_iter, - launch_external_viewer, -) +""" +Functions, classes, decorators, and context managers to help wrap GMT modules. +""" +from pygmt.helpers.decorators import fmt_docstring, kwargs_to_strings, use_alias +from pygmt.helpers.tempfile import GMTTempFile, unique_name +from pygmt.helpers.utils import ( + args_in_kwargs, + build_arg_string, + data_kind, + dummy_context, + is_nonstr_iter, + launch_external_viewer, +) diff --git a/pygmt/helpers/decorators.py b/pygmt/helpers/decorators.py index af56d5ab98f..82d22c5366b 100644 --- a/pygmt/helpers/decorators.py +++ b/pygmt/helpers/decorators.py @@ -1,463 +1,463 @@ -""" -Decorators to help wrap the GMT modules. - -Apply them to functions wrapping GMT modules to automate: alias generation for -arguments, insert common text into docstrings, transform arguments to strings, -etc. -""" -import functools -import textwrap - -import numpy as np -from pygmt.exceptions import GMTInvalidInput -from pygmt.helpers.utils import is_nonstr_iter - -COMMON_OPTIONS = { - "R": r""" - region : str or list - *Required if this is the first plot command*. - *xmin/xmax/ymin/ymax*\ [**+r**][**+u**\ *unit*]. - Specify the region of interest.""", - "J": r""" - projection : str - *Required if this is the first plot command*. - *projcode*\[*projparams*/]\ *width*. - Select map projection.""", - "B": r""" - frame : bool or str or list - Set map boundary frame and axes attributes.""", - "U": """\ - timestamp : bool or str - Draw GMT time stamp logo on plot.""", - "CPT": r""" - 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.""", - "G": """\ - color : str - Select color or pattern for filling of symbols or polygons. Default - is no fill.""", - "V": """\ - verbose : bool or str - 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 algorthms); - - **i** - Informational messages (same as ``verbose=True``) - - **c** - Compatibility warnings - - **d** - Debugging messages""", - "W": """\ - pen : str - Set pen attributes for lines or the outline of symbols.""", - "XY": r""" - xshift : str - [**a**\|\ **c**\|\ **f**\|\ **r**\][*xshift*]. - Shift plot origin in x-direction. - yshift : str - [**a**\|\ **c**\|\ **f**\|\ **r**\][*yshift*]. - Shift plot origin in y-direction. Full documentation is at - :gmt-docs:`gmt.html#xy-full`. - """, - "c": r""" - panel : bool or int or list - [*row,col*\|\ *index*]. - Selects a specific subplot panel. Only allowed when in subplot - mode. Use ``panel=True`` to advance to the next panel in the - selected order. Instead of *row,col* you may also give a scalar - value *index* which depends on the order you set via ``autolabel`` - when the subplot was defined. **Note**: *row*, *col*, and *index* - all start at 0. - """, - "f": r""" - coltypes : str - [**i**\|\ **o**]\ *colinfo*. - Specify data types of input and/or output columns (time or - geographical data). Full documentation is at - :gmt-docs:`gmt.html#f-full`. - """, - "j": r""" - distcalc : str - **e**\|\ **f**\|\ **g**. - Determine how spherical distances are calculated. - - - **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 - (:gmt-term:`PROJ_MEAN_RADIUS`), and the specification of latitude type - (:gmt-term:`PROJ_AUX_LATITUDE`). Geodesic distance calculations is also - controlled by method (:gmt-term:`PROJ_GEODESIC`).""", - "n": r""" - interpolation : str - [**b**\|\ **c**\|\ **l**\|\ **n**][**+a**][**+b**\ *BC*][**+c**][**+t**\ *threshold*]. - Select interpolation mode for grids. You can select the type of - spline used: - - - **b** for B-spline - - **c** for bicubic [Default] - - **l** for bilinear - - **n** for nearest-neighbor""", - "p": r""" - perspective : list or str - [**x**\|\ **y**\|\ **z**]\ *azim*\[/*elev*\[/*zlevel*]]\ - [**+w**\ *lon0*/*lat0*\[/*z0*]][**+v**\ *x0*/*y0*]. - Select perspective view and set the azimuth and elevation angle of - the viewpoint. Default is [180, 90]. Full documentation is at - :gmt-docs:`gmt.html#perspective-full`. - """, - "registration": r""" - registration : str - **g**\|\ **p**. - Force output grid to be gridline (g) or pixel (p) node registered. - Default is gridline (g).""", - "t": """\ - transparency : int or float - Set transparency level, in [0-100] percent range. - Default is 0, i.e., opaque. - Only visible when PDF or raster format output is selected. - Only the PNG format selection adds a transparency layer - in the image (for further processing). """, - "x": r""" - cores : bool or int - [[**-**]\ *n*]. - Limit the number of cores to be used in any OpenMP-enabled - multi-threaded algorithms. By default we try to use all available - cores. Set a number *n* to only use n cores (if too large it will - be truncated to the maximum cores available). Finally, give a - negative number *-n* to select (all - *n*) cores (or at least 1 if - *n* equals or exceeds all). - """, -} - - -def fmt_docstring(module_func): - r""" - Decorator to insert common text into module docstrings. - - Should be the last decorator (at the top). - - Use any of these placeholders in your docstring to have them substituted: - - * ``{aliases}``: Insert a section listing the parameter aliases defined by - decorator ``use_alias``. - - The following are places for common parameter descriptions: - - * ``{R}``: region (bounding box as west, east, south, north) - * ``{J}``: projection (coordinate system to use) - * ``{B}``: frame (map frame and axes parameters) - * ``{U}``: timestamp (insert time stamp logo) - * ``{CPT}``: cmap (the color palette table) - * ``{G}``: color - * ``{W}``: pen - * ``{n}``: interpolation - - Parameters - ---------- - module_func : function - The module function. - - Returns - ------- - module_func - The same *module_func* but with the docstring formatted. - - Examples - -------- - - >>> @fmt_docstring - ... @use_alias(R="region", J="projection") - ... def gmtinfo(**kwargs): - ... ''' - ... My nice module. - ... - ... Parameters - ... ---------- - ... {R} - ... {J} - ... - ... {aliases} - ... ''' - ... pass - >>> print(gmtinfo.__doc__) - - My nice module. - - Parameters - ---------- - region : str or list - *Required if this is the first plot command*. - *xmin/xmax/ymin/ymax*\ [**+r**][**+u**\ *unit*]. - Specify the region of interest. - projection : str - *Required if this is the first plot command*. - *projcode*\[*projparams*/]\ *width*. - Select map projection. - - **Aliases:** - - - J = projection - - R = region - - """ - filler_text = {} - - if hasattr(module_func, "aliases"): - aliases = ["**Aliases:**\n"] - for arg in sorted(module_func.aliases): - alias = module_func.aliases[arg] - aliases.append("- {} = {}".format(arg, alias)) - filler_text["aliases"] = "\n".join(aliases) - - for marker, text in COMMON_OPTIONS.items(): - # Remove the indentation and the first line break from the multiline - # strings so that it doesn't mess up the original docstring - filler_text[marker] = textwrap.dedent(text.lstrip("\n")) - - # Dedent the docstring to make it all match the option text. - docstring = textwrap.dedent(module_func.__doc__) - - module_func.__doc__ = docstring.format(**filler_text) - - return module_func - - -def use_alias(**aliases): - """ - Decorator to add aliases to keyword arguments of a function. - - Use this decorator above the argument parsing decorators, usually only - below ``fmt_docstring``. - - Replaces the aliases with their desired names before passing them along to - the module function. - - Keywords passed to this decorator are the desired argument name and their - value is the alias. - - Adds a dictionary attribute to the function with the aliases. Use in - conjunction with ``fmt_docstring`` to insert a list of valid aliases in - your docstring. - - Examples - -------- - - >>> @use_alias(R="region", J="projection") - ... def my_module(**kwargs): - ... print("R =", kwargs["R"], "J =", kwargs["J"]) - >>> my_module(R="bla", J="meh") - R = bla J = meh - >>> my_module(region="bla", J="meh") - R = bla J = meh - >>> my_module(R="bla", projection="meh") - R = bla J = meh - >>> my_module(region="bla", projection="meh") - R = bla J = meh - >>> my_module( - ... region="bla", projection="meh", J="bla" - ... ) # doctest: +NORMALIZE_WHITESPACE - Traceback (most recent call last): - ... - pygmt.exceptions.GMTInvalidInput: - Arguments in short-form (J) and long-form (projection) can't coexist - """ - - def alias_decorator(module_func): - """ - Decorator that replaces the aliases for arguments. - """ - - @functools.wraps(module_func) - def new_module(*args, **kwargs): - """ - New module that parses and replaces the registered aliases. - """ - for arg, alias in aliases.items(): - if alias in kwargs and arg in kwargs: - raise GMTInvalidInput( - f"Arguments in short-form ({arg}) and long-form ({alias}) can't coexist" - ) - if alias in kwargs: - kwargs[arg] = kwargs.pop(alias) - return module_func(*args, **kwargs) - - new_module.aliases = aliases - - return new_module - - return alias_decorator - - -def kwargs_to_strings(convert_bools=True, **conversions): - """ - Decorator to convert given keyword arguments to strings. - - The strings are what GMT expects from command line arguments. - - Converts all boolean arguments by default. Transforms ``True`` into ``''`` - (empty string) and removes the argument from ``kwargs`` if ``False``. - - You can also specify other conversions to specific arguments. - - Conversions available: - - * 'sequence': transforms a sequence (list, tuple) into a ``'/'`` separated - string - * 'sequence_comma': transforms a sequence into a ``','`` separated string - * 'sequence_plus': transforms a sequence into a ``'+'`` separated string - * 'sequence_space': transforms a sequence into a ``' '`` separated string - - Parameters - ---------- - convert_bools : bool - If ``True``, convert all boolean arguments to strings using the rules - specified above. If ``False``, leave them as they are. - conversions : keyword arguments - Keyword arguments specifying other kinds of conversions that should be - performed. The keyword is the name of the argument and the value is the - conversion type (see list above). - - Examples - -------- - - >>> @kwargs_to_strings( - ... R="sequence", i="sequence_comma", files="sequence_space" - ... ) - ... def module(*args, **kwargs): - ... "A module that prints the arguments it received" - ... print("{", end="") - ... print( - ... ", ".join( - ... "'{}': {}".format(k, repr(kwargs[k])) - ... for k in sorted(kwargs) - ... ), - ... end="", - ... ) - ... print("}") - ... if args: - ... print("args:", " ".join("{}".format(x) for x in args)) - >>> module(R=[1, 2, 3, 4]) - {'R': '1/2/3/4'} - >>> # It's already a string, do nothing - >>> module(R="5/6/7/8") - {'R': '5/6/7/8'} - >>> module(P=True) - {'P': ''} - >>> module(P=False) - {} - >>> module(i=[1, 2]) - {'i': '1,2'} - >>> module(files=["data1.txt", "data2.txt"]) - {'files': 'data1.txt data2.txt'} - >>> # Other non-boolean arguments are passed along as they are - >>> module(123, bla=(1, 2, 3), foo=True, A=False, i=(5, 6)) - {'bla': (1, 2, 3), 'foo': '', 'i': '5,6'} - args: 123 - >>> import datetime - >>> module( - ... R=[ - ... np.datetime64("2010-01-01T16:00:00"), - ... datetime.datetime(2020, 1, 1, 12, 23, 45), - ... ] - ... ) - {'R': '2010-01-01T16:00:00/2020-01-01T12:23:45.000000'} - >>> import pandas as pd - >>> import xarray as xr - >>> module( - ... R=[ - ... xr.DataArray(data=np.datetime64("2005-01-01T08:00:00")), - ... pd.Timestamp("2015-01-01T12:00:00.123456789"), - ... ] - ... ) - {'R': '2005-01-01T08:00:00.000000000/2015-01-01T12:00:00.123456'} - """ - valid_conversions = [ - "sequence", - "sequence_comma", - "sequence_plus", - "sequence_space", - ] - - for arg, fmt in conversions.items(): - if fmt not in valid_conversions: - raise GMTInvalidInput( - "Invalid conversion type '{}' for argument '{}'.".format(fmt, arg) - ) - - separators = { - "sequence": "/", - "sequence_comma": ",", - "sequence_plus": "+", - "sequence_space": " ", - } - - # Make the actual decorator function - def converter(module_func): - """ - The decorator that creates our new function with the conversions. - """ - - @functools.wraps(module_func) - def new_module(*args, **kwargs): - """ - New module instance that converts the arguments first. - """ - if convert_bools: - kwargs = remove_bools(kwargs) - for arg, fmt in conversions.items(): - if arg in kwargs: - value = kwargs[arg] - issequence = fmt in separators - if issequence and is_nonstr_iter(value): - for index, item in enumerate(value): - try: - # check if there is a space " " when converting - # a pandas.Timestamp/xr.DataArray to a string. - # If so, use np.datetime_as_string instead. - assert " " not in str(item) - except AssertionError: - # convert datetime-like item to ISO 8601 - # string format like YYYY-MM-DDThh:mm:ss.ffffff - value[index] = np.datetime_as_string( - np.asarray(item, dtype=np.datetime64) - ) - kwargs[arg] = separators[fmt].join(f"{item}" for item in value) - # Execute the original function and return its output - return module_func(*args, **kwargs) - - return new_module - - return converter - - -def remove_bools(kwargs): - """ - Remove booleans from arguments. - - If ``True``, replace it with an empty string. If ``False``, completely - remove the entry from the argument list. - - Parameters - ---------- - kwargs : dict - Dictionary with the keyword arguments. - - Returns - ------- - new_kwargs : dict - A copy of `kwargs` with the booleans parsed. - """ - new_kwargs = {} - for arg, value in kwargs.items(): - if isinstance(value, bool): - if value: - new_kwargs[arg] = "" - else: - new_kwargs[arg] = value - return new_kwargs +""" +Decorators to help wrap the GMT modules. + +Apply them to functions wrapping GMT modules to automate: alias generation for +arguments, insert common text into docstrings, transform arguments to strings, +etc. +""" +import functools +import textwrap + +import numpy as np +from pygmt.exceptions import GMTInvalidInput +from pygmt.helpers.utils import is_nonstr_iter + +COMMON_OPTIONS = { + "R": r""" + region : str or list + *Required if this is the first plot command*. + *xmin/xmax/ymin/ymax*\ [**+r**][**+u**\ *unit*]. + Specify the region of interest.""", + "J": r""" + projection : str + *Required if this is the first plot command*. + *projcode*\[*projparams*/]\ *width*. + Select map projection.""", + "B": r""" + frame : bool or str or list + Set map boundary frame and axes attributes.""", + "U": """\ + timestamp : bool or str + Draw GMT time stamp logo on plot.""", + "CPT": r""" + 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.""", + "G": """\ + color : str + Select color or pattern for filling of symbols or polygons. Default + is no fill.""", + "V": """\ + verbose : bool or str + 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 algorthms); + - **i** - Informational messages (same as ``verbose=True``) + - **c** - Compatibility warnings + - **d** - Debugging messages""", + "W": """\ + pen : str + Set pen attributes for lines or the outline of symbols.""", + "XY": r""" + xshift : str + [**a**\|\ **c**\|\ **f**\|\ **r**\][*xshift*]. + Shift plot origin in x-direction. + yshift : str + [**a**\|\ **c**\|\ **f**\|\ **r**\][*yshift*]. + Shift plot origin in y-direction. Full documentation is at + :gmt-docs:`gmt.html#xy-full`. + """, + "c": r""" + panel : bool or int or list + [*row,col*\|\ *index*]. + Selects a specific subplot panel. Only allowed when in subplot + mode. Use ``panel=True`` to advance to the next panel in the + selected order. Instead of *row,col* you may also give a scalar + value *index* which depends on the order you set via ``autolabel`` + when the subplot was defined. **Note**: *row*, *col*, and *index* + all start at 0. + """, + "f": r""" + coltypes : str + [**i**\|\ **o**]\ *colinfo*. + Specify data types of input and/or output columns (time or + geographical data). Full documentation is at + :gmt-docs:`gmt.html#f-full`. + """, + "j": r""" + distcalc : str + **e**\|\ **f**\|\ **g**. + Determine how spherical distances are calculated. + + - **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 + (:gmt-term:`PROJ_MEAN_RADIUS`), and the specification of latitude type + (:gmt-term:`PROJ_AUX_LATITUDE`). Geodesic distance calculations is also + controlled by method (:gmt-term:`PROJ_GEODESIC`).""", + "n": r""" + interpolation : str + [**b**\|\ **c**\|\ **l**\|\ **n**][**+a**][**+b**\ *BC*][**+c**][**+t**\ *threshold*]. + Select interpolation mode for grids. You can select the type of + spline used: + + - **b** for B-spline + - **c** for bicubic [Default] + - **l** for bilinear + - **n** for nearest-neighbor""", + "p": r""" + perspective : list or str + [**x**\|\ **y**\|\ **z**]\ *azim*\[/*elev*\[/*zlevel*]]\ + [**+w**\ *lon0*/*lat0*\[/*z0*]][**+v**\ *x0*/*y0*]. + Select perspective view and set the azimuth and elevation angle of + the viewpoint. Default is [180, 90]. Full documentation is at + :gmt-docs:`gmt.html#perspective-full`. + """, + "registration": r""" + registration : str + **g**\|\ **p**. + Force output grid to be gridline (g) or pixel (p) node registered. + Default is gridline (g).""", + "t": """\ + transparency : int or float + Set transparency level, in [0-100] percent range. + Default is 0, i.e., opaque. + Only visible when PDF or raster format output is selected. + Only the PNG format selection adds a transparency layer + in the image (for further processing). """, + "x": r""" + cores : bool or int + [[**-**]\ *n*]. + Limit the number of cores to be used in any OpenMP-enabled + multi-threaded algorithms. By default we try to use all available + cores. Set a number *n* to only use n cores (if too large it will + be truncated to the maximum cores available). Finally, give a + negative number *-n* to select (all - *n*) cores (or at least 1 if + *n* equals or exceeds all). + """, +} + + +def fmt_docstring(module_func): + r""" + Decorator to insert common text into module docstrings. + + Should be the last decorator (at the top). + + Use any of these placeholders in your docstring to have them substituted: + + * ``{aliases}``: Insert a section listing the parameter aliases defined by + decorator ``use_alias``. + + The following are places for common parameter descriptions: + + * ``{R}``: region (bounding box as west, east, south, north) + * ``{J}``: projection (coordinate system to use) + * ``{B}``: frame (map frame and axes parameters) + * ``{U}``: timestamp (insert time stamp logo) + * ``{CPT}``: cmap (the color palette table) + * ``{G}``: color + * ``{W}``: pen + * ``{n}``: interpolation + + Parameters + ---------- + module_func : function + The module function. + + Returns + ------- + module_func + The same *module_func* but with the docstring formatted. + + Examples + -------- + + >>> @fmt_docstring + ... @use_alias(R="region", J="projection") + ... def gmtinfo(**kwargs): + ... ''' + ... My nice module. + ... + ... Parameters + ... ---------- + ... {R} + ... {J} + ... + ... {aliases} + ... ''' + ... pass + >>> print(gmtinfo.__doc__) + + My nice module. + + Parameters + ---------- + region : str or list + *Required if this is the first plot command*. + *xmin/xmax/ymin/ymax*\ [**+r**][**+u**\ *unit*]. + Specify the region of interest. + projection : str + *Required if this is the first plot command*. + *projcode*\[*projparams*/]\ *width*. + Select map projection. + + **Aliases:** + + - J = projection + - R = region + + """ + filler_text = {} + + if hasattr(module_func, "aliases"): + aliases = ["**Aliases:**\n"] + for arg in sorted(module_func.aliases): + alias = module_func.aliases[arg] + aliases.append("- {} = {}".format(arg, alias)) + filler_text["aliases"] = "\n".join(aliases) + + for marker, text in COMMON_OPTIONS.items(): + # Remove the indentation and the first line break from the multiline + # strings so that it doesn't mess up the original docstring + filler_text[marker] = textwrap.dedent(text.lstrip("\n")) + + # Dedent the docstring to make it all match the option text. + docstring = textwrap.dedent(module_func.__doc__) + + module_func.__doc__ = docstring.format(**filler_text) + + return module_func + + +def use_alias(**aliases): + """ + Decorator to add aliases to keyword arguments of a function. + + Use this decorator above the argument parsing decorators, usually only + below ``fmt_docstring``. + + Replaces the aliases with their desired names before passing them along to + the module function. + + Keywords passed to this decorator are the desired argument name and their + value is the alias. + + Adds a dictionary attribute to the function with the aliases. Use in + conjunction with ``fmt_docstring`` to insert a list of valid aliases in + your docstring. + + Examples + -------- + + >>> @use_alias(R="region", J="projection") + ... def my_module(**kwargs): + ... print("R =", kwargs["R"], "J =", kwargs["J"]) + >>> my_module(R="bla", J="meh") + R = bla J = meh + >>> my_module(region="bla", J="meh") + R = bla J = meh + >>> my_module(R="bla", projection="meh") + R = bla J = meh + >>> my_module(region="bla", projection="meh") + R = bla J = meh + >>> my_module( + ... region="bla", projection="meh", J="bla" + ... ) # doctest: +NORMALIZE_WHITESPACE + Traceback (most recent call last): + ... + pygmt.exceptions.GMTInvalidInput: + Arguments in short-form (J) and long-form (projection) can't coexist + """ + + def alias_decorator(module_func): + """ + Decorator that replaces the aliases for arguments. + """ + + @functools.wraps(module_func) + def new_module(*args, **kwargs): + """ + New module that parses and replaces the registered aliases. + """ + for arg, alias in aliases.items(): + if alias in kwargs and arg in kwargs: + raise GMTInvalidInput( + f"Arguments in short-form ({arg}) and long-form ({alias}) can't coexist" + ) + if alias in kwargs: + kwargs[arg] = kwargs.pop(alias) + return module_func(*args, **kwargs) + + new_module.aliases = aliases + + return new_module + + return alias_decorator + + +def kwargs_to_strings(convert_bools=True, **conversions): + """ + Decorator to convert given keyword arguments to strings. + + The strings are what GMT expects from command line arguments. + + Converts all boolean arguments by default. Transforms ``True`` into ``''`` + (empty string) and removes the argument from ``kwargs`` if ``False``. + + You can also specify other conversions to specific arguments. + + Conversions available: + + * 'sequence': transforms a sequence (list, tuple) into a ``'/'`` separated + string + * 'sequence_comma': transforms a sequence into a ``','`` separated string + * 'sequence_plus': transforms a sequence into a ``'+'`` separated string + * 'sequence_space': transforms a sequence into a ``' '`` separated string + + Parameters + ---------- + convert_bools : bool + If ``True``, convert all boolean arguments to strings using the rules + specified above. If ``False``, leave them as they are. + conversions : keyword arguments + Keyword arguments specifying other kinds of conversions that should be + performed. The keyword is the name of the argument and the value is the + conversion type (see list above). + + Examples + -------- + + >>> @kwargs_to_strings( + ... R="sequence", i="sequence_comma", files="sequence_space" + ... ) + ... def module(*args, **kwargs): + ... "A module that prints the arguments it received" + ... print("{", end="") + ... print( + ... ", ".join( + ... "'{}': {}".format(k, repr(kwargs[k])) + ... for k in sorted(kwargs) + ... ), + ... end="", + ... ) + ... print("}") + ... if args: + ... print("args:", " ".join("{}".format(x) for x in args)) + >>> module(R=[1, 2, 3, 4]) + {'R': '1/2/3/4'} + >>> # It's already a string, do nothing + >>> module(R="5/6/7/8") + {'R': '5/6/7/8'} + >>> module(P=True) + {'P': ''} + >>> module(P=False) + {} + >>> module(i=[1, 2]) + {'i': '1,2'} + >>> module(files=["data1.txt", "data2.txt"]) + {'files': 'data1.txt data2.txt'} + >>> # Other non-boolean arguments are passed along as they are + >>> module(123, bla=(1, 2, 3), foo=True, A=False, i=(5, 6)) + {'bla': (1, 2, 3), 'foo': '', 'i': '5,6'} + args: 123 + >>> import datetime + >>> module( + ... R=[ + ... np.datetime64("2010-01-01T16:00:00"), + ... datetime.datetime(2020, 1, 1, 12, 23, 45), + ... ] + ... ) + {'R': '2010-01-01T16:00:00/2020-01-01T12:23:45.000000'} + >>> import pandas as pd + >>> import xarray as xr + >>> module( + ... R=[ + ... xr.DataArray(data=np.datetime64("2005-01-01T08:00:00")), + ... pd.Timestamp("2015-01-01T12:00:00.123456789"), + ... ] + ... ) + {'R': '2005-01-01T08:00:00.000000000/2015-01-01T12:00:00.123456'} + """ + valid_conversions = [ + "sequence", + "sequence_comma", + "sequence_plus", + "sequence_space", + ] + + for arg, fmt in conversions.items(): + if fmt not in valid_conversions: + raise GMTInvalidInput( + "Invalid conversion type '{}' for argument '{}'.".format(fmt, arg) + ) + + separators = { + "sequence": "/", + "sequence_comma": ",", + "sequence_plus": "+", + "sequence_space": " ", + } + + # Make the actual decorator function + def converter(module_func): + """ + The decorator that creates our new function with the conversions. + """ + + @functools.wraps(module_func) + def new_module(*args, **kwargs): + """ + New module instance that converts the arguments first. + """ + if convert_bools: + kwargs = remove_bools(kwargs) + for arg, fmt in conversions.items(): + if arg in kwargs: + value = kwargs[arg] + issequence = fmt in separators + if issequence and is_nonstr_iter(value): + for index, item in enumerate(value): + try: + # check if there is a space " " when converting + # a pandas.Timestamp/xr.DataArray to a string. + # If so, use np.datetime_as_string instead. + assert " " not in str(item) + except AssertionError: + # convert datetime-like item to ISO 8601 + # string format like YYYY-MM-DDThh:mm:ss.ffffff + value[index] = np.datetime_as_string( + np.asarray(item, dtype=np.datetime64) + ) + kwargs[arg] = separators[fmt].join(f"{item}" for item in value) + # Execute the original function and return its output + return module_func(*args, **kwargs) + + return new_module + + return converter + + +def remove_bools(kwargs): + """ + Remove booleans from arguments. + + If ``True``, replace it with an empty string. If ``False``, completely + remove the entry from the argument list. + + Parameters + ---------- + kwargs : dict + Dictionary with the keyword arguments. + + Returns + ------- + new_kwargs : dict + A copy of `kwargs` with the booleans parsed. + """ + new_kwargs = {} + for arg, value in kwargs.items(): + if isinstance(value, bool): + if value: + new_kwargs[arg] = "" + else: + new_kwargs[arg] = value + return new_kwargs diff --git a/pygmt/helpers/tempfile.py b/pygmt/helpers/tempfile.py index 6b14d769015..79ca7a0cf44 100644 --- a/pygmt/helpers/tempfile.py +++ b/pygmt/helpers/tempfile.py @@ -1,106 +1,106 @@ -""" -Utilities for dealing with temporary file management. -""" -import os -import uuid -from tempfile import NamedTemporaryFile - -import numpy as np - - -def unique_name(): - """ - Generate a unique name. - - Useful for generating unique names for figures (otherwise GMT will plot - everything on the same figure instead of creating a new one). - - Returns - ------- - name : str - A unique name generated by :func:`uuid.uuid4` - """ - return uuid.uuid4().hex - - -class GMTTempFile: - """ - Context manager for creating closed temporary files. - - This class does not return a file-like object. So, you can't do - ``for line in GMTTempFile()``, for example, or pass it to things that - need file objects. - - Parameters - ---------- - prefix : str - The temporary file name begins with the prefix. - suffix : str - The temporary file name ends with the suffix. - - Examples - -------- - >>> import numpy as np - >>> with GMTTempFile() as tmpfile: - ... # write data to temporary file - ... x = y = z = np.arange(0, 3, 1) - ... np.savetxt(tmpfile.name, (x, y, z), fmt="%.1f") - ... lines = tmpfile.read() - ... print(lines) - ... nx, ny, nz = tmpfile.loadtxt(unpack=True, dtype=float) - ... print(nx, ny, nz) - ... - 0.0 1.0 2.0 - 0.0 1.0 2.0 - 0.0 1.0 2.0 - - [0. 0. 0.] [1. 1. 1.] [2. 2. 2.] - """ - - def __init__(self, prefix="pygmt-", suffix=".txt"): - args = dict(prefix=prefix, suffix=suffix, delete=False) - with NamedTemporaryFile(**args) as tmpfile: - self.name = tmpfile.name - - def __enter__(self): - return self - - def __exit__(self, *args): - if os.path.exists(self.name): - os.remove(self.name) - - def read(self, keep_tabs=False): - """ - Read the entire contents of the file as a Unicode string. - - Parameters - ---------- - keep_tabs : bool - If False, replace the tabs that GMT uses with spaces. - - Returns - ------- - content : str - Content of the temporary file as a Unicode string. - """ - with open(self.name) as tmpfile: - content = tmpfile.read() - if not keep_tabs: - content = content.replace("\t", " ") - return content - - def loadtxt(self, **kwargs): - """ - Load data from the temporary file using numpy.loadtxt. - - Parameters - ---------- - kwargs : dict - Any keyword arguments that can be passed to numpy.loadtxt. - - Returns - ------- - ndarray - Data read from the text file. - """ - return np.loadtxt(self.name, **kwargs) +""" +Utilities for dealing with temporary file management. +""" +import os +import uuid +from tempfile import NamedTemporaryFile + +import numpy as np + + +def unique_name(): + """ + Generate a unique name. + + Useful for generating unique names for figures (otherwise GMT will plot + everything on the same figure instead of creating a new one). + + Returns + ------- + name : str + A unique name generated by :func:`uuid.uuid4` + """ + return uuid.uuid4().hex + + +class GMTTempFile: + """ + Context manager for creating closed temporary files. + + This class does not return a file-like object. So, you can't do + ``for line in GMTTempFile()``, for example, or pass it to things that + need file objects. + + Parameters + ---------- + prefix : str + The temporary file name begins with the prefix. + suffix : str + The temporary file name ends with the suffix. + + Examples + -------- + >>> import numpy as np + >>> with GMTTempFile() as tmpfile: + ... # write data to temporary file + ... x = y = z = np.arange(0, 3, 1) + ... np.savetxt(tmpfile.name, (x, y, z), fmt="%.1f") + ... lines = tmpfile.read() + ... print(lines) + ... nx, ny, nz = tmpfile.loadtxt(unpack=True, dtype=float) + ... print(nx, ny, nz) + ... + 0.0 1.0 2.0 + 0.0 1.0 2.0 + 0.0 1.0 2.0 + + [0. 0. 0.] [1. 1. 1.] [2. 2. 2.] + """ + + def __init__(self, prefix="pygmt-", suffix=".txt"): + args = dict(prefix=prefix, suffix=suffix, delete=False) + with NamedTemporaryFile(**args) as tmpfile: + self.name = tmpfile.name + + def __enter__(self): + return self + + def __exit__(self, *args): + if os.path.exists(self.name): + os.remove(self.name) + + def read(self, keep_tabs=False): + """ + Read the entire contents of the file as a Unicode string. + + Parameters + ---------- + keep_tabs : bool + If False, replace the tabs that GMT uses with spaces. + + Returns + ------- + content : str + Content of the temporary file as a Unicode string. + """ + with open(self.name) as tmpfile: + content = tmpfile.read() + if not keep_tabs: + content = content.replace("\t", " ") + return content + + def loadtxt(self, **kwargs): + """ + Load data from the temporary file using numpy.loadtxt. + + Parameters + ---------- + kwargs : dict + Any keyword arguments that can be passed to numpy.loadtxt. + + Returns + ------- + ndarray + Data read from the text file. + """ + return np.loadtxt(self.name, **kwargs) diff --git a/pygmt/helpers/utils.py b/pygmt/helpers/utils.py index 749ebe63e5b..00b0daf0bc9 100644 --- a/pygmt/helpers/utils.py +++ b/pygmt/helpers/utils.py @@ -1,248 +1,248 @@ -""" -Utilities and common tasks for wrapping the GMT modules. -""" -import os -import shutil -import subprocess -import sys -import webbrowser -from collections.abc import Iterable -from contextlib import contextmanager - -import xarray as xr -from pygmt.exceptions import GMTInvalidInput - - -def data_kind(data, x=None, y=None, z=None): - """ - Check what kind of data is provided to a module. - - Possible types: - - * a file name provided as 'data' - * an xarray.DataArray provided as 'data' - * a matrix provided as 'data' - * 1D arrays x and y (and z, optionally) - - Arguments should be ``None`` if not used. If doesn't fit any of these - categories (or fits more than one), will raise an exception. - - Parameters - ---------- - data : str, xarray.DataArray, 2d array, or None - Data file name, xarray.DataArray or numpy array. - x/y : 1d arrays or None - x and y columns as numpy arrays. - z : 1d array or None - z column as numpy array. To be used optionally when x and y - are given. - - Returns - ------- - kind : str - One of: ``'file'``, ``'grid'``, ``'matrix'``, ``'vectors'``. - - Examples - -------- - - >>> import numpy as np - >>> import xarray as xr - >>> data_kind(data=None, x=np.array([1, 2, 3]), y=np.array([4, 5, 6])) - 'vectors' - >>> data_kind(data=np.arange(10).reshape((5, 2)), x=None, y=None) - 'matrix' - >>> data_kind(data="my-data-file.txt", x=None, y=None) - 'file' - >>> data_kind(data=xr.DataArray(np.random.rand(4, 3))) - 'grid' - """ - if data is None and x is None and y is None: - raise GMTInvalidInput("No input data provided.") - if data is not None and (x is not None or y is not None or z is not None): - raise GMTInvalidInput("Too much data. Use either data or x and y.") - if data is None and (x is None or y is None): - raise GMTInvalidInput("Must provided both x and y.") - - if isinstance(data, str): - kind = "file" - elif isinstance(data, xr.DataArray): - kind = "grid" - elif data is not None: - kind = "matrix" - else: - kind = "vectors" - return kind - - -@contextmanager -def dummy_context(arg): - """ - Dummy context manager. - - Does nothing when entering or exiting a ``with`` block and yields the - argument passed to it. - - Useful when you have a choice of context managers but need one that does - nothing. - - Parameters - ---------- - arg : anything - The argument that will be returned by the context manager. - - Examples - -------- - - >>> with dummy_context("some argument") as temp: - ... print(temp) - ... - some argument - """ - yield arg - - -def build_arg_string(kwargs): - """ - Transform keyword arguments into a GMT argument string. - - Make sure all arguments have been previously converted to a string - representation using the ``kwargs_to_strings`` decorator. - - Any lists or tuples left will be interpreted as multiple entries for the - same command line argument. For example, the kwargs entry ``'B': ['xa', - 'yaf']`` will be converted to ``-Bxa -Byaf`` in the argument string. - - Parameters - ---------- - kwargs : dict - Parsed keyword arguments. - - Returns - ------- - args : str - The space-delimited argument string with '-' inserted before each - keyword. The arguments are sorted alphabetically. - - Examples - -------- - - >>> print( - ... build_arg_string( - ... dict(R="1/2/3/4", J="X4i", P="", E=200, X=None, Y=None) - ... ) - ... ) - -E200 -JX4i -P -R1/2/3/4 - >>> print( - ... build_arg_string( - ... dict( - ... R="1/2/3/4", - ... J="X4i", - ... B=["xaf", "yaf", "WSen"], - ... I=("1/1p,blue", "2/0.25p,blue"), - ... ) - ... ) - ... ) - -Bxaf -Byaf -BWSen -I1/1p,blue -I2/0.25p,blue -JX4i -R1/2/3/4 - """ - sorted_args = [] - for key in sorted(kwargs): - if is_nonstr_iter(kwargs[key]): - for value in kwargs[key]: - sorted_args.append("-{}{}".format(key, value)) - elif kwargs[key] is None: # arguments like -XNone are invalid - continue - else: - sorted_args.append("-{}{}".format(key, kwargs[key])) - - arg_str = " ".join(sorted_args) - return arg_str - - -def is_nonstr_iter(value): - """ - Check if the value is not a string but is iterable (list, tuple, array) - - Parameters - ---------- - value - What you want to check. - - Returns - ------- - is_iterable : bool - Whether it is a non-string iterable or not. - - Examples - -------- - - >>> is_nonstr_iter("abc") - False - >>> is_nonstr_iter(10) - False - >>> is_nonstr_iter([1, 2, 3]) - True - >>> is_nonstr_iter((1, 2, 3)) - True - >>> import numpy as np - >>> is_nonstr_iter(np.array([1.0, 2.0, 3.0])) - True - >>> is_nonstr_iter(np.array(["abc", "def", "ghi"])) - True - """ - return isinstance(value, Iterable) and not isinstance(value, str) - - -def launch_external_viewer(fname): - """ - Open a file in an external viewer program. - - Uses the ``xdg-open`` command on Linux, the ``open`` command on macOS, the - associated application on Windows, and the default web browser on other - systems. - - Parameters - ---------- - fname : str - The file name of the file (preferably a full path). - """ - # Redirect stdout and stderr to devnull so that the terminal isn't filled - # with noise - run_args = dict(stdout=subprocess.DEVNULL, stderr=subprocess.DEVNULL) - - # Open the file with the default viewer. - # Fall back to the browser if can't recognize the operating system. - os_name = sys.platform - if os_name.startswith(("linux", "freebsd")) and shutil.which("xdg-open"): - subprocess.run(["xdg-open", fname], check=False, **run_args) - elif os_name == "darwin": # Darwin is macOS - subprocess.run(["open", fname], check=False, **run_args) - elif os_name == "win32": - os.startfile(fname) # pylint: disable=no-member - else: - webbrowser.open_new_tab(f"file://{fname}") - - -def args_in_kwargs(args, kwargs): - """ - Take a list and a dictionary, and determine if any entries in the list are - keys in the dictionary. - - This function is used to determine if at least one of the required - arguments is passed to raise a GMTInvalidInput Error. - - Parameters - ---------- - args : list - List of required arguments, using the GMT short-form aliases. - - kwargs : dict - The dictionary of kwargs is the format returned by the _preprocess - function of the BasePlotting class. The keys are the GMT - short-form aliases of the parameters. - - Returns - -------- - bool - If one of the required arguments is in ``kwargs``. - """ - return any(arg in kwargs for arg in args) +""" +Utilities and common tasks for wrapping the GMT modules. +""" +import os +import shutil +import subprocess +import sys +import webbrowser +from collections.abc import Iterable +from contextlib import contextmanager + +import xarray as xr +from pygmt.exceptions import GMTInvalidInput + + +def data_kind(data, x=None, y=None, z=None): + """ + Check what kind of data is provided to a module. + + Possible types: + + * a file name provided as 'data' + * an xarray.DataArray provided as 'data' + * a matrix provided as 'data' + * 1D arrays x and y (and z, optionally) + + Arguments should be ``None`` if not used. If doesn't fit any of these + categories (or fits more than one), will raise an exception. + + Parameters + ---------- + data : str, xarray.DataArray, 2d array, or None + Data file name, xarray.DataArray or numpy array. + x/y : 1d arrays or None + x and y columns as numpy arrays. + z : 1d array or None + z column as numpy array. To be used optionally when x and y + are given. + + Returns + ------- + kind : str + One of: ``'file'``, ``'grid'``, ``'matrix'``, ``'vectors'``. + + Examples + -------- + + >>> import numpy as np + >>> import xarray as xr + >>> data_kind(data=None, x=np.array([1, 2, 3]), y=np.array([4, 5, 6])) + 'vectors' + >>> data_kind(data=np.arange(10).reshape((5, 2)), x=None, y=None) + 'matrix' + >>> data_kind(data="my-data-file.txt", x=None, y=None) + 'file' + >>> data_kind(data=xr.DataArray(np.random.rand(4, 3))) + 'grid' + """ + if data is None and x is None and y is None: + raise GMTInvalidInput("No input data provided.") + if data is not None and (x is not None or y is not None or z is not None): + raise GMTInvalidInput("Too much data. Use either data or x and y.") + if data is None and (x is None or y is None): + raise GMTInvalidInput("Must provided both x and y.") + + if isinstance(data, str): + kind = "file" + elif isinstance(data, xr.DataArray): + kind = "grid" + elif data is not None: + kind = "matrix" + else: + kind = "vectors" + return kind + + +@contextmanager +def dummy_context(arg): + """ + Dummy context manager. + + Does nothing when entering or exiting a ``with`` block and yields the + argument passed to it. + + Useful when you have a choice of context managers but need one that does + nothing. + + Parameters + ---------- + arg : anything + The argument that will be returned by the context manager. + + Examples + -------- + + >>> with dummy_context("some argument") as temp: + ... print(temp) + ... + some argument + """ + yield arg + + +def build_arg_string(kwargs): + """ + Transform keyword arguments into a GMT argument string. + + Make sure all arguments have been previously converted to a string + representation using the ``kwargs_to_strings`` decorator. + + Any lists or tuples left will be interpreted as multiple entries for the + same command line argument. For example, the kwargs entry ``'B': ['xa', + 'yaf']`` will be converted to ``-Bxa -Byaf`` in the argument string. + + Parameters + ---------- + kwargs : dict + Parsed keyword arguments. + + Returns + ------- + args : str + The space-delimited argument string with '-' inserted before each + keyword. The arguments are sorted alphabetically. + + Examples + -------- + + >>> print( + ... build_arg_string( + ... dict(R="1/2/3/4", J="X4i", P="", E=200, X=None, Y=None) + ... ) + ... ) + -E200 -JX4i -P -R1/2/3/4 + >>> print( + ... build_arg_string( + ... dict( + ... R="1/2/3/4", + ... J="X4i", + ... B=["xaf", "yaf", "WSen"], + ... I=("1/1p,blue", "2/0.25p,blue"), + ... ) + ... ) + ... ) + -Bxaf -Byaf -BWSen -I1/1p,blue -I2/0.25p,blue -JX4i -R1/2/3/4 + """ + sorted_args = [] + for key in sorted(kwargs): + if is_nonstr_iter(kwargs[key]): + for value in kwargs[key]: + sorted_args.append("-{}{}".format(key, value)) + elif kwargs[key] is None: # arguments like -XNone are invalid + continue + else: + sorted_args.append("-{}{}".format(key, kwargs[key])) + + arg_str = " ".join(sorted_args) + return arg_str + + +def is_nonstr_iter(value): + """ + Check if the value is not a string but is iterable (list, tuple, array) + + Parameters + ---------- + value + What you want to check. + + Returns + ------- + is_iterable : bool + Whether it is a non-string iterable or not. + + Examples + -------- + + >>> is_nonstr_iter("abc") + False + >>> is_nonstr_iter(10) + False + >>> is_nonstr_iter([1, 2, 3]) + True + >>> is_nonstr_iter((1, 2, 3)) + True + >>> import numpy as np + >>> is_nonstr_iter(np.array([1.0, 2.0, 3.0])) + True + >>> is_nonstr_iter(np.array(["abc", "def", "ghi"])) + True + """ + return isinstance(value, Iterable) and not isinstance(value, str) + + +def launch_external_viewer(fname): + """ + Open a file in an external viewer program. + + Uses the ``xdg-open`` command on Linux, the ``open`` command on macOS, the + associated application on Windows, and the default web browser on other + systems. + + Parameters + ---------- + fname : str + The file name of the file (preferably a full path). + """ + # Redirect stdout and stderr to devnull so that the terminal isn't filled + # with noise + run_args = dict(stdout=subprocess.DEVNULL, stderr=subprocess.DEVNULL) + + # Open the file with the default viewer. + # Fall back to the browser if can't recognize the operating system. + os_name = sys.platform + if os_name.startswith(("linux", "freebsd")) and shutil.which("xdg-open"): + subprocess.run(["xdg-open", fname], check=False, **run_args) + elif os_name == "darwin": # Darwin is macOS + subprocess.run(["open", fname], check=False, **run_args) + elif os_name == "win32": + os.startfile(fname) # pylint: disable=no-member + else: + webbrowser.open_new_tab(f"file://{fname}") + + +def args_in_kwargs(args, kwargs): + """ + Take a list and a dictionary, and determine if any entries in the list are + keys in the dictionary. + + This function is used to determine if at least one of the required + arguments is passed to raise a GMTInvalidInput Error. + + Parameters + ---------- + args : list + List of required arguments, using the GMT short-form aliases. + + kwargs : dict + The dictionary of kwargs is the format returned by the _preprocess + function of the BasePlotting class. The keys are the GMT + short-form aliases of the parameters. + + Returns + -------- + bool + If one of the required arguments is in ``kwargs``. + """ + return any(arg in kwargs for arg in args) diff --git a/pygmt/modules.py b/pygmt/modules.py index 2dd6370f57e..feb75cd3ba5 100644 --- a/pygmt/modules.py +++ b/pygmt/modules.py @@ -1,144 +1,144 @@ -""" -Non-plot GMT modules. -""" -import xarray as xr -from pygmt.clib import Session -from pygmt.exceptions import GMTInvalidInput -from pygmt.src.grdinfo import grdinfo - - -class config: # pylint: disable=invalid-name - """ - Set GMT defaults globally or locally. - - Change GMT defaults globally:: - - pygmt.config(PARAMETER=value) - - Change GMT defaults locally by using it as a context manager:: - - with pygmt.config(PARAMETER=value): - ... - - Full GMT defaults list at :gmt-docs:`gmt.conf.html` - """ - - def __init__(self, **kwargs): - # Save values so that we can revert to their initial values - self.old_defaults = {} - self.special_params = { - "FONT": [ - "FONT_ANNOT_PRIMARY", - "FONT_ANNOT_SECONDARY", - "FONT_HEADING", - "FONT_LABEL", - "FONT_TAG", - "FONT_TITLE", - ], - "FONT_ANNOT": ["FONT_ANNOT_PRIMARY", "FONT_ANNOT_SECONDARY"], - "FORMAT_TIME_MAP": ["FORMAT_TIME_PRIMARY_MAP", "FORMAT_TIME_SECONDARY_MAP"], - "MAP_ANNOT_OFFSET": [ - "MAP_ANNOT_OFFSET_PRIMARY", - "MAP_ANNOT_OFFSET_SECONDARY", - ], - "MAP_GRID_CROSS_SIZE": [ - "MAP_GRID_CROSS_SIZE_PRIMARY", - "MAP_GRID_CROSS_SIZE_SECONDARY", - ], - "MAP_GRID_PEN": ["MAP_GRID_PEN_PRIMARY", "MAP_GRID_PEN_SECONDARY"], - "MAP_TICK_LENGTH": ["MAP_TICK_LENGTH_PRIMARY", "MAP_TICK_LENGTH_SECONDARY"], - "MAP_TICK_PEN": ["MAP_TICK_PEN_PRIMARY", "MAP_TICK_PEN_SECONDARY"], - } - with Session() as lib: - for key in kwargs: - if key in self.special_params: - for k in self.special_params[key]: - self.old_defaults[k] = lib.get_default(k) - else: - self.old_defaults[key] = lib.get_default(key) - - # call gmt set to change GMT defaults - arg_str = " ".join( - ["{}={}".format(key, value) for key, value in kwargs.items()] - ) - with Session() as lib: - lib.call_module("set", arg_str) - - def __enter__(self): - return self - - def __exit__(self, exc_type, exc_value, traceback): - # revert to initial values - arg_str = " ".join( - ["{}={}".format(key, value) for key, value in self.old_defaults.items()] - ) - with Session() as lib: - lib.call_module("set", arg_str) - - -@xr.register_dataarray_accessor("gmt") -class GMTDataArrayAccessor: - """ - This is the GMT extension for :class:`xarray.DataArray`. - - You can access various GMT specific metadata about your grid as follows: - - >>> from pygmt.datasets import load_earth_relief - >>> # Use the global Earth relief grid with 1 degree spacing - >>> grid = load_earth_relief(resolution="01d") - - >>> # See if grid uses Gridline (0) or Pixel (1) registration - >>> grid.gmt.registration - 1 - >>> # See if grid uses Cartesian (0) or Geographic (1) coordinate system - >>> grid.gmt.gtype - 1 - """ - - def __init__(self, xarray_obj): - self._obj = xarray_obj - try: - self._source = self._obj.encoding["source"] # filepath to NetCDF source - # From the shortened summary information of `grdinfo`, - # get grid registration in column 10, and grid type in column 11 - self._registration, self._gtype = map( - int, grdinfo(self._source, per_column="n", o="10,11").split() - ) - except KeyError: - self._registration = 0 # Default to Gridline registration - self._gtype = 0 # Default to Cartesian grid type - - @property - def registration(self): - """ - Registration type of the grid, either Gridline (0) or Pixel (1). - """ - return self._registration - - @registration.setter - def registration(self, value): - if value in (0, 1): - self._registration = value - else: - raise GMTInvalidInput( - f"Invalid grid registration value: {value}, should be a boolean of " - "either 0 for Gridline registration or 1 for Pixel registration" - ) - - @property - def gtype(self): - """ - Coordinate system type of the grid, either Cartesian (0) or Geographic - (1). - """ - return self._gtype - - @gtype.setter - def gtype(self, value): - if value in (0, 1): - self._gtype = value - else: - raise GMTInvalidInput( - f"Invalid coordinate system type: {value}, should be a boolean of " - "either 0 for Cartesian or 1 for Geographic" - ) +""" +Non-plot GMT modules. +""" +import xarray as xr +from pygmt.clib import Session +from pygmt.exceptions import GMTInvalidInput +from pygmt.src.grdinfo import grdinfo + + +class config: # pylint: disable=invalid-name + """ + Set GMT defaults globally or locally. + + Change GMT defaults globally:: + + pygmt.config(PARAMETER=value) + + Change GMT defaults locally by using it as a context manager:: + + with pygmt.config(PARAMETER=value): + ... + + Full GMT defaults list at :gmt-docs:`gmt.conf.html` + """ + + def __init__(self, **kwargs): + # Save values so that we can revert to their initial values + self.old_defaults = {} + self.special_params = { + "FONT": [ + "FONT_ANNOT_PRIMARY", + "FONT_ANNOT_SECONDARY", + "FONT_HEADING", + "FONT_LABEL", + "FONT_TAG", + "FONT_TITLE", + ], + "FONT_ANNOT": ["FONT_ANNOT_PRIMARY", "FONT_ANNOT_SECONDARY"], + "FORMAT_TIME_MAP": ["FORMAT_TIME_PRIMARY_MAP", "FORMAT_TIME_SECONDARY_MAP"], + "MAP_ANNOT_OFFSET": [ + "MAP_ANNOT_OFFSET_PRIMARY", + "MAP_ANNOT_OFFSET_SECONDARY", + ], + "MAP_GRID_CROSS_SIZE": [ + "MAP_GRID_CROSS_SIZE_PRIMARY", + "MAP_GRID_CROSS_SIZE_SECONDARY", + ], + "MAP_GRID_PEN": ["MAP_GRID_PEN_PRIMARY", "MAP_GRID_PEN_SECONDARY"], + "MAP_TICK_LENGTH": ["MAP_TICK_LENGTH_PRIMARY", "MAP_TICK_LENGTH_SECONDARY"], + "MAP_TICK_PEN": ["MAP_TICK_PEN_PRIMARY", "MAP_TICK_PEN_SECONDARY"], + } + with Session() as lib: + for key in kwargs: + if key in self.special_params: + for k in self.special_params[key]: + self.old_defaults[k] = lib.get_default(k) + else: + self.old_defaults[key] = lib.get_default(key) + + # call gmt set to change GMT defaults + arg_str = " ".join( + ["{}={}".format(key, value) for key, value in kwargs.items()] + ) + with Session() as lib: + lib.call_module("set", arg_str) + + def __enter__(self): + return self + + def __exit__(self, exc_type, exc_value, traceback): + # revert to initial values + arg_str = " ".join( + ["{}={}".format(key, value) for key, value in self.old_defaults.items()] + ) + with Session() as lib: + lib.call_module("set", arg_str) + + +@xr.register_dataarray_accessor("gmt") +class GMTDataArrayAccessor: + """ + This is the GMT extension for :class:`xarray.DataArray`. + + You can access various GMT specific metadata about your grid as follows: + + >>> from pygmt.datasets import load_earth_relief + >>> # Use the global Earth relief grid with 1 degree spacing + >>> grid = load_earth_relief(resolution="01d") + + >>> # See if grid uses Gridline (0) or Pixel (1) registration + >>> grid.gmt.registration + 1 + >>> # See if grid uses Cartesian (0) or Geographic (1) coordinate system + >>> grid.gmt.gtype + 1 + """ + + def __init__(self, xarray_obj): + self._obj = xarray_obj + try: + self._source = self._obj.encoding["source"] # filepath to NetCDF source + # From the shortened summary information of `grdinfo`, + # get grid registration in column 10, and grid type in column 11 + self._registration, self._gtype = map( + int, grdinfo(self._source, per_column="n", o="10,11").split() + ) + except KeyError: + self._registration = 0 # Default to Gridline registration + self._gtype = 0 # Default to Cartesian grid type + + @property + def registration(self): + """ + Registration type of the grid, either Gridline (0) or Pixel (1). + """ + return self._registration + + @registration.setter + def registration(self, value): + if value in (0, 1): + self._registration = value + else: + raise GMTInvalidInput( + f"Invalid grid registration value: {value}, should be a boolean of " + "either 0 for Gridline registration or 1 for Pixel registration" + ) + + @property + def gtype(self): + """ + Coordinate system type of the grid, either Cartesian (0) or Geographic + (1). + """ + return self._gtype + + @gtype.setter + def gtype(self, value): + if value in (0, 1): + self._gtype = value + else: + raise GMTInvalidInput( + f"Invalid coordinate system type: {value}, should be a boolean of " + "either 0 for Cartesian or 1 for Geographic" + ) diff --git a/pygmt/src/__init__.py b/pygmt/src/__init__.py index 2e080c71448..1a28c03d012 100644 --- a/pygmt/src/__init__.py +++ b/pygmt/src/__init__.py @@ -1,32 +1,32 @@ -""" -Source code for PyGMT modules. -""" -# pylint: disable=import-outside-toplevel -from pygmt.src.basemap import basemap -from pygmt.src.blockmedian import blockmedian -from pygmt.src.coast import coast -from pygmt.src.colorbar import colorbar -from pygmt.src.contour import contour -from pygmt.src.grd2cpt import grd2cpt -from pygmt.src.grdcontour import grdcontour -from pygmt.src.grdcut import grdcut -from pygmt.src.grdfilter import grdfilter -from pygmt.src.grdimage import grdimage -from pygmt.src.grdinfo import grdinfo -from pygmt.src.grdtrack import grdtrack -from pygmt.src.grdview import grdview -from pygmt.src.image import image -from pygmt.src.info import info -from pygmt.src.inset import inset -from pygmt.src.legend import legend -from pygmt.src.logo import logo -from pygmt.src.makecpt import makecpt -from pygmt.src.meca import meca -from pygmt.src.plot import plot -from pygmt.src.plot3d import plot3d -from pygmt.src.subplot import set_panel, subplot -from pygmt.src.surface import surface -from pygmt.src.text import text_ as text # "text" is an argument within "text_" -from pygmt.src.which import which -from pygmt.src.x2sys_cross import x2sys_cross -from pygmt.src.x2sys_init import x2sys_init +""" +Source code for PyGMT modules. +""" +# pylint: disable=import-outside-toplevel +from pygmt.src.basemap import basemap +from pygmt.src.blockmedian import blockmedian +from pygmt.src.coast import coast +from pygmt.src.colorbar import colorbar +from pygmt.src.contour import contour +from pygmt.src.grd2cpt import grd2cpt +from pygmt.src.grdcontour import grdcontour +from pygmt.src.grdcut import grdcut +from pygmt.src.grdfilter import grdfilter +from pygmt.src.grdimage import grdimage +from pygmt.src.grdinfo import grdinfo +from pygmt.src.grdtrack import grdtrack +from pygmt.src.grdview import grdview +from pygmt.src.image import image +from pygmt.src.info import info +from pygmt.src.inset import inset +from pygmt.src.legend import legend +from pygmt.src.logo import logo +from pygmt.src.makecpt import makecpt +from pygmt.src.meca import meca +from pygmt.src.plot import plot +from pygmt.src.plot3d import plot3d +from pygmt.src.subplot import set_panel, subplot +from pygmt.src.surface import surface +from pygmt.src.text import text_ as text # "text" is an argument within "text_" +from pygmt.src.which import which +from pygmt.src.x2sys_cross import x2sys_cross +from pygmt.src.x2sys_init import x2sys_init diff --git a/pygmt/src/basemap.py b/pygmt/src/basemap.py index 1a742ea4d59..56997a8e9f4 100644 --- a/pygmt/src/basemap.py +++ b/pygmt/src/basemap.py @@ -1,83 +1,83 @@ -""" -basemap - Plot base maps and frames for the figure. -""" - -from pygmt.clib import Session -from pygmt.exceptions import GMTInvalidInput -from pygmt.helpers import ( - args_in_kwargs, - build_arg_string, - fmt_docstring, - kwargs_to_strings, - use_alias, -) - - -@fmt_docstring -@use_alias( - R="region", - J="projection", - Jz="zscale", - JZ="zsize", - B="frame", - L="map_scale", - Td="rose", - Tm="compass", - U="timestamp", - V="verbose", - X="xshift", - Y="yshift", - c="panel", - f="coltypes", - p="perspective", - t="transparency", -) -@kwargs_to_strings(R="sequence", c="sequence_comma", p="sequence") -def basemap(self, **kwargs): - r""" - Plot base maps and frames for the figure. - - Creates a basic or fancy basemap with axes, fill, and titles. Several - map projections are available, and the user may specify separate - tick-mark intervals for boundary annotation, ticking, and [optionally] - gridlines. A simple map scale or directional rose may also be plotted. - - At least one of the parameters ``frame``, ``map_scale``, ``rose`` or - ``compass`` must be specified. - - Full option list at :gmt-docs:`basemap.html` - - {aliases} - - Parameters - ---------- - {J} - zscale/zsize : float or str - Set z-axis scaling or z-axis size. - {R} - {B} - map_scale : str - [**g**\|\ **j**\|\ **J**\|\ **n**\|\ **x**]\ *refpoint*\ - **+w**\ *length*. - Draws a simple map scale centered on the reference point specified. - rose : str - Draws a map directional rose on the map at the location defined by - the reference and anchor points. - compass : str - Draws a map magnetic rose on the map at the location defined by the - reference and anchor points - {U} - {V} - {XY} - {c} - {f} - {p} - {t} - """ - kwargs = self._preprocess(**kwargs) # pylint: disable=protected-access - if not args_in_kwargs(args=["B", "L", "Td", "Tm", "c"], kwargs=kwargs): - raise GMTInvalidInput( - "At least one of frame, map_scale, compass, rose, or panel must be specified." - ) - with Session() as lib: - lib.call_module("basemap", build_arg_string(kwargs)) +""" +basemap - Plot base maps and frames for the figure. +""" + +from pygmt.clib import Session +from pygmt.exceptions import GMTInvalidInput +from pygmt.helpers import ( + args_in_kwargs, + build_arg_string, + fmt_docstring, + kwargs_to_strings, + use_alias, +) + + +@fmt_docstring +@use_alias( + R="region", + J="projection", + Jz="zscale", + JZ="zsize", + B="frame", + L="map_scale", + Td="rose", + Tm="compass", + U="timestamp", + V="verbose", + X="xshift", + Y="yshift", + c="panel", + f="coltypes", + p="perspective", + t="transparency", +) +@kwargs_to_strings(R="sequence", c="sequence_comma", p="sequence") +def basemap(self, **kwargs): + r""" + Plot base maps and frames for the figure. + + Creates a basic or fancy basemap with axes, fill, and titles. Several + map projections are available, and the user may specify separate + tick-mark intervals for boundary annotation, ticking, and [optionally] + gridlines. A simple map scale or directional rose may also be plotted. + + At least one of the parameters ``frame``, ``map_scale``, ``rose`` or + ``compass`` must be specified. + + Full option list at :gmt-docs:`basemap.html` + + {aliases} + + Parameters + ---------- + {J} + zscale/zsize : float or str + Set z-axis scaling or z-axis size. + {R} + {B} + map_scale : str + [**g**\|\ **j**\|\ **J**\|\ **n**\|\ **x**]\ *refpoint*\ + **+w**\ *length*. + Draws a simple map scale centered on the reference point specified. + rose : str + Draws a map directional rose on the map at the location defined by + the reference and anchor points. + compass : str + Draws a map magnetic rose on the map at the location defined by the + reference and anchor points + {U} + {V} + {XY} + {c} + {f} + {p} + {t} + """ + kwargs = self._preprocess(**kwargs) # pylint: disable=protected-access + if not args_in_kwargs(args=["B", "L", "Td", "Tm", "c"], kwargs=kwargs): + raise GMTInvalidInput( + "At least one of frame, map_scale, compass, rose, or panel must be specified." + ) + with Session() as lib: + lib.call_module("basemap", build_arg_string(kwargs)) diff --git a/pygmt/src/blockmedian.py b/pygmt/src/blockmedian.py index 190717e8ceb..1d045f3738f 100644 --- a/pygmt/src/blockmedian.py +++ b/pygmt/src/blockmedian.py @@ -1,93 +1,93 @@ -""" -blockmedian - Block average (x,y,z) data tables by median estimation. -""" -import pandas as pd -from pygmt.clib import Session -from pygmt.exceptions import GMTInvalidInput -from pygmt.helpers import ( - GMTTempFile, - build_arg_string, - data_kind, - dummy_context, - fmt_docstring, - kwargs_to_strings, - use_alias, -) - - -@fmt_docstring -@use_alias(I="spacing", R="region", V="verbose", f="coltypes") -@kwargs_to_strings(R="sequence") -def blockmedian(table, outfile=None, **kwargs): - r""" - Block average (x,y,z) data tables by median estimation. - - Reads arbitrarily located (x,y,z) triples [or optionally weighted - quadruples (x,y,z,w)] from a table and writes to the output a median - position and value for every non-empty block in a grid region defined by - the ``region`` and ``spacing`` parameters. - - Full option list at :gmt-docs:`blockmedian.html` - - {aliases} - - Parameters - ---------- - table : pandas.DataFrame or str - Either a pandas dataframe with (x, y, z) or (longitude, latitude, - elevation) values in the first three columns, or a file name to an - ASCII data table. - - spacing : str - *xinc*\[\ *unit*\][**+e**\|\ **n**] - [/*yinc*\ [*unit*][**+e**\|\ **n**]]. - *xinc* [and optionally *yinc*] is the grid spacing. - - region : str or list - *xmin/xmax/ymin/ymax*\[\ **+r**\][**+u**\ *unit*]. - Specify the region of interest. - - outfile : str - Required if ``table`` is a file. The file name for the output ASCII - file. - - {V} - {f} - - Returns - ------- - output : pandas.DataFrame or None - Return type depends on whether the ``outfile`` parameter is set: - - - :class:`pandas.DataFrame` table with (x, y, z) columns if ``outfile`` - is not set - - None if ``outfile`` is set (filtered output will be stored in file - set by ``outfile``) - """ - kind = data_kind(table) - with GMTTempFile(suffix=".csv") as tmpfile: - with Session() as lib: - if kind == "matrix": - if not hasattr(table, "values"): - raise GMTInvalidInput(f"Unrecognized data type: {type(table)}") - file_context = lib.virtualfile_from_matrix(table.values) - elif kind == "file": - if outfile is None: - raise GMTInvalidInput("Please pass in a str to 'outfile'") - file_context = dummy_context(table) - else: - raise GMTInvalidInput(f"Unrecognized data type: {type(table)}") - - with file_context as infile: - if outfile is None: - outfile = tmpfile.name - arg_str = " ".join([infile, build_arg_string(kwargs), "->" + outfile]) - lib.call_module(module="blockmedian", args=arg_str) - - # Read temporary csv output to a pandas table - if outfile == tmpfile.name: # if user did not set outfile, return pd.DataFrame - result = pd.read_csv(tmpfile.name, sep="\t", names=table.columns) - elif outfile != tmpfile.name: # return None if outfile set, output in outfile - result = None - - return result +""" +blockmedian - Block average (x,y,z) data tables by median estimation. +""" +import pandas as pd +from pygmt.clib import Session +from pygmt.exceptions import GMTInvalidInput +from pygmt.helpers import ( + GMTTempFile, + build_arg_string, + data_kind, + dummy_context, + fmt_docstring, + kwargs_to_strings, + use_alias, +) + + +@fmt_docstring +@use_alias(I="spacing", R="region", V="verbose", f="coltypes") +@kwargs_to_strings(R="sequence") +def blockmedian(table, outfile=None, **kwargs): + r""" + Block average (x,y,z) data tables by median estimation. + + Reads arbitrarily located (x,y,z) triples [or optionally weighted + quadruples (x,y,z,w)] from a table and writes to the output a median + position and value for every non-empty block in a grid region defined by + the ``region`` and ``spacing`` parameters. + + Full option list at :gmt-docs:`blockmedian.html` + + {aliases} + + Parameters + ---------- + table : pandas.DataFrame or str + Either a pandas dataframe with (x, y, z) or (longitude, latitude, + elevation) values in the first three columns, or a file name to an + ASCII data table. + + spacing : str + *xinc*\[\ *unit*\][**+e**\|\ **n**] + [/*yinc*\ [*unit*][**+e**\|\ **n**]]. + *xinc* [and optionally *yinc*] is the grid spacing. + + region : str or list + *xmin/xmax/ymin/ymax*\[\ **+r**\][**+u**\ *unit*]. + Specify the region of interest. + + outfile : str + Required if ``table`` is a file. The file name for the output ASCII + file. + + {V} + {f} + + Returns + ------- + output : pandas.DataFrame or None + Return type depends on whether the ``outfile`` parameter is set: + + - :class:`pandas.DataFrame` table with (x, y, z) columns if ``outfile`` + is not set + - None if ``outfile`` is set (filtered output will be stored in file + set by ``outfile``) + """ + kind = data_kind(table) + with GMTTempFile(suffix=".csv") as tmpfile: + with Session() as lib: + if kind == "matrix": + if not hasattr(table, "values"): + raise GMTInvalidInput(f"Unrecognized data type: {type(table)}") + file_context = lib.virtualfile_from_matrix(table.values) + elif kind == "file": + if outfile is None: + raise GMTInvalidInput("Please pass in a str to 'outfile'") + file_context = dummy_context(table) + else: + raise GMTInvalidInput(f"Unrecognized data type: {type(table)}") + + with file_context as infile: + if outfile is None: + outfile = tmpfile.name + arg_str = " ".join([infile, build_arg_string(kwargs), "->" + outfile]) + lib.call_module(module="blockmedian", args=arg_str) + + # Read temporary csv output to a pandas table + if outfile == tmpfile.name: # if user did not set outfile, return pd.DataFrame + result = pd.read_csv(tmpfile.name, sep="\t", names=table.columns) + elif outfile != tmpfile.name: # return None if outfile set, output in outfile + result = None + + return result diff --git a/pygmt/src/coast.py b/pygmt/src/coast.py index 6155651cc68..bddf319ea0f 100644 --- a/pygmt/src/coast.py +++ b/pygmt/src/coast.py @@ -1,196 +1,196 @@ -""" -coast - Plot land and water. -""" - -from pygmt.clib import Session -from pygmt.exceptions import GMTInvalidInput -from pygmt.helpers import ( - args_in_kwargs, - build_arg_string, - fmt_docstring, - kwargs_to_strings, - use_alias, -) - - -@fmt_docstring -@use_alias( - R="region", - J="projection", - A="area_thresh", - C="lakes", - B="frame", - D="resolution", - E="dcw", - I="rivers", - L="map_scale", - N="borders", - W="shorelines", - G="land", - S="water", - U="timestamp", - V="verbose", - X="xshift", - Y="yshift", - c="panel", - p="perspective", - t="transparency", -) -@kwargs_to_strings(R="sequence", c="sequence_comma", p="sequence") -def coast(self, **kwargs): - r""" - Plot continents, shorelines, rivers, and borders on maps - - Plots grayshaded, colored, or textured land-masses [or water-masses] on - maps and [optionally] draws coastlines, rivers, and political - boundaries. Alternatively, it can (1) issue clip paths that will - contain all land or all water areas, or (2) dump the data to an ASCII - table. The data files come in 5 different resolutions: (**f**)ull, - (**h**)igh, (**i**)ntermediate, (**l**)ow, and (**c**)rude. The full - resolution files amount to more than 55 Mb of data and provide great - detail; for maps of larger geographical extent it is more economical to - use one of the other resolutions. If the user selects to paint the - land-areas and does not specify fill of water-areas then the latter - will be transparent (i.e., earlier graphics drawn in those areas will - not be overwritten). Likewise, if the water-areas are painted and no - land fill is set then the land-areas will be transparent. - - A map projection must be supplied. - - Full option list at :gmt-docs:`coast.html` - - {aliases} - - Parameters - ---------- - {J} - {R} - area_thresh : int, float, or str - *min_area*\ [/*min_level*/*max_level*][**+a**\[**g**\|\ **i**]\ - [**s**\|\ **S**][**+l**\|\ **r**][**+p**\ *percent*]. - Features with an area smaller than *min_area* in km\ :sup:`2` or of - hierarchical level that is lower than *min_level* or higher than - *max_level* will not be plotted. - {B} - lakes : str or list - *fill*\ [**+l**\|\ **+r**]. - Set the shade, color, or pattern for lakes and river-lakes. The - default is the fill chosen for wet areas set by the ``water`` - parameter. Optionally, specify separate fills by appending - **+l** for lakes or **+r** for river-lakes, and passing multiple - strings in a list. - resolution : str - **f**\|\ **h**\|\ **i**\|\ **l**\|\ **c**. - Selects the resolution of the data set to: (**f**\ )ull, - (**h**\ )igh, (**i**\ )ntermediate, (**l**\ )ow, - and (**c**\ )rude. - land : str - Select filling or clipping of “dry” areas. - rivers : int or str or list - *river*\ [/*pen*]. - Draw rivers. Specify the type of rivers and [optionally] append - pen attributes [Default pen is width = default, color = black, - style = solid]. - - Choose from the list of river types below; pass a list to - ``rivers`` to use multiple arguments. - - 0 = Double-lined rivers (river-lakes) - - 1 = Permanent major rivers - - 2 = Additional major rivers - - 3 = Additional rivers - - 4 = Minor rivers - - 5 = Intermittent rivers - major - - 6 = Intermittent rivers - additional - - 7 = Intermittent rivers - minor - - 8 = Major canals - - 9 = Minor canals - - 10 = Irrigation canals - - You can also choose from several preconfigured river groups: - - a = All rivers and canals (0-10) - - A = All rivers and canals except river-lakes (1-10) - - r = All permanent rivers (0-4) - - R = All permanent rivers except river-lakes (1-4) - - i = All intermittent rivers (5-7) - - c = All canals (8-10) - map_scale : str - [**g**\|\ **j**\|\ **J**\|\ **n**\|\ **x**]\ *refpoint*\ - **+w**\ *length*. - Draws a simple map scale centered on the reference point specified. - borders : int or str or list - *border*\ [/*pen*]. - Draw political boundaries. Specify the type of boundary and - [optionally] append pen attributes [Default pen is width = default, - color = black, style = solid]. - - Choose from the list of boundaries below. Pass a list to - ``borders`` to use multiple arguments. - - 1 = National boundaries - - 2 = State boundaries within the Americas - - 3 = Marine boundaries - - a = All boundaries (1-3) - water : str - Select filling or clipping of “wet” areas. - {U} - shorelines : int or str or list - [*level*\ /]\ *pen*. - Draw shorelines [Default is no shorelines]. Append pen attributes - [Default is width = default, color = black, style = solid] which - apply to all four levels. To set the pen for a single level, - pass a string with *level*\ /*pen*\ , where level is - 1-4 and represent coastline, lakeshore, island-in-lake shore, and - lake-in-island-in-lake shore. Pass a list of *level*\ /*pen* - strings to ``shorelines`` to set multiple levels. When specific - level pens are set, those not listed will not be drawn. - dcw : str or list - *code1,code2,…*\ [**+l**\|\ **L**\ ][**+g**\ *fill*\ ] - [**+p**\ *pen*\ ][**+z**]. - Select painting or dumping country polygons from the - `Digital Chart of the World - `__. - Append one or more comma-separated countries using the 2-character - `ISO 3166-1 alpha-2 convention - `__. - To select a state of a country (if available), append - .\ *state*, (e.g, US.TX for Texas). To specify a whole continent, - prepend **=** to any of the continent codes (e.g. =EU for Europe). - Append **+p**\ *pen* to draw polygon outlines - (default is no outline) and **+g**\ *fill* to fill them - (default is no fill). Append **+l**\|\ **+L** to =\ *continent* to - only list countries in that continent; repeat if more than one - continent is requested. - {XY} - {c} - {p} - {t} - {V} - """ - kwargs = self._preprocess(**kwargs) # pylint: disable=protected-access - if not args_in_kwargs(args=["C", "G", "S", "I", "N", "E", "Q", "W"], kwargs=kwargs): - raise GMTInvalidInput( - """At least one of the following parameters must be specified: - lakes, land, water, rivers, borders, dcw, Q, or shorelines""" - ) - with Session() as lib: - lib.call_module("coast", build_arg_string(kwargs)) +""" +coast - Plot land and water. +""" + +from pygmt.clib import Session +from pygmt.exceptions import GMTInvalidInput +from pygmt.helpers import ( + args_in_kwargs, + build_arg_string, + fmt_docstring, + kwargs_to_strings, + use_alias, +) + + +@fmt_docstring +@use_alias( + R="region", + J="projection", + A="area_thresh", + C="lakes", + B="frame", + D="resolution", + E="dcw", + I="rivers", + L="map_scale", + N="borders", + W="shorelines", + G="land", + S="water", + U="timestamp", + V="verbose", + X="xshift", + Y="yshift", + c="panel", + p="perspective", + t="transparency", +) +@kwargs_to_strings(R="sequence", c="sequence_comma", p="sequence") +def coast(self, **kwargs): + r""" + Plot continents, shorelines, rivers, and borders on maps + + Plots grayshaded, colored, or textured land-masses [or water-masses] on + maps and [optionally] draws coastlines, rivers, and political + boundaries. Alternatively, it can (1) issue clip paths that will + contain all land or all water areas, or (2) dump the data to an ASCII + table. The data files come in 5 different resolutions: (**f**)ull, + (**h**)igh, (**i**)ntermediate, (**l**)ow, and (**c**)rude. The full + resolution files amount to more than 55 Mb of data and provide great + detail; for maps of larger geographical extent it is more economical to + use one of the other resolutions. If the user selects to paint the + land-areas and does not specify fill of water-areas then the latter + will be transparent (i.e., earlier graphics drawn in those areas will + not be overwritten). Likewise, if the water-areas are painted and no + land fill is set then the land-areas will be transparent. + + A map projection must be supplied. + + Full option list at :gmt-docs:`coast.html` + + {aliases} + + Parameters + ---------- + {J} + {R} + area_thresh : int, float, or str + *min_area*\ [/*min_level*/*max_level*][**+a**\[**g**\|\ **i**]\ + [**s**\|\ **S**][**+l**\|\ **r**][**+p**\ *percent*]. + Features with an area smaller than *min_area* in km\ :sup:`2` or of + hierarchical level that is lower than *min_level* or higher than + *max_level* will not be plotted. + {B} + lakes : str or list + *fill*\ [**+l**\|\ **+r**]. + Set the shade, color, or pattern for lakes and river-lakes. The + default is the fill chosen for wet areas set by the ``water`` + parameter. Optionally, specify separate fills by appending + **+l** for lakes or **+r** for river-lakes, and passing multiple + strings in a list. + resolution : str + **f**\|\ **h**\|\ **i**\|\ **l**\|\ **c**. + Selects the resolution of the data set to: (**f**\ )ull, + (**h**\ )igh, (**i**\ )ntermediate, (**l**\ )ow, + and (**c**\ )rude. + land : str + Select filling or clipping of “dry” areas. + rivers : int or str or list + *river*\ [/*pen*]. + Draw rivers. Specify the type of rivers and [optionally] append + pen attributes [Default pen is width = default, color = black, + style = solid]. + + Choose from the list of river types below; pass a list to + ``rivers`` to use multiple arguments. + + 0 = Double-lined rivers (river-lakes) + + 1 = Permanent major rivers + + 2 = Additional major rivers + + 3 = Additional rivers + + 4 = Minor rivers + + 5 = Intermittent rivers - major + + 6 = Intermittent rivers - additional + + 7 = Intermittent rivers - minor + + 8 = Major canals + + 9 = Minor canals + + 10 = Irrigation canals + + You can also choose from several preconfigured river groups: + + a = All rivers and canals (0-10) + + A = All rivers and canals except river-lakes (1-10) + + r = All permanent rivers (0-4) + + R = All permanent rivers except river-lakes (1-4) + + i = All intermittent rivers (5-7) + + c = All canals (8-10) + map_scale : str + [**g**\|\ **j**\|\ **J**\|\ **n**\|\ **x**]\ *refpoint*\ + **+w**\ *length*. + Draws a simple map scale centered on the reference point specified. + borders : int or str or list + *border*\ [/*pen*]. + Draw political boundaries. Specify the type of boundary and + [optionally] append pen attributes [Default pen is width = default, + color = black, style = solid]. + + Choose from the list of boundaries below. Pass a list to + ``borders`` to use multiple arguments. + + 1 = National boundaries + + 2 = State boundaries within the Americas + + 3 = Marine boundaries + + a = All boundaries (1-3) + water : str + Select filling or clipping of “wet” areas. + {U} + shorelines : int or str or list + [*level*\ /]\ *pen*. + Draw shorelines [Default is no shorelines]. Append pen attributes + [Default is width = default, color = black, style = solid] which + apply to all four levels. To set the pen for a single level, + pass a string with *level*\ /*pen*\ , where level is + 1-4 and represent coastline, lakeshore, island-in-lake shore, and + lake-in-island-in-lake shore. Pass a list of *level*\ /*pen* + strings to ``shorelines`` to set multiple levels. When specific + level pens are set, those not listed will not be drawn. + dcw : str or list + *code1,code2,…*\ [**+l**\|\ **L**\ ][**+g**\ *fill*\ ] + [**+p**\ *pen*\ ][**+z**]. + Select painting or dumping country polygons from the + `Digital Chart of the World + `__. + Append one or more comma-separated countries using the 2-character + `ISO 3166-1 alpha-2 convention + `__. + To select a state of a country (if available), append + .\ *state*, (e.g, US.TX for Texas). To specify a whole continent, + prepend **=** to any of the continent codes (e.g. =EU for Europe). + Append **+p**\ *pen* to draw polygon outlines + (default is no outline) and **+g**\ *fill* to fill them + (default is no fill). Append **+l**\|\ **+L** to =\ *continent* to + only list countries in that continent; repeat if more than one + continent is requested. + {XY} + {c} + {p} + {t} + {V} + """ + kwargs = self._preprocess(**kwargs) # pylint: disable=protected-access + if not args_in_kwargs(args=["C", "G", "S", "I", "N", "E", "Q", "W"], kwargs=kwargs): + raise GMTInvalidInput( + """At least one of the following parameters must be specified: + lakes, land, water, rivers, borders, dcw, Q, or shorelines""" + ) + with Session() as lib: + lib.call_module("coast", build_arg_string(kwargs)) diff --git a/pygmt/src/colorbar.py b/pygmt/src/colorbar.py index 3fd25c8ee99..08ddc981dd0 100644 --- a/pygmt/src/colorbar.py +++ b/pygmt/src/colorbar.py @@ -1,110 +1,110 @@ -""" -colorbar - Plot a colorbar. -""" - -from pygmt.clib import Session -from pygmt.helpers import build_arg_string, fmt_docstring, kwargs_to_strings, use_alias - - -@fmt_docstring -@use_alias( - R="region", - J="projection", - B="frame", - C="cmap", - D="position", - F="box", - G="truncate", - I="shading", - W="scale", - V="verbose", - X="xshift", - Y="yshift", - c="panel", - p="perspective", - t="transparency", -) -@kwargs_to_strings( - R="sequence", G="sequence", I="sequence", c="sequence_comma", p="sequence" -) -def colorbar(self, **kwargs): - r""" - Plot a gray or color scale-bar on maps. - - Both horizontal and vertical scales are supported. For CPTs with - gradational colors (i.e., the lower and upper boundary of an interval - have different colors) we will interpolate to give a continuous scale. - Variations in intensity due to shading/illumination may be displayed by - setting the ``shading`` parameter. Colors may be spaced according to a - linear scale, all be equal size, or by providing a file with individual - tile widths. - - Full option list at :gmt-docs:`colorbar.html` - - {aliases} - - Parameters - ---------- - frame : str or list - Set color bar boundary frame, labels, and axes attributes. - {CPT} - position : str - [**g**\|\ **j**\|\ **J**\|\ **n**\|\ **x**]\ *refpoint*\ - [**+w**\ *length*\ [/\ *width*]]\ [**+e**\ [**b**\|\ **f**][*length*]]\ - [**+h**\|\ **v**][**+j**\ *justify*]\ - [**+m**\ [**a**\|\ **c**\|\ **l**\|\ **u**]]\ - [**+n**\ [*txt*]][**+o**\ *dx*\ [/*dy*]]. - Defines the reference point on the map for the color scale using one of - four coordinate systems: (1) Use **g** for map (user) coordinates, (2) - use **j** or **J** for setting *refpoint* via a 2-char justification - code that refers to the (invisible) map domain rectangle, (3) use **n** - for normalized (0-1) coordinates, or (4) use **x** for plot - coordinates (inches, cm, etc.). All but **x** requires both ``region`` - and ``projection`` to be specified. Append **+w** followed by the - length and width of the color bar. If width is not specified then it is - set to 4% of the given length. Give a negative length to reverse - the scale bar. Append **+h** to get a horizontal scale - [Default is vertical (**+v**)]. By default, the anchor point on the - scale is assumed to be the bottom left corner (**BL**), but this can be - changed by appending **+j** followed by a 2-char justification code - *justify*. - box : bool or str - [**+c**\ *clearances*][**+g**\ *fill*][**+i**\ [[*gap*/]\ *pen*]]\ - [**+p**\ [*pen*]][**+r**\ [*radius*]][**+s**\ [[*dx*/*dy*/][*shade*]]]. - If set to ``True``, draws a rectangular border around the color scale. - Alternatively, specify a different pen with **+p**\ *pen*. Add - **+g**\ *fill* to fill the scale panel [default is no fill]. Append - **+c**\ *clearance* where *clearance* is either gap, xgap/ygap, or - lgap/rgap/bgap/tgap where these items are uniform, separate in x- and - y-direction, or individual side spacings between scale and border. - Append **+i** to draw a secondary, inner border as well. We use a - uniform gap between borders of 2p and the :gmt-term:`MAP_DEFAULTS_PEN` - unless other values are specified. Append **+r** to draw rounded - rectangular borders instead, with a 6p corner radius. You can override - this radius by appending another value. Finally, append **+s** to draw - an offset background shaded region. Here, *dx/dy* indicates the shift - relative to the foreground frame [4p/-4p] and shade sets the fill - style to use for shading [default is gray50]. - truncate : list or str - *zlo*/*zhi*. - Truncate the incoming CPT so that the lowest and highest z-levels are - to *zlo* and *zhi*. If one of these equal NaN then we leave that end of - the CPT alone. The truncation takes place before the plotting. - scale : float - Multiply all z-values in the CPT by the provided scale. By default - the CPT is used as is. - shading : str or list or bool - Add illumination effects. Passing a single numerical value sets the - range of intensities from -value to +value. If not specified, 1 is - used. Alternatively, set ``shading=[low, high]`` to specify an - asymmetric intensity range from *low* to *high*. [Default is no - illumination]. - {V} - {XY} - {c} - {p} - {t} - """ - kwargs = self._preprocess(**kwargs) # pylint: disable=protected-access - with Session() as lib: - lib.call_module("colorbar", build_arg_string(kwargs)) +""" +colorbar - Plot a colorbar. +""" + +from pygmt.clib import Session +from pygmt.helpers import build_arg_string, fmt_docstring, kwargs_to_strings, use_alias + + +@fmt_docstring +@use_alias( + R="region", + J="projection", + B="frame", + C="cmap", + D="position", + F="box", + G="truncate", + I="shading", + W="scale", + V="verbose", + X="xshift", + Y="yshift", + c="panel", + p="perspective", + t="transparency", +) +@kwargs_to_strings( + R="sequence", G="sequence", I="sequence", c="sequence_comma", p="sequence" +) +def colorbar(self, **kwargs): + r""" + Plot a gray or color scale-bar on maps. + + Both horizontal and vertical scales are supported. For CPTs with + gradational colors (i.e., the lower and upper boundary of an interval + have different colors) we will interpolate to give a continuous scale. + Variations in intensity due to shading/illumination may be displayed by + setting the ``shading`` parameter. Colors may be spaced according to a + linear scale, all be equal size, or by providing a file with individual + tile widths. + + Full option list at :gmt-docs:`colorbar.html` + + {aliases} + + Parameters + ---------- + frame : str or list + Set color bar boundary frame, labels, and axes attributes. + {CPT} + position : str + [**g**\|\ **j**\|\ **J**\|\ **n**\|\ **x**]\ *refpoint*\ + [**+w**\ *length*\ [/\ *width*]]\ [**+e**\ [**b**\|\ **f**][*length*]]\ + [**+h**\|\ **v**][**+j**\ *justify*]\ + [**+m**\ [**a**\|\ **c**\|\ **l**\|\ **u**]]\ + [**+n**\ [*txt*]][**+o**\ *dx*\ [/*dy*]]. + Defines the reference point on the map for the color scale using one of + four coordinate systems: (1) Use **g** for map (user) coordinates, (2) + use **j** or **J** for setting *refpoint* via a 2-char justification + code that refers to the (invisible) map domain rectangle, (3) use **n** + for normalized (0-1) coordinates, or (4) use **x** for plot + coordinates (inches, cm, etc.). All but **x** requires both ``region`` + and ``projection`` to be specified. Append **+w** followed by the + length and width of the color bar. If width is not specified then it is + set to 4% of the given length. Give a negative length to reverse + the scale bar. Append **+h** to get a horizontal scale + [Default is vertical (**+v**)]. By default, the anchor point on the + scale is assumed to be the bottom left corner (**BL**), but this can be + changed by appending **+j** followed by a 2-char justification code + *justify*. + box : bool or str + [**+c**\ *clearances*][**+g**\ *fill*][**+i**\ [[*gap*/]\ *pen*]]\ + [**+p**\ [*pen*]][**+r**\ [*radius*]][**+s**\ [[*dx*/*dy*/][*shade*]]]. + If set to ``True``, draws a rectangular border around the color scale. + Alternatively, specify a different pen with **+p**\ *pen*. Add + **+g**\ *fill* to fill the scale panel [default is no fill]. Append + **+c**\ *clearance* where *clearance* is either gap, xgap/ygap, or + lgap/rgap/bgap/tgap where these items are uniform, separate in x- and + y-direction, or individual side spacings between scale and border. + Append **+i** to draw a secondary, inner border as well. We use a + uniform gap between borders of 2p and the :gmt-term:`MAP_DEFAULTS_PEN` + unless other values are specified. Append **+r** to draw rounded + rectangular borders instead, with a 6p corner radius. You can override + this radius by appending another value. Finally, append **+s** to draw + an offset background shaded region. Here, *dx/dy* indicates the shift + relative to the foreground frame [4p/-4p] and shade sets the fill + style to use for shading [default is gray50]. + truncate : list or str + *zlo*/*zhi*. + Truncate the incoming CPT so that the lowest and highest z-levels are + to *zlo* and *zhi*. If one of these equal NaN then we leave that end of + the CPT alone. The truncation takes place before the plotting. + scale : float + Multiply all z-values in the CPT by the provided scale. By default + the CPT is used as is. + shading : str or list or bool + Add illumination effects. Passing a single numerical value sets the + range of intensities from -value to +value. If not specified, 1 is + used. Alternatively, set ``shading=[low, high]`` to specify an + asymmetric intensity range from *low* to *high*. [Default is no + illumination]. + {V} + {XY} + {c} + {p} + {t} + """ + kwargs = self._preprocess(**kwargs) # pylint: disable=protected-access + with Session() as lib: + lib.call_module("colorbar", build_arg_string(kwargs)) diff --git a/pygmt/src/contour.py b/pygmt/src/contour.py index 9578d26e9be..e46cec5ae34 100644 --- a/pygmt/src/contour.py +++ b/pygmt/src/contour.py @@ -1,119 +1,119 @@ -""" -contour - Plot contour table data. -""" - -from pygmt.clib import Session -from pygmt.exceptions import GMTInvalidInput -from pygmt.helpers import ( - build_arg_string, - data_kind, - dummy_context, - fmt_docstring, - kwargs_to_strings, - use_alias, -) - - -@fmt_docstring -@use_alias( - A="annotation", - B="frame", - C="levels", - G="label_placement", - J="projection", - L="triangular_mesh_pen", - N="no_clip", - R="region", - S="skip", - V="verbose", - W="pen", - X="xshift", - Y="yshift", - c="panel", - i="columns", - l="label", - p="perspective", - t="transparency", -) -@kwargs_to_strings(R="sequence", c="sequence_comma", i="sequence_comma", p="sequence") -def contour(self, x=None, y=None, z=None, data=None, **kwargs): - r""" - Contour table data by direct triangulation. - - Takes a matrix, (x,y,z) pairs, or a file name as input and plots lines, - polygons, or symbols at those locations on a map. - - Must provide either ``data`` or ``x``/``y``/``z``. - - Full option list at :gmt-docs:`contour.html` - - {aliases} - - Parameters - ---------- - x/y/z : 1d arrays - Arrays of x and y coordinates and values z of the data points. - data : str or 2d array - Either a data file name or a 2d numpy array with the tabular data. - {J} - {R} - annotation : str or int - Specify or disable annotated contour levels, modifies annotated - contours specified in ``interval``. - - - Specify a fixed annotation interval *annot_int* or a - single annotation level +\ *annot_int*. - {B} - levels : str - Contour file or level(s) - D : str - Dump contour coordinates. - E : str - Network information. - label_placement : str - Placement of labels. - I : bool - Color the triangles using CPT. - triangular_mesh_pen : str - Pen to draw the underlying triangulation [Default is none]. - no_clip : bool - Do NOT clip contours or image at the boundaries [Default will clip - to fit inside region]. - Q : float or str - [*cut*][**+z**]. - Do not draw contours with less than cut number of points. - skip : bool or str - [**p**\|\ **t**]. - Skip input points outside region. - {W} - label : str - Add a legend entry for the contour being plotted. Normally, the - annotated contour is selected for the legend. You can select the - regular contour instead, or both of them, by considering the label - to be of the format [*annotcontlabel*][/*contlabel*]. If either - label contains a slash (/) character then use ``|`` as the - separator for the two labels instead. - {V} - {XY} - {c} - {p} - {t} - """ - kwargs = self._preprocess(**kwargs) # pylint: disable=protected-access - - kind = data_kind(data, x, y, z) - if kind == "vectors" and z is None: - raise GMTInvalidInput("Must provided both x, y, and z.") - - with Session() as lib: - # Choose how data will be passed in to the module - if kind == "file": - file_context = dummy_context(data) - elif kind == "matrix": - file_context = lib.virtualfile_from_matrix(data) - elif kind == "vectors": - file_context = lib.virtualfile_from_vectors(x, y, z) - - with file_context as fname: - arg_str = " ".join([fname, build_arg_string(kwargs)]) - lib.call_module("contour", arg_str) +""" +contour - Plot contour table data. +""" + +from pygmt.clib import Session +from pygmt.exceptions import GMTInvalidInput +from pygmt.helpers import ( + build_arg_string, + data_kind, + dummy_context, + fmt_docstring, + kwargs_to_strings, + use_alias, +) + + +@fmt_docstring +@use_alias( + A="annotation", + B="frame", + C="levels", + G="label_placement", + J="projection", + L="triangular_mesh_pen", + N="no_clip", + R="region", + S="skip", + V="verbose", + W="pen", + X="xshift", + Y="yshift", + c="panel", + i="columns", + l="label", + p="perspective", + t="transparency", +) +@kwargs_to_strings(R="sequence", c="sequence_comma", i="sequence_comma", p="sequence") +def contour(self, x=None, y=None, z=None, data=None, **kwargs): + r""" + Contour table data by direct triangulation. + + Takes a matrix, (x,y,z) pairs, or a file name as input and plots lines, + polygons, or symbols at those locations on a map. + + Must provide either ``data`` or ``x``/``y``/``z``. + + Full option list at :gmt-docs:`contour.html` + + {aliases} + + Parameters + ---------- + x/y/z : 1d arrays + Arrays of x and y coordinates and values z of the data points. + data : str or 2d array + Either a data file name or a 2d numpy array with the tabular data. + {J} + {R} + annotation : str or int + Specify or disable annotated contour levels, modifies annotated + contours specified in ``interval``. + + - Specify a fixed annotation interval *annot_int* or a + single annotation level +\ *annot_int*. + {B} + levels : str + Contour file or level(s) + D : str + Dump contour coordinates. + E : str + Network information. + label_placement : str + Placement of labels. + I : bool + Color the triangles using CPT. + triangular_mesh_pen : str + Pen to draw the underlying triangulation [Default is none]. + no_clip : bool + Do NOT clip contours or image at the boundaries [Default will clip + to fit inside region]. + Q : float or str + [*cut*][**+z**]. + Do not draw contours with less than cut number of points. + skip : bool or str + [**p**\|\ **t**]. + Skip input points outside region. + {W} + label : str + Add a legend entry for the contour being plotted. Normally, the + annotated contour is selected for the legend. You can select the + regular contour instead, or both of them, by considering the label + to be of the format [*annotcontlabel*][/*contlabel*]. If either + label contains a slash (/) character then use ``|`` as the + separator for the two labels instead. + {V} + {XY} + {c} + {p} + {t} + """ + kwargs = self._preprocess(**kwargs) # pylint: disable=protected-access + + kind = data_kind(data, x, y, z) + if kind == "vectors" and z is None: + raise GMTInvalidInput("Must provided both x, y, and z.") + + with Session() as lib: + # Choose how data will be passed in to the module + if kind == "file": + file_context = dummy_context(data) + elif kind == "matrix": + file_context = lib.virtualfile_from_matrix(data) + elif kind == "vectors": + file_context = lib.virtualfile_from_vectors(x, y, z) + + with file_context as fname: + arg_str = " ".join([fname, build_arg_string(kwargs)]) + lib.call_module("contour", arg_str) diff --git a/pygmt/src/grd2cpt.py b/pygmt/src/grd2cpt.py index 5f5339436f7..a0769e6a694 100644 --- a/pygmt/src/grd2cpt.py +++ b/pygmt/src/grd2cpt.py @@ -1,177 +1,177 @@ -""" -grd2cpt - Create a CPT from a grid file. -""" - -from pygmt.clib import Session -from pygmt.exceptions import GMTInvalidInput -from pygmt.helpers import build_arg_string, fmt_docstring, kwargs_to_strings, use_alias - - -@fmt_docstring -@use_alias( - A="transparency", - C="cmap", - D="background", - F="color_model", - E="nlevels", - G="truncate", - H="output", - I="reverse", - L="limit", - M="overrule_bg", - N="no_bg", - Q="log", - R="region", - T="series", - V="verbose", - W="categorical", - Ww="cyclic", - Z="continuous", -) -@kwargs_to_strings(G="sequence", L="sequence", R="sequence", T="sequence") -def grd2cpt(grid, **kwargs): - r""" - Make GMT color palette tables from a grid file. - - This is a module that will help you make static color palette tables - (CPTs). By default, the CPT will simply be saved to the current session, - but you can use ``output`` to save it to a file. The CPT is based on an - existing dynamic master CPT of your choice, and the mapping from data value - to colors is through the data's cumulative distribution function (CDF), so - that the colors are histogram equalized. Thus if the grid(s) and the - resulting CPT are used in :meth:`pygmt.Figure.grdimage` with a linear - projection, the colors will be uniformly distributed in area on the plot. - Let z be the data values in the grid. Define CDF(Z) = (# of z < Z) / (# of - z in grid). (NaNs are ignored). These z-values are then normalized to the - master CPT and colors are sampled at the desired intervals. - - The CPT includes three additional colors beyond the range of z-values. - These are the background color (B) assigned to values lower than the lowest - *z*-value, the foreground color (F) assigned to values higher than the - highest *z*-value, and the NaN color (N) painted wherever values are - undefined. For color tables beyond the standard GMT offerings, visit - `cpt-city `_ and - `Scientific Colour-Maps `_. - - If the master CPT includes B, F, and N entries, these will be copied into - the new master file. If not, the parameters :gmt-term:`COLOR_BACKGROUND`, - :gmt-term:`COLOR_FOREGROUND`, and :gmt-term:`COLOR_NAN` from the - :gmt-docs:`gmt.conf ` file or the command line will be used. This - default behavior can be overruled using the options ``background``, - ``overrule_bg`` or ``no_bg``. - - The color model (RGB, HSV or CMYK) of the palette created by - :meth:`pygmt.grd2cpt` will be the same as specified in the header of the - master CPT. When there is no :gmt-term:`COLOR_MODEL` entry in the master - CPT, the :gmt-term:`COLOR_MODEL` specified in the - :gmt-docs:`gmt.conf ` file or the ``color_model`` option will be - used. - - Full option list at :gmt-docs:`grd2cpt.html` - - {aliases} - - Parameters - ---------- - grid : str or xarray.DataArray - The file name of the input grid or the grid loaded as a DataArray. - transparency : int or float or str - Sets a constant level of transparency (0-100) for all color slices. - Append **+a** to also affect the fore-, back-, and nan-colors - [Default is no transparency, i.e., 0 (opaque)]. - cmap : str - Selects the master color palette table (CPT) to use in the - interpolation. Full list of built-in color palette tables can be found - at :gmt-docs:`cookbook/cpts.html#built-in-color-palette-tables-cpt`. - background : bool or str - Select the back- and foreground colors to match the colors for lowest - and highest *z*-values in the output CPT [Default (``background=True`` - or ``background='o'``) uses the colors specified in the master file, or - those defined by the parameters :gmt-term:`COLOR_BACKGROUND`, - :gmt-term:`COLOR_FOREGROUND`, and :gmt-term:`COLOR_NAN`]. Use - ``background='i'`` to match the colors for the lowest and highest - values in the input (instead of the output) CPT. - color_model : - [**R**\|\ **r**\|\ **h**\|\ **c**][**+c**\ [*label*]]. - Force output CPT to be written with r/g/b codes, gray-scale values or - color name (**R**, default) or r/g/b codes only (**r**), or h-s-v codes - (**h**), or c/m/y/k codes (**c**). Optionally or alternatively, append - **+c** to write discrete palettes in categorical format. If *label* is - appended then we create labels for each category to be used when the - CPT is plotted. The *label* may be a comma-separated list of category - names (you can skip a category by not giving a name), or give - *start*\[-], where we automatically build monotonically increasing - labels from *start* (a single letter or an integer). Append ``-`` to - build ranges *start*-*start+1* instead. - nlevels : bool or int or str - Set to ``True`` to create a linear color table by using the grid - z-range as the new limits in the CPT. Alternatively, set *nlevels* - to resample the color table into *nlevels* equidistant slices. - series : list or str - [*min/max/inc*\ [**+b**\|\ **l**\|\ **n**\]|\ *file*\|\ *list*\]. - Defines the range of the new CPT by giving the lowest and highest - z-value (and optionally an interval). If this is not given, the - existing range in the master CPT will be used intact. The values - produced defines the color slice boundaries. If **+n** is used it - refers to the number of such boundaries and not the number of slices. - For details on array creation, see - :gmt-docs:`makecpt.html#generate-1d-array`. - truncate : list or str - *zlo/zhi*. - Truncate the incoming CPT so that the lowest and highest z-levels are - to *zlo* and *zhi*. If one of these equal NaN then we leave that end of - the CPT alone. The truncation takes place before any resampling. See - also :gmt-docs:`cookbook/features.html#manipulating-cpts`. - output : str - Optional parameter to set the file name with extension .cpt to store - the generated CPT file. If not given or False (default), saves the CPT - as the session current CPT. - reverse : str - Set this to True or c [Default] to reverse the sense of color - progression in the master CPT. Set this to z to reverse the sign of - z-values in the color table. Note that this change of z-direction - happens before *truncate* and *series* values are used so the latter - must be compatible with the changed *z*-range. See also - :gmt-docs:`cookbook/features.html#manipulating-cpts`. - overrule_bg : str - Overrule background, foreground, and NaN colors specified in the master - CPT with the values of the parameters :gmt-term:`COLOR_BACKGROUND`, - :gmt-term:`COLOR_FOREGROUND`, and :gmt-term:`COLOR_NAN` specified in - the :gmt-docs:`gmt.conf ` file or on the command line. When - combined with ``background``, only :gmt-term:`COLOR_NAN` is considered. - no_bg : bool - Do not write out the background, foreground, and NaN-color fields - [Default will write them, i.e. ``no_bg=False``]. - log : bool - For logarithmic interpolation scheme with input given as logarithms. - Expects input z-values provided via ``series`` to be log10(*z*), - assigns colors, and writes out *z*. - continuous : bool - Force a continuous CPT when building from a list of colors and a list - of z-values [Default is None, i.e. discrete values]. - categorical : bool - Do not interpolate the input color table but pick the output colors - starting at the beginning of the color table, until colors for all - intervals are assigned. This is particularly useful in combination with - a categorical color table, like ``cmap='categorical'``. - cyclic : bool - Produce a wrapped (cyclic) color table that endlessly repeats its - range. Note that ``cyclic=True`` cannot be set together with - ``categorical=True``. - {V} - """ - if "W" in kwargs and "Ww" in kwargs: - raise GMTInvalidInput("Set only categorical or cyclic to True, not both.") - with Session() as lib: - file_context = lib.virtualfile_from_data(check_kind="raster", data=grid) - with file_context as infile: - if "H" not in kwargs.keys(): # if no output is set - arg_str = " ".join([infile, build_arg_string(kwargs)]) - elif "H" in kwargs.keys(): # if output is set - outfile = kwargs.pop("H") - if not outfile or not isinstance(outfile, str): - raise GMTInvalidInput("'output' should be a proper file name.") - arg_str = " ".join( - [infile, build_arg_string(kwargs), f"-H > {outfile}"] - ) - lib.call_module("grd2cpt", arg_str) +""" +grd2cpt - Create a CPT from a grid file. +""" + +from pygmt.clib import Session +from pygmt.exceptions import GMTInvalidInput +from pygmt.helpers import build_arg_string, fmt_docstring, kwargs_to_strings, use_alias + + +@fmt_docstring +@use_alias( + A="transparency", + C="cmap", + D="background", + F="color_model", + E="nlevels", + G="truncate", + H="output", + I="reverse", + L="limit", + M="overrule_bg", + N="no_bg", + Q="log", + R="region", + T="series", + V="verbose", + W="categorical", + Ww="cyclic", + Z="continuous", +) +@kwargs_to_strings(G="sequence", L="sequence", R="sequence", T="sequence") +def grd2cpt(grid, **kwargs): + r""" + Make GMT color palette tables from a grid file. + + This is a module that will help you make static color palette tables + (CPTs). By default, the CPT will simply be saved to the current session, + but you can use ``output`` to save it to a file. The CPT is based on an + existing dynamic master CPT of your choice, and the mapping from data value + to colors is through the data's cumulative distribution function (CDF), so + that the colors are histogram equalized. Thus if the grid(s) and the + resulting CPT are used in :meth:`pygmt.Figure.grdimage` with a linear + projection, the colors will be uniformly distributed in area on the plot. + Let z be the data values in the grid. Define CDF(Z) = (# of z < Z) / (# of + z in grid). (NaNs are ignored). These z-values are then normalized to the + master CPT and colors are sampled at the desired intervals. + + The CPT includes three additional colors beyond the range of z-values. + These are the background color (B) assigned to values lower than the lowest + *z*-value, the foreground color (F) assigned to values higher than the + highest *z*-value, and the NaN color (N) painted wherever values are + undefined. For color tables beyond the standard GMT offerings, visit + `cpt-city `_ and + `Scientific Colour-Maps `_. + + If the master CPT includes B, F, and N entries, these will be copied into + the new master file. If not, the parameters :gmt-term:`COLOR_BACKGROUND`, + :gmt-term:`COLOR_FOREGROUND`, and :gmt-term:`COLOR_NAN` from the + :gmt-docs:`gmt.conf ` file or the command line will be used. This + default behavior can be overruled using the options ``background``, + ``overrule_bg`` or ``no_bg``. + + The color model (RGB, HSV or CMYK) of the palette created by + :meth:`pygmt.grd2cpt` will be the same as specified in the header of the + master CPT. When there is no :gmt-term:`COLOR_MODEL` entry in the master + CPT, the :gmt-term:`COLOR_MODEL` specified in the + :gmt-docs:`gmt.conf ` file or the ``color_model`` option will be + used. + + Full option list at :gmt-docs:`grd2cpt.html` + + {aliases} + + Parameters + ---------- + grid : str or xarray.DataArray + The file name of the input grid or the grid loaded as a DataArray. + transparency : int or float or str + Sets a constant level of transparency (0-100) for all color slices. + Append **+a** to also affect the fore-, back-, and nan-colors + [Default is no transparency, i.e., 0 (opaque)]. + cmap : str + Selects the master color palette table (CPT) to use in the + interpolation. Full list of built-in color palette tables can be found + at :gmt-docs:`cookbook/cpts.html#built-in-color-palette-tables-cpt`. + background : bool or str + Select the back- and foreground colors to match the colors for lowest + and highest *z*-values in the output CPT [Default (``background=True`` + or ``background='o'``) uses the colors specified in the master file, or + those defined by the parameters :gmt-term:`COLOR_BACKGROUND`, + :gmt-term:`COLOR_FOREGROUND`, and :gmt-term:`COLOR_NAN`]. Use + ``background='i'`` to match the colors for the lowest and highest + values in the input (instead of the output) CPT. + color_model : + [**R**\|\ **r**\|\ **h**\|\ **c**][**+c**\ [*label*]]. + Force output CPT to be written with r/g/b codes, gray-scale values or + color name (**R**, default) or r/g/b codes only (**r**), or h-s-v codes + (**h**), or c/m/y/k codes (**c**). Optionally or alternatively, append + **+c** to write discrete palettes in categorical format. If *label* is + appended then we create labels for each category to be used when the + CPT is plotted. The *label* may be a comma-separated list of category + names (you can skip a category by not giving a name), or give + *start*\[-], where we automatically build monotonically increasing + labels from *start* (a single letter or an integer). Append ``-`` to + build ranges *start*-*start+1* instead. + nlevels : bool or int or str + Set to ``True`` to create a linear color table by using the grid + z-range as the new limits in the CPT. Alternatively, set *nlevels* + to resample the color table into *nlevels* equidistant slices. + series : list or str + [*min/max/inc*\ [**+b**\|\ **l**\|\ **n**\]|\ *file*\|\ *list*\]. + Defines the range of the new CPT by giving the lowest and highest + z-value (and optionally an interval). If this is not given, the + existing range in the master CPT will be used intact. The values + produced defines the color slice boundaries. If **+n** is used it + refers to the number of such boundaries and not the number of slices. + For details on array creation, see + :gmt-docs:`makecpt.html#generate-1d-array`. + truncate : list or str + *zlo/zhi*. + Truncate the incoming CPT so that the lowest and highest z-levels are + to *zlo* and *zhi*. If one of these equal NaN then we leave that end of + the CPT alone. The truncation takes place before any resampling. See + also :gmt-docs:`cookbook/features.html#manipulating-cpts`. + output : str + Optional parameter to set the file name with extension .cpt to store + the generated CPT file. If not given or False (default), saves the CPT + as the session current CPT. + reverse : str + Set this to True or c [Default] to reverse the sense of color + progression in the master CPT. Set this to z to reverse the sign of + z-values in the color table. Note that this change of z-direction + happens before *truncate* and *series* values are used so the latter + must be compatible with the changed *z*-range. See also + :gmt-docs:`cookbook/features.html#manipulating-cpts`. + overrule_bg : str + Overrule background, foreground, and NaN colors specified in the master + CPT with the values of the parameters :gmt-term:`COLOR_BACKGROUND`, + :gmt-term:`COLOR_FOREGROUND`, and :gmt-term:`COLOR_NAN` specified in + the :gmt-docs:`gmt.conf ` file or on the command line. When + combined with ``background``, only :gmt-term:`COLOR_NAN` is considered. + no_bg : bool + Do not write out the background, foreground, and NaN-color fields + [Default will write them, i.e. ``no_bg=False``]. + log : bool + For logarithmic interpolation scheme with input given as logarithms. + Expects input z-values provided via ``series`` to be log10(*z*), + assigns colors, and writes out *z*. + continuous : bool + Force a continuous CPT when building from a list of colors and a list + of z-values [Default is None, i.e. discrete values]. + categorical : bool + Do not interpolate the input color table but pick the output colors + starting at the beginning of the color table, until colors for all + intervals are assigned. This is particularly useful in combination with + a categorical color table, like ``cmap='categorical'``. + cyclic : bool + Produce a wrapped (cyclic) color table that endlessly repeats its + range. Note that ``cyclic=True`` cannot be set together with + ``categorical=True``. + {V} + """ + if "W" in kwargs and "Ww" in kwargs: + raise GMTInvalidInput("Set only categorical or cyclic to True, not both.") + with Session() as lib: + file_context = lib.virtualfile_from_data(check_kind="raster", data=grid) + with file_context as infile: + if "H" not in kwargs.keys(): # if no output is set + arg_str = " ".join([infile, build_arg_string(kwargs)]) + elif "H" in kwargs.keys(): # if output is set + outfile = kwargs.pop("H") + if not outfile or not isinstance(outfile, str): + raise GMTInvalidInput("'output' should be a proper file name.") + arg_str = " ".join( + [infile, build_arg_string(kwargs), f"-H > {outfile}"] + ) + lib.call_module("grd2cpt", arg_str) diff --git a/pygmt/src/grdcontour.py b/pygmt/src/grdcontour.py index 5514a98c57e..0c66a5d1a3c 100644 --- a/pygmt/src/grdcontour.py +++ b/pygmt/src/grdcontour.py @@ -1,104 +1,104 @@ -""" -grdcontour - Plot a contour figure. -""" -from pygmt.clib import Session -from pygmt.helpers import build_arg_string, fmt_docstring, kwargs_to_strings, use_alias - - -@fmt_docstring -@use_alias( - A="annotation", - B="frame", - C="interval", - G="label_placement", - J="projection", - L="limit", - Q="cut", - R="region", - S="resample", - U="timestamp", - V="verbose", - W="pen", - l="label", - X="xshift", - Y="yshift", - c="panel", - f="coltypes", - p="perspective", - t="transparency", -) -@kwargs_to_strings( - R="sequence", L="sequence", A="sequence_plus", c="sequence_comma", p="sequence" -) -def grdcontour(self, grid, **kwargs): - r""" - Convert grids or images to contours and plot them on maps. - - Takes a grid file name or an xarray.DataArray object as input. - - Full option list at :gmt-docs:`grdcontour.html` - - {aliases} - - Parameters - ---------- - grid : str or xarray.DataArray - The file name of the input grid or the grid loaded as a DataArray. - interval : str or int - Specify the contour lines to generate. - - - The filename of a `CPT` file where the color boundaries will - be used as contour levels. - - The filename of a 2 (or 3) column file containing the contour - levels (col 1), (**C**)ontour or (**A**)nnotate (col 2), and optional - angle (col 3) - - A fixed contour interval *cont_int* or a single contour with - +\ *cont_int* - annotation : str, int, or list - Specify or disable annotated contour levels, modifies annotated - contours specified in ``interval``. - - - Specify a fixed annotation interval *annot_int* or a - single annotation level +\ *annot_int* - - Disable all annotation with **-** - - Optional label modifiers can be specified as a single string - ``'[annot_int]+e'`` or with a list of arguments - ``([annot_int], 'e', 'f10p', 'gred')``. - limit : str or list of 2 ints - *low*/*high*. - Do no draw contours below `low` or above `high`, specify as string - cut : str or int - Do not draw contours with less than `cut` number of points. - resample : str or int - Resample smoothing factor. - {J} - {R} - {B} - label_placement : str - [**d**\|\ **f**\|\ **n**\|\ **l**\|\ **L**\|\ **x**\|\ **X**]\ - *args*. - The required parameter controls the placement of labels along the - quoted lines. It supports five controlling algorithms. See - :gmt-docs:`grdcontour.html#g` for details. - {U} - {V} - {W} - {XY} - {c} - {f} - label : str - Add a legend entry for the contour being plotted. Normally, the - annotated contour is selected for the legend. You can select the - regular contour instead, or both of them, by considering the label - to be of the format [*annotcontlabel*][/*contlabel*]. If either - label contains a slash (/) character then use ``|`` as the - separator for the two labels instead. - {p} - {t} - """ - kwargs = self._preprocess(**kwargs) # pylint: disable=protected-access - with Session() as lib: - file_context = lib.virtualfile_from_data(check_kind="raster", data=grid) - with file_context as fname: - arg_str = " ".join([fname, build_arg_string(kwargs)]) - lib.call_module("grdcontour", arg_str) +""" +grdcontour - Plot a contour figure. +""" +from pygmt.clib import Session +from pygmt.helpers import build_arg_string, fmt_docstring, kwargs_to_strings, use_alias + + +@fmt_docstring +@use_alias( + A="annotation", + B="frame", + C="interval", + G="label_placement", + J="projection", + L="limit", + Q="cut", + R="region", + S="resample", + U="timestamp", + V="verbose", + W="pen", + l="label", + X="xshift", + Y="yshift", + c="panel", + f="coltypes", + p="perspective", + t="transparency", +) +@kwargs_to_strings( + R="sequence", L="sequence", A="sequence_plus", c="sequence_comma", p="sequence" +) +def grdcontour(self, grid, **kwargs): + r""" + Convert grids or images to contours and plot them on maps. + + Takes a grid file name or an xarray.DataArray object as input. + + Full option list at :gmt-docs:`grdcontour.html` + + {aliases} + + Parameters + ---------- + grid : str or xarray.DataArray + The file name of the input grid or the grid loaded as a DataArray. + interval : str or int + Specify the contour lines to generate. + + - The filename of a `CPT` file where the color boundaries will + be used as contour levels. + - The filename of a 2 (or 3) column file containing the contour + levels (col 1), (**C**)ontour or (**A**)nnotate (col 2), and optional + angle (col 3) + - A fixed contour interval *cont_int* or a single contour with + +\ *cont_int* + annotation : str, int, or list + Specify or disable annotated contour levels, modifies annotated + contours specified in ``interval``. + + - Specify a fixed annotation interval *annot_int* or a + single annotation level +\ *annot_int* + - Disable all annotation with **-** + - Optional label modifiers can be specified as a single string + ``'[annot_int]+e'`` or with a list of arguments + ``([annot_int], 'e', 'f10p', 'gred')``. + limit : str or list of 2 ints + *low*/*high*. + Do no draw contours below `low` or above `high`, specify as string + cut : str or int + Do not draw contours with less than `cut` number of points. + resample : str or int + Resample smoothing factor. + {J} + {R} + {B} + label_placement : str + [**d**\|\ **f**\|\ **n**\|\ **l**\|\ **L**\|\ **x**\|\ **X**]\ + *args*. + The required parameter controls the placement of labels along the + quoted lines. It supports five controlling algorithms. See + :gmt-docs:`grdcontour.html#g` for details. + {U} + {V} + {W} + {XY} + {c} + {f} + label : str + Add a legend entry for the contour being plotted. Normally, the + annotated contour is selected for the legend. You can select the + regular contour instead, or both of them, by considering the label + to be of the format [*annotcontlabel*][/*contlabel*]. If either + label contains a slash (/) character then use ``|`` as the + separator for the two labels instead. + {p} + {t} + """ + kwargs = self._preprocess(**kwargs) # pylint: disable=protected-access + with Session() as lib: + file_context = lib.virtualfile_from_data(check_kind="raster", data=grid) + with file_context as fname: + arg_str = " ".join([fname, build_arg_string(kwargs)]) + lib.call_module("grdcontour", arg_str) diff --git a/pygmt/src/grdcut.py b/pygmt/src/grdcut.py index 353b82bc3a5..204b31d3916 100644 --- a/pygmt/src/grdcut.py +++ b/pygmt/src/grdcut.py @@ -1,108 +1,108 @@ -""" -grdcut - Extract subregion from a grid. -""" - -import xarray as xr -from pygmt.clib import Session -from pygmt.helpers import ( - GMTTempFile, - build_arg_string, - fmt_docstring, - kwargs_to_strings, - use_alias, -) - - -@fmt_docstring -@use_alias( - G="outgrid", - R="region", - J="projection", - N="extend", - S="circ_subregion", - V="verbose", - Z="z_subregion", - f="coltypes", -) -@kwargs_to_strings(R="sequence") -def grdcut(grid, **kwargs): - r""" - Extract subregion from a grid. - - Produce a new ``outgrid`` file which is a subregion of ``grid``. The - subregion is specified with ``region``; the specified range must not exceed - the range of ``grid`` (but see ``extend``). If in doubt, run - :meth:`pygmt.grdinfo` to check range. Alternatively, define the subregion - indirectly via a range check on the node values or via distances from a - given point. Finally, you can give ``projection`` for oblique projections - to determine the corresponding rectangular ``region`` that will give a grid - that fully covers the oblique domain. - - Full option list at :gmt-docs:`grdcut.html` - - {aliases} - - Parameters - ---------- - grid : str or xarray.DataArray - The file name of the input grid or the grid loaded as a DataArray. - outgrid : str or None - The name of the output netCDF file with extension .nc to store the grid - in. - {J} - {R} - extend : bool or int or float - Allow grid to be extended if new ``region`` exceeds existing - boundaries. Give a value to initialize nodes outside current region. - circ_subregion : str - *lon/lat/radius*\[\ *unit*\][**+n**]. - Specify an origin (*lon* and *lat*) and *radius*; append a distance - *unit* and we determine the corresponding rectangular region so that - all grid nodes on or inside the circle are contained in the subset. - If **+n** is appended we set all nodes outside the circle to NaN. - z_subregion : str - [*min/max*\][**+n**\|\ **N**\|\ **r**]. - Determine a new rectangular region so that all nodes outside this - region are also outside the given z-range [-inf/+inf]. To indicate no - limit on *min* or *max* only, specify a hyphen (-). Normally, any NaNs - encountered are simply skipped and not considered in the - range-decision. Append **+n** to consider a NaN to be outside the given - z-range. This means the new subset will be NaN-free. Alternatively, - append **+r** to consider NaNs to be within the data range. In this - case we stop shrinking the boundaries once a NaN is found [Default - simply skips NaNs when making the range decision]. Finally, if your - core subset grid is surrounded by rows and/or columns that are all - NaNs, append **+N** to strip off such columns before (optionally) - considering the range of the core subset for further reduction of the - area. - - {V} - {f} - - Returns - ------- - ret: xarray.DataArray or None - Return type depends on whether the ``outgrid`` parameter is set: - - - :class:`xarray.DataArray` if ``outgrid`` is not set - - None if ``outgrid`` is set (grid output will be stored in file set by - ``outgrid``) - """ - with GMTTempFile(suffix=".nc") as tmpfile: - with Session() as lib: - file_context = lib.virtualfile_from_data(check_kind="raster", data=grid) - with file_context as infile: - if "G" not in kwargs.keys(): # if outgrid is unset, output to tempfile - kwargs.update({"G": tmpfile.name}) - outgrid = kwargs["G"] - arg_str = " ".join([infile, build_arg_string(kwargs)]) - lib.call_module("grdcut", arg_str) - - if outgrid == tmpfile.name: # if user did not set outgrid, return DataArray - with xr.open_dataarray(outgrid) as dataarray: - result = dataarray.load() - _ = result.gmt # load GMTDataArray accessor information - else: - result = None # if user sets an outgrid, return None - - return result +""" +grdcut - Extract subregion from a grid. +""" + +import xarray as xr +from pygmt.clib import Session +from pygmt.helpers import ( + GMTTempFile, + build_arg_string, + fmt_docstring, + kwargs_to_strings, + use_alias, +) + + +@fmt_docstring +@use_alias( + G="outgrid", + R="region", + J="projection", + N="extend", + S="circ_subregion", + V="verbose", + Z="z_subregion", + f="coltypes", +) +@kwargs_to_strings(R="sequence") +def grdcut(grid, **kwargs): + r""" + Extract subregion from a grid. + + Produce a new ``outgrid`` file which is a subregion of ``grid``. The + subregion is specified with ``region``; the specified range must not exceed + the range of ``grid`` (but see ``extend``). If in doubt, run + :meth:`pygmt.grdinfo` to check range. Alternatively, define the subregion + indirectly via a range check on the node values or via distances from a + given point. Finally, you can give ``projection`` for oblique projections + to determine the corresponding rectangular ``region`` that will give a grid + that fully covers the oblique domain. + + Full option list at :gmt-docs:`grdcut.html` + + {aliases} + + Parameters + ---------- + grid : str or xarray.DataArray + The file name of the input grid or the grid loaded as a DataArray. + outgrid : str or None + The name of the output netCDF file with extension .nc to store the grid + in. + {J} + {R} + extend : bool or int or float + Allow grid to be extended if new ``region`` exceeds existing + boundaries. Give a value to initialize nodes outside current region. + circ_subregion : str + *lon/lat/radius*\[\ *unit*\][**+n**]. + Specify an origin (*lon* and *lat*) and *radius*; append a distance + *unit* and we determine the corresponding rectangular region so that + all grid nodes on or inside the circle are contained in the subset. + If **+n** is appended we set all nodes outside the circle to NaN. + z_subregion : str + [*min/max*\][**+n**\|\ **N**\|\ **r**]. + Determine a new rectangular region so that all nodes outside this + region are also outside the given z-range [-inf/+inf]. To indicate no + limit on *min* or *max* only, specify a hyphen (-). Normally, any NaNs + encountered are simply skipped and not considered in the + range-decision. Append **+n** to consider a NaN to be outside the given + z-range. This means the new subset will be NaN-free. Alternatively, + append **+r** to consider NaNs to be within the data range. In this + case we stop shrinking the boundaries once a NaN is found [Default + simply skips NaNs when making the range decision]. Finally, if your + core subset grid is surrounded by rows and/or columns that are all + NaNs, append **+N** to strip off such columns before (optionally) + considering the range of the core subset for further reduction of the + area. + + {V} + {f} + + Returns + ------- + ret: xarray.DataArray or None + Return type depends on whether the ``outgrid`` parameter is set: + + - :class:`xarray.DataArray` if ``outgrid`` is not set + - None if ``outgrid`` is set (grid output will be stored in file set by + ``outgrid``) + """ + with GMTTempFile(suffix=".nc") as tmpfile: + with Session() as lib: + file_context = lib.virtualfile_from_data(check_kind="raster", data=grid) + with file_context as infile: + if "G" not in kwargs.keys(): # if outgrid is unset, output to tempfile + kwargs.update({"G": tmpfile.name}) + outgrid = kwargs["G"] + arg_str = " ".join([infile, build_arg_string(kwargs)]) + lib.call_module("grdcut", arg_str) + + if outgrid == tmpfile.name: # if user did not set outgrid, return DataArray + with xr.open_dataarray(outgrid) as dataarray: + result = dataarray.load() + _ = result.gmt # load GMTDataArray accessor information + else: + result = None # if user sets an outgrid, return None + + return result diff --git a/pygmt/src/grdfilter.py b/pygmt/src/grdfilter.py index 7a60d990f25..c76caef153b 100644 --- a/pygmt/src/grdfilter.py +++ b/pygmt/src/grdfilter.py @@ -1,162 +1,162 @@ -""" -grdfilter - Filter a grid in the space (or time) domain. -""" - -import xarray as xr -from pygmt.clib import Session -from pygmt.helpers import ( - GMTTempFile, - build_arg_string, - fmt_docstring, - kwargs_to_strings, - use_alias, -) - - -@fmt_docstring -@use_alias( - D="distance", - F="filter", - G="outgrid", - I="spacing", - N="nans", - R="region", - T="toggle", - V="verbose", - f="coltypes", -) -@kwargs_to_strings(R="sequence") -def grdfilter(grid, **kwargs): - r""" - Filter a grid in the space (or time) domain. - - Filter a grid file in the time domain using one of the selected convolution - or non-convolution isotropic or rectangular filters and compute distances - using Cartesian or Spherical geometries. The output grid file can - optionally be generated as a sub-region of the input (via ``region``) - and/or with new increment (via ``spacing``) or registration - (via ``toggle``). In this way, one may have "extra space" in the input - data so that the edges will not be used and the output can be within one - half-width of the input edges. If the filter is low-pass, then the output - may be less frequently sampled than the input. - - Full option list at :gmt-docs:`grdfilter.html` - - {aliases} - - Parameters - ---------- - grid : str or xarray.DataArray - The file name of the input grid or the grid loaded as a DataArray. - outgrid : str or None - The name of the output netCDF file with extension .nc to store the grid - in. - filter : str - **b**\|\ **c**\|\ **g**\|\ **o**\|\ **m**\|\ **p**\|\ **h**\ *xwidth*\ - [/*width2*\][*modifiers*]. - Name of filter type you which to apply, followed by the width: - - b: Box Car - - c: Cosine Arch - - g: Gaussian - - o: Operator - - m: Median - - p: Maximum Likelihood probability - - h: histogram - distance : str - Distance *flag* tells how grid (x,y) relates to filter width as - follows: - - p: grid (px,py) with *width* an odd number of pixels; Cartesian - distances. - - 0: grid (x,y) same units as *width*, Cartesian distances. - - 1: grid (x,y) in degrees, *width* in kilometers, Cartesian distances. - - 2: grid (x,y) in degrees, *width* in km, dx scaled by cos(middle y), - Cartesian distances. - - The above options are fastest because they allow weight matrix to be - computed only once. The next three options are slower because they - recompute weights for each latitude. - - 3: grid (x,y) in degrees, *width* in km, dx scaled by cosine(y), - Cartesian distance calculation. - - 4: grid (x,y) in degrees, *width* in km, Spherical distance - calculation. - - 5: grid (x,y) in Mercator ``projection='m1'`` img units, *width* in km, - Spherical distance calculation. - - spacing : str - *xinc*\[\ *unit*\][**+e**\|\ **n**] - [/*yinc*\ [*unit*][**+e**\|\ **n**]]. - *xinc* [and optionally *yinc*] is the grid spacing. - nans : str or float - **i**\|\ **p**\|\ **r**. - Determine how NaN-values in the input grid affects the filtered output. - {R} - toggle : bool - Toggle the node registration for the output grid so as to become the - opposite of the input grid. [Default gives the same registration as the - input grid]. - {V} - {f} - - Returns - ------- - ret: xarray.DataArray or None - Return type depends on whether the ``outgrid`` parameter is set: - - - :class:`xarray.DataArray` if ``outgrid`` is not set - - None if ``outgrid`` is set (grid output will be stored in file set by - ``outgrid``) - - Examples - -------- - >>> import os - >>> import pygmt - - >>> # Apply a filter of 600km (full width) to the @earth_relief_30m file - >>> # and return a filtered field (saved as netcdf) - >>> pygmt.grdfilter( - ... grid="@earth_relief_30m", - ... filter="m600", - ... distance="4", - ... region=[150, 250, 10, 40], - ... spacing=0.5, - ... outgrid="filtered_pacific.nc", - ... ) - >>> os.remove("filtered_pacific.nc") # cleanup file - - >>> # Apply a gaussian smoothing filter of 600 km in the input data array, - >>> # and returns a filtered data array with the smoothed field. - >>> grid = pygmt.datasets.load_earth_relief() - >>> smooth_field = pygmt.grdfilter(grid=grid, filter="g600", distance="4") - """ - with GMTTempFile(suffix=".nc") as tmpfile: - with Session() as lib: - file_context = lib.virtualfile_from_data(check_kind="raster", data=grid) - with file_context as infile: - if "G" not in kwargs.keys(): # if outgrid is unset, output to tempfile - kwargs.update({"G": tmpfile.name}) - outgrid = kwargs["G"] - arg_str = " ".join([infile, build_arg_string(kwargs)]) - lib.call_module("grdfilter", arg_str) - - if outgrid == tmpfile.name: # if user did not set outgrid, return DataArray - with xr.open_dataarray(outgrid) as dataarray: - result = dataarray.load() - _ = result.gmt # load GMTDataArray accessor information - else: - result = None # if user sets an outgrid, return None - - return result +""" +grdfilter - Filter a grid in the space (or time) domain. +""" + +import xarray as xr +from pygmt.clib import Session +from pygmt.helpers import ( + GMTTempFile, + build_arg_string, + fmt_docstring, + kwargs_to_strings, + use_alias, +) + + +@fmt_docstring +@use_alias( + D="distance", + F="filter", + G="outgrid", + I="spacing", + N="nans", + R="region", + T="toggle", + V="verbose", + f="coltypes", +) +@kwargs_to_strings(R="sequence") +def grdfilter(grid, **kwargs): + r""" + Filter a grid in the space (or time) domain. + + Filter a grid file in the time domain using one of the selected convolution + or non-convolution isotropic or rectangular filters and compute distances + using Cartesian or Spherical geometries. The output grid file can + optionally be generated as a sub-region of the input (via ``region``) + and/or with new increment (via ``spacing``) or registration + (via ``toggle``). In this way, one may have "extra space" in the input + data so that the edges will not be used and the output can be within one + half-width of the input edges. If the filter is low-pass, then the output + may be less frequently sampled than the input. + + Full option list at :gmt-docs:`grdfilter.html` + + {aliases} + + Parameters + ---------- + grid : str or xarray.DataArray + The file name of the input grid or the grid loaded as a DataArray. + outgrid : str or None + The name of the output netCDF file with extension .nc to store the grid + in. + filter : str + **b**\|\ **c**\|\ **g**\|\ **o**\|\ **m**\|\ **p**\|\ **h**\ *xwidth*\ + [/*width2*\][*modifiers*]. + Name of filter type you which to apply, followed by the width: + + b: Box Car + + c: Cosine Arch + + g: Gaussian + + o: Operator + + m: Median + + p: Maximum Likelihood probability + + h: histogram + distance : str + Distance *flag* tells how grid (x,y) relates to filter width as + follows: + + p: grid (px,py) with *width* an odd number of pixels; Cartesian + distances. + + 0: grid (x,y) same units as *width*, Cartesian distances. + + 1: grid (x,y) in degrees, *width* in kilometers, Cartesian distances. + + 2: grid (x,y) in degrees, *width* in km, dx scaled by cos(middle y), + Cartesian distances. + + The above options are fastest because they allow weight matrix to be + computed only once. The next three options are slower because they + recompute weights for each latitude. + + 3: grid (x,y) in degrees, *width* in km, dx scaled by cosine(y), + Cartesian distance calculation. + + 4: grid (x,y) in degrees, *width* in km, Spherical distance + calculation. + + 5: grid (x,y) in Mercator ``projection='m1'`` img units, *width* in km, + Spherical distance calculation. + + spacing : str + *xinc*\[\ *unit*\][**+e**\|\ **n**] + [/*yinc*\ [*unit*][**+e**\|\ **n**]]. + *xinc* [and optionally *yinc*] is the grid spacing. + nans : str or float + **i**\|\ **p**\|\ **r**. + Determine how NaN-values in the input grid affects the filtered output. + {R} + toggle : bool + Toggle the node registration for the output grid so as to become the + opposite of the input grid. [Default gives the same registration as the + input grid]. + {V} + {f} + + Returns + ------- + ret: xarray.DataArray or None + Return type depends on whether the ``outgrid`` parameter is set: + + - :class:`xarray.DataArray` if ``outgrid`` is not set + - None if ``outgrid`` is set (grid output will be stored in file set by + ``outgrid``) + + Examples + -------- + >>> import os + >>> import pygmt + + >>> # Apply a filter of 600km (full width) to the @earth_relief_30m file + >>> # and return a filtered field (saved as netcdf) + >>> pygmt.grdfilter( + ... grid="@earth_relief_30m", + ... filter="m600", + ... distance="4", + ... region=[150, 250, 10, 40], + ... spacing=0.5, + ... outgrid="filtered_pacific.nc", + ... ) + >>> os.remove("filtered_pacific.nc") # cleanup file + + >>> # Apply a gaussian smoothing filter of 600 km in the input data array, + >>> # and returns a filtered data array with the smoothed field. + >>> grid = pygmt.datasets.load_earth_relief() + >>> smooth_field = pygmt.grdfilter(grid=grid, filter="g600", distance="4") + """ + with GMTTempFile(suffix=".nc") as tmpfile: + with Session() as lib: + file_context = lib.virtualfile_from_data(check_kind="raster", data=grid) + with file_context as infile: + if "G" not in kwargs.keys(): # if outgrid is unset, output to tempfile + kwargs.update({"G": tmpfile.name}) + outgrid = kwargs["G"] + arg_str = " ".join([infile, build_arg_string(kwargs)]) + lib.call_module("grdfilter", arg_str) + + if outgrid == tmpfile.name: # if user did not set outgrid, return DataArray + with xr.open_dataarray(outgrid) as dataarray: + result = dataarray.load() + _ = result.gmt # load GMTDataArray accessor information + else: + result = None # if user sets an outgrid, return None + + return result diff --git a/pygmt/src/grdimage.py b/pygmt/src/grdimage.py index 2d8f41ce703..92a8dd04b1d 100644 --- a/pygmt/src/grdimage.py +++ b/pygmt/src/grdimage.py @@ -1,158 +1,158 @@ -""" -grdimage - Plot grids or images. -""" -from pygmt.clib import Session -from pygmt.helpers import build_arg_string, fmt_docstring, kwargs_to_strings, use_alias - - -@fmt_docstring -@use_alias( - A="img_out", - B="frame", - C="cmap", - D="img_in", - E="dpi", - G="bit_color", - I="shading", - J="projection", - M="monochrome", - N="no_clip", - Q="nan_transparent", - R="region", - U="timestamp", - V="verbose", - X="xshift", - Y="yshift", - n="interpolation", - c="panel", - f="coltypes", - p="perspective", - t="transparency", - x="cores", -) -@kwargs_to_strings(R="sequence", c="sequence_comma", p="sequence") -def grdimage(self, grid, **kwargs): - r""" - Project and plot grids or images. - - Reads a 2-D grid file and produces a gray-shaded (or colored) map by - building a rectangular image and assigning pixels a gray-shade (or - color) based on the z-value and the CPT file. Optionally, illumination - may be added by providing a file with intensities in the (-1,+1) range - or instructions to derive intensities from the input data grid. Values - outside this range will be clipped. If GMT is built with GDAL support, - ``grid`` can be an image file (geo-referenced or not). In this case the - image can optionally be illuminated with the file provided via the - ``shading`` parameter. Here, if image has no coordinates then those of the - intensity file will be used. - - When using map projections, the grid is first resampled on a new - rectangular grid with the same dimensions. Higher resolution images can - be obtained by using the ``dpi`` parameter. To obtain the resampled value - (and hence shade or color) of each map pixel, its location is inversely - projected back onto the input grid after which a value is interpolated - between the surrounding input grid values. By default bi-cubic - interpolation is used. Aliasing is avoided by also forward projecting - the input grid nodes. If two or more nodes are projected onto the same - pixel, their average will dominate in the calculation of the pixel - value. Interpolation and aliasing is controlled with the - ``interpolation`` parameter. - - The ``region`` parameter can be used to select a map region larger or - smaller than that implied by the extent of the grid. - - Full parameter list at :gmt-docs:`grdimage.html` - - {aliases} - - Parameters - ---------- - grid : str or xarray.DataArray - The file name or a DataArray containing the input 2-D gridded data - set or image to be plotted (See GRID FILE FORMATS at - :gmt-docs:`grdimage.html#grid-file-formats`). - img_out : str - *out_img*\[=\ *driver*]. - Save an image in a raster format instead of PostScript. Use - extension .ppm for a Portable Pixel Map format which is the only - raster format GMT can natively write. For GMT installations - configured with GDAL support there are more choices: Append - *out_img* to select the image file name and extension. If the - extension is one of .bmp, .gif, .jpg, .png, or .tif then no driver - information is required. For other output formats you must append - the required GDAL driver. The *driver* is the driver code name used - by GDAL; see your GDAL installation's documentation for available - drivers. Append a **+c**\ *args* string where *args* is a list - of one or more concatenated number of GDAL **-co** arguments. For - example, to write a GeoPDF with the TerraGo format use - ``=PDF+cGEO_ENCODING=OGC_BP``. Notes: (1) If a tiff file (.tif) is - selected then we will write a GeoTiff image if the GMT projection - syntax translates into a PROJ syntax, otherwise a plain tiff file - is produced. (2) Any vector elements will be lost. - {B} - {CPT} - img_in : str - [**r**]. - GMT will automatically detect standard image files (Geotiff, TIFF, - JPG, PNG, GIF, etc.) and will read those via GDAL. For very obscure - image formats you may need to explicitly set ``img_in``, which - specifies that the grid is in fact an image file to be read via - GDAL. Append **r** to assign the region specified by ``region`` - to the image. For example, if you have used ``region='d'`` then the - image will be assigned a global domain. This mode allows you to - project a raw image (an image without referencing coordinates). - dpi : int - [**i**\|\ *dpi*]. - Sets the resolution of the projected grid that will be created if a - map projection other than Linear or Mercator was selected [100]. By - default, the projected grid will be of the same size (rows and - columns) as the input file. Specify **i** to use the PostScript - image operator to interpolate the image at the device resolution. - bit_color : str - *color*\ [**+b**\|\ **f**\]. - This parameter only applies when a resulting 1-bit image otherwise - would consist of only two colors: black (0) and white (255). If so, - this parameter will instead use the image as a transparent mask and - paint the mask with the given color. Append **+b** to paint the - background pixels (1) or **+f** for the foreground pixels - [Default is **+f**]. - shading : str - *intensfile*\|\ *intensity*\|\ *modifiers*. - Give the name of a grid file with intensities in the (-1,+1) range, - or a constant intensity to apply everywhere (affects the ambient - light). Alternatively, derive an intensity grid from the input data - grid via a call to ``grdgradient``; append **+a**\ *azimuth*, - **+n**\ *args*, and **+m**\ *ambient* to specify azimuth, - intensity, and ambient arguments for that module, or just give - **+d** to select the default arguments - [Default is **+a**\ -45\ **+nt**\ 1\ **+m**\ 0]. If you want a more - specific intensity scenario then run ``grdgradient`` separately first. - If we should derive intensities from another file than grid, specify - the file with suitable modifiers [Default is no illumination]. - {J} - monochrome : bool - Force conversion to monochrome image using the (television) YIQ - transformation. Cannot be used with ``nan_transparent``. - no_clip : bool - Do not clip the image at the map boundary (only relevant for - non-rectangular maps). - nan_transparent : bool - Make grid nodes with z = NaN transparent, using the color-masking - feature in PostScript Level 3 (the PS device must support PS Level - 3). - {R} - {V} - {XY} - {c} - {f} - {n} - {p} - {t} - {x} - """ - kwargs = self._preprocess(**kwargs) # pylint: disable=protected-access - with Session() as lib: - file_context = lib.virtualfile_from_data(check_kind="raster", data=grid) - with file_context as fname: - arg_str = " ".join([fname, build_arg_string(kwargs)]) - lib.call_module("grdimage", arg_str) +""" +grdimage - Plot grids or images. +""" +from pygmt.clib import Session +from pygmt.helpers import build_arg_string, fmt_docstring, kwargs_to_strings, use_alias + + +@fmt_docstring +@use_alias( + A="img_out", + B="frame", + C="cmap", + D="img_in", + E="dpi", + G="bit_color", + I="shading", + J="projection", + M="monochrome", + N="no_clip", + Q="nan_transparent", + R="region", + U="timestamp", + V="verbose", + X="xshift", + Y="yshift", + n="interpolation", + c="panel", + f="coltypes", + p="perspective", + t="transparency", + x="cores", +) +@kwargs_to_strings(R="sequence", c="sequence_comma", p="sequence") +def grdimage(self, grid, **kwargs): + r""" + Project and plot grids or images. + + Reads a 2-D grid file and produces a gray-shaded (or colored) map by + building a rectangular image and assigning pixels a gray-shade (or + color) based on the z-value and the CPT file. Optionally, illumination + may be added by providing a file with intensities in the (-1,+1) range + or instructions to derive intensities from the input data grid. Values + outside this range will be clipped. If GMT is built with GDAL support, + ``grid`` can be an image file (geo-referenced or not). In this case the + image can optionally be illuminated with the file provided via the + ``shading`` parameter. Here, if image has no coordinates then those of the + intensity file will be used. + + When using map projections, the grid is first resampled on a new + rectangular grid with the same dimensions. Higher resolution images can + be obtained by using the ``dpi`` parameter. To obtain the resampled value + (and hence shade or color) of each map pixel, its location is inversely + projected back onto the input grid after which a value is interpolated + between the surrounding input grid values. By default bi-cubic + interpolation is used. Aliasing is avoided by also forward projecting + the input grid nodes. If two or more nodes are projected onto the same + pixel, their average will dominate in the calculation of the pixel + value. Interpolation and aliasing is controlled with the + ``interpolation`` parameter. + + The ``region`` parameter can be used to select a map region larger or + smaller than that implied by the extent of the grid. + + Full parameter list at :gmt-docs:`grdimage.html` + + {aliases} + + Parameters + ---------- + grid : str or xarray.DataArray + The file name or a DataArray containing the input 2-D gridded data + set or image to be plotted (See GRID FILE FORMATS at + :gmt-docs:`grdimage.html#grid-file-formats`). + img_out : str + *out_img*\[=\ *driver*]. + Save an image in a raster format instead of PostScript. Use + extension .ppm for a Portable Pixel Map format which is the only + raster format GMT can natively write. For GMT installations + configured with GDAL support there are more choices: Append + *out_img* to select the image file name and extension. If the + extension is one of .bmp, .gif, .jpg, .png, or .tif then no driver + information is required. For other output formats you must append + the required GDAL driver. The *driver* is the driver code name used + by GDAL; see your GDAL installation's documentation for available + drivers. Append a **+c**\ *args* string where *args* is a list + of one or more concatenated number of GDAL **-co** arguments. For + example, to write a GeoPDF with the TerraGo format use + ``=PDF+cGEO_ENCODING=OGC_BP``. Notes: (1) If a tiff file (.tif) is + selected then we will write a GeoTiff image if the GMT projection + syntax translates into a PROJ syntax, otherwise a plain tiff file + is produced. (2) Any vector elements will be lost. + {B} + {CPT} + img_in : str + [**r**]. + GMT will automatically detect standard image files (Geotiff, TIFF, + JPG, PNG, GIF, etc.) and will read those via GDAL. For very obscure + image formats you may need to explicitly set ``img_in``, which + specifies that the grid is in fact an image file to be read via + GDAL. Append **r** to assign the region specified by ``region`` + to the image. For example, if you have used ``region='d'`` then the + image will be assigned a global domain. This mode allows you to + project a raw image (an image without referencing coordinates). + dpi : int + [**i**\|\ *dpi*]. + Sets the resolution of the projected grid that will be created if a + map projection other than Linear or Mercator was selected [100]. By + default, the projected grid will be of the same size (rows and + columns) as the input file. Specify **i** to use the PostScript + image operator to interpolate the image at the device resolution. + bit_color : str + *color*\ [**+b**\|\ **f**\]. + This parameter only applies when a resulting 1-bit image otherwise + would consist of only two colors: black (0) and white (255). If so, + this parameter will instead use the image as a transparent mask and + paint the mask with the given color. Append **+b** to paint the + background pixels (1) or **+f** for the foreground pixels + [Default is **+f**]. + shading : str + *intensfile*\|\ *intensity*\|\ *modifiers*. + Give the name of a grid file with intensities in the (-1,+1) range, + or a constant intensity to apply everywhere (affects the ambient + light). Alternatively, derive an intensity grid from the input data + grid via a call to ``grdgradient``; append **+a**\ *azimuth*, + **+n**\ *args*, and **+m**\ *ambient* to specify azimuth, + intensity, and ambient arguments for that module, or just give + **+d** to select the default arguments + [Default is **+a**\ -45\ **+nt**\ 1\ **+m**\ 0]. If you want a more + specific intensity scenario then run ``grdgradient`` separately first. + If we should derive intensities from another file than grid, specify + the file with suitable modifiers [Default is no illumination]. + {J} + monochrome : bool + Force conversion to monochrome image using the (television) YIQ + transformation. Cannot be used with ``nan_transparent``. + no_clip : bool + Do not clip the image at the map boundary (only relevant for + non-rectangular maps). + nan_transparent : bool + Make grid nodes with z = NaN transparent, using the color-masking + feature in PostScript Level 3 (the PS device must support PS Level + 3). + {R} + {V} + {XY} + {c} + {f} + {n} + {p} + {t} + {x} + """ + kwargs = self._preprocess(**kwargs) # pylint: disable=protected-access + with Session() as lib: + file_context = lib.virtualfile_from_data(check_kind="raster", data=grid) + with file_context as fname: + arg_str = " ".join([fname, build_arg_string(kwargs)]) + lib.call_module("grdimage", arg_str) diff --git a/pygmt/src/grdinfo.py b/pygmt/src/grdinfo.py index 44aea01f31a..c0f1c9be54d 100644 --- a/pygmt/src/grdinfo.py +++ b/pygmt/src/grdinfo.py @@ -1,120 +1,120 @@ -""" -grdinfo - Retrieve info about grid file. -""" -from pygmt.clib import Session -from pygmt.helpers import ( - GMTTempFile, - build_arg_string, - fmt_docstring, - kwargs_to_strings, - use_alias, -) - - -@fmt_docstring -@use_alias( - C="per_column", - D="tiles", - F="geographic", - I="spacing", - L="force_scan", - M="minmax_pos", - R="region", - T="nearest_multiple", - V="verbose", - f="coltypes", -) -@kwargs_to_strings(D="sequence", I="sequence", R="sequence") -def grdinfo(grid, **kwargs): - r""" - Get information about a grid. - - Can read the grid from a file or given as an xarray.DataArray grid. - - Full option list at :gmt-docs:`grdinfo.html` - - Parameters - ---------- - grid : str or xarray.DataArray - The file name of the input grid or the grid loaded as a DataArray. - This is the only required parameter. - {R} - per_column : str or bool - **n**\|\ **t**. - Formats the report using tab-separated fields on a single line. The - output is name *w e s n z0 z1 dx dy nx ny* [ *x0 y0 x1 y1* ] - [ *med scale* ] [ *mean std rms* ] [ *n_nan* ] *registration gtype*. - The data in brackets are outputted depending on the ``force_scan`` - and ``minmax_pos`` parameters. Use **t** to place file name at the end - of the output record or, **n** or ``True`` to only output numerical - columns. The registration is either 0 (gridline) or 1 (pixel), while - gtype is either 0 (Cartesian) or 1 (geographic). The default value is - ``False``. This cannot be called if ``geographic`` is also set. - tiles : str or list - *xoff*\ [/*yoff*][**+i**]. - Divide a single grid's domain (or the ``region`` domain, if no grid - given) into tiles of size dx times dy (set via ``spacing``). You can - specify overlap between tiles by appending *xoff*\ [/*yoff*]. If the - single grid is given you may use the modifier **+i** to ignore tiles - that have no data within each tile subregion. Default output is text - region strings. Use ``per_column`` to instead report four columns with - xmin xmax ymin ymax per tile, or use ``per_column="t"`` to also have - the region string appended as trailing text. - geographic : bool - Report grid domain and x/y-increments in world mapping format - The default value is ``False``. This cannot be called if - ``per_column`` is also set. - spacing : str or list - *dx*\ [/*dy*]\|\ **b**\|\ **i**\|\ **r**. - Report the min/max of the region to the nearest multiple of dx and dy, - and output this in the form w/e/s/n (unless ``per_column`` is set). To - report the actual grid region, append **r**. For a grid produced by - the img supplement (a Cartesian Mercator grid), the exact geographic - region is given with **i** (if not found then we return the actual - grid region instead). If no argument is given then we report the grid - increment in the form *xinc*\ [/*yinc*]. If **b** is given we write - each grid's bounding box polygon instead. Finally, if ``tiles`` is in - effect then *dx* and *dy* are the dimensions of the desired tiles. - force_scan : int or str - **0**\|\ **1**\|\ **2**\|\ **p**\|\ **a**. - - **0**\ : Report range of z after actually scanning the data, not just - reporting what the header says. - **1**\ : Report median and L1 scale of z (L1 scale = 1.4826 * Median - Absolute Deviation (MAD)). - **2**\ : Report mean, standard deviation, and root-mean-square (rms) - of z. - **p**\ : Report mode (LMS) and LMS scale of z. - **a**\ : Include all of the above. - minxmax_pos : bool - Include the x/y values at the location of the minimum and maximum - z-values. - nearest_multiple : str - [*dz*]\ [**+a**\ [*alpha*]]\ [**+s**]. - Determine min and max z-value. If *dz* is provided then we first round - these values off to multiples of *dz*. To exclude the two tails of the - distribution when determining the min and max you can add **+a** to - set the *alpha* value (in percent): We then sort the grid, exclude the - data in the 0.5*\ *alpha* and 100 - 0.5*\ *alpha* tails, and revise - the min and max. To force a symmetrical range about zero, using - minus/plus the max absolute value of the two extremes, append **+s**\ . - We report the result via the text string *zmin/zmax* or *zmin/zmax/dz* - (if *dz* was given) as expected by :meth:`pygmt.makecpt`. - {V} - {f} - - Returns - ------- - info : str - A string with information about the grid. - """ - with GMTTempFile() as outfile: - with Session() as lib: - file_context = lib.virtualfile_from_data(check_kind="raster", data=grid) - with file_context as infile: - arg_str = " ".join( - [infile, build_arg_string(kwargs), "->" + outfile.name] - ) - lib.call_module("grdinfo", arg_str) - result = outfile.read() - return result +""" +grdinfo - Retrieve info about grid file. +""" +from pygmt.clib import Session +from pygmt.helpers import ( + GMTTempFile, + build_arg_string, + fmt_docstring, + kwargs_to_strings, + use_alias, +) + + +@fmt_docstring +@use_alias( + C="per_column", + D="tiles", + F="geographic", + I="spacing", + L="force_scan", + M="minmax_pos", + R="region", + T="nearest_multiple", + V="verbose", + f="coltypes", +) +@kwargs_to_strings(D="sequence", I="sequence", R="sequence") +def grdinfo(grid, **kwargs): + r""" + Get information about a grid. + + Can read the grid from a file or given as an xarray.DataArray grid. + + Full option list at :gmt-docs:`grdinfo.html` + + Parameters + ---------- + grid : str or xarray.DataArray + The file name of the input grid or the grid loaded as a DataArray. + This is the only required parameter. + {R} + per_column : str or bool + **n**\|\ **t**. + Formats the report using tab-separated fields on a single line. The + output is name *w e s n z0 z1 dx dy nx ny* [ *x0 y0 x1 y1* ] + [ *med scale* ] [ *mean std rms* ] [ *n_nan* ] *registration gtype*. + The data in brackets are outputted depending on the ``force_scan`` + and ``minmax_pos`` parameters. Use **t** to place file name at the end + of the output record or, **n** or ``True`` to only output numerical + columns. The registration is either 0 (gridline) or 1 (pixel), while + gtype is either 0 (Cartesian) or 1 (geographic). The default value is + ``False``. This cannot be called if ``geographic`` is also set. + tiles : str or list + *xoff*\ [/*yoff*][**+i**]. + Divide a single grid's domain (or the ``region`` domain, if no grid + given) into tiles of size dx times dy (set via ``spacing``). You can + specify overlap between tiles by appending *xoff*\ [/*yoff*]. If the + single grid is given you may use the modifier **+i** to ignore tiles + that have no data within each tile subregion. Default output is text + region strings. Use ``per_column`` to instead report four columns with + xmin xmax ymin ymax per tile, or use ``per_column="t"`` to also have + the region string appended as trailing text. + geographic : bool + Report grid domain and x/y-increments in world mapping format + The default value is ``False``. This cannot be called if + ``per_column`` is also set. + spacing : str or list + *dx*\ [/*dy*]\|\ **b**\|\ **i**\|\ **r**. + Report the min/max of the region to the nearest multiple of dx and dy, + and output this in the form w/e/s/n (unless ``per_column`` is set). To + report the actual grid region, append **r**. For a grid produced by + the img supplement (a Cartesian Mercator grid), the exact geographic + region is given with **i** (if not found then we return the actual + grid region instead). If no argument is given then we report the grid + increment in the form *xinc*\ [/*yinc*]. If **b** is given we write + each grid's bounding box polygon instead. Finally, if ``tiles`` is in + effect then *dx* and *dy* are the dimensions of the desired tiles. + force_scan : int or str + **0**\|\ **1**\|\ **2**\|\ **p**\|\ **a**. + + **0**\ : Report range of z after actually scanning the data, not just + reporting what the header says. + **1**\ : Report median and L1 scale of z (L1 scale = 1.4826 * Median + Absolute Deviation (MAD)). + **2**\ : Report mean, standard deviation, and root-mean-square (rms) + of z. + **p**\ : Report mode (LMS) and LMS scale of z. + **a**\ : Include all of the above. + minxmax_pos : bool + Include the x/y values at the location of the minimum and maximum + z-values. + nearest_multiple : str + [*dz*]\ [**+a**\ [*alpha*]]\ [**+s**]. + Determine min and max z-value. If *dz* is provided then we first round + these values off to multiples of *dz*. To exclude the two tails of the + distribution when determining the min and max you can add **+a** to + set the *alpha* value (in percent): We then sort the grid, exclude the + data in the 0.5*\ *alpha* and 100 - 0.5*\ *alpha* tails, and revise + the min and max. To force a symmetrical range about zero, using + minus/plus the max absolute value of the two extremes, append **+s**\ . + We report the result via the text string *zmin/zmax* or *zmin/zmax/dz* + (if *dz* was given) as expected by :meth:`pygmt.makecpt`. + {V} + {f} + + Returns + ------- + info : str + A string with information about the grid. + """ + with GMTTempFile() as outfile: + with Session() as lib: + file_context = lib.virtualfile_from_data(check_kind="raster", data=grid) + with file_context as infile: + arg_str = " ".join( + [infile, build_arg_string(kwargs), "->" + outfile.name] + ) + lib.call_module("grdinfo", arg_str) + result = outfile.read() + return result diff --git a/pygmt/src/grdtrack.py b/pygmt/src/grdtrack.py index f67cc842ba7..3ba4168336d 100644 --- a/pygmt/src/grdtrack.py +++ b/pygmt/src/grdtrack.py @@ -1,108 +1,108 @@ -""" -grdtrack - Sample grids at specified (x,y) locations. -""" -import pandas as pd -from pygmt.clib import Session -from pygmt.exceptions import GMTInvalidInput -from pygmt.helpers import ( - GMTTempFile, - build_arg_string, - data_kind, - dummy_context, - fmt_docstring, - use_alias, -) - - -@fmt_docstring -@use_alias(V="verbose", f="coltypes", n="interpolation") -def grdtrack(points, grid, newcolname=None, outfile=None, **kwargs): - """ - Sample grids at specified (x,y) locations. - - Grdtrack reads one or more grid files and a table with (x,y) [or (lon,lat)] - positions in the first two columns (more columns may be present). It - interpolates the grid(s) at the positions in the table and writes out the - table with the interpolated values added as (one or more) new columns. A - bicubic [Default], bilinear, B-spline or nearest-neighbor interpolation is - used, requiring boundary conditions at the limits of the region (see - ``interpolation``; Default uses “natural” conditions (second partial - derivative normal to edge is zero) unless the grid is automatically - recognized as periodic.) - - Full option list at :gmt-docs:`grdtrack.html` - - {aliases} - - Parameters - ---------- - points : pandas.DataFrame or str - Either a table with (x, y) or (lon, lat) values in the first two - columns, or a filename (e.g. csv, txt format). More columns may be - present. - - grid : xarray.DataArray or str - Gridded array from which to sample values from, or a filename (netcdf - format). - - newcolname : str - Required if ``points`` is a :class:`pandas.DataFrame`. The name for the - new column in the track :class:`pandas.DataFrame` table where the - sampled values will be placed. - - outfile : str - Required if ``points`` is a file. The file name for the output ASCII - file. - - {V} - {f} - {n} - - Returns - ------- - track: pandas.DataFrame or None - Return type depends on whether the ``outfile`` parameter is set: - - - :class:`pandas.DataFrame` table with (x, y, ..., newcolname) if - ``outfile`` is not set - - None if ``outfile`` is set (track output will be stored in file set - by ``outfile``) - """ - - with GMTTempFile(suffix=".csv") as tmpfile: - with Session() as lib: - # Store the pandas.DataFrame points table in virtualfile - if data_kind(points) == "matrix": - if newcolname is None: - raise GMTInvalidInput("Please pass in a str to 'newcolname'") - table_context = lib.virtualfile_from_matrix(points.values) - elif data_kind(points) == "file": - if outfile is None: - raise GMTInvalidInput("Please pass in a str to 'outfile'") - table_context = dummy_context(points) - else: - raise GMTInvalidInput(f"Unrecognized data type {type(points)}") - - # Store the xarray.DataArray grid in virtualfile - grid_context = lib.virtualfile_from_data(check_kind="raster", data=grid) - - # Run grdtrack on the temporary (csv) points table - # and (netcdf) grid virtualfile - with table_context as csvfile: - with grid_context as grdfile: - kwargs.update({"G": grdfile}) - if outfile is None: # Output to tmpfile if outfile is not set - outfile = tmpfile.name - arg_str = " ".join( - [csvfile, build_arg_string(kwargs), "->" + outfile] - ) - lib.call_module(module="grdtrack", args=arg_str) - - # Read temporary csv output to a pandas table - if outfile == tmpfile.name: # if user did not set outfile, return pd.DataFrame - column_names = points.columns.to_list() + [newcolname] - result = pd.read_csv(tmpfile.name, sep="\t", names=column_names) - elif outfile != tmpfile.name: # return None if outfile set, output in outfile - result = None - - return result +""" +grdtrack - Sample grids at specified (x,y) locations. +""" +import pandas as pd +from pygmt.clib import Session +from pygmt.exceptions import GMTInvalidInput +from pygmt.helpers import ( + GMTTempFile, + build_arg_string, + data_kind, + dummy_context, + fmt_docstring, + use_alias, +) + + +@fmt_docstring +@use_alias(V="verbose", f="coltypes", n="interpolation") +def grdtrack(points, grid, newcolname=None, outfile=None, **kwargs): + """ + Sample grids at specified (x,y) locations. + + Grdtrack reads one or more grid files and a table with (x,y) [or (lon,lat)] + positions in the first two columns (more columns may be present). It + interpolates the grid(s) at the positions in the table and writes out the + table with the interpolated values added as (one or more) new columns. A + bicubic [Default], bilinear, B-spline or nearest-neighbor interpolation is + used, requiring boundary conditions at the limits of the region (see + ``interpolation``; Default uses “natural” conditions (second partial + derivative normal to edge is zero) unless the grid is automatically + recognized as periodic.) + + Full option list at :gmt-docs:`grdtrack.html` + + {aliases} + + Parameters + ---------- + points : pandas.DataFrame or str + Either a table with (x, y) or (lon, lat) values in the first two + columns, or a filename (e.g. csv, txt format). More columns may be + present. + + grid : xarray.DataArray or str + Gridded array from which to sample values from, or a filename (netcdf + format). + + newcolname : str + Required if ``points`` is a :class:`pandas.DataFrame`. The name for the + new column in the track :class:`pandas.DataFrame` table where the + sampled values will be placed. + + outfile : str + Required if ``points`` is a file. The file name for the output ASCII + file. + + {V} + {f} + {n} + + Returns + ------- + track: pandas.DataFrame or None + Return type depends on whether the ``outfile`` parameter is set: + + - :class:`pandas.DataFrame` table with (x, y, ..., newcolname) if + ``outfile`` is not set + - None if ``outfile`` is set (track output will be stored in file set + by ``outfile``) + """ + + with GMTTempFile(suffix=".csv") as tmpfile: + with Session() as lib: + # Store the pandas.DataFrame points table in virtualfile + if data_kind(points) == "matrix": + if newcolname is None: + raise GMTInvalidInput("Please pass in a str to 'newcolname'") + table_context = lib.virtualfile_from_matrix(points.values) + elif data_kind(points) == "file": + if outfile is None: + raise GMTInvalidInput("Please pass in a str to 'outfile'") + table_context = dummy_context(points) + else: + raise GMTInvalidInput(f"Unrecognized data type {type(points)}") + + # Store the xarray.DataArray grid in virtualfile + grid_context = lib.virtualfile_from_data(check_kind="raster", data=grid) + + # Run grdtrack on the temporary (csv) points table + # and (netcdf) grid virtualfile + with table_context as csvfile: + with grid_context as grdfile: + kwargs.update({"G": grdfile}) + if outfile is None: # Output to tmpfile if outfile is not set + outfile = tmpfile.name + arg_str = " ".join( + [csvfile, build_arg_string(kwargs), "->" + outfile] + ) + lib.call_module(module="grdtrack", args=arg_str) + + # Read temporary csv output to a pandas table + if outfile == tmpfile.name: # if user did not set outfile, return pd.DataFrame + column_names = points.columns.to_list() + [newcolname] + result = pd.read_csv(tmpfile.name, sep="\t", names=column_names) + elif outfile != tmpfile.name: # return None if outfile set, output in outfile + result = None + + return result diff --git a/pygmt/src/grdview.py b/pygmt/src/grdview.py index 0810a2a06bd..f7454f7b025 100644 --- a/pygmt/src/grdview.py +++ b/pygmt/src/grdview.py @@ -1,132 +1,132 @@ -""" -grdview - Create a three-dimensional plot from a grid. -""" -import contextlib - -from pygmt.clib import Session -from pygmt.exceptions import GMTInvalidInput -from pygmt.helpers import ( - build_arg_string, - data_kind, - fmt_docstring, - kwargs_to_strings, - use_alias, -) - - -@fmt_docstring -@use_alias( - R="region", - J="projection", - Jz="zscale", - JZ="zsize", - B="frame", - C="cmap", - G="drapegrid", - N="plane", - Q="surftype", - Wc="contourpen", - Wm="meshpen", - Wf="facadepen", - I="shading", - V="verbose", - X="xshift", - Y="yshift", - c="panel", - f="coltypes", - p="perspective", - t="transparency", -) -@kwargs_to_strings(R="sequence", c="sequence_comma", p="sequence") -def grdview(self, grid, **kwargs): - r""" - Create 3-D perspective image or surface mesh from a grid. - - Reads a 2-D grid file and produces a 3-D perspective plot by drawing a - mesh, painting a colored/gray-shaded surface made up of polygons, or by - scanline conversion of these polygons to a raster image. Options - include draping a data set on top of a surface, plotting of contours on - top of the surface, and apply artificial illumination based on - intensities provided in a separate grid file. - - Full option list at :gmt-docs:`grdview.html` - - {aliases} - - Parameters - ---------- - grid : str or xarray.DataArray - The file name of the input relief grid or the grid loaded as a - DataArray. - zscale/zsize : float or str - Set z-axis scaling or z-axis size. - {B} - cmap : str - The name of the color palette table to use. - drapegrid : str or xarray.DataArray - The file name or a DataArray of the image grid to be draped on top - of the relief provided by grid. [Default determines colors from - grid]. Note that ``zscale`` and ``plane`` always refers to the grid. - The drapegrid only provides the information pertaining to colors, which - (if drapegrid is a grid) will be looked-up via the CPT (see ``cmap``). - plane : float or str - *level*\ [**+g**\ *fill*]. - Draws a plane at this z-level. If the optional color is provided - via the **+g** modifier, and the projection is not oblique, the frontal - facade between the plane and the data perimeter is colored. - surftype : str - Specifies 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 - drawn on top of the surface. - - **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**. - contourpen : str - Draw contour lines on top of surface or mesh (not image). Append - pen attributes used for the contours. - meshpen : str - Sets the pen attributes used for the mesh. You must also select - ``surftype`` of **m** or **sm** for meshlines to be drawn. - facadepen :str - Sets the pen attributes used for the facade. You must also select - ``plane`` for the facade outline to be drawn. - shading : str - Provide the name of a grid file with intensities in the (-1,+1) - range, or a constant intensity to apply everywhere (affects the - ambient light). Alternatively, derive an intensity grid from the - input data grid reliefgrid via a call to ``grdgradient``; append - **+a**\ *azimuth*, **+n**\ *args*, and **+m**\ *ambient* to specify - azimuth, intensity, and ambient arguments for that module, or just give - **+d** to select the default arguments - [Default is **+a**\ -45\ **+nt**\ 1\ **+m**\ 0]. - {V} - {XY} - {c} - {f} - {p} - {t} - """ - kwargs = self._preprocess(**kwargs) # pylint: disable=protected-access - with Session() as lib: - file_context = lib.virtualfile_from_data(check_kind="raster", data=grid) - - with contextlib.ExitStack() as stack: - if "G" in kwargs: # deal with kwargs["G"] if drapegrid is xr.DataArray - drapegrid = kwargs["G"] - if data_kind(drapegrid) in ("file", "grid"): - if data_kind(drapegrid) == "grid": - drape_context = lib.virtualfile_from_grid(drapegrid) - kwargs["G"] = stack.enter_context(drape_context) - else: - raise GMTInvalidInput( - f"Unrecognized data type for drapegrid: {type(drapegrid)}" - ) - fname = stack.enter_context(file_context) - arg_str = " ".join([fname, build_arg_string(kwargs)]) - lib.call_module("grdview", arg_str) +""" +grdview - Create a three-dimensional plot from a grid. +""" +import contextlib + +from pygmt.clib import Session +from pygmt.exceptions import GMTInvalidInput +from pygmt.helpers import ( + build_arg_string, + data_kind, + fmt_docstring, + kwargs_to_strings, + use_alias, +) + + +@fmt_docstring +@use_alias( + R="region", + J="projection", + Jz="zscale", + JZ="zsize", + B="frame", + C="cmap", + G="drapegrid", + N="plane", + Q="surftype", + Wc="contourpen", + Wm="meshpen", + Wf="facadepen", + I="shading", + V="verbose", + X="xshift", + Y="yshift", + c="panel", + f="coltypes", + p="perspective", + t="transparency", +) +@kwargs_to_strings(R="sequence", c="sequence_comma", p="sequence") +def grdview(self, grid, **kwargs): + r""" + Create 3-D perspective image or surface mesh from a grid. + + Reads a 2-D grid file and produces a 3-D perspective plot by drawing a + mesh, painting a colored/gray-shaded surface made up of polygons, or by + scanline conversion of these polygons to a raster image. Options + include draping a data set on top of a surface, plotting of contours on + top of the surface, and apply artificial illumination based on + intensities provided in a separate grid file. + + Full option list at :gmt-docs:`grdview.html` + + {aliases} + + Parameters + ---------- + grid : str or xarray.DataArray + The file name of the input relief grid or the grid loaded as a + DataArray. + zscale/zsize : float or str + Set z-axis scaling or z-axis size. + {B} + cmap : str + The name of the color palette table to use. + drapegrid : str or xarray.DataArray + The file name or a DataArray of the image grid to be draped on top + of the relief provided by grid. [Default determines colors from + grid]. Note that ``zscale`` and ``plane`` always refers to the grid. + The drapegrid only provides the information pertaining to colors, which + (if drapegrid is a grid) will be looked-up via the CPT (see ``cmap``). + plane : float or str + *level*\ [**+g**\ *fill*]. + Draws a plane at this z-level. If the optional color is provided + via the **+g** modifier, and the projection is not oblique, the frontal + facade between the plane and the data perimeter is colored. + surftype : str + Specifies 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 + drawn on top of the surface. + - **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**. + contourpen : str + Draw contour lines on top of surface or mesh (not image). Append + pen attributes used for the contours. + meshpen : str + Sets the pen attributes used for the mesh. You must also select + ``surftype`` of **m** or **sm** for meshlines to be drawn. + facadepen :str + Sets the pen attributes used for the facade. You must also select + ``plane`` for the facade outline to be drawn. + shading : str + Provide the name of a grid file with intensities in the (-1,+1) + range, or a constant intensity to apply everywhere (affects the + ambient light). Alternatively, derive an intensity grid from the + input data grid reliefgrid via a call to ``grdgradient``; append + **+a**\ *azimuth*, **+n**\ *args*, and **+m**\ *ambient* to specify + azimuth, intensity, and ambient arguments for that module, or just give + **+d** to select the default arguments + [Default is **+a**\ -45\ **+nt**\ 1\ **+m**\ 0]. + {V} + {XY} + {c} + {f} + {p} + {t} + """ + kwargs = self._preprocess(**kwargs) # pylint: disable=protected-access + with Session() as lib: + file_context = lib.virtualfile_from_data(check_kind="raster", data=grid) + + with contextlib.ExitStack() as stack: + if "G" in kwargs: # deal with kwargs["G"] if drapegrid is xr.DataArray + drapegrid = kwargs["G"] + if data_kind(drapegrid) in ("file", "grid"): + if data_kind(drapegrid) == "grid": + drape_context = lib.virtualfile_from_grid(drapegrid) + kwargs["G"] = stack.enter_context(drape_context) + else: + raise GMTInvalidInput( + f"Unrecognized data type for drapegrid: {type(drapegrid)}" + ) + fname = stack.enter_context(file_context) + arg_str = " ".join([fname, build_arg_string(kwargs)]) + lib.call_module("grdview", arg_str) diff --git a/pygmt/src/image.py b/pygmt/src/image.py index 769f18835bc..24822132bc9 100644 --- a/pygmt/src/image.py +++ b/pygmt/src/image.py @@ -1,66 +1,66 @@ -""" -image - Plot an image. -""" -from pygmt.clib import Session -from pygmt.helpers import build_arg_string, fmt_docstring, kwargs_to_strings, use_alias - - -@fmt_docstring -@use_alias( - R="region", - J="projection", - D="position", - F="box", - M="monochrome", - V="verbose", - X="xshift", - Y="yshift", - c="panel", - p="perspective", - t="transparency", -) -@kwargs_to_strings(R="sequence", c="sequence_comma", p="sequence") -def image(self, imagefile, **kwargs): - r""" - Place images or EPS files on maps. - - Reads an Encapsulated PostScript file or a raster image file and plots - it on a map. - - Full option list at :gmt-docs:`image.html` - - {aliases} - - Parameters - ---------- - imagefile : str - This must be an Encapsulated PostScript (EPS) file or a raster - image. An EPS file must contain an appropriate BoundingBox. A - raster file can have a depth of 1, 8, 24, or 32 bits and is read - via GDAL. Note: If GDAL was not configured during GMT installation - then only EPS files are supported. - {J} - {R} - position : str - [**g**\|\ **j**\|\ **J**\|\ **n**\|\ **x**]\ *refpoint*\ **+r**\ *dpi*\ - **+w**\ [**-**]\ *width*\ [/*height*]\ [**+j**\ *justify*]\ - [**+n**\ *nx*\ [/*ny*] ]\ [**+o**\ *dx*\ [/*dy*]]. - Sets reference point on the map for the image. - box : bool or str - [**+c**\ *clearances*][**+g**\ *fill*][**+i**\ [[*gap*/]\ *pen*]]\ - [**+p**\ [*pen*]][**+r**\ [*radius*]][**+s**\ [[*dx*/*dy*/][*shade*]]]. - Without further arguments, draws a rectangular border around the image - using :gmt-term:`MAP_FRAME_PEN`. - monochrome : bool - Convert color image to monochrome grayshades using the (television) - YIQ-transformation. - {V} - {XY} - {c} - {p} - {t} - """ - kwargs = self._preprocess(**kwargs) # pylint: disable=protected-access - with Session() as lib: - arg_str = " ".join([imagefile, build_arg_string(kwargs)]) - lib.call_module("image", arg_str) +""" +image - Plot an image. +""" +from pygmt.clib import Session +from pygmt.helpers import build_arg_string, fmt_docstring, kwargs_to_strings, use_alias + + +@fmt_docstring +@use_alias( + R="region", + J="projection", + D="position", + F="box", + M="monochrome", + V="verbose", + X="xshift", + Y="yshift", + c="panel", + p="perspective", + t="transparency", +) +@kwargs_to_strings(R="sequence", c="sequence_comma", p="sequence") +def image(self, imagefile, **kwargs): + r""" + Place images or EPS files on maps. + + Reads an Encapsulated PostScript file or a raster image file and plots + it on a map. + + Full option list at :gmt-docs:`image.html` + + {aliases} + + Parameters + ---------- + imagefile : str + This must be an Encapsulated PostScript (EPS) file or a raster + image. An EPS file must contain an appropriate BoundingBox. A + raster file can have a depth of 1, 8, 24, or 32 bits and is read + via GDAL. Note: If GDAL was not configured during GMT installation + then only EPS files are supported. + {J} + {R} + position : str + [**g**\|\ **j**\|\ **J**\|\ **n**\|\ **x**]\ *refpoint*\ **+r**\ *dpi*\ + **+w**\ [**-**]\ *width*\ [/*height*]\ [**+j**\ *justify*]\ + [**+n**\ *nx*\ [/*ny*] ]\ [**+o**\ *dx*\ [/*dy*]]. + Sets reference point on the map for the image. + box : bool or str + [**+c**\ *clearances*][**+g**\ *fill*][**+i**\ [[*gap*/]\ *pen*]]\ + [**+p**\ [*pen*]][**+r**\ [*radius*]][**+s**\ [[*dx*/*dy*/][*shade*]]]. + Without further arguments, draws a rectangular border around the image + using :gmt-term:`MAP_FRAME_PEN`. + monochrome : bool + Convert color image to monochrome grayshades using the (television) + YIQ-transformation. + {V} + {XY} + {c} + {p} + {t} + """ + kwargs = self._preprocess(**kwargs) # pylint: disable=protected-access + with Session() as lib: + arg_str = " ".join([imagefile, build_arg_string(kwargs)]) + lib.call_module("image", arg_str) diff --git a/pygmt/src/info.py b/pygmt/src/info.py index 34a7a59787e..337999df533 100644 --- a/pygmt/src/info.py +++ b/pygmt/src/info.py @@ -1,83 +1,83 @@ -""" -info - Get information about data tables. -""" -import numpy as np -from pygmt.clib import Session -from pygmt.helpers import GMTTempFile, build_arg_string, fmt_docstring, use_alias - - -@fmt_docstring -@use_alias(C="per_column", I="spacing", T="nearest_multiple", V="verbose", f="coltypes") -def info(table, **kwargs): - r""" - Get information about data tables. - - Reads from files and finds the extreme values in each of the columns - reported as min/max pairs. It recognizes NaNs and will print warnings if - the number of columns vary from record to record. As an option, it will - find the extent of the first two columns rounded up and down to the nearest - multiple of the supplied increments given by ``spacing``. Such output will - be in a numpy.ndarray form [*w*, *e*, *s*, *n*], which can be used - directly as the ``region`` parameter for other modules (hence only *dx* - and *dy* are needed). If the ``per_column`` parameter is combined with - ``spacing``, then the numpy.ndarray output will be rounded up/down for as - many columns as there are increments provided in ``spacing``. A similar - parameter ``nearest_multiple`` will provide a numpy.ndarray in the form - of [*zmin*, *zmax*, *dz*] for makecpt. - - Full option list at :gmt-docs:`gmtinfo.html` - - {aliases} - - Parameters - ---------- - table : str or np.ndarray or pandas.DataFrame or xarray.Dataset - Pass in either a file name to an ASCII data table, a 1D/2D numpy array, - a pandas dataframe, or an xarray dataset made up of 1D xarray.DataArray - data variables. - per_column : bool - Report the min/max values per column in separate columns. - spacing : str - [**b**\|\ **p**\|\ **f**\|\ **s**]\ *dx*\[/*dy*\[/*dz*...]]. - Report the min/max of the first n columns to the nearest multiple of - the provided increments and output results in the form - ``[w, e, s, n]``. - nearest_multiple : str - **dz**\[\ **+c**\ *col*]. - Report the min/max of the first (0'th) column to the nearest multiple - of dz and output this in the form ``[zmin, zmax, dz]``. - - {V} - {f} - - Returns - ------- - output : np.ndarray or str - Return type depends on whether any of the ``per_column``, - ``spacing``, or ``nearest_multiple`` parameters are set. - - - :class:`numpy.ndarray` if either of the above parameters are used. - - str if none of the above parameters are used. - """ - with Session() as lib: - file_context = lib.virtualfile_from_data(data=table) - with GMTTempFile() as tmpfile: - with file_context as fname: - arg_str = " ".join( - [fname, build_arg_string(kwargs), "->" + tmpfile.name] - ) - lib.call_module("info", arg_str) - result = tmpfile.read() - - if any(arg in kwargs for arg in ["C", "I", "T"]): - # Converts certain output types into a numpy array - # instead of a raw string that is less useful. - if result.startswith(("-R", "-T")): # e.g. -R0/1/2/3 or -T0/9/1 - result = result[2:].replace("/", " ") - try: - result = np.loadtxt(result.splitlines()) - except ValueError: - # Load non-numerical outputs in str type, e.g. for datetime - result = np.loadtxt(result.splitlines(), dtype="str") - - return result +""" +info - Get information about data tables. +""" +import numpy as np +from pygmt.clib import Session +from pygmt.helpers import GMTTempFile, build_arg_string, fmt_docstring, use_alias + + +@fmt_docstring +@use_alias(C="per_column", I="spacing", T="nearest_multiple", V="verbose", f="coltypes") +def info(table, **kwargs): + r""" + Get information about data tables. + + Reads from files and finds the extreme values in each of the columns + reported as min/max pairs. It recognizes NaNs and will print warnings if + the number of columns vary from record to record. As an option, it will + find the extent of the first two columns rounded up and down to the nearest + multiple of the supplied increments given by ``spacing``. Such output will + be in a numpy.ndarray form [*w*, *e*, *s*, *n*], which can be used + directly as the ``region`` parameter for other modules (hence only *dx* + and *dy* are needed). If the ``per_column`` parameter is combined with + ``spacing``, then the numpy.ndarray output will be rounded up/down for as + many columns as there are increments provided in ``spacing``. A similar + parameter ``nearest_multiple`` will provide a numpy.ndarray in the form + of [*zmin*, *zmax*, *dz*] for makecpt. + + Full option list at :gmt-docs:`gmtinfo.html` + + {aliases} + + Parameters + ---------- + table : str or np.ndarray or pandas.DataFrame or xarray.Dataset + Pass in either a file name to an ASCII data table, a 1D/2D numpy array, + a pandas dataframe, or an xarray dataset made up of 1D xarray.DataArray + data variables. + per_column : bool + Report the min/max values per column in separate columns. + spacing : str + [**b**\|\ **p**\|\ **f**\|\ **s**]\ *dx*\[/*dy*\[/*dz*...]]. + Report the min/max of the first n columns to the nearest multiple of + the provided increments and output results in the form + ``[w, e, s, n]``. + nearest_multiple : str + **dz**\[\ **+c**\ *col*]. + Report the min/max of the first (0'th) column to the nearest multiple + of dz and output this in the form ``[zmin, zmax, dz]``. + + {V} + {f} + + Returns + ------- + output : np.ndarray or str + Return type depends on whether any of the ``per_column``, + ``spacing``, or ``nearest_multiple`` parameters are set. + + - :class:`numpy.ndarray` if either of the above parameters are used. + - str if none of the above parameters are used. + """ + with Session() as lib: + file_context = lib.virtualfile_from_data(data=table) + with GMTTempFile() as tmpfile: + with file_context as fname: + arg_str = " ".join( + [fname, build_arg_string(kwargs), "->" + tmpfile.name] + ) + lib.call_module("info", arg_str) + result = tmpfile.read() + + if any(arg in kwargs for arg in ["C", "I", "T"]): + # Converts certain output types into a numpy array + # instead of a raw string that is less useful. + if result.startswith(("-R", "-T")): # e.g. -R0/1/2/3 or -T0/9/1 + result = result[2:].replace("/", " ") + try: + result = np.loadtxt(result.splitlines()) + except ValueError: + # Load non-numerical outputs in str type, e.g. for datetime + result = np.loadtxt(result.splitlines(), dtype="str") + + return result diff --git a/pygmt/src/inset.py b/pygmt/src/inset.py index 45fabacce53..e8fac1bea8e 100644 --- a/pygmt/src/inset.py +++ b/pygmt/src/inset.py @@ -1,132 +1,132 @@ -""" -inset - Create inset figures. -""" -import contextlib - -from pygmt.clib import Session -from pygmt.helpers import build_arg_string, fmt_docstring, kwargs_to_strings, use_alias - - -@fmt_docstring -@contextlib.contextmanager -@use_alias(D="position", F="box", M="margin", N="no_clip", V="verbose") -@kwargs_to_strings(D="sequence", M="sequence") -def inset(self, **kwargs): - r""" - Create an inset figure to be placed within a larger figure. - - This function sets the position, frame, and margins for a smaller figure - inside of the larger figure. Plotting functions that are called within the - context manager are added to the inset figure. - - Full option list at :gmt-docs:`inset.html` - - {aliases} - - Parameters - ---------- - position : str or list - *xmin/xmax/ymin/ymax*\ [**+r**][**+u**\ *unit*]] \ - | [**g**\|\ **j**\|\ **J**\|\ **n**\|\ **x**]\ *refpoint*\ - **+w**\ *width*\ [/*height*][**+j**\ *justify*]\ - [**+o**\ *dx*\ [/*dy*]]. - - *This is the only required parameter.* - Define the map inset rectangle on the map. Specify the rectangle - in one of three ways: - - Append **g**\ *lon*/*lat* for map (user) coordinates, - **j**\ *code* or **J**\ *code* for setting the *refpoint* via a - 2-char justification code \ that refers to the (invisible) - projected map bounding box, **n**\ *xn*/*yn* for normalized (0-1) - bounding box coordinates, or **x**\ *x*/*y* for plot - coordinates (inches, cm, points, append unit). - All but **x** requires both ``region`` and ``projection`` to be - specified. You can offset the reference point via - **+o**\ *dx*/*dy* in the direction implied by *code* or - **+j**\ *justify*. - - Alternatively, give *west/east/south/north* of geographic - rectangle bounded by parallels and meridians; append **+r** if the - coordinates instead are the lower left and upper right corners of - the desired rectangle. (Or, give *xmin/xmax/ymin/ymax* of bounding - rectangle in projected coordinates and optionally - append **+u**\ *unit* [Default coordinate unit is meter (e)]. - - Append **+w**\ *width*\ [/*height*] of bounding rectangle or box - in plot coordinates (inches, cm, etc.). By default, the anchor - point on the scale is assumed to be the bottom left corner (**BL**), - but this can be changed by appending **+j** followed by a 2-char - justification code *justify*. - **Note**: If **j** is used then *justify* defaults to the same - as *refpoint*, if **J** is used then *justify* defaults to the - mirror opposite of *refpoint*. Specify inset box attributes via - the ``box`` parameter [Default is outline only]. - box : str or bool - [**+c**\ *clearances*][**+g**\ *fill*][**+i**\ [[*gap*/]\ - *pen*]][**+p**\ [*pen*]][**+r**\ [*radius*]][**+s**\ - [[*dx*/*dy*/][*shade*]]]. - - If passed ``True``, this draws a rectangular box around the map - inset using the default pen; specify a different pen - with **+p**\ *pen*. Add **+g**\ *fill* to fill the logo box - [Default is no fill]. - Append **+c**\ *clearance* where *clearance* is either - *gap*, *xgap*\ /\ *ygap*, or *lgap*\ /\ *rgap*\ /\ *bgap*\ /\ - *tgap* where these items are uniform, separate in x- and - y-direction, or individual side spacings between logo and border. - Append **+i** to draw a secondary, inner border as well. We use a - uniform *gap* between borders of 2\ **p** and the default pen - unless other values are specified. Append **+r** to draw rounded - rectangular borders instead, with a 6p corner radius. You - can override this radius by appending another value. Append - **+s** to draw an offset background shaded region. Here, *dx*/*dy* - indicates the shift relative to the foreground frame - [4p/-4p] and ``shade`` sets the fill style to use for - shading [Default is gray50]. - margin : int or str or list - This is clearance that is added around the inside of the inset. - Plotting will take place within the inner region only. The margins - can be a single value, a pair of values separated (for setting - separate horizontal and vertical margins), or the full set of four - margins (for setting separate left, right, bottom, and top - margins). When passing multiple values, it can be either a list or - a string with the values separated by forward - slashes [Default is no margins]. - no_clip : bool - Do NOT clip features extruding outside map inset boundaries [Default - is clip]. - {V} - - Examples - -------- - >>> import pygmt - >>> - >>> # Create the larger figure - >>> fig = pygmt.Figure() - >>> fig.coast(region="MG+r2", water="lightblue", shorelines="thin") - >>> # Use a "with" statement to initialize the inset context manager - >>> # Setting the position to top left and a width of 3.5 centimeters - >>> with fig.inset(position="jTL+w3.5c+o0.2c", margin=0, box="+pgreen"): - ... # Map elements under the "with" statement are plotted in the inset - ... fig.coast( - ... region="g", - ... projection="G47/-20/3.5c", - ... land="gray", - ... water="white", - ... dcw="MG+gred", - ... ) - ... - >>> # Map elements outside the "with" block are plotted in the main figure - >>> fig.logo(position="jBR+o0.2c+w3c") - >>> fig.show() - - """ - kwargs = self._preprocess(**kwargs) # pylint: disable=protected-access - with Session() as lib: - try: - lib.call_module("inset", f"begin {build_arg_string(kwargs)}") - yield - finally: - v_arg = build_arg_string({"V": kwargs.get("V")}) - lib.call_module("inset", f"end {v_arg}".strip()) +""" +inset - Create inset figures. +""" +import contextlib + +from pygmt.clib import Session +from pygmt.helpers import build_arg_string, fmt_docstring, kwargs_to_strings, use_alias + + +@fmt_docstring +@contextlib.contextmanager +@use_alias(D="position", F="box", M="margin", N="no_clip", V="verbose") +@kwargs_to_strings(D="sequence", M="sequence") +def inset(self, **kwargs): + r""" + Create an inset figure to be placed within a larger figure. + + This function sets the position, frame, and margins for a smaller figure + inside of the larger figure. Plotting functions that are called within the + context manager are added to the inset figure. + + Full option list at :gmt-docs:`inset.html` + + {aliases} + + Parameters + ---------- + position : str or list + *xmin/xmax/ymin/ymax*\ [**+r**][**+u**\ *unit*]] \ + | [**g**\|\ **j**\|\ **J**\|\ **n**\|\ **x**]\ *refpoint*\ + **+w**\ *width*\ [/*height*][**+j**\ *justify*]\ + [**+o**\ *dx*\ [/*dy*]]. + + *This is the only required parameter.* + Define the map inset rectangle on the map. Specify the rectangle + in one of three ways: + + Append **g**\ *lon*/*lat* for map (user) coordinates, + **j**\ *code* or **J**\ *code* for setting the *refpoint* via a + 2-char justification code \ that refers to the (invisible) + projected map bounding box, **n**\ *xn*/*yn* for normalized (0-1) + bounding box coordinates, or **x**\ *x*/*y* for plot + coordinates (inches, cm, points, append unit). + All but **x** requires both ``region`` and ``projection`` to be + specified. You can offset the reference point via + **+o**\ *dx*/*dy* in the direction implied by *code* or + **+j**\ *justify*. + + Alternatively, give *west/east/south/north* of geographic + rectangle bounded by parallels and meridians; append **+r** if the + coordinates instead are the lower left and upper right corners of + the desired rectangle. (Or, give *xmin/xmax/ymin/ymax* of bounding + rectangle in projected coordinates and optionally + append **+u**\ *unit* [Default coordinate unit is meter (e)]. + + Append **+w**\ *width*\ [/*height*] of bounding rectangle or box + in plot coordinates (inches, cm, etc.). By default, the anchor + point on the scale is assumed to be the bottom left corner (**BL**), + but this can be changed by appending **+j** followed by a 2-char + justification code *justify*. + **Note**: If **j** is used then *justify* defaults to the same + as *refpoint*, if **J** is used then *justify* defaults to the + mirror opposite of *refpoint*. Specify inset box attributes via + the ``box`` parameter [Default is outline only]. + box : str or bool + [**+c**\ *clearances*][**+g**\ *fill*][**+i**\ [[*gap*/]\ + *pen*]][**+p**\ [*pen*]][**+r**\ [*radius*]][**+s**\ + [[*dx*/*dy*/][*shade*]]]. + + If passed ``True``, this draws a rectangular box around the map + inset using the default pen; specify a different pen + with **+p**\ *pen*. Add **+g**\ *fill* to fill the logo box + [Default is no fill]. + Append **+c**\ *clearance* where *clearance* is either + *gap*, *xgap*\ /\ *ygap*, or *lgap*\ /\ *rgap*\ /\ *bgap*\ /\ + *tgap* where these items are uniform, separate in x- and + y-direction, or individual side spacings between logo and border. + Append **+i** to draw a secondary, inner border as well. We use a + uniform *gap* between borders of 2\ **p** and the default pen + unless other values are specified. Append **+r** to draw rounded + rectangular borders instead, with a 6p corner radius. You + can override this radius by appending another value. Append + **+s** to draw an offset background shaded region. Here, *dx*/*dy* + indicates the shift relative to the foreground frame + [4p/-4p] and ``shade`` sets the fill style to use for + shading [Default is gray50]. + margin : int or str or list + This is clearance that is added around the inside of the inset. + Plotting will take place within the inner region only. The margins + can be a single value, a pair of values separated (for setting + separate horizontal and vertical margins), or the full set of four + margins (for setting separate left, right, bottom, and top + margins). When passing multiple values, it can be either a list or + a string with the values separated by forward + slashes [Default is no margins]. + no_clip : bool + Do NOT clip features extruding outside map inset boundaries [Default + is clip]. + {V} + + Examples + -------- + >>> import pygmt + >>> + >>> # Create the larger figure + >>> fig = pygmt.Figure() + >>> fig.coast(region="MG+r2", water="lightblue", shorelines="thin") + >>> # Use a "with" statement to initialize the inset context manager + >>> # Setting the position to top left and a width of 3.5 centimeters + >>> with fig.inset(position="jTL+w3.5c+o0.2c", margin=0, box="+pgreen"): + ... # Map elements under the "with" statement are plotted in the inset + ... fig.coast( + ... region="g", + ... projection="G47/-20/3.5c", + ... land="gray", + ... water="white", + ... dcw="MG+gred", + ... ) + ... + >>> # Map elements outside the "with" block are plotted in the main figure + >>> fig.logo(position="jBR+o0.2c+w3c") + >>> fig.show() + + """ + kwargs = self._preprocess(**kwargs) # pylint: disable=protected-access + with Session() as lib: + try: + lib.call_module("inset", f"begin {build_arg_string(kwargs)}") + yield + finally: + v_arg = build_arg_string({"V": kwargs.get("V")}) + lib.call_module("inset", f"end {v_arg}".strip()) diff --git a/pygmt/src/legend.py b/pygmt/src/legend.py index 6e30b65a22b..87995424eaa 100644 --- a/pygmt/src/legend.py +++ b/pygmt/src/legend.py @@ -1,89 +1,89 @@ -""" -legend - Plot a legend. -""" - -from pygmt.clib import Session -from pygmt.exceptions import GMTInvalidInput -from pygmt.helpers import ( - build_arg_string, - data_kind, - fmt_docstring, - kwargs_to_strings, - use_alias, -) - - -@fmt_docstring -@use_alias( - R="region", - J="projection", - D="position", - F="box", - V="verbose", - X="xshift", - Y="yshift", - c="panel", - p="perspective", - t="transparency", -) -@kwargs_to_strings(R="sequence", c="sequence_comma", p="sequence") -def legend(self, spec=None, position="JTR+jTR+o0.2c", box="+gwhite+p1p", **kwargs): - r""" - Plot legends on maps. - - Makes legends that can be overlaid on maps. Reads specific - legend-related information from an input file, or automatically creates - legend entries from plotted symbols that have labels. Unless otherwise - noted, annotations will be made using the primary annotation font and - size in effect (i.e., FONT_ANNOT_PRIMARY). - - Full option list at :gmt-docs:`legend.html` - - {aliases} - - Parameters - ---------- - spec : None or str - Either ``None`` [default] for using the automatically generated legend - specification file, or a *filename* pointing to the legend - specification file. - {J} - {R} - position : str - [**g**\|\ **j**\|\ **J**\|\ **n**\|\ **x**]\ *refpoint*\ - **+w**\ *width*\ [/*height*]\ [**+j**\ *justify*]\ [**+l**\ *spacing*]\ - [**+o**\ *dx*\ [/*dy*]]. - Defines the reference point on the map for the - legend. By default, uses **JTR**\ +\ **jTR**\ +\ **o**\ *0.2c* which - places the legend at the top-right corner inside the map frame, with a - 0.2 cm offset. - box : bool or str - [**+c**\ *clearances*][**+g**\ *fill*][**+i**\ [[*gap*/]\ *pen*]]\ - [**+p**\ [*pen*]][**+r**\ [*radius*]][**+s**\ [[*dx*/*dy*/][*shade*]]]. - Without further arguments, draws a rectangular border around the legend - using :gmt-term:`MAP_FRAME_PEN`. By default, uses - **+g**\ white\ **+p**\ 1p which draws a box around the legend using a - 1p black pen and adds a white background. - {V} - {XY} - {c} - {p} - {t} - """ - kwargs = self._preprocess(**kwargs) # pylint: disable=protected-access - - if "D" not in kwargs: - kwargs["D"] = position - - if "F" not in kwargs: - kwargs["F"] = box - - with Session() as lib: - if spec is None: - specfile = "" - elif data_kind(spec) == "file": - specfile = spec - else: - raise GMTInvalidInput("Unrecognized data type: {}".format(type(spec))) - arg_str = " ".join([specfile, build_arg_string(kwargs)]) - lib.call_module("legend", arg_str) +""" +legend - Plot a legend. +""" + +from pygmt.clib import Session +from pygmt.exceptions import GMTInvalidInput +from pygmt.helpers import ( + build_arg_string, + data_kind, + fmt_docstring, + kwargs_to_strings, + use_alias, +) + + +@fmt_docstring +@use_alias( + R="region", + J="projection", + D="position", + F="box", + V="verbose", + X="xshift", + Y="yshift", + c="panel", + p="perspective", + t="transparency", +) +@kwargs_to_strings(R="sequence", c="sequence_comma", p="sequence") +def legend(self, spec=None, position="JTR+jTR+o0.2c", box="+gwhite+p1p", **kwargs): + r""" + Plot legends on maps. + + Makes legends that can be overlaid on maps. Reads specific + legend-related information from an input file, or automatically creates + legend entries from plotted symbols that have labels. Unless otherwise + noted, annotations will be made using the primary annotation font and + size in effect (i.e., FONT_ANNOT_PRIMARY). + + Full option list at :gmt-docs:`legend.html` + + {aliases} + + Parameters + ---------- + spec : None or str + Either ``None`` [default] for using the automatically generated legend + specification file, or a *filename* pointing to the legend + specification file. + {J} + {R} + position : str + [**g**\|\ **j**\|\ **J**\|\ **n**\|\ **x**]\ *refpoint*\ + **+w**\ *width*\ [/*height*]\ [**+j**\ *justify*]\ [**+l**\ *spacing*]\ + [**+o**\ *dx*\ [/*dy*]]. + Defines the reference point on the map for the + legend. By default, uses **JTR**\ +\ **jTR**\ +\ **o**\ *0.2c* which + places the legend at the top-right corner inside the map frame, with a + 0.2 cm offset. + box : bool or str + [**+c**\ *clearances*][**+g**\ *fill*][**+i**\ [[*gap*/]\ *pen*]]\ + [**+p**\ [*pen*]][**+r**\ [*radius*]][**+s**\ [[*dx*/*dy*/][*shade*]]]. + Without further arguments, draws a rectangular border around the legend + using :gmt-term:`MAP_FRAME_PEN`. By default, uses + **+g**\ white\ **+p**\ 1p which draws a box around the legend using a + 1p black pen and adds a white background. + {V} + {XY} + {c} + {p} + {t} + """ + kwargs = self._preprocess(**kwargs) # pylint: disable=protected-access + + if "D" not in kwargs: + kwargs["D"] = position + + if "F" not in kwargs: + kwargs["F"] = box + + with Session() as lib: + if spec is None: + specfile = "" + elif data_kind(spec) == "file": + specfile = spec + else: + raise GMTInvalidInput("Unrecognized data type: {}".format(type(spec))) + arg_str = " ".join([specfile, build_arg_string(kwargs)]) + lib.call_module("legend", arg_str) diff --git a/pygmt/src/logo.py b/pygmt/src/logo.py index 1101fd0aacb..3324f139516 100644 --- a/pygmt/src/logo.py +++ b/pygmt/src/logo.py @@ -1,64 +1,64 @@ -""" -logo - Plot the GMT logo -""" - -from pygmt.clib import Session -from pygmt.helpers import build_arg_string, fmt_docstring, kwargs_to_strings, use_alias - - -@fmt_docstring -@use_alias( - R="region", - J="projection", - D="position", - F="box", - S="style", - U="timestamp", - V="verbose", - X="xshift", - Y="yshift", - c="panel", - t="transparency", -) -@kwargs_to_strings(R="sequence", c="sequence_comma", p="sequence") -def logo(self, **kwargs): - r""" - Plot the GMT logo. - - By default, the GMT logo is 2 inches wide and 1 inch high and - will be positioned relative to the current plot origin. - Use various options to change this and to place a transparent or - opaque rectangular map panel behind the GMT logo. - - Full option list at :gmt-docs:`gmtlogo.html`. - - {aliases} - - Parameters - ---------- - {J} - {R} - position : str - [**g**\|\ **j**\|\ **J**\|\ **n**\|\ **x**]\ *refpoint*\ - **+w**\ *width*\ [**+j**\ *justify*]\ [**+o**\ *dx*\ [/*dy*]]. - Sets reference point on the map for the image. - box : bool or str - Without further arguments, draws a rectangular border around the - GMT logo. - style : str - [**l**\|\ **n**\|\ **u**]. - Control what is written beneath the map portion of the logo. - - - **l** to plot the text label "The Generic Mapping Tools" - [Default] - - **n** to skip the label placement - - **u** to place the URL to the GMT site - {U} - {V} - {XY} - {c} - {t} - """ - kwargs = self._preprocess(**kwargs) # pylint: disable=protected-access - with Session() as lib: - lib.call_module("logo", build_arg_string(kwargs)) +""" +logo - Plot the GMT logo +""" + +from pygmt.clib import Session +from pygmt.helpers import build_arg_string, fmt_docstring, kwargs_to_strings, use_alias + + +@fmt_docstring +@use_alias( + R="region", + J="projection", + D="position", + F="box", + S="style", + U="timestamp", + V="verbose", + X="xshift", + Y="yshift", + c="panel", + t="transparency", +) +@kwargs_to_strings(R="sequence", c="sequence_comma", p="sequence") +def logo(self, **kwargs): + r""" + Plot the GMT logo. + + By default, the GMT logo is 2 inches wide and 1 inch high and + will be positioned relative to the current plot origin. + Use various options to change this and to place a transparent or + opaque rectangular map panel behind the GMT logo. + + Full option list at :gmt-docs:`gmtlogo.html`. + + {aliases} + + Parameters + ---------- + {J} + {R} + position : str + [**g**\|\ **j**\|\ **J**\|\ **n**\|\ **x**]\ *refpoint*\ + **+w**\ *width*\ [**+j**\ *justify*]\ [**+o**\ *dx*\ [/*dy*]]. + Sets reference point on the map for the image. + box : bool or str + Without further arguments, draws a rectangular border around the + GMT logo. + style : str + [**l**\|\ **n**\|\ **u**]. + Control what is written beneath the map portion of the logo. + + - **l** to plot the text label "The Generic Mapping Tools" + [Default] + - **n** to skip the label placement + - **u** to place the URL to the GMT site + {U} + {V} + {XY} + {c} + {t} + """ + kwargs = self._preprocess(**kwargs) # pylint: disable=protected-access + with Session() as lib: + lib.call_module("logo", build_arg_string(kwargs)) diff --git a/pygmt/src/makecpt.py b/pygmt/src/makecpt.py index 744f7158542..3e97a5f76db 100644 --- a/pygmt/src/makecpt.py +++ b/pygmt/src/makecpt.py @@ -1,159 +1,159 @@ -""" -makecpt - Make GMT color palette tables. -""" -from pygmt.clib import Session -from pygmt.exceptions import GMTInvalidInput -from pygmt.helpers import build_arg_string, fmt_docstring, kwargs_to_strings, use_alias - - -@fmt_docstring -@use_alias( - A="transparency", - C="cmap", - D="background", - F="color_model", - G="truncate", - H="output", - I="reverse", - M="overrule_bg", - N="no_bg", - Q="log", - T="series", - V="verbose", - W="categorical", - Ww="cyclic", - Z="continuous", -) -@kwargs_to_strings(T="sequence", G="sequence") -def makecpt(**kwargs): - r""" - Make GMT color palette tables. - - This is a module that will help you make static color palette tables - (CPTs). By default, the CPT will simply be saved to the current session, - but you can use ``output`` to save it to a file. You define an equidistant - set of contour intervals or pass your own z-table or list, and create a new - CPT based on an existing master (dynamic) CPT. The resulting CPT can be - reversed relative to the master cpt, and can be made continuous or - discrete. For color tables beyond the standard GMT offerings, visit - `cpt-city `_ and - `Scientific Colour-Maps `_. - - The CPT includes three additional colors beyond the range of z-values. - These are the background color (B) assigned to values lower than the lowest - *z*-value, the foreground color (F) assigned to values higher than the - highest *z*-value, and the NaN color (N) painted wherever values are - undefined. - - If the master CPT includes B, F, and N entries, these will be copied into - the new master file. If not, the parameters :gmt-term:`COLOR_BACKGROUND`, - :gmt-term:`COLOR_FOREGROUND`, and :gmt-term:`COLOR_NAN` from the - :gmt-docs:`gmt.conf ` file or the command line will be used. This - default behavior can be overruled using the parameters ``background``, - ``overrule_bg`` or ``no_bg``. - - The color model (RGB, HSV or CMYK) of the palette created by **makecpt** - will be the same as specified in the header of the master CPT. When there - is no :gmt-term:`COLOR_MODEL` entry in the master CPT, the - :gmt-term:`COLOR_MODEL` specified in the :gmt-docs:`gmt.conf ` - file or on the command line will be used. - - Full option list at :gmt-docs:`makecpt.html` - - {aliases} - - Parameters - ---------- - transparency : str - Sets a constant level of transparency (0-100) for all color slices. - Append **+a** to also affect the fore-, back-, and nan-colors - [Default is no transparency, i.e., 0 (opaque)]. - cmap : str - Selects the master color palette table (CPT) to use in the - interpolation. Full list of built-in color palette tables can be found - at :gmt-docs:`cookbook/cpts.html#built-in-color-palette-tables-cpt`. - background : bool or str - Select the back- and foreground colors to match the colors for lowest - and highest *z*-values in the output CPT [Default (``background=True`` - or ``background='o'``) uses the colors specified in the master file, or - those defined by the parameters :gmt-term:`COLOR_BACKGROUND`, - :gmt-term:`COLOR_FOREGROUND`, and :gmt-term:`COLOR_NAN`]. Use - ``background='i'`` to match the colors for the lowest and highest - values in the input (instead of the output) CPT. - color_model : - [**R**\|\ **r**\|\ **h**\|\ **c**][**+c**\ [*label*]]. - Force output CPT to be written with r/g/b codes, gray-scale values or - color name (**R**, default) or r/g/b codes only (**r**), or h-s-v codes - (**h**), or c/m/y/k codes (**c**). Optionally or alternatively, append - **+c** to write discrete palettes in categorical format. If *label* is - appended then we create labels for each category to be used when the - CPT is plotted. The *label* may be a comma-separated list of category - names (you can skip a category by not giving a name), or give - *start*[-], where we automatically build monotonically increasing - labels from *start* (a single letter or an integer). Append - to build - ranges *start*-*start+1* instead. - series : list or str - [*min/max/inc*\[**+b**\|\ **l**\|\ **n**]\|\ *file*\|\ *list*]. - Defines the range of the new CPT by giving the lowest and highest - z-value (and optionally an interval). If this is not given, the - existing range in the master CPT will be used intact. The values - produced defines the color slice boundaries. If **+n** is used it - refers to the number of such boundaries and not the number of slices. - For details on array creation, see - :gmt-docs:`makecpt.html#generate-1d-array`. - truncate : list or str - *zlow/zhigh*. - Truncate the incoming CPT so that the lowest and highest z-levels are - to *zlow* and *zhigh*. If one of these equal NaN then we leave that - end of the CPT alone. The truncation takes place before any - resampling. See - also :gmt-docs:`cookbook/features.html#manipulating-cpts`. - output : str - Optional. The file name with extension .cpt to store the generated CPT - file. If not given or False (default), saves the CPT as the session - current CPT. - reverse : str - Set this to True or **c**\ [Default] to reverse the sense of color - progression in the master CPT. Set this to z to reverse the sign of - z-values in the color table. Note that this change of z-direction - happens before ``truncate`` and ``series`` values are used so the - latter must be compatible with the changed *z*-range. See also - :gmt-docs:`cookbook/features.html#manipulating-cpts`. - overrule_bg : str - Overrule background, foreground, and NaN colors specified in the master - CPT with the values of the parameters :gmt-term:`COLOR_BACKGROUND`, - :gmt-term:`COLOR_FOREGROUND`, and :gmt-term:`COLOR_NAN` specified in - the :gmt-docs:`gmt.conf ` file or on the command line. When - combined with **background**, only :gmt-term:`COLOR_NAN` is considered. - no_bg : bool - Do not write out the background, foreground, and NaN-color fields - [Default will write them, i.e. ``no_bg=False``]. - log : bool - For logarithmic interpolation scheme with input given as logarithms. - Expects input z-values provided via **series** to be log10(*z*), - assigns colors, and writes out *z*. - continuous : bool - Force a continuous CPT when building from a list of colors and a list - of z-values [Default is None, i.e. discrete values]. - {V} - categorical : bool - Do not interpolate the input color table but pick the output colors - starting at the beginning of the color table, until colors for all - intervals are assigned. This is particularly useful in combination with - a categorical color table, like ``cmap='categorical'``. - cyclic : bool - Produce a wrapped (cyclic) color table that endlessly repeats its - range. Note that ``cyclic=True`` cannot be set together with - ``categorical=True``. - """ - with Session() as lib: - if "W" in kwargs and "Ww" in kwargs: - raise GMTInvalidInput("Set only categorical or cyclic to True, not both.") - if "H" not in kwargs.keys(): # if no output is set - arg_str = build_arg_string(kwargs) - elif "H" in kwargs.keys(): # if output is set - outfile = kwargs.pop("H") - if not outfile or not isinstance(outfile, str): - raise GMTInvalidInput("'output' should be a proper file name.") - arg_str = " ".join([build_arg_string(kwargs), f"-H > {outfile}"]) - lib.call_module(module="makecpt", args=arg_str) +""" +makecpt - Make GMT color palette tables. +""" +from pygmt.clib import Session +from pygmt.exceptions import GMTInvalidInput +from pygmt.helpers import build_arg_string, fmt_docstring, kwargs_to_strings, use_alias + + +@fmt_docstring +@use_alias( + A="transparency", + C="cmap", + D="background", + F="color_model", + G="truncate", + H="output", + I="reverse", + M="overrule_bg", + N="no_bg", + Q="log", + T="series", + V="verbose", + W="categorical", + Ww="cyclic", + Z="continuous", +) +@kwargs_to_strings(T="sequence", G="sequence") +def makecpt(**kwargs): + r""" + Make GMT color palette tables. + + This is a module that will help you make static color palette tables + (CPTs). By default, the CPT will simply be saved to the current session, + but you can use ``output`` to save it to a file. You define an equidistant + set of contour intervals or pass your own z-table or list, and create a new + CPT based on an existing master (dynamic) CPT. The resulting CPT can be + reversed relative to the master cpt, and can be made continuous or + discrete. For color tables beyond the standard GMT offerings, visit + `cpt-city `_ and + `Scientific Colour-Maps `_. + + The CPT includes three additional colors beyond the range of z-values. + These are the background color (B) assigned to values lower than the lowest + *z*-value, the foreground color (F) assigned to values higher than the + highest *z*-value, and the NaN color (N) painted wherever values are + undefined. + + If the master CPT includes B, F, and N entries, these will be copied into + the new master file. If not, the parameters :gmt-term:`COLOR_BACKGROUND`, + :gmt-term:`COLOR_FOREGROUND`, and :gmt-term:`COLOR_NAN` from the + :gmt-docs:`gmt.conf ` file or the command line will be used. This + default behavior can be overruled using the parameters ``background``, + ``overrule_bg`` or ``no_bg``. + + The color model (RGB, HSV or CMYK) of the palette created by **makecpt** + will be the same as specified in the header of the master CPT. When there + is no :gmt-term:`COLOR_MODEL` entry in the master CPT, the + :gmt-term:`COLOR_MODEL` specified in the :gmt-docs:`gmt.conf ` + file or on the command line will be used. + + Full option list at :gmt-docs:`makecpt.html` + + {aliases} + + Parameters + ---------- + transparency : str + Sets a constant level of transparency (0-100) for all color slices. + Append **+a** to also affect the fore-, back-, and nan-colors + [Default is no transparency, i.e., 0 (opaque)]. + cmap : str + Selects the master color palette table (CPT) to use in the + interpolation. Full list of built-in color palette tables can be found + at :gmt-docs:`cookbook/cpts.html#built-in-color-palette-tables-cpt`. + background : bool or str + Select the back- and foreground colors to match the colors for lowest + and highest *z*-values in the output CPT [Default (``background=True`` + or ``background='o'``) uses the colors specified in the master file, or + those defined by the parameters :gmt-term:`COLOR_BACKGROUND`, + :gmt-term:`COLOR_FOREGROUND`, and :gmt-term:`COLOR_NAN`]. Use + ``background='i'`` to match the colors for the lowest and highest + values in the input (instead of the output) CPT. + color_model : + [**R**\|\ **r**\|\ **h**\|\ **c**][**+c**\ [*label*]]. + Force output CPT to be written with r/g/b codes, gray-scale values or + color name (**R**, default) or r/g/b codes only (**r**), or h-s-v codes + (**h**), or c/m/y/k codes (**c**). Optionally or alternatively, append + **+c** to write discrete palettes in categorical format. If *label* is + appended then we create labels for each category to be used when the + CPT is plotted. The *label* may be a comma-separated list of category + names (you can skip a category by not giving a name), or give + *start*[-], where we automatically build monotonically increasing + labels from *start* (a single letter or an integer). Append - to build + ranges *start*-*start+1* instead. + series : list or str + [*min/max/inc*\[**+b**\|\ **l**\|\ **n**]\|\ *file*\|\ *list*]. + Defines the range of the new CPT by giving the lowest and highest + z-value (and optionally an interval). If this is not given, the + existing range in the master CPT will be used intact. The values + produced defines the color slice boundaries. If **+n** is used it + refers to the number of such boundaries and not the number of slices. + For details on array creation, see + :gmt-docs:`makecpt.html#generate-1d-array`. + truncate : list or str + *zlow/zhigh*. + Truncate the incoming CPT so that the lowest and highest z-levels are + to *zlow* and *zhigh*. If one of these equal NaN then we leave that + end of the CPT alone. The truncation takes place before any + resampling. See + also :gmt-docs:`cookbook/features.html#manipulating-cpts`. + output : str + Optional. The file name with extension .cpt to store the generated CPT + file. If not given or False (default), saves the CPT as the session + current CPT. + reverse : str + Set this to True or **c**\ [Default] to reverse the sense of color + progression in the master CPT. Set this to z to reverse the sign of + z-values in the color table. Note that this change of z-direction + happens before ``truncate`` and ``series`` values are used so the + latter must be compatible with the changed *z*-range. See also + :gmt-docs:`cookbook/features.html#manipulating-cpts`. + overrule_bg : str + Overrule background, foreground, and NaN colors specified in the master + CPT with the values of the parameters :gmt-term:`COLOR_BACKGROUND`, + :gmt-term:`COLOR_FOREGROUND`, and :gmt-term:`COLOR_NAN` specified in + the :gmt-docs:`gmt.conf ` file or on the command line. When + combined with **background**, only :gmt-term:`COLOR_NAN` is considered. + no_bg : bool + Do not write out the background, foreground, and NaN-color fields + [Default will write them, i.e. ``no_bg=False``]. + log : bool + For logarithmic interpolation scheme with input given as logarithms. + Expects input z-values provided via **series** to be log10(*z*), + assigns colors, and writes out *z*. + continuous : bool + Force a continuous CPT when building from a list of colors and a list + of z-values [Default is None, i.e. discrete values]. + {V} + categorical : bool + Do not interpolate the input color table but pick the output colors + starting at the beginning of the color table, until colors for all + intervals are assigned. This is particularly useful in combination with + a categorical color table, like ``cmap='categorical'``. + cyclic : bool + Produce a wrapped (cyclic) color table that endlessly repeats its + range. Note that ``cyclic=True`` cannot be set together with + ``categorical=True``. + """ + with Session() as lib: + if "W" in kwargs and "Ww" in kwargs: + raise GMTInvalidInput("Set only categorical or cyclic to True, not both.") + if "H" not in kwargs.keys(): # if no output is set + arg_str = build_arg_string(kwargs) + elif "H" in kwargs.keys(): # if output is set + outfile = kwargs.pop("H") + if not outfile or not isinstance(outfile, str): + raise GMTInvalidInput("'output' should be a proper file name.") + arg_str = " ".join([build_arg_string(kwargs), f"-H > {outfile}"]) + lib.call_module(module="makecpt", args=arg_str) diff --git a/pygmt/src/meca.py b/pygmt/src/meca.py index 0fe68b8c8b9..3ab28b57581 100644 --- a/pygmt/src/meca.py +++ b/pygmt/src/meca.py @@ -1,411 +1,411 @@ -""" -meca - Plot focal mechanisms. -""" - -import numpy as np -import pandas as pd -from pygmt.clib import Session -from pygmt.exceptions import GMTError, GMTInvalidInput -from pygmt.helpers import ( - build_arg_string, - data_kind, - dummy_context, - fmt_docstring, - kwargs_to_strings, - use_alias, -) - - -@fmt_docstring -@use_alias( - R="region", - J="projection", - B="frame", - C="offset", - N="no_clip", - V="verbose", - X="xshift", - Y="yshift", - c="panel", - p="perspective", - t="transparency", -) -@kwargs_to_strings(R="sequence", c="sequence_comma", p="sequence") -def meca( - self, # pylint: disable=unused-argument - spec, - scale, - longitude=None, - latitude=None, - depth=None, - convention=None, - component="full", - plot_longitude=None, - plot_latitude=None, - **kwargs, -): - """ - Plot focal mechanisms. - - Full option list at :gmt-docs:`supplements/seis/meca.html` - - Note - ---- - Currently, labeling of beachballs with text strings is only supported - via providing a file to `spec` as input. - - {aliases} - - Parameters - ---------- - spec: dict, 1D array, 2D array, pd.DataFrame, or str - Either a filename containing focal mechanism parameters as columns, a - 1- or 2-D array with the same, or a dictionary. If a filename or array, - `convention` is required so we know how to interpret the - columns/entries. If a dictionary, the following combinations of keys - are supported; these determine the convention. Dictionary may contain - values for a single focal mechanism or lists of values for many focal - mechanisms. A Pandas DataFrame may optionally contain columns latitude, - longitude, depth, plot_longitude, and/or plot_latitude instead of - passing them to the meca method. - - - ``"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_exponent, t_azimuth, t_plunge, n_exponent, - n_azimuth, n_plunge, p_exponent, p_azimuth, p_plunge, exponent* - - scale: str - Adjusts the scaling of the radius of the beachball, which is - proportional to the magnitude. Scale defines the size for magnitude = 5 - (i.e. scalar seismic moment M0 = 4.0E23 dynes-cm) - longitude: int, float, list, or 1d numpy array - Longitude(s) of event location. Ignored if `spec` is not a dictionary. - List must be the length of the number of events. Ignored if `spec` is a - DataFrame and contains a 'longitude' column. - latitude: int, float, list, or 1d numpy array - Latitude(s) of event location. Ignored if `spec` is not a dictionary. - List must be the length of the number of events. Ignored if `spec` is a - DataFrame and contains a 'latitude' column. - depth: int, float, list, or 1d numpy array - Depth(s) of event location in kilometers. Ignored if `spec` is not a - dictionary. List must be the length of the number of events. Ignored if - `spec` is a DataFrame and contains a 'depth' column. - convention: str - ``"aki"`` (Aki & Richards), ``"gcmt"`` (global CMT), ``"mt"`` (seismic - moment tensor), ``"partial"`` (partial focal mechanism), or - ``"principal_axis"`` (principal axis). Ignored if `spec` is a - dictionary or dataframe. - component: str - The component of the seismic moment tensor to plot. ``"full"`` (the - full seismic moment tensor), ``"dc"`` (the closest double couple with - zero trace and zero determinant), ``"deviatoric"`` (zero trace) - plot_longitude: int, float, list, or 1d numpy array - Longitude(s) at which to place beachball, only used if `spec` is a - dictionary. List must be the length of the number of events. Ignored if - `spec` is a DataFrame and contains a 'plot_longitude' column. - plot_latitude: int, float, list, or 1d numpy array - Latitude(s) at which to place beachball, only used if `spec` is a - dictionary. List must be the length of the number of events. Ignored if - `spec` is a DataFrame and contains a 'plot_latitude' column. - offset: bool or str - Offsets beachballs to the longitude, latitude specified in 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. Specify pen - and optionally append ``+ssize`` to change the line style and/or size - of the circle. - no_clip : bool - Does NOT skip symbols that fall outside frame boundary specified by - *region* [Default is False, i.e. plot symbols inside map frame only]. - {J} - {R} - {B} - {V} - {XY} - {c} - {p} - {t} - """ - - # pylint warnings that need to be fixed - # pylint: disable=too-many-locals - # pylint: disable=too-many-nested-blocks - # pylint: disable=too-many-branches - # pylint: disable=too-many-statements - - def set_pointer(data_pointers, spec): - """ - Set optional parameter pointers based on DataFrame or dict, if those - parameters are present in the DataFrame or dict. - """ - for param in list(data_pointers.keys()): - if param in spec: - # set pointer based on param name - data_pointers[param] = spec[param] - - def update_pointers(data_pointers): - """ - Updates variables based on the location of data, as the following data - can be passed as parameters or it can be contained in `spec`. - """ - # update all pointers - longitude = data_pointers["longitude"] - latitude = data_pointers["latitude"] - depth = data_pointers["depth"] - plot_longitude = data_pointers["plot_longitude"] - plot_latitude = data_pointers["plot_latitude"] - return (longitude, latitude, depth, plot_longitude, plot_latitude) - - kwargs = self._preprocess(**kwargs) # pylint: disable=protected-access - # Check the spec and parse the data according to the specified - # convention - if isinstance(spec, (dict, pd.DataFrame)): - # dicts and DataFrames are handed similarly but not identically - if (longitude is None or latitude is None or depth is None) and not isinstance( - spec, (dict, pd.DataFrame) - ): - raise GMTError("Location not fully specified.") - - param_conventions = { - "AKI": ["strike", "dip", "rake", "magnitude"], - "GCMT": ["strike1", "dip1", "dip2", "rake2", "mantissa", "exponent"], - "MT": ["mrr", "mtt", "mff", "mrt", "mrf", "mtf", "exponent"], - "PARTIAL": ["strike1", "dip1", "strike2", "fault_type", "magnitude"], - "PRINCIPAL_AXIS": [ - "t_exponent", - "t_azimuth", - "t_plunge", - "n_exponent", - "n_azimuth", - "n_plunge", - "p_exponent", - "p_azimuth", - "p_plunge", - "exponent", - ], - } - - # to keep track of where optional parameters exist - data_pointers = { - "longitude": longitude, - "latitude": latitude, - "depth": depth, - "plot_longitude": plot_longitude, - "plot_latitude": plot_latitude, - } - - # make a DataFrame copy to check convention if it contains - # other parameters - if isinstance(spec, (dict, pd.DataFrame)): - # check if a copy is necessary - copy = False - drop_list = [] - for pointer in data_pointers: - if pointer in spec: - copy = True - drop_list.append(pointer) - if copy: - spec_conv = spec.copy() - # delete optional parameters from copy for convention check - for item in drop_list: - del spec_conv[item] - else: - spec_conv = spec - - # set convention and focal parameters based on spec convention - convention_assigned = False - for conv in param_conventions: - if set(spec_conv.keys()) == set(param_conventions[conv]): - convention = conv.lower() - foc_params = param_conventions[conv] - convention_assigned = True - break - if not convention_assigned: - raise GMTError( - "Parameters in spec dictionary do not match known " "conventions." - ) - - # create a dict type pointer for easier to read code - if isinstance(spec, dict): - dict_type_pointer = list(spec.values())[0] - elif isinstance(spec, pd.DataFrame): - # use df.values as pointer for DataFrame behavior - dict_type_pointer = spec.values - - # assemble the 1D array for the case of floats and ints as values - if isinstance(dict_type_pointer, (int, float)): - # update pointers - set_pointer(data_pointers, spec) - # look for optional parameters in the right place - ( - longitude, - latitude, - depth, - plot_longitude, - plot_latitude, - ) = update_pointers(data_pointers) - - # Construct the array (order matters) - spec = [longitude, latitude, depth] + [spec[key] for key in foc_params] - - # Add in plotting options, if given, otherwise add 0s - for arg in plot_longitude, plot_latitude: - if arg is None: - spec.append(0) - else: - if "C" not in kwargs: - kwargs["C"] = True - spec.append(arg) - - # or assemble the 2D array for the case of lists as values - elif isinstance(dict_type_pointer, list): - # update pointers - set_pointer(data_pointers, spec) - # look for optional parameters in the right place - ( - longitude, - latitude, - depth, - plot_longitude, - plot_latitude, - ) = update_pointers(data_pointers) - - # before constructing the 2D array lets check that each key - # of the dict has the same quantity of values to avoid bugs - list_length = len(list(spec.values())[0]) - for value in list(spec.values()): - if len(value) != list_length: - raise GMTError( - "Unequal number of focal mechanism " - "parameters supplied in 'spec'." - ) - # lets also check the inputs for longitude, latitude, - # and depth if it is a list or array - if ( - isinstance(longitude, (list, np.ndarray)) - or isinstance(latitude, (list, np.ndarray)) - or isinstance(depth, (list, np.ndarray)) - ): - if (len(longitude) != len(latitude)) or ( - len(longitude) != len(depth) - ): - raise GMTError( - "Unequal number of focal mechanism " "locations supplied." - ) - - # values are ok, so build the 2D array - spec_array = [] - for index in range(list_length): - # Construct the array one row at a time (note that order - # matters here, hence the list comprehension!) - row = [longitude[index], latitude[index], depth[index]] + [ - spec[key][index] for key in foc_params - ] - - # Add in plotting options, if given, otherwise add 0s as - # required by GMT - for arg in plot_longitude, plot_latitude: - if arg is None: - row.append(0) - else: - if "C" not in kwargs: - kwargs["C"] = True - row.append(arg[index]) - spec_array.append(row) - spec = spec_array - - # or assemble the array for the case of pd.DataFrames - elif isinstance(dict_type_pointer, np.ndarray): - # update pointers - set_pointer(data_pointers, spec) - # look for optional parameters in the right place - ( - longitude, - latitude, - depth, - plot_longitude, - plot_latitude, - ) = update_pointers(data_pointers) - - # lets also check the inputs for longitude, latitude, and depth - # just in case the user entered different length lists - if ( - isinstance(longitude, (list, np.ndarray)) - or isinstance(latitude, (list, np.ndarray)) - or isinstance(depth, (list, np.ndarray)) - ): - if (len(longitude) != len(latitude)) or (len(longitude) != len(depth)): - raise GMTError( - "Unequal number of focal mechanism locations supplied." - ) - - # values are ok, so build the 2D array in the correct order - spec_array = [] - for index in range(len(spec)): - # Construct the array one row at a time (note that order - # matters here, hence the list comprehension!) - row = [longitude[index], latitude[index], depth[index]] + [ - spec[key][index] for key in foc_params - ] - - # Add in plotting options, if given, otherwise add 0s as - # required by GMT - for arg in plot_longitude, plot_latitude: - if arg is None: - row.append(0) - else: - if "C" not in kwargs: - kwargs["C"] = True - row.append(arg[index]) - spec_array.append(row) - spec = spec_array - - else: - raise GMTError("Parameter 'spec' contains values of an unsupported type.") - - # Add condition and scale to kwargs - if convention == "aki": - data_format = "a" - elif convention == "gcmt": - data_format = "c" - elif convention == "mt": - # Check which component of mechanism the user wants plotted - if component == "deviatoric": - data_format = "z" - elif component == "dc": - data_format = "d" - else: # component == 'full' - data_format = "m" - elif convention == "partial": - data_format = "p" - elif convention == "principal_axis": - # Check which component of mechanism the user wants plotted - if component == "deviatoric": - data_format = "t" - elif component == "dc": - data_format = "y" - else: # component == 'full' - data_format = "x" - # Support old-school GMT format options - elif convention in ["a", "c", "m", "d", "z", "p", "x", "y", "t"]: - data_format = convention - else: - raise GMTError("Convention not recognized.") - - # Assemble -S flag - kwargs["S"] = data_format + scale - - kind = data_kind(spec) - with Session() as lib: - if kind == "matrix": - file_context = lib.virtualfile_from_matrix(np.atleast_2d(spec)) - elif kind == "file": - file_context = dummy_context(spec) - else: - raise GMTInvalidInput("Unrecognized data type: {}".format(type(spec))) - with file_context as fname: - arg_str = " ".join([fname, build_arg_string(kwargs)]) - lib.call_module("meca", arg_str) +""" +meca - Plot focal mechanisms. +""" + +import numpy as np +import pandas as pd +from pygmt.clib import Session +from pygmt.exceptions import GMTError, GMTInvalidInput +from pygmt.helpers import ( + build_arg_string, + data_kind, + dummy_context, + fmt_docstring, + kwargs_to_strings, + use_alias, +) + + +@fmt_docstring +@use_alias( + R="region", + J="projection", + B="frame", + C="offset", + N="no_clip", + V="verbose", + X="xshift", + Y="yshift", + c="panel", + p="perspective", + t="transparency", +) +@kwargs_to_strings(R="sequence", c="sequence_comma", p="sequence") +def meca( + self, # pylint: disable=unused-argument + spec, + scale, + longitude=None, + latitude=None, + depth=None, + convention=None, + component="full", + plot_longitude=None, + plot_latitude=None, + **kwargs, +): + """ + Plot focal mechanisms. + + Full option list at :gmt-docs:`supplements/seis/meca.html` + + Note + ---- + Currently, labeling of beachballs with text strings is only supported + via providing a file to `spec` as input. + + {aliases} + + Parameters + ---------- + spec: dict, 1D array, 2D array, pd.DataFrame, or str + Either a filename containing focal mechanism parameters as columns, a + 1- or 2-D array with the same, or a dictionary. If a filename or array, + `convention` is required so we know how to interpret the + columns/entries. If a dictionary, the following combinations of keys + are supported; these determine the convention. Dictionary may contain + values for a single focal mechanism or lists of values for many focal + mechanisms. A Pandas DataFrame may optionally contain columns latitude, + longitude, depth, plot_longitude, and/or plot_latitude instead of + passing them to the meca method. + + - ``"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_exponent, t_azimuth, t_plunge, n_exponent, + n_azimuth, n_plunge, p_exponent, p_azimuth, p_plunge, exponent* + + scale: str + Adjusts the scaling of the radius of the beachball, which is + proportional to the magnitude. Scale defines the size for magnitude = 5 + (i.e. scalar seismic moment M0 = 4.0E23 dynes-cm) + longitude: int, float, list, or 1d numpy array + Longitude(s) of event location. Ignored if `spec` is not a dictionary. + List must be the length of the number of events. Ignored if `spec` is a + DataFrame and contains a 'longitude' column. + latitude: int, float, list, or 1d numpy array + Latitude(s) of event location. Ignored if `spec` is not a dictionary. + List must be the length of the number of events. Ignored if `spec` is a + DataFrame and contains a 'latitude' column. + depth: int, float, list, or 1d numpy array + Depth(s) of event location in kilometers. Ignored if `spec` is not a + dictionary. List must be the length of the number of events. Ignored if + `spec` is a DataFrame and contains a 'depth' column. + convention: str + ``"aki"`` (Aki & Richards), ``"gcmt"`` (global CMT), ``"mt"`` (seismic + moment tensor), ``"partial"`` (partial focal mechanism), or + ``"principal_axis"`` (principal axis). Ignored if `spec` is a + dictionary or dataframe. + component: str + The component of the seismic moment tensor to plot. ``"full"`` (the + full seismic moment tensor), ``"dc"`` (the closest double couple with + zero trace and zero determinant), ``"deviatoric"`` (zero trace) + plot_longitude: int, float, list, or 1d numpy array + Longitude(s) at which to place beachball, only used if `spec` is a + dictionary. List must be the length of the number of events. Ignored if + `spec` is a DataFrame and contains a 'plot_longitude' column. + plot_latitude: int, float, list, or 1d numpy array + Latitude(s) at which to place beachball, only used if `spec` is a + dictionary. List must be the length of the number of events. Ignored if + `spec` is a DataFrame and contains a 'plot_latitude' column. + offset: bool or str + Offsets beachballs to the longitude, latitude specified in 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. Specify pen + and optionally append ``+ssize`` to change the line style and/or size + of the circle. + no_clip : bool + Does NOT skip symbols that fall outside frame boundary specified by + *region* [Default is False, i.e. plot symbols inside map frame only]. + {J} + {R} + {B} + {V} + {XY} + {c} + {p} + {t} + """ + + # pylint warnings that need to be fixed + # pylint: disable=too-many-locals + # pylint: disable=too-many-nested-blocks + # pylint: disable=too-many-branches + # pylint: disable=too-many-statements + + def set_pointer(data_pointers, spec): + """ + Set optional parameter pointers based on DataFrame or dict, if those + parameters are present in the DataFrame or dict. + """ + for param in list(data_pointers.keys()): + if param in spec: + # set pointer based on param name + data_pointers[param] = spec[param] + + def update_pointers(data_pointers): + """ + Updates variables based on the location of data, as the following data + can be passed as parameters or it can be contained in `spec`. + """ + # update all pointers + longitude = data_pointers["longitude"] + latitude = data_pointers["latitude"] + depth = data_pointers["depth"] + plot_longitude = data_pointers["plot_longitude"] + plot_latitude = data_pointers["plot_latitude"] + return (longitude, latitude, depth, plot_longitude, plot_latitude) + + kwargs = self._preprocess(**kwargs) # pylint: disable=protected-access + # Check the spec and parse the data according to the specified + # convention + if isinstance(spec, (dict, pd.DataFrame)): + # dicts and DataFrames are handed similarly but not identically + if (longitude is None or latitude is None or depth is None) and not isinstance( + spec, (dict, pd.DataFrame) + ): + raise GMTError("Location not fully specified.") + + param_conventions = { + "AKI": ["strike", "dip", "rake", "magnitude"], + "GCMT": ["strike1", "dip1", "dip2", "rake2", "mantissa", "exponent"], + "MT": ["mrr", "mtt", "mff", "mrt", "mrf", "mtf", "exponent"], + "PARTIAL": ["strike1", "dip1", "strike2", "fault_type", "magnitude"], + "PRINCIPAL_AXIS": [ + "t_exponent", + "t_azimuth", + "t_plunge", + "n_exponent", + "n_azimuth", + "n_plunge", + "p_exponent", + "p_azimuth", + "p_plunge", + "exponent", + ], + } + + # to keep track of where optional parameters exist + data_pointers = { + "longitude": longitude, + "latitude": latitude, + "depth": depth, + "plot_longitude": plot_longitude, + "plot_latitude": plot_latitude, + } + + # make a DataFrame copy to check convention if it contains + # other parameters + if isinstance(spec, (dict, pd.DataFrame)): + # check if a copy is necessary + copy = False + drop_list = [] + for pointer in data_pointers: + if pointer in spec: + copy = True + drop_list.append(pointer) + if copy: + spec_conv = spec.copy() + # delete optional parameters from copy for convention check + for item in drop_list: + del spec_conv[item] + else: + spec_conv = spec + + # set convention and focal parameters based on spec convention + convention_assigned = False + for conv in param_conventions: + if set(spec_conv.keys()) == set(param_conventions[conv]): + convention = conv.lower() + foc_params = param_conventions[conv] + convention_assigned = True + break + if not convention_assigned: + raise GMTError( + "Parameters in spec dictionary do not match known " "conventions." + ) + + # create a dict type pointer for easier to read code + if isinstance(spec, dict): + dict_type_pointer = list(spec.values())[0] + elif isinstance(spec, pd.DataFrame): + # use df.values as pointer for DataFrame behavior + dict_type_pointer = spec.values + + # assemble the 1D array for the case of floats and ints as values + if isinstance(dict_type_pointer, (int, float)): + # update pointers + set_pointer(data_pointers, spec) + # look for optional parameters in the right place + ( + longitude, + latitude, + depth, + plot_longitude, + plot_latitude, + ) = update_pointers(data_pointers) + + # Construct the array (order matters) + spec = [longitude, latitude, depth] + [spec[key] for key in foc_params] + + # Add in plotting options, if given, otherwise add 0s + for arg in plot_longitude, plot_latitude: + if arg is None: + spec.append(0) + else: + if "C" not in kwargs: + kwargs["C"] = True + spec.append(arg) + + # or assemble the 2D array for the case of lists as values + elif isinstance(dict_type_pointer, list): + # update pointers + set_pointer(data_pointers, spec) + # look for optional parameters in the right place + ( + longitude, + latitude, + depth, + plot_longitude, + plot_latitude, + ) = update_pointers(data_pointers) + + # before constructing the 2D array lets check that each key + # of the dict has the same quantity of values to avoid bugs + list_length = len(list(spec.values())[0]) + for value in list(spec.values()): + if len(value) != list_length: + raise GMTError( + "Unequal number of focal mechanism " + "parameters supplied in 'spec'." + ) + # lets also check the inputs for longitude, latitude, + # and depth if it is a list or array + if ( + isinstance(longitude, (list, np.ndarray)) + or isinstance(latitude, (list, np.ndarray)) + or isinstance(depth, (list, np.ndarray)) + ): + if (len(longitude) != len(latitude)) or ( + len(longitude) != len(depth) + ): + raise GMTError( + "Unequal number of focal mechanism " "locations supplied." + ) + + # values are ok, so build the 2D array + spec_array = [] + for index in range(list_length): + # Construct the array one row at a time (note that order + # matters here, hence the list comprehension!) + row = [longitude[index], latitude[index], depth[index]] + [ + spec[key][index] for key in foc_params + ] + + # Add in plotting options, if given, otherwise add 0s as + # required by GMT + for arg in plot_longitude, plot_latitude: + if arg is None: + row.append(0) + else: + if "C" not in kwargs: + kwargs["C"] = True + row.append(arg[index]) + spec_array.append(row) + spec = spec_array + + # or assemble the array for the case of pd.DataFrames + elif isinstance(dict_type_pointer, np.ndarray): + # update pointers + set_pointer(data_pointers, spec) + # look for optional parameters in the right place + ( + longitude, + latitude, + depth, + plot_longitude, + plot_latitude, + ) = update_pointers(data_pointers) + + # lets also check the inputs for longitude, latitude, and depth + # just in case the user entered different length lists + if ( + isinstance(longitude, (list, np.ndarray)) + or isinstance(latitude, (list, np.ndarray)) + or isinstance(depth, (list, np.ndarray)) + ): + if (len(longitude) != len(latitude)) or (len(longitude) != len(depth)): + raise GMTError( + "Unequal number of focal mechanism locations supplied." + ) + + # values are ok, so build the 2D array in the correct order + spec_array = [] + for index in range(len(spec)): + # Construct the array one row at a time (note that order + # matters here, hence the list comprehension!) + row = [longitude[index], latitude[index], depth[index]] + [ + spec[key][index] for key in foc_params + ] + + # Add in plotting options, if given, otherwise add 0s as + # required by GMT + for arg in plot_longitude, plot_latitude: + if arg is None: + row.append(0) + else: + if "C" not in kwargs: + kwargs["C"] = True + row.append(arg[index]) + spec_array.append(row) + spec = spec_array + + else: + raise GMTError("Parameter 'spec' contains values of an unsupported type.") + + # Add condition and scale to kwargs + if convention == "aki": + data_format = "a" + elif convention == "gcmt": + data_format = "c" + elif convention == "mt": + # Check which component of mechanism the user wants plotted + if component == "deviatoric": + data_format = "z" + elif component == "dc": + data_format = "d" + else: # component == 'full' + data_format = "m" + elif convention == "partial": + data_format = "p" + elif convention == "principal_axis": + # Check which component of mechanism the user wants plotted + if component == "deviatoric": + data_format = "t" + elif component == "dc": + data_format = "y" + else: # component == 'full' + data_format = "x" + # Support old-school GMT format options + elif convention in ["a", "c", "m", "d", "z", "p", "x", "y", "t"]: + data_format = convention + else: + raise GMTError("Convention not recognized.") + + # Assemble -S flag + kwargs["S"] = data_format + scale + + kind = data_kind(spec) + with Session() as lib: + if kind == "matrix": + file_context = lib.virtualfile_from_matrix(np.atleast_2d(spec)) + elif kind == "file": + file_context = dummy_context(spec) + else: + raise GMTInvalidInput("Unrecognized data type: {}".format(type(spec))) + with file_context as fname: + arg_str = " ".join([fname, build_arg_string(kwargs)]) + lib.call_module("meca", arg_str) diff --git a/pygmt/src/plot.py b/pygmt/src/plot.py index f367d816562..42afaee34fb 100644 --- a/pygmt/src/plot.py +++ b/pygmt/src/plot.py @@ -1,240 +1,240 @@ -""" -plot - Plot in two dimensions. -""" -import numpy as np -from pygmt.clib import Session -from pygmt.exceptions import GMTInvalidInput -from pygmt.helpers import ( - build_arg_string, - data_kind, - dummy_context, - fmt_docstring, - is_nonstr_iter, - kwargs_to_strings, - use_alias, -) - - -@fmt_docstring -@use_alias( - A="straight_line", - B="frame", - C="cmap", - D="offset", - E="error_bar", - F="connection", - G="color", - I="intensity", - J="projection", - L="close", - N="no_clip", - R="region", - S="style", - U="timestamp", - V="verbose", - W="pen", - X="xshift", - Y="yshift", - Z="zvalue", - i="columns", - l="label", - c="panel", - f="coltypes", - p="perspective", - t="transparency", -) -@kwargs_to_strings(R="sequence", c="sequence_comma", i="sequence_comma", p="sequence") -def plot(self, x=None, y=None, data=None, sizes=None, direction=None, **kwargs): - r""" - Plot lines, polygons, and symbols in 2-D. - - Takes a matrix, (x,y) pairs, or a file name as input and plots lines, - polygons, or symbols at those locations on a map. - - Must provide either ``data`` or ``x``/``y``. - - If providing data through ``x``/``y``, ``color`` can be a 1d array that - will be mapped to a colormap. - - If a symbol is selected and no symbol size given, then plot will - interpret the third column of the input data as symbol size. Symbols - whose size is <= 0 are skipped. If no symbols are specified then the - symbol code (see ``style`` below) must be present as last column in the - input. If ``style`` is not used, a line connecting the data points will - be drawn instead. To explicitly close polygons, use ``close``. Select a - fill with ``color``. If ``color`` is set, ``pen`` will control whether the - polygon outline is drawn or not. If a symbol is selected, ``color`` and - ``pen`` determines the fill and outline/no outline, respectively. - - Full parameter list at :gmt-docs:`plot.html` - - {aliases} - - Parameters - ---------- - x/y : float or 1d arrays - The x and y coordinates, or arrays of x and y coordinates of the - data points - data : str or 2d array - Either a data file name or a 2d numpy array with the tabular data. - Use parameter ``columns`` to choose which columns are x, y, color, - and size, respectively. - sizes : 1d array - The sizes of the data points in units specified using ``style``. - Only valid if using ``x``/``y``. - direction : list of two 1d arrays - If plotting vectors (using ``style='V'`` or ``style='v'``), then - should be a list of two 1d arrays with the vector directions. These - can be angle and length, azimuth and length, or x and y components, - depending on the style options chosen. - {J} - {R} - 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. - {B} - {CPT} - offset : str - *dx*/*dy*. - Offset the plot symbol or line locations by the given amounts - *dx/dy* [Default is no offset]. If *dy* is not given it is set - equal to *dx*. - error_bar : bool or str - [**+b**\|\ **d**\|\ **D**][**+xl**\|\ **r**\|\ *x0*]\ - [**+yl**\|\ **r**\|\ *y0*][**+p**\ *pen*]. - Draw symmetrical error bars. Full documentation is at - :gmt-docs:`plot.html#e`. - connection : str - [**c**\|\ **n**\|\ **r**]\ - [**a**\|\ **f**\|\ **s**\|\ **r**\|\ *refpoint*]. - Alter the way points are connected (by specifying a *scheme*) and - data are grouped (by specifying a *method*). Append one of three - line connection schemes: - - - **c** : Draw continuous line segments for each group [Default]. - - **r** : Draw line segments from a reference point reset for each - group. - - **n** : Draw networks of line segments between all points in - each group. - - Optionally, append the one of four segmentation methods to define - the group: - - - **a** : Ignore all segment headers, i.e., let all points belong - to a single group, and set group reference point to the very - first point of the first file. - - **f** : Consider all data in each file to be a single separate - group and reset the group reference point to the first point of - each group. - - **s** : Segment headers are honored so each segment is a group; - the group reference point is reset to the first point of each - incoming segment [Default]. - - **r** : Same as **s**, but the group reference point is reset - after each record to the previous point (this method is only - available with the ``connection='r'`` scheme). - - Instead of the codes **a**\|\ **f**\|\ **s**\|\ **r** you may append - the coordinates of a *refpoint* which will serve as a fixed external - reference point for all groups. - {G} - intensity : float or bool - Provide an *intens* value (nominally in the -1 to +1 range) to - modulate the fill color by simulating illumination [None]. If - using ``intensity=True``, we will instead read *intens* from the - first data column after the symbol parameters (if given). - close : str - [**+b**\|\ **d**\|\ **D**][**+xl**\|\ **r**\|\ *x0*]\ - [**+yl**\|\ **r**\|\ *y0*][**+p**\ *pen*]. - Force closed polygons. Full documentation is at - :gmt-docs:`plot.html#l`. - no_clip : bool or str - [**c**\|\ **r**]. - Do NOT clip symbols that fall outside map border [Default plots - points whose coordinates are strictly inside the map border only]. - The parameter does not apply to lines and polygons which are always - clipped to the map region. For periodic (360-longitude) maps we - must plot all symbols twice in case they are clipped by the - repeating boundary. ``no_clip=True`` will turn off clipping and not - plot repeating symbols. Use ``no_clip="r"`` to turn off clipping - but retain the plotting of such repeating symbols, or use - ``no_clip="c"`` to retain clipping but turn off plotting of - repeating symbols. - style : str - Plot symbols (including vectors, pie slices, fronts, decorated or - quoted lines). - {W} - {U} - {V} - {XY} - zvalue : str - *value*\|\ *file*. - Instead of specifying a symbol or polygon fill and outline color - via ``color`` and ``pen``, give both a *value* via ``zvalue`` and a - color lookup table via ``cmap``. Alternatively, give the name of a - *file* with one z-value (read from the last column) for each - polygon in the input data. To apply it to the fill color, use - ``color='+z'``. To apply it to the pen color, append **+z** to - ``pen``. - {c} - {f} - columns : str or 1d array - Choose which columns are x, y, color, and size, respectively if - input is provided via *data*. E.g. ``columns = [0, 1]`` or - ``columns = '0,1'`` if the *x* values are stored in the first - column and *y* values in the second one. Note: zero-based - indexing is used. - label : str - Add a legend entry for the symbol or line being plotted. - - {p} - {t} - *transparency* can also be a 1d array to set varying transparency - for symbols. - """ - kwargs = self._preprocess(**kwargs) # pylint: disable=protected-access - - kind = data_kind(data, x, y) - - extra_arrays = [] - if "S" in kwargs and kwargs["S"][0] in "vV" and direction is not None: - extra_arrays.extend(direction) - if "G" in kwargs and not isinstance(kwargs["G"], str): - if kind != "vectors": - raise GMTInvalidInput( - "Can't use arrays for color if data is matrix or file." - ) - extra_arrays.append(kwargs["G"]) - del kwargs["G"] - if sizes is not None: - if kind != "vectors": - raise GMTInvalidInput( - "Can't use arrays for sizes if data is matrix or file." - ) - extra_arrays.append(sizes) - - if "t" in kwargs and is_nonstr_iter(kwargs["t"]): - extra_arrays.append(kwargs["t"]) - kwargs["t"] = "" - - with Session() as lib: - # Choose how data will be passed in to the module - if kind == "file": - file_context = dummy_context(data) - elif kind == "matrix": - file_context = lib.virtualfile_from_matrix(data) - elif kind == "vectors": - file_context = lib.virtualfile_from_vectors( - np.atleast_1d(x), np.atleast_1d(y), *extra_arrays - ) - - with file_context as fname: - arg_str = " ".join([fname, build_arg_string(kwargs)]) - lib.call_module("plot", arg_str) +""" +plot - Plot in two dimensions. +""" +import numpy as np +from pygmt.clib import Session +from pygmt.exceptions import GMTInvalidInput +from pygmt.helpers import ( + build_arg_string, + data_kind, + dummy_context, + fmt_docstring, + is_nonstr_iter, + kwargs_to_strings, + use_alias, +) + + +@fmt_docstring +@use_alias( + A="straight_line", + B="frame", + C="cmap", + D="offset", + E="error_bar", + F="connection", + G="color", + I="intensity", + J="projection", + L="close", + N="no_clip", + R="region", + S="style", + U="timestamp", + V="verbose", + W="pen", + X="xshift", + Y="yshift", + Z="zvalue", + i="columns", + l="label", + c="panel", + f="coltypes", + p="perspective", + t="transparency", +) +@kwargs_to_strings(R="sequence", c="sequence_comma", i="sequence_comma", p="sequence") +def plot(self, x=None, y=None, data=None, sizes=None, direction=None, **kwargs): + r""" + Plot lines, polygons, and symbols in 2-D. + + Takes a matrix, (x,y) pairs, or a file name as input and plots lines, + polygons, or symbols at those locations on a map. + + Must provide either ``data`` or ``x``/``y``. + + If providing data through ``x``/``y``, ``color`` can be a 1d array that + will be mapped to a colormap. + + If a symbol is selected and no symbol size given, then plot will + interpret the third column of the input data as symbol size. Symbols + whose size is <= 0 are skipped. If no symbols are specified then the + symbol code (see ``style`` below) must be present as last column in the + input. If ``style`` is not used, a line connecting the data points will + be drawn instead. To explicitly close polygons, use ``close``. Select a + fill with ``color``. If ``color`` is set, ``pen`` will control whether the + polygon outline is drawn or not. If a symbol is selected, ``color`` and + ``pen`` determines the fill and outline/no outline, respectively. + + Full parameter list at :gmt-docs:`plot.html` + + {aliases} + + Parameters + ---------- + x/y : float or 1d arrays + The x and y coordinates, or arrays of x and y coordinates of the + data points + data : str or 2d array + Either a data file name or a 2d numpy array with the tabular data. + Use parameter ``columns`` to choose which columns are x, y, color, + and size, respectively. + sizes : 1d array + The sizes of the data points in units specified using ``style``. + Only valid if using ``x``/``y``. + direction : list of two 1d arrays + If plotting vectors (using ``style='V'`` or ``style='v'``), then + should be a list of two 1d arrays with the vector directions. These + can be angle and length, azimuth and length, or x and y components, + depending on the style options chosen. + {J} + {R} + 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. + {B} + {CPT} + offset : str + *dx*/*dy*. + Offset the plot symbol or line locations by the given amounts + *dx/dy* [Default is no offset]. If *dy* is not given it is set + equal to *dx*. + error_bar : bool or str + [**+b**\|\ **d**\|\ **D**][**+xl**\|\ **r**\|\ *x0*]\ + [**+yl**\|\ **r**\|\ *y0*][**+p**\ *pen*]. + Draw symmetrical error bars. Full documentation is at + :gmt-docs:`plot.html#e`. + connection : str + [**c**\|\ **n**\|\ **r**]\ + [**a**\|\ **f**\|\ **s**\|\ **r**\|\ *refpoint*]. + Alter the way points are connected (by specifying a *scheme*) and + data are grouped (by specifying a *method*). Append one of three + line connection schemes: + + - **c** : Draw continuous line segments for each group [Default]. + - **r** : Draw line segments from a reference point reset for each + group. + - **n** : Draw networks of line segments between all points in + each group. + + Optionally, append the one of four segmentation methods to define + the group: + + - **a** : Ignore all segment headers, i.e., let all points belong + to a single group, and set group reference point to the very + first point of the first file. + - **f** : Consider all data in each file to be a single separate + group and reset the group reference point to the first point of + each group. + - **s** : Segment headers are honored so each segment is a group; + the group reference point is reset to the first point of each + incoming segment [Default]. + - **r** : Same as **s**, but the group reference point is reset + after each record to the previous point (this method is only + available with the ``connection='r'`` scheme). + + Instead of the codes **a**\|\ **f**\|\ **s**\|\ **r** you may append + the coordinates of a *refpoint* which will serve as a fixed external + reference point for all groups. + {G} + intensity : float or bool + Provide an *intens* value (nominally in the -1 to +1 range) to + modulate the fill color by simulating illumination [None]. If + using ``intensity=True``, we will instead read *intens* from the + first data column after the symbol parameters (if given). + close : str + [**+b**\|\ **d**\|\ **D**][**+xl**\|\ **r**\|\ *x0*]\ + [**+yl**\|\ **r**\|\ *y0*][**+p**\ *pen*]. + Force closed polygons. Full documentation is at + :gmt-docs:`plot.html#l`. + no_clip : bool or str + [**c**\|\ **r**]. + Do NOT clip symbols that fall outside map border [Default plots + points whose coordinates are strictly inside the map border only]. + The parameter does not apply to lines and polygons which are always + clipped to the map region. For periodic (360-longitude) maps we + must plot all symbols twice in case they are clipped by the + repeating boundary. ``no_clip=True`` will turn off clipping and not + plot repeating symbols. Use ``no_clip="r"`` to turn off clipping + but retain the plotting of such repeating symbols, or use + ``no_clip="c"`` to retain clipping but turn off plotting of + repeating symbols. + style : str + Plot symbols (including vectors, pie slices, fronts, decorated or + quoted lines). + {W} + {U} + {V} + {XY} + zvalue : str + *value*\|\ *file*. + Instead of specifying a symbol or polygon fill and outline color + via ``color`` and ``pen``, give both a *value* via ``zvalue`` and a + color lookup table via ``cmap``. Alternatively, give the name of a + *file* with one z-value (read from the last column) for each + polygon in the input data. To apply it to the fill color, use + ``color='+z'``. To apply it to the pen color, append **+z** to + ``pen``. + {c} + {f} + columns : str or 1d array + Choose which columns are x, y, color, and size, respectively if + input is provided via *data*. E.g. ``columns = [0, 1]`` or + ``columns = '0,1'`` if the *x* values are stored in the first + column and *y* values in the second one. Note: zero-based + indexing is used. + label : str + Add a legend entry for the symbol or line being plotted. + + {p} + {t} + *transparency* can also be a 1d array to set varying transparency + for symbols. + """ + kwargs = self._preprocess(**kwargs) # pylint: disable=protected-access + + kind = data_kind(data, x, y) + + extra_arrays = [] + if "S" in kwargs and kwargs["S"][0] in "vV" and direction is not None: + extra_arrays.extend(direction) + if "G" in kwargs and not isinstance(kwargs["G"], str): + if kind != "vectors": + raise GMTInvalidInput( + "Can't use arrays for color if data is matrix or file." + ) + extra_arrays.append(kwargs["G"]) + del kwargs["G"] + if sizes is not None: + if kind != "vectors": + raise GMTInvalidInput( + "Can't use arrays for sizes if data is matrix or file." + ) + extra_arrays.append(sizes) + + if "t" in kwargs and is_nonstr_iter(kwargs["t"]): + extra_arrays.append(kwargs["t"]) + kwargs["t"] = "" + + with Session() as lib: + # Choose how data will be passed in to the module + if kind == "file": + file_context = dummy_context(data) + elif kind == "matrix": + file_context = lib.virtualfile_from_matrix(data) + elif kind == "vectors": + file_context = lib.virtualfile_from_vectors( + np.atleast_1d(x), np.atleast_1d(y), *extra_arrays + ) + + with file_context as fname: + arg_str = " ".join([fname, build_arg_string(kwargs)]) + lib.call_module("plot", arg_str) diff --git a/pygmt/src/plot3d.py b/pygmt/src/plot3d.py index 3b7ad917bdb..313a3c82801 100644 --- a/pygmt/src/plot3d.py +++ b/pygmt/src/plot3d.py @@ -1,203 +1,203 @@ -""" -plot3d - Plot in three dimensions. -""" -import numpy as np -from pygmt.clib import Session -from pygmt.exceptions import GMTInvalidInput -from pygmt.helpers import ( - build_arg_string, - data_kind, - dummy_context, - fmt_docstring, - is_nonstr_iter, - kwargs_to_strings, - use_alias, -) - - -@fmt_docstring -@use_alias( - A="straight_line", - B="frame", - C="cmap", - D="offset", - G="color", - I="intensity", - J="projection", - Jz="zscale", - JZ="zsize", - L="close", - N="no_clip", - Q="no_sort", - R="region", - S="style", - V="verbose", - W="pen", - X="xshift", - Y="yshift", - Z="zvalue", - i="columns", - l="label", - c="panel", - f="coltypes", - p="perspective", - t="transparency", -) -@kwargs_to_strings(R="sequence", c="sequence_comma", i="sequence_comma", p="sequence") -def plot3d( - self, x=None, y=None, z=None, data=None, sizes=None, direction=None, **kwargs -): - r""" - Plot lines, polygons, and symbols in 3-D. - - Takes a matrix, (x,y,z) triplets, or a file name as input and plots - lines, polygons, or symbols at those locations in 3-D. - - Must provide either ``data`` or ``x``/``y``/``z``. - - If providing data through ``x/y/z``, ``color`` can be a 1d array - that will be mapped to a colormap. - - If a symbol is selected and no symbol size given, then plot3d will - interpret the fourth column of the input data as symbol size. Symbols - whose size is <= 0 are skipped. If no symbols are specified then the - symbol code (see ``style`` below) must be present as last column in the - input. If ``style`` is not used, a line connecting the data points will - be drawn instead. To explicitly close polygons, use ``close``. Select a - fill with ``color``. If ``color`` is set, ``pen`` will control whether the - polygon outline is drawn or not. If a symbol is selected, ``color`` and - ``pen`` determines the fill and outline/no outline, respectively. - - Full parameter list at :gmt-docs:`plot3d.html` - - {aliases} - - Parameters - ---------- - x/y/z : float or 1d arrays - The x, y, and z coordinates, or arrays of x, y and z coordinates of - the data points - data : str or 2d array - Either a data file name or a 2d numpy array with the tabular data. - Use parameter ``columns`` to choose which columns are x, y, z, - color, and size, respectively. - sizes : 1d array - The sizes of the data points in units specified in ``style``. - Only valid if using ``x``/``y``/``z``. - direction : list of two 1d arrays - If plotting vectors (using ``style='V'`` or ``style='v'``), then - should be a list of two 1d arrays with the vector directions. These - can be angle and length, azimuth and length, or x and y components, - depending on the style options chosen. - {J} - zscale/zsize : float or str - Set z-axis scaling or z-axis size. - {R} - 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. - {B} - {CPT} - offset : str - *dx*/*dy*\ [/*dz*]. - Offset the plot symbol or line locations by the given amounts - *dx*/*dy*\ [/*dz*] [Default is no offset]. - {G} - intensity : float or bool - Provide an *intens* value (nominally in the -1 to +1 range) to - modulate the fill color by simulating illumination [Default is None]. - If using ``intensity=True``, we will instead read *intens* from the - first data column after the symbol parameters (if given). - close : str - [**+b**\|\ **d**\|\ **D**][**+xl**\|\ **r**\|\ *x0*]\ - [**+yl**\|\ **r**\|\ *y0*][**+p**\ *pen*]. - Force closed polygons. Full documentation is at - :gmt-docs:`plot3d.html#l`. - no_clip : bool or str - [**c**\|\ **r**]. - Do NOT clip symbols that fall outside map border [Default plots - points whose coordinates are strictly inside the map border only]. - This parameter does not apply to lines and polygons which are always - clipped to the map region. For periodic (360-longitude) maps we - must plot all symbols twice in case they are clipped by the - repeating boundary. ``no_clip=True`` will turn off clipping and not - plot repeating symbols. Use ``no_clip="r"`` to turn off clipping - but retain the plotting of such repeating symbols, or use - ``no_clip="c"`` to retain clipping but turn off plotting of - repeating symbols. - no_sort : bool - Turn off the automatic sorting of items based on their distance - from the viewer. The default is to sort the items so that items in - the foreground are plotted after items in the background. - style : str - Plot symbols. Full documentation is at :gmt-docs:`plot3d.html#s`. - {U} - {V} - {W} - {XY} - zvalue : str - *value*\|\ *file*. - Instead of specifying a symbol or polygon fill and outline color - via ``color`` and ``pen``, give both a *value* via **zvalue** and a - color lookup table via ``cmap``. Alternatively, give the name of a - *file* with one z-value (read from the last column) for each - polygon in the input data. To apply it to the fill color, use - ``color='+z'``. To apply it to the pen color, append **+z** to - ``pen``. - {c} - {f} - label : str - Add a legend entry for the symbol or line being plotted. - {p} - {t} - *transparency* can also be a 1d array to set varying transparency - for symbols. - """ - kwargs = self._preprocess(**kwargs) # pylint: disable=protected-access - - kind = data_kind(data, x, y, z) - - extra_arrays = [] - if "S" in kwargs and kwargs["S"][0] in "vV" and direction is not None: - extra_arrays.extend(direction) - if "G" in kwargs and not isinstance(kwargs["G"], str): - if kind != "vectors": - raise GMTInvalidInput( - "Can't use arrays for color if data is matrix or file." - ) - extra_arrays.append(kwargs["G"]) - del kwargs["G"] - if sizes is not None: - if kind != "vectors": - raise GMTInvalidInput( - "Can't use arrays for sizes if data is matrix or file." - ) - extra_arrays.append(sizes) - - if "t" in kwargs and is_nonstr_iter(kwargs["t"]): - extra_arrays.append(kwargs["t"]) - kwargs["t"] = "" - - with Session() as lib: - # Choose how data will be passed in to the module - if kind == "file": - file_context = dummy_context(data) - elif kind == "matrix": - file_context = lib.virtualfile_from_matrix(data) - elif kind == "vectors": - file_context = lib.virtualfile_from_vectors( - np.atleast_1d(x), np.atleast_1d(y), np.atleast_1d(z), *extra_arrays - ) - - with file_context as fname: - arg_str = " ".join([fname, build_arg_string(kwargs)]) - lib.call_module("plot3d", arg_str) +""" +plot3d - Plot in three dimensions. +""" +import numpy as np +from pygmt.clib import Session +from pygmt.exceptions import GMTInvalidInput +from pygmt.helpers import ( + build_arg_string, + data_kind, + dummy_context, + fmt_docstring, + is_nonstr_iter, + kwargs_to_strings, + use_alias, +) + + +@fmt_docstring +@use_alias( + A="straight_line", + B="frame", + C="cmap", + D="offset", + G="color", + I="intensity", + J="projection", + Jz="zscale", + JZ="zsize", + L="close", + N="no_clip", + Q="no_sort", + R="region", + S="style", + V="verbose", + W="pen", + X="xshift", + Y="yshift", + Z="zvalue", + i="columns", + l="label", + c="panel", + f="coltypes", + p="perspective", + t="transparency", +) +@kwargs_to_strings(R="sequence", c="sequence_comma", i="sequence_comma", p="sequence") +def plot3d( + self, x=None, y=None, z=None, data=None, sizes=None, direction=None, **kwargs +): + r""" + Plot lines, polygons, and symbols in 3-D. + + Takes a matrix, (x,y,z) triplets, or a file name as input and plots + lines, polygons, or symbols at those locations in 3-D. + + Must provide either ``data`` or ``x``/``y``/``z``. + + If providing data through ``x/y/z``, ``color`` can be a 1d array + that will be mapped to a colormap. + + If a symbol is selected and no symbol size given, then plot3d will + interpret the fourth column of the input data as symbol size. Symbols + whose size is <= 0 are skipped. If no symbols are specified then the + symbol code (see ``style`` below) must be present as last column in the + input. If ``style`` is not used, a line connecting the data points will + be drawn instead. To explicitly close polygons, use ``close``. Select a + fill with ``color``. If ``color`` is set, ``pen`` will control whether the + polygon outline is drawn or not. If a symbol is selected, ``color`` and + ``pen`` determines the fill and outline/no outline, respectively. + + Full parameter list at :gmt-docs:`plot3d.html` + + {aliases} + + Parameters + ---------- + x/y/z : float or 1d arrays + The x, y, and z coordinates, or arrays of x, y and z coordinates of + the data points + data : str or 2d array + Either a data file name or a 2d numpy array with the tabular data. + Use parameter ``columns`` to choose which columns are x, y, z, + color, and size, respectively. + sizes : 1d array + The sizes of the data points in units specified in ``style``. + Only valid if using ``x``/``y``/``z``. + direction : list of two 1d arrays + If plotting vectors (using ``style='V'`` or ``style='v'``), then + should be a list of two 1d arrays with the vector directions. These + can be angle and length, azimuth and length, or x and y components, + depending on the style options chosen. + {J} + zscale/zsize : float or str + Set z-axis scaling or z-axis size. + {R} + 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. + {B} + {CPT} + offset : str + *dx*/*dy*\ [/*dz*]. + Offset the plot symbol or line locations by the given amounts + *dx*/*dy*\ [/*dz*] [Default is no offset]. + {G} + intensity : float or bool + Provide an *intens* value (nominally in the -1 to +1 range) to + modulate the fill color by simulating illumination [Default is None]. + If using ``intensity=True``, we will instead read *intens* from the + first data column after the symbol parameters (if given). + close : str + [**+b**\|\ **d**\|\ **D**][**+xl**\|\ **r**\|\ *x0*]\ + [**+yl**\|\ **r**\|\ *y0*][**+p**\ *pen*]. + Force closed polygons. Full documentation is at + :gmt-docs:`plot3d.html#l`. + no_clip : bool or str + [**c**\|\ **r**]. + Do NOT clip symbols that fall outside map border [Default plots + points whose coordinates are strictly inside the map border only]. + This parameter does not apply to lines and polygons which are always + clipped to the map region. For periodic (360-longitude) maps we + must plot all symbols twice in case they are clipped by the + repeating boundary. ``no_clip=True`` will turn off clipping and not + plot repeating symbols. Use ``no_clip="r"`` to turn off clipping + but retain the plotting of such repeating symbols, or use + ``no_clip="c"`` to retain clipping but turn off plotting of + repeating symbols. + no_sort : bool + Turn off the automatic sorting of items based on their distance + from the viewer. The default is to sort the items so that items in + the foreground are plotted after items in the background. + style : str + Plot symbols. Full documentation is at :gmt-docs:`plot3d.html#s`. + {U} + {V} + {W} + {XY} + zvalue : str + *value*\|\ *file*. + Instead of specifying a symbol or polygon fill and outline color + via ``color`` and ``pen``, give both a *value* via **zvalue** and a + color lookup table via ``cmap``. Alternatively, give the name of a + *file* with one z-value (read from the last column) for each + polygon in the input data. To apply it to the fill color, use + ``color='+z'``. To apply it to the pen color, append **+z** to + ``pen``. + {c} + {f} + label : str + Add a legend entry for the symbol or line being plotted. + {p} + {t} + *transparency* can also be a 1d array to set varying transparency + for symbols. + """ + kwargs = self._preprocess(**kwargs) # pylint: disable=protected-access + + kind = data_kind(data, x, y, z) + + extra_arrays = [] + if "S" in kwargs and kwargs["S"][0] in "vV" and direction is not None: + extra_arrays.extend(direction) + if "G" in kwargs and not isinstance(kwargs["G"], str): + if kind != "vectors": + raise GMTInvalidInput( + "Can't use arrays for color if data is matrix or file." + ) + extra_arrays.append(kwargs["G"]) + del kwargs["G"] + if sizes is not None: + if kind != "vectors": + raise GMTInvalidInput( + "Can't use arrays for sizes if data is matrix or file." + ) + extra_arrays.append(sizes) + + if "t" in kwargs and is_nonstr_iter(kwargs["t"]): + extra_arrays.append(kwargs["t"]) + kwargs["t"] = "" + + with Session() as lib: + # Choose how data will be passed in to the module + if kind == "file": + file_context = dummy_context(data) + elif kind == "matrix": + file_context = lib.virtualfile_from_matrix(data) + elif kind == "vectors": + file_context = lib.virtualfile_from_vectors( + np.atleast_1d(x), np.atleast_1d(y), np.atleast_1d(z), *extra_arrays + ) + + with file_context as fname: + arg_str = " ".join([fname, build_arg_string(kwargs)]) + lib.call_module("plot3d", arg_str) diff --git a/pygmt/src/subplot.py b/pygmt/src/subplot.py index fdf11856bf1..c60871ebb81 100644 --- a/pygmt/src/subplot.py +++ b/pygmt/src/subplot.py @@ -1,232 +1,232 @@ -""" -subplot - Manage modern mode figure subplot configuration and selection. -""" -import contextlib - -from pygmt.clib import Session -from pygmt.exceptions import GMTInvalidInput -from pygmt.helpers import ( - build_arg_string, - fmt_docstring, - is_nonstr_iter, - kwargs_to_strings, - use_alias, -) - - -@fmt_docstring -@contextlib.contextmanager -@use_alias( - Ff="figsize", - Fs="subsize", - A="autolabel", - B="frame", - C="clearance", - J="projecton", - M="margins", - R="region", - SC="sharex", - SR="sharey", - T="title", - V="verbose", - X="xshift", - Y="yshift", -) -@kwargs_to_strings(Ff="sequence", Fs="sequence", M="sequence", R="sequence") -def subplot(self, nrows=1, ncols=1, **kwargs): - r""" - Create multi-panel subplot figures. - - This function is used to split the current figure into a rectangular layout - of subplots that each may contain a single self-contained figure. Begin by - defining the layout of the entire multi-panel illustration. Several - parameters are available to specify the systematic layout, labeling, - dimensions, and more for the subplots. - - Full option list at :gmt-docs:`subplot.html#synopsis-begin-mode` - - {aliases} - - Parameters - ---------- - nrows : int - Number of vertical rows of the subplot grid. - ncols : int - Number of horizontal columns of the subplot grid. - figsize : tuple - Specify the final figure dimensions as (*width*, *height*). - subsize : tuple - Specify the dimensions of each subplot directly as (*width*, *height*). - Note that only one of ``figsize`` or ``subsize`` can be provided at - once. - - autolabel : bool or str - [*autolabel*][**+c**\ *dx*\ [/*dy*]][**+g**\ *fill*][**+j**\|\ **J**\ - *refpoint*][**+o**\ *dx*\ [/*dy*]][**+p**\ *pen*][**+r**\|\ **R**] - [**+v**]. - Specify automatic tagging of each subplot. Append either a number or - letter [a]. This sets the tag of the first, top-left subplot and others - follow sequentially. Surround the number or letter by parentheses on - any side if these should be typeset as part of the tag. Use - **+j**\|\ **J**\ *refpoint* to specify where the tag should be placed - in the subplot [TL]. Note: **+j** sets the justification of the tag to - *refpoint* (suitable for interior tags) while **+J** instead selects - the mirror opposite (suitable for exterior tags). Append - **+c**\ *dx*\[/*dy*] to set the clearance between the tag and a - surrounding text box requested via **+g** or **+p** [3p/3p, i.e., 15% - of the :gmt-term:`FONT_TAG` size dimension]. Append **+g**\ *fill* to - paint the tag's text box with *fill* [no painting]. Append - **+o**\ *dx*\ [/*dy*] to offset the tag's reference point in the - direction implied by the justification [4p/4p, i.e., 20% of the - :gmt-term:`FONT_TAG` size]. Append **+p**\ *pen* to draw the outline of - the tag's text box using selected *pen* [no outline]. Append **+r** to - typeset your tag numbers using lowercase Roman numerals; use **+R** for - uppercase Roman numerals [Arabic numerals]. Append **+v** to increase - tag numbers vertically down columns [horizontally across rows]. - {B} - clearance : str or list - [*side*]\ *clearance*. - Reserve a space of dimension *clearance* between the margin and the - subplot on the specified side, using *side* values from **w**, **e**, - **s**, or **n**; or **x** for both **w** and **e**; or **y** for both - **s** and **n**. No *side* means all sides (i.e. ``clearance='1c'`` - would set a clearance of 1 cm on all sides). The option is repeatable - to set aside space on more than one side (e.g. ``clearance=['w1c', - 's2c']`` would set a clearance of 1 cm on west side and 2 cm on south - side). Such space will be left untouched by the main map plotting but - can be accessed by modules that plot scales, bars, text, etc. - {J} - margins : str or list - This is margin space that is added between neighboring subplots (i.e., - the interior margins) in addition to the automatic space added for tick - marks, annotations, and labels. The margins can be specified as either: - - - a single value (for same margin on all sides). E.g. '5c'. - - a pair of values (for setting separate horizontal and vertical - margins). E.g. ['5c', '3c']. - - a set of four values (for setting separate left, right, bottom, and - top margins). E.g. ['1c', '2c', '3c', '4c']. - - The actual gap created is always a sum of the margins for the two - opposing sides (e.g., east plus west or south plus north margins) - [Default is half the primary annotation font size, giving the full - annotation font size as the default gap]. - {R} - sharex : bool or str - Set subplot layout for shared x-axes. Use when all subplots in a column - share a common *x*-range. If ``sharex=True``, the first (i.e., - **t**\ op) and the last (i.e., **b**\ ottom) rows will have - *x*-annotations; use ``sharex='t'`` or ``sharex='b'`` to select only - one of those two rows [both]. Append **+l** if annotated *x*-axes - should have a label [none]; optionally append the label if it is the - same for the entire subplot. Append **+t** to make space for subplot - titles for each row; use **+tc** for top row titles only [no subplot - titles]. - sharey : bool or str - Set subplot layout for shared y-axes. Use when all subplots in a row - share a common *y*-range. If ``sharey=True``, the first (i.e., - **l**\ eft) and the last (i.e., **r**\ ight) columns will have - *y*-annotations; use ``sharey='l'`` or ``sharey='r'`` to select only - one of those two columns [both]. Append **+l** if annotated *y*-axes - will have a label [none]; optionally, append the label if it is the - same for the entire subplot. Append **+p** to make all annotations - axis-parallel [horizontal]; if not used you may have to set - ``clearance`` to secure extra space for long horizontal annotations. - - Notes for ``sharex``/``sharey``: - - - Labels and titles that depends on which row or column are specified - as usual via a subplot's own ``frame`` setting. - - Append **+w** to the ``figsize`` or ``subsize`` parameter to draw - horizontal and vertical lines between interior panels using selected - pen [no lines]. - title : str - While individual subplots can have titles (see ``sharex``/``sharey`` or - ``frame``), the entire figure may also have an overarching *heading* - [no heading]. Font is determined by setting :gmt-term:`FONT_HEADING`. - {V} - {XY} - """ - kwargs = self._preprocess(**kwargs) # pylint: disable=protected-access - # allow for spaces in string with needing double quotes - kwargs["A"] = f'"{kwargs.get("A")}"' if kwargs.get("A") is not None else None - kwargs["T"] = f'"{kwargs.get("T")}"' if kwargs.get("T") else None - - if nrows < 1 or ncols < 1: - raise GMTInvalidInput("Please ensure that both 'nrows'>=1 and 'ncols'>=1.") - if kwargs.get("Ff") and kwargs.get("Fs"): - raise GMTInvalidInput( - "Please provide either one of 'figsize' or 'subsize' only." - ) - - with Session() as lib: - try: - arg_str = " ".join(["begin", f"{nrows}x{ncols}", build_arg_string(kwargs)]) - lib.call_module("subplot", arg_str) - yield - finally: - v_arg = build_arg_string({"V": kwargs.get("V")}) - lib.call_module("subplot", f"end {v_arg}") - - -@fmt_docstring -@contextlib.contextmanager -@use_alias(A="fixedlabel", C="clearance", V="verbose") -def set_panel(self, panel=None, **kwargs): - r""" - Set the current subplot panel to plot on. - - Before you start plotting you must first select the active subplot. Note: - If any *projection* option is passed with the question mark **?** as scale - or width when plotting subplots, then the dimensions of the map are - automatically determined by the subplot size and your region. For Cartesian - plots: If you want the scale to apply equally to both dimensions then you - must specify ``projection="x"`` [The default ``projection="X"`` will fill - the subplot by using unequal scales]. - - {aliases} - - Parameters - ---------- - panel : str or list - *row,col*\|\ *index*. - Sets the current subplot until further notice. **Note**: First *row* - or *col* is 0, not 1. If not given we go to the next subplot by order - specified via ``autolabel`` in :meth:`pygmt.Figure.subplot`. As an - alternative, you may bypass using :meth:`pygmt.Figure.set_panel` and - instead supply the common option **panel**\ =[*row,col*] to the first - plot command you issue in that subplot. GMT maintains information about - the current figure and subplot. Also, you may give the one-dimensional - *index* instead which starts at 0 and follows the row or column order - set via ``autolabel`` in :meth:`pygmt.Figure.subplot`. - - fixedlabel : str - Overrides the automatic labeling with the given string. No modifiers - are allowed. Placement, justification, etc. are all inherited from how - ``autolabel`` was specified by the initial :meth:`pygmt.Figure.subplot` - command. - - clearance : str or list - [*side*]\ *clearance*. - Reserve a space of dimension *clearance* between the margin and the - subplot on the specified side, using *side* values from **w**, **e**, - **s**, or **n**. The option is repeatable to set aside space on more - than one side (e.g. ``clearance=['w1c', 's2c']`` would set a clearance - of 1 cm on west side and 2 cm on south side). Such space will be left - untouched by the main map plotting but can be accessed by modules that - plot scales, bars, text, etc. This setting overrides the common - clearances set by ``clearance`` in the initial - :meth:`pygmt.Figure.subplot` call. - - {V} - """ - kwargs = self._preprocess(**kwargs) # pylint: disable=protected-access - # allow for spaces in string with needing double quotes - kwargs["A"] = f'"{kwargs.get("A")}"' if kwargs.get("A") is not None else None - # convert tuple or list to comma-separated str - panel = ",".join(map(str, panel)) if is_nonstr_iter(panel) else panel - - with Session() as lib: - arg_str = " ".join(["set", f"{panel}", build_arg_string(kwargs)]) - lib.call_module(module="subplot", args=arg_str) - yield +""" +subplot - Manage modern mode figure subplot configuration and selection. +""" +import contextlib + +from pygmt.clib import Session +from pygmt.exceptions import GMTInvalidInput +from pygmt.helpers import ( + build_arg_string, + fmt_docstring, + is_nonstr_iter, + kwargs_to_strings, + use_alias, +) + + +@fmt_docstring +@contextlib.contextmanager +@use_alias( + Ff="figsize", + Fs="subsize", + A="autolabel", + B="frame", + C="clearance", + J="projecton", + M="margins", + R="region", + SC="sharex", + SR="sharey", + T="title", + V="verbose", + X="xshift", + Y="yshift", +) +@kwargs_to_strings(Ff="sequence", Fs="sequence", M="sequence", R="sequence") +def subplot(self, nrows=1, ncols=1, **kwargs): + r""" + Create multi-panel subplot figures. + + This function is used to split the current figure into a rectangular layout + of subplots that each may contain a single self-contained figure. Begin by + defining the layout of the entire multi-panel illustration. Several + parameters are available to specify the systematic layout, labeling, + dimensions, and more for the subplots. + + Full option list at :gmt-docs:`subplot.html#synopsis-begin-mode` + + {aliases} + + Parameters + ---------- + nrows : int + Number of vertical rows of the subplot grid. + ncols : int + Number of horizontal columns of the subplot grid. + figsize : tuple + Specify the final figure dimensions as (*width*, *height*). + subsize : tuple + Specify the dimensions of each subplot directly as (*width*, *height*). + Note that only one of ``figsize`` or ``subsize`` can be provided at + once. + + autolabel : bool or str + [*autolabel*][**+c**\ *dx*\ [/*dy*]][**+g**\ *fill*][**+j**\|\ **J**\ + *refpoint*][**+o**\ *dx*\ [/*dy*]][**+p**\ *pen*][**+r**\|\ **R**] + [**+v**]. + Specify automatic tagging of each subplot. Append either a number or + letter [a]. This sets the tag of the first, top-left subplot and others + follow sequentially. Surround the number or letter by parentheses on + any side if these should be typeset as part of the tag. Use + **+j**\|\ **J**\ *refpoint* to specify where the tag should be placed + in the subplot [TL]. Note: **+j** sets the justification of the tag to + *refpoint* (suitable for interior tags) while **+J** instead selects + the mirror opposite (suitable for exterior tags). Append + **+c**\ *dx*\[/*dy*] to set the clearance between the tag and a + surrounding text box requested via **+g** or **+p** [3p/3p, i.e., 15% + of the :gmt-term:`FONT_TAG` size dimension]. Append **+g**\ *fill* to + paint the tag's text box with *fill* [no painting]. Append + **+o**\ *dx*\ [/*dy*] to offset the tag's reference point in the + direction implied by the justification [4p/4p, i.e., 20% of the + :gmt-term:`FONT_TAG` size]. Append **+p**\ *pen* to draw the outline of + the tag's text box using selected *pen* [no outline]. Append **+r** to + typeset your tag numbers using lowercase Roman numerals; use **+R** for + uppercase Roman numerals [Arabic numerals]. Append **+v** to increase + tag numbers vertically down columns [horizontally across rows]. + {B} + clearance : str or list + [*side*]\ *clearance*. + Reserve a space of dimension *clearance* between the margin and the + subplot on the specified side, using *side* values from **w**, **e**, + **s**, or **n**; or **x** for both **w** and **e**; or **y** for both + **s** and **n**. No *side* means all sides (i.e. ``clearance='1c'`` + would set a clearance of 1 cm on all sides). The option is repeatable + to set aside space on more than one side (e.g. ``clearance=['w1c', + 's2c']`` would set a clearance of 1 cm on west side and 2 cm on south + side). Such space will be left untouched by the main map plotting but + can be accessed by modules that plot scales, bars, text, etc. + {J} + margins : str or list + This is margin space that is added between neighboring subplots (i.e., + the interior margins) in addition to the automatic space added for tick + marks, annotations, and labels. The margins can be specified as either: + + - a single value (for same margin on all sides). E.g. '5c'. + - a pair of values (for setting separate horizontal and vertical + margins). E.g. ['5c', '3c']. + - a set of four values (for setting separate left, right, bottom, and + top margins). E.g. ['1c', '2c', '3c', '4c']. + + The actual gap created is always a sum of the margins for the two + opposing sides (e.g., east plus west or south plus north margins) + [Default is half the primary annotation font size, giving the full + annotation font size as the default gap]. + {R} + sharex : bool or str + Set subplot layout for shared x-axes. Use when all subplots in a column + share a common *x*-range. If ``sharex=True``, the first (i.e., + **t**\ op) and the last (i.e., **b**\ ottom) rows will have + *x*-annotations; use ``sharex='t'`` or ``sharex='b'`` to select only + one of those two rows [both]. Append **+l** if annotated *x*-axes + should have a label [none]; optionally append the label if it is the + same for the entire subplot. Append **+t** to make space for subplot + titles for each row; use **+tc** for top row titles only [no subplot + titles]. + sharey : bool or str + Set subplot layout for shared y-axes. Use when all subplots in a row + share a common *y*-range. If ``sharey=True``, the first (i.e., + **l**\ eft) and the last (i.e., **r**\ ight) columns will have + *y*-annotations; use ``sharey='l'`` or ``sharey='r'`` to select only + one of those two columns [both]. Append **+l** if annotated *y*-axes + will have a label [none]; optionally, append the label if it is the + same for the entire subplot. Append **+p** to make all annotations + axis-parallel [horizontal]; if not used you may have to set + ``clearance`` to secure extra space for long horizontal annotations. + + Notes for ``sharex``/``sharey``: + + - Labels and titles that depends on which row or column are specified + as usual via a subplot's own ``frame`` setting. + - Append **+w** to the ``figsize`` or ``subsize`` parameter to draw + horizontal and vertical lines between interior panels using selected + pen [no lines]. + title : str + While individual subplots can have titles (see ``sharex``/``sharey`` or + ``frame``), the entire figure may also have an overarching *heading* + [no heading]. Font is determined by setting :gmt-term:`FONT_HEADING`. + {V} + {XY} + """ + kwargs = self._preprocess(**kwargs) # pylint: disable=protected-access + # allow for spaces in string with needing double quotes + kwargs["A"] = f'"{kwargs.get("A")}"' if kwargs.get("A") is not None else None + kwargs["T"] = f'"{kwargs.get("T")}"' if kwargs.get("T") else None + + if nrows < 1 or ncols < 1: + raise GMTInvalidInput("Please ensure that both 'nrows'>=1 and 'ncols'>=1.") + if kwargs.get("Ff") and kwargs.get("Fs"): + raise GMTInvalidInput( + "Please provide either one of 'figsize' or 'subsize' only." + ) + + with Session() as lib: + try: + arg_str = " ".join(["begin", f"{nrows}x{ncols}", build_arg_string(kwargs)]) + lib.call_module("subplot", arg_str) + yield + finally: + v_arg = build_arg_string({"V": kwargs.get("V")}) + lib.call_module("subplot", f"end {v_arg}") + + +@fmt_docstring +@contextlib.contextmanager +@use_alias(A="fixedlabel", C="clearance", V="verbose") +def set_panel(self, panel=None, **kwargs): + r""" + Set the current subplot panel to plot on. + + Before you start plotting you must first select the active subplot. Note: + If any *projection* option is passed with the question mark **?** as scale + or width when plotting subplots, then the dimensions of the map are + automatically determined by the subplot size and your region. For Cartesian + plots: If you want the scale to apply equally to both dimensions then you + must specify ``projection="x"`` [The default ``projection="X"`` will fill + the subplot by using unequal scales]. + + {aliases} + + Parameters + ---------- + panel : str or list + *row,col*\|\ *index*. + Sets the current subplot until further notice. **Note**: First *row* + or *col* is 0, not 1. If not given we go to the next subplot by order + specified via ``autolabel`` in :meth:`pygmt.Figure.subplot`. As an + alternative, you may bypass using :meth:`pygmt.Figure.set_panel` and + instead supply the common option **panel**\ =[*row,col*] to the first + plot command you issue in that subplot. GMT maintains information about + the current figure and subplot. Also, you may give the one-dimensional + *index* instead which starts at 0 and follows the row or column order + set via ``autolabel`` in :meth:`pygmt.Figure.subplot`. + + fixedlabel : str + Overrides the automatic labeling with the given string. No modifiers + are allowed. Placement, justification, etc. are all inherited from how + ``autolabel`` was specified by the initial :meth:`pygmt.Figure.subplot` + command. + + clearance : str or list + [*side*]\ *clearance*. + Reserve a space of dimension *clearance* between the margin and the + subplot on the specified side, using *side* values from **w**, **e**, + **s**, or **n**. The option is repeatable to set aside space on more + than one side (e.g. ``clearance=['w1c', 's2c']`` would set a clearance + of 1 cm on west side and 2 cm on south side). Such space will be left + untouched by the main map plotting but can be accessed by modules that + plot scales, bars, text, etc. This setting overrides the common + clearances set by ``clearance`` in the initial + :meth:`pygmt.Figure.subplot` call. + + {V} + """ + kwargs = self._preprocess(**kwargs) # pylint: disable=protected-access + # allow for spaces in string with needing double quotes + kwargs["A"] = f'"{kwargs.get("A")}"' if kwargs.get("A") is not None else None + # convert tuple or list to comma-separated str + panel = ",".join(map(str, panel)) if is_nonstr_iter(panel) else panel + + with Session() as lib: + arg_str = " ".join(["set", f"{panel}", build_arg_string(kwargs)]) + lib.call_module(module="subplot", args=arg_str) + yield diff --git a/pygmt/src/surface.py b/pygmt/src/surface.py index 0c6fc85f0d0..cc0248617d4 100644 --- a/pygmt/src/surface.py +++ b/pygmt/src/surface.py @@ -1,102 +1,102 @@ -""" -surface - Grids table data using adjustable tension continuous curvature -splines. -""" -import xarray as xr -from pygmt.clib import Session -from pygmt.exceptions import GMTInvalidInput -from pygmt.helpers import ( - GMTTempFile, - build_arg_string, - data_kind, - dummy_context, - fmt_docstring, - kwargs_to_strings, - use_alias, -) - - -@fmt_docstring -@use_alias(I="spacing", R="region", G="outfile", V="verbose", f="coltypes") -@kwargs_to_strings(R="sequence") -def surface(x=None, y=None, z=None, data=None, **kwargs): - r""" - Grids table data using adjustable tension continuous curvature splines. - - Surface reads randomly-spaced (x,y,z) triples and produces gridded values - z(x,y) by solving: - - .. math:: (1 - t)\nabla^2(z)+t\nabla(z) = 0 - - where :math:`t` is a tension factor between 0 and 1, and :math:`\nabla` - indicates the Laplacian operator. - - Takes a matrix, xyz triples, or a file name as input. - - Must provide either ``data`` or ``x``, ``y``, and ``z``. - - Full option list at :gmt-docs:`surface.html` - - {aliases} - - Parameters - ---------- - x/y/z : 1d arrays - Arrays of x and y coordinates and values z of the data points. - data : str or 2d array - Either a data file name or a 2d numpy array with the tabular data. - - spacing : str - *xinc*\[\ *unit*\][**+e**\|\ **n**]\ - [/*yinc*\ [*unit*][**+e**\|\ **n**]]. - *xinc* [and optionally *yinc*] is the grid spacing. - - region : str or list - *xmin/xmax/ymin/ymax*\[**+r**][**+u**\ *unit*]. - Specify the region of interest. - - outfile : str - Optional. The file name for the output netcdf file with extension .nc - to store the grid in. - - {V} - {f} - - Returns - ------- - ret: xarray.DataArray or None - Return type depends on whether the ``outfile`` parameter is set: - - - :class:`xarray.DataArray`: if ``outfile`` is not set - - None if ``outfile`` is set (grid output will be stored in file set by - ``outfile``) - """ - kind = data_kind(data, x, y, z) - if kind == "vectors" and z is None: - raise GMTInvalidInput("Must provide z with x and y.") - - with GMTTempFile(suffix=".nc") as tmpfile: - with Session() as lib: - if kind == "file": - file_context = dummy_context(data) - elif kind == "matrix": - file_context = lib.virtualfile_from_matrix(data) - elif kind == "vectors": - file_context = lib.virtualfile_from_vectors(x, y, z) - else: - raise GMTInvalidInput("Unrecognized data type: {}".format(type(data))) - with file_context as infile: - if "G" not in kwargs.keys(): # if outfile is unset, output to tmpfile - kwargs.update({"G": tmpfile.name}) - outfile = kwargs["G"] - arg_str = " ".join([infile, build_arg_string(kwargs)]) - lib.call_module(module="surface", args=arg_str) - - if outfile == tmpfile.name: # if user did not set outfile, return DataArray - with xr.open_dataarray(outfile) as dataarray: - result = dataarray.load() - _ = result.gmt # load GMTDataArray accessor information - elif outfile != tmpfile.name: # if user sets an outfile, return None - result = None - - return result +""" +surface - Grids table data using adjustable tension continuous curvature +splines. +""" +import xarray as xr +from pygmt.clib import Session +from pygmt.exceptions import GMTInvalidInput +from pygmt.helpers import ( + GMTTempFile, + build_arg_string, + data_kind, + dummy_context, + fmt_docstring, + kwargs_to_strings, + use_alias, +) + + +@fmt_docstring +@use_alias(I="spacing", R="region", G="outfile", V="verbose", f="coltypes") +@kwargs_to_strings(R="sequence") +def surface(x=None, y=None, z=None, data=None, **kwargs): + r""" + Grids table data using adjustable tension continuous curvature splines. + + Surface reads randomly-spaced (x,y,z) triples and produces gridded values + z(x,y) by solving: + + .. math:: (1 - t)\nabla^2(z)+t\nabla(z) = 0 + + where :math:`t` is a tension factor between 0 and 1, and :math:`\nabla` + indicates the Laplacian operator. + + Takes a matrix, xyz triples, or a file name as input. + + Must provide either ``data`` or ``x``, ``y``, and ``z``. + + Full option list at :gmt-docs:`surface.html` + + {aliases} + + Parameters + ---------- + x/y/z : 1d arrays + Arrays of x and y coordinates and values z of the data points. + data : str or 2d array + Either a data file name or a 2d numpy array with the tabular data. + + spacing : str + *xinc*\[\ *unit*\][**+e**\|\ **n**]\ + [/*yinc*\ [*unit*][**+e**\|\ **n**]]. + *xinc* [and optionally *yinc*] is the grid spacing. + + region : str or list + *xmin/xmax/ymin/ymax*\[**+r**][**+u**\ *unit*]. + Specify the region of interest. + + outfile : str + Optional. The file name for the output netcdf file with extension .nc + to store the grid in. + + {V} + {f} + + Returns + ------- + ret: xarray.DataArray or None + Return type depends on whether the ``outfile`` parameter is set: + + - :class:`xarray.DataArray`: if ``outfile`` is not set + - None if ``outfile`` is set (grid output will be stored in file set by + ``outfile``) + """ + kind = data_kind(data, x, y, z) + if kind == "vectors" and z is None: + raise GMTInvalidInput("Must provide z with x and y.") + + with GMTTempFile(suffix=".nc") as tmpfile: + with Session() as lib: + if kind == "file": + file_context = dummy_context(data) + elif kind == "matrix": + file_context = lib.virtualfile_from_matrix(data) + elif kind == "vectors": + file_context = lib.virtualfile_from_vectors(x, y, z) + else: + raise GMTInvalidInput("Unrecognized data type: {}".format(type(data))) + with file_context as infile: + if "G" not in kwargs.keys(): # if outfile is unset, output to tmpfile + kwargs.update({"G": tmpfile.name}) + outfile = kwargs["G"] + arg_str = " ".join([infile, build_arg_string(kwargs)]) + lib.call_module(module="surface", args=arg_str) + + if outfile == tmpfile.name: # if user did not set outfile, return DataArray + with xr.open_dataarray(outfile) as dataarray: + result = dataarray.load() + _ = result.gmt # load GMTDataArray accessor information + elif outfile != tmpfile.name: # if user sets an outfile, return None + result = None + + return result diff --git a/pygmt/src/text.py b/pygmt/src/text.py index 4c7b5421410..12906f9627a 100644 --- a/pygmt/src/text.py +++ b/pygmt/src/text.py @@ -1,211 +1,211 @@ -""" -text - Plot text on a figure. -""" -import numpy as np -from pygmt.clib import Session -from pygmt.exceptions import GMTInvalidInput -from pygmt.helpers import ( - build_arg_string, - data_kind, - dummy_context, - fmt_docstring, - is_nonstr_iter, - kwargs_to_strings, - use_alias, -) - - -@fmt_docstring -@use_alias( - R="region", - J="projection", - B="frame", - C="clearance", - D="offset", - G="fill", - N="no_clip", - V="verbose", - W="pen", - X="xshift", - Y="yshift", - c="panel", - f="coltypes", - p="perspective", - t="transparency", -) -@kwargs_to_strings( - R="sequence", - textfiles="sequence_space", - angle="sequence_comma", - font="sequence_comma", - justify="sequence_comma", - c="sequence_comma", - p="sequence", -) -def text_( - self, - textfiles=None, - x=None, - y=None, - position=None, - text=None, - angle=None, - font=None, - justify=None, - **kwargs, -): - r""" - Plot or typeset text strings of variable size, font type, and orientation. - - Must provide at least one of the following combinations as input: - - - ``textfiles`` - - ``x``/``y``, and ``text`` - - ``position`` and ``text`` - - Full parameter list at :gmt-docs:`text.html` - - {aliases} - - Parameters - ---------- - textfiles : str or list - A text data file name, or a list of filenames containing 1 or more - records with (x, y[, angle, font, justify], text). - x/y : float or 1d arrays - The x and y coordinates, or an array of x and y coordinates to plot - the text - position : str - Sets reference point on the map for the text by using x,y - coordinates extracted from ``region`` instead of providing them - through ``x``/``y``. Specify with a two letter (order independent) - code, chosen from: - - * Horizontal: **L**\ (eft), **C**\ (entre), **R**\ (ight) - * Vertical: **T**\ (op), **M**\ (iddle), **B**\ (ottom) - - For example, ``position="TL"`` plots the text at the Upper Left corner - of the map. - text : str or 1d array - The text string, or an array of strings to plot on the figure - angle: int, float, str or bool - Set the angle measured in degrees counter-clockwise from - horizontal (e.g. 30 sets the text at 30 degrees). If no angle is - explicitly given (i.e. ``angle=True``) then the input to ``textfiles`` - must have this as a column. - font : str or bool - Set the font specification with format *size*\ ,\ *font*\ ,\ *color* - where *size* is text size in points, *font* is the font to use, and - *color* sets the font color. For example, - ``font="12p,Helvetica-Bold,red"`` selects a 12p, red, Helvetica-Bold - font. If no font info is explicitly given (i.e. ``font=True``), then - the input to ``textfiles`` must have this information in one of its - columns. - justify : str or bool - Set the alignment which refers to the part of the text string that - will be mapped onto the (x,y) point. Choose a 2 character - combination of **L**, **C**, **R** (for left, center, or right) and - **T**, **M**, **B** for top, middle, or bottom. E.g., **BL** for lower - left. If no justification is explicitly given (i.e. ``justify=True``), - then the input to ``textfiles`` must have this as a column. - {J} - {R} - clearance : str - [*dx/dy*][**+to**\|\ **O**\|\ **c**\|\ **C**]. - Adjust the clearance between the text and the surrounding box - [Default is 15% of the font size]. Only used if ``pen`` or ``fill`` are - specified. Append the unit you want (*c* for cm, *i* for inch, or *p* - for point; if not given we consult **PROJ_LENGTH_UNIT**) or *%* for a - percentage of the font size. Optionally, use modifier **+t** to set - the shape of the textbox when using ``fill`` and/or ``pen``. Append - lower case **o** to get a straight rectangle [Default is **o**]. Append - upper case **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. - fill : str - Sets the shade or color used for filling the text box [Default is - no fill]. - offset : str - [**j**\|\ **J**]\ *dx*\[/*dy*][**+v**\[*pen*]]. - Offsets the text from the projected (x,y) point by *dx*,\ *dy* [0/0]. - If *dy* is not specified then it is set equal to *dx*. Use **j** to - offset the text away from the point instead (i.e., the text - justification will determine the direction of the shift). Using - **J** will shorten diagonal offsets at corners by sqrt(2). - Optionally, append **+v** which will draw a line from the original - point to the shifted point; append a pen to change the attributes - for this line. - pen : str - Sets the pen used to draw a rectangle around the text string - (see ``clearance``) [Default is width = default, color = black, - style = solid]. - no_clip : bool - Do NOT clip text at map boundaries [Default is will clip]. - {V} - {XY} - {c} - {f} - {p} - {t} - """ - - # pylint: disable=too-many-locals - # pylint: disable=too-many-branches - - kwargs = self._preprocess(**kwargs) # pylint: disable=protected-access - - # Ensure inputs are either textfiles, x/y/text, or position/text - if position is None: - kind = data_kind(textfiles, x, y, text) - else: - if x is not None or y is not None: - raise GMTInvalidInput( - "Provide either position only, or x/y pairs, not both" - ) - kind = "vectors" - - if kind == "vectors" and text is None: - raise GMTInvalidInput("Must provide text with x/y pairs or position") - - # Build the -F option in gmt text. - if "F" not in kwargs.keys() and ( - ( - position is not None - or angle is not None - or font is not None - or justify is not None - ) - ): - kwargs.update({"F": ""}) - if angle is not None and isinstance(angle, (int, float, str)): - kwargs["F"] += f"+a{str(angle)}" - if font is not None and isinstance(font, str): - kwargs["F"] += f"+f{font}" - if justify is not None and isinstance(justify, str): - kwargs["F"] += f"+j{justify}" - if position is not None and isinstance(position, str): - kwargs["F"] += f'+c{position}+t"{text}"' - - extra_arrays = [] - # If an array of transparency is given, GMT will read it from - # the last numerical column per data record. - if "t" in kwargs and is_nonstr_iter(kwargs["t"]): - extra_arrays.append(kwargs["t"]) - kwargs["t"] = "" - - with Session() as lib: - file_context = dummy_context(textfiles) if kind == "file" else "" - if kind == "vectors": - if position is not None: - file_context = dummy_context("") - else: - file_context = lib.virtualfile_from_vectors( - np.atleast_1d(x), - np.atleast_1d(y), - *extra_arrays, - # text must be in str type, see issue #706 - np.atleast_1d(text).astype(str), - ) - with file_context as fname: - arg_str = " ".join([fname, build_arg_string(kwargs)]) - lib.call_module("text", arg_str) +""" +text - Plot text on a figure. +""" +import numpy as np +from pygmt.clib import Session +from pygmt.exceptions import GMTInvalidInput +from pygmt.helpers import ( + build_arg_string, + data_kind, + dummy_context, + fmt_docstring, + is_nonstr_iter, + kwargs_to_strings, + use_alias, +) + + +@fmt_docstring +@use_alias( + R="region", + J="projection", + B="frame", + C="clearance", + D="offset", + G="fill", + N="no_clip", + V="verbose", + W="pen", + X="xshift", + Y="yshift", + c="panel", + f="coltypes", + p="perspective", + t="transparency", +) +@kwargs_to_strings( + R="sequence", + textfiles="sequence_space", + angle="sequence_comma", + font="sequence_comma", + justify="sequence_comma", + c="sequence_comma", + p="sequence", +) +def text_( + self, + textfiles=None, + x=None, + y=None, + position=None, + text=None, + angle=None, + font=None, + justify=None, + **kwargs, +): + r""" + Plot or typeset text strings of variable size, font type, and orientation. + + Must provide at least one of the following combinations as input: + + - ``textfiles`` + - ``x``/``y``, and ``text`` + - ``position`` and ``text`` + + Full parameter list at :gmt-docs:`text.html` + + {aliases} + + Parameters + ---------- + textfiles : str or list + A text data file name, or a list of filenames containing 1 or more + records with (x, y[, angle, font, justify], text). + x/y : float or 1d arrays + The x and y coordinates, or an array of x and y coordinates to plot + the text + position : str + Sets reference point on the map for the text by using x,y + coordinates extracted from ``region`` instead of providing them + through ``x``/``y``. Specify with a two letter (order independent) + code, chosen from: + + * Horizontal: **L**\ (eft), **C**\ (entre), **R**\ (ight) + * Vertical: **T**\ (op), **M**\ (iddle), **B**\ (ottom) + + For example, ``position="TL"`` plots the text at the Upper Left corner + of the map. + text : str or 1d array + The text string, or an array of strings to plot on the figure + angle: int, float, str or bool + Set the angle measured in degrees counter-clockwise from + horizontal (e.g. 30 sets the text at 30 degrees). If no angle is + explicitly given (i.e. ``angle=True``) then the input to ``textfiles`` + must have this as a column. + font : str or bool + Set the font specification with format *size*\ ,\ *font*\ ,\ *color* + where *size* is text size in points, *font* is the font to use, and + *color* sets the font color. For example, + ``font="12p,Helvetica-Bold,red"`` selects a 12p, red, Helvetica-Bold + font. If no font info is explicitly given (i.e. ``font=True``), then + the input to ``textfiles`` must have this information in one of its + columns. + justify : str or bool + Set the alignment which refers to the part of the text string that + will be mapped onto the (x,y) point. Choose a 2 character + combination of **L**, **C**, **R** (for left, center, or right) and + **T**, **M**, **B** for top, middle, or bottom. E.g., **BL** for lower + left. If no justification is explicitly given (i.e. ``justify=True``), + then the input to ``textfiles`` must have this as a column. + {J} + {R} + clearance : str + [*dx/dy*][**+to**\|\ **O**\|\ **c**\|\ **C**]. + Adjust the clearance between the text and the surrounding box + [Default is 15% of the font size]. Only used if ``pen`` or ``fill`` are + specified. Append the unit you want (*c* for cm, *i* for inch, or *p* + for point; if not given we consult **PROJ_LENGTH_UNIT**) or *%* for a + percentage of the font size. Optionally, use modifier **+t** to set + the shape of the textbox when using ``fill`` and/or ``pen``. Append + lower case **o** to get a straight rectangle [Default is **o**]. Append + upper case **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. + fill : str + Sets the shade or color used for filling the text box [Default is + no fill]. + offset : str + [**j**\|\ **J**]\ *dx*\[/*dy*][**+v**\[*pen*]]. + Offsets the text from the projected (x,y) point by *dx*,\ *dy* [0/0]. + If *dy* is not specified then it is set equal to *dx*. Use **j** to + offset the text away from the point instead (i.e., the text + justification will determine the direction of the shift). Using + **J** will shorten diagonal offsets at corners by sqrt(2). + Optionally, append **+v** which will draw a line from the original + point to the shifted point; append a pen to change the attributes + for this line. + pen : str + Sets the pen used to draw a rectangle around the text string + (see ``clearance``) [Default is width = default, color = black, + style = solid]. + no_clip : bool + Do NOT clip text at map boundaries [Default is will clip]. + {V} + {XY} + {c} + {f} + {p} + {t} + """ + + # pylint: disable=too-many-locals + # pylint: disable=too-many-branches + + kwargs = self._preprocess(**kwargs) # pylint: disable=protected-access + + # Ensure inputs are either textfiles, x/y/text, or position/text + if position is None: + kind = data_kind(textfiles, x, y, text) + else: + if x is not None or y is not None: + raise GMTInvalidInput( + "Provide either position only, or x/y pairs, not both" + ) + kind = "vectors" + + if kind == "vectors" and text is None: + raise GMTInvalidInput("Must provide text with x/y pairs or position") + + # Build the -F option in gmt text. + if "F" not in kwargs.keys() and ( + ( + position is not None + or angle is not None + or font is not None + or justify is not None + ) + ): + kwargs.update({"F": ""}) + if angle is not None and isinstance(angle, (int, float, str)): + kwargs["F"] += f"+a{str(angle)}" + if font is not None and isinstance(font, str): + kwargs["F"] += f"+f{font}" + if justify is not None and isinstance(justify, str): + kwargs["F"] += f"+j{justify}" + if position is not None and isinstance(position, str): + kwargs["F"] += f'+c{position}+t"{text}"' + + extra_arrays = [] + # If an array of transparency is given, GMT will read it from + # the last numerical column per data record. + if "t" in kwargs and is_nonstr_iter(kwargs["t"]): + extra_arrays.append(kwargs["t"]) + kwargs["t"] = "" + + with Session() as lib: + file_context = dummy_context(textfiles) if kind == "file" else "" + if kind == "vectors": + if position is not None: + file_context = dummy_context("") + else: + file_context = lib.virtualfile_from_vectors( + np.atleast_1d(x), + np.atleast_1d(y), + *extra_arrays, + # text must be in str type, see issue #706 + np.atleast_1d(text).astype(str), + ) + with file_context as fname: + arg_str = " ".join([fname, build_arg_string(kwargs)]) + lib.call_module("text", arg_str) diff --git a/pygmt/src/which.py b/pygmt/src/which.py index db1eb00c667..f88940b31c6 100644 --- a/pygmt/src/which.py +++ b/pygmt/src/which.py @@ -1,56 +1,56 @@ -""" -which - Find the full path to specified files. -""" -from pygmt.clib import Session -from pygmt.helpers import GMTTempFile, build_arg_string, fmt_docstring, use_alias - - -@fmt_docstring -@use_alias(G="download", V="verbose") -def which(fname, **kwargs): - """ - Find the full path to specified files. - - Reports the full paths to the files given through *fname*. We look for - the file in (1) the current directory, (2) in $GMT_USERDIR (if defined), - (3) in $GMT_DATADIR (if defined), or (4) in $GMT_CACHEDIR (if defined). - - *fname* can also be a downloadable file (either a full URL, a - `@file` special file for downloading from the GMT Site Cache, or - `@earth_relief_*` topography grids). In these cases, use option *download* - to set the desired behavior. If *download* is not used (or False), the file - will not be found. - - Full option list at :gmt-docs:`gmtwhich.html` - - {aliases} - - Parameters - ---------- - fname : str - The file name that you want to check. - download : bool or str - If the file is downloadable and not found, we will try to download the - it. Use True or 'l' (default) to download to the current directory. Use - 'c' to place in the user cache directory or 'u' user data directory - instead. - {V} - - Returns - ------- - path : str - The path of the file, depending on the options used. - - Raises - ------ - FileNotFoundError - If the file is not found. - """ - with GMTTempFile() as tmpfile: - arg_str = " ".join([fname, build_arg_string(kwargs), "->" + tmpfile.name]) - with Session() as lib: - lib.call_module("which", arg_str) - path = tmpfile.read().strip() - if not path: - raise FileNotFoundError("File '{}' not found.".format(fname)) - return path +""" +which - Find the full path to specified files. +""" +from pygmt.clib import Session +from pygmt.helpers import GMTTempFile, build_arg_string, fmt_docstring, use_alias + + +@fmt_docstring +@use_alias(G="download", V="verbose") +def which(fname, **kwargs): + """ + Find the full path to specified files. + + Reports the full paths to the files given through *fname*. We look for + the file in (1) the current directory, (2) in $GMT_USERDIR (if defined), + (3) in $GMT_DATADIR (if defined), or (4) in $GMT_CACHEDIR (if defined). + + *fname* can also be a downloadable file (either a full URL, a + `@file` special file for downloading from the GMT Site Cache, or + `@earth_relief_*` topography grids). In these cases, use option *download* + to set the desired behavior. If *download* is not used (or False), the file + will not be found. + + Full option list at :gmt-docs:`gmtwhich.html` + + {aliases} + + Parameters + ---------- + fname : str + The file name that you want to check. + download : bool or str + If the file is downloadable and not found, we will try to download the + it. Use True or 'l' (default) to download to the current directory. Use + 'c' to place in the user cache directory or 'u' user data directory + instead. + {V} + + Returns + ------- + path : str + The path of the file, depending on the options used. + + Raises + ------ + FileNotFoundError + If the file is not found. + """ + with GMTTempFile() as tmpfile: + arg_str = " ".join([fname, build_arg_string(kwargs), "->" + tmpfile.name]) + with Session() as lib: + lib.call_module("which", arg_str) + path = tmpfile.read().strip() + if not path: + raise FileNotFoundError("File '{}' not found.".format(fname)) + return path diff --git a/pygmt/src/x2sys_cross.py b/pygmt/src/x2sys_cross.py index dd9fef6c61d..b06483f2d15 100644 --- a/pygmt/src/x2sys_cross.py +++ b/pygmt/src/x2sys_cross.py @@ -1,240 +1,240 @@ -""" -x2sys_cross - Calculate crossovers between track data files. -""" -import contextlib -import os -from pathlib import Path - -import pandas as pd -from pygmt.clib import Session -from pygmt.exceptions import GMTInvalidInput -from pygmt.helpers import ( - GMTTempFile, - build_arg_string, - data_kind, - dummy_context, - fmt_docstring, - kwargs_to_strings, - unique_name, - use_alias, -) - - -@contextlib.contextmanager -def tempfile_from_dftrack(track, suffix): - """ - Saves pandas.DataFrame track table to a temporary tab-separated ASCII text - file with a unique name (to prevent clashes when running x2sys_cross), - adding a suffix extension to the end. - - Parameters - ---------- - track : pandas.DataFrame - A table holding track data with coordinate (x, y) or (lon, lat) values, - and (optionally) time (t). - suffix : str - File extension, e.g. xyz, tsv, etc. - - Yields - ------ - tmpfilename : str - A temporary tab-separated value file with a unique name holding the - track data. E.g. 'track-1a2b3c4.tsv'. - """ - try: - tmpfilename = f"track-{unique_name()[:7]}.{suffix}" - track.to_csv( - path_or_buf=tmpfilename, - sep="\t", - index=False, - date_format="%Y-%m-%dT%H:%M:%S.%fZ", - ) - yield tmpfilename - finally: - os.remove(tmpfilename) - - -@fmt_docstring -@use_alias( - A="combitable", - C="runtimes", - D="override", - I="interpolation", - R="region", - S="speed", - T="tag", - Q="coe", - V="verbose", - W="numpoints", - Z="trackvalues", -) -@kwargs_to_strings(R="sequence") -def x2sys_cross(tracks=None, outfile=None, **kwargs): - r""" - Calculate crossovers between track data files. - - Determines all intersections between ("external cross-overs") or within - ("internal cross-overs") tracks (Cartesian or geographic), and report the - time, position, distance along track, heading and speed along each track - segment, and the crossover error (COE) and mean values for all observables. - By default, :meth:`pygmt.x2sys_cross` will look for both external and - internal COEs. As an option, you may choose to project all data using one - of the map projections prior to calculating the COE. - - Full option list at :gmt-docs:`supplements/x2sys/x2sys_cross.html` - - {aliases} - - Parameters - ---------- - tracks : pandas.DataFrame or str or list - A table or a list of tables with (x, y) or (lon, lat) values in the - first two columns. Track(s) can be provided as pandas DataFrame tables - or file names. Supported file formats are ASCII, native binary, or - COARDS netCDF 1-D data. More columns may also be present. - - If the filenames are missing their file extension, we will append the - suffix specified for this TAG. Track files will be searched for first - in the current directory and second in all directories listed in - $X2SYS_HOME/TAG/TAG_paths.txt (if it exists). [If $X2SYS_HOME is not - set it will default to $GMT_SHAREDIR/x2sys]. (Note: MGD77 files will - also be looked for via $MGD77_HOME/mgd77_paths.txt and .gmt files - will be searched for via $GMT_SHAREDIR/mgg/gmtfile_paths). - - outfile : str - Optional. The file name for the output ASCII txt file to store the - table in. - - tag : str - Specify the x2sys TAG which identifies the attributes of this data - type. - - combitable : str - Only process the pair-combinations found in the file *combitable* - [Default process all possible combinations among the specified files]. - The file *combitable* is created by :gmt-docs:`x2sys_get's -L option - `. - - runtimes : bool or str - Compute and append the processing run-time for each pair to the - progress message (use ``runtimes=True``). Pass in a filename (e.g. - ``runtimes="file.txt"``) to save these run-times to file. The idea here - is to use the knowledge of run-times to split the main process in a - number of sub-processes that can each be launched in a different - processor of your multi-core machine. See the MATLAB function - `split_file4coes.m - `_. - - override : bool or str - **S**\|\ **N**. - Control how geographic coordinates are handled (Cartesian data are - unaffected). By default, we determine if the data are closer to one - pole than the other, and then we use a cylindrical polar conversion to - avoid problems with longitude jumps. You can turn this off entirely - with ``override`` and then the calculations uses the original data (we - have protections against longitude jumps). However, you can force the - selection of the pole for the projection by appending **S** or **N** - for the south or north pole, respectively. The conversion is used - because the algorithm used to find crossovers is inherently a - Cartesian algorithm that can run into trouble with data that has large - longitudinal range at higher latitudes. - - interpolation : str - **l**\|\ **a**\|\ **c**. - Sets the interpolation mode for estimating values at the crossover. - Choose among: - - - **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 - [Default is all COEs]. - - {R} - - speed : str or list - **l**\|\ **u**\|\ **h**\ *speed*. - Defines window of track speeds. If speeds are outside this window we do - not calculate a COE. Specify: - - - **l** sets lower speed [Default is 0]. - - **u** sets upper speed [Default is infinity]. - - **h** does not limit the speed but sets a lower speed below which - headings will not be computed (i.e., set to NaN) [Default - calculates headings regardless of speed]. - - For example, you can use ``speed=["l0", "u10", "h5"]`` to set a lower - speed of 0, upper speed of 10, and disable heading calculations for - speeds below 5. - - {V} - - numpoints : int - Give the maximum number of data points on either side of the crossover - to use in the spline interpolation [Default is 3]. - - trackvalues : bool - Report the values of each track at the crossover [Default reports the - crossover value and the mean value]. - - Returns - ------- - crossover_errors : :class:`pandas.DataFrame` or None - Table containing crossover error information. - Return type depends on whether the ``outfile`` parameter is set: - - - :class:`pandas.DataFrame` with (x, y, ..., etc) if ``outfile`` is not - set - - None if ``outfile`` is set (track output will be stored in the set in - ``outfile``) - """ - with Session() as lib: - file_contexts = [] - for track in tracks: - kind = data_kind(track) - if kind == "file": - file_contexts.append(dummy_context(track)) - elif kind == "matrix": - # find suffix (-E) of trackfiles used (e.g. xyz, csv, etc) from - # $X2SYS_HOME/TAGNAME/TAGNAME.tag file - lastline = ( - Path(os.environ["X2SYS_HOME"], kwargs["T"], f"{kwargs['T']}.tag") - .read_text() - .strip() - .split("\n")[-1] - ) # e.g. "-Dxyz -Etsv -I1/1" - for item in sorted(lastline.split()): # sort list alphabetically - if item.startswith(("-E", "-D")): # prefer -Etsv over -Dxyz - suffix = item[2:] # e.g. tsv (1st choice) or xyz (2nd choice) - - # Save pandas.DataFrame track data to temporary file - file_contexts.append(tempfile_from_dftrack(track=track, suffix=suffix)) - else: - raise GMTInvalidInput(f"Unrecognized data type: {type(track)}") - - with GMTTempFile(suffix=".txt") as tmpfile: - with contextlib.ExitStack() as stack: - fnames = [stack.enter_context(c) for c in file_contexts] - if outfile is None: - outfile = tmpfile.name - arg_str = " ".join([*fnames, build_arg_string(kwargs), "->" + outfile]) - lib.call_module(module="x2sys_cross", args=arg_str) - - # Read temporary csv output to a pandas table - if outfile == tmpfile.name: # if outfile isn't set, return pd.DataFrame - # Read the tab-separated ASCII table - table = pd.read_csv( - tmpfile.name, - sep="\t", - header=2, # Column names are on 2nd row - comment=">", # Skip the 3rd row with a ">" - parse_dates=[2, 3], # Datetimes on 3rd and 4th column - ) - # Remove the "# " from "# x" in the first column - table = table.rename(columns={table.columns[0]: table.columns[0][2:]}) - elif outfile != tmpfile.name: # if outfile is set, output in outfile only - table = None - - return table +""" +x2sys_cross - Calculate crossovers between track data files. +""" +import contextlib +import os +from pathlib import Path + +import pandas as pd +from pygmt.clib import Session +from pygmt.exceptions import GMTInvalidInput +from pygmt.helpers import ( + GMTTempFile, + build_arg_string, + data_kind, + dummy_context, + fmt_docstring, + kwargs_to_strings, + unique_name, + use_alias, +) + + +@contextlib.contextmanager +def tempfile_from_dftrack(track, suffix): + """ + Saves pandas.DataFrame track table to a temporary tab-separated ASCII text + file with a unique name (to prevent clashes when running x2sys_cross), + adding a suffix extension to the end. + + Parameters + ---------- + track : pandas.DataFrame + A table holding track data with coordinate (x, y) or (lon, lat) values, + and (optionally) time (t). + suffix : str + File extension, e.g. xyz, tsv, etc. + + Yields + ------ + tmpfilename : str + A temporary tab-separated value file with a unique name holding the + track data. E.g. 'track-1a2b3c4.tsv'. + """ + try: + tmpfilename = f"track-{unique_name()[:7]}.{suffix}" + track.to_csv( + path_or_buf=tmpfilename, + sep="\t", + index=False, + date_format="%Y-%m-%dT%H:%M:%S.%fZ", + ) + yield tmpfilename + finally: + os.remove(tmpfilename) + + +@fmt_docstring +@use_alias( + A="combitable", + C="runtimes", + D="override", + I="interpolation", + R="region", + S="speed", + T="tag", + Q="coe", + V="verbose", + W="numpoints", + Z="trackvalues", +) +@kwargs_to_strings(R="sequence") +def x2sys_cross(tracks=None, outfile=None, **kwargs): + r""" + Calculate crossovers between track data files. + + Determines all intersections between ("external cross-overs") or within + ("internal cross-overs") tracks (Cartesian or geographic), and report the + time, position, distance along track, heading and speed along each track + segment, and the crossover error (COE) and mean values for all observables. + By default, :meth:`pygmt.x2sys_cross` will look for both external and + internal COEs. As an option, you may choose to project all data using one + of the map projections prior to calculating the COE. + + Full option list at :gmt-docs:`supplements/x2sys/x2sys_cross.html` + + {aliases} + + Parameters + ---------- + tracks : pandas.DataFrame or str or list + A table or a list of tables with (x, y) or (lon, lat) values in the + first two columns. Track(s) can be provided as pandas DataFrame tables + or file names. Supported file formats are ASCII, native binary, or + COARDS netCDF 1-D data. More columns may also be present. + + If the filenames are missing their file extension, we will append the + suffix specified for this TAG. Track files will be searched for first + in the current directory and second in all directories listed in + $X2SYS_HOME/TAG/TAG_paths.txt (if it exists). [If $X2SYS_HOME is not + set it will default to $GMT_SHAREDIR/x2sys]. (Note: MGD77 files will + also be looked for via $MGD77_HOME/mgd77_paths.txt and .gmt files + will be searched for via $GMT_SHAREDIR/mgg/gmtfile_paths). + + outfile : str + Optional. The file name for the output ASCII txt file to store the + table in. + + tag : str + Specify the x2sys TAG which identifies the attributes of this data + type. + + combitable : str + Only process the pair-combinations found in the file *combitable* + [Default process all possible combinations among the specified files]. + The file *combitable* is created by :gmt-docs:`x2sys_get's -L option + `. + + runtimes : bool or str + Compute and append the processing run-time for each pair to the + progress message (use ``runtimes=True``). Pass in a filename (e.g. + ``runtimes="file.txt"``) to save these run-times to file. The idea here + is to use the knowledge of run-times to split the main process in a + number of sub-processes that can each be launched in a different + processor of your multi-core machine. See the MATLAB function + `split_file4coes.m + `_. + + override : bool or str + **S**\|\ **N**. + Control how geographic coordinates are handled (Cartesian data are + unaffected). By default, we determine if the data are closer to one + pole than the other, and then we use a cylindrical polar conversion to + avoid problems with longitude jumps. You can turn this off entirely + with ``override`` and then the calculations uses the original data (we + have protections against longitude jumps). However, you can force the + selection of the pole for the projection by appending **S** or **N** + for the south or north pole, respectively. The conversion is used + because the algorithm used to find crossovers is inherently a + Cartesian algorithm that can run into trouble with data that has large + longitudinal range at higher latitudes. + + interpolation : str + **l**\|\ **a**\|\ **c**. + Sets the interpolation mode for estimating values at the crossover. + Choose among: + + - **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 + [Default is all COEs]. + + {R} + + speed : str or list + **l**\|\ **u**\|\ **h**\ *speed*. + Defines window of track speeds. If speeds are outside this window we do + not calculate a COE. Specify: + + - **l** sets lower speed [Default is 0]. + - **u** sets upper speed [Default is infinity]. + - **h** does not limit the speed but sets a lower speed below which + headings will not be computed (i.e., set to NaN) [Default + calculates headings regardless of speed]. + + For example, you can use ``speed=["l0", "u10", "h5"]`` to set a lower + speed of 0, upper speed of 10, and disable heading calculations for + speeds below 5. + + {V} + + numpoints : int + Give the maximum number of data points on either side of the crossover + to use in the spline interpolation [Default is 3]. + + trackvalues : bool + Report the values of each track at the crossover [Default reports the + crossover value and the mean value]. + + Returns + ------- + crossover_errors : :class:`pandas.DataFrame` or None + Table containing crossover error information. + Return type depends on whether the ``outfile`` parameter is set: + + - :class:`pandas.DataFrame` with (x, y, ..., etc) if ``outfile`` is not + set + - None if ``outfile`` is set (track output will be stored in the set in + ``outfile``) + """ + with Session() as lib: + file_contexts = [] + for track in tracks: + kind = data_kind(track) + if kind == "file": + file_contexts.append(dummy_context(track)) + elif kind == "matrix": + # find suffix (-E) of trackfiles used (e.g. xyz, csv, etc) from + # $X2SYS_HOME/TAGNAME/TAGNAME.tag file + lastline = ( + Path(os.environ["X2SYS_HOME"], kwargs["T"], f"{kwargs['T']}.tag") + .read_text() + .strip() + .split("\n")[-1] + ) # e.g. "-Dxyz -Etsv -I1/1" + for item in sorted(lastline.split()): # sort list alphabetically + if item.startswith(("-E", "-D")): # prefer -Etsv over -Dxyz + suffix = item[2:] # e.g. tsv (1st choice) or xyz (2nd choice) + + # Save pandas.DataFrame track data to temporary file + file_contexts.append(tempfile_from_dftrack(track=track, suffix=suffix)) + else: + raise GMTInvalidInput(f"Unrecognized data type: {type(track)}") + + with GMTTempFile(suffix=".txt") as tmpfile: + with contextlib.ExitStack() as stack: + fnames = [stack.enter_context(c) for c in file_contexts] + if outfile is None: + outfile = tmpfile.name + arg_str = " ".join([*fnames, build_arg_string(kwargs), "->" + outfile]) + lib.call_module(module="x2sys_cross", args=arg_str) + + # Read temporary csv output to a pandas table + if outfile == tmpfile.name: # if outfile isn't set, return pd.DataFrame + # Read the tab-separated ASCII table + table = pd.read_csv( + tmpfile.name, + sep="\t", + header=2, # Column names are on 2nd row + comment=">", # Skip the 3rd row with a ">" + parse_dates=[2, 3], # Datetimes on 3rd and 4th column + ) + # Remove the "# " from "# x" in the first column + table = table.rename(columns={table.columns[0]: table.columns[0][2:]}) + elif outfile != tmpfile.name: # if outfile is set, output in outfile only + table = None + + return table diff --git a/pygmt/src/x2sys_init.py b/pygmt/src/x2sys_init.py index e7f6d1f04ed..af9db04402b 100644 --- a/pygmt/src/x2sys_init.py +++ b/pygmt/src/x2sys_init.py @@ -1,114 +1,114 @@ -""" -x2sys_init - Initialize a new x2sys track database -""" -from pygmt.clib import Session -from pygmt.helpers import build_arg_string, fmt_docstring, kwargs_to_strings, use_alias - - -@fmt_docstring -@use_alias( - D="fmtfile", - E="suffix", - F="force", - G="discontinuity", - I="spacing", - N="units", - R="region", - V="verbose", - W="gap", - j="distcalc", -) -@kwargs_to_strings(I="sequence", R="sequence") -def x2sys_init(tag, **kwargs): - r""" - Initialize a new x2sys track database. - - Serves as the starting point for x2sys and initializes a set of data bases - that are particular to one kind of track data. These data, their associated - data bases, and key parameters are given a short-hand notation called an - x2sys TAG. The TAG keeps track of settings such as file format, whether the - data are geographic or not, and the binning resolution for track indices. - - Before you can run :meth:`pygmt.x2sys_init` you must set the environmental - parameter X2SYS_HOME to a directory where you have write permission, which - is where x2sys can keep track of your settings. - - Full option list at :gmt-docs:`supplements/x2sys/x2sys_init.html` - - {aliases} - - Parameters - ---------- - tag : str - The unique name of this data type x2sys TAG. - - fmtfile : str - Format definition file prefix for this data set (see - :gmt-docs:`GMT's Format Definition Files - ` - for more information). Specify full path if the file is not in the - current directory. - - Some file formats already have definition files premade. These include: - - - **mgd77** (for plain ASCII MGD77 data files) - - **mgd77+** (for enhanced MGD77+ netCDF files) - - **gmt** (for old mgg supplement binary files) - - **xy** (for plain ASCII x, y tables) - - **xyz** (same, with one z-column) - - **geo** (for plain ASCII longitude, latitude files) - - **geoz** (same, with one z-column). - - suffix : str - Specifies the file extension (suffix) for these data files. If not - given we use the format definition file prefix as the suffix (see - ``fmtfile``). - - discontinuity : str - **d**\|\ **g**. - Selects geographical coordinates. Append **d** for discontinuity at the - Dateline (makes longitude go from -180 to +180) or **g** for - discontinuity at Greenwich (makes longitude go from 0 to 360 - [Default]). If not given we assume the data are Cartesian. - - spacing : str or list - *dx*\[/*dy*]. - *dx* and optionally *dy* is the grid spacing. Append **m** to - indicate minutes or **s** to indicate seconds for geographic data. - These spacings refer to the binning used in the track bin-index data - base. - - units : str or list - **d**\|\ **s**\ *unit*. - Sets the units used for distance and speed when requested by other - 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 feet/s - - **k** - km or km/hr - - **m** - miles or miles/hr - - **n** - nautical miles or knots - - **u** - survey feet or survey feet/s - - [Default is ``units=["dk", "se"]`` (km and m/s) if ``discontinuity`` is - set, and ``units=["dc", "sc"]`` otherwise (e.g., for Cartesian units)]. - - {R} - {V} - - gap : str or list - **t**\|\ **d**\ *gap*. - Give **t** or **d** and append the corresponding maximum time gap (in - user units; this is typically seconds [Default is infinity]), or - distance (for units, see ``units``) gap [Default is infinity]) allowed - between the two data points immediately on either side of a crossover. - If these limits are exceeded then a data gap is assumed and no COE will - be determined. - - {j} - """ - with Session() as lib: - arg_str = " ".join([tag, build_arg_string(kwargs)]) - lib.call_module(module="x2sys_init", args=arg_str) +""" +x2sys_init - Initialize a new x2sys track database +""" +from pygmt.clib import Session +from pygmt.helpers import build_arg_string, fmt_docstring, kwargs_to_strings, use_alias + + +@fmt_docstring +@use_alias( + D="fmtfile", + E="suffix", + F="force", + G="discontinuity", + I="spacing", + N="units", + R="region", + V="verbose", + W="gap", + j="distcalc", +) +@kwargs_to_strings(I="sequence", R="sequence") +def x2sys_init(tag, **kwargs): + r""" + Initialize a new x2sys track database. + + Serves as the starting point for x2sys and initializes a set of data bases + that are particular to one kind of track data. These data, their associated + data bases, and key parameters are given a short-hand notation called an + x2sys TAG. The TAG keeps track of settings such as file format, whether the + data are geographic or not, and the binning resolution for track indices. + + Before you can run :meth:`pygmt.x2sys_init` you must set the environmental + parameter X2SYS_HOME to a directory where you have write permission, which + is where x2sys can keep track of your settings. + + Full option list at :gmt-docs:`supplements/x2sys/x2sys_init.html` + + {aliases} + + Parameters + ---------- + tag : str + The unique name of this data type x2sys TAG. + + fmtfile : str + Format definition file prefix for this data set (see + :gmt-docs:`GMT's Format Definition Files + ` + for more information). Specify full path if the file is not in the + current directory. + + Some file formats already have definition files premade. These include: + + - **mgd77** (for plain ASCII MGD77 data files) + - **mgd77+** (for enhanced MGD77+ netCDF files) + - **gmt** (for old mgg supplement binary files) + - **xy** (for plain ASCII x, y tables) + - **xyz** (same, with one z-column) + - **geo** (for plain ASCII longitude, latitude files) + - **geoz** (same, with one z-column). + + suffix : str + Specifies the file extension (suffix) for these data files. If not + given we use the format definition file prefix as the suffix (see + ``fmtfile``). + + discontinuity : str + **d**\|\ **g**. + Selects geographical coordinates. Append **d** for discontinuity at the + Dateline (makes longitude go from -180 to +180) or **g** for + discontinuity at Greenwich (makes longitude go from 0 to 360 + [Default]). If not given we assume the data are Cartesian. + + spacing : str or list + *dx*\[/*dy*]. + *dx* and optionally *dy* is the grid spacing. Append **m** to + indicate minutes or **s** to indicate seconds for geographic data. + These spacings refer to the binning used in the track bin-index data + base. + + units : str or list + **d**\|\ **s**\ *unit*. + Sets the units used for distance and speed when requested by other + 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 feet/s + - **k** - km or km/hr + - **m** - miles or miles/hr + - **n** - nautical miles or knots + - **u** - survey feet or survey feet/s + + [Default is ``units=["dk", "se"]`` (km and m/s) if ``discontinuity`` is + set, and ``units=["dc", "sc"]`` otherwise (e.g., for Cartesian units)]. + + {R} + {V} + + gap : str or list + **t**\|\ **d**\ *gap*. + Give **t** or **d** and append the corresponding maximum time gap (in + user units; this is typically seconds [Default is infinity]), or + distance (for units, see ``units``) gap [Default is infinity]) allowed + between the two data points immediately on either side of a crossover. + If these limits are exceeded then a data gap is assumed and no COE will + be determined. + + {j} + """ + with Session() as lib: + arg_str = " ".join([tag, build_arg_string(kwargs)]) + lib.call_module(module="x2sys_init", args=arg_str) diff --git a/pygmt/tests/test_accessor.py b/pygmt/tests/test_accessor.py index 32fa3e4001b..e973f02bc0d 100644 --- a/pygmt/tests/test_accessor.py +++ b/pygmt/tests/test_accessor.py @@ -1,68 +1,68 @@ -""" -Test the behaviour of the GMTDataArrayAccessor class. -""" -import pytest -import xarray as xr -from pygmt import which -from pygmt.exceptions import GMTInvalidInput - - -def test_accessor_gridline_cartesian(): - """ - Check that a grid returns a registration value of 0 when Gridline - registered, and a gtype value of 1 when using Geographic coordinates. - """ - fname = which(fname="@test.dat.nc", download="a") - grid = xr.open_dataarray(fname) - assert grid.gmt.registration == 0 # gridline registration - assert grid.gmt.gtype == 0 # cartesian coordinate type - - -def test_accessor_pixel_geographic(): - """ - Check that a grid returns a registration value of 1 when Pixel registered, - and a gtype value of 0 when using Cartesian coordinates. - """ - fname = which(fname="@earth_relief_01d_p", download="a") - grid = xr.open_dataarray(fname) - assert grid.gmt.registration == 1 # pixel registration - assert grid.gmt.gtype == 1 # geographic coordinate type - - -def test_accessor_set_pixel_registration(): - """ - Check that we can set a grid to be Pixel registered with a registration - value of 1. - """ - grid = xr.DataArray(data=[[0.1, 0.2], [0.3, 0.4]]) - assert grid.gmt.registration == 0 # default to gridline registration - grid.gmt.registration = 1 # set to pixel registration - assert grid.gmt.registration == 1 # ensure changed to pixel registration - - -def test_accessor_set_geographic_cartesian_roundtrip(): - """ - Check that we can set a grid to switch between the default Cartesian - coordinate type using a gtype of 1, set it to Geographic 0, and then back - to Cartesian again 1. - """ - grid = xr.DataArray(data=[[0.1, 0.2], [0.3, 0.4]]) - assert grid.gmt.gtype == 0 # default to cartesian coordinate type - grid.gmt.gtype = 1 # set to geographic type - assert grid.gmt.gtype == 1 # ensure changed to geographic coordinate type - grid.gmt.gtype = 0 # set back to cartesian type - assert grid.gmt.gtype == 0 # ensure changed to cartesian coordinate type - - -def test_accessor_set_non_boolean(): - """ - Check that setting non boolean values on registration and gtype do not - work. - """ - grid = xr.DataArray(data=[[0.1, 0.2], [0.3, 0.4]]) - - with pytest.raises(GMTInvalidInput): - grid.gmt.registration = "2" - - with pytest.raises(GMTInvalidInput): - grid.gmt.gtype = 2 +""" +Test the behaviour of the GMTDataArrayAccessor class. +""" +import pytest +import xarray as xr +from pygmt import which +from pygmt.exceptions import GMTInvalidInput + + +def test_accessor_gridline_cartesian(): + """ + Check that a grid returns a registration value of 0 when Gridline + registered, and a gtype value of 1 when using Geographic coordinates. + """ + fname = which(fname="@test.dat.nc", download="a") + grid = xr.open_dataarray(fname) + assert grid.gmt.registration == 0 # gridline registration + assert grid.gmt.gtype == 0 # cartesian coordinate type + + +def test_accessor_pixel_geographic(): + """ + Check that a grid returns a registration value of 1 when Pixel registered, + and a gtype value of 0 when using Cartesian coordinates. + """ + fname = which(fname="@earth_relief_01d_p", download="a") + grid = xr.open_dataarray(fname) + assert grid.gmt.registration == 1 # pixel registration + assert grid.gmt.gtype == 1 # geographic coordinate type + + +def test_accessor_set_pixel_registration(): + """ + Check that we can set a grid to be Pixel registered with a registration + value of 1. + """ + grid = xr.DataArray(data=[[0.1, 0.2], [0.3, 0.4]]) + assert grid.gmt.registration == 0 # default to gridline registration + grid.gmt.registration = 1 # set to pixel registration + assert grid.gmt.registration == 1 # ensure changed to pixel registration + + +def test_accessor_set_geographic_cartesian_roundtrip(): + """ + Check that we can set a grid to switch between the default Cartesian + coordinate type using a gtype of 1, set it to Geographic 0, and then back + to Cartesian again 1. + """ + grid = xr.DataArray(data=[[0.1, 0.2], [0.3, 0.4]]) + assert grid.gmt.gtype == 0 # default to cartesian coordinate type + grid.gmt.gtype = 1 # set to geographic type + assert grid.gmt.gtype == 1 # ensure changed to geographic coordinate type + grid.gmt.gtype = 0 # set back to cartesian type + assert grid.gmt.gtype == 0 # ensure changed to cartesian coordinate type + + +def test_accessor_set_non_boolean(): + """ + Check that setting non boolean values on registration and gtype do not + work. + """ + grid = xr.DataArray(data=[[0.1, 0.2], [0.3, 0.4]]) + + with pytest.raises(GMTInvalidInput): + grid.gmt.registration = "2" + + with pytest.raises(GMTInvalidInput): + grid.gmt.gtype = 2 diff --git a/pygmt/tests/test_basemap.py b/pygmt/tests/test_basemap.py index b3011588a45..d0119a914e7 100644 --- a/pygmt/tests/test_basemap.py +++ b/pygmt/tests/test_basemap.py @@ -1,127 +1,127 @@ -""" -Tests fig.basemap. -""" -import pytest -from pygmt import Figure -from pygmt.exceptions import GMTInvalidInput -from pygmt.helpers.testing import check_figures_equal - - -def test_basemap_required_args(): - """ - fig.basemap fails when not given required arguments. - """ - fig = Figure() - with pytest.raises(GMTInvalidInput): - fig.basemap(R="10/70/-3/8", J="X4i/3i") - - -@pytest.mark.mpl_image_compare -def test_basemap(): - """ - Create a simple basemap plot. - """ - fig = Figure() - fig.basemap(R="10/70/-3/8", J="X4i/3i", B="afg") - return fig - - -@pytest.mark.mpl_image_compare -def test_basemap_list_region(): - """ - Create a simple basemap plot passing the region as a list. - """ - fig = Figure() - fig.basemap(R=[-20, 50, 200, 500], J="X3i/3i", B="a") - return fig - - -@pytest.mark.mpl_image_compare -def test_basemap_loglog(): - """ - Create a loglog basemap plot. - """ - fig = Figure() - fig.basemap( - R="1/10000/1e20/1e25", - J="X25cl/15cl", - Bx="2+lWavelength", - By="a1pf3+lPower", - B="WS", - ) - return fig - - -@pytest.mark.mpl_image_compare -def test_basemap_power_axis(): - """ - Create a power axis basemap plot. - """ - fig = Figure() - fig.basemap( - R=[0, 100, 0, 5000], J="x1p0.5/-0.001", B=['x1p+l"Crustal age"', "y500+lDepth"] - ) - return fig - - -@check_figures_equal() -def test_basemap_polar(): - """ - Create a polar basemap plot. - """ - fig_ref, fig_test = Figure(), Figure() - # Use single-character arguments for the reference image - fig_ref.basemap(R="0/360/0/1000", J="P6i", B="afg") - fig_test.basemap(region=[0, 360, 0, 1000], projection="P6i", frame="afg") - - return fig_ref, fig_test - - -@check_figures_equal() -def test_basemap_winkel_tripel(): - """ - Create a Winkel Tripel basemap plot. - """ - fig_ref, fig_test = Figure(), Figure() - fig_ref.basemap(R="90/450/-90/90", J="R270/25c", B="afg") - fig_test.basemap(region=[90, 450, -90, 90], projection="R270/25c", frame="afg") - return fig_ref, fig_test - - -@check_figures_equal() -def test_basemap_rose(): - """ - Create a map with coast and use basemap to add a rose. - """ - fig_ref, fig_test = Figure(), Figure() - fig_ref.coast(R="127.5/128.5/26/27", W="1/0.5p") - fig_ref.basemap(Td="jBR+w5c") - fig_test.coast(region=[127.5, 128.5, 26, 27], shorelines="1/0.5p") - fig_test.basemap(rose="jBR+w5c") - return fig_ref, fig_test - - -@check_figures_equal() -def test_basemap_compass(): - """ - Create a map with coast and use basemap to add a compass. - """ - fig_ref, fig_test = Figure(), Figure() - fig_ref.coast(R="127.5/128.5/26/27", W="1/0.5p") - fig_ref.basemap(Tm="jBR+w5c+d11.5") - fig_test.coast(region=[127.5, 128.5, 26, 27], shorelines="1/0.5p") - fig_test.basemap(compass="jBR+w5c+d11.5") - return fig_ref, fig_test - - -@check_figures_equal() -def test_basemap_map_scale(): - """ - Create a map with coast and use basemap to add a map scale. - """ - fig_ref, fig_test = Figure(), Figure() - fig_ref.coast(R="127.5/128.5/26/27", W="1/0.5p") - fig_ref.basemap(L="jMB+c1+w10k+l+f") - fig_test.coast(region=[127.5, 128.5, 26, 27], shorelines="1/0.5p") - fig_test.basemap(map_scale="jMB+c1+w10k+f+l") - return fig_ref, fig_test +""" +Tests fig.basemap. +""" +import pytest +from pygmt import Figure +from pygmt.exceptions import GMTInvalidInput +from pygmt.helpers.testing import check_figures_equal + + +def test_basemap_required_args(): + """ + fig.basemap fails when not given required arguments. + """ + fig = Figure() + with pytest.raises(GMTInvalidInput): + fig.basemap(R="10/70/-3/8", J="X4i/3i") + + +@pytest.mark.mpl_image_compare +def test_basemap(): + """ + Create a simple basemap plot. + """ + fig = Figure() + fig.basemap(R="10/70/-3/8", J="X4i/3i", B="afg") + return fig + + +@pytest.mark.mpl_image_compare +def test_basemap_list_region(): + """ + Create a simple basemap plot passing the region as a list. + """ + fig = Figure() + fig.basemap(R=[-20, 50, 200, 500], J="X3i/3i", B="a") + return fig + + +@pytest.mark.mpl_image_compare +def test_basemap_loglog(): + """ + Create a loglog basemap plot. + """ + fig = Figure() + fig.basemap( + R="1/10000/1e20/1e25", + J="X25cl/15cl", + Bx="2+lWavelength", + By="a1pf3+lPower", + B="WS", + ) + return fig + + +@pytest.mark.mpl_image_compare +def test_basemap_power_axis(): + """ + Create a power axis basemap plot. + """ + fig = Figure() + fig.basemap( + R=[0, 100, 0, 5000], J="x1p0.5/-0.001", B=['x1p+l"Crustal age"', "y500+lDepth"] + ) + return fig + + +@check_figures_equal() +def test_basemap_polar(): + """ + Create a polar basemap plot. + """ + fig_ref, fig_test = Figure(), Figure() + # Use single-character arguments for the reference image + fig_ref.basemap(R="0/360/0/1000", J="P6i", B="afg") + fig_test.basemap(region=[0, 360, 0, 1000], projection="P6i", frame="afg") + + return fig_ref, fig_test + + +@check_figures_equal() +def test_basemap_winkel_tripel(): + """ + Create a Winkel Tripel basemap plot. + """ + fig_ref, fig_test = Figure(), Figure() + fig_ref.basemap(R="90/450/-90/90", J="R270/25c", B="afg") + fig_test.basemap(region=[90, 450, -90, 90], projection="R270/25c", frame="afg") + return fig_ref, fig_test + + +@check_figures_equal() +def test_basemap_rose(): + """ + Create a map with coast and use basemap to add a rose. + """ + fig_ref, fig_test = Figure(), Figure() + fig_ref.coast(R="127.5/128.5/26/27", W="1/0.5p") + fig_ref.basemap(Td="jBR+w5c") + fig_test.coast(region=[127.5, 128.5, 26, 27], shorelines="1/0.5p") + fig_test.basemap(rose="jBR+w5c") + return fig_ref, fig_test + + +@check_figures_equal() +def test_basemap_compass(): + """ + Create a map with coast and use basemap to add a compass. + """ + fig_ref, fig_test = Figure(), Figure() + fig_ref.coast(R="127.5/128.5/26/27", W="1/0.5p") + fig_ref.basemap(Tm="jBR+w5c+d11.5") + fig_test.coast(region=[127.5, 128.5, 26, 27], shorelines="1/0.5p") + fig_test.basemap(compass="jBR+w5c+d11.5") + return fig_ref, fig_test + + +@check_figures_equal() +def test_basemap_map_scale(): + """ + Create a map with coast and use basemap to add a map scale. + """ + fig_ref, fig_test = Figure(), Figure() + fig_ref.coast(R="127.5/128.5/26/27", W="1/0.5p") + fig_ref.basemap(L="jMB+c1+w10k+l+f") + fig_test.coast(region=[127.5, 128.5, 26, 27], shorelines="1/0.5p") + fig_test.basemap(map_scale="jMB+c1+w10k+f+l") + return fig_ref, fig_test diff --git a/pygmt/tests/test_blockmedian.py b/pygmt/tests/test_blockmedian.py index ff0dd1cf059..cf364a7491b 100644 --- a/pygmt/tests/test_blockmedian.py +++ b/pygmt/tests/test_blockmedian.py @@ -1,78 +1,78 @@ -""" -Tests for blockmedian. -""" -import os - -import numpy.testing as npt -import pandas as pd -import pytest -from pygmt import blockmedian -from pygmt.datasets import load_sample_bathymetry -from pygmt.exceptions import GMTInvalidInput -from pygmt.helpers import GMTTempFile, data_kind - - -def test_blockmedian_input_dataframe(): - """ - Run blockmedian by passing in a pandas.DataFrame as input. - """ - dataframe = load_sample_bathymetry() - output = blockmedian(table=dataframe, spacing="5m", region=[245, 255, 20, 30]) - assert isinstance(output, pd.DataFrame) - assert all(dataframe.columns == output.columns) - assert output.shape == (5849, 3) - npt.assert_allclose(output.iloc[0], [245.88819, 29.97895, -385.0]) - - return output - - -def test_blockmedian_wrong_kind_of_input_table_matrix(): - """ - Run blockmedian using table input that is not a pandas.DataFrame but still - a matrix. - """ - dataframe = load_sample_bathymetry() - invalid_table = dataframe.values - assert data_kind(invalid_table) == "matrix" - with pytest.raises(GMTInvalidInput): - blockmedian(table=invalid_table, spacing="5m", region=[245, 255, 20, 30]) - - -def test_blockmedian_wrong_kind_of_input_table_grid(): - """ - Run blockmedian using table input that is not a pandas.DataFrame or file - but a grid. - """ - dataframe = load_sample_bathymetry() - invalid_table = dataframe.bathymetry.to_xarray() - assert data_kind(invalid_table) == "grid" - with pytest.raises(GMTInvalidInput): - blockmedian(table=invalid_table, spacing="5m", region=[245, 255, 20, 30]) - - -def test_blockmedian_input_filename(): - """ - Run blockmedian by passing in an ASCII text file as input. - """ - with GMTTempFile() as tmpfile: - output = blockmedian( - table="@tut_ship.xyz", - spacing="5m", - region=[245, 255, 20, 30], - outfile=tmpfile.name, - ) - assert output is None # check that output is None since outfile is set - assert os.path.exists(path=tmpfile.name) # check that outfile exists at path - output = pd.read_csv(tmpfile.name, sep="\t", header=None) - assert output.shape == (5849, 3) - npt.assert_allclose(output.iloc[0], [245.88819, 29.97895, -385.0]) - - return output - - -def test_blockmedian_without_outfile_setting(): - """ - Run blockmedian by not passing in outfile parameter setting. - """ - with pytest.raises(GMTInvalidInput): - blockmedian(table="@tut_ship.xyz", spacing="5m", region=[245, 255, 20, 30]) +""" +Tests for blockmedian. +""" +import os + +import numpy.testing as npt +import pandas as pd +import pytest +from pygmt import blockmedian +from pygmt.datasets import load_sample_bathymetry +from pygmt.exceptions import GMTInvalidInput +from pygmt.helpers import GMTTempFile, data_kind + + +def test_blockmedian_input_dataframe(): + """ + Run blockmedian by passing in a pandas.DataFrame as input. + """ + dataframe = load_sample_bathymetry() + output = blockmedian(table=dataframe, spacing="5m", region=[245, 255, 20, 30]) + assert isinstance(output, pd.DataFrame) + assert all(dataframe.columns == output.columns) + assert output.shape == (5849, 3) + npt.assert_allclose(output.iloc[0], [245.88819, 29.97895, -385.0]) + + return output + + +def test_blockmedian_wrong_kind_of_input_table_matrix(): + """ + Run blockmedian using table input that is not a pandas.DataFrame but still + a matrix. + """ + dataframe = load_sample_bathymetry() + invalid_table = dataframe.values + assert data_kind(invalid_table) == "matrix" + with pytest.raises(GMTInvalidInput): + blockmedian(table=invalid_table, spacing="5m", region=[245, 255, 20, 30]) + + +def test_blockmedian_wrong_kind_of_input_table_grid(): + """ + Run blockmedian using table input that is not a pandas.DataFrame or file + but a grid. + """ + dataframe = load_sample_bathymetry() + invalid_table = dataframe.bathymetry.to_xarray() + assert data_kind(invalid_table) == "grid" + with pytest.raises(GMTInvalidInput): + blockmedian(table=invalid_table, spacing="5m", region=[245, 255, 20, 30]) + + +def test_blockmedian_input_filename(): + """ + Run blockmedian by passing in an ASCII text file as input. + """ + with GMTTempFile() as tmpfile: + output = blockmedian( + table="@tut_ship.xyz", + spacing="5m", + region=[245, 255, 20, 30], + outfile=tmpfile.name, + ) + assert output is None # check that output is None since outfile is set + assert os.path.exists(path=tmpfile.name) # check that outfile exists at path + output = pd.read_csv(tmpfile.name, sep="\t", header=None) + assert output.shape == (5849, 3) + npt.assert_allclose(output.iloc[0], [245.88819, 29.97895, -385.0]) + + return output + + +def test_blockmedian_without_outfile_setting(): + """ + Run blockmedian by not passing in outfile parameter setting. + """ + with pytest.raises(GMTInvalidInput): + blockmedian(table="@tut_ship.xyz", spacing="5m", region=[245, 255, 20, 30]) diff --git a/pygmt/tests/test_clib.py b/pygmt/tests/test_clib.py index e219c50ef47..f38d34eb17d 100644 --- a/pygmt/tests/test_clib.py +++ b/pygmt/tests/test_clib.py @@ -1,815 +1,815 @@ -# pylint: disable=protected-access -""" -Test the wrappers for the C API. -""" -import os -from contextlib import contextmanager - -import numpy as np -import numpy.testing as npt -import pandas as pd -import pytest -import xarray as xr -from packaging.version import Version -from pygmt import Figure, clib -from pygmt.clib.conversion import dataarray_to_matrix -from pygmt.clib.session import FAMILIES, VIAS -from pygmt.exceptions import ( - GMTCLibError, - GMTCLibNoSessionError, - GMTInvalidInput, - GMTVersionError, -) -from pygmt.helpers import GMTTempFile - -TEST_DATA_DIR = os.path.join(os.path.dirname(__file__), "data") - -with clib.Session() as _lib: - gmt_version = Version(_lib.info["version"]) - - -@contextmanager -def mock(session, func, returns=None, mock_func=None): - """ - Mock a GMT C API function to make it always return a given value. - - Used to test that exceptions are raised when API functions fail by - producing a NULL pointer as output or non-zero status codes. - - Needed because it's not easy to get some API functions to fail without - inducing a Segmentation Fault (which is a good thing because libgmt usually - only fails with errors). - """ - if mock_func is None: - - def mock_api_function(*args): # pylint: disable=unused-argument - """ - A mock GMT API function that always returns a given value. - """ - return returns - - mock_func = mock_api_function - - get_libgmt_func = session.get_libgmt_func - - def mock_get_libgmt_func(name, argtypes=None, restype=None): - """ - Return our mock function. - """ - if name == func: - return mock_func - return get_libgmt_func(name, argtypes, restype) - - setattr(session, "get_libgmt_func", mock_get_libgmt_func) - - yield - - setattr(session, "get_libgmt_func", get_libgmt_func) - - -def test_getitem(): - """ - Test that I can get correct constants from the C lib. - """ - ses = clib.Session() - assert ses["GMT_SESSION_EXTERNAL"] != -99999 - assert ses["GMT_MODULE_CMD"] != -99999 - assert ses["GMT_PAD_DEFAULT"] != -99999 - assert ses["GMT_DOUBLE"] != -99999 - with pytest.raises(GMTCLibError): - ses["A_WHOLE_LOT_OF_JUNK"] # pylint: disable=pointless-statement - - -def test_create_destroy_session(): - """ - Test that create and destroy session are called without errors. - """ - # Create two session and make sure they are not pointing to the same memory - session1 = clib.Session() - session1.create(name="test_session1") - assert session1.session_pointer is not None - session2 = clib.Session() - session2.create(name="test_session2") - assert session2.session_pointer is not None - assert session2.session_pointer != session1.session_pointer - session1.destroy() - session2.destroy() - # Create and destroy a session twice - ses = clib.Session() - for __ in range(2): - with pytest.raises(GMTCLibNoSessionError): - ses.session_pointer # pylint: disable=pointless-statement - ses.create("session1") - assert ses.session_pointer is not None - ses.destroy() - with pytest.raises(GMTCLibNoSessionError): - ses.session_pointer # pylint: disable=pointless-statement - - -def test_create_session_fails(): - """ - Check that an exception is raised when failing to create a session. - """ - ses = clib.Session() - with mock(ses, "GMT_Create_Session", returns=None): - with pytest.raises(GMTCLibError): - ses.create("test-session-name") - # Should fail if trying to create a session before destroying the old one. - ses.create("test1") - with pytest.raises(GMTCLibError): - ses.create("test2") - - -def test_destroy_session_fails(): - """ - Fail to destroy session when given bad input. - """ - ses = clib.Session() - with pytest.raises(GMTCLibNoSessionError): - ses.destroy() - ses.create("test-session") - with mock(ses, "GMT_Destroy_Session", returns=1): - with pytest.raises(GMTCLibError): - ses.destroy() - ses.destroy() - - -def test_call_module(): - """ - Run a command to see if call_module works. - """ - data_fname = os.path.join(TEST_DATA_DIR, "points.txt") - out_fname = "test_call_module.txt" - with clib.Session() as lib: - with GMTTempFile() as out_fname: - lib.call_module("info", "{} -C ->{}".format(data_fname, out_fname.name)) - assert os.path.exists(out_fname.name) - output = out_fname.read().strip() - assert output == "11.5309 61.7074 -2.9289 7.8648 0.1412 0.9338" - - -def test_call_module_invalid_arguments(): - """ - Fails for invalid module arguments. - """ - with clib.Session() as lib: - with pytest.raises(GMTCLibError): - lib.call_module("info", "bogus-data.bla") - - -def test_call_module_invalid_name(): - """ - Fails when given bad input. - """ - with clib.Session() as lib: - with pytest.raises(GMTCLibError): - lib.call_module("meh", "") - - -def test_call_module_error_message(): - """ - Check is the GMT error message was captured. - """ - with clib.Session() as lib: - try: - lib.call_module("info", "bogus-data.bla") - except GMTCLibError as error: - assert "Module 'info' failed with status code" in str(error) - assert "gmtinfo [ERROR]: Cannot find file bogus-data.bla" in str(error) - - -def test_method_no_session(): - """ - Fails when not in a session. - """ - # Create an instance of Session without "with" so no session is created. - lib = clib.Session() - with pytest.raises(GMTCLibNoSessionError): - lib.call_module("gmtdefaults", "") - with pytest.raises(GMTCLibNoSessionError): - lib.session_pointer # pylint: disable=pointless-statement - - -def test_parse_constant_single(): - """ - Parsing a single family argument correctly. - """ - lib = clib.Session() - for family in FAMILIES: - parsed = lib._parse_constant(family, valid=FAMILIES) - assert parsed == lib[family] - - -def test_parse_constant_composite(): - """ - Parsing a composite constant argument (separated by |) correctly. - """ - lib = clib.Session() - test_cases = ((family, via) for family in FAMILIES for via in VIAS) - for family, via in test_cases: - composite = "|".join([family, via]) - expected = lib[family] + lib[via] - parsed = lib._parse_constant(composite, valid=FAMILIES, valid_modifiers=VIAS) - assert parsed == expected - - -def test_parse_constant_fails(): - """ - Check if the function fails when given bad input. - """ - lib = clib.Session() - test_cases = [ - "SOME_random_STRING", - "GMT_IS_DATASET|GMT_VIA_MATRIX|GMT_VIA_VECTOR", - "GMT_IS_DATASET|NOT_A_PROPER_VIA", - "NOT_A_PROPER_FAMILY|GMT_VIA_MATRIX", - "NOT_A_PROPER_FAMILY|ALSO_INVALID", - ] - for test_case in test_cases: - with pytest.raises(GMTInvalidInput): - lib._parse_constant(test_case, valid=FAMILIES, valid_modifiers=VIAS) - - # Should also fail if not given valid modifiers but is using them anyway. - # This should work... - lib._parse_constant( - "GMT_IS_DATASET|GMT_VIA_MATRIX", valid=FAMILIES, valid_modifiers=VIAS - ) - # But this shouldn't. - with pytest.raises(GMTInvalidInput): - lib._parse_constant( - "GMT_IS_DATASET|GMT_VIA_MATRIX", valid=FAMILIES, valid_modifiers=None - ) - - -def test_create_data_dataset(): - """ - Run the function to make sure it doesn't fail badly. - """ - with clib.Session() as lib: - # Dataset from vectors - data_vector = lib.create_data( - family="GMT_IS_DATASET|GMT_VIA_VECTOR", - geometry="GMT_IS_POINT", - mode="GMT_CONTAINER_ONLY", - dim=[10, 20, 1, 0], # columns, rows, layers, dtype - ) - # Dataset from matrices - data_matrix = lib.create_data( - family="GMT_IS_DATASET|GMT_VIA_MATRIX", - geometry="GMT_IS_POINT", - mode="GMT_CONTAINER_ONLY", - dim=[10, 20, 1, 0], - ) - assert data_vector != data_matrix - - -def test_create_data_grid_dim(): - """ - Create a grid ignoring range and inc. - """ - with clib.Session() as lib: - # Grids from matrices using dim - lib.create_data( - family="GMT_IS_GRID|GMT_VIA_MATRIX", - geometry="GMT_IS_SURFACE", - mode="GMT_CONTAINER_ONLY", - dim=[10, 20, 1, 0], - ) - - -def test_create_data_grid_range(): - """ - Create a grid specifying range and inc instead of dim. - """ - with clib.Session() as lib: - # Grids from matrices using range and int - lib.create_data( - family="GMT_IS_GRID|GMT_VIA_MATRIX", - geometry="GMT_IS_SURFACE", - mode="GMT_CONTAINER_ONLY", - ranges=[150.0, 250.0, -20.0, 20.0], - inc=[0.1, 0.2], - ) - - -def test_create_data_fails(): - """ - Check that create_data raises exceptions for invalid input and output. - """ - # Passing in invalid mode - with pytest.raises(GMTInvalidInput): - with clib.Session() as lib: - lib.create_data( - family="GMT_IS_DATASET", - geometry="GMT_IS_SURFACE", - mode="Not_a_valid_mode", - dim=[0, 0, 1, 0], - ranges=[150.0, 250.0, -20.0, 20.0], - inc=[0.1, 0.2], - ) - # Passing in invalid geometry - with pytest.raises(GMTInvalidInput): - with clib.Session() as lib: - lib.create_data( - family="GMT_IS_GRID", - geometry="Not_a_valid_geometry", - mode="GMT_CONTAINER_ONLY", - dim=[0, 0, 1, 0], - ranges=[150.0, 250.0, -20.0, 20.0], - inc=[0.1, 0.2], - ) - - # If the data pointer returned is None (NULL pointer) - with pytest.raises(GMTCLibError): - with clib.Session() as lib: - with mock(lib, "GMT_Create_Data", returns=None): - lib.create_data( - family="GMT_IS_DATASET", - geometry="GMT_IS_SURFACE", - mode="GMT_CONTAINER_ONLY", - dim=[11, 10, 2, 0], - ) - - -def test_virtual_file(): - """ - Test passing in data via a virtual file with a Dataset. - """ - dtypes = "float32 float64 int32 int64 uint32 uint64".split() - shape = (5, 3) - for dtype in dtypes: - 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], dtype=dtype).reshape(shape) - lib.put_matrix(dataset, matrix=data) - # Add the dataset to a virtual file and pass it along to gmt info - 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", "{} ->{}".format(vfile, outfile.name)) - output = outfile.read(keep_tabs=True) - bounds = "\t".join( - ["<{:.0f}/{:.0f}>".format(col.min(), col.max()) for col in data.T] - ) - expected = ": N = {}\t{}\n".format(shape[0], bounds) - assert output == expected - - -def test_virtual_file_fails(): - """ - Check that opening and closing virtual files raises an exception for non- - zero return codes. - """ - vfargs = ( - "GMT_IS_DATASET|GMT_VIA_MATRIX", - "GMT_IS_POINT", - "GMT_IN|GMT_IS_REFERENCE", - None, - ) - - # Mock Open_VirtualFile to test the status check when entering the context. - # If the exception is raised, the code won't get to the closing of the - # virtual file. - with clib.Session() as lib, mock(lib, "GMT_Open_VirtualFile", returns=1): - with pytest.raises(GMTCLibError): - with lib.open_virtual_file(*vfargs): - print("Should not get to this code") - - # Test the status check when closing the virtual file - # Mock the opening to return 0 (success) so that we don't open a file that - # we won't close later. - with clib.Session() as lib, mock(lib, "GMT_Open_VirtualFile", returns=0), mock( - lib, "GMT_Close_VirtualFile", returns=1 - ): - with pytest.raises(GMTCLibError): - with lib.open_virtual_file(*vfargs): - pass - print("Shouldn't get to this code either") - - -def test_virtual_file_bad_direction(): - """ - Test passing an invalid direction argument. - """ - with clib.Session() as lib: - vfargs = ( - "GMT_IS_DATASET|GMT_VIA_MATRIX", - "GMT_IS_POINT", - "GMT_IS_GRID", # The invalid direction argument - 0, - ) - with pytest.raises(GMTInvalidInput): - with lib.open_virtual_file(*vfargs): - print("This should have failed") - - -def test_virtualfile_from_vectors(): - """ - Test the automation for transforming vectors to virtual file dataset. - """ - dtypes = "float32 float64 int32 int64 uint32 uint64".split() - size = 10 - for dtype in dtypes: - x = np.arange(size, dtype=dtype) - y = np.arange(size, size * 2, 1, dtype=dtype) - z = np.arange(size * 2, size * 3, 1, dtype=dtype) - with clib.Session() as lib: - with lib.virtualfile_from_vectors(x, y, z) as vfile: - with GMTTempFile() as outfile: - lib.call_module("info", "{} ->{}".format(vfile, outfile.name)) - output = outfile.read(keep_tabs=True) - bounds = "\t".join( - ["<{:.0f}/{:.0f}>".format(i.min(), i.max()) for i in (x, y, z)] - ) - expected = ": N = {}\t{}\n".format(size, bounds) - assert output == expected - - -@pytest.mark.parametrize("dtype", [str, object]) -def test_virtualfile_from_vectors_one_string_or_object_column(dtype): - """ - Test passing in one column with string or object dtype into virtual file - dataset. - """ - size = 5 - x = np.arange(size, dtype=np.int32) - y = np.arange(size, size * 2, 1, dtype=np.int32) - strings = np.array(["a", "bc", "defg", "hijklmn", "opqrst"], dtype=dtype) - with clib.Session() as lib: - with lib.virtualfile_from_vectors(x, y, strings) as vfile: - with GMTTempFile() as outfile: - lib.call_module("convert", f"{vfile} ->{outfile.name}") - output = outfile.read(keep_tabs=True) - expected = "".join(f"{i}\t{j}\t{k}\n" for i, j, k in zip(x, y, strings)) - assert output == expected - - -@pytest.mark.parametrize("dtype", [str, object]) -def test_virtualfile_from_vectors_two_string_or_object_columns(dtype): - """ - Test passing in two columns of string or object dtype into virtual file - dataset. - """ - size = 5 - x = np.arange(size, dtype=np.int32) - y = np.arange(size, size * 2, 1, dtype=np.int32) - strings1 = np.array(["a", "bc", "def", "ghij", "klmno"], dtype=dtype) - strings2 = np.array(["pqrst", "uvwx", "yz!", "@#", "$"], dtype=dtype) - with clib.Session() as lib: - with lib.virtualfile_from_vectors(x, y, strings1, strings2) as vfile: - with GMTTempFile() as outfile: - lib.call_module("convert", f"{vfile} ->{outfile.name}") - output = outfile.read(keep_tabs=True) - expected = "".join( - f"{h}\t{i}\t{j} {k}\n" for h, i, j, k in zip(x, y, strings1, strings2) - ) - assert output == expected - - -def test_virtualfile_from_vectors_transpose(): - """ - Test transforming matrix columns to virtual file dataset. - """ - dtypes = "float32 float64 int32 int64 uint32 uint64".split() - shape = (7, 5) - for dtype in dtypes: - data = np.arange(shape[0] * shape[1], dtype=dtype).reshape(shape) - with clib.Session() as lib: - with lib.virtualfile_from_vectors(*data.T) as vfile: - with GMTTempFile() as outfile: - lib.call_module("info", "{} -C ->{}".format(vfile, outfile.name)) - output = outfile.read(keep_tabs=True) - bounds = "\t".join( - ["{:.0f}\t{:.0f}".format(col.min(), col.max()) for col in data.T] - ) - expected = "{}\n".format(bounds) - assert output == expected - - -def test_virtualfile_from_vectors_diff_size(): - """ - Test the function fails for arrays of different sizes. - """ - x = np.arange(5) - y = np.arange(6) - with clib.Session() as lib: - with pytest.raises(GMTInvalidInput): - with lib.virtualfile_from_vectors(x, y): - print("This should have failed") - - -def test_virtualfile_from_matrix(): - """ - Test transforming a matrix to virtual file dataset. - """ - dtypes = "float32 float64 int32 int64 uint32 uint64".split() - shape = (7, 5) - for dtype in dtypes: - data = np.arange(shape[0] * shape[1], dtype=dtype).reshape(shape) - with clib.Session() as lib: - with lib.virtualfile_from_matrix(data) as vfile: - with GMTTempFile() as outfile: - lib.call_module("info", "{} ->{}".format(vfile, outfile.name)) - output = outfile.read(keep_tabs=True) - bounds = "\t".join( - ["<{:.0f}/{:.0f}>".format(col.min(), col.max()) for col in data.T] - ) - expected = ": N = {}\t{}\n".format(shape[0], bounds) - assert output == expected - - -def test_virtualfile_from_matrix_slice(): - """ - Test transforming a slice of a larger array to virtual file dataset. - """ - dtypes = "float32 float64 int32 int64 uint32 uint64".split() - shape = (10, 6) - for dtype in dtypes: - full_data = np.arange(shape[0] * shape[1], dtype=dtype).reshape(shape) - rows = 5 - cols = 3 - data = full_data[:rows, :cols] - with clib.Session() as lib: - with lib.virtualfile_from_matrix(data) as vfile: - with GMTTempFile() as outfile: - lib.call_module("info", "{} ->{}".format(vfile, outfile.name)) - output = outfile.read(keep_tabs=True) - bounds = "\t".join( - ["<{:.0f}/{:.0f}>".format(col.min(), col.max()) for col in data.T] - ) - expected = ": N = {}\t{}\n".format(rows, bounds) - assert output == expected - - -def test_virtualfile_from_vectors_pandas(): - """ - Pass vectors to a dataset using pandas Series. - """ - dtypes = "float32 float64 int32 int64 uint32 uint64".split() - size = 13 - for dtype in dtypes: - data = pd.DataFrame( - data=dict( - x=np.arange(size, dtype=dtype), - y=np.arange(size, size * 2, 1, dtype=dtype), - z=np.arange(size * 2, size * 3, 1, dtype=dtype), - ) - ) - with clib.Session() as lib: - with lib.virtualfile_from_vectors(data.x, data.y, data.z) as vfile: - with GMTTempFile() as outfile: - lib.call_module("info", "{} ->{}".format(vfile, outfile.name)) - output = outfile.read(keep_tabs=True) - bounds = "\t".join( - [ - "<{:.0f}/{:.0f}>".format(i.min(), i.max()) - for i in (data.x, data.y, data.z) - ] - ) - expected = ": N = {}\t{}\n".format(size, bounds) - assert output == expected - - -def test_virtualfile_from_vectors_arraylike(): - """ - Pass array-like vectors to a dataset. - """ - size = 13 - x = list(range(0, size, 1)) - y = tuple(range(size, size * 2, 1)) - z = range(size * 2, size * 3, 1) - with clib.Session() as lib: - with lib.virtualfile_from_vectors(x, y, z) as vfile: - with GMTTempFile() as outfile: - lib.call_module("info", "{} ->{}".format(vfile, outfile.name)) - output = outfile.read(keep_tabs=True) - bounds = "\t".join( - ["<{:.0f}/{:.0f}>".format(min(i), max(i)) for i in (x, y, z)] - ) - expected = ": N = {}\t{}\n".format(size, bounds) - assert output == expected - - -def test_extract_region_fails(): - """ - Check that extract region fails if nothing has been plotted. - """ - Figure() - with pytest.raises(GMTCLibError): - with clib.Session() as lib: - lib.extract_region() - - -def test_extract_region_two_figures(): - """ - Extract region should handle multiple figures existing at the same time. - """ - # Make two figures before calling extract_region to make sure that it's - # getting from the current figure, not the last figure. - fig1 = Figure() - region1 = np.array([0, 10, -20, -10]) - fig1.coast(region=region1, projection="M6i", frame=True, land="black") - - fig2 = Figure() - fig2.basemap(region="US.HI+r5", projection="M6i", frame=True) - - # Activate the first figure and extract the region from it - # Use in a different session to avoid any memory problems. - with clib.Session() as lib: - lib.call_module("figure", "{} -".format(fig1._name)) - with clib.Session() as lib: - wesn1 = lib.extract_region() - npt.assert_allclose(wesn1, region1) - - # Now try it with the second one - with clib.Session() as lib: - lib.call_module("figure", "{} -".format(fig2._name)) - with clib.Session() as lib: - wesn2 = lib.extract_region() - npt.assert_allclose(wesn2, np.array([-165.0, -150.0, 15.0, 25.0])) - - -def test_write_data_fails(): - """ - Check that write data raises an exception for non-zero return codes. - """ - # It's hard to make the C API function fail without causing a Segmentation - # Fault. Can't test this if by giving a bad file name because if - # output=='', GMT will just write to stdout and spaces are valid file - # names. Use a mock instead just to exercise this part of the code. - with clib.Session() as lib: - with mock(lib, "GMT_Write_Data", returns=1): - with pytest.raises(GMTCLibError): - lib.write_data( - "GMT_IS_VECTOR", - "GMT_IS_POINT", - "GMT_WRITE_SET", - [1] * 6, - "some-file-name", - None, - ) - - -def test_dataarray_to_matrix_works(): - """ - Check that dataarray_to_matrix returns correct output. - """ - data = np.diag(v=np.arange(3)) - x = np.linspace(start=0, stop=4, num=3) - y = np.linspace(start=5, stop=9, num=3) - grid = xr.DataArray(data, coords=[("y", y), ("x", x)]) - - matrix, region, inc = dataarray_to_matrix(grid) - npt.assert_allclose(actual=matrix, desired=np.flipud(data)) - npt.assert_allclose(actual=region, desired=[x.min(), x.max(), y.min(), y.max()]) - npt.assert_allclose(actual=inc, desired=[x[1] - x[0], y[1] - y[0]]) - - -def test_dataarray_to_matrix_negative_x_increment(): - """ - Check if dataarray_to_matrix returns correct output with flipped x. - """ - data = np.diag(v=np.arange(3)) - x = np.linspace(start=4, stop=0, num=3) - y = np.linspace(start=5, stop=9, num=3) - grid = xr.DataArray(data, coords=[("y", y), ("x", x)]) - - matrix, region, inc = dataarray_to_matrix(grid) - npt.assert_allclose(actual=matrix, desired=np.flip(data, axis=(0, 1))) - npt.assert_allclose(actual=region, desired=[x.min(), x.max(), y.min(), y.max()]) - npt.assert_allclose(actual=inc, desired=[abs(x[1] - x[0]), abs(y[1] - y[0])]) - - -def test_dataarray_to_matrix_negative_y_increment(): - """ - Check that dataarray_to_matrix returns correct output with flipped y. - """ - data = np.diag(v=np.arange(3)) - x = np.linspace(start=0, stop=4, num=3) - y = np.linspace(start=9, stop=5, num=3) - grid = xr.DataArray(data, coords=[("y", y), ("x", x)]) - - matrix, region, inc = dataarray_to_matrix(grid) - npt.assert_allclose(actual=matrix, desired=data) - npt.assert_allclose(actual=region, desired=[x.min(), x.max(), y.min(), y.max()]) - npt.assert_allclose(actual=inc, desired=[abs(x[1] - x[0]), abs(y[1] - y[0])]) - - -def test_dataarray_to_matrix_negative_x_and_y_increment(): - """ - Check that dataarray_to_matrix returns correct output with flipped x/y. - """ - data = np.diag(v=np.arange(3)) - x = np.linspace(start=4, stop=0, num=3) - y = np.linspace(start=9, stop=5, num=3) - grid = xr.DataArray(data, coords=[("y", y), ("x", x)]) - - matrix, region, inc = dataarray_to_matrix(grid) - npt.assert_allclose(actual=matrix, desired=np.fliplr(data)) - npt.assert_allclose(actual=region, desired=[x.min(), x.max(), y.min(), y.max()]) - npt.assert_allclose(actual=inc, desired=[abs(x[1] - x[0]), abs(y[1] - y[0])]) - - -def test_dataarray_to_matrix_dims_fails(): - """ - Check that it fails for > 2 dims. - """ - # Make a 3D regular grid - data = np.ones((10, 12, 11), dtype="float32") - x = np.arange(11) - y = np.arange(12) - z = np.arange(10) - grid = xr.DataArray(data, coords=[("z", z), ("y", y), ("x", x)]) - with pytest.raises(GMTInvalidInput): - dataarray_to_matrix(grid) - - -def test_dataarray_to_matrix_inc_fails(): - """ - Check that it fails for variable increments. - """ - data = np.ones((4, 5), dtype="float64") - x = np.linspace(0, 1, 5) - y = np.logspace(2, 3, 4) - grid = xr.DataArray(data, coords=[("y", y), ("x", x)]) - with pytest.raises(GMTInvalidInput): - dataarray_to_matrix(grid) - - -def test_get_default(): - """ - Make sure get_default works without crashing and gives reasonable results. - """ - with clib.Session() as lib: - assert lib.get_default("API_GRID_LAYOUT") in ["rows", "columns"] - assert int(lib.get_default("API_CORES")) >= 1 - assert Version(lib.get_default("API_VERSION")) >= Version("6.1.1") - - -def test_get_default_fails(): - """ - Make sure get_default raises an exception for invalid names. - """ - with clib.Session() as lib: - with pytest.raises(GMTCLibError): - lib.get_default("NOT_A_VALID_NAME") - - -def test_info_dict(): - """ - Make sure the clib.Session.info dict is working. - """ - # Check if there are no errors or segfaults from getting all of the - # properties. - with clib.Session() as lib: - assert lib.info - - # Mock GMT_Get_Default to return always the same string - def mock_defaults(api, name, value): # pylint: disable=unused-argument - """ - Put 'bla' in the value buffer. - """ - value.value = b"bla" - return 0 - - ses = clib.Session() - ses.create("test-session") - with mock(ses, "GMT_Get_Default", mock_func=mock_defaults): - # Check for an empty dictionary - assert ses.info - for key in ses.info: - assert ses.info[key] == "bla" - ses.destroy() - - -def test_fails_for_wrong_version(): - """ - Make sure the clib.Session raises an exception if GMT is too old. - """ - - # Mock GMT_Get_Default to return an old version - def mock_defaults(api, name, value): # pylint: disable=unused-argument - """ - Return an old version. - """ - if name == b"API_VERSION": - value.value = b"5.4.3" - else: - value.value = b"bla" - return 0 - - lib = clib.Session() - with mock(lib, "GMT_Get_Default", mock_func=mock_defaults): - with pytest.raises(GMTVersionError): - with lib: - assert lib.info["version"] != "5.4.3" - # Make sure the session is closed when the exception is raised. - with pytest.raises(GMTCLibNoSessionError): - assert lib.session_pointer +# pylint: disable=protected-access +""" +Test the wrappers for the C API. +""" +import os +from contextlib import contextmanager + +import numpy as np +import numpy.testing as npt +import pandas as pd +import pytest +import xarray as xr +from packaging.version import Version +from pygmt import Figure, clib +from pygmt.clib.conversion import dataarray_to_matrix +from pygmt.clib.session import FAMILIES, VIAS +from pygmt.exceptions import ( + GMTCLibError, + GMTCLibNoSessionError, + GMTInvalidInput, + GMTVersionError, +) +from pygmt.helpers import GMTTempFile + +TEST_DATA_DIR = os.path.join(os.path.dirname(__file__), "data") + +with clib.Session() as _lib: + gmt_version = Version(_lib.info["version"]) + + +@contextmanager +def mock(session, func, returns=None, mock_func=None): + """ + Mock a GMT C API function to make it always return a given value. + + Used to test that exceptions are raised when API functions fail by + producing a NULL pointer as output or non-zero status codes. + + Needed because it's not easy to get some API functions to fail without + inducing a Segmentation Fault (which is a good thing because libgmt usually + only fails with errors). + """ + if mock_func is None: + + def mock_api_function(*args): # pylint: disable=unused-argument + """ + A mock GMT API function that always returns a given value. + """ + return returns + + mock_func = mock_api_function + + get_libgmt_func = session.get_libgmt_func + + def mock_get_libgmt_func(name, argtypes=None, restype=None): + """ + Return our mock function. + """ + if name == func: + return mock_func + return get_libgmt_func(name, argtypes, restype) + + setattr(session, "get_libgmt_func", mock_get_libgmt_func) + + yield + + setattr(session, "get_libgmt_func", get_libgmt_func) + + +def test_getitem(): + """ + Test that I can get correct constants from the C lib. + """ + ses = clib.Session() + assert ses["GMT_SESSION_EXTERNAL"] != -99999 + assert ses["GMT_MODULE_CMD"] != -99999 + assert ses["GMT_PAD_DEFAULT"] != -99999 + assert ses["GMT_DOUBLE"] != -99999 + with pytest.raises(GMTCLibError): + ses["A_WHOLE_LOT_OF_JUNK"] # pylint: disable=pointless-statement + + +def test_create_destroy_session(): + """ + Test that create and destroy session are called without errors. + """ + # Create two session and make sure they are not pointing to the same memory + session1 = clib.Session() + session1.create(name="test_session1") + assert session1.session_pointer is not None + session2 = clib.Session() + session2.create(name="test_session2") + assert session2.session_pointer is not None + assert session2.session_pointer != session1.session_pointer + session1.destroy() + session2.destroy() + # Create and destroy a session twice + ses = clib.Session() + for __ in range(2): + with pytest.raises(GMTCLibNoSessionError): + ses.session_pointer # pylint: disable=pointless-statement + ses.create("session1") + assert ses.session_pointer is not None + ses.destroy() + with pytest.raises(GMTCLibNoSessionError): + ses.session_pointer # pylint: disable=pointless-statement + + +def test_create_session_fails(): + """ + Check that an exception is raised when failing to create a session. + """ + ses = clib.Session() + with mock(ses, "GMT_Create_Session", returns=None): + with pytest.raises(GMTCLibError): + ses.create("test-session-name") + # Should fail if trying to create a session before destroying the old one. + ses.create("test1") + with pytest.raises(GMTCLibError): + ses.create("test2") + + +def test_destroy_session_fails(): + """ + Fail to destroy session when given bad input. + """ + ses = clib.Session() + with pytest.raises(GMTCLibNoSessionError): + ses.destroy() + ses.create("test-session") + with mock(ses, "GMT_Destroy_Session", returns=1): + with pytest.raises(GMTCLibError): + ses.destroy() + ses.destroy() + + +def test_call_module(): + """ + Run a command to see if call_module works. + """ + data_fname = os.path.join(TEST_DATA_DIR, "points.txt") + out_fname = "test_call_module.txt" + with clib.Session() as lib: + with GMTTempFile() as out_fname: + lib.call_module("info", "{} -C ->{}".format(data_fname, out_fname.name)) + assert os.path.exists(out_fname.name) + output = out_fname.read().strip() + assert output == "11.5309 61.7074 -2.9289 7.8648 0.1412 0.9338" + + +def test_call_module_invalid_arguments(): + """ + Fails for invalid module arguments. + """ + with clib.Session() as lib: + with pytest.raises(GMTCLibError): + lib.call_module("info", "bogus-data.bla") + + +def test_call_module_invalid_name(): + """ + Fails when given bad input. + """ + with clib.Session() as lib: + with pytest.raises(GMTCLibError): + lib.call_module("meh", "") + + +def test_call_module_error_message(): + """ + Check is the GMT error message was captured. + """ + with clib.Session() as lib: + try: + lib.call_module("info", "bogus-data.bla") + except GMTCLibError as error: + assert "Module 'info' failed with status code" in str(error) + assert "gmtinfo [ERROR]: Cannot find file bogus-data.bla" in str(error) + + +def test_method_no_session(): + """ + Fails when not in a session. + """ + # Create an instance of Session without "with" so no session is created. + lib = clib.Session() + with pytest.raises(GMTCLibNoSessionError): + lib.call_module("gmtdefaults", "") + with pytest.raises(GMTCLibNoSessionError): + lib.session_pointer # pylint: disable=pointless-statement + + +def test_parse_constant_single(): + """ + Parsing a single family argument correctly. + """ + lib = clib.Session() + for family in FAMILIES: + parsed = lib._parse_constant(family, valid=FAMILIES) + assert parsed == lib[family] + + +def test_parse_constant_composite(): + """ + Parsing a composite constant argument (separated by |) correctly. + """ + lib = clib.Session() + test_cases = ((family, via) for family in FAMILIES for via in VIAS) + for family, via in test_cases: + composite = "|".join([family, via]) + expected = lib[family] + lib[via] + parsed = lib._parse_constant(composite, valid=FAMILIES, valid_modifiers=VIAS) + assert parsed == expected + + +def test_parse_constant_fails(): + """ + Check if the function fails when given bad input. + """ + lib = clib.Session() + test_cases = [ + "SOME_random_STRING", + "GMT_IS_DATASET|GMT_VIA_MATRIX|GMT_VIA_VECTOR", + "GMT_IS_DATASET|NOT_A_PROPER_VIA", + "NOT_A_PROPER_FAMILY|GMT_VIA_MATRIX", + "NOT_A_PROPER_FAMILY|ALSO_INVALID", + ] + for test_case in test_cases: + with pytest.raises(GMTInvalidInput): + lib._parse_constant(test_case, valid=FAMILIES, valid_modifiers=VIAS) + + # Should also fail if not given valid modifiers but is using them anyway. + # This should work... + lib._parse_constant( + "GMT_IS_DATASET|GMT_VIA_MATRIX", valid=FAMILIES, valid_modifiers=VIAS + ) + # But this shouldn't. + with pytest.raises(GMTInvalidInput): + lib._parse_constant( + "GMT_IS_DATASET|GMT_VIA_MATRIX", valid=FAMILIES, valid_modifiers=None + ) + + +def test_create_data_dataset(): + """ + Run the function to make sure it doesn't fail badly. + """ + with clib.Session() as lib: + # Dataset from vectors + data_vector = lib.create_data( + family="GMT_IS_DATASET|GMT_VIA_VECTOR", + geometry="GMT_IS_POINT", + mode="GMT_CONTAINER_ONLY", + dim=[10, 20, 1, 0], # columns, rows, layers, dtype + ) + # Dataset from matrices + data_matrix = lib.create_data( + family="GMT_IS_DATASET|GMT_VIA_MATRIX", + geometry="GMT_IS_POINT", + mode="GMT_CONTAINER_ONLY", + dim=[10, 20, 1, 0], + ) + assert data_vector != data_matrix + + +def test_create_data_grid_dim(): + """ + Create a grid ignoring range and inc. + """ + with clib.Session() as lib: + # Grids from matrices using dim + lib.create_data( + family="GMT_IS_GRID|GMT_VIA_MATRIX", + geometry="GMT_IS_SURFACE", + mode="GMT_CONTAINER_ONLY", + dim=[10, 20, 1, 0], + ) + + +def test_create_data_grid_range(): + """ + Create a grid specifying range and inc instead of dim. + """ + with clib.Session() as lib: + # Grids from matrices using range and int + lib.create_data( + family="GMT_IS_GRID|GMT_VIA_MATRIX", + geometry="GMT_IS_SURFACE", + mode="GMT_CONTAINER_ONLY", + ranges=[150.0, 250.0, -20.0, 20.0], + inc=[0.1, 0.2], + ) + + +def test_create_data_fails(): + """ + Check that create_data raises exceptions for invalid input and output. + """ + # Passing in invalid mode + with pytest.raises(GMTInvalidInput): + with clib.Session() as lib: + lib.create_data( + family="GMT_IS_DATASET", + geometry="GMT_IS_SURFACE", + mode="Not_a_valid_mode", + dim=[0, 0, 1, 0], + ranges=[150.0, 250.0, -20.0, 20.0], + inc=[0.1, 0.2], + ) + # Passing in invalid geometry + with pytest.raises(GMTInvalidInput): + with clib.Session() as lib: + lib.create_data( + family="GMT_IS_GRID", + geometry="Not_a_valid_geometry", + mode="GMT_CONTAINER_ONLY", + dim=[0, 0, 1, 0], + ranges=[150.0, 250.0, -20.0, 20.0], + inc=[0.1, 0.2], + ) + + # If the data pointer returned is None (NULL pointer) + with pytest.raises(GMTCLibError): + with clib.Session() as lib: + with mock(lib, "GMT_Create_Data", returns=None): + lib.create_data( + family="GMT_IS_DATASET", + geometry="GMT_IS_SURFACE", + mode="GMT_CONTAINER_ONLY", + dim=[11, 10, 2, 0], + ) + + +def test_virtual_file(): + """ + Test passing in data via a virtual file with a Dataset. + """ + dtypes = "float32 float64 int32 int64 uint32 uint64".split() + shape = (5, 3) + for dtype in dtypes: + 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], dtype=dtype).reshape(shape) + lib.put_matrix(dataset, matrix=data) + # Add the dataset to a virtual file and pass it along to gmt info + 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", "{} ->{}".format(vfile, outfile.name)) + output = outfile.read(keep_tabs=True) + bounds = "\t".join( + ["<{:.0f}/{:.0f}>".format(col.min(), col.max()) for col in data.T] + ) + expected = ": N = {}\t{}\n".format(shape[0], bounds) + assert output == expected + + +def test_virtual_file_fails(): + """ + Check that opening and closing virtual files raises an exception for non- + zero return codes. + """ + vfargs = ( + "GMT_IS_DATASET|GMT_VIA_MATRIX", + "GMT_IS_POINT", + "GMT_IN|GMT_IS_REFERENCE", + None, + ) + + # Mock Open_VirtualFile to test the status check when entering the context. + # If the exception is raised, the code won't get to the closing of the + # virtual file. + with clib.Session() as lib, mock(lib, "GMT_Open_VirtualFile", returns=1): + with pytest.raises(GMTCLibError): + with lib.open_virtual_file(*vfargs): + print("Should not get to this code") + + # Test the status check when closing the virtual file + # Mock the opening to return 0 (success) so that we don't open a file that + # we won't close later. + with clib.Session() as lib, mock(lib, "GMT_Open_VirtualFile", returns=0), mock( + lib, "GMT_Close_VirtualFile", returns=1 + ): + with pytest.raises(GMTCLibError): + with lib.open_virtual_file(*vfargs): + pass + print("Shouldn't get to this code either") + + +def test_virtual_file_bad_direction(): + """ + Test passing an invalid direction argument. + """ + with clib.Session() as lib: + vfargs = ( + "GMT_IS_DATASET|GMT_VIA_MATRIX", + "GMT_IS_POINT", + "GMT_IS_GRID", # The invalid direction argument + 0, + ) + with pytest.raises(GMTInvalidInput): + with lib.open_virtual_file(*vfargs): + print("This should have failed") + + +def test_virtualfile_from_vectors(): + """ + Test the automation for transforming vectors to virtual file dataset. + """ + dtypes = "float32 float64 int32 int64 uint32 uint64".split() + size = 10 + for dtype in dtypes: + x = np.arange(size, dtype=dtype) + y = np.arange(size, size * 2, 1, dtype=dtype) + z = np.arange(size * 2, size * 3, 1, dtype=dtype) + with clib.Session() as lib: + with lib.virtualfile_from_vectors(x, y, z) as vfile: + with GMTTempFile() as outfile: + lib.call_module("info", "{} ->{}".format(vfile, outfile.name)) + output = outfile.read(keep_tabs=True) + bounds = "\t".join( + ["<{:.0f}/{:.0f}>".format(i.min(), i.max()) for i in (x, y, z)] + ) + expected = ": N = {}\t{}\n".format(size, bounds) + assert output == expected + + +@pytest.mark.parametrize("dtype", [str, object]) +def test_virtualfile_from_vectors_one_string_or_object_column(dtype): + """ + Test passing in one column with string or object dtype into virtual file + dataset. + """ + size = 5 + x = np.arange(size, dtype=np.int32) + y = np.arange(size, size * 2, 1, dtype=np.int32) + strings = np.array(["a", "bc", "defg", "hijklmn", "opqrst"], dtype=dtype) + with clib.Session() as lib: + with lib.virtualfile_from_vectors(x, y, strings) as vfile: + with GMTTempFile() as outfile: + lib.call_module("convert", f"{vfile} ->{outfile.name}") + output = outfile.read(keep_tabs=True) + expected = "".join(f"{i}\t{j}\t{k}\n" for i, j, k in zip(x, y, strings)) + assert output == expected + + +@pytest.mark.parametrize("dtype", [str, object]) +def test_virtualfile_from_vectors_two_string_or_object_columns(dtype): + """ + Test passing in two columns of string or object dtype into virtual file + dataset. + """ + size = 5 + x = np.arange(size, dtype=np.int32) + y = np.arange(size, size * 2, 1, dtype=np.int32) + strings1 = np.array(["a", "bc", "def", "ghij", "klmno"], dtype=dtype) + strings2 = np.array(["pqrst", "uvwx", "yz!", "@#", "$"], dtype=dtype) + with clib.Session() as lib: + with lib.virtualfile_from_vectors(x, y, strings1, strings2) as vfile: + with GMTTempFile() as outfile: + lib.call_module("convert", f"{vfile} ->{outfile.name}") + output = outfile.read(keep_tabs=True) + expected = "".join( + f"{h}\t{i}\t{j} {k}\n" for h, i, j, k in zip(x, y, strings1, strings2) + ) + assert output == expected + + +def test_virtualfile_from_vectors_transpose(): + """ + Test transforming matrix columns to virtual file dataset. + """ + dtypes = "float32 float64 int32 int64 uint32 uint64".split() + shape = (7, 5) + for dtype in dtypes: + data = np.arange(shape[0] * shape[1], dtype=dtype).reshape(shape) + with clib.Session() as lib: + with lib.virtualfile_from_vectors(*data.T) as vfile: + with GMTTempFile() as outfile: + lib.call_module("info", "{} -C ->{}".format(vfile, outfile.name)) + output = outfile.read(keep_tabs=True) + bounds = "\t".join( + ["{:.0f}\t{:.0f}".format(col.min(), col.max()) for col in data.T] + ) + expected = "{}\n".format(bounds) + assert output == expected + + +def test_virtualfile_from_vectors_diff_size(): + """ + Test the function fails for arrays of different sizes. + """ + x = np.arange(5) + y = np.arange(6) + with clib.Session() as lib: + with pytest.raises(GMTInvalidInput): + with lib.virtualfile_from_vectors(x, y): + print("This should have failed") + + +def test_virtualfile_from_matrix(): + """ + Test transforming a matrix to virtual file dataset. + """ + dtypes = "float32 float64 int32 int64 uint32 uint64".split() + shape = (7, 5) + for dtype in dtypes: + data = np.arange(shape[0] * shape[1], dtype=dtype).reshape(shape) + with clib.Session() as lib: + with lib.virtualfile_from_matrix(data) as vfile: + with GMTTempFile() as outfile: + lib.call_module("info", "{} ->{}".format(vfile, outfile.name)) + output = outfile.read(keep_tabs=True) + bounds = "\t".join( + ["<{:.0f}/{:.0f}>".format(col.min(), col.max()) for col in data.T] + ) + expected = ": N = {}\t{}\n".format(shape[0], bounds) + assert output == expected + + +def test_virtualfile_from_matrix_slice(): + """ + Test transforming a slice of a larger array to virtual file dataset. + """ + dtypes = "float32 float64 int32 int64 uint32 uint64".split() + shape = (10, 6) + for dtype in dtypes: + full_data = np.arange(shape[0] * shape[1], dtype=dtype).reshape(shape) + rows = 5 + cols = 3 + data = full_data[:rows, :cols] + with clib.Session() as lib: + with lib.virtualfile_from_matrix(data) as vfile: + with GMTTempFile() as outfile: + lib.call_module("info", "{} ->{}".format(vfile, outfile.name)) + output = outfile.read(keep_tabs=True) + bounds = "\t".join( + ["<{:.0f}/{:.0f}>".format(col.min(), col.max()) for col in data.T] + ) + expected = ": N = {}\t{}\n".format(rows, bounds) + assert output == expected + + +def test_virtualfile_from_vectors_pandas(): + """ + Pass vectors to a dataset using pandas Series. + """ + dtypes = "float32 float64 int32 int64 uint32 uint64".split() + size = 13 + for dtype in dtypes: + data = pd.DataFrame( + data=dict( + x=np.arange(size, dtype=dtype), + y=np.arange(size, size * 2, 1, dtype=dtype), + z=np.arange(size * 2, size * 3, 1, dtype=dtype), + ) + ) + with clib.Session() as lib: + with lib.virtualfile_from_vectors(data.x, data.y, data.z) as vfile: + with GMTTempFile() as outfile: + lib.call_module("info", "{} ->{}".format(vfile, outfile.name)) + output = outfile.read(keep_tabs=True) + bounds = "\t".join( + [ + "<{:.0f}/{:.0f}>".format(i.min(), i.max()) + for i in (data.x, data.y, data.z) + ] + ) + expected = ": N = {}\t{}\n".format(size, bounds) + assert output == expected + + +def test_virtualfile_from_vectors_arraylike(): + """ + Pass array-like vectors to a dataset. + """ + size = 13 + x = list(range(0, size, 1)) + y = tuple(range(size, size * 2, 1)) + z = range(size * 2, size * 3, 1) + with clib.Session() as lib: + with lib.virtualfile_from_vectors(x, y, z) as vfile: + with GMTTempFile() as outfile: + lib.call_module("info", "{} ->{}".format(vfile, outfile.name)) + output = outfile.read(keep_tabs=True) + bounds = "\t".join( + ["<{:.0f}/{:.0f}>".format(min(i), max(i)) for i in (x, y, z)] + ) + expected = ": N = {}\t{}\n".format(size, bounds) + assert output == expected + + +def test_extract_region_fails(): + """ + Check that extract region fails if nothing has been plotted. + """ + Figure() + with pytest.raises(GMTCLibError): + with clib.Session() as lib: + lib.extract_region() + + +def test_extract_region_two_figures(): + """ + Extract region should handle multiple figures existing at the same time. + """ + # Make two figures before calling extract_region to make sure that it's + # getting from the current figure, not the last figure. + fig1 = Figure() + region1 = np.array([0, 10, -20, -10]) + fig1.coast(region=region1, projection="M6i", frame=True, land="black") + + fig2 = Figure() + fig2.basemap(region="US.HI+r5", projection="M6i", frame=True) + + # Activate the first figure and extract the region from it + # Use in a different session to avoid any memory problems. + with clib.Session() as lib: + lib.call_module("figure", "{} -".format(fig1._name)) + with clib.Session() as lib: + wesn1 = lib.extract_region() + npt.assert_allclose(wesn1, region1) + + # Now try it with the second one + with clib.Session() as lib: + lib.call_module("figure", "{} -".format(fig2._name)) + with clib.Session() as lib: + wesn2 = lib.extract_region() + npt.assert_allclose(wesn2, np.array([-165.0, -150.0, 15.0, 25.0])) + + +def test_write_data_fails(): + """ + Check that write data raises an exception for non-zero return codes. + """ + # It's hard to make the C API function fail without causing a Segmentation + # Fault. Can't test this if by giving a bad file name because if + # output=='', GMT will just write to stdout and spaces are valid file + # names. Use a mock instead just to exercise this part of the code. + with clib.Session() as lib: + with mock(lib, "GMT_Write_Data", returns=1): + with pytest.raises(GMTCLibError): + lib.write_data( + "GMT_IS_VECTOR", + "GMT_IS_POINT", + "GMT_WRITE_SET", + [1] * 6, + "some-file-name", + None, + ) + + +def test_dataarray_to_matrix_works(): + """ + Check that dataarray_to_matrix returns correct output. + """ + data = np.diag(v=np.arange(3)) + x = np.linspace(start=0, stop=4, num=3) + y = np.linspace(start=5, stop=9, num=3) + grid = xr.DataArray(data, coords=[("y", y), ("x", x)]) + + matrix, region, inc = dataarray_to_matrix(grid) + npt.assert_allclose(actual=matrix, desired=np.flipud(data)) + npt.assert_allclose(actual=region, desired=[x.min(), x.max(), y.min(), y.max()]) + npt.assert_allclose(actual=inc, desired=[x[1] - x[0], y[1] - y[0]]) + + +def test_dataarray_to_matrix_negative_x_increment(): + """ + Check if dataarray_to_matrix returns correct output with flipped x. + """ + data = np.diag(v=np.arange(3)) + x = np.linspace(start=4, stop=0, num=3) + y = np.linspace(start=5, stop=9, num=3) + grid = xr.DataArray(data, coords=[("y", y), ("x", x)]) + + matrix, region, inc = dataarray_to_matrix(grid) + npt.assert_allclose(actual=matrix, desired=np.flip(data, axis=(0, 1))) + npt.assert_allclose(actual=region, desired=[x.min(), x.max(), y.min(), y.max()]) + npt.assert_allclose(actual=inc, desired=[abs(x[1] - x[0]), abs(y[1] - y[0])]) + + +def test_dataarray_to_matrix_negative_y_increment(): + """ + Check that dataarray_to_matrix returns correct output with flipped y. + """ + data = np.diag(v=np.arange(3)) + x = np.linspace(start=0, stop=4, num=3) + y = np.linspace(start=9, stop=5, num=3) + grid = xr.DataArray(data, coords=[("y", y), ("x", x)]) + + matrix, region, inc = dataarray_to_matrix(grid) + npt.assert_allclose(actual=matrix, desired=data) + npt.assert_allclose(actual=region, desired=[x.min(), x.max(), y.min(), y.max()]) + npt.assert_allclose(actual=inc, desired=[abs(x[1] - x[0]), abs(y[1] - y[0])]) + + +def test_dataarray_to_matrix_negative_x_and_y_increment(): + """ + Check that dataarray_to_matrix returns correct output with flipped x/y. + """ + data = np.diag(v=np.arange(3)) + x = np.linspace(start=4, stop=0, num=3) + y = np.linspace(start=9, stop=5, num=3) + grid = xr.DataArray(data, coords=[("y", y), ("x", x)]) + + matrix, region, inc = dataarray_to_matrix(grid) + npt.assert_allclose(actual=matrix, desired=np.fliplr(data)) + npt.assert_allclose(actual=region, desired=[x.min(), x.max(), y.min(), y.max()]) + npt.assert_allclose(actual=inc, desired=[abs(x[1] - x[0]), abs(y[1] - y[0])]) + + +def test_dataarray_to_matrix_dims_fails(): + """ + Check that it fails for > 2 dims. + """ + # Make a 3D regular grid + data = np.ones((10, 12, 11), dtype="float32") + x = np.arange(11) + y = np.arange(12) + z = np.arange(10) + grid = xr.DataArray(data, coords=[("z", z), ("y", y), ("x", x)]) + with pytest.raises(GMTInvalidInput): + dataarray_to_matrix(grid) + + +def test_dataarray_to_matrix_inc_fails(): + """ + Check that it fails for variable increments. + """ + data = np.ones((4, 5), dtype="float64") + x = np.linspace(0, 1, 5) + y = np.logspace(2, 3, 4) + grid = xr.DataArray(data, coords=[("y", y), ("x", x)]) + with pytest.raises(GMTInvalidInput): + dataarray_to_matrix(grid) + + +def test_get_default(): + """ + Make sure get_default works without crashing and gives reasonable results. + """ + with clib.Session() as lib: + assert lib.get_default("API_GRID_LAYOUT") in ["rows", "columns"] + assert int(lib.get_default("API_CORES")) >= 1 + assert Version(lib.get_default("API_VERSION")) >= Version("6.1.1") + + +def test_get_default_fails(): + """ + Make sure get_default raises an exception for invalid names. + """ + with clib.Session() as lib: + with pytest.raises(GMTCLibError): + lib.get_default("NOT_A_VALID_NAME") + + +def test_info_dict(): + """ + Make sure the clib.Session.info dict is working. + """ + # Check if there are no errors or segfaults from getting all of the + # properties. + with clib.Session() as lib: + assert lib.info + + # Mock GMT_Get_Default to return always the same string + def mock_defaults(api, name, value): # pylint: disable=unused-argument + """ + Put 'bla' in the value buffer. + """ + value.value = b"bla" + return 0 + + ses = clib.Session() + ses.create("test-session") + with mock(ses, "GMT_Get_Default", mock_func=mock_defaults): + # Check for an empty dictionary + assert ses.info + for key in ses.info: + assert ses.info[key] == "bla" + ses.destroy() + + +def test_fails_for_wrong_version(): + """ + Make sure the clib.Session raises an exception if GMT is too old. + """ + + # Mock GMT_Get_Default to return an old version + def mock_defaults(api, name, value): # pylint: disable=unused-argument + """ + Return an old version. + """ + if name == b"API_VERSION": + value.value = b"5.4.3" + else: + value.value = b"bla" + return 0 + + lib = clib.Session() + with mock(lib, "GMT_Get_Default", mock_func=mock_defaults): + with pytest.raises(GMTVersionError): + with lib: + assert lib.info["version"] != "5.4.3" + # Make sure the session is closed when the exception is raised. + with pytest.raises(GMTCLibNoSessionError): + assert lib.session_pointer diff --git a/pygmt/tests/test_clib_loading.py b/pygmt/tests/test_clib_loading.py index eb6d239e4da..d901f84a944 100644 --- a/pygmt/tests/test_clib_loading.py +++ b/pygmt/tests/test_clib_loading.py @@ -1,197 +1,197 @@ -""" -Test the functions that load libgmt. -""" -import shutil -import subprocess -import sys -import types -from pathlib import PurePath - -import pytest -from pygmt.clib.loading import check_libgmt, clib_full_names, clib_names, load_libgmt -from pygmt.exceptions import GMTCLibError, GMTCLibNotFoundError, GMTOSError - - -def test_check_libgmt(): - """ - Make sure check_libgmt fails when given a bogus library. - """ - # create a fake library with a "_name" property - def libgmt(): - pass - - libgmt._name = "/path/to/libgmt.so" # pylint: disable=protected-access - msg = ( - # pylint: disable=protected-access - f"Error loading '{libgmt._name}'. " - "Couldn't access function GMT_Create_Session. " - "Ensure that you have installed an up-to-date GMT version 6 library. " - "Please set the environment variable 'GMT_LIBRARY_PATH' to the " - "directory of the GMT 6 library." - ) - with pytest.raises(GMTCLibError, match=msg): - check_libgmt(libgmt) - - -def test_load_libgmt(): - """ - Test that loading libgmt works and doesn't crash. - """ - check_libgmt(load_libgmt()) - - -@pytest.mark.skipif(sys.platform == "win32", reason="run on UNIX platforms only") -def test_load_libgmt_fails(monkeypatch): - """ - Test that GMTCLibNotFoundError is raised when GMT's shared library cannot - be found. - """ - with monkeypatch.context() as mpatch: - mpatch.setattr(sys, "platform", "win32") # pretend to be on Windows - mpatch.setattr( - subprocess, "check_output", lambda cmd, encoding: "libfakegmt.so" - ) - with pytest.raises(GMTCLibNotFoundError): - check_libgmt(load_libgmt()) - - -def test_load_libgmt_with_a_bad_library_path(monkeypatch): - """ - Test that loading still works when given a bad library path. - """ - # Set a fake "GMT_LIBRARY_PATH" - monkeypatch.setenv("GMT_LIBRARY_PATH", "/not/a/real/path") - assert check_libgmt(load_libgmt()) is None - - -def test_clib_names(): - """ - Make sure we get the correct library name for different OS names. - """ - for linux in ["linux", "linux2", "linux3"]: - assert clib_names(linux) == ["libgmt.so"] - assert clib_names("darwin") == ["libgmt.dylib"] - assert clib_names("win32") == ["gmt.dll", "gmt_w64.dll", "gmt_w32.dll"] - for freebsd in ["freebsd10", "freebsd11", "freebsd12"]: - assert clib_names(freebsd) == ["libgmt.so"] - with pytest.raises(GMTOSError): - clib_names("meh") - - -############################################################################### -# Tests for clib_full_names -@pytest.fixture(scope="module", name="gmt_lib_names") -def fixture_gmt_lib_names(): - """ - Return a list of the library names for the current operating system. - """ - return clib_names(sys.platform) - - -@pytest.fixture(scope="module", name="gmt_bin_dir") -def fixture_gmt_bin_dir(): - """ - Return GMT's bin directory. - """ - return str(PurePath(shutil.which("gmt")).parent) - - -@pytest.fixture(scope="module", name="gmt_lib_realpath") -def fixture_gmt_lib_realpath(): - """ - Return the real path of the GMT library. - """ - lib_realpath = subprocess.check_output( - ["gmt", "--show-library"], encoding="utf-8" - ).rstrip("\n") - # On Windows, clib_full_names() returns paths with separator "\\", - # but "gmt --show-library" returns paths with separator "/". - # Use `str(PurePath(realpath)` to mimic the behavior of clib_full_names() - return str(PurePath(lib_realpath)) - - -def test_clib_full_names_gmt_library_path_undefined_path_empty( - monkeypatch, gmt_lib_names -): - """ - Make sure that clib_full_names() returns a generator with expected names - when GMT_LIBRARY_PATH is undefined and PATH is empty. - """ - with monkeypatch.context() as mpatch: - mpatch.delenv("GMT_LIBRARY_PATH", raising=False) - mpatch.setenv("PATH", "") - lib_fullpaths = clib_full_names() - - assert isinstance(lib_fullpaths, types.GeneratorType) - assert list(lib_fullpaths) == gmt_lib_names - - -def test_clib_full_names_gmt_library_path_defined_path_empty( - monkeypatch, gmt_lib_names, gmt_lib_realpath -): - """ - Make sure that clib_full_names() returns a generator with expected names - when GMT_LIBRARY_PATH is defined and PATH is empty. - """ - with monkeypatch.context() as mpatch: - mpatch.setenv("GMT_LIBRARY_PATH", str(PurePath(gmt_lib_realpath).parent)) - mpatch.setenv("PATH", "") - lib_fullpaths = clib_full_names() - - assert isinstance(lib_fullpaths, types.GeneratorType) - assert list(lib_fullpaths) == [gmt_lib_realpath] + gmt_lib_names - - -def test_clib_full_names_gmt_library_path_undefined_path_included( - monkeypatch, gmt_lib_names, gmt_lib_realpath, gmt_bin_dir -): - """ - Make sure that clib_full_names() returns a generator with expected names - when GMT_LIBRARY_PATH is undefined and PATH includes GMT's bin path. - """ - with monkeypatch.context() as mpatch: - mpatch.delenv("GMT_LIBRARY_PATH", raising=False) - mpatch.setenv("PATH", gmt_bin_dir) - lib_fullpaths = clib_full_names() - - assert isinstance(lib_fullpaths, types.GeneratorType) - # Windows: find_library() searches the library in PATH, so one more - npath = 2 if sys.platform == "win32" else 1 - assert list(lib_fullpaths) == [gmt_lib_realpath] * npath + gmt_lib_names - - -def test_clib_full_names_gmt_library_path_defined_path_included( - monkeypatch, gmt_lib_names, gmt_lib_realpath, gmt_bin_dir -): - """ - Make sure that clib_full_names() returns a generator with expected names - when GMT_LIBRARY_PATH is defined and PATH includes GMT's bin path. - """ - with monkeypatch.context() as mpatch: - mpatch.setenv("GMT_LIBRARY_PATH", str(PurePath(gmt_lib_realpath).parent)) - mpatch.setenv("PATH", gmt_bin_dir) - lib_fullpaths = clib_full_names() - - assert isinstance(lib_fullpaths, types.GeneratorType) - # Windows: find_library() searches the library in PATH, so one more - npath = 3 if sys.platform == "win32" else 2 - assert list(lib_fullpaths) == [gmt_lib_realpath] * npath + gmt_lib_names - - -def test_clib_full_names_gmt_library_path_incorrect_path_included( - monkeypatch, gmt_lib_names, gmt_lib_realpath, gmt_bin_dir -): - """ - Make sure that clib_full_names() returns a generator with expected names - when GMT_LIBRARY_PATH is defined but incorrect and PATH includes GMT's bin - path. - """ - with monkeypatch.context() as mpatch: - mpatch.setenv("GMT_LIBRARY_PATH", "/not/a/valid/library/path") - mpatch.setenv("PATH", gmt_bin_dir) - lib_fullpaths = clib_full_names() - - assert isinstance(lib_fullpaths, types.GeneratorType) - # Windows: find_library() searches the library in PATH, so one more - npath = 2 if sys.platform == "win32" else 1 - assert list(lib_fullpaths) == [gmt_lib_realpath] * npath + gmt_lib_names +""" +Test the functions that load libgmt. +""" +import shutil +import subprocess +import sys +import types +from pathlib import PurePath + +import pytest +from pygmt.clib.loading import check_libgmt, clib_full_names, clib_names, load_libgmt +from pygmt.exceptions import GMTCLibError, GMTCLibNotFoundError, GMTOSError + + +def test_check_libgmt(): + """ + Make sure check_libgmt fails when given a bogus library. + """ + # create a fake library with a "_name" property + def libgmt(): + pass + + libgmt._name = "/path/to/libgmt.so" # pylint: disable=protected-access + msg = ( + # pylint: disable=protected-access + f"Error loading '{libgmt._name}'. " + "Couldn't access function GMT_Create_Session. " + "Ensure that you have installed an up-to-date GMT version 6 library. " + "Please set the environment variable 'GMT_LIBRARY_PATH' to the " + "directory of the GMT 6 library." + ) + with pytest.raises(GMTCLibError, match=msg): + check_libgmt(libgmt) + + +def test_load_libgmt(): + """ + Test that loading libgmt works and doesn't crash. + """ + check_libgmt(load_libgmt()) + + +@pytest.mark.skipif(sys.platform == "win32", reason="run on UNIX platforms only") +def test_load_libgmt_fails(monkeypatch): + """ + Test that GMTCLibNotFoundError is raised when GMT's shared library cannot + be found. + """ + with monkeypatch.context() as mpatch: + mpatch.setattr(sys, "platform", "win32") # pretend to be on Windows + mpatch.setattr( + subprocess, "check_output", lambda cmd, encoding: "libfakegmt.so" + ) + with pytest.raises(GMTCLibNotFoundError): + check_libgmt(load_libgmt()) + + +def test_load_libgmt_with_a_bad_library_path(monkeypatch): + """ + Test that loading still works when given a bad library path. + """ + # Set a fake "GMT_LIBRARY_PATH" + monkeypatch.setenv("GMT_LIBRARY_PATH", "/not/a/real/path") + assert check_libgmt(load_libgmt()) is None + + +def test_clib_names(): + """ + Make sure we get the correct library name for different OS names. + """ + for linux in ["linux", "linux2", "linux3"]: + assert clib_names(linux) == ["libgmt.so"] + assert clib_names("darwin") == ["libgmt.dylib"] + assert clib_names("win32") == ["gmt.dll", "gmt_w64.dll", "gmt_w32.dll"] + for freebsd in ["freebsd10", "freebsd11", "freebsd12"]: + assert clib_names(freebsd) == ["libgmt.so"] + with pytest.raises(GMTOSError): + clib_names("meh") + + +############################################################################### +# Tests for clib_full_names +@pytest.fixture(scope="module", name="gmt_lib_names") +def fixture_gmt_lib_names(): + """ + Return a list of the library names for the current operating system. + """ + return clib_names(sys.platform) + + +@pytest.fixture(scope="module", name="gmt_bin_dir") +def fixture_gmt_bin_dir(): + """ + Return GMT's bin directory. + """ + return str(PurePath(shutil.which("gmt")).parent) + + +@pytest.fixture(scope="module", name="gmt_lib_realpath") +def fixture_gmt_lib_realpath(): + """ + Return the real path of the GMT library. + """ + lib_realpath = subprocess.check_output( + ["gmt", "--show-library"], encoding="utf-8" + ).rstrip("\n") + # On Windows, clib_full_names() returns paths with separator "\\", + # but "gmt --show-library" returns paths with separator "/". + # Use `str(PurePath(realpath)` to mimic the behavior of clib_full_names() + return str(PurePath(lib_realpath)) + + +def test_clib_full_names_gmt_library_path_undefined_path_empty( + monkeypatch, gmt_lib_names +): + """ + Make sure that clib_full_names() returns a generator with expected names + when GMT_LIBRARY_PATH is undefined and PATH is empty. + """ + with monkeypatch.context() as mpatch: + mpatch.delenv("GMT_LIBRARY_PATH", raising=False) + mpatch.setenv("PATH", "") + lib_fullpaths = clib_full_names() + + assert isinstance(lib_fullpaths, types.GeneratorType) + assert list(lib_fullpaths) == gmt_lib_names + + +def test_clib_full_names_gmt_library_path_defined_path_empty( + monkeypatch, gmt_lib_names, gmt_lib_realpath +): + """ + Make sure that clib_full_names() returns a generator with expected names + when GMT_LIBRARY_PATH is defined and PATH is empty. + """ + with monkeypatch.context() as mpatch: + mpatch.setenv("GMT_LIBRARY_PATH", str(PurePath(gmt_lib_realpath).parent)) + mpatch.setenv("PATH", "") + lib_fullpaths = clib_full_names() + + assert isinstance(lib_fullpaths, types.GeneratorType) + assert list(lib_fullpaths) == [gmt_lib_realpath] + gmt_lib_names + + +def test_clib_full_names_gmt_library_path_undefined_path_included( + monkeypatch, gmt_lib_names, gmt_lib_realpath, gmt_bin_dir +): + """ + Make sure that clib_full_names() returns a generator with expected names + when GMT_LIBRARY_PATH is undefined and PATH includes GMT's bin path. + """ + with monkeypatch.context() as mpatch: + mpatch.delenv("GMT_LIBRARY_PATH", raising=False) + mpatch.setenv("PATH", gmt_bin_dir) + lib_fullpaths = clib_full_names() + + assert isinstance(lib_fullpaths, types.GeneratorType) + # Windows: find_library() searches the library in PATH, so one more + npath = 2 if sys.platform == "win32" else 1 + assert list(lib_fullpaths) == [gmt_lib_realpath] * npath + gmt_lib_names + + +def test_clib_full_names_gmt_library_path_defined_path_included( + monkeypatch, gmt_lib_names, gmt_lib_realpath, gmt_bin_dir +): + """ + Make sure that clib_full_names() returns a generator with expected names + when GMT_LIBRARY_PATH is defined and PATH includes GMT's bin path. + """ + with monkeypatch.context() as mpatch: + mpatch.setenv("GMT_LIBRARY_PATH", str(PurePath(gmt_lib_realpath).parent)) + mpatch.setenv("PATH", gmt_bin_dir) + lib_fullpaths = clib_full_names() + + assert isinstance(lib_fullpaths, types.GeneratorType) + # Windows: find_library() searches the library in PATH, so one more + npath = 3 if sys.platform == "win32" else 2 + assert list(lib_fullpaths) == [gmt_lib_realpath] * npath + gmt_lib_names + + +def test_clib_full_names_gmt_library_path_incorrect_path_included( + monkeypatch, gmt_lib_names, gmt_lib_realpath, gmt_bin_dir +): + """ + Make sure that clib_full_names() returns a generator with expected names + when GMT_LIBRARY_PATH is defined but incorrect and PATH includes GMT's bin + path. + """ + with monkeypatch.context() as mpatch: + mpatch.setenv("GMT_LIBRARY_PATH", "/not/a/valid/library/path") + mpatch.setenv("PATH", gmt_bin_dir) + lib_fullpaths = clib_full_names() + + assert isinstance(lib_fullpaths, types.GeneratorType) + # Windows: find_library() searches the library in PATH, so one more + npath = 2 if sys.platform == "win32" else 1 + assert list(lib_fullpaths) == [gmt_lib_realpath] * npath + gmt_lib_names diff --git a/pygmt/tests/test_clib_put_matrix.py b/pygmt/tests/test_clib_put_matrix.py index 46e9ebd9d5c..e975c1889d5 100644 --- a/pygmt/tests/test_clib_put_matrix.py +++ b/pygmt/tests/test_clib_put_matrix.py @@ -1,112 +1,112 @@ -""" -Test the functions that put matrix data into GMT. -""" -import numpy as np -import numpy.testing as npt -import pytest -import xarray as xr -from pygmt import clib -from pygmt.exceptions import GMTCLibError -from pygmt.helpers import GMTTempFile -from pygmt.tests.test_clib import mock - - -def test_put_matrix(): - """ - Check that assigning a numpy 2d array to a dataset works. - """ - dtypes = "float32 float64 int32 int64 uint32 uint64".split() - shape = (3, 4) - for dtype in dtypes: - with clib.Session() as lib: - dataset = lib.create_data( - family="GMT_IS_DATASET|GMT_VIA_MATRIX", - geometry="GMT_IS_POINT", - mode="GMT_CONTAINER_ONLY", - dim=[shape[1], shape[0], 1, 0], # columns, rows, layers, dtype - ) - data = np.arange(shape[0] * shape[1], dtype=dtype).reshape(shape) - lib.put_matrix(dataset, matrix=data) - # wesn doesn't matter for Datasets - wesn = [0] * 6 - # Save the data to a file to see if it's being accessed correctly - with GMTTempFile() as tmp_file: - lib.write_data( - "GMT_IS_MATRIX", - "GMT_IS_POINT", - "GMT_WRITE_SET", - wesn, - tmp_file.name, - dataset, - ) - # Load the data and check that it's correct - newdata = tmp_file.loadtxt(dtype=dtype) - npt.assert_allclose(newdata, data) - - -def test_put_matrix_fails(): - """ - Check that put_matrix raises an exception if return code is not zero. - """ - # It's hard to make put_matrix fail on the C API level because of all the - # checks on input arguments. Mock the C API function just to make sure it - # works. - with clib.Session() as lib: - with mock(lib, "GMT_Put_Matrix", returns=1): - with pytest.raises(GMTCLibError): - lib.put_matrix(dataset=None, matrix=np.empty((10, 2)), pad=0) - - -def test_put_matrix_grid(): - """ - Check that assigning a numpy 2d array to an ASCII and NetCDF grid works. - """ - dtypes = "float32 float64 int32 int64 uint32 uint64".split() - wesn = [10, 15, 30, 40, 0, 0] - inc = [1, 1] - shape = ((wesn[3] - wesn[2]) // inc[1] + 1, (wesn[1] - wesn[0]) // inc[0] + 1) - for dtype in dtypes: - with clib.Session() as lib: - grid = lib.create_data( - family="GMT_IS_GRID|GMT_VIA_MATRIX", - geometry="GMT_IS_SURFACE", - mode="GMT_CONTAINER_ONLY", - ranges=wesn[:4], - inc=inc, - registration="GMT_GRID_NODE_REG", - ) - data = np.arange(shape[0] * shape[1], dtype=dtype).reshape(shape) - lib.put_matrix(grid, matrix=data) - # Save the data to a file to see if it's being accessed correctly - with GMTTempFile() as tmp_file: - lib.write_data( - "GMT_IS_MATRIX", - "GMT_IS_POINT", - "GMT_CONTAINER_AND_DATA", - wesn, - tmp_file.name, - grid, - ) - # Load the data and check that it's correct - newdata = tmp_file.loadtxt(dtype=dtype) - npt.assert_allclose(newdata, data) - - # Save the data to a netCDF grid and check that xarray can load it - with GMTTempFile() as tmp_grid: - lib.write_data( - "GMT_IS_MATRIX", - "GMT_IS_SURFACE", - "GMT_CONTAINER_AND_DATA", - wesn, - tmp_grid.name, - grid, - ) - with xr.open_dataarray(tmp_grid.name) as dataarray: - assert dataarray.shape == shape - npt.assert_allclose(dataarray.data, np.flipud(data)) - npt.assert_allclose( - dataarray.coords["x"].actual_range, np.array(wesn[0:2]) - ) - npt.assert_allclose( - dataarray.coords["y"].actual_range, np.array(wesn[2:4]) - ) +""" +Test the functions that put matrix data into GMT. +""" +import numpy as np +import numpy.testing as npt +import pytest +import xarray as xr +from pygmt import clib +from pygmt.exceptions import GMTCLibError +from pygmt.helpers import GMTTempFile +from pygmt.tests.test_clib import mock + + +def test_put_matrix(): + """ + Check that assigning a numpy 2d array to a dataset works. + """ + dtypes = "float32 float64 int32 int64 uint32 uint64".split() + shape = (3, 4) + for dtype in dtypes: + with clib.Session() as lib: + dataset = lib.create_data( + family="GMT_IS_DATASET|GMT_VIA_MATRIX", + geometry="GMT_IS_POINT", + mode="GMT_CONTAINER_ONLY", + dim=[shape[1], shape[0], 1, 0], # columns, rows, layers, dtype + ) + data = np.arange(shape[0] * shape[1], dtype=dtype).reshape(shape) + lib.put_matrix(dataset, matrix=data) + # wesn doesn't matter for Datasets + wesn = [0] * 6 + # Save the data to a file to see if it's being accessed correctly + with GMTTempFile() as tmp_file: + lib.write_data( + "GMT_IS_MATRIX", + "GMT_IS_POINT", + "GMT_WRITE_SET", + wesn, + tmp_file.name, + dataset, + ) + # Load the data and check that it's correct + newdata = tmp_file.loadtxt(dtype=dtype) + npt.assert_allclose(newdata, data) + + +def test_put_matrix_fails(): + """ + Check that put_matrix raises an exception if return code is not zero. + """ + # It's hard to make put_matrix fail on the C API level because of all the + # checks on input arguments. Mock the C API function just to make sure it + # works. + with clib.Session() as lib: + with mock(lib, "GMT_Put_Matrix", returns=1): + with pytest.raises(GMTCLibError): + lib.put_matrix(dataset=None, matrix=np.empty((10, 2)), pad=0) + + +def test_put_matrix_grid(): + """ + Check that assigning a numpy 2d array to an ASCII and NetCDF grid works. + """ + dtypes = "float32 float64 int32 int64 uint32 uint64".split() + wesn = [10, 15, 30, 40, 0, 0] + inc = [1, 1] + shape = ((wesn[3] - wesn[2]) // inc[1] + 1, (wesn[1] - wesn[0]) // inc[0] + 1) + for dtype in dtypes: + with clib.Session() as lib: + grid = lib.create_data( + family="GMT_IS_GRID|GMT_VIA_MATRIX", + geometry="GMT_IS_SURFACE", + mode="GMT_CONTAINER_ONLY", + ranges=wesn[:4], + inc=inc, + registration="GMT_GRID_NODE_REG", + ) + data = np.arange(shape[0] * shape[1], dtype=dtype).reshape(shape) + lib.put_matrix(grid, matrix=data) + # Save the data to a file to see if it's being accessed correctly + with GMTTempFile() as tmp_file: + lib.write_data( + "GMT_IS_MATRIX", + "GMT_IS_POINT", + "GMT_CONTAINER_AND_DATA", + wesn, + tmp_file.name, + grid, + ) + # Load the data and check that it's correct + newdata = tmp_file.loadtxt(dtype=dtype) + npt.assert_allclose(newdata, data) + + # Save the data to a netCDF grid and check that xarray can load it + with GMTTempFile() as tmp_grid: + lib.write_data( + "GMT_IS_MATRIX", + "GMT_IS_SURFACE", + "GMT_CONTAINER_AND_DATA", + wesn, + tmp_grid.name, + grid, + ) + with xr.open_dataarray(tmp_grid.name) as dataarray: + assert dataarray.shape == shape + npt.assert_allclose(dataarray.data, np.flipud(data)) + npt.assert_allclose( + dataarray.coords["x"].actual_range, np.array(wesn[0:2]) + ) + npt.assert_allclose( + dataarray.coords["y"].actual_range, np.array(wesn[2:4]) + ) diff --git a/pygmt/tests/test_clib_put_strings.py b/pygmt/tests/test_clib_put_strings.py index 2e9e66ad858..f12ac04bb22 100644 --- a/pygmt/tests/test_clib_put_strings.py +++ b/pygmt/tests/test_clib_put_strings.py @@ -1,66 +1,66 @@ -""" -Test the functions that put string data into GMT. -""" -import numpy as np -import numpy.testing as npt -import pytest -from packaging.version import Version -from pygmt import clib -from pygmt.exceptions import GMTCLibError -from pygmt.helpers import GMTTempFile - -with clib.Session() as _lib: - gmt_version = Version(_lib.info["version"]) - - -def test_put_strings(): - """ - Check that assigning a numpy array of dtype str to a dataset works. - """ - with clib.Session() as lib: - dataset = lib.create_data( - family="GMT_IS_DATASET|GMT_VIA_VECTOR", - geometry="GMT_IS_POINT", - mode="GMT_CONTAINER_ONLY", - dim=[2, 5, 1, 0], # columns, rows, layers, dtype - ) - x = np.array([1, 2, 3, 4, 5], dtype=np.int32) - y = np.array([6, 7, 8, 9, 10], dtype=np.int32) - strings = np.array(["a", "bc", "defg", "hijklmn", "opqrst"], dtype=str) - lib.put_vector(dataset, column=lib["GMT_X"], vector=x) - lib.put_vector(dataset, column=lib["GMT_Y"], vector=y) - lib.put_strings( - dataset, family="GMT_IS_VECTOR|GMT_IS_DUPLICATE", strings=strings - ) - # Turns out wesn doesn't matter for Datasets - wesn = [0] * 6 - # Save the data to a file to see if it's being accessed correctly - with GMTTempFile() as tmp_file: - lib.write_data( - "GMT_IS_VECTOR", - "GMT_IS_POINT", - "GMT_WRITE_SET", - wesn, - tmp_file.name, - dataset, - ) - # Load the data and check that it's correct - newx, newy, newstrings = tmp_file.loadtxt( - unpack=True, dtype=[("x", np.int32), ("y", np.int32), ("text", " 0 - - -def test_grd2cpt_unrecognized_data_type(): - """ - Test that an error will be raised if an invalid data type is passed to - grid. - """ - with pytest.raises(GMTInvalidInput): - grd2cpt(grid=0) - - -def test_grd2cpt_categorical_and_cyclic(grid): - """ - Use incorrect setting by setting both categorical and cyclic to True. - """ - with pytest.raises(GMTInvalidInput): - grd2cpt(grid=grid, cmap="batlow", categorical=True, cyclic=True) +""" +Tests for grd2cpt. +""" +import os + +import pytest +from pygmt import Figure +from pygmt.datasets import load_earth_relief +from pygmt.exceptions import GMTInvalidInput +from pygmt.helpers import GMTTempFile +from pygmt.helpers.testing import check_figures_equal +from pygmt.src.grd2cpt import grd2cpt + + +@pytest.fixture(scope="module", name="grid") +def fixture_grid(): + """ + Load the grid data from the sample earth_relief file. + """ + return load_earth_relief() + + +@check_figures_equal() +def test_grd2cpt(grid): + """ + Test creating a CPT with grd2cpt to create a CPT based off a grid input and + plot it with a color bar. + """ + fig_ref, fig_test = Figure(), Figure() + # Use single-character arguments for the reference image + fig_ref.basemap(B="a", J="W0/15c", R="d") + grd2cpt(grid="@earth_relief_01d") + fig_ref.colorbar(B="a2000") + fig_test.basemap(frame="a", projection="W0/15c", region="d") + grd2cpt(grid=grid) + fig_test.colorbar(frame="a2000") + return fig_ref, fig_test + + +def test_grd2cpt_blank_output(grid): + """ + Use incorrect setting by passing in blank file name to output parameter. + """ + with pytest.raises(GMTInvalidInput): + grd2cpt(grid=grid, output="") + + +def test_grd2cpt_invalid_output(grid): + """ + Use incorrect setting by passing in invalid type to output parameter. + """ + with pytest.raises(GMTInvalidInput): + grd2cpt(grid=grid, output=["some.cpt"]) + + +def test_grd2cpt_output_to_cpt_file(grid): + """ + Save the generated static color palette table to a .cpt file. + """ + with GMTTempFile(suffix=".cpt") as cptfile: + grd2cpt(grid=grid, output=cptfile.name) + assert os.path.getsize(cptfile.name) > 0 + + +def test_grd2cpt_unrecognized_data_type(): + """ + Test that an error will be raised if an invalid data type is passed to + grid. + """ + with pytest.raises(GMTInvalidInput): + grd2cpt(grid=0) + + +def test_grd2cpt_categorical_and_cyclic(grid): + """ + Use incorrect setting by setting both categorical and cyclic to True. + """ + with pytest.raises(GMTInvalidInput): + grd2cpt(grid=grid, cmap="batlow", categorical=True, cyclic=True) diff --git a/pygmt/tests/test_grdcontour.py b/pygmt/tests/test_grdcontour.py index b208be55d6e..83759acb0ae 100644 --- a/pygmt/tests/test_grdcontour.py +++ b/pygmt/tests/test_grdcontour.py @@ -1,130 +1,130 @@ -""" -Test Figure.grdcontour. -""" -import os - -import numpy as np -import pytest -from pygmt import Figure -from pygmt.datasets import load_earth_relief -from pygmt.exceptions import GMTInvalidInput -from pygmt.helpers.testing import check_figures_equal - -TEST_DATA_DIR = os.path.join(os.path.dirname(__file__), "data") -TEST_CONTOUR_FILE = os.path.join(TEST_DATA_DIR, "contours.txt") - - -@pytest.fixture(scope="module", name="grid") -def fixture_grid(): - """ - Load the grid data from the sample earth_relief file. - """ - return load_earth_relief(registration="gridline") - - -@check_figures_equal() -def test_grdcontour(grid): - """ - Plot a contour image using an xarray grid with fixed contour interval. - """ - fig_ref, fig_test = Figure(), Figure() - kwargs = dict(interval="1000", projection="W0/6i") - fig_ref.grdcontour("@earth_relief_01d_g", **kwargs) - fig_test.grdcontour(grid, **kwargs) - return fig_ref, fig_test - - -@check_figures_equal() -def test_grdcontour_labels(grid): - """ - Plot a contour image using a xarray grid with contour labels and alternate - colors. - """ - fig_ref, fig_test = Figure(), Figure() - kwargs = dict( - interval="1000", - annotation="5000", - projection="W0/6i", - pen=["a1p,red", "c0.5p,black"], - label_placement="d3i", - ) - fig_ref.grdcontour("@earth_relief_01d_g", **kwargs) - fig_test.grdcontour(grid, **kwargs) - return fig_ref, fig_test - - -@check_figures_equal() -def test_grdcontour_slice(grid): - """ - Plot an contour image using an xarray grid that has been sliced. - """ - - fig_ref, fig_test = Figure(), Figure() - - grid_ = grid.sel(lat=slice(-30, 30)) - kwargs = dict(interval="1000", projection="M6i") - fig_ref.grdcontour( - grid="@earth_relief_01d_g", region=[-180, 180, -30, 30], **kwargs - ) - fig_test.grdcontour(grid=grid_, **kwargs) - return fig_ref, fig_test - - -@pytest.mark.mpl_image_compare -def test_grdcontour_file(): - """ - Plot a contour image using grid file input. - """ - fig = Figure() - fig.grdcontour( - "@earth_relief_01d_g", - interval="1000", - limit="0", - pen="0.5p,black", - region=[-180, 180, -70, 70], - projection="M10i", - ) - return fig - - -@check_figures_equal() -def test_grdcontour_interval_file_full_opts(): - """ - Plot based on external contour level file. - """ - fig_ref, fig_test = Figure(), Figure() - # Use single-character arguments for the reference image - comargs_ref = { - "grid": "@earth_relief_10m", - "R": "-161.5/-154/18.5/23", - "C": TEST_CONTOUR_FILE, - "S": 100, - "J": "M6i", - "Q": 10, - } - fig_ref.grdcontour(**comargs_ref, L="-25000/-1", W=["a1p,blue", "c0.5p,blue"]) - fig_ref.grdcontour(**comargs_ref, L="0", W=["a1p,black", "c0.5p,black"]) - - comargs_test = { - "region": [-161.5, -154, 18.5, 23], - "interval": TEST_CONTOUR_FILE, - "grid": "@earth_relief_10m", - "resample": "100", - "projection": "M6i", - "cut": 10, - } - fig_test.grdcontour( - **comargs_test, limit=(-25000, -1), pen=["a1p,blue", "c0.5p,blue"] - ) - fig_test.grdcontour(**comargs_test, limit=0, pen=["a1p,black", "c0.5p,black"]) - - return fig_ref, fig_test - - -def test_grdcontour_fails(): - """ - Should fail for unrecognized input. - """ - fig = Figure() - with pytest.raises(GMTInvalidInput): - fig.grdcontour(np.arange(20).reshape((4, 5))) +""" +Test Figure.grdcontour. +""" +import os + +import numpy as np +import pytest +from pygmt import Figure +from pygmt.datasets import load_earth_relief +from pygmt.exceptions import GMTInvalidInput +from pygmt.helpers.testing import check_figures_equal + +TEST_DATA_DIR = os.path.join(os.path.dirname(__file__), "data") +TEST_CONTOUR_FILE = os.path.join(TEST_DATA_DIR, "contours.txt") + + +@pytest.fixture(scope="module", name="grid") +def fixture_grid(): + """ + Load the grid data from the sample earth_relief file. + """ + return load_earth_relief(registration="gridline") + + +@check_figures_equal() +def test_grdcontour(grid): + """ + Plot a contour image using an xarray grid with fixed contour interval. + """ + fig_ref, fig_test = Figure(), Figure() + kwargs = dict(interval="1000", projection="W0/6i") + fig_ref.grdcontour("@earth_relief_01d_g", **kwargs) + fig_test.grdcontour(grid, **kwargs) + return fig_ref, fig_test + + +@check_figures_equal() +def test_grdcontour_labels(grid): + """ + Plot a contour image using a xarray grid with contour labels and alternate + colors. + """ + fig_ref, fig_test = Figure(), Figure() + kwargs = dict( + interval="1000", + annotation="5000", + projection="W0/6i", + pen=["a1p,red", "c0.5p,black"], + label_placement="d3i", + ) + fig_ref.grdcontour("@earth_relief_01d_g", **kwargs) + fig_test.grdcontour(grid, **kwargs) + return fig_ref, fig_test + + +@check_figures_equal() +def test_grdcontour_slice(grid): + """ + Plot an contour image using an xarray grid that has been sliced. + """ + + fig_ref, fig_test = Figure(), Figure() + + grid_ = grid.sel(lat=slice(-30, 30)) + kwargs = dict(interval="1000", projection="M6i") + fig_ref.grdcontour( + grid="@earth_relief_01d_g", region=[-180, 180, -30, 30], **kwargs + ) + fig_test.grdcontour(grid=grid_, **kwargs) + return fig_ref, fig_test + + +@pytest.mark.mpl_image_compare +def test_grdcontour_file(): + """ + Plot a contour image using grid file input. + """ + fig = Figure() + fig.grdcontour( + "@earth_relief_01d_g", + interval="1000", + limit="0", + pen="0.5p,black", + region=[-180, 180, -70, 70], + projection="M10i", + ) + return fig + + +@check_figures_equal() +def test_grdcontour_interval_file_full_opts(): + """ + Plot based on external contour level file. + """ + fig_ref, fig_test = Figure(), Figure() + # Use single-character arguments for the reference image + comargs_ref = { + "grid": "@earth_relief_10m", + "R": "-161.5/-154/18.5/23", + "C": TEST_CONTOUR_FILE, + "S": 100, + "J": "M6i", + "Q": 10, + } + fig_ref.grdcontour(**comargs_ref, L="-25000/-1", W=["a1p,blue", "c0.5p,blue"]) + fig_ref.grdcontour(**comargs_ref, L="0", W=["a1p,black", "c0.5p,black"]) + + comargs_test = { + "region": [-161.5, -154, 18.5, 23], + "interval": TEST_CONTOUR_FILE, + "grid": "@earth_relief_10m", + "resample": "100", + "projection": "M6i", + "cut": 10, + } + fig_test.grdcontour( + **comargs_test, limit=(-25000, -1), pen=["a1p,blue", "c0.5p,blue"] + ) + fig_test.grdcontour(**comargs_test, limit=0, pen=["a1p,black", "c0.5p,black"]) + + return fig_ref, fig_test + + +def test_grdcontour_fails(): + """ + Should fail for unrecognized input. + """ + fig = Figure() + with pytest.raises(GMTInvalidInput): + fig.grdcontour(np.arange(20).reshape((4, 5))) diff --git a/pygmt/tests/test_grdcut.py b/pygmt/tests/test_grdcut.py index d1676fca669..486861ee7fe 100644 --- a/pygmt/tests/test_grdcut.py +++ b/pygmt/tests/test_grdcut.py @@ -1,91 +1,91 @@ -""" -Tests for grdcut. -""" -import os - -import numpy as np -import pytest -import xarray as xr -from pygmt import grdcut, grdinfo -from pygmt.datasets import load_earth_relief -from pygmt.exceptions import GMTInvalidInput -from pygmt.helpers import GMTTempFile - - -@pytest.fixture(scope="module", name="grid") -def fixture_grid(): - """ - Load the grid data from the sample earth_relief file. - """ - return load_earth_relief(registration="pixel") - - -def test_grdcut_file_in_file_out(): - """ - grdcut an input grid file, and output to a grid file. - """ - with GMTTempFile(suffix=".nc") as tmpfile: - result = grdcut("@earth_relief_01d", outgrid=tmpfile.name, region="0/180/0/90") - assert result is None # return value is None - assert os.path.exists(path=tmpfile.name) # check that outgrid exists - result = grdinfo(tmpfile.name, per_column=True) - assert result == "0 180 0 90 -8182 5651.5 1 1 180 90 1 1\n" - - -def test_grdcut_file_in_dataarray_out(): - """ - grdcut an input grid file, and output as DataArray. - """ - outgrid = grdcut("@earth_relief_01d", region="0/180/0/90") - assert isinstance(outgrid, xr.DataArray) - assert outgrid.gmt.registration == 1 # Pixel registration - assert outgrid.gmt.gtype == 1 # Geographic type - # check information of the output grid - # the '@earth_relief_01d' is in pixel registration, so the grid range is - # not exactly 0/180/0/90 - assert outgrid.coords["lat"].data.min() == 0.5 - assert outgrid.coords["lat"].data.max() == 89.5 - assert outgrid.coords["lon"].data.min() == 0.5 - assert outgrid.coords["lon"].data.max() == 179.5 - assert outgrid.data.min() == -8182.0 - assert outgrid.data.max() == 5651.5 - assert outgrid.sizes["lat"] == 90 - assert outgrid.sizes["lon"] == 180 - - -def test_grdcut_dataarray_in_file_out(grid): - """ - grdcut an input DataArray, and output to a grid file. - """ - with GMTTempFile(suffix=".nc") as tmpfile: - result = grdcut(grid, outgrid=tmpfile.name, region="0/180/0/90") - assert result is None # grdcut returns None if output to a file - result = grdinfo(tmpfile.name, per_column=True) - assert result == "0 180 0 90 -8182 5651.5 1 1 180 90 1 1\n" - - -def test_grdcut_dataarray_in_dataarray_out(grid): - """ - grdcut an input DataArray, and output as DataArray. - """ - outgrid = grdcut(grid, region="0/180/0/90") - assert isinstance(outgrid, xr.DataArray) - # check information of the output grid - # the '@earth_relief_01d' is in pixel registration, so the grid range is - # not exactly 0/180/0/90 - assert outgrid.coords["lat"].data.min() == 0.5 - assert outgrid.coords["lat"].data.max() == 89.5 - assert outgrid.coords["lon"].data.min() == 0.5 - assert outgrid.coords["lon"].data.max() == 179.5 - assert outgrid.data.min() == -8182.0 - assert outgrid.data.max() == 5651.5 - assert outgrid.sizes["lat"] == 90 - assert outgrid.sizes["lon"] == 180 - - -def test_grdcut_fails(): - """ - Check that grdcut fails correctly. - """ - with pytest.raises(GMTInvalidInput): - grdcut(np.arange(10).reshape((5, 2))) +""" +Tests for grdcut. +""" +import os + +import numpy as np +import pytest +import xarray as xr +from pygmt import grdcut, grdinfo +from pygmt.datasets import load_earth_relief +from pygmt.exceptions import GMTInvalidInput +from pygmt.helpers import GMTTempFile + + +@pytest.fixture(scope="module", name="grid") +def fixture_grid(): + """ + Load the grid data from the sample earth_relief file. + """ + return load_earth_relief(registration="pixel") + + +def test_grdcut_file_in_file_out(): + """ + grdcut an input grid file, and output to a grid file. + """ + with GMTTempFile(suffix=".nc") as tmpfile: + result = grdcut("@earth_relief_01d", outgrid=tmpfile.name, region="0/180/0/90") + assert result is None # return value is None + assert os.path.exists(path=tmpfile.name) # check that outgrid exists + result = grdinfo(tmpfile.name, per_column=True) + assert result == "0 180 0 90 -8182 5651.5 1 1 180 90 1 1\n" + + +def test_grdcut_file_in_dataarray_out(): + """ + grdcut an input grid file, and output as DataArray. + """ + outgrid = grdcut("@earth_relief_01d", region="0/180/0/90") + assert isinstance(outgrid, xr.DataArray) + assert outgrid.gmt.registration == 1 # Pixel registration + assert outgrid.gmt.gtype == 1 # Geographic type + # check information of the output grid + # the '@earth_relief_01d' is in pixel registration, so the grid range is + # not exactly 0/180/0/90 + assert outgrid.coords["lat"].data.min() == 0.5 + assert outgrid.coords["lat"].data.max() == 89.5 + assert outgrid.coords["lon"].data.min() == 0.5 + assert outgrid.coords["lon"].data.max() == 179.5 + assert outgrid.data.min() == -8182.0 + assert outgrid.data.max() == 5651.5 + assert outgrid.sizes["lat"] == 90 + assert outgrid.sizes["lon"] == 180 + + +def test_grdcut_dataarray_in_file_out(grid): + """ + grdcut an input DataArray, and output to a grid file. + """ + with GMTTempFile(suffix=".nc") as tmpfile: + result = grdcut(grid, outgrid=tmpfile.name, region="0/180/0/90") + assert result is None # grdcut returns None if output to a file + result = grdinfo(tmpfile.name, per_column=True) + assert result == "0 180 0 90 -8182 5651.5 1 1 180 90 1 1\n" + + +def test_grdcut_dataarray_in_dataarray_out(grid): + """ + grdcut an input DataArray, and output as DataArray. + """ + outgrid = grdcut(grid, region="0/180/0/90") + assert isinstance(outgrid, xr.DataArray) + # check information of the output grid + # the '@earth_relief_01d' is in pixel registration, so the grid range is + # not exactly 0/180/0/90 + assert outgrid.coords["lat"].data.min() == 0.5 + assert outgrid.coords["lat"].data.max() == 89.5 + assert outgrid.coords["lon"].data.min() == 0.5 + assert outgrid.coords["lon"].data.max() == 179.5 + assert outgrid.data.min() == -8182.0 + assert outgrid.data.max() == 5651.5 + assert outgrid.sizes["lat"] == 90 + assert outgrid.sizes["lon"] == 180 + + +def test_grdcut_fails(): + """ + Check that grdcut fails correctly. + """ + with pytest.raises(GMTInvalidInput): + grdcut(np.arange(10).reshape((5, 2))) diff --git a/pygmt/tests/test_grdfilter.py b/pygmt/tests/test_grdfilter.py index bb064327223..c5449100cd0 100644 --- a/pygmt/tests/test_grdfilter.py +++ b/pygmt/tests/test_grdfilter.py @@ -1,100 +1,100 @@ -""" -Tests for grdfilter. -""" -import os - -import numpy as np -import numpy.testing as npt -import pytest -import xarray as xr -from pygmt import grdfilter, grdinfo -from pygmt.datasets import load_earth_relief -from pygmt.exceptions import GMTInvalidInput -from pygmt.helpers import GMTTempFile - - -@pytest.fixture(scope="module", name="grid") -def fixture_grid(): - """ - Load the grid data from the sample earth_relief file. - """ - return load_earth_relief(registration="pixel") - - -def test_grfilter_dataarray_in_dataarray_out(grid): - """ - grdfilter an input DataArray, and output as DataArray. - """ - result = grdfilter(grid=grid, filter="g600", distance="4") - # check information of the output grid - assert isinstance(result, xr.DataArray) - assert result.coords["lat"].data.min() == -89.5 - assert result.coords["lat"].data.max() == 89.5 - assert result.coords["lon"].data.min() == -179.5 - assert result.coords["lon"].data.max() == 179.5 - npt.assert_almost_equal(result.data.min(), -6147.47265625, decimal=2) - npt.assert_almost_equal(result.data.max(), 5164.1157, decimal=2) - assert result.sizes["lat"] == 180 - assert result.sizes["lon"] == 360 - - -def test_grdfilter_dataarray_in_file_out(grid): - """ - grdfilter an input DataArray, and output to a grid file. - """ - with GMTTempFile(suffix=".nc") as tmpfile: - result = grdfilter(grid, outgrid=tmpfile.name, filter="g600", distance="4") - assert result is None # grdfilter returns None if output to a file - result = grdinfo(tmpfile.name, per_column=True) - assert ( - result == "-180 180 -90 90 -6147.47265625 5164.11572266 1 1 360 180 1 1\n" - ) - - -def test_grfilter_file_in_dataarray_out(): - """ - grdfilter an input grid file, and output as DataArray. - """ - outgrid = grdfilter( - "@earth_relief_01d", region="0/180/0/90", filter="g600", distance="4" - ) - assert isinstance(outgrid, xr.DataArray) - assert outgrid.gmt.registration == 1 # Pixel registration - assert outgrid.gmt.gtype == 1 # Geographic type - # check information of the output DataArray - # the '@earth_relief_01d' is in pixel registration, so the grid range is - # not exactly 0/180/0/90 - assert outgrid.coords["lat"].data.min() == 0.5 - assert outgrid.coords["lat"].data.max() == 89.5 - assert outgrid.coords["lon"].data.min() == 0.5 - assert outgrid.coords["lon"].data.max() == 179.5 - npt.assert_almost_equal(outgrid.data.min(), -6147.4907, decimal=2) - npt.assert_almost_equal(outgrid.data.max(), 5164.06, decimal=2) - assert outgrid.sizes["lat"] == 90 - assert outgrid.sizes["lon"] == 180 - - -def test_grdfilter_file_in_file_out(): - """ - grdfilter an input grid file, and output to a grid file. - """ - with GMTTempFile(suffix=".nc") as tmpfile: - result = grdfilter( - "@earth_relief_01d", - outgrid=tmpfile.name, - region=[0, 180, 0, 90], - filter="g600", - distance="4", - ) - assert result is None # return value is None - assert os.path.exists(path=tmpfile.name) # check that outgrid exists - result = grdinfo(tmpfile.name, per_column=True) - assert result == "0 180 0 90 -6147.49072266 5164.06005859 1 1 180 90 1 1\n" - - -def test_grdfilter_fails(): - """ - Check that grdfilter fails correctly. - """ - with pytest.raises(GMTInvalidInput): - grdfilter(np.arange(10).reshape((5, 2))) +""" +Tests for grdfilter. +""" +import os + +import numpy as np +import numpy.testing as npt +import pytest +import xarray as xr +from pygmt import grdfilter, grdinfo +from pygmt.datasets import load_earth_relief +from pygmt.exceptions import GMTInvalidInput +from pygmt.helpers import GMTTempFile + + +@pytest.fixture(scope="module", name="grid") +def fixture_grid(): + """ + Load the grid data from the sample earth_relief file. + """ + return load_earth_relief(registration="pixel") + + +def test_grfilter_dataarray_in_dataarray_out(grid): + """ + grdfilter an input DataArray, and output as DataArray. + """ + result = grdfilter(grid=grid, filter="g600", distance="4") + # check information of the output grid + assert isinstance(result, xr.DataArray) + assert result.coords["lat"].data.min() == -89.5 + assert result.coords["lat"].data.max() == 89.5 + assert result.coords["lon"].data.min() == -179.5 + assert result.coords["lon"].data.max() == 179.5 + npt.assert_almost_equal(result.data.min(), -6147.47265625, decimal=2) + npt.assert_almost_equal(result.data.max(), 5164.1157, decimal=2) + assert result.sizes["lat"] == 180 + assert result.sizes["lon"] == 360 + + +def test_grdfilter_dataarray_in_file_out(grid): + """ + grdfilter an input DataArray, and output to a grid file. + """ + with GMTTempFile(suffix=".nc") as tmpfile: + result = grdfilter(grid, outgrid=tmpfile.name, filter="g600", distance="4") + assert result is None # grdfilter returns None if output to a file + result = grdinfo(tmpfile.name, per_column=True) + assert ( + result == "-180 180 -90 90 -6147.47265625 5164.11572266 1 1 360 180 1 1\n" + ) + + +def test_grfilter_file_in_dataarray_out(): + """ + grdfilter an input grid file, and output as DataArray. + """ + outgrid = grdfilter( + "@earth_relief_01d", region="0/180/0/90", filter="g600", distance="4" + ) + assert isinstance(outgrid, xr.DataArray) + assert outgrid.gmt.registration == 1 # Pixel registration + assert outgrid.gmt.gtype == 1 # Geographic type + # check information of the output DataArray + # the '@earth_relief_01d' is in pixel registration, so the grid range is + # not exactly 0/180/0/90 + assert outgrid.coords["lat"].data.min() == 0.5 + assert outgrid.coords["lat"].data.max() == 89.5 + assert outgrid.coords["lon"].data.min() == 0.5 + assert outgrid.coords["lon"].data.max() == 179.5 + npt.assert_almost_equal(outgrid.data.min(), -6147.4907, decimal=2) + npt.assert_almost_equal(outgrid.data.max(), 5164.06, decimal=2) + assert outgrid.sizes["lat"] == 90 + assert outgrid.sizes["lon"] == 180 + + +def test_grdfilter_file_in_file_out(): + """ + grdfilter an input grid file, and output to a grid file. + """ + with GMTTempFile(suffix=".nc") as tmpfile: + result = grdfilter( + "@earth_relief_01d", + outgrid=tmpfile.name, + region=[0, 180, 0, 90], + filter="g600", + distance="4", + ) + assert result is None # return value is None + assert os.path.exists(path=tmpfile.name) # check that outgrid exists + result = grdinfo(tmpfile.name, per_column=True) + assert result == "0 180 0 90 -6147.49072266 5164.06005859 1 1 180 90 1 1\n" + + +def test_grdfilter_fails(): + """ + Check that grdfilter fails correctly. + """ + with pytest.raises(GMTInvalidInput): + grdfilter(np.arange(10).reshape((5, 2))) diff --git a/pygmt/tests/test_grdimage.py b/pygmt/tests/test_grdimage.py index 2927c95e1d1..3c37742e01d 100644 --- a/pygmt/tests/test_grdimage.py +++ b/pygmt/tests/test_grdimage.py @@ -1,183 +1,183 @@ -""" -Test Figure.grdimage. -""" -import sys - -import numpy as np -import pytest -import xarray as xr -from packaging.version import Version -from pygmt import Figure, clib -from pygmt.datasets import load_earth_relief -from pygmt.exceptions import GMTInvalidInput -from pygmt.helpers.testing import check_figures_equal - -with clib.Session() as _lib: - gmt_version = Version(_lib.info["version"]) - - -@pytest.fixture(scope="module", name="grid") -def fixture_grid(): - """ - Load the grid data from the sample earth_relief file. - """ - return load_earth_relief(registration="gridline") - - -@pytest.fixture(scope="module", name="xrgrid") -def fixture_xrgrid(): - """ - Create a sample xarray.DataArray grid for testing. - """ - longitude = np.arange(0, 360, 1) - latitude = np.arange(-89, 90, 1) - x = np.sin(np.deg2rad(longitude)) - y = np.linspace(start=0, stop=1, num=179) - data = y[:, np.newaxis] * x - - return xr.DataArray( - data, - coords=[ - ("latitude", latitude, {"units": "degrees_north"}), - ("longitude", longitude, {"units": "degrees_east"}), - ], - attrs={"actual_range": [-1, 1]}, - ) - - -@pytest.mark.mpl_image_compare -def test_grdimage(grid): - """ - Plot an image using an xarray grid. - """ - fig = Figure() - fig.grdimage(grid, cmap="earth", projection="W0/6i") - return fig - - -@pytest.mark.mpl_image_compare -def test_grdimage_slice(grid): - """ - Plot an image using an xarray grid that has been sliced. - """ - grid_ = grid.sel(lat=slice(-30, 30)) - fig = Figure() - fig.grdimage(grid_, cmap="earth", projection="M6i") - return fig - - -@pytest.mark.mpl_image_compare -def test_grdimage_file(): - """ - Plot an image using file input. - """ - fig = Figure() - fig.grdimage( - "@earth_relief_01d_g", - cmap="ocean", - region=[-180, 180, -70, 70], - projection="W0/10i", - shading=True, - ) - return fig - - -@pytest.mark.skipif( - gmt_version <= Version("6.1.1") and sys.platform == "darwin", - reason="Upstream bug in GMT 6.1.1 that causes segfault on macOS", -) -@pytest.mark.xfail( - condition=gmt_version <= Version("6.1.1") and sys.platform != "darwin", - reason="Upstream bug in GMT 6.1.1 that causes this test to fail on Linux/Windows", -) -@check_figures_equal() -@pytest.mark.parametrize( - "shading", - [True, 0.5, "+a30+nt0.8", "@earth_relief_01d_g+d", "@earth_relief_01d_g+a60+nt0.8"], -) -def test_grdimage_shading_xarray(grid, shading): - """ - Test that shading works well for xarray. - - The ``shading`` can be True, a constant intensity, some modifiers, or - a grid with modifiers. - - See https://github.com/GenericMappingTools/pygmt/issues/364 and - https://github.com/GenericMappingTools/pygmt/issues/618. - """ - fig_ref, fig_test = Figure(), Figure() - kwargs = dict( - region=[-180, 180, -90, 90], - frame=True, - projection="Cyl_stere/6i", - cmap="geo", - shading=shading, - ) - - fig_ref.grdimage("@earth_relief_01d_g", **kwargs) - fig_test.grdimage(grid, **kwargs) - return fig_ref, fig_test - - -def test_grdimage_fails(): - """ - Should fail for unrecognized input. - """ - fig = Figure() - with pytest.raises(GMTInvalidInput): - fig.grdimage(np.arange(20).reshape((4, 5))) - - -@pytest.mark.mpl_image_compare -def test_grdimage_over_dateline(xrgrid): - """ - Ensure no gaps are plotted over the 180 degree international dateline. - - Specifically checking that `xrgrid.gmt.gtype = 1` sets `GMT_GRID_IS_GEO`, - and that `xrgrid.gmt.registration = 0` sets `GMT_GRID_NODE_REG`. Note that - there would be a gap over the dateline if a pixel registered grid is used. - See also https://github.com/GenericMappingTools/pygmt/issues/375. - """ - fig = Figure() - assert xrgrid.gmt.registration == 0 # gridline registration - xrgrid.gmt.gtype = 1 # geographic coordinate system - fig.grdimage(grid=xrgrid, region="g", projection="A0/0/1c", V="i") - return fig - - -@check_figures_equal() -@pytest.mark.parametrize("lon0", [0, 123, 180]) -@pytest.mark.parametrize("proj_type", ["H", "W"]) -def test_grdimage_central_meridians(grid, proj_type, lon0): - """ - Test that plotting a grid with different central meridians (lon0) using - Hammer (H) and Mollweide (W) projection systems work. - """ - fig_ref, fig_test = Figure(), Figure() - fig_ref.grdimage( - "@earth_relief_01d_g", projection=f"{proj_type}{lon0}/15c", cmap="geo" - ) - fig_test.grdimage(grid, projection=f"{proj_type}{lon0}/15c", cmap="geo") - return fig_ref, fig_test - - -# Cylindrical Equidistant (Q) projections plotted with xarray and NetCDF grids -# are still slightly different with an RMS error of 25, see issue at -# https://github.com/GenericMappingTools/pygmt/issues/390 -# TO-DO remove tol=1.5 and pytest.mark.xfail once bug is solved in upstream GMT -@check_figures_equal(tol=1.5) -@pytest.mark.parametrize("lat0", [0, 30]) -@pytest.mark.parametrize("lon0", [0, 123, 180]) -@pytest.mark.parametrize("proj_type", [pytest.param("Q", marks=pytest.mark.xfail), "S"]) -def test_grdimage_central_meridians_and_standard_parallels(grid, proj_type, lon0, lat0): - """ - Test that plotting a grid with different central meridians (lon0) and - standard_parallels (lat0) using Cylindrical Equidistant (Q) and General - Stereographic (S) projection systems work. - """ - fig_ref, fig_test = Figure(), Figure() - fig_ref.grdimage( - "@earth_relief_01d_g", projection=f"{proj_type}{lon0}/{lat0}/15c", cmap="geo" - ) - fig_test.grdimage(grid, projection=f"{proj_type}{lon0}/{lat0}/15c", cmap="geo") - return fig_ref, fig_test +""" +Test Figure.grdimage. +""" +import sys + +import numpy as np +import pytest +import xarray as xr +from packaging.version import Version +from pygmt import Figure, clib +from pygmt.datasets import load_earth_relief +from pygmt.exceptions import GMTInvalidInput +from pygmt.helpers.testing import check_figures_equal + +with clib.Session() as _lib: + gmt_version = Version(_lib.info["version"]) + + +@pytest.fixture(scope="module", name="grid") +def fixture_grid(): + """ + Load the grid data from the sample earth_relief file. + """ + return load_earth_relief(registration="gridline") + + +@pytest.fixture(scope="module", name="xrgrid") +def fixture_xrgrid(): + """ + Create a sample xarray.DataArray grid for testing. + """ + longitude = np.arange(0, 360, 1) + latitude = np.arange(-89, 90, 1) + x = np.sin(np.deg2rad(longitude)) + y = np.linspace(start=0, stop=1, num=179) + data = y[:, np.newaxis] * x + + return xr.DataArray( + data, + coords=[ + ("latitude", latitude, {"units": "degrees_north"}), + ("longitude", longitude, {"units": "degrees_east"}), + ], + attrs={"actual_range": [-1, 1]}, + ) + + +@pytest.mark.mpl_image_compare +def test_grdimage(grid): + """ + Plot an image using an xarray grid. + """ + fig = Figure() + fig.grdimage(grid, cmap="earth", projection="W0/6i") + return fig + + +@pytest.mark.mpl_image_compare +def test_grdimage_slice(grid): + """ + Plot an image using an xarray grid that has been sliced. + """ + grid_ = grid.sel(lat=slice(-30, 30)) + fig = Figure() + fig.grdimage(grid_, cmap="earth", projection="M6i") + return fig + + +@pytest.mark.mpl_image_compare +def test_grdimage_file(): + """ + Plot an image using file input. + """ + fig = Figure() + fig.grdimage( + "@earth_relief_01d_g", + cmap="ocean", + region=[-180, 180, -70, 70], + projection="W0/10i", + shading=True, + ) + return fig + + +@pytest.mark.skipif( + gmt_version <= Version("6.1.1") and sys.platform == "darwin", + reason="Upstream bug in GMT 6.1.1 that causes segfault on macOS", +) +@pytest.mark.xfail( + condition=gmt_version <= Version("6.1.1") and sys.platform != "darwin", + reason="Upstream bug in GMT 6.1.1 that causes this test to fail on Linux/Windows", +) +@check_figures_equal() +@pytest.mark.parametrize( + "shading", + [True, 0.5, "+a30+nt0.8", "@earth_relief_01d_g+d", "@earth_relief_01d_g+a60+nt0.8"], +) +def test_grdimage_shading_xarray(grid, shading): + """ + Test that shading works well for xarray. + + The ``shading`` can be True, a constant intensity, some modifiers, or + a grid with modifiers. + + See https://github.com/GenericMappingTools/pygmt/issues/364 and + https://github.com/GenericMappingTools/pygmt/issues/618. + """ + fig_ref, fig_test = Figure(), Figure() + kwargs = dict( + region=[-180, 180, -90, 90], + frame=True, + projection="Cyl_stere/6i", + cmap="geo", + shading=shading, + ) + + fig_ref.grdimage("@earth_relief_01d_g", **kwargs) + fig_test.grdimage(grid, **kwargs) + return fig_ref, fig_test + + +def test_grdimage_fails(): + """ + Should fail for unrecognized input. + """ + fig = Figure() + with pytest.raises(GMTInvalidInput): + fig.grdimage(np.arange(20).reshape((4, 5))) + + +@pytest.mark.mpl_image_compare +def test_grdimage_over_dateline(xrgrid): + """ + Ensure no gaps are plotted over the 180 degree international dateline. + + Specifically checking that `xrgrid.gmt.gtype = 1` sets `GMT_GRID_IS_GEO`, + and that `xrgrid.gmt.registration = 0` sets `GMT_GRID_NODE_REG`. Note that + there would be a gap over the dateline if a pixel registered grid is used. + See also https://github.com/GenericMappingTools/pygmt/issues/375. + """ + fig = Figure() + assert xrgrid.gmt.registration == 0 # gridline registration + xrgrid.gmt.gtype = 1 # geographic coordinate system + fig.grdimage(grid=xrgrid, region="g", projection="A0/0/1c", V="i") + return fig + + +@check_figures_equal() +@pytest.mark.parametrize("lon0", [0, 123, 180]) +@pytest.mark.parametrize("proj_type", ["H", "W"]) +def test_grdimage_central_meridians(grid, proj_type, lon0): + """ + Test that plotting a grid with different central meridians (lon0) using + Hammer (H) and Mollweide (W) projection systems work. + """ + fig_ref, fig_test = Figure(), Figure() + fig_ref.grdimage( + "@earth_relief_01d_g", projection=f"{proj_type}{lon0}/15c", cmap="geo" + ) + fig_test.grdimage(grid, projection=f"{proj_type}{lon0}/15c", cmap="geo") + return fig_ref, fig_test + + +# Cylindrical Equidistant (Q) projections plotted with xarray and NetCDF grids +# are still slightly different with an RMS error of 25, see issue at +# https://github.com/GenericMappingTools/pygmt/issues/390 +# TO-DO remove tol=1.5 and pytest.mark.xfail once bug is solved in upstream GMT +@check_figures_equal(tol=1.5) +@pytest.mark.parametrize("lat0", [0, 30]) +@pytest.mark.parametrize("lon0", [0, 123, 180]) +@pytest.mark.parametrize("proj_type", [pytest.param("Q", marks=pytest.mark.xfail), "S"]) +def test_grdimage_central_meridians_and_standard_parallels(grid, proj_type, lon0, lat0): + """ + Test that plotting a grid with different central meridians (lon0) and + standard_parallels (lat0) using Cylindrical Equidistant (Q) and General + Stereographic (S) projection systems work. + """ + fig_ref, fig_test = Figure(), Figure() + fig_ref.grdimage( + "@earth_relief_01d_g", projection=f"{proj_type}{lon0}/{lat0}/15c", cmap="geo" + ) + fig_test.grdimage(grid, projection=f"{proj_type}{lon0}/{lat0}/15c", cmap="geo") + return fig_ref, fig_test diff --git a/pygmt/tests/test_grdinfo.py b/pygmt/tests/test_grdinfo.py index 5526a23811e..e982a6406eb 100644 --- a/pygmt/tests/test_grdinfo.py +++ b/pygmt/tests/test_grdinfo.py @@ -1,46 +1,46 @@ -""" -Tests for grdinfo. -""" -import numpy as np -import pytest -from pygmt import grdinfo -from pygmt.datasets import load_earth_relief -from pygmt.exceptions import GMTInvalidInput - - -def test_grdinfo(): - """ - Make sure grd info works as expected. - """ - grid = load_earth_relief(registration="gridline") - result = grdinfo(grid=grid, force_scan=0, per_column="n") - assert result.strip() == "-180 180 -90 90 -8592.5 5559 1 1 361 181 0 1" - - -def test_grdinfo_file(): - """ - Test grdinfo with file input. - """ - result = grdinfo(grid="@earth_relief_01d", force_scan=0, per_column="n") - assert result.strip() == "-180 180 -90 90 -8182 5651.5 1 1 360 180 1 1" - - -def test_grdinfo_fails(): - """ - Check that grdinfo fails correctly. - """ - with pytest.raises(GMTInvalidInput): - grdinfo(np.arange(10).reshape((5, 2))) - - -def test_grdinfo_region(): - """ - Check that the region argument works in grdinfo. - """ - result = grdinfo( - grid="@earth_relief_01d", - force_scan=0, - per_column="n", - region=[-170, 170, -80, 80], - ) - assert result.strip() == "-170 170 -80 80 -8182 5651.5 1 1 340 160 1 1" +""" +Tests for grdinfo. +""" +import numpy as np +import pytest +from pygmt import grdinfo +from pygmt.datasets import load_earth_relief +from pygmt.exceptions import GMTInvalidInput + + +def test_grdinfo(): + """ + Make sure grd info works as expected. + """ + grid = load_earth_relief(registration="gridline") + result = grdinfo(grid=grid, force_scan=0, per_column="n") + assert result.strip() == "-180 180 -90 90 -8592.5 5559 1 1 361 181 0 1" + + +def test_grdinfo_file(): + """ + Test grdinfo with file input. + """ + result = grdinfo(grid="@earth_relief_01d", force_scan=0, per_column="n") + assert result.strip() == "-180 180 -90 90 -8182 5651.5 1 1 360 180 1 1" + + +def test_grdinfo_fails(): + """ + Check that grdinfo fails correctly. + """ + with pytest.raises(GMTInvalidInput): + grdinfo(np.arange(10).reshape((5, 2))) + + +def test_grdinfo_region(): + """ + Check that the region argument works in grdinfo. + """ + result = grdinfo( + grid="@earth_relief_01d", + force_scan=0, + per_column="n", + region=[-170, 170, -80, 80], + ) + assert result.strip() == "-170 170 -80 80 -8182 5651.5 1 1 340 160 1 1" diff --git a/pygmt/tests/test_grdtrack.py b/pygmt/tests/test_grdtrack.py index 8ed74adcd47..9d2920cb83b 100644 --- a/pygmt/tests/test_grdtrack.py +++ b/pygmt/tests/test_grdtrack.py @@ -1,142 +1,142 @@ -""" -Tests for grdtrack. -""" -import os - -import numpy.testing as npt -import pandas as pd -import pytest -from pygmt import grdtrack, which -from pygmt.datasets import load_earth_relief, load_ocean_ridge_points -from pygmt.exceptions import GMTInvalidInput -from pygmt.helpers import data_kind - -TEST_DATA_DIR = os.path.join(os.path.dirname(__file__), "data") -TEMP_TRACK = os.path.join(TEST_DATA_DIR, "tmp_track.txt") - - -@pytest.fixture(scope="module", name="dataarray") -def fixture_dataarray(): - """ - Load the grid data from the sample earth_relief file. - """ - return load_earth_relief(registration="gridline").sel( - lat=slice(-49, -42), lon=slice(-118, -107) - ) - - -@pytest.mark.xfail(reason="The reason why it fails is unclear now") -def test_grdtrack_input_dataframe_and_dataarray(dataarray): - """ - Run grdtrack by passing in a pandas.DataFrame and xarray.DataArray as - inputs. - """ - dataframe = load_ocean_ridge_points() - - output = grdtrack(points=dataframe, grid=dataarray, newcolname="bathymetry") - assert isinstance(output, pd.DataFrame) - assert output.columns.to_list() == ["longitude", "latitude", "bathymetry"] - npt.assert_allclose(output.iloc[0], [-110.9536, -42.2489, -2797.394987]) - - return output - - -@pytest.mark.xfail(reason="The reason why it fails is unclear now") -def test_grdtrack_input_csvfile_and_dataarray(dataarray): - """ - Run grdtrack by passing in a csvfile and xarray.DataArray as inputs. - """ - csvfile = which("@ridge.txt", download="c") - - try: - output = grdtrack(points=csvfile, grid=dataarray, outfile=TEMP_TRACK) - assert output is None # check that output is None since outfile is set - assert os.path.exists(path=TEMP_TRACK) # check that outfile exists at path - - track = pd.read_csv(TEMP_TRACK, sep="\t", header=None, comment=">") - npt.assert_allclose(track.iloc[0], [-110.9536, -42.2489, -2797.394987]) - finally: - os.remove(path=TEMP_TRACK) - - return output - - -def test_grdtrack_input_dataframe_and_ncfile(): - """ - Run grdtrack by passing in a pandas.DataFrame and netcdf file as inputs. - """ - dataframe = load_ocean_ridge_points() - ncfile = which("@earth_relief_01d", download="a") - - output = grdtrack(points=dataframe, grid=ncfile, newcolname="bathymetry") - assert isinstance(output, pd.DataFrame) - assert output.columns.to_list() == ["longitude", "latitude", "bathymetry"] - npt.assert_allclose(output.iloc[0], [-32.2971, 37.4118, -1939.748245]) - - return output - - -def test_grdtrack_input_csvfile_and_ncfile(): - """ - Run grdtrack by passing in a csvfile and netcdf file as inputs. - """ - csvfile = which("@ridge.txt", download="c") - ncfile = which("@earth_relief_01d", download="a") - - try: - output = grdtrack(points=csvfile, grid=ncfile, outfile=TEMP_TRACK) - assert output is None # check that output is None since outfile is set - assert os.path.exists(path=TEMP_TRACK) # check that outfile exists at path - - track = pd.read_csv(TEMP_TRACK, sep="\t", header=None, comment=">") - npt.assert_allclose(track.iloc[0], [-32.2971, 37.4118, -1939.748245]) - finally: - os.remove(path=TEMP_TRACK) - - return output - - -def test_grdtrack_wrong_kind_of_points_input(dataarray): - """ - Run grdtrack using points input that is not a pandas.DataFrame (matrix) or - file. - """ - dataframe = load_ocean_ridge_points() - invalid_points = dataframe.longitude.to_xarray() - - assert data_kind(invalid_points) == "grid" - with pytest.raises(GMTInvalidInput): - grdtrack(points=invalid_points, grid=dataarray, newcolname="bathymetry") - - -def test_grdtrack_wrong_kind_of_grid_input(dataarray): - """ - Run grdtrack using grid input that is not as xarray.DataArray (grid) or - file. - """ - dataframe = load_ocean_ridge_points() - invalid_grid = dataarray.to_dataset() - - assert data_kind(invalid_grid) == "matrix" - with pytest.raises(GMTInvalidInput): - grdtrack(points=dataframe, grid=invalid_grid, newcolname="bathymetry") - - -def test_grdtrack_without_newcolname_setting(dataarray): - """ - Run grdtrack by not passing in newcolname parameter setting. - """ - dataframe = load_ocean_ridge_points() - - with pytest.raises(GMTInvalidInput): - grdtrack(points=dataframe, grid=dataarray) - - -def test_grdtrack_without_outfile_setting(dataarray): - """ - Run grdtrack by not passing in outfile parameter setting. - """ - csvfile = which("@ridge.txt", download="c") - - with pytest.raises(GMTInvalidInput): - grdtrack(points=csvfile, grid=dataarray) +""" +Tests for grdtrack. +""" +import os + +import numpy.testing as npt +import pandas as pd +import pytest +from pygmt import grdtrack, which +from pygmt.datasets import load_earth_relief, load_ocean_ridge_points +from pygmt.exceptions import GMTInvalidInput +from pygmt.helpers import data_kind + +TEST_DATA_DIR = os.path.join(os.path.dirname(__file__), "data") +TEMP_TRACK = os.path.join(TEST_DATA_DIR, "tmp_track.txt") + + +@pytest.fixture(scope="module", name="dataarray") +def fixture_dataarray(): + """ + Load the grid data from the sample earth_relief file. + """ + return load_earth_relief(registration="gridline").sel( + lat=slice(-49, -42), lon=slice(-118, -107) + ) + + +@pytest.mark.xfail(reason="The reason why it fails is unclear now") +def test_grdtrack_input_dataframe_and_dataarray(dataarray): + """ + Run grdtrack by passing in a pandas.DataFrame and xarray.DataArray as + inputs. + """ + dataframe = load_ocean_ridge_points() + + output = grdtrack(points=dataframe, grid=dataarray, newcolname="bathymetry") + assert isinstance(output, pd.DataFrame) + assert output.columns.to_list() == ["longitude", "latitude", "bathymetry"] + npt.assert_allclose(output.iloc[0], [-110.9536, -42.2489, -2797.394987]) + + return output + + +@pytest.mark.xfail(reason="The reason why it fails is unclear now") +def test_grdtrack_input_csvfile_and_dataarray(dataarray): + """ + Run grdtrack by passing in a csvfile and xarray.DataArray as inputs. + """ + csvfile = which("@ridge.txt", download="c") + + try: + output = grdtrack(points=csvfile, grid=dataarray, outfile=TEMP_TRACK) + assert output is None # check that output is None since outfile is set + assert os.path.exists(path=TEMP_TRACK) # check that outfile exists at path + + track = pd.read_csv(TEMP_TRACK, sep="\t", header=None, comment=">") + npt.assert_allclose(track.iloc[0], [-110.9536, -42.2489, -2797.394987]) + finally: + os.remove(path=TEMP_TRACK) + + return output + + +def test_grdtrack_input_dataframe_and_ncfile(): + """ + Run grdtrack by passing in a pandas.DataFrame and netcdf file as inputs. + """ + dataframe = load_ocean_ridge_points() + ncfile = which("@earth_relief_01d", download="a") + + output = grdtrack(points=dataframe, grid=ncfile, newcolname="bathymetry") + assert isinstance(output, pd.DataFrame) + assert output.columns.to_list() == ["longitude", "latitude", "bathymetry"] + npt.assert_allclose(output.iloc[0], [-32.2971, 37.4118, -1939.748245]) + + return output + + +def test_grdtrack_input_csvfile_and_ncfile(): + """ + Run grdtrack by passing in a csvfile and netcdf file as inputs. + """ + csvfile = which("@ridge.txt", download="c") + ncfile = which("@earth_relief_01d", download="a") + + try: + output = grdtrack(points=csvfile, grid=ncfile, outfile=TEMP_TRACK) + assert output is None # check that output is None since outfile is set + assert os.path.exists(path=TEMP_TRACK) # check that outfile exists at path + + track = pd.read_csv(TEMP_TRACK, sep="\t", header=None, comment=">") + npt.assert_allclose(track.iloc[0], [-32.2971, 37.4118, -1939.748245]) + finally: + os.remove(path=TEMP_TRACK) + + return output + + +def test_grdtrack_wrong_kind_of_points_input(dataarray): + """ + Run grdtrack using points input that is not a pandas.DataFrame (matrix) or + file. + """ + dataframe = load_ocean_ridge_points() + invalid_points = dataframe.longitude.to_xarray() + + assert data_kind(invalid_points) == "grid" + with pytest.raises(GMTInvalidInput): + grdtrack(points=invalid_points, grid=dataarray, newcolname="bathymetry") + + +def test_grdtrack_wrong_kind_of_grid_input(dataarray): + """ + Run grdtrack using grid input that is not as xarray.DataArray (grid) or + file. + """ + dataframe = load_ocean_ridge_points() + invalid_grid = dataarray.to_dataset() + + assert data_kind(invalid_grid) == "matrix" + with pytest.raises(GMTInvalidInput): + grdtrack(points=dataframe, grid=invalid_grid, newcolname="bathymetry") + + +def test_grdtrack_without_newcolname_setting(dataarray): + """ + Run grdtrack by not passing in newcolname parameter setting. + """ + dataframe = load_ocean_ridge_points() + + with pytest.raises(GMTInvalidInput): + grdtrack(points=dataframe, grid=dataarray) + + +def test_grdtrack_without_outfile_setting(dataarray): + """ + Run grdtrack by not passing in outfile parameter setting. + """ + csvfile = which("@ridge.txt", download="c") + + with pytest.raises(GMTInvalidInput): + grdtrack(points=csvfile, grid=dataarray) diff --git a/pygmt/tests/test_grdview.py b/pygmt/tests/test_grdview.py index 9ddff041bb6..bf071ebe9ca 100644 --- a/pygmt/tests/test_grdview.py +++ b/pygmt/tests/test_grdview.py @@ -1,261 +1,261 @@ -""" -Tests grdview. -""" -import pytest -from pygmt import Figure, grdcut, which -from pygmt.exceptions import GMTInvalidInput -from pygmt.helpers import GMTTempFile, data_kind -from pygmt.helpers.testing import check_figures_equal - - -@pytest.fixture(scope="module", name="region") -def fixture_region(): - """ - Test region as lonmin, lonmax, latmin, latmax. - """ - return (-116, -109, -47, -44) - - -@pytest.fixture(scope="module", name="gridfile") -def fixture_gridfile(region): - """ - Load the NetCDF grid file from the sample earth_relief file. - """ - with GMTTempFile(suffix=".nc") as tmpfile: - grdcut(grid="@earth_relief_01d_g", region=region, outgrid=tmpfile.name) - yield tmpfile.name - - -@pytest.fixture(scope="module", name="xrgrid") -def fixture_xrgrid(region): - """ - Load the xarray.DataArray grid from the sample earth_relief file. - """ - return grdcut(grid="@earth_relief_01d_g", region=region) - - -@check_figures_equal() -def test_grdview_grid_dataarray(gridfile, xrgrid): - """ - Run grdview by passing in a grid as an xarray.DataArray. - """ - fig_ref, fig_test = Figure(), Figure() - fig_ref.grdview(grid=gridfile) - fig_test.grdview(grid=xrgrid) - return fig_ref, fig_test - - -@pytest.mark.mpl_image_compare -def test_grdview_grid_file_with_region_subset(region): - """ - Run grdview by passing in a grid filename, and cropping it to a region. - """ - gridfile = which("@earth_relief_01d_g", download="a") - - fig = Figure() - fig.grdview(grid=gridfile, region=region) - return fig - - -def test_grdview_wrong_kind_of_grid(xrgrid): - """ - Run grdview using grid input that is not an xarray.DataArray or file. - """ - dataset = xrgrid.to_dataset() # convert xarray.DataArray to xarray.Dataset - assert data_kind(dataset) == "matrix" - - fig = Figure() - with pytest.raises(GMTInvalidInput): - fig.grdview(grid=dataset) - - -@check_figures_equal() -def test_grdview_with_perspective(gridfile, xrgrid): - """ - Run grdview by passing in a grid and setting a perspective viewpoint with - an azimuth from the SouthEast and an elevation angle 15 degrees from the - z-plane. - """ - fig_ref, fig_test = Figure(), Figure() - fig_ref.grdview(grid=gridfile, perspective=[135, 15]) - fig_test.grdview(grid=xrgrid, perspective=[135, 15]) - return fig_ref, fig_test - - -@check_figures_equal() -def test_grdview_with_perspective_and_zscale(gridfile, xrgrid): - """ - Run grdview by passing in a grid and setting a perspective viewpoint with - an azimuth from the SouthWest and an elevation angle 30 degrees from the - z-plane, plus a z-axis scaling factor of 0.005. - """ - fig_ref, fig_test = Figure(), Figure() - kwargs = dict(perspective=[225, 30], zscale=0.005) - fig_ref.grdview(grid=gridfile, **kwargs) - fig_test.grdview(grid=xrgrid, **kwargs) - return fig_ref, fig_test - - -@check_figures_equal() -def test_grdview_with_perspective_and_zsize(gridfile, xrgrid): - """ - Run grdview by passing in a grid and setting a perspective viewpoint with - an azimuth from the SouthWest and an elevation angle 30 degrees from the - z-plane, plus a z-axis size of 10cm. - """ - fig_ref, fig_test = Figure(), Figure() - kwargs = dict(perspective=[225, 30], zsize="10c") - fig_ref.grdview(grid=gridfile, **kwargs) - fig_test.grdview(grid=xrgrid, **kwargs) - return fig_ref, fig_test - - -@check_figures_equal() -def test_grdview_with_cmap_for_image_plot(gridfile, xrgrid): - """ - Run grdview by passing in a grid and setting a colormap for producing an - image plot. - """ - fig_ref, fig_test = Figure(), Figure() - kwargs = dict(cmap="oleron", surftype="i") - fig_ref.grdview(grid=gridfile, **kwargs) - fig_test.grdview(grid=xrgrid, **kwargs) - return fig_ref, fig_test - - -@check_figures_equal() -def test_grdview_with_cmap_for_surface_monochrome_plot(gridfile, xrgrid): - """ - Run grdview by passing in a grid and setting a colormap for producing a - surface monochrome plot. - """ - fig_ref, fig_test = Figure(), Figure() - kwargs = dict(cmap="oleron", surftype="s+m") - fig_ref.grdview(grid=gridfile, **kwargs) - fig_test.grdview(grid=xrgrid, **kwargs) - return fig_ref, fig_test - - -@check_figures_equal() -def test_grdview_with_cmap_for_perspective_surface_plot(gridfile, xrgrid): - """ - Run grdview by passing in a grid and setting a colormap for producing a - surface plot with a 3D perspective viewpoint. - """ - fig_ref, fig_test = Figure(), Figure() - kwargs = dict(cmap="oleron", surftype="s", perspective=[225, 30], zscale=0.005) - fig_ref.grdview(grid=gridfile, **kwargs) - fig_test.grdview(grid=xrgrid, **kwargs) - return fig_ref, fig_test - - -@check_figures_equal() -def test_grdview_on_a_plane(gridfile, xrgrid): - """ - Run grdview by passing in a grid and plotting it on a z-plane, while - setting a 3D perspective viewpoint. - """ - fig_ref, fig_test = Figure(), Figure() - kwargs = dict(plane=-4000, perspective=[225, 30], zscale=0.005) - fig_ref.grdview(grid=gridfile, **kwargs) - fig_test.grdview(grid=xrgrid, **kwargs) - return fig_ref, fig_test - - -@check_figures_equal() -def test_grdview_on_a_plane_with_colored_frontal_facade(gridfile, xrgrid): - """ - Run grdview by passing in a grid and plotting it on a z-plane whose frontal - facade is colored gray, while setting a 3D perspective viewpoint. - """ - fig_ref, fig_test = Figure(), Figure() - kwargs = dict(plane="-4000+ggray", perspective=[225, 30], zscale=0.005) - fig_ref.grdview(grid=gridfile, **kwargs) - fig_test.grdview(grid=xrgrid, **kwargs) - return fig_ref, fig_test - - -@check_figures_equal() -def test_grdview_with_perspective_and_zaxis_frame(gridfile, xrgrid, region): - """ - Run grdview by passing in a grid and plotting an annotated vertical z-axis - frame on a Transverse Mercator (T) projection. - """ - fig_ref, fig_test = Figure(), Figure() - projection = f"T{(region[0]+region[1])/2}/{abs((region[2]+region[3])/2)}" - kwargs = dict( - projection=projection, - perspective=[225, 30], - zscale=0.005, - frame=["xaf", "yaf", "zaf"], - ) - fig_ref.grdview(grid=gridfile, **kwargs) - fig_test.grdview(grid=xrgrid, **kwargs) - return fig_ref, fig_test - - -@check_figures_equal() -def test_grdview_surface_plot_styled_with_contourpen(gridfile, xrgrid): - """ - Run grdview by passing in a grid with styled contour lines plotted on top - of a surface plot. - """ - fig_ref, fig_test = Figure(), Figure() - kwargs = dict(cmap="relief", surftype="s", contourpen="0.5p,black,dash") - fig_ref.grdview(grid=gridfile, **kwargs) - fig_test.grdview(grid=xrgrid, **kwargs) - return fig_ref, fig_test - - -@check_figures_equal() -def test_grdview_surface_mesh_plot_styled_with_meshpen(gridfile, xrgrid): - """ - Run grdview by passing in a grid with styled mesh lines plotted on top of a - surface mesh plot. - """ - fig_ref, fig_test = Figure(), Figure() - kwargs = dict(cmap="relief", surftype="sm", meshpen="0.5p,black,dash") - fig_ref.grdview(grid=gridfile, **kwargs) - fig_test.grdview(grid=xrgrid, **kwargs) - return fig_ref, fig_test - - -@check_figures_equal() -def test_grdview_on_a_plane_styled_with_facadepen(gridfile, xrgrid): - """ - Run grdview by passing in a grid and plotting it on a z-plane with styled - lines for the frontal facade. - """ - fig_ref, fig_test = Figure(), Figure() - kwargs = dict( - plane=-4000, perspective=[225, 30], zscale=0.005, facadepen="0.5p,blue,dash" - ) - fig_ref.grdview(grid=gridfile, **kwargs) - fig_test.grdview(grid=xrgrid, **kwargs) - return fig_ref, fig_test - - -@check_figures_equal() -def test_grdview_drapegrid_dataarray(gridfile, xrgrid): - """ - Run grdview by passing in both a grid and drapegrid as an xarray.DataArray, - setting a colormap for producing an image plot. - """ - drapegrid = 1.1 * xrgrid - - fig_ref, fig_test = Figure(), Figure() - fig_ref.grdview(grid=gridfile, drapegrid=drapegrid, cmap="oleron", surftype="c") - fig_test.grdview(grid=xrgrid, drapegrid=drapegrid, cmap="oleron", surftype="c") - return fig_ref, fig_test - - -def test_grdview_wrong_kind_of_drapegrid(xrgrid): - """ - Run grdview using drapegrid input that is not an xarray.DataArray or file. - """ - dataset = xrgrid.to_dataset() # convert xarray.DataArray to xarray.Dataset - assert data_kind(dataset) == "matrix" - - fig = Figure() - with pytest.raises(GMTInvalidInput): - fig.grdview(grid=xrgrid, drapegrid=dataset) +""" +Tests grdview. +""" +import pytest +from pygmt import Figure, grdcut, which +from pygmt.exceptions import GMTInvalidInput +from pygmt.helpers import GMTTempFile, data_kind +from pygmt.helpers.testing import check_figures_equal + + +@pytest.fixture(scope="module", name="region") +def fixture_region(): + """ + Test region as lonmin, lonmax, latmin, latmax. + """ + return (-116, -109, -47, -44) + + +@pytest.fixture(scope="module", name="gridfile") +def fixture_gridfile(region): + """ + Load the NetCDF grid file from the sample earth_relief file. + """ + with GMTTempFile(suffix=".nc") as tmpfile: + grdcut(grid="@earth_relief_01d_g", region=region, outgrid=tmpfile.name) + yield tmpfile.name + + +@pytest.fixture(scope="module", name="xrgrid") +def fixture_xrgrid(region): + """ + Load the xarray.DataArray grid from the sample earth_relief file. + """ + return grdcut(grid="@earth_relief_01d_g", region=region) + + +@check_figures_equal() +def test_grdview_grid_dataarray(gridfile, xrgrid): + """ + Run grdview by passing in a grid as an xarray.DataArray. + """ + fig_ref, fig_test = Figure(), Figure() + fig_ref.grdview(grid=gridfile) + fig_test.grdview(grid=xrgrid) + return fig_ref, fig_test + + +@pytest.mark.mpl_image_compare +def test_grdview_grid_file_with_region_subset(region): + """ + Run grdview by passing in a grid filename, and cropping it to a region. + """ + gridfile = which("@earth_relief_01d_g", download="a") + + fig = Figure() + fig.grdview(grid=gridfile, region=region) + return fig + + +def test_grdview_wrong_kind_of_grid(xrgrid): + """ + Run grdview using grid input that is not an xarray.DataArray or file. + """ + dataset = xrgrid.to_dataset() # convert xarray.DataArray to xarray.Dataset + assert data_kind(dataset) == "matrix" + + fig = Figure() + with pytest.raises(GMTInvalidInput): + fig.grdview(grid=dataset) + + +@check_figures_equal() +def test_grdview_with_perspective(gridfile, xrgrid): + """ + Run grdview by passing in a grid and setting a perspective viewpoint with + an azimuth from the SouthEast and an elevation angle 15 degrees from the + z-plane. + """ + fig_ref, fig_test = Figure(), Figure() + fig_ref.grdview(grid=gridfile, perspective=[135, 15]) + fig_test.grdview(grid=xrgrid, perspective=[135, 15]) + return fig_ref, fig_test + + +@check_figures_equal() +def test_grdview_with_perspective_and_zscale(gridfile, xrgrid): + """ + Run grdview by passing in a grid and setting a perspective viewpoint with + an azimuth from the SouthWest and an elevation angle 30 degrees from the + z-plane, plus a z-axis scaling factor of 0.005. + """ + fig_ref, fig_test = Figure(), Figure() + kwargs = dict(perspective=[225, 30], zscale=0.005) + fig_ref.grdview(grid=gridfile, **kwargs) + fig_test.grdview(grid=xrgrid, **kwargs) + return fig_ref, fig_test + + +@check_figures_equal() +def test_grdview_with_perspective_and_zsize(gridfile, xrgrid): + """ + Run grdview by passing in a grid and setting a perspective viewpoint with + an azimuth from the SouthWest and an elevation angle 30 degrees from the + z-plane, plus a z-axis size of 10cm. + """ + fig_ref, fig_test = Figure(), Figure() + kwargs = dict(perspective=[225, 30], zsize="10c") + fig_ref.grdview(grid=gridfile, **kwargs) + fig_test.grdview(grid=xrgrid, **kwargs) + return fig_ref, fig_test + + +@check_figures_equal() +def test_grdview_with_cmap_for_image_plot(gridfile, xrgrid): + """ + Run grdview by passing in a grid and setting a colormap for producing an + image plot. + """ + fig_ref, fig_test = Figure(), Figure() + kwargs = dict(cmap="oleron", surftype="i") + fig_ref.grdview(grid=gridfile, **kwargs) + fig_test.grdview(grid=xrgrid, **kwargs) + return fig_ref, fig_test + + +@check_figures_equal() +def test_grdview_with_cmap_for_surface_monochrome_plot(gridfile, xrgrid): + """ + Run grdview by passing in a grid and setting a colormap for producing a + surface monochrome plot. + """ + fig_ref, fig_test = Figure(), Figure() + kwargs = dict(cmap="oleron", surftype="s+m") + fig_ref.grdview(grid=gridfile, **kwargs) + fig_test.grdview(grid=xrgrid, **kwargs) + return fig_ref, fig_test + + +@check_figures_equal() +def test_grdview_with_cmap_for_perspective_surface_plot(gridfile, xrgrid): + """ + Run grdview by passing in a grid and setting a colormap for producing a + surface plot with a 3D perspective viewpoint. + """ + fig_ref, fig_test = Figure(), Figure() + kwargs = dict(cmap="oleron", surftype="s", perspective=[225, 30], zscale=0.005) + fig_ref.grdview(grid=gridfile, **kwargs) + fig_test.grdview(grid=xrgrid, **kwargs) + return fig_ref, fig_test + + +@check_figures_equal() +def test_grdview_on_a_plane(gridfile, xrgrid): + """ + Run grdview by passing in a grid and plotting it on a z-plane, while + setting a 3D perspective viewpoint. + """ + fig_ref, fig_test = Figure(), Figure() + kwargs = dict(plane=-4000, perspective=[225, 30], zscale=0.005) + fig_ref.grdview(grid=gridfile, **kwargs) + fig_test.grdview(grid=xrgrid, **kwargs) + return fig_ref, fig_test + + +@check_figures_equal() +def test_grdview_on_a_plane_with_colored_frontal_facade(gridfile, xrgrid): + """ + Run grdview by passing in a grid and plotting it on a z-plane whose frontal + facade is colored gray, while setting a 3D perspective viewpoint. + """ + fig_ref, fig_test = Figure(), Figure() + kwargs = dict(plane="-4000+ggray", perspective=[225, 30], zscale=0.005) + fig_ref.grdview(grid=gridfile, **kwargs) + fig_test.grdview(grid=xrgrid, **kwargs) + return fig_ref, fig_test + + +@check_figures_equal() +def test_grdview_with_perspective_and_zaxis_frame(gridfile, xrgrid, region): + """ + Run grdview by passing in a grid and plotting an annotated vertical z-axis + frame on a Transverse Mercator (T) projection. + """ + fig_ref, fig_test = Figure(), Figure() + projection = f"T{(region[0]+region[1])/2}/{abs((region[2]+region[3])/2)}" + kwargs = dict( + projection=projection, + perspective=[225, 30], + zscale=0.005, + frame=["xaf", "yaf", "zaf"], + ) + fig_ref.grdview(grid=gridfile, **kwargs) + fig_test.grdview(grid=xrgrid, **kwargs) + return fig_ref, fig_test + + +@check_figures_equal() +def test_grdview_surface_plot_styled_with_contourpen(gridfile, xrgrid): + """ + Run grdview by passing in a grid with styled contour lines plotted on top + of a surface plot. + """ + fig_ref, fig_test = Figure(), Figure() + kwargs = dict(cmap="relief", surftype="s", contourpen="0.5p,black,dash") + fig_ref.grdview(grid=gridfile, **kwargs) + fig_test.grdview(grid=xrgrid, **kwargs) + return fig_ref, fig_test + + +@check_figures_equal() +def test_grdview_surface_mesh_plot_styled_with_meshpen(gridfile, xrgrid): + """ + Run grdview by passing in a grid with styled mesh lines plotted on top of a + surface mesh plot. + """ + fig_ref, fig_test = Figure(), Figure() + kwargs = dict(cmap="relief", surftype="sm", meshpen="0.5p,black,dash") + fig_ref.grdview(grid=gridfile, **kwargs) + fig_test.grdview(grid=xrgrid, **kwargs) + return fig_ref, fig_test + + +@check_figures_equal() +def test_grdview_on_a_plane_styled_with_facadepen(gridfile, xrgrid): + """ + Run grdview by passing in a grid and plotting it on a z-plane with styled + lines for the frontal facade. + """ + fig_ref, fig_test = Figure(), Figure() + kwargs = dict( + plane=-4000, perspective=[225, 30], zscale=0.005, facadepen="0.5p,blue,dash" + ) + fig_ref.grdview(grid=gridfile, **kwargs) + fig_test.grdview(grid=xrgrid, **kwargs) + return fig_ref, fig_test + + +@check_figures_equal() +def test_grdview_drapegrid_dataarray(gridfile, xrgrid): + """ + Run grdview by passing in both a grid and drapegrid as an xarray.DataArray, + setting a colormap for producing an image plot. + """ + drapegrid = 1.1 * xrgrid + + fig_ref, fig_test = Figure(), Figure() + fig_ref.grdview(grid=gridfile, drapegrid=drapegrid, cmap="oleron", surftype="c") + fig_test.grdview(grid=xrgrid, drapegrid=drapegrid, cmap="oleron", surftype="c") + return fig_ref, fig_test + + +def test_grdview_wrong_kind_of_drapegrid(xrgrid): + """ + Run grdview using drapegrid input that is not an xarray.DataArray or file. + """ + dataset = xrgrid.to_dataset() # convert xarray.DataArray to xarray.Dataset + assert data_kind(dataset) == "matrix" + + fig = Figure() + with pytest.raises(GMTInvalidInput): + fig.grdview(grid=xrgrid, drapegrid=dataset) diff --git a/pygmt/tests/test_helpers.py b/pygmt/tests/test_helpers.py index 5f33291798f..9ea7554ee7d 100644 --- a/pygmt/tests/test_helpers.py +++ b/pygmt/tests/test_helpers.py @@ -1,132 +1,132 @@ -""" -Tests the helper functions/classes/etc used in wrapping GMT. -""" -import os - -import numpy as np -import pytest -from pygmt.exceptions import GMTInvalidInput -from pygmt.helpers import ( - GMTTempFile, - args_in_kwargs, - data_kind, - kwargs_to_strings, - unique_name, -) - - -@pytest.mark.parametrize( - "data,x,y", - [ - (None, None, None), - ("data.txt", np.array([1, 2]), np.array([4, 5])), - ("data.txt", np.array([1, 2]), None), - ("data.txt", None, np.array([4, 5])), - (None, np.array([1, 2]), None), - (None, None, np.array([4, 5])), - ], -) -def test_data_kind_fails(data, x, y): - """ - Make sure data_kind raises exceptions when it should. - """ - with pytest.raises(GMTInvalidInput): - data_kind(data=data, x=x, y=y) - - -def test_unique_name(): - """ - Make sure the names are really unique. - """ - names = [unique_name() for i in range(100)] - assert len(names) == len(set(names)) - - -def test_kwargs_to_strings_fails(): - """ - Make sure it fails for invalid conversion types. - """ - with pytest.raises(GMTInvalidInput): - kwargs_to_strings(bla="blablabla") - - -def test_kwargs_to_strings_no_bools(): - """ - Test that not converting bools works. - """ - - @kwargs_to_strings(convert_bools=False) - def my_module(**kwargs): - """ - Function that does nothing. - """ - return kwargs - - # The module should return the exact same arguments it was given - args = dict(P=True, A=False, R="1/2/3/4") - assert my_module(**args) == args - - -def test_gmttempfile(): - """ - Check that file is really created and deleted. - """ - with GMTTempFile() as tmpfile: - assert os.path.exists(tmpfile.name) - # File should be deleted when leaving the with block - assert not os.path.exists(tmpfile.name) - - -def test_gmttempfile_unique(): - """ - Check that generating multiple files creates unique names. - """ - with GMTTempFile() as tmp1: - with GMTTempFile() as tmp2: - with GMTTempFile() as tmp3: - assert tmp1.name != tmp2.name != tmp3.name - - -def test_gmttempfile_prefix_suffix(): - """ - Make sure the prefix and suffix of temporary files are user specifiable. - """ - with GMTTempFile() as tmpfile: - assert os.path.basename(tmpfile.name).startswith("pygmt-") - assert os.path.basename(tmpfile.name).endswith(".txt") - with GMTTempFile(prefix="user-prefix-") as tmpfile: - assert os.path.basename(tmpfile.name).startswith("user-prefix-") - assert os.path.basename(tmpfile.name).endswith(".txt") - with GMTTempFile(suffix=".log") as tmpfile: - assert os.path.basename(tmpfile.name).startswith("pygmt-") - assert os.path.basename(tmpfile.name).endswith(".log") - with GMTTempFile(prefix="user-prefix-", suffix=".log") as tmpfile: - assert os.path.basename(tmpfile.name).startswith("user-prefix-") - assert os.path.basename(tmpfile.name).endswith(".log") - - -def test_gmttempfile_read(): - """ - Make sure GMTTempFile.read() works. - """ - with GMTTempFile() as tmpfile: - with open(tmpfile.name, "w") as ftmp: - ftmp.write("in.dat: N = 2\t<1/3>\t<2/4>\n") - assert tmpfile.read() == "in.dat: N = 2 <1/3> <2/4>\n" - assert tmpfile.read(keep_tabs=True) == "in.dat: N = 2\t<1/3>\t<2/4>\n" - - -def test_args_in_kwargs(): - """ - Test that args_in_kwargs function returns correct Boolean responses. - """ - kwargs = {"A": 1, "B": 2, "C": 3} - # Passing list of arguments with passing values in the beginning - passing_args_1 = ["B", "C", "D"] - assert args_in_kwargs(args=passing_args_1, kwargs=kwargs) - # Passing list of arguments that starts with failing arguments - passing_args_2 = ["D", "X", "C"] - assert args_in_kwargs(args=passing_args_2, kwargs=kwargs) - # Failing list of arguments - failing_args = ["D", "E", "F"] - assert not args_in_kwargs(args=failing_args, kwargs=kwargs) +""" +Tests the helper functions/classes/etc used in wrapping GMT. +""" +import os + +import numpy as np +import pytest +from pygmt.exceptions import GMTInvalidInput +from pygmt.helpers import ( + GMTTempFile, + args_in_kwargs, + data_kind, + kwargs_to_strings, + unique_name, +) + + +@pytest.mark.parametrize( + "data,x,y", + [ + (None, None, None), + ("data.txt", np.array([1, 2]), np.array([4, 5])), + ("data.txt", np.array([1, 2]), None), + ("data.txt", None, np.array([4, 5])), + (None, np.array([1, 2]), None), + (None, None, np.array([4, 5])), + ], +) +def test_data_kind_fails(data, x, y): + """ + Make sure data_kind raises exceptions when it should. + """ + with pytest.raises(GMTInvalidInput): + data_kind(data=data, x=x, y=y) + + +def test_unique_name(): + """ + Make sure the names are really unique. + """ + names = [unique_name() for i in range(100)] + assert len(names) == len(set(names)) + + +def test_kwargs_to_strings_fails(): + """ + Make sure it fails for invalid conversion types. + """ + with pytest.raises(GMTInvalidInput): + kwargs_to_strings(bla="blablabla") + + +def test_kwargs_to_strings_no_bools(): + """ + Test that not converting bools works. + """ + + @kwargs_to_strings(convert_bools=False) + def my_module(**kwargs): + """ + Function that does nothing. + """ + return kwargs + + # The module should return the exact same arguments it was given + args = dict(P=True, A=False, R="1/2/3/4") + assert my_module(**args) == args + + +def test_gmttempfile(): + """ + Check that file is really created and deleted. + """ + with GMTTempFile() as tmpfile: + assert os.path.exists(tmpfile.name) + # File should be deleted when leaving the with block + assert not os.path.exists(tmpfile.name) + + +def test_gmttempfile_unique(): + """ + Check that generating multiple files creates unique names. + """ + with GMTTempFile() as tmp1: + with GMTTempFile() as tmp2: + with GMTTempFile() as tmp3: + assert tmp1.name != tmp2.name != tmp3.name + + +def test_gmttempfile_prefix_suffix(): + """ + Make sure the prefix and suffix of temporary files are user specifiable. + """ + with GMTTempFile() as tmpfile: + assert os.path.basename(tmpfile.name).startswith("pygmt-") + assert os.path.basename(tmpfile.name).endswith(".txt") + with GMTTempFile(prefix="user-prefix-") as tmpfile: + assert os.path.basename(tmpfile.name).startswith("user-prefix-") + assert os.path.basename(tmpfile.name).endswith(".txt") + with GMTTempFile(suffix=".log") as tmpfile: + assert os.path.basename(tmpfile.name).startswith("pygmt-") + assert os.path.basename(tmpfile.name).endswith(".log") + with GMTTempFile(prefix="user-prefix-", suffix=".log") as tmpfile: + assert os.path.basename(tmpfile.name).startswith("user-prefix-") + assert os.path.basename(tmpfile.name).endswith(".log") + + +def test_gmttempfile_read(): + """ + Make sure GMTTempFile.read() works. + """ + with GMTTempFile() as tmpfile: + with open(tmpfile.name, "w") as ftmp: + ftmp.write("in.dat: N = 2\t<1/3>\t<2/4>\n") + assert tmpfile.read() == "in.dat: N = 2 <1/3> <2/4>\n" + assert tmpfile.read(keep_tabs=True) == "in.dat: N = 2\t<1/3>\t<2/4>\n" + + +def test_args_in_kwargs(): + """ + Test that args_in_kwargs function returns correct Boolean responses. + """ + kwargs = {"A": 1, "B": 2, "C": 3} + # Passing list of arguments with passing values in the beginning + passing_args_1 = ["B", "C", "D"] + assert args_in_kwargs(args=passing_args_1, kwargs=kwargs) + # Passing list of arguments that starts with failing arguments + passing_args_2 = ["D", "X", "C"] + assert args_in_kwargs(args=passing_args_2, kwargs=kwargs) + # Failing list of arguments + failing_args = ["D", "E", "F"] + assert not args_in_kwargs(args=failing_args, kwargs=kwargs) diff --git a/pygmt/tests/test_image.py b/pygmt/tests/test_image.py index 3140ba6cbba..00a1e42e497 100644 --- a/pygmt/tests/test_image.py +++ b/pygmt/tests/test_image.py @@ -1,21 +1,21 @@ -""" -Tests image. -""" -import os -import sys - -import pytest -from pygmt import Figure - -TEST_IMG = os.path.join(os.path.dirname(__file__), "baseline", "test_logo.png") - - -@pytest.mark.skipif(sys.platform == "win32", reason="crashes on Windows") -@pytest.mark.mpl_image_compare -def test_image(): - """ - Place images on map. - """ - fig = Figure() - fig.image(TEST_IMG, D="x0/0+w1i", F="+pthin,blue") - return fig +""" +Tests image. +""" +import os +import sys + +import pytest +from pygmt import Figure + +TEST_IMG = os.path.join(os.path.dirname(__file__), "baseline", "test_logo.png") + + +@pytest.mark.skipif(sys.platform == "win32", reason="crashes on Windows") +@pytest.mark.mpl_image_compare +def test_image(): + """ + Place images on map. + """ + fig = Figure() + fig.image(TEST_IMG, D="x0/0+w1i", F="+pthin,blue") + return fig diff --git a/pygmt/tests/test_info.py b/pygmt/tests/test_info.py index 1c40657ea1d..ea85a590711 100644 --- a/pygmt/tests/test_info.py +++ b/pygmt/tests/test_info.py @@ -1,192 +1,192 @@ -""" -Tests for gmtinfo. -""" -import os - -import numpy as np -import numpy.testing as npt -import pandas as pd -import pytest -import xarray as xr -from pygmt import info -from pygmt.exceptions import GMTInvalidInput - -TEST_DATA_DIR = os.path.join(os.path.dirname(__file__), "data") -POINTS_DATA = os.path.join(TEST_DATA_DIR, "points.txt") - - -def test_info(): - """ - Make sure info works on file name inputs. - """ - output = info(table=POINTS_DATA) - expected_output = ( - f"{POINTS_DATA}: N = 20 " - "<11.5309/61.7074> " - "<-2.9289/7.8648> " - "<0.1412/0.9338>\n" - ) - assert output == expected_output - - -def test_info_dataframe(): - """ - Make sure info works on pandas.DataFrame inputs. - """ - table = pd.read_csv(POINTS_DATA, sep=" ", header=None) - output = info(table=table) - expected_output = ( - ": N = 20 <11.5309/61.7074> <-2.9289/7.8648> <0.1412/0.9338>\n" - ) - assert output == expected_output - - -def test_info_numpy_array_time_column(): - """ - Make sure info works on a numpy.ndarray input with a datetime type. - """ - table = pd.date_range(start="2020-01-01", periods=5).to_numpy() - # Please remove coltypes="0T" workaround after - # https://github.com/GenericMappingTools/gmt/issues/4241 is resolved - output = info(table=table, coltypes="0T") - expected_output = ( - ": N = 5 <2020-01-01T00:00:00/2020-01-05T00:00:00>\n" - ) - assert output == expected_output - - -@pytest.mark.xfail( - reason="UNIX timestamps returned instead of ISO datetime, should work on GMT 6.2.0 " - "after https://github.com/GenericMappingTools/gmt/issues/4241 is resolved", -) -def test_info_pandas_dataframe_time_column(): - """ - Make sure info works on pandas.DataFrame inputs with a time column. - """ - table = pd.DataFrame( - data={ - "z": [10, 13, 12, 15, 14], - "time": pd.date_range(start="2020-01-01", periods=5), - } - ) - output = info(table=table) - expected_output = ( - ": N = 5 <10/15> <2020-01-01T00:00:00/2020-01-05T00:00:00>\n" - ) - assert output == expected_output - - -@pytest.mark.xfail( - reason="UNIX timestamp returned instead of ISO datetime, should work on GMT 6.2.0 " - "after https://github.com/GenericMappingTools/gmt/issues/4241 is resolved", -) -def test_info_xarray_dataset_time_column(): - """ - Make sure info works on xarray.Dataset 1D inputs with a time column. - """ - table = xr.Dataset( - coords={"index": [0, 1, 2, 3, 4]}, - data_vars={ - "z": ("index", [10, 13, 12, 15, 14]), - "time": ("index", pd.date_range(start="2020-01-01", periods=5)), - }, - ) - output = info(table=table) - expected_output = ( - ": N = 5 <10/15> <2020-01-01T00:00:00/2020-01-05T00:00:00>\n" - ) - assert output == expected_output - - -def test_info_2d_array(): - """ - Make sure info works on 2D numpy.ndarray inputs. - """ - table = np.loadtxt(POINTS_DATA) - output = info(table=table) - expected_output = ( - ": N = 20 <11.5309/61.7074> <-2.9289/7.8648> <0.1412/0.9338>\n" - ) - assert output == expected_output - - -def test_info_1d_array(): - """ - Make sure info works on 1D numpy.ndarray inputs. - """ - output = info(table=np.arange(20)) - expected_output = ": N = 20 <0/19>\n" - assert output == expected_output - - -def test_info_per_column(): - """ - Make sure the per_column option works. - """ - output = info(table=POINTS_DATA, per_column=True) - npt.assert_allclose( - actual=output, desired=[11.5309, 61.7074, -2.9289, 7.8648, 0.1412, 0.9338] - ) - - -def test_info_per_column_with_time_inputs(): - """ - Make sure the per_column option works with time inputs. - """ - table = pd.date_range(start="2020-01-01", periods=5).to_numpy() - # Please remove coltypes="0T" workaround after - # https://github.com/GenericMappingTools/gmt/issues/4241 is resolved - output = info(table=table, per_column=True, coltypes="0T") - npt.assert_equal( - actual=output, desired=["2020-01-01T00:00:00", "2020-01-05T00:00:00"] - ) - - -def test_info_spacing(): - """ - Make sure the spacing option works. - """ - output = info(table=POINTS_DATA, spacing=0.1) - npt.assert_allclose(actual=output, desired=[11.5, 61.8, -3, 7.9]) - - -def test_info_spacing_bounding_box(): - """ - Make sure the spacing option for writing a bounding box works. - """ - output = info(table=POINTS_DATA, spacing="b") - npt.assert_allclose( - actual=output, - desired=[ - [11.5309, -2.9289], - [61.7074, -2.9289], - [61.7074, 7.8648], - [11.5309, 7.8648], - [11.5309, -2.9289], - ], - ) - - -def test_info_per_column_spacing(): - """ - Make sure the per_column and spacing options work together. - """ - output = info(table=POINTS_DATA, per_column=True, spacing=0.1) - npt.assert_allclose(actual=output, desired=[11.5, 61.8, -3, 7.9, 0.1412, 0.9338]) - - -def test_info_nearest_multiple(): - """ - Make sure the nearest_multiple option works. - """ - output = info(table=POINTS_DATA, nearest_multiple=0.1) - npt.assert_allclose(actual=output, desired=[11.5, 61.8, 0.1]) - - -def test_info_fails(): - """ - Make sure info raises an exception if not given either a file name, pandas - DataFrame, or numpy ndarray. - """ - with pytest.raises(GMTInvalidInput): - info(table=xr.DataArray(21)) +""" +Tests for gmtinfo. +""" +import os + +import numpy as np +import numpy.testing as npt +import pandas as pd +import pytest +import xarray as xr +from pygmt import info +from pygmt.exceptions import GMTInvalidInput + +TEST_DATA_DIR = os.path.join(os.path.dirname(__file__), "data") +POINTS_DATA = os.path.join(TEST_DATA_DIR, "points.txt") + + +def test_info(): + """ + Make sure info works on file name inputs. + """ + output = info(table=POINTS_DATA) + expected_output = ( + f"{POINTS_DATA}: N = 20 " + "<11.5309/61.7074> " + "<-2.9289/7.8648> " + "<0.1412/0.9338>\n" + ) + assert output == expected_output + + +def test_info_dataframe(): + """ + Make sure info works on pandas.DataFrame inputs. + """ + table = pd.read_csv(POINTS_DATA, sep=" ", header=None) + output = info(table=table) + expected_output = ( + ": N = 20 <11.5309/61.7074> <-2.9289/7.8648> <0.1412/0.9338>\n" + ) + assert output == expected_output + + +def test_info_numpy_array_time_column(): + """ + Make sure info works on a numpy.ndarray input with a datetime type. + """ + table = pd.date_range(start="2020-01-01", periods=5).to_numpy() + # Please remove coltypes="0T" workaround after + # https://github.com/GenericMappingTools/gmt/issues/4241 is resolved + output = info(table=table, coltypes="0T") + expected_output = ( + ": N = 5 <2020-01-01T00:00:00/2020-01-05T00:00:00>\n" + ) + assert output == expected_output + + +@pytest.mark.xfail( + reason="UNIX timestamps returned instead of ISO datetime, should work on GMT 6.2.0 " + "after https://github.com/GenericMappingTools/gmt/issues/4241 is resolved", +) +def test_info_pandas_dataframe_time_column(): + """ + Make sure info works on pandas.DataFrame inputs with a time column. + """ + table = pd.DataFrame( + data={ + "z": [10, 13, 12, 15, 14], + "time": pd.date_range(start="2020-01-01", periods=5), + } + ) + output = info(table=table) + expected_output = ( + ": N = 5 <10/15> <2020-01-01T00:00:00/2020-01-05T00:00:00>\n" + ) + assert output == expected_output + + +@pytest.mark.xfail( + reason="UNIX timestamp returned instead of ISO datetime, should work on GMT 6.2.0 " + "after https://github.com/GenericMappingTools/gmt/issues/4241 is resolved", +) +def test_info_xarray_dataset_time_column(): + """ + Make sure info works on xarray.Dataset 1D inputs with a time column. + """ + table = xr.Dataset( + coords={"index": [0, 1, 2, 3, 4]}, + data_vars={ + "z": ("index", [10, 13, 12, 15, 14]), + "time": ("index", pd.date_range(start="2020-01-01", periods=5)), + }, + ) + output = info(table=table) + expected_output = ( + ": N = 5 <10/15> <2020-01-01T00:00:00/2020-01-05T00:00:00>\n" + ) + assert output == expected_output + + +def test_info_2d_array(): + """ + Make sure info works on 2D numpy.ndarray inputs. + """ + table = np.loadtxt(POINTS_DATA) + output = info(table=table) + expected_output = ( + ": N = 20 <11.5309/61.7074> <-2.9289/7.8648> <0.1412/0.9338>\n" + ) + assert output == expected_output + + +def test_info_1d_array(): + """ + Make sure info works on 1D numpy.ndarray inputs. + """ + output = info(table=np.arange(20)) + expected_output = ": N = 20 <0/19>\n" + assert output == expected_output + + +def test_info_per_column(): + """ + Make sure the per_column option works. + """ + output = info(table=POINTS_DATA, per_column=True) + npt.assert_allclose( + actual=output, desired=[11.5309, 61.7074, -2.9289, 7.8648, 0.1412, 0.9338] + ) + + +def test_info_per_column_with_time_inputs(): + """ + Make sure the per_column option works with time inputs. + """ + table = pd.date_range(start="2020-01-01", periods=5).to_numpy() + # Please remove coltypes="0T" workaround after + # https://github.com/GenericMappingTools/gmt/issues/4241 is resolved + output = info(table=table, per_column=True, coltypes="0T") + npt.assert_equal( + actual=output, desired=["2020-01-01T00:00:00", "2020-01-05T00:00:00"] + ) + + +def test_info_spacing(): + """ + Make sure the spacing option works. + """ + output = info(table=POINTS_DATA, spacing=0.1) + npt.assert_allclose(actual=output, desired=[11.5, 61.8, -3, 7.9]) + + +def test_info_spacing_bounding_box(): + """ + Make sure the spacing option for writing a bounding box works. + """ + output = info(table=POINTS_DATA, spacing="b") + npt.assert_allclose( + actual=output, + desired=[ + [11.5309, -2.9289], + [61.7074, -2.9289], + [61.7074, 7.8648], + [11.5309, 7.8648], + [11.5309, -2.9289], + ], + ) + + +def test_info_per_column_spacing(): + """ + Make sure the per_column and spacing options work together. + """ + output = info(table=POINTS_DATA, per_column=True, spacing=0.1) + npt.assert_allclose(actual=output, desired=[11.5, 61.8, -3, 7.9, 0.1412, 0.9338]) + + +def test_info_nearest_multiple(): + """ + Make sure the nearest_multiple option works. + """ + output = info(table=POINTS_DATA, nearest_multiple=0.1) + npt.assert_allclose(actual=output, desired=[11.5, 61.8, 0.1]) + + +def test_info_fails(): + """ + Make sure info raises an exception if not given either a file name, pandas + DataFrame, or numpy ndarray. + """ + with pytest.raises(GMTInvalidInput): + info(table=xr.DataArray(21)) diff --git a/pygmt/tests/test_inset.py b/pygmt/tests/test_inset.py index 1d87b5a77f5..bc9458cc276 100644 --- a/pygmt/tests/test_inset.py +++ b/pygmt/tests/test_inset.py @@ -1,42 +1,42 @@ -""" -Tests for the inset function. -""" -from pygmt import Figure -from pygmt.helpers.testing import check_figures_equal - - -@check_figures_equal() -def test_inset_aliases(): - """ - Test the aliases for the inset function. - """ - fig_ref, fig_test = Figure(), Figure() - fig_ref.basemap(R="MG+r2", B="afg") - with fig_ref.inset(D="jTL+w3.5c+o0.2c", M=0, F="+pgreen"): - fig_ref.basemap(R="g", J="G47/-20/4c", B="afg") - - fig_test.basemap(region="MG+r2", frame="afg") - with fig_test.inset(position="jTL+w3.5c+o0.2c", margin=0, box="+pgreen"): - fig_test.basemap(region="g", projection="G47/-20/4c", frame="afg") - return fig_ref, fig_test - - -@check_figures_equal() -def test_inset_context_manager(): - """ - Test that the inset context manager works and, once closed, plotting - elements are added to the larger figure. - """ - fig_ref, fig_test = Figure(), Figure() - - fig_ref.basemap(region=[-74, -69.5, 41, 43], projection="M9c", frame=True) - fig_ref.basemap(rose="jTR+w3c") # Pass rose argument with basemap before the inset - with fig_ref.inset(position="jBL+w3c+o0.2c", margin=0, box="+pblack"): - fig_ref.basemap(region=[-80, -65, 35, 50], projection="M3c", frame="afg") - - fig_test.basemap(region=[-74, -69.5, 41, 43], projection="M9c", frame=True) - with fig_test.inset(position="jBL+w3c+o0.2c", margin=0, box="+pblack"): - fig_test.basemap(region=[-80, -65, 35, 50], projection="M3c", frame="afg") - fig_test.basemap(rose="jTR+w3c") # Pass rose argument with basemap after the inset - - return fig_ref, fig_test +""" +Tests for the inset function. +""" +from pygmt import Figure +from pygmt.helpers.testing import check_figures_equal + + +@check_figures_equal() +def test_inset_aliases(): + """ + Test the aliases for the inset function. + """ + fig_ref, fig_test = Figure(), Figure() + fig_ref.basemap(R="MG+r2", B="afg") + with fig_ref.inset(D="jTL+w3.5c+o0.2c", M=0, F="+pgreen"): + fig_ref.basemap(R="g", J="G47/-20/4c", B="afg") + + fig_test.basemap(region="MG+r2", frame="afg") + with fig_test.inset(position="jTL+w3.5c+o0.2c", margin=0, box="+pgreen"): + fig_test.basemap(region="g", projection="G47/-20/4c", frame="afg") + return fig_ref, fig_test + + +@check_figures_equal() +def test_inset_context_manager(): + """ + Test that the inset context manager works and, once closed, plotting + elements are added to the larger figure. + """ + fig_ref, fig_test = Figure(), Figure() + + fig_ref.basemap(region=[-74, -69.5, 41, 43], projection="M9c", frame=True) + fig_ref.basemap(rose="jTR+w3c") # Pass rose argument with basemap before the inset + with fig_ref.inset(position="jBL+w3c+o0.2c", margin=0, box="+pblack"): + fig_ref.basemap(region=[-80, -65, 35, 50], projection="M3c", frame="afg") + + fig_test.basemap(region=[-74, -69.5, 41, 43], projection="M9c", frame=True) + with fig_test.inset(position="jBL+w3c+o0.2c", margin=0, box="+pblack"): + fig_test.basemap(region=[-80, -65, 35, 50], projection="M3c", frame="afg") + fig_test.basemap(rose="jTR+w3c") # Pass rose argument with basemap after the inset + + return fig_ref, fig_test diff --git a/pygmt/tests/test_legend.py b/pygmt/tests/test_legend.py index 60479814b0d..c658088a02b 100644 --- a/pygmt/tests/test_legend.py +++ b/pygmt/tests/test_legend.py @@ -1,137 +1,137 @@ -""" -Tests for legend. -""" -import pytest -from pygmt import Figure -from pygmt.exceptions import GMTInvalidInput -from pygmt.helpers import GMTTempFile -from pygmt.helpers.testing import check_figures_equal - - -@pytest.mark.mpl_image_compare -def test_legend_position(): - """ - Try positioning with each of the four legend coordinate systems. - """ - - fig = Figure() - - fig.basemap(region=[-2, 2, -2, 2], frame=True) - - positions = ["jTR+jTR", "g0/1", "n0.2/0.2", "x4i/2i/2i"] - - for i, position in enumerate(positions): - - fig.plot(x=[0], y=[0], style="p10p", label=i) - fig.legend(position=position, box=True) - - return fig - - -@pytest.mark.mpl_image_compare -def test_legend_default_position(): - """ - Try using the default legend position. - """ - - fig = Figure() - - fig.basemap(region=[-1, 1, -1, 1], frame=True) - - fig.plot(x=[0], y=[0], style="p10p", label="Default") - fig.legend() - - return fig - - -@check_figures_equal() -def test_legend_entries(): - """ - Test different marker types/shapes. - """ - fig_ref, fig_test = Figure(), Figure() - - # Use single-character arguments for the reference image - fig_ref = Figure() - fig_ref.basemap(J="x1i", R="0/7/3/7", B="") - fig_ref.plot( - data="@Table_5_11.txt", - S="c0.15i", - G="lightgreen", - W="faint", - l="Apples", - ) - fig_ref.plot(data="@Table_5_11.txt", W="1.5p,gray", l='"My lines"') - fig_ref.plot(data="@Table_5_11.txt", S="t0.15i", G="orange", l="Oranges") - fig_ref.legend(D="JTR+jTR") - - fig_test.basemap(projection="x1i", region=[0, 7, 3, 7], frame=True) - fig_test.plot( - data="@Table_5_11.txt", - style="c0.15i", - color="lightgreen", - pen="faint", - label="Apples", - ) - fig_test.plot(data="@Table_5_11.txt", pen="1.5p,gray", label='"My lines"') - fig_test.plot( - data="@Table_5_11.txt", style="t0.15i", color="orange", label="Oranges" - ) - fig_test.legend(position="JTR+jTR") - - return fig_ref, fig_test - - -@pytest.mark.mpl_image_compare -def test_legend_specfile(): - """ - Test specfile functionality. - """ - - specfile_contents = """ -G -0.1i -H 24 Times-Roman My Map Legend -D 0.2i 1p -N 2 -V 0 1p -S 0.1i c 0.15i p300/12 0.25p 0.3i This circle is hachured -S 0.1i e 0.15i yellow 0.25p 0.3i This ellipse is yellow -S 0.1i w 0.15i green 0.25p 0.3i This wedge is green -S 0.1i f0.1i+l+t 0.25i blue 0.25p 0.3i This is a fault -S 0.1i - 0.15i - 0.25p,- 0.3i A dashed contour -S 0.1i v0.1i+a40+e 0.25i magenta 0.25p 0.3i This is a vector -S 0.1i i 0.15i cyan 0.25p 0.3i This triangle is boring -V 0 1p -D 0.2i 1p -N 1 -G 0.05i -G 0.05i -G 0.05i -L 9 4 R Smith et al., @%5%J. Geophys. Res., 99@%%, 2000 -G 0.1i -P -T Let us just try some simple text that can go on a few lines. -T There is no easy way to predetermine how many lines will be required, -T so we may have to adjust the box height to get the right size box. -""" - - with GMTTempFile() as specfile: - - with open(specfile.name, "w") as file: - file.write(specfile_contents) - - fig = Figure() - - fig.basemap(projection="x6i", region=[0, 1, 0, 1], frame=True) - fig.legend(specfile.name, position="JTM+jCM+w5i") - - return fig - - -def test_legend_fails(): - """ - Test legend fails with invalid spec. - """ - fig = Figure() - with pytest.raises(GMTInvalidInput): - fig.legend(spec=["@Table_5_11.txt"]) +""" +Tests for legend. +""" +import pytest +from pygmt import Figure +from pygmt.exceptions import GMTInvalidInput +from pygmt.helpers import GMTTempFile +from pygmt.helpers.testing import check_figures_equal + + +@pytest.mark.mpl_image_compare +def test_legend_position(): + """ + Try positioning with each of the four legend coordinate systems. + """ + + fig = Figure() + + fig.basemap(region=[-2, 2, -2, 2], frame=True) + + positions = ["jTR+jTR", "g0/1", "n0.2/0.2", "x4i/2i/2i"] + + for i, position in enumerate(positions): + + fig.plot(x=[0], y=[0], style="p10p", label=i) + fig.legend(position=position, box=True) + + return fig + + +@pytest.mark.mpl_image_compare +def test_legend_default_position(): + """ + Try using the default legend position. + """ + + fig = Figure() + + fig.basemap(region=[-1, 1, -1, 1], frame=True) + + fig.plot(x=[0], y=[0], style="p10p", label="Default") + fig.legend() + + return fig + + +@check_figures_equal() +def test_legend_entries(): + """ + Test different marker types/shapes. + """ + fig_ref, fig_test = Figure(), Figure() + + # Use single-character arguments for the reference image + fig_ref = Figure() + fig_ref.basemap(J="x1i", R="0/7/3/7", B="") + fig_ref.plot( + data="@Table_5_11.txt", + S="c0.15i", + G="lightgreen", + W="faint", + l="Apples", + ) + fig_ref.plot(data="@Table_5_11.txt", W="1.5p,gray", l='"My lines"') + fig_ref.plot(data="@Table_5_11.txt", S="t0.15i", G="orange", l="Oranges") + fig_ref.legend(D="JTR+jTR") + + fig_test.basemap(projection="x1i", region=[0, 7, 3, 7], frame=True) + fig_test.plot( + data="@Table_5_11.txt", + style="c0.15i", + color="lightgreen", + pen="faint", + label="Apples", + ) + fig_test.plot(data="@Table_5_11.txt", pen="1.5p,gray", label='"My lines"') + fig_test.plot( + data="@Table_5_11.txt", style="t0.15i", color="orange", label="Oranges" + ) + fig_test.legend(position="JTR+jTR") + + return fig_ref, fig_test + + +@pytest.mark.mpl_image_compare +def test_legend_specfile(): + """ + Test specfile functionality. + """ + + specfile_contents = """ +G -0.1i +H 24 Times-Roman My Map Legend +D 0.2i 1p +N 2 +V 0 1p +S 0.1i c 0.15i p300/12 0.25p 0.3i This circle is hachured +S 0.1i e 0.15i yellow 0.25p 0.3i This ellipse is yellow +S 0.1i w 0.15i green 0.25p 0.3i This wedge is green +S 0.1i f0.1i+l+t 0.25i blue 0.25p 0.3i This is a fault +S 0.1i - 0.15i - 0.25p,- 0.3i A dashed contour +S 0.1i v0.1i+a40+e 0.25i magenta 0.25p 0.3i This is a vector +S 0.1i i 0.15i cyan 0.25p 0.3i This triangle is boring +V 0 1p +D 0.2i 1p +N 1 +G 0.05i +G 0.05i +G 0.05i +L 9 4 R Smith et al., @%5%J. Geophys. Res., 99@%%, 2000 +G 0.1i +P +T Let us just try some simple text that can go on a few lines. +T There is no easy way to predetermine how many lines will be required, +T so we may have to adjust the box height to get the right size box. +""" + + with GMTTempFile() as specfile: + + with open(specfile.name, "w") as file: + file.write(specfile_contents) + + fig = Figure() + + fig.basemap(projection="x6i", region=[0, 1, 0, 1], frame=True) + fig.legend(specfile.name, position="JTM+jCM+w5i") + + return fig + + +def test_legend_fails(): + """ + Test legend fails with invalid spec. + """ + fig = Figure() + with pytest.raises(GMTInvalidInput): + fig.legend(spec=["@Table_5_11.txt"]) diff --git a/pygmt/tests/test_logo.py b/pygmt/tests/test_logo.py index f5a15571f76..32ac8fbdc29 100644 --- a/pygmt/tests/test_logo.py +++ b/pygmt/tests/test_logo.py @@ -1,34 +1,34 @@ -""" -Tests for fig.logo. -""" -from pygmt import Figure -from pygmt.helpers.testing import check_figures_equal - - -@check_figures_equal() -def test_logo(): - """ - Plot a GMT logo of a 2 inch width as a stand-alone plot. - """ - fig_ref, fig_test = Figure(), Figure() - # Use single-character arguments for the reference image - fig_ref.logo(D="x0/0+w2i") - fig_test.logo(position="x0/0+w2i") - return fig_ref, fig_test - - -@check_figures_equal() -def test_logo_on_a_map(): - """ - Plot a GMT logo in the upper right corner of a map. - """ - fig_ref, fig_test = Figure(), Figure() - # Use single-character arguments for the reference image - fig_ref.coast(R="-90/-70/0/20", J="M6i", G="chocolate", B="") - fig_ref.logo(D="jTR+o0.1i/0.1i+w3i", F="") - - fig_test.coast( - region=[-90, -70, 0, 20], projection="M6i", land="chocolate", frame=True - ) - fig_test.logo(position="jTR+o0.1i/0.1i+w3i", box=True) - return fig_ref, fig_test +""" +Tests for fig.logo. +""" +from pygmt import Figure +from pygmt.helpers.testing import check_figures_equal + + +@check_figures_equal() +def test_logo(): + """ + Plot a GMT logo of a 2 inch width as a stand-alone plot. + """ + fig_ref, fig_test = Figure(), Figure() + # Use single-character arguments for the reference image + fig_ref.logo(D="x0/0+w2i") + fig_test.logo(position="x0/0+w2i") + return fig_ref, fig_test + + +@check_figures_equal() +def test_logo_on_a_map(): + """ + Plot a GMT logo in the upper right corner of a map. + """ + fig_ref, fig_test = Figure(), Figure() + # Use single-character arguments for the reference image + fig_ref.coast(R="-90/-70/0/20", J="M6i", G="chocolate", B="") + fig_ref.logo(D="jTR+o0.1i/0.1i+w3i", F="") + + fig_test.coast( + region=[-90, -70, 0, 20], projection="M6i", land="chocolate", frame=True + ) + fig_test.logo(position="jTR+o0.1i/0.1i+w3i", box=True) + return fig_ref, fig_test diff --git a/pygmt/tests/test_makecpt.py b/pygmt/tests/test_makecpt.py index 0cb8517ceb5..476754d51bf 100644 --- a/pygmt/tests/test_makecpt.py +++ b/pygmt/tests/test_makecpt.py @@ -1,227 +1,227 @@ -""" -Tests for makecpt. -""" -import os - -import numpy as np -import pytest -from pygmt import Figure, makecpt -from pygmt.datasets import load_earth_relief -from pygmt.exceptions import GMTInvalidInput -from pygmt.helpers import GMTTempFile -from pygmt.helpers.testing import check_figures_equal - -TEST_DATA_DIR = os.path.join(os.path.dirname(__file__), "data") -POINTS_DATA = os.path.join(TEST_DATA_DIR, "points.txt") - - -@pytest.fixture(scope="module", name="points") -def fixture_points(): - """ - Load the points data from the test file. - """ - return np.loadtxt(POINTS_DATA) - - -@pytest.fixture(scope="module", name="region") -def fixture_region(): - """ - The data region. - """ - return [10, 70, -5, 10] - - -@pytest.fixture(scope="module", name="grid") -def fixture_grid(): - """ - Load the grid data from the sample earth_relief file. - """ - return load_earth_relief(registration="gridline") - - -@pytest.mark.mpl_image_compare -def test_makecpt_to_plot_points(points, region): - """ - Use static color palette table to change color of points. - """ - fig = Figure() - makecpt(cmap="rainbow") - fig.plot( - x=points[:, 0], - y=points[:, 1], - color=points[:, 2], - region=region, - style="c1c", - cmap=True, - ) - return fig - - -@pytest.mark.mpl_image_compare -def test_makecpt_to_plot_grid(grid): - """ - Use static color palette table to change color of grid. - """ - fig = Figure() - makecpt(cmap="relief") - fig.grdimage(grid, projection="W0/6i") - return fig - - -@check_figures_equal() -def test_makecpt_to_plot_grid_scaled_with_series(grid): - """ - Use static color palette table scaled to a min/max series to change color - of grid. - """ - # Use single-character arguments for the reference image - fig_ref = Figure() - makecpt(C="oleron", T="-4500/4500") - fig_ref.grdimage(grid, J="W0/6i") - - fig_test = Figure() - makecpt(cmap="oleron", series="-4500/4500") - fig_test.grdimage(grid, projection="W0/6i") - return fig_ref, fig_test - - -def test_makecpt_output_to_cpt_file(): - """ - Save the generated static color palette table to a .cpt file. - """ - with GMTTempFile(suffix=".cpt") as cptfile: - makecpt(output=cptfile.name) - assert os.path.exists(cptfile.name) - - -def test_makecpt_blank_output(): - """ - Use incorrect setting by passing in blank file name to output parameter. - """ - with pytest.raises(GMTInvalidInput): - makecpt(output="") - - -def test_makecpt_invalid_output(): - """ - Use incorrect setting by passing in invalid type to output parameter. - """ - with pytest.raises(GMTInvalidInput): - makecpt(output=["some.cpt"]) - - -@pytest.mark.mpl_image_compare -def test_makecpt_truncated_to_zlow_zhigh(grid): - """ - Use static color palette table that is truncated to z-low and z-high. - """ - fig = Figure() - makecpt(cmap="rainbow", truncate=[0.15, 0.85], series=[-4500, 4500]) - fig.grdimage(grid, projection="W0/6i") - return fig - - -@pytest.mark.mpl_image_compare -def test_makecpt_truncated_at_zlow_only(grid): - """ - Use static color palette table that is truncated at z-low only. - """ - fig = Figure() - makecpt(cmap="rainbow", truncate=[0.5, None], series=[-4500, 4500]) - fig.grdimage(grid, projection="W0/6i") - return fig - - -@pytest.mark.mpl_image_compare -def test_makecpt_truncated_at_zhigh_only(grid): - """ - Use static color palette table that is truncated at z-high only. - """ - fig = Figure() - makecpt(cmap="rainbow", truncate=[None, 0.5], series=[-4500, 4500]) - fig.grdimage(grid, projection="W0/6i") - return fig - - -@pytest.mark.mpl_image_compare -def test_makecpt_reverse_color_only(grid): - """ - Use static color palette table with its colors reversed. - """ - fig = Figure() - makecpt(cmap="earth", reverse=True) - fig.grdimage(grid, projection="W0/6i") - return fig - - -@pytest.mark.mpl_image_compare -def test_makecpt_reverse_zsign_only(grid): - """ - Use static color palette table with its z-value sign reversed. - """ - fig = Figure() - makecpt(cmap="earth", reverse="z") - fig.grdimage(grid, projection="W0/6i") - return fig - - -@pytest.mark.mpl_image_compare -def test_makecpt_reverse_color_and_zsign(grid): - """ - Use static color palette table with both its colors and z-value sign - reversed. - """ - fig = Figure() - makecpt(cmap="earth", reverse="cz") - fig.grdimage(grid, projection="W0/6i") - return fig - - -@pytest.mark.mpl_image_compare -def test_makecpt_continuous(grid): - """ - Use static color palette table that is continuous from blue to white and - scaled from -4500 to 4500m. - """ - fig = Figure() - makecpt(cmap="blue,white", continuous=True, series="-4500,4500") - fig.grdimage(grid, projection="W0/6i") - return fig - - -@check_figures_equal() -def test_makecpt_categorical(region): - """ - Use static color palette table that is categorical. - """ - fig_ref = Figure() - makecpt(C="categorical", W="") - fig_ref.colorbar(cmap=True, region=region, frame=True, position="JBC") - - fig_test = Figure() - makecpt(cmap="categorical", categorical=True) - fig_test.colorbar(cmap=True, region=region, frame=True, position="JBC") - return fig_ref, fig_test - - -@check_figures_equal() -def test_makecpt_cyclic(region): - """ - Use static color palette table that is cyclic. - """ - fig_ref = Figure() - makecpt(C="cork", W="w") - fig_ref.colorbar(cmap=True, region=region, frame=True, position="JBC") - - fig_test = Figure() - makecpt(cmap="cork", cyclic=True) - fig_test.colorbar(cmap=True, region=region, frame=True, position="JBC") - return fig_ref, fig_test - - -def test_makecpt_categorical_and_cyclic(): - """ - Use incorrect setting by setting both categorical and cyclic to True. - """ - with pytest.raises(GMTInvalidInput): - makecpt(cmap="batlow", categorical=True, cyclic=True) +""" +Tests for makecpt. +""" +import os + +import numpy as np +import pytest +from pygmt import Figure, makecpt +from pygmt.datasets import load_earth_relief +from pygmt.exceptions import GMTInvalidInput +from pygmt.helpers import GMTTempFile +from pygmt.helpers.testing import check_figures_equal + +TEST_DATA_DIR = os.path.join(os.path.dirname(__file__), "data") +POINTS_DATA = os.path.join(TEST_DATA_DIR, "points.txt") + + +@pytest.fixture(scope="module", name="points") +def fixture_points(): + """ + Load the points data from the test file. + """ + return np.loadtxt(POINTS_DATA) + + +@pytest.fixture(scope="module", name="region") +def fixture_region(): + """ + The data region. + """ + return [10, 70, -5, 10] + + +@pytest.fixture(scope="module", name="grid") +def fixture_grid(): + """ + Load the grid data from the sample earth_relief file. + """ + return load_earth_relief(registration="gridline") + + +@pytest.mark.mpl_image_compare +def test_makecpt_to_plot_points(points, region): + """ + Use static color palette table to change color of points. + """ + fig = Figure() + makecpt(cmap="rainbow") + fig.plot( + x=points[:, 0], + y=points[:, 1], + color=points[:, 2], + region=region, + style="c1c", + cmap=True, + ) + return fig + + +@pytest.mark.mpl_image_compare +def test_makecpt_to_plot_grid(grid): + """ + Use static color palette table to change color of grid. + """ + fig = Figure() + makecpt(cmap="relief") + fig.grdimage(grid, projection="W0/6i") + return fig + + +@check_figures_equal() +def test_makecpt_to_plot_grid_scaled_with_series(grid): + """ + Use static color palette table scaled to a min/max series to change color + of grid. + """ + # Use single-character arguments for the reference image + fig_ref = Figure() + makecpt(C="oleron", T="-4500/4500") + fig_ref.grdimage(grid, J="W0/6i") + + fig_test = Figure() + makecpt(cmap="oleron", series="-4500/4500") + fig_test.grdimage(grid, projection="W0/6i") + return fig_ref, fig_test + + +def test_makecpt_output_to_cpt_file(): + """ + Save the generated static color palette table to a .cpt file. + """ + with GMTTempFile(suffix=".cpt") as cptfile: + makecpt(output=cptfile.name) + assert os.path.exists(cptfile.name) + + +def test_makecpt_blank_output(): + """ + Use incorrect setting by passing in blank file name to output parameter. + """ + with pytest.raises(GMTInvalidInput): + makecpt(output="") + + +def test_makecpt_invalid_output(): + """ + Use incorrect setting by passing in invalid type to output parameter. + """ + with pytest.raises(GMTInvalidInput): + makecpt(output=["some.cpt"]) + + +@pytest.mark.mpl_image_compare +def test_makecpt_truncated_to_zlow_zhigh(grid): + """ + Use static color palette table that is truncated to z-low and z-high. + """ + fig = Figure() + makecpt(cmap="rainbow", truncate=[0.15, 0.85], series=[-4500, 4500]) + fig.grdimage(grid, projection="W0/6i") + return fig + + +@pytest.mark.mpl_image_compare +def test_makecpt_truncated_at_zlow_only(grid): + """ + Use static color palette table that is truncated at z-low only. + """ + fig = Figure() + makecpt(cmap="rainbow", truncate=[0.5, None], series=[-4500, 4500]) + fig.grdimage(grid, projection="W0/6i") + return fig + + +@pytest.mark.mpl_image_compare +def test_makecpt_truncated_at_zhigh_only(grid): + """ + Use static color palette table that is truncated at z-high only. + """ + fig = Figure() + makecpt(cmap="rainbow", truncate=[None, 0.5], series=[-4500, 4500]) + fig.grdimage(grid, projection="W0/6i") + return fig + + +@pytest.mark.mpl_image_compare +def test_makecpt_reverse_color_only(grid): + """ + Use static color palette table with its colors reversed. + """ + fig = Figure() + makecpt(cmap="earth", reverse=True) + fig.grdimage(grid, projection="W0/6i") + return fig + + +@pytest.mark.mpl_image_compare +def test_makecpt_reverse_zsign_only(grid): + """ + Use static color palette table with its z-value sign reversed. + """ + fig = Figure() + makecpt(cmap="earth", reverse="z") + fig.grdimage(grid, projection="W0/6i") + return fig + + +@pytest.mark.mpl_image_compare +def test_makecpt_reverse_color_and_zsign(grid): + """ + Use static color palette table with both its colors and z-value sign + reversed. + """ + fig = Figure() + makecpt(cmap="earth", reverse="cz") + fig.grdimage(grid, projection="W0/6i") + return fig + + +@pytest.mark.mpl_image_compare +def test_makecpt_continuous(grid): + """ + Use static color palette table that is continuous from blue to white and + scaled from -4500 to 4500m. + """ + fig = Figure() + makecpt(cmap="blue,white", continuous=True, series="-4500,4500") + fig.grdimage(grid, projection="W0/6i") + return fig + + +@check_figures_equal() +def test_makecpt_categorical(region): + """ + Use static color palette table that is categorical. + """ + fig_ref = Figure() + makecpt(C="categorical", W="") + fig_ref.colorbar(cmap=True, region=region, frame=True, position="JBC") + + fig_test = Figure() + makecpt(cmap="categorical", categorical=True) + fig_test.colorbar(cmap=True, region=region, frame=True, position="JBC") + return fig_ref, fig_test + + +@check_figures_equal() +def test_makecpt_cyclic(region): + """ + Use static color palette table that is cyclic. + """ + fig_ref = Figure() + makecpt(C="cork", W="w") + fig_ref.colorbar(cmap=True, region=region, frame=True, position="JBC") + + fig_test = Figure() + makecpt(cmap="cork", cyclic=True) + fig_test.colorbar(cmap=True, region=region, frame=True, position="JBC") + return fig_ref, fig_test + + +def test_makecpt_categorical_and_cyclic(): + """ + Use incorrect setting by setting both categorical and cyclic to True. + """ + with pytest.raises(GMTInvalidInput): + makecpt(cmap="batlow", categorical=True, cyclic=True) diff --git a/pygmt/tests/test_meca.py b/pygmt/tests/test_meca.py index 24715cd92a2..eb90240e7c0 100644 --- a/pygmt/tests/test_meca.py +++ b/pygmt/tests/test_meca.py @@ -1,238 +1,238 @@ -""" -Tests for meca. -""" -import os - -import numpy as np -import pandas as pd -import pytest -from pygmt import Figure -from pygmt.helpers import GMTTempFile - -TEST_DATA_DIR = os.path.join(os.path.dirname(__file__), "data") - - -@pytest.mark.mpl_image_compare -def test_meca_spec_dictionary(): - """ - Test supplying a dictionary containing a single focal mechanism to the - `spec` argument. - """ - - fig = Figure() - - # Right lateral strike slip focal mechanism - fig.meca( - dict(strike=0, dip=90, rake=0, magnitude=5), - longitude=0, - latitude=5, - depth=0, - scale="2.5c", - region=[-1, 1, 4, 6], - projection="M14c", - frame=2, - ) - - return fig - - -@pytest.mark.mpl_image_compare -def test_meca_spec_dict_list(): - """ - Test supplying a dictionary containing a list of focal mechanism to the - `spec` argument. - """ - - fig = Figure() - - # supply focal mechanisms as a dict of lists - focal_mechanisms = dict( - strike=[330, 350], dip=[30, 50], rake=[90, 90], magnitude=[3, 2] - ) - - fig.meca( - focal_mechanisms, - longitude=[-124.3, -124.4], - latitude=[48.1, 48.2], - depth=[12.0, 11.0], - region=[-125, -122, 47, 49], - scale="2c", - projection="M14c", - ) - - return fig - - -@pytest.mark.mpl_image_compare -def test_meca_spec_dataframe(): - """ - Test supplying a pandas DataFrame containing focal mechanisms and locations - to the `spec` argument. - """ - - fig = Figure() - - # supply focal mechanisms to meca as a dataframe - focal_mechanisms = dict( - strike=[324, 353], - dip=[20.6, 40], - rake=[83, 90], - magnitude=[3.4, 2.9], - longitude=[-124, -124.4], - latitude=[48.1, 48.2], - depth=[12, 11.0], - ) - spec_dataframe = pd.DataFrame(data=focal_mechanisms) - - fig.meca(spec_dataframe, region=[-125, -122, 47, 49], scale="2c", projection="M14c") - - return fig - - -@pytest.mark.mpl_image_compare -def test_meca_spec_1d_array(): - """ - Test supplying a 1D numpy array containing focal mechanisms and locations - to the `spec` argument. - """ - - fig = Figure() - - # supply focal mechanisms to meca as a 1D numpy array, here we are using - # the Harvard CMT zero trace convention but the focal mechanism - # parameters may be specified any of the available conventions. Since we - # are not using a dict or dataframe the convention and component should - # be specified. - focal_mechanism = [ - -127.40, # longitude - 40.87, # latitude - 12, # depth - -3.19, # mrr - 0.16, # mtt - 3.03, # mff - -1.02, # mrt - -3.93, # mrf - -0.02, # mtf - 23, # exponent - 0, # plot_lon, 0 to plot at event location - 0, # plot_lat, 0 to plot at event location - ] - focal_mech_array = np.asarray(focal_mechanism) - - fig.meca( - focal_mech_array, - convention="mt", - component="full", - region=[-128, -127, 40, 41], - scale="2c", - projection="M14c", - ) - - return fig - - -@pytest.mark.mpl_image_compare -def test_meca_spec_2d_array(): - """ - Test supplying a 2D numpy array containing focal mechanisms and locations - to the `spec` argument. - """ - - fig = Figure() - - # supply focal mechanisms to meca as a 2D numpy array, here we are using - # the GCMT convention but the focal mechanism parameters may be - # specified any of the available conventions. Since we are not using a - # dict or dataframe the convention and component should be specified. - focal_mechanisms = [ - [ - -127.40, # longitude - 40.87, # latitude - 12, # depth - 170, # strike1 - 20, # dip1 - -110, # rake1 - 11, # strike2 - 71, # dip2 - -83, # rake2 - 5.1, # mantissa - 23, # exponent - 0, # plot_lon, 0 means we want to plot at the event location - 0, # plot_lat - ], - [-127.50, 40.88, 12.0, 168, 40, -115, 20, 54, -70, 4.0, 23, 0, 0], - ] - focal_mechs_array = np.asarray(focal_mechanisms) - - fig.meca( - focal_mechs_array, - convention="gcmt", - region=[-128, -127, 40, 41], - scale="2c", - projection="M14c", - ) - - return fig - - -@pytest.mark.mpl_image_compare -def test_meca_spec_file(): - """ - Test supplying a file containing focal mechanisms and locations to the - `spec` argument. - """ - - fig = Figure() - - focal_mechanism = [-127.43, 40.81, 12, -3.19, 1.16, 3.93, -1.02, -3.93, -1.02, 23] - - # writes temp file to pass to gmt - with GMTTempFile() as temp: - with open(temp.name, mode="w") as temp_file: - temp_file.write(" ".join([str(x) for x in focal_mechanism])) - # supply focal mechanisms to meca as a file - fig.meca( - temp.name, - convention="mt", - component="full", - region=[-128, -127, 40, 41], - scale="2c", - projection="M14c", - ) - - return fig - - -@pytest.mark.mpl_image_compare -def test_meca_loc_array(): - """ - Test supplying lists and np.ndarrays as the event location (longitude, - latitude, and depth). - """ - - fig = Figure() - - # specify focal mechanisms - focal_mechanisms = dict( - strike=[327, 350], dip=[41, 50], rake=[68, 90], magnitude=[3, 2] - ) - - # longitude, latitude, and depth may be specified as an int, float, - # list, or 1d numpy array - longitude = np.array([-123.3, -124.4]) - latitude = np.array([48.4, 48.2]) - depth = [12.0, 11.0] # to test mixed data types as inputs - - scale = "2c" - - fig.meca( - focal_mechanisms, - scale, - longitude, - latitude, - depth, - region=[-125, -122, 47, 49], - projection="M14c", - ) - - return fig +""" +Tests for meca. +""" +import os + +import numpy as np +import pandas as pd +import pytest +from pygmt import Figure +from pygmt.helpers import GMTTempFile + +TEST_DATA_DIR = os.path.join(os.path.dirname(__file__), "data") + + +@pytest.mark.mpl_image_compare +def test_meca_spec_dictionary(): + """ + Test supplying a dictionary containing a single focal mechanism to the + `spec` argument. + """ + + fig = Figure() + + # Right lateral strike slip focal mechanism + fig.meca( + dict(strike=0, dip=90, rake=0, magnitude=5), + longitude=0, + latitude=5, + depth=0, + scale="2.5c", + region=[-1, 1, 4, 6], + projection="M14c", + frame=2, + ) + + return fig + + +@pytest.mark.mpl_image_compare +def test_meca_spec_dict_list(): + """ + Test supplying a dictionary containing a list of focal mechanism to the + `spec` argument. + """ + + fig = Figure() + + # supply focal mechanisms as a dict of lists + focal_mechanisms = dict( + strike=[330, 350], dip=[30, 50], rake=[90, 90], magnitude=[3, 2] + ) + + fig.meca( + focal_mechanisms, + longitude=[-124.3, -124.4], + latitude=[48.1, 48.2], + depth=[12.0, 11.0], + region=[-125, -122, 47, 49], + scale="2c", + projection="M14c", + ) + + return fig + + +@pytest.mark.mpl_image_compare +def test_meca_spec_dataframe(): + """ + Test supplying a pandas DataFrame containing focal mechanisms and locations + to the `spec` argument. + """ + + fig = Figure() + + # supply focal mechanisms to meca as a dataframe + focal_mechanisms = dict( + strike=[324, 353], + dip=[20.6, 40], + rake=[83, 90], + magnitude=[3.4, 2.9], + longitude=[-124, -124.4], + latitude=[48.1, 48.2], + depth=[12, 11.0], + ) + spec_dataframe = pd.DataFrame(data=focal_mechanisms) + + fig.meca(spec_dataframe, region=[-125, -122, 47, 49], scale="2c", projection="M14c") + + return fig + + +@pytest.mark.mpl_image_compare +def test_meca_spec_1d_array(): + """ + Test supplying a 1D numpy array containing focal mechanisms and locations + to the `spec` argument. + """ + + fig = Figure() + + # supply focal mechanisms to meca as a 1D numpy array, here we are using + # the Harvard CMT zero trace convention but the focal mechanism + # parameters may be specified any of the available conventions. Since we + # are not using a dict or dataframe the convention and component should + # be specified. + focal_mechanism = [ + -127.40, # longitude + 40.87, # latitude + 12, # depth + -3.19, # mrr + 0.16, # mtt + 3.03, # mff + -1.02, # mrt + -3.93, # mrf + -0.02, # mtf + 23, # exponent + 0, # plot_lon, 0 to plot at event location + 0, # plot_lat, 0 to plot at event location + ] + focal_mech_array = np.asarray(focal_mechanism) + + fig.meca( + focal_mech_array, + convention="mt", + component="full", + region=[-128, -127, 40, 41], + scale="2c", + projection="M14c", + ) + + return fig + + +@pytest.mark.mpl_image_compare +def test_meca_spec_2d_array(): + """ + Test supplying a 2D numpy array containing focal mechanisms and locations + to the `spec` argument. + """ + + fig = Figure() + + # supply focal mechanisms to meca as a 2D numpy array, here we are using + # the GCMT convention but the focal mechanism parameters may be + # specified any of the available conventions. Since we are not using a + # dict or dataframe the convention and component should be specified. + focal_mechanisms = [ + [ + -127.40, # longitude + 40.87, # latitude + 12, # depth + 170, # strike1 + 20, # dip1 + -110, # rake1 + 11, # strike2 + 71, # dip2 + -83, # rake2 + 5.1, # mantissa + 23, # exponent + 0, # plot_lon, 0 means we want to plot at the event location + 0, # plot_lat + ], + [-127.50, 40.88, 12.0, 168, 40, -115, 20, 54, -70, 4.0, 23, 0, 0], + ] + focal_mechs_array = np.asarray(focal_mechanisms) + + fig.meca( + focal_mechs_array, + convention="gcmt", + region=[-128, -127, 40, 41], + scale="2c", + projection="M14c", + ) + + return fig + + +@pytest.mark.mpl_image_compare +def test_meca_spec_file(): + """ + Test supplying a file containing focal mechanisms and locations to the + `spec` argument. + """ + + fig = Figure() + + focal_mechanism = [-127.43, 40.81, 12, -3.19, 1.16, 3.93, -1.02, -3.93, -1.02, 23] + + # writes temp file to pass to gmt + with GMTTempFile() as temp: + with open(temp.name, mode="w") as temp_file: + temp_file.write(" ".join([str(x) for x in focal_mechanism])) + # supply focal mechanisms to meca as a file + fig.meca( + temp.name, + convention="mt", + component="full", + region=[-128, -127, 40, 41], + scale="2c", + projection="M14c", + ) + + return fig + + +@pytest.mark.mpl_image_compare +def test_meca_loc_array(): + """ + Test supplying lists and np.ndarrays as the event location (longitude, + latitude, and depth). + """ + + fig = Figure() + + # specify focal mechanisms + focal_mechanisms = dict( + strike=[327, 350], dip=[41, 50], rake=[68, 90], magnitude=[3, 2] + ) + + # longitude, latitude, and depth may be specified as an int, float, + # list, or 1d numpy array + longitude = np.array([-123.3, -124.4]) + latitude = np.array([48.4, 48.2]) + depth = [12.0, 11.0] # to test mixed data types as inputs + + scale = "2c" + + fig.meca( + focal_mechanisms, + scale, + longitude, + latitude, + depth, + region=[-125, -122, 47, 49], + projection="M14c", + ) + + return fig diff --git a/pygmt/tests/test_plot.py b/pygmt/tests/test_plot.py index 5ef8945e125..496dc503b99 100644 --- a/pygmt/tests/test_plot.py +++ b/pygmt/tests/test_plot.py @@ -1,488 +1,488 @@ -# pylint: disable=redefined-outer-name -""" -Tests plot. -""" -import datetime -import os - -import numpy as np -import pandas as pd -import pytest -import xarray as xr -from pygmt import Figure -from pygmt.exceptions import GMTInvalidInput -from pygmt.helpers import GMTTempFile -from pygmt.helpers.testing import check_figures_equal - -TEST_DATA_DIR = os.path.join(os.path.dirname(__file__), "data") -POINTS_DATA = os.path.join(TEST_DATA_DIR, "points.txt") - - -@pytest.fixture(scope="module") -def data(): - """ - Load the point data from the test file. - """ - return np.loadtxt(POINTS_DATA) - - -@pytest.fixture(scope="module") -def region(): - """ - The data region. - """ - return [10, 70, -5, 10] - - -@pytest.mark.mpl_image_compare -def test_plot_red_circles(data, region): - """ - Plot the data in red circles passing in vectors. - """ - fig = Figure() - fig.plot( - x=data[:, 0], - y=data[:, 1], - region=region, - projection="X4i", - style="c0.2c", - color="red", - frame="afg", - ) - return fig - - -def test_plot_fail_no_data(data): - """ - Plot should raise an exception if no data is given. - """ - fig = Figure() - with pytest.raises(GMTInvalidInput): - fig.plot( - region=region, projection="X4i", style="c0.2c", color="red", frame="afg" - ) - with pytest.raises(GMTInvalidInput): - fig.plot( - x=data[:, 0], - region=region, - projection="X4i", - style="c0.2c", - color="red", - frame="afg", - ) - with pytest.raises(GMTInvalidInput): - fig.plot( - y=data[:, 0], - region=region, - projection="X4i", - style="c0.2c", - color="red", - frame="afg", - ) - # Should also fail if given too much data - with pytest.raises(GMTInvalidInput): - fig.plot( - x=data[:, 0], - y=data[:, 1], - data=data, - region=region, - projection="X4i", - style="c0.2c", - color="red", - frame="afg", - ) - - -def test_plot_fail_size_color(data): - """ - Should raise an exception if array sizes and color are used with matrix. - """ - fig = Figure() - with pytest.raises(GMTInvalidInput): - fig.plot( - data=data, - region=region, - projection="X4i", - style="c0.2c", - color=data[:, 2], - frame="afg", - ) - with pytest.raises(GMTInvalidInput): - fig.plot( - data=data, - region=region, - projection="X4i", - style="cc", - sizes=data[:, 2], - color="red", - frame="afg", - ) - - -@check_figures_equal() -def test_plot_projection(data): - """ - Plot the data in green squares with a projection. - """ - fig_ref, fig_test = Figure(), Figure() - fig_ref.plot( - data=POINTS_DATA, - R="g", - J="R270/4i", - S="s0.2c", - G="green", - B="ag", - ) - fig_test.plot( - x=data[:, 0], - y=data[:, 1], - region="g", - projection="R270/4i", - style="s0.2c", - color="green", - frame="ag", - ) - return fig_ref, fig_test - - -@check_figures_equal() -def test_plot_colors(data, region): - """ - Plot the data using z as colors. - """ - fig_ref, fig_test = Figure(), Figure() - # Use single-character arguments for the reference image - fig_ref.plot( - data=POINTS_DATA, - R="/".join(map(str, region)), - J="X3i", - S="c0.5c", - C="cubhelix", - B="af", - ) - - fig_test.plot( - x=data[:, 0], - y=data[:, 1], - color=data[:, 2], - region=region, - projection="X3i", - style="c0.5c", - cmap="cubhelix", - frame="af", - ) - return fig_ref, fig_test - - -@pytest.mark.mpl_image_compare -def test_plot_sizes(data, region): - """ - Plot the data using z as sizes. - """ - fig = Figure() - fig.plot( - x=data[:, 0], - y=data[:, 1], - sizes=0.5 * data[:, 2], - region=region, - projection="X4i", - style="cc", - color="blue", - frame="af", - ) - return fig - - -@pytest.mark.mpl_image_compare -def test_plot_colors_sizes(data, region): - """ - Plot the data using z as sizes and colors. - """ - fig = Figure() - fig.plot( - x=data[:, 0], - y=data[:, 1], - color=data[:, 2], - sizes=0.5 * data[:, 2], - region=region, - projection="X3i", - style="cc", - cmap="copper", - frame="af", - ) - return fig - - -@pytest.mark.mpl_image_compare -def test_plot_colors_sizes_proj(data, region): - """ - Plot the data using z as sizes and colors with a projection. - """ - fig = Figure() - fig.coast(region=region, projection="M10i", frame="af", water="skyblue") - fig.plot( - x=data[:, 0], - y=data[:, 1], - color=data[:, 2], - sizes=0.5 * data[:, 2], - style="cc", - cmap="copper", - ) - return fig - - -@check_figures_equal() -def test_plot_transparency(): - """ - Plot the data with a constant transparency. - """ - x = np.arange(1, 10) - y = np.arange(1, 10) - - fig_ref, fig_test = Figure(), Figure() - # Use single-character arguments for the reference image - with GMTTempFile() as tmpfile: - np.savetxt(tmpfile.name, np.c_[x, y], fmt="%d") - fig_ref.plot( - data=tmpfile.name, S="c0.2c", G="blue", t=80.0, R="0/10/0/10", J="X4i", B="" - ) - - fig_test.plot( - x=x, - y=y, - region=[0, 10, 0, 10], - projection="X4i", - frame=True, - style="c0.2c", - color="blue", - transparency=80.0, - ) - return fig_ref, fig_test - - -@check_figures_equal() -def test_plot_varying_transparency(): - """ - Plot the data using z as transparency. - """ - x = np.arange(1, 10) - y = np.arange(1, 10) - z = np.arange(1, 10) * 10 - - fig_ref, fig_test = Figure(), Figure() - # Use single-character arguments for the reference image - with GMTTempFile() as tmpfile: - np.savetxt(tmpfile.name, np.c_[x, y, z], fmt="%d") - fig_ref.plot( - data=tmpfile.name, - R="0/10/0/10", - J="X4i", - B="", - S="c0.2c", - G="blue", - t="", - ) - - fig_test.plot( - x=x, - y=y, - region=[0, 10, 0, 10], - projection="X4i", - frame=True, - style="c0.2c", - color="blue", - transparency=z, - ) - return fig_ref, fig_test - - -@check_figures_equal() -def test_plot_sizes_colors_transparencies(): - """ - Plot the data with varying sizes and colors using z as transparency. - """ - x = np.arange(1.0, 10.0) - y = np.arange(1.0, 10.0) - color = np.arange(1, 10) * 0.15 - size = np.arange(1, 10) * 0.2 - transparency = np.arange(1, 10) * 10 - - fig_ref, fig_test = Figure(), Figure() - # Use single-character arguments for the reference image - with GMTTempFile() as tmpfile: - np.savetxt(tmpfile.name, np.c_[x, y, color, size, transparency]) - fig_ref.plot( - data=tmpfile.name, - R="0/10/0/10", - J="X4i", - B="", - S="cc", - C="gray", - t="", - ) - fig_test.plot( - x=x, - y=y, - region=[0, 10, 0, 10], - projection="X4i", - frame=True, - style="cc", - color=color, - sizes=size, - cmap="gray", - transparency=transparency, - ) - return fig_ref, fig_test - - -@pytest.mark.mpl_image_compare -def test_plot_matrix(data): - """ - Plot the data passing in a matrix and specifying columns. - """ - fig = Figure() - fig.plot( - data=data, - region=[10, 70, -5, 10], - projection="M10i", - style="cc", - color="#aaaaaa", - B="a", - columns="0,1,2+s0.005", - ) - return fig - - -@pytest.mark.mpl_image_compare -def test_plot_matrix_color(data): - """ - Plot the data passing in a matrix and using a colormap. - """ - fig = Figure() - fig.plot( - data=data, - region=[10, 70, -5, 10], - projection="X5i", - style="c0.5c", - cmap="rainbow", - B="a", - ) - return fig - - -@pytest.mark.mpl_image_compare -def test_plot_from_file(region): - """ - Plot using the data file name instead of loaded data. - """ - fig = Figure() - fig.plot( - data=POINTS_DATA, - region=region, - projection="X10i", - style="d1c", - color="yellow", - frame=True, - columns=[0, 1], - ) - return fig - - -@pytest.mark.mpl_image_compare -def test_plot_vectors(): - """ - Plot vectors. - """ - azimuth = np.array([0, 45, 90, 135, 180, 225, 270, 310]) - lengths = np.linspace(0.1, 1, len(azimuth)) - lon = np.sin(np.deg2rad(azimuth)) - lat = np.cos(np.deg2rad(azimuth)) - fig = Figure() - fig.plot( - x=lon, - y=lat, - direction=(azimuth, lengths), - region="-2/2/-2/2", - projection="X4i", - style="V0.2c+e", - color="black", - frame="af", - ) - return fig - - -@pytest.mark.mpl_image_compare -def test_plot_lines_with_arrows(): - """ - Plot lines with arrows. - - The test is slightly different from test_plot_vectors(). - Here the vectors are plotted as lines, with arrows at the end. - - The test also check if the API crashes. - See https://github.com/GenericMappingTools/pygmt/issues/406. - """ - fig = Figure() - fig.basemap(region=[-2, 2, -2, 2], frame=True) - fig.plot(x=[-1.0, -1.0], y=[-1.0, 1.0], pen="1p,black+ve0.2c") - fig.plot(x=[1.0, 1.0], y=[-1.0, 1.0], pen="1p,black+ve0.2c") - return fig - - -@pytest.mark.mpl_image_compare -def test_plot_scalar_xy(): - """ - Plot symbols given scalar x, y coordinates. - """ - fig = Figure() - fig.basemap(region=[-2, 2, -2, 2], frame=True) - fig.plot(x=-1.5, y=1.5, style="c1c") - fig.plot(x=0, y=0, style="t1c") - fig.plot(x=1.5, y=-1.5, style="s1c") - return fig - - -@pytest.mark.mpl_image_compare -def test_plot_datetime(): - """ - Test various datetime input data. - """ - fig = Figure() - fig.basemap( - projection="X15c/5c", - region=[ - np.array("2010-01-01T00:00:00", dtype=np.datetime64), - pd.Timestamp("2020-01-01"), - 0, - 10, - ], - frame=True, - ) - - # numpy.datetime64 types - x = np.array( - ["2010-06-01", "2011-06-01T12", "2012-01-01T12:34:56"], dtype="datetime64" - ) - y = [1.0, 2.0, 3.0] - fig.plot(x, y, style="c0.2c", pen="1p") - - # pandas.DatetimeIndex - x = pd.date_range("2013", freq="YS", periods=3) - y = [4, 5, 6] - fig.plot(x, y, style="t0.2c", pen="1p") - - # xarray.DataArray - x = xr.DataArray(data=pd.date_range(start="2015-03", freq="QS", periods=3)) - y = [7.5, 6, 4.5] - fig.plot(x, y, style="s0.2c", pen="1p") - - # raw datetime strings - x = ["2016-02-01", "2017-03-04T00:00"] - y = [7, 8] - fig.plot(x, y, style="a0.2c", pen="1p") - - # the Python built-in datetime and date - x = [datetime.date(2018, 1, 1), datetime.datetime(2019, 1, 1)] - y = [8.5, 9.5] - fig.plot(x, y, style="i0.2c", pen="1p") - return fig +# pylint: disable=redefined-outer-name +""" +Tests plot. +""" +import datetime +import os + +import numpy as np +import pandas as pd +import pytest +import xarray as xr +from pygmt import Figure +from pygmt.exceptions import GMTInvalidInput +from pygmt.helpers import GMTTempFile +from pygmt.helpers.testing import check_figures_equal + +TEST_DATA_DIR = os.path.join(os.path.dirname(__file__), "data") +POINTS_DATA = os.path.join(TEST_DATA_DIR, "points.txt") + + +@pytest.fixture(scope="module") +def data(): + """ + Load the point data from the test file. + """ + return np.loadtxt(POINTS_DATA) + + +@pytest.fixture(scope="module") +def region(): + """ + The data region. + """ + return [10, 70, -5, 10] + + +@pytest.mark.mpl_image_compare +def test_plot_red_circles(data, region): + """ + Plot the data in red circles passing in vectors. + """ + fig = Figure() + fig.plot( + x=data[:, 0], + y=data[:, 1], + region=region, + projection="X4i", + style="c0.2c", + color="red", + frame="afg", + ) + return fig + + +def test_plot_fail_no_data(data): + """ + Plot should raise an exception if no data is given. + """ + fig = Figure() + with pytest.raises(GMTInvalidInput): + fig.plot( + region=region, projection="X4i", style="c0.2c", color="red", frame="afg" + ) + with pytest.raises(GMTInvalidInput): + fig.plot( + x=data[:, 0], + region=region, + projection="X4i", + style="c0.2c", + color="red", + frame="afg", + ) + with pytest.raises(GMTInvalidInput): + fig.plot( + y=data[:, 0], + region=region, + projection="X4i", + style="c0.2c", + color="red", + frame="afg", + ) + # Should also fail if given too much data + with pytest.raises(GMTInvalidInput): + fig.plot( + x=data[:, 0], + y=data[:, 1], + data=data, + region=region, + projection="X4i", + style="c0.2c", + color="red", + frame="afg", + ) + + +def test_plot_fail_size_color(data): + """ + Should raise an exception if array sizes and color are used with matrix. + """ + fig = Figure() + with pytest.raises(GMTInvalidInput): + fig.plot( + data=data, + region=region, + projection="X4i", + style="c0.2c", + color=data[:, 2], + frame="afg", + ) + with pytest.raises(GMTInvalidInput): + fig.plot( + data=data, + region=region, + projection="X4i", + style="cc", + sizes=data[:, 2], + color="red", + frame="afg", + ) + + +@check_figures_equal() +def test_plot_projection(data): + """ + Plot the data in green squares with a projection. + """ + fig_ref, fig_test = Figure(), Figure() + fig_ref.plot( + data=POINTS_DATA, + R="g", + J="R270/4i", + S="s0.2c", + G="green", + B="ag", + ) + fig_test.plot( + x=data[:, 0], + y=data[:, 1], + region="g", + projection="R270/4i", + style="s0.2c", + color="green", + frame="ag", + ) + return fig_ref, fig_test + + +@check_figures_equal() +def test_plot_colors(data, region): + """ + Plot the data using z as colors. + """ + fig_ref, fig_test = Figure(), Figure() + # Use single-character arguments for the reference image + fig_ref.plot( + data=POINTS_DATA, + R="/".join(map(str, region)), + J="X3i", + S="c0.5c", + C="cubhelix", + B="af", + ) + + fig_test.plot( + x=data[:, 0], + y=data[:, 1], + color=data[:, 2], + region=region, + projection="X3i", + style="c0.5c", + cmap="cubhelix", + frame="af", + ) + return fig_ref, fig_test + + +@pytest.mark.mpl_image_compare +def test_plot_sizes(data, region): + """ + Plot the data using z as sizes. + """ + fig = Figure() + fig.plot( + x=data[:, 0], + y=data[:, 1], + sizes=0.5 * data[:, 2], + region=region, + projection="X4i", + style="cc", + color="blue", + frame="af", + ) + return fig + + +@pytest.mark.mpl_image_compare +def test_plot_colors_sizes(data, region): + """ + Plot the data using z as sizes and colors. + """ + fig = Figure() + fig.plot( + x=data[:, 0], + y=data[:, 1], + color=data[:, 2], + sizes=0.5 * data[:, 2], + region=region, + projection="X3i", + style="cc", + cmap="copper", + frame="af", + ) + return fig + + +@pytest.mark.mpl_image_compare +def test_plot_colors_sizes_proj(data, region): + """ + Plot the data using z as sizes and colors with a projection. + """ + fig = Figure() + fig.coast(region=region, projection="M10i", frame="af", water="skyblue") + fig.plot( + x=data[:, 0], + y=data[:, 1], + color=data[:, 2], + sizes=0.5 * data[:, 2], + style="cc", + cmap="copper", + ) + return fig + + +@check_figures_equal() +def test_plot_transparency(): + """ + Plot the data with a constant transparency. + """ + x = np.arange(1, 10) + y = np.arange(1, 10) + + fig_ref, fig_test = Figure(), Figure() + # Use single-character arguments for the reference image + with GMTTempFile() as tmpfile: + np.savetxt(tmpfile.name, np.c_[x, y], fmt="%d") + fig_ref.plot( + data=tmpfile.name, S="c0.2c", G="blue", t=80.0, R="0/10/0/10", J="X4i", B="" + ) + + fig_test.plot( + x=x, + y=y, + region=[0, 10, 0, 10], + projection="X4i", + frame=True, + style="c0.2c", + color="blue", + transparency=80.0, + ) + return fig_ref, fig_test + + +@check_figures_equal() +def test_plot_varying_transparency(): + """ + Plot the data using z as transparency. + """ + x = np.arange(1, 10) + y = np.arange(1, 10) + z = np.arange(1, 10) * 10 + + fig_ref, fig_test = Figure(), Figure() + # Use single-character arguments for the reference image + with GMTTempFile() as tmpfile: + np.savetxt(tmpfile.name, np.c_[x, y, z], fmt="%d") + fig_ref.plot( + data=tmpfile.name, + R="0/10/0/10", + J="X4i", + B="", + S="c0.2c", + G="blue", + t="", + ) + + fig_test.plot( + x=x, + y=y, + region=[0, 10, 0, 10], + projection="X4i", + frame=True, + style="c0.2c", + color="blue", + transparency=z, + ) + return fig_ref, fig_test + + +@check_figures_equal() +def test_plot_sizes_colors_transparencies(): + """ + Plot the data with varying sizes and colors using z as transparency. + """ + x = np.arange(1.0, 10.0) + y = np.arange(1.0, 10.0) + color = np.arange(1, 10) * 0.15 + size = np.arange(1, 10) * 0.2 + transparency = np.arange(1, 10) * 10 + + fig_ref, fig_test = Figure(), Figure() + # Use single-character arguments for the reference image + with GMTTempFile() as tmpfile: + np.savetxt(tmpfile.name, np.c_[x, y, color, size, transparency]) + fig_ref.plot( + data=tmpfile.name, + R="0/10/0/10", + J="X4i", + B="", + S="cc", + C="gray", + t="", + ) + fig_test.plot( + x=x, + y=y, + region=[0, 10, 0, 10], + projection="X4i", + frame=True, + style="cc", + color=color, + sizes=size, + cmap="gray", + transparency=transparency, + ) + return fig_ref, fig_test + + +@pytest.mark.mpl_image_compare +def test_plot_matrix(data): + """ + Plot the data passing in a matrix and specifying columns. + """ + fig = Figure() + fig.plot( + data=data, + region=[10, 70, -5, 10], + projection="M10i", + style="cc", + color="#aaaaaa", + B="a", + columns="0,1,2+s0.005", + ) + return fig + + +@pytest.mark.mpl_image_compare +def test_plot_matrix_color(data): + """ + Plot the data passing in a matrix and using a colormap. + """ + fig = Figure() + fig.plot( + data=data, + region=[10, 70, -5, 10], + projection="X5i", + style="c0.5c", + cmap="rainbow", + B="a", + ) + return fig + + +@pytest.mark.mpl_image_compare +def test_plot_from_file(region): + """ + Plot using the data file name instead of loaded data. + """ + fig = Figure() + fig.plot( + data=POINTS_DATA, + region=region, + projection="X10i", + style="d1c", + color="yellow", + frame=True, + columns=[0, 1], + ) + return fig + + +@pytest.mark.mpl_image_compare +def test_plot_vectors(): + """ + Plot vectors. + """ + azimuth = np.array([0, 45, 90, 135, 180, 225, 270, 310]) + lengths = np.linspace(0.1, 1, len(azimuth)) + lon = np.sin(np.deg2rad(azimuth)) + lat = np.cos(np.deg2rad(azimuth)) + fig = Figure() + fig.plot( + x=lon, + y=lat, + direction=(azimuth, lengths), + region="-2/2/-2/2", + projection="X4i", + style="V0.2c+e", + color="black", + frame="af", + ) + return fig + + +@pytest.mark.mpl_image_compare +def test_plot_lines_with_arrows(): + """ + Plot lines with arrows. + + The test is slightly different from test_plot_vectors(). + Here the vectors are plotted as lines, with arrows at the end. + + The test also check if the API crashes. + See https://github.com/GenericMappingTools/pygmt/issues/406. + """ + fig = Figure() + fig.basemap(region=[-2, 2, -2, 2], frame=True) + fig.plot(x=[-1.0, -1.0], y=[-1.0, 1.0], pen="1p,black+ve0.2c") + fig.plot(x=[1.0, 1.0], y=[-1.0, 1.0], pen="1p,black+ve0.2c") + return fig + + +@pytest.mark.mpl_image_compare +def test_plot_scalar_xy(): + """ + Plot symbols given scalar x, y coordinates. + """ + fig = Figure() + fig.basemap(region=[-2, 2, -2, 2], frame=True) + fig.plot(x=-1.5, y=1.5, style="c1c") + fig.plot(x=0, y=0, style="t1c") + fig.plot(x=1.5, y=-1.5, style="s1c") + return fig + + +@pytest.mark.mpl_image_compare +def test_plot_datetime(): + """ + Test various datetime input data. + """ + fig = Figure() + fig.basemap( + projection="X15c/5c", + region=[ + np.array("2010-01-01T00:00:00", dtype=np.datetime64), + pd.Timestamp("2020-01-01"), + 0, + 10, + ], + frame=True, + ) + + # numpy.datetime64 types + x = np.array( + ["2010-06-01", "2011-06-01T12", "2012-01-01T12:34:56"], dtype="datetime64" + ) + y = [1.0, 2.0, 3.0] + fig.plot(x, y, style="c0.2c", pen="1p") + + # pandas.DatetimeIndex + x = pd.date_range("2013", freq="YS", periods=3) + y = [4, 5, 6] + fig.plot(x, y, style="t0.2c", pen="1p") + + # xarray.DataArray + x = xr.DataArray(data=pd.date_range(start="2015-03", freq="QS", periods=3)) + y = [7.5, 6, 4.5] + fig.plot(x, y, style="s0.2c", pen="1p") + + # raw datetime strings + x = ["2016-02-01", "2017-03-04T00:00"] + y = [7, 8] + fig.plot(x, y, style="a0.2c", pen="1p") + + # the Python built-in datetime and date + x = [datetime.date(2018, 1, 1), datetime.datetime(2019, 1, 1)] + y = [8.5, 9.5] + fig.plot(x, y, style="i0.2c", pen="1p") + return fig diff --git a/pygmt/tests/test_plot3d.py b/pygmt/tests/test_plot3d.py index 82630af48c5..73deb5b0980 100644 --- a/pygmt/tests/test_plot3d.py +++ b/pygmt/tests/test_plot3d.py @@ -1,620 +1,620 @@ -""" -Tests plot3d. -""" -import os - -import numpy as np -import pytest -from pygmt import Figure -from pygmt.exceptions import GMTInvalidInput -from pygmt.helpers import GMTTempFile -from pygmt.helpers.testing import check_figures_equal - -TEST_DATA_DIR = os.path.join(os.path.dirname(__file__), "data") -POINTS_DATA = os.path.join(TEST_DATA_DIR, "points.txt") - - -@pytest.fixture(scope="module", name="data") -def fixture_data(): - """ - Load the point data from the test file. - """ - return np.loadtxt(POINTS_DATA) - - -@pytest.fixture(scope="module", name="region") -def fixture_region(): - """ - The data region. - """ - return [10, 70, -5, 10, 0, 1] - - -@check_figures_equal() -def test_plot3d_red_circles_zscale(data, region): - "Plot the 3D data in red circles passing in vectors and setting zscale = 5" - fig_ref, fig_test = Figure(), Figure() - fig_ref.plot3d( - data=POINTS_DATA, - Jz=5, - p="225/30", - R="/".join(map(str, region)), - J="X4i", - S="c0.2c", - G="red", - B=["afg", "zafg"], - ) - fig_test.plot3d( - x=data[:, 0], - y=data[:, 1], - z=data[:, 2], - zscale=5, - perspective=[225, 30], - region=region, - projection="X4i", - style="c0.2c", - color="red", - frame=["afg", "zafg"], - ) - return fig_ref, fig_test - - -@check_figures_equal() -def test_plot3d_red_circles_zsize(data, region): - "Plot the 3D data in red circles passing in vectors and setting zsize = 3i" - fig_ref, fig_test = Figure(), Figure() - fig_ref.plot3d( - data=POINTS_DATA, - JZ="3i", - p="225/30", - R="/".join(map(str, region)), - J="X4i", - S="c0.2c", - G="red", - B=["afg", "zafg"], - ) - fig_test.plot3d( - x=data[:, 0], - y=data[:, 1], - z=data[:, 2], - zsize="3i", - perspective=[225, 30], - region=region, - projection="X4i", - style="c0.2c", - color="red", - frame=["afg", "zafg"], - ) - return fig_ref, fig_test - - -def test_plot3d_fail_no_data(data, region): - """ - Plot should raise an exception if no data is given. - """ - fig = Figure() - with pytest.raises(GMTInvalidInput): - fig.plot3d( - region=region, projection="X4i", style="c0.2c", color="red", frame="afg" - ) - with pytest.raises(GMTInvalidInput): - fig.plot3d( - x=data[:, 0], - region=region, - projection="X4i", - style="c0.2c", - color="red", - frame="afg", - ) - with pytest.raises(GMTInvalidInput): - fig.plot3d( - y=data[:, 0], - region=region, - projection="X4i", - style="c0.2c", - color="red", - frame="afg", - ) - # Should also fail if given too much data - with pytest.raises(GMTInvalidInput): - fig.plot3d( - x=data[:, 0], - y=data[:, 1], - z=data[:, 2], - data=data, - region=region, - projection="X4i", - style="c0.2c", - color="red", - frame="afg", - ) - - -def test_plot3d_fail_size_color(data, region): - """ - Should raise an exception if array sizes and color are used with matrix. - """ - fig = Figure() - with pytest.raises(GMTInvalidInput): - fig.plot3d( - data=data, - region=region, - projection="X4i", - style="c0.2c", - color=data[:, 2], - frame="afg", - ) - with pytest.raises(GMTInvalidInput): - fig.plot3d( - data=data, - region=region, - projection="X4i", - style="cc", - sizes=data[:, 2], - color="red", - frame="afg", - ) - - -@check_figures_equal() -def test_plot3d_projection(data, region): - """ - Plot the data in green squares with a projection. - """ - fig_ref, fig_test = Figure(), Figure() - fig_ref.plot3d( - data=POINTS_DATA, - Jz=5, - p="225/30", - R="/".join(map(str, region)), - J="R270/4i", - S="s1c", - G="green", - B=["ag", "zag"], - ) - fig_test.plot3d( - x=data[:, 0], - y=data[:, 1], - z=data[:, 2], - zscale=5, - perspective=[225, 30], - region=region, - projection="R270/4i", - style="s1c", - color="green", - frame=["ag", "zag"], - ) - return fig_ref, fig_test - - -@check_figures_equal() -def test_plot3d_colors(data, region): - """ - Plot the data using z as colors. - """ - fig_ref, fig_test = Figure(), Figure() - fig_ref.plot3d( - data=POINTS_DATA, - Jz=5, - p="225/30", - G="+z", - R="/".join(map(str, region)), - J="X3i", - S="c0.5c", - C="cubhelix", - B=["afg", "zafg"], - i="0,1,2,2", - ) - fig_test.plot3d( - x=data[:, 0], - y=data[:, 1], - z=data[:, 2], - zscale=5, - perspective=[225, 30], - color=data[:, 2], - region=region, - projection="X3i", - style="c0.5c", - cmap="cubhelix", - frame=["afg", "zafg"], - ) - return fig_ref, fig_test - - -@check_figures_equal() -def test_plot3d_sizes(data, region): - """ - Plot the data using z as sizes. - """ - fig_ref, fig_test = Figure(), Figure() - fig_ref.plot3d( - data=POINTS_DATA, - Jz=5, - p="225/30", - i="0,1,2,2+s0.5", - R="/".join(map(str, region)), - J="X4i", - S="ui", - G="blue", - B=["af", "zaf"], - ) - fig_test.plot3d( - x=data[:, 0], - y=data[:, 1], - z=data[:, 2], - zscale=5, - perspective=[225, 30], - sizes=0.5 * data[:, 2], - region=region, - projection="X4i", - # Using inches instead of cm because of upstream bug at - # https://github.com/GenericMappingTools/gmt/issues/4386 - style="ui", - color="blue", - frame=["af", "zaf"], - ) - return fig_ref, fig_test - - -@check_figures_equal() -def test_plot3d_colors_sizes(data, region): - """ - Plot the data using z as sizes and colors. - """ - fig_ref, fig_test = Figure(), Figure() - fig_ref.plot3d( - data=POINTS_DATA, - Jz=5, - p="225/30", - i="0,1,2,2,2+s0.5", - R="/".join(map(str, region)), - J="X3i", - S="ui", - C="copper", - B=["af", "zaf"], - ) - fig_test.plot3d( - x=data[:, 0], - y=data[:, 1], - z=data[:, 2], - zscale=5, - perspective=[225, 30], - color=data[:, 2], - sizes=0.5 * data[:, 2], - region=region, - projection="X3i", - # Using inches instead of cm because of upstream bug at - # https://github.com/GenericMappingTools/gmt/issues/4386 - style="ui", - cmap="copper", - frame=["af", "zaf"], - ) - return fig_ref, fig_test - - -@check_figures_equal() -def test_plot3d_colors_sizes_proj(data, region): - """ - Plot the data using z as sizes and colors with a projection. - """ - fig_ref, fig_test = Figure(), Figure() - fig_ref.plot3d( - data=POINTS_DATA, - Jz=5, - p="225/30", - R="/".join(map(str, region)), - J="M10i", - B=["af", "zaf"], - G="+z", - i="0,1,2,2,2+s1", - S="ui", - C="copper", - ) - fig_test.plot3d( - x=data[:, 0], - y=data[:, 1], - z=data[:, 2], - zscale=5, - perspective=[225, 30], - region=region, - projection="M10i", - frame=["af", "zaf"], - color=data[:, 2], - sizes=data[:, 2], - # Using inches instead of cm because of upstream bug at - # https://github.com/GenericMappingTools/gmt/issues/4386 - style="ui", - cmap="copper", - ) - return fig_ref, fig_test - - -@check_figures_equal() -def test_plot3d_transparency(): - """ - Plot the data with a constant transparency. - """ - x = np.arange(1, 10) - y = np.arange(1, 10) - z = np.arange(1, 10) * 10 - - fig_ref, fig_test = Figure(), Figure() - # Use single-character arguments for the reference image - with GMTTempFile() as tmpfile: - np.savetxt(tmpfile.name, np.c_[x, y, z], fmt="%d") - fig_ref.plot3d( - data=tmpfile.name, - S="u0.2c", - G="blue", - R="0/10/0/10/10/90", - J="X4i", - Jz=0.1, - B="", - p="135/30", - t=80.0, - ) - - fig_test.plot3d( - x=x, - y=y, - z=z, - style="u0.2c", - color="blue", - region=[0, 10, 0, 10, 10, 90], - projection="X4i", - zscale=0.1, - frame=True, - perspective=[135, 30], - transparency=80.0, - ) - return fig_ref, fig_test - - -@check_figures_equal() -def test_plot3d_varying_transparency(): - """ - Plot the data using z as transparency using 3-D column symbols. - """ - x = np.arange(1, 10) - y = np.arange(1, 10) - z = np.arange(1, 10) * 10 - - fig_ref, fig_test = Figure(), Figure() - # Use single-character arguments for the reference image - with GMTTempFile() as tmpfile: - np.savetxt(tmpfile.name, np.c_[x, y, z, z, z], fmt="%d") - fig_ref.plot3d( - data=tmpfile.name, - S="o0.2c+B5", - G="blue", - R="0/10/0/10/10/90", - J="X4i", - Jz=0.1, - B="", - p="135/30", - t="", - ) - fig_test.plot3d( - x=x, - y=y, - z=z, - style="o0.2c+B5", - color="blue", - region=[0, 10, 0, 10, 10, 90], - projection="X4i", - zscale=0.1, - frame=True, - perspective=[135, 30], - transparency=z, - ) - return fig_ref, fig_test - - -@check_figures_equal() -def test_plot3d_sizes_colors_transparencies(): - """ - Plot the data with varying sizes and colors using z as transparency. - """ - x = np.arange(1.0, 10.0) - y = np.arange(1.0, 10.0) - z = np.arange(1, 10) * 10 - color = np.arange(1, 10) * 0.15 - size = np.arange(1, 10) * 0.2 - transparency = np.arange(1, 10) * 10 - - fig_ref, fig_test = Figure(), Figure() - # Use single-character arguments for the reference image - with GMTTempFile() as tmpfile: - np.savetxt(tmpfile.name, np.c_[x, y, z, color, size, transparency]) - fig_ref.plot3d( - data=tmpfile.name, - R="0/10/0/10/10/90", - J="X4i", - Jz=0.1, - p="135/30", - B="", - S="uc", - C="gray", - t="", - ) - fig_test.plot3d( - x=x, - y=y, - z=z, - region=[0, 10, 0, 10, 10, 90], - projection="X4i", - zscale=0.1, - perspective=[135, 30], - frame=True, - style="uc", - color=color, - sizes=size, - cmap="gray", - transparency=transparency, - ) - return fig_ref, fig_test - - -@check_figures_equal() -def test_plot3d_matrix(data, region): - """ - Plot the data passing in a matrix and specifying columns. - """ - fig_ref, fig_test = Figure(), Figure() - fig_ref.plot3d( - data=POINTS_DATA, - Jz=5, - p="225/30", - R="/".join(map(str, region)), - J="M10i", - S="c1c", - G="#aaaaaa", - B=["a", "za"], - i="0,1,2", - ) - fig_test.plot3d( - data=data, - zscale=5, - perspective=[225, 30], - region=region, - projection="M10i", - style="c1c", - color="#aaaaaa", - frame=["a", "za"], - columns="0,1,2", - ) - return fig_ref, fig_test - - -@check_figures_equal() -def test_plot3d_matrix_color(data, region): - """ - Plot the data passing in a matrix and using a colormap. - """ - fig_ref, fig_test = Figure(), Figure() - fig_ref.plot3d( - data=POINTS_DATA, - Jz=5, - p="225/30", - R="/".join(map(str, region)), - J="X5i", - S="c0.5c", - C="rainbow", - i="0,1,2,2", - B=["a", "za"], - ) - fig_test.plot3d( - data=data, - zscale=5, - perspective=[225, 30], - region=region, - projection="X5i", - style="c0.5c", - cmap="rainbow", - columns=[0, 1, 2, 2], - frame=["a", "za"], - ) - return fig_ref, fig_test - - -@check_figures_equal() -def test_plot3d_from_file(region): - """ - Plot using the data file name instead of loaded data. - """ - fig_ref, fig_test = Figure(), Figure() - fig_ref.plot3d( - data=POINTS_DATA, - Jz=5, - p="225/30", - R="/".join(map(str, region)), - J="X10i", - S="d1c", - G="yellow", - B=["af", "zaf"], - i="0,1,2", - ) - fig_test.plot3d( - data=POINTS_DATA, - zscale=5, - perspective=[225, 30], - region=region, - projection="X10i", - style="d1c", - color="yellow", - frame=["af", "zaf"], - columns=[0, 1, 2], - ) - return fig_ref, fig_test - - -@check_figures_equal() -def test_plot3d_vectors(): - """ - Plot vectors. - """ - azimuth = np.array([0, 45, 90, 135, 180, 225, 270, 310]) - lengths = np.linspace(0.1, 1, len(azimuth)) - lon = np.sin(np.deg2rad(azimuth)) - lat = np.cos(np.deg2rad(azimuth)) - elev = np.tan(np.deg2rad(azimuth)) - fig_ref, fig_test = Figure(), Figure() - with GMTTempFile() as tmpfile: - np.savetxt(tmpfile.name, np.c_[lon, lat, elev, azimuth, lengths]) - fig_ref.plot3d( - data=tmpfile.name, - Jz=2, - p="225/30", - R="-2/2/-2/2/-2/2", - J="X4i", - S="V1c+e", - G="black", - B=["af", "zaf"], - ) - fig_test.plot3d( - x=lon, - y=lat, - z=elev, - zscale=2, - perspective=[225, 30], - direction=(azimuth, lengths), - region=[-2, 2, -2, 2, -2, 2], - projection="X4i", - style="V1c+e", - color="black", - frame=["af", "zaf"], - ) - return fig_ref, fig_test - - -@check_figures_equal() -def test_plot3d_scalar_xyz(): - """ - Plot symbols given scalar x, y, z coordinates. - """ - fig_ref, fig_test = Figure(), Figure() - with GMTTempFile() as tmpfile: - np.savetxt(tmpfile.name, np.c_[[-1.5, 0, 1.5], [1.5, 0, -1.5], [-1.5, 0, 1.5]]) - fig_ref.basemap( - R="-2/2/-2/2/-2/2", B=["xaf+lx", "yaf+ly", "zaf+lz"], Jz=2, p="225/30" - ) - fig_ref.plot3d(data=tmpfile.name, S="c1c", G="red", Jz="", p="", qi=0) - fig_ref.plot3d(data=tmpfile.name, S="t1c", G="green", Jz="", p="", qi=1) - fig_ref.plot3d(data=tmpfile.name, S="s1c", G="blue", Jz="", p="", qi=2) - - fig_test.basemap( - region=[-2, 2, -2, 2, -2, 2], - frame=["xaf+lx", "yaf+ly", "zaf+lz"], - zscale=2, - perspective=[225, 30], - ) - fig_test.plot3d( - x=-1.5, y=1.5, z=-1.5, style="c1c", color="red", zscale=True, perspective=True - ) - fig_test.plot3d( - x=0, y=0, z=0, style="t1c", color="green", zscale=True, perspective=True - ) - fig_test.plot3d( - x=1.5, y=-1.5, z=1.5, style="s1c", color="blue", zscale=True, perspective=True - ) - return fig_ref, fig_test +""" +Tests plot3d. +""" +import os + +import numpy as np +import pytest +from pygmt import Figure +from pygmt.exceptions import GMTInvalidInput +from pygmt.helpers import GMTTempFile +from pygmt.helpers.testing import check_figures_equal + +TEST_DATA_DIR = os.path.join(os.path.dirname(__file__), "data") +POINTS_DATA = os.path.join(TEST_DATA_DIR, "points.txt") + + +@pytest.fixture(scope="module", name="data") +def fixture_data(): + """ + Load the point data from the test file. + """ + return np.loadtxt(POINTS_DATA) + + +@pytest.fixture(scope="module", name="region") +def fixture_region(): + """ + The data region. + """ + return [10, 70, -5, 10, 0, 1] + + +@check_figures_equal() +def test_plot3d_red_circles_zscale(data, region): + "Plot the 3D data in red circles passing in vectors and setting zscale = 5" + fig_ref, fig_test = Figure(), Figure() + fig_ref.plot3d( + data=POINTS_DATA, + Jz=5, + p="225/30", + R="/".join(map(str, region)), + J="X4i", + S="c0.2c", + G="red", + B=["afg", "zafg"], + ) + fig_test.plot3d( + x=data[:, 0], + y=data[:, 1], + z=data[:, 2], + zscale=5, + perspective=[225, 30], + region=region, + projection="X4i", + style="c0.2c", + color="red", + frame=["afg", "zafg"], + ) + return fig_ref, fig_test + + +@check_figures_equal() +def test_plot3d_red_circles_zsize(data, region): + "Plot the 3D data in red circles passing in vectors and setting zsize = 3i" + fig_ref, fig_test = Figure(), Figure() + fig_ref.plot3d( + data=POINTS_DATA, + JZ="3i", + p="225/30", + R="/".join(map(str, region)), + J="X4i", + S="c0.2c", + G="red", + B=["afg", "zafg"], + ) + fig_test.plot3d( + x=data[:, 0], + y=data[:, 1], + z=data[:, 2], + zsize="3i", + perspective=[225, 30], + region=region, + projection="X4i", + style="c0.2c", + color="red", + frame=["afg", "zafg"], + ) + return fig_ref, fig_test + + +def test_plot3d_fail_no_data(data, region): + """ + Plot should raise an exception if no data is given. + """ + fig = Figure() + with pytest.raises(GMTInvalidInput): + fig.plot3d( + region=region, projection="X4i", style="c0.2c", color="red", frame="afg" + ) + with pytest.raises(GMTInvalidInput): + fig.plot3d( + x=data[:, 0], + region=region, + projection="X4i", + style="c0.2c", + color="red", + frame="afg", + ) + with pytest.raises(GMTInvalidInput): + fig.plot3d( + y=data[:, 0], + region=region, + projection="X4i", + style="c0.2c", + color="red", + frame="afg", + ) + # Should also fail if given too much data + with pytest.raises(GMTInvalidInput): + fig.plot3d( + x=data[:, 0], + y=data[:, 1], + z=data[:, 2], + data=data, + region=region, + projection="X4i", + style="c0.2c", + color="red", + frame="afg", + ) + + +def test_plot3d_fail_size_color(data, region): + """ + Should raise an exception if array sizes and color are used with matrix. + """ + fig = Figure() + with pytest.raises(GMTInvalidInput): + fig.plot3d( + data=data, + region=region, + projection="X4i", + style="c0.2c", + color=data[:, 2], + frame="afg", + ) + with pytest.raises(GMTInvalidInput): + fig.plot3d( + data=data, + region=region, + projection="X4i", + style="cc", + sizes=data[:, 2], + color="red", + frame="afg", + ) + + +@check_figures_equal() +def test_plot3d_projection(data, region): + """ + Plot the data in green squares with a projection. + """ + fig_ref, fig_test = Figure(), Figure() + fig_ref.plot3d( + data=POINTS_DATA, + Jz=5, + p="225/30", + R="/".join(map(str, region)), + J="R270/4i", + S="s1c", + G="green", + B=["ag", "zag"], + ) + fig_test.plot3d( + x=data[:, 0], + y=data[:, 1], + z=data[:, 2], + zscale=5, + perspective=[225, 30], + region=region, + projection="R270/4i", + style="s1c", + color="green", + frame=["ag", "zag"], + ) + return fig_ref, fig_test + + +@check_figures_equal() +def test_plot3d_colors(data, region): + """ + Plot the data using z as colors. + """ + fig_ref, fig_test = Figure(), Figure() + fig_ref.plot3d( + data=POINTS_DATA, + Jz=5, + p="225/30", + G="+z", + R="/".join(map(str, region)), + J="X3i", + S="c0.5c", + C="cubhelix", + B=["afg", "zafg"], + i="0,1,2,2", + ) + fig_test.plot3d( + x=data[:, 0], + y=data[:, 1], + z=data[:, 2], + zscale=5, + perspective=[225, 30], + color=data[:, 2], + region=region, + projection="X3i", + style="c0.5c", + cmap="cubhelix", + frame=["afg", "zafg"], + ) + return fig_ref, fig_test + + +@check_figures_equal() +def test_plot3d_sizes(data, region): + """ + Plot the data using z as sizes. + """ + fig_ref, fig_test = Figure(), Figure() + fig_ref.plot3d( + data=POINTS_DATA, + Jz=5, + p="225/30", + i="0,1,2,2+s0.5", + R="/".join(map(str, region)), + J="X4i", + S="ui", + G="blue", + B=["af", "zaf"], + ) + fig_test.plot3d( + x=data[:, 0], + y=data[:, 1], + z=data[:, 2], + zscale=5, + perspective=[225, 30], + sizes=0.5 * data[:, 2], + region=region, + projection="X4i", + # Using inches instead of cm because of upstream bug at + # https://github.com/GenericMappingTools/gmt/issues/4386 + style="ui", + color="blue", + frame=["af", "zaf"], + ) + return fig_ref, fig_test + + +@check_figures_equal() +def test_plot3d_colors_sizes(data, region): + """ + Plot the data using z as sizes and colors. + """ + fig_ref, fig_test = Figure(), Figure() + fig_ref.plot3d( + data=POINTS_DATA, + Jz=5, + p="225/30", + i="0,1,2,2,2+s0.5", + R="/".join(map(str, region)), + J="X3i", + S="ui", + C="copper", + B=["af", "zaf"], + ) + fig_test.plot3d( + x=data[:, 0], + y=data[:, 1], + z=data[:, 2], + zscale=5, + perspective=[225, 30], + color=data[:, 2], + sizes=0.5 * data[:, 2], + region=region, + projection="X3i", + # Using inches instead of cm because of upstream bug at + # https://github.com/GenericMappingTools/gmt/issues/4386 + style="ui", + cmap="copper", + frame=["af", "zaf"], + ) + return fig_ref, fig_test + + +@check_figures_equal() +def test_plot3d_colors_sizes_proj(data, region): + """ + Plot the data using z as sizes and colors with a projection. + """ + fig_ref, fig_test = Figure(), Figure() + fig_ref.plot3d( + data=POINTS_DATA, + Jz=5, + p="225/30", + R="/".join(map(str, region)), + J="M10i", + B=["af", "zaf"], + G="+z", + i="0,1,2,2,2+s1", + S="ui", + C="copper", + ) + fig_test.plot3d( + x=data[:, 0], + y=data[:, 1], + z=data[:, 2], + zscale=5, + perspective=[225, 30], + region=region, + projection="M10i", + frame=["af", "zaf"], + color=data[:, 2], + sizes=data[:, 2], + # Using inches instead of cm because of upstream bug at + # https://github.com/GenericMappingTools/gmt/issues/4386 + style="ui", + cmap="copper", + ) + return fig_ref, fig_test + + +@check_figures_equal() +def test_plot3d_transparency(): + """ + Plot the data with a constant transparency. + """ + x = np.arange(1, 10) + y = np.arange(1, 10) + z = np.arange(1, 10) * 10 + + fig_ref, fig_test = Figure(), Figure() + # Use single-character arguments for the reference image + with GMTTempFile() as tmpfile: + np.savetxt(tmpfile.name, np.c_[x, y, z], fmt="%d") + fig_ref.plot3d( + data=tmpfile.name, + S="u0.2c", + G="blue", + R="0/10/0/10/10/90", + J="X4i", + Jz=0.1, + B="", + p="135/30", + t=80.0, + ) + + fig_test.plot3d( + x=x, + y=y, + z=z, + style="u0.2c", + color="blue", + region=[0, 10, 0, 10, 10, 90], + projection="X4i", + zscale=0.1, + frame=True, + perspective=[135, 30], + transparency=80.0, + ) + return fig_ref, fig_test + + +@check_figures_equal() +def test_plot3d_varying_transparency(): + """ + Plot the data using z as transparency using 3-D column symbols. + """ + x = np.arange(1, 10) + y = np.arange(1, 10) + z = np.arange(1, 10) * 10 + + fig_ref, fig_test = Figure(), Figure() + # Use single-character arguments for the reference image + with GMTTempFile() as tmpfile: + np.savetxt(tmpfile.name, np.c_[x, y, z, z, z], fmt="%d") + fig_ref.plot3d( + data=tmpfile.name, + S="o0.2c+B5", + G="blue", + R="0/10/0/10/10/90", + J="X4i", + Jz=0.1, + B="", + p="135/30", + t="", + ) + fig_test.plot3d( + x=x, + y=y, + z=z, + style="o0.2c+B5", + color="blue", + region=[0, 10, 0, 10, 10, 90], + projection="X4i", + zscale=0.1, + frame=True, + perspective=[135, 30], + transparency=z, + ) + return fig_ref, fig_test + + +@check_figures_equal() +def test_plot3d_sizes_colors_transparencies(): + """ + Plot the data with varying sizes and colors using z as transparency. + """ + x = np.arange(1.0, 10.0) + y = np.arange(1.0, 10.0) + z = np.arange(1, 10) * 10 + color = np.arange(1, 10) * 0.15 + size = np.arange(1, 10) * 0.2 + transparency = np.arange(1, 10) * 10 + + fig_ref, fig_test = Figure(), Figure() + # Use single-character arguments for the reference image + with GMTTempFile() as tmpfile: + np.savetxt(tmpfile.name, np.c_[x, y, z, color, size, transparency]) + fig_ref.plot3d( + data=tmpfile.name, + R="0/10/0/10/10/90", + J="X4i", + Jz=0.1, + p="135/30", + B="", + S="uc", + C="gray", + t="", + ) + fig_test.plot3d( + x=x, + y=y, + z=z, + region=[0, 10, 0, 10, 10, 90], + projection="X4i", + zscale=0.1, + perspective=[135, 30], + frame=True, + style="uc", + color=color, + sizes=size, + cmap="gray", + transparency=transparency, + ) + return fig_ref, fig_test + + +@check_figures_equal() +def test_plot3d_matrix(data, region): + """ + Plot the data passing in a matrix and specifying columns. + """ + fig_ref, fig_test = Figure(), Figure() + fig_ref.plot3d( + data=POINTS_DATA, + Jz=5, + p="225/30", + R="/".join(map(str, region)), + J="M10i", + S="c1c", + G="#aaaaaa", + B=["a", "za"], + i="0,1,2", + ) + fig_test.plot3d( + data=data, + zscale=5, + perspective=[225, 30], + region=region, + projection="M10i", + style="c1c", + color="#aaaaaa", + frame=["a", "za"], + columns="0,1,2", + ) + return fig_ref, fig_test + + +@check_figures_equal() +def test_plot3d_matrix_color(data, region): + """ + Plot the data passing in a matrix and using a colormap. + """ + fig_ref, fig_test = Figure(), Figure() + fig_ref.plot3d( + data=POINTS_DATA, + Jz=5, + p="225/30", + R="/".join(map(str, region)), + J="X5i", + S="c0.5c", + C="rainbow", + i="0,1,2,2", + B=["a", "za"], + ) + fig_test.plot3d( + data=data, + zscale=5, + perspective=[225, 30], + region=region, + projection="X5i", + style="c0.5c", + cmap="rainbow", + columns=[0, 1, 2, 2], + frame=["a", "za"], + ) + return fig_ref, fig_test + + +@check_figures_equal() +def test_plot3d_from_file(region): + """ + Plot using the data file name instead of loaded data. + """ + fig_ref, fig_test = Figure(), Figure() + fig_ref.plot3d( + data=POINTS_DATA, + Jz=5, + p="225/30", + R="/".join(map(str, region)), + J="X10i", + S="d1c", + G="yellow", + B=["af", "zaf"], + i="0,1,2", + ) + fig_test.plot3d( + data=POINTS_DATA, + zscale=5, + perspective=[225, 30], + region=region, + projection="X10i", + style="d1c", + color="yellow", + frame=["af", "zaf"], + columns=[0, 1, 2], + ) + return fig_ref, fig_test + + +@check_figures_equal() +def test_plot3d_vectors(): + """ + Plot vectors. + """ + azimuth = np.array([0, 45, 90, 135, 180, 225, 270, 310]) + lengths = np.linspace(0.1, 1, len(azimuth)) + lon = np.sin(np.deg2rad(azimuth)) + lat = np.cos(np.deg2rad(azimuth)) + elev = np.tan(np.deg2rad(azimuth)) + fig_ref, fig_test = Figure(), Figure() + with GMTTempFile() as tmpfile: + np.savetxt(tmpfile.name, np.c_[lon, lat, elev, azimuth, lengths]) + fig_ref.plot3d( + data=tmpfile.name, + Jz=2, + p="225/30", + R="-2/2/-2/2/-2/2", + J="X4i", + S="V1c+e", + G="black", + B=["af", "zaf"], + ) + fig_test.plot3d( + x=lon, + y=lat, + z=elev, + zscale=2, + perspective=[225, 30], + direction=(azimuth, lengths), + region=[-2, 2, -2, 2, -2, 2], + projection="X4i", + style="V1c+e", + color="black", + frame=["af", "zaf"], + ) + return fig_ref, fig_test + + +@check_figures_equal() +def test_plot3d_scalar_xyz(): + """ + Plot symbols given scalar x, y, z coordinates. + """ + fig_ref, fig_test = Figure(), Figure() + with GMTTempFile() as tmpfile: + np.savetxt(tmpfile.name, np.c_[[-1.5, 0, 1.5], [1.5, 0, -1.5], [-1.5, 0, 1.5]]) + fig_ref.basemap( + R="-2/2/-2/2/-2/2", B=["xaf+lx", "yaf+ly", "zaf+lz"], Jz=2, p="225/30" + ) + fig_ref.plot3d(data=tmpfile.name, S="c1c", G="red", Jz="", p="", qi=0) + fig_ref.plot3d(data=tmpfile.name, S="t1c", G="green", Jz="", p="", qi=1) + fig_ref.plot3d(data=tmpfile.name, S="s1c", G="blue", Jz="", p="", qi=2) + + fig_test.basemap( + region=[-2, 2, -2, 2, -2, 2], + frame=["xaf+lx", "yaf+ly", "zaf+lz"], + zscale=2, + perspective=[225, 30], + ) + fig_test.plot3d( + x=-1.5, y=1.5, z=-1.5, style="c1c", color="red", zscale=True, perspective=True + ) + fig_test.plot3d( + x=0, y=0, z=0, style="t1c", color="green", zscale=True, perspective=True + ) + fig_test.plot3d( + x=1.5, y=-1.5, z=1.5, style="s1c", color="blue", zscale=True, perspective=True + ) + return fig_ref, fig_test diff --git a/pygmt/tests/test_session_management.py b/pygmt/tests/test_session_management.py index 5dddf3b75ef..10676bf3c43 100644 --- a/pygmt/tests/test_session_management.py +++ b/pygmt/tests/test_session_management.py @@ -1,57 +1,57 @@ -""" -Test the session management modules. -""" -import os - -from pygmt.clib import Session -from pygmt.session_management import begin, end - - -def test_begin_end(): - """ - Run a command inside a begin-end modern mode block. - - First, end the global session. When finished, restart it. - """ - end() # Kill the global session - begin() - with Session() as lib: - lib.call_module("basemap", "-R10/70/-3/8 -JX4i/3i -Ba") - end() - begin() # Restart the global session - assert os.path.exists("pygmt-session.pdf") - os.remove("pygmt-session.pdf") - - -def test_gmt_compat_6_is_applied(capsys): - """ - Ensure that users with old gmt.conf files won't get pygmt-session [ERROR]: - - GMT_COMPATIBILITY: Expects values from 6 to 6; reset to 6. - """ - end() # Kill the global session - try: - # Generate a gmt.conf file in the currenty directory - # with GMT_COMPATIBILITY = 5 - with Session() as lib: - lib.call_module("gmtset", "GMT_COMPATIBILITY 5") - begin() - with Session() as lib: - lib.call_module("basemap", "-R10/70/-3/8 -JX4i/3i -Ba") - out, err = capsys.readouterr() # capture stdout and stderr - assert out == "" - assert err != ( - "pygmt-session [ERROR]: GMT_COMPATIBILITY:" - " Expects values from 6 to 6; reset to 6.\n" - ) - assert err == "" # double check that there are no other errors - finally: - end() - # Clean up the global "gmt.conf" in the current directory - assert os.path.exists("gmt.conf") - os.remove("gmt.conf") - assert os.path.exists("pygmt-session.pdf") - os.remove("pygmt-session.pdf") - # Make sure no global "gmt.conf" in the current directory - assert not os.path.exists("gmt.conf") - begin() # Restart the global session +""" +Test the session management modules. +""" +import os + +from pygmt.clib import Session +from pygmt.session_management import begin, end + + +def test_begin_end(): + """ + Run a command inside a begin-end modern mode block. + + First, end the global session. When finished, restart it. + """ + end() # Kill the global session + begin() + with Session() as lib: + lib.call_module("basemap", "-R10/70/-3/8 -JX4i/3i -Ba") + end() + begin() # Restart the global session + assert os.path.exists("pygmt-session.pdf") + os.remove("pygmt-session.pdf") + + +def test_gmt_compat_6_is_applied(capsys): + """ + Ensure that users with old gmt.conf files won't get pygmt-session [ERROR]: + + GMT_COMPATIBILITY: Expects values from 6 to 6; reset to 6. + """ + end() # Kill the global session + try: + # Generate a gmt.conf file in the currenty directory + # with GMT_COMPATIBILITY = 5 + with Session() as lib: + lib.call_module("gmtset", "GMT_COMPATIBILITY 5") + begin() + with Session() as lib: + lib.call_module("basemap", "-R10/70/-3/8 -JX4i/3i -Ba") + out, err = capsys.readouterr() # capture stdout and stderr + assert out == "" + assert err != ( + "pygmt-session [ERROR]: GMT_COMPATIBILITY:" + " Expects values from 6 to 6; reset to 6.\n" + ) + assert err == "" # double check that there are no other errors + finally: + end() + # Clean up the global "gmt.conf" in the current directory + assert os.path.exists("gmt.conf") + os.remove("gmt.conf") + assert os.path.exists("pygmt-session.pdf") + os.remove("pygmt-session.pdf") + # Make sure no global "gmt.conf" in the current directory + assert not os.path.exists("gmt.conf") + begin() # Restart the global session diff --git a/pygmt/tests/test_sphinx_gallery.py b/pygmt/tests/test_sphinx_gallery.py index 3dfed2ef76e..5d7dfb52898 100644 --- a/pygmt/tests/test_sphinx_gallery.py +++ b/pygmt/tests/test_sphinx_gallery.py @@ -1,43 +1,43 @@ -""" -Test the sphinx-gallery scraper and code required to make it work. -""" -import os -from tempfile import TemporaryDirectory - -import pytest - -try: - import sphinx_gallery -except ImportError: - sphinx_gallery = None - -from pygmt.figure import SHOWED_FIGURES, Figure -from pygmt.sphinx_gallery import PyGMTScraper - - -@pytest.mark.skipif(sphinx_gallery is None, reason="requires sphinx-gallery") -def test_pygmtscraper(): - """ - Make sure the scraper finds the figures and removes them from the pool. - """ - - showed = SHOWED_FIGURES.copy() - for _ in range(len(SHOWED_FIGURES)): - SHOWED_FIGURES.pop() - try: - fig = Figure() - fig.coast(region="BR", projection="M6i", land="gray", frame=True) - fig.show() - assert len(SHOWED_FIGURES) == 1 - assert SHOWED_FIGURES[0] is fig - scraper = PyGMTScraper() - with TemporaryDirectory(dir=os.getcwd()) as tmpdir: - conf = {"src_dir": "meh"} - fname = os.path.join(tmpdir, "meh.png") - block_vars = {"image_path_iterator": (i for i in [fname])} - assert not os.path.exists(fname) - scraper(None, block_vars, conf) - assert os.path.exists(fname) - assert not SHOWED_FIGURES - finally: - SHOWED_FIGURES.extend(showed) +""" +Test the sphinx-gallery scraper and code required to make it work. +""" +import os +from tempfile import TemporaryDirectory + +import pytest + +try: + import sphinx_gallery +except ImportError: + sphinx_gallery = None + +from pygmt.figure import SHOWED_FIGURES, Figure +from pygmt.sphinx_gallery import PyGMTScraper + + +@pytest.mark.skipif(sphinx_gallery is None, reason="requires sphinx-gallery") +def test_pygmtscraper(): + """ + Make sure the scraper finds the figures and removes them from the pool. + """ + + showed = SHOWED_FIGURES.copy() + for _ in range(len(SHOWED_FIGURES)): + SHOWED_FIGURES.pop() + try: + fig = Figure() + fig.coast(region="BR", projection="M6i", land="gray", frame=True) + fig.show() + assert len(SHOWED_FIGURES) == 1 + assert SHOWED_FIGURES[0] is fig + scraper = PyGMTScraper() + with TemporaryDirectory(dir=os.getcwd()) as tmpdir: + conf = {"src_dir": "meh"} + fname = os.path.join(tmpdir, "meh.png") + block_vars = {"image_path_iterator": (i for i in [fname])} + assert not os.path.exists(fname) + scraper(None, block_vars, conf) + assert os.path.exists(fname) + assert not SHOWED_FIGURES + finally: + SHOWED_FIGURES.extend(showed) diff --git a/pygmt/tests/test_subplot.py b/pygmt/tests/test_subplot.py index 44d78a6aa9d..e176ff637e2 100644 --- a/pygmt/tests/test_subplot.py +++ b/pygmt/tests/test_subplot.py @@ -1,110 +1,110 @@ -""" -Tests subplot. -""" -import pytest -from pygmt import Figure -from pygmt.exceptions import GMTInvalidInput -from pygmt.helpers.testing import check_figures_equal - - -@check_figures_equal() -def test_subplot_basic_frame(): - """ - Create a subplot figure with 1 vertical row and 2 horizontal columns, and - ensure map frame setting is applied to all subplot figures. - """ - fig_ref, fig_test = Figure(), Figure() - with fig_ref.subplot(nrows=1, ncols=2, Ff="6c/3c", B="WSne"): - with fig_ref.set_panel(panel=0): - fig_ref.basemap(region=[0, 3, 0, 3], frame="+tplot0") - with fig_ref.set_panel(panel=1): - fig_ref.basemap(region=[0, 3, 0, 3], frame="+tplot1") - with fig_test.subplot(nrows=1, ncols=2, figsize=("6c", "3c"), frame="WSne"): - with fig_test.set_panel(panel="0,0"): - fig_test.basemap(region=[0, 3, 0, 3], frame="+tplot0") - with fig_test.set_panel(panel=[0, 1]): - fig_test.basemap(region=[0, 3, 0, 3], frame="+tplot1") - return fig_ref, fig_test - - -@check_figures_equal() -def test_subplot_direct(): - """ - Plot map elements to subplot directly using the panel parameter. - """ - fig_ref, fig_test = Figure(), Figure() - with fig_ref.subplot(nrows=2, ncols=1, Fs="3c/3c"): - fig_ref.basemap(region=[0, 3, 0, 3], frame="af", panel=0) - fig_ref.basemap(region=[0, 3, 0, 3], frame="af", panel=1) - with fig_test.subplot(nrows=2, ncols=1, subsize=("3c", "3c")): - fig_test.basemap(region=[0, 3, 0, 3], frame="af", panel=[0, 0]) - fig_test.basemap(region=[0, 3, 0, 3], frame="af", panel=[1, 0]) - return fig_ref, fig_test - - -@check_figures_equal() -def test_subplot_autolabel_margins_title(): - """ - Make subplot figure with autolabels, setting some margins and a title. - """ - fig_ref, fig_test = Figure(), Figure() - kwargs = dict(nrows=2, ncols=1, figsize=("15c", "6c")) - - with fig_ref.subplot(A="a)", M="0.3c/0.1c", T="Subplot Title", **kwargs): - fig_ref.basemap(region=[0, 1, 2, 3], frame="WSne", c="0,0") - fig_ref.basemap(region=[4, 5, 6, 7], frame="WSne", c="1,0") - - with fig_test.subplot( - autolabel=True, margins=["0.3c", "0.1c"], title="Subplot Title", **kwargs - ): - fig_test.basemap(region=[0, 1, 2, 3], frame="WSne", panel=[0, 0]) - fig_test.basemap(region=[4, 5, 6, 7], frame="WSne", panel=[1, 0]) - - return fig_ref, fig_test - - -@check_figures_equal() -def test_subplot_clearance_and_shared_xy_axis_layout(): - """ - Ensure subplot clearance works, and that the layout can be set to use - shared X and Y axis labels across columns and rows. - """ - fig_ref, fig_test = Figure(), Figure() - kwargs = dict(nrows=2, ncols=2, frame="WSrt", figsize=("5c", "5c")) - - with fig_ref.subplot(C="y0.2c", SC="t", SR="", **kwargs): - fig_ref.basemap(region=[0, 4, 0, 4], projection="X?", panel=True) - fig_ref.basemap(region=[0, 8, 0, 4], projection="X?", panel=True) - fig_ref.basemap(region=[0, 4, 0, 8], projection="X?", panel=True) - fig_ref.basemap(region=[0, 8, 0, 8], projection="X?", panel=True) - - with fig_test.subplot( - clearance=["s0.2c", "n0.2c"], sharex="t", sharey=True, **kwargs - ): - fig_test.basemap(region=[0, 4, 0, 4], projection="X?", panel=True) - fig_test.basemap(region=[0, 8, 0, 4], projection="X?", panel=True) - fig_test.basemap(region=[0, 4, 0, 8], projection="X?", panel=True) - fig_test.basemap(region=[0, 8, 0, 8], projection="X?", panel=True) - - return fig_ref, fig_test - - -def test_subplot_figsize_and_subsize_error(): - """ - Check that an error is raised when both figsize and subsize parameters are - passed into subplot. - """ - fig = Figure() - with pytest.raises(GMTInvalidInput): - with fig.subplot(figsize=("2c", "1c"), subsize=("2c", "1c")): - pass - - -def test_subplot_nrows_ncols_less_than_one_error(): - """ - Check that an error is raised when nrows or ncols is less than one. - """ - fig = Figure() - with pytest.raises(GMTInvalidInput): - with fig.subplot(nrows=0, ncols=-1, figsize=("2c", "1c")): - pass +""" +Tests subplot. +""" +import pytest +from pygmt import Figure +from pygmt.exceptions import GMTInvalidInput +from pygmt.helpers.testing import check_figures_equal + + +@check_figures_equal() +def test_subplot_basic_frame(): + """ + Create a subplot figure with 1 vertical row and 2 horizontal columns, and + ensure map frame setting is applied to all subplot figures. + """ + fig_ref, fig_test = Figure(), Figure() + with fig_ref.subplot(nrows=1, ncols=2, Ff="6c/3c", B="WSne"): + with fig_ref.set_panel(panel=0): + fig_ref.basemap(region=[0, 3, 0, 3], frame="+tplot0") + with fig_ref.set_panel(panel=1): + fig_ref.basemap(region=[0, 3, 0, 3], frame="+tplot1") + with fig_test.subplot(nrows=1, ncols=2, figsize=("6c", "3c"), frame="WSne"): + with fig_test.set_panel(panel="0,0"): + fig_test.basemap(region=[0, 3, 0, 3], frame="+tplot0") + with fig_test.set_panel(panel=[0, 1]): + fig_test.basemap(region=[0, 3, 0, 3], frame="+tplot1") + return fig_ref, fig_test + + +@check_figures_equal() +def test_subplot_direct(): + """ + Plot map elements to subplot directly using the panel parameter. + """ + fig_ref, fig_test = Figure(), Figure() + with fig_ref.subplot(nrows=2, ncols=1, Fs="3c/3c"): + fig_ref.basemap(region=[0, 3, 0, 3], frame="af", panel=0) + fig_ref.basemap(region=[0, 3, 0, 3], frame="af", panel=1) + with fig_test.subplot(nrows=2, ncols=1, subsize=("3c", "3c")): + fig_test.basemap(region=[0, 3, 0, 3], frame="af", panel=[0, 0]) + fig_test.basemap(region=[0, 3, 0, 3], frame="af", panel=[1, 0]) + return fig_ref, fig_test + + +@check_figures_equal() +def test_subplot_autolabel_margins_title(): + """ + Make subplot figure with autolabels, setting some margins and a title. + """ + fig_ref, fig_test = Figure(), Figure() + kwargs = dict(nrows=2, ncols=1, figsize=("15c", "6c")) + + with fig_ref.subplot(A="a)", M="0.3c/0.1c", T="Subplot Title", **kwargs): + fig_ref.basemap(region=[0, 1, 2, 3], frame="WSne", c="0,0") + fig_ref.basemap(region=[4, 5, 6, 7], frame="WSne", c="1,0") + + with fig_test.subplot( + autolabel=True, margins=["0.3c", "0.1c"], title="Subplot Title", **kwargs + ): + fig_test.basemap(region=[0, 1, 2, 3], frame="WSne", panel=[0, 0]) + fig_test.basemap(region=[4, 5, 6, 7], frame="WSne", panel=[1, 0]) + + return fig_ref, fig_test + + +@check_figures_equal() +def test_subplot_clearance_and_shared_xy_axis_layout(): + """ + Ensure subplot clearance works, and that the layout can be set to use + shared X and Y axis labels across columns and rows. + """ + fig_ref, fig_test = Figure(), Figure() + kwargs = dict(nrows=2, ncols=2, frame="WSrt", figsize=("5c", "5c")) + + with fig_ref.subplot(C="y0.2c", SC="t", SR="", **kwargs): + fig_ref.basemap(region=[0, 4, 0, 4], projection="X?", panel=True) + fig_ref.basemap(region=[0, 8, 0, 4], projection="X?", panel=True) + fig_ref.basemap(region=[0, 4, 0, 8], projection="X?", panel=True) + fig_ref.basemap(region=[0, 8, 0, 8], projection="X?", panel=True) + + with fig_test.subplot( + clearance=["s0.2c", "n0.2c"], sharex="t", sharey=True, **kwargs + ): + fig_test.basemap(region=[0, 4, 0, 4], projection="X?", panel=True) + fig_test.basemap(region=[0, 8, 0, 4], projection="X?", panel=True) + fig_test.basemap(region=[0, 4, 0, 8], projection="X?", panel=True) + fig_test.basemap(region=[0, 8, 0, 8], projection="X?", panel=True) + + return fig_ref, fig_test + + +def test_subplot_figsize_and_subsize_error(): + """ + Check that an error is raised when both figsize and subsize parameters are + passed into subplot. + """ + fig = Figure() + with pytest.raises(GMTInvalidInput): + with fig.subplot(figsize=("2c", "1c"), subsize=("2c", "1c")): + pass + + +def test_subplot_nrows_ncols_less_than_one_error(): + """ + Check that an error is raised when nrows or ncols is less than one. + """ + fig = Figure() + with pytest.raises(GMTInvalidInput): + with fig.subplot(nrows=0, ncols=-1, figsize=("2c", "1c")): + pass diff --git a/pygmt/tests/test_surface.py b/pygmt/tests/test_surface.py index 9f442b16c2d..a70bbc6efff 100644 --- a/pygmt/tests/test_surface.py +++ b/pygmt/tests/test_surface.py @@ -1,115 +1,115 @@ -""" -Tests for surface. -""" -import os - -import pytest -import xarray as xr -from pygmt import surface, which -from pygmt.datasets import load_sample_bathymetry -from pygmt.exceptions import GMTInvalidInput -from pygmt.helpers import data_kind - -TEST_DATA_DIR = os.path.join(os.path.dirname(__file__), "data") -TEMP_GRID = os.path.join(TEST_DATA_DIR, "tmp_grid.nc") - - -def test_surface_input_file(): - """ - Run surface by passing in a filename. - """ - fname = which("@tut_ship.xyz", download="c") - output = surface(data=fname, spacing="5m", region=[245, 255, 20, 30]) - assert isinstance(output, xr.DataArray) - assert output.gmt.registration == 0 # Gridline registration - assert output.gmt.gtype == 0 # Cartesian type - return output - - -def test_surface_input_data_array(): - """ - Run surface by passing in a numpy array into data. - """ - ship_data = load_sample_bathymetry() - data = ship_data.values # convert pandas.DataFrame to numpy.ndarray - output = surface(data=data, spacing="5m", region=[245, 255, 20, 30]) - assert isinstance(output, xr.DataArray) - return output - - -def test_surface_input_xyz(): - """ - Run surface by passing in x, y, z numpy.ndarrays individually. - """ - ship_data = load_sample_bathymetry() - output = surface( - x=ship_data.longitude, - y=ship_data.latitude, - z=ship_data.bathymetry, - spacing="5m", - region=[245, 255, 20, 30], - ) - assert isinstance(output, xr.DataArray) - return output - - -def test_surface_input_xy_no_z(): - """ - Run surface by passing in x and y, but no z. - """ - ship_data = load_sample_bathymetry() - with pytest.raises(GMTInvalidInput): - surface( - x=ship_data.longitude, - y=ship_data.latitude, - spacing="5m", - region=[245, 255, 20, 30], - ) - - -def test_surface_wrong_kind_of_input(): - """ - Run surface using grid input that is not file/matrix/vectors. - """ - ship_data = load_sample_bathymetry() - data = ship_data.bathymetry.to_xarray() # convert pandas.Series to xarray.DataArray - assert data_kind(data) == "grid" - with pytest.raises(GMTInvalidInput): - surface(data=data, spacing="5m", region=[245, 255, 20, 30]) - - -def test_surface_with_outfile_param(): - """ - Run surface with the -Goutputfile.nc parameter. - """ - ship_data = load_sample_bathymetry() - data = ship_data.values # convert pandas.DataFrame to numpy.ndarray - try: - output = surface( - data=data, spacing="5m", region=[245, 255, 20, 30], outfile=TEMP_GRID - ) - assert output is None # check that output is None since outfile is set - assert os.path.exists(path=TEMP_GRID) # check that outfile exists at path - with xr.open_dataarray(TEMP_GRID) as grid: - assert isinstance(grid, xr.DataArray) # ensure netcdf grid loads ok - finally: - os.remove(path=TEMP_GRID) - return output - - -def test_surface_short_aliases(): - """ - Run surface using short aliases -I for spacing, -R for region, -G for - outfile. - """ - ship_data = load_sample_bathymetry() - data = ship_data.values # convert pandas.DataFrame to numpy.ndarray - try: - output = surface(data=data, I="5m", R=[245, 255, 20, 30], G=TEMP_GRID) - assert output is None # check that output is None since outfile is set - assert os.path.exists(path=TEMP_GRID) # check that outfile exists at path - with xr.open_dataarray(TEMP_GRID) as grid: - assert isinstance(grid, xr.DataArray) # ensure netcdf grid loads ok - finally: - os.remove(path=TEMP_GRID) - return output +""" +Tests for surface. +""" +import os + +import pytest +import xarray as xr +from pygmt import surface, which +from pygmt.datasets import load_sample_bathymetry +from pygmt.exceptions import GMTInvalidInput +from pygmt.helpers import data_kind + +TEST_DATA_DIR = os.path.join(os.path.dirname(__file__), "data") +TEMP_GRID = os.path.join(TEST_DATA_DIR, "tmp_grid.nc") + + +def test_surface_input_file(): + """ + Run surface by passing in a filename. + """ + fname = which("@tut_ship.xyz", download="c") + output = surface(data=fname, spacing="5m", region=[245, 255, 20, 30]) + assert isinstance(output, xr.DataArray) + assert output.gmt.registration == 0 # Gridline registration + assert output.gmt.gtype == 0 # Cartesian type + return output + + +def test_surface_input_data_array(): + """ + Run surface by passing in a numpy array into data. + """ + ship_data = load_sample_bathymetry() + data = ship_data.values # convert pandas.DataFrame to numpy.ndarray + output = surface(data=data, spacing="5m", region=[245, 255, 20, 30]) + assert isinstance(output, xr.DataArray) + return output + + +def test_surface_input_xyz(): + """ + Run surface by passing in x, y, z numpy.ndarrays individually. + """ + ship_data = load_sample_bathymetry() + output = surface( + x=ship_data.longitude, + y=ship_data.latitude, + z=ship_data.bathymetry, + spacing="5m", + region=[245, 255, 20, 30], + ) + assert isinstance(output, xr.DataArray) + return output + + +def test_surface_input_xy_no_z(): + """ + Run surface by passing in x and y, but no z. + """ + ship_data = load_sample_bathymetry() + with pytest.raises(GMTInvalidInput): + surface( + x=ship_data.longitude, + y=ship_data.latitude, + spacing="5m", + region=[245, 255, 20, 30], + ) + + +def test_surface_wrong_kind_of_input(): + """ + Run surface using grid input that is not file/matrix/vectors. + """ + ship_data = load_sample_bathymetry() + data = ship_data.bathymetry.to_xarray() # convert pandas.Series to xarray.DataArray + assert data_kind(data) == "grid" + with pytest.raises(GMTInvalidInput): + surface(data=data, spacing="5m", region=[245, 255, 20, 30]) + + +def test_surface_with_outfile_param(): + """ + Run surface with the -Goutputfile.nc parameter. + """ + ship_data = load_sample_bathymetry() + data = ship_data.values # convert pandas.DataFrame to numpy.ndarray + try: + output = surface( + data=data, spacing="5m", region=[245, 255, 20, 30], outfile=TEMP_GRID + ) + assert output is None # check that output is None since outfile is set + assert os.path.exists(path=TEMP_GRID) # check that outfile exists at path + with xr.open_dataarray(TEMP_GRID) as grid: + assert isinstance(grid, xr.DataArray) # ensure netcdf grid loads ok + finally: + os.remove(path=TEMP_GRID) + return output + + +def test_surface_short_aliases(): + """ + Run surface using short aliases -I for spacing, -R for region, -G for + outfile. + """ + ship_data = load_sample_bathymetry() + data = ship_data.values # convert pandas.DataFrame to numpy.ndarray + try: + output = surface(data=data, I="5m", R=[245, 255, 20, 30], G=TEMP_GRID) + assert output is None # check that output is None since outfile is set + assert os.path.exists(path=TEMP_GRID) # check that outfile exists at path + with xr.open_dataarray(TEMP_GRID) as grid: + assert isinstance(grid, xr.DataArray) # ensure netcdf grid loads ok + finally: + os.remove(path=TEMP_GRID) + return output diff --git a/pygmt/tests/test_text.py b/pygmt/tests/test_text.py index 8c3305c5267..d467013f1a3 100644 --- a/pygmt/tests/test_text.py +++ b/pygmt/tests/test_text.py @@ -1,374 +1,374 @@ -# pylint: disable=redefined-outer-name -""" -Tests text. -""" -import os - -import numpy as np -import pytest -from pygmt import Figure -from pygmt.exceptions import GMTCLibError, GMTInvalidInput -from pygmt.helpers import GMTTempFile -from pygmt.helpers.testing import check_figures_equal - -TEST_DATA_DIR = os.path.join(os.path.dirname(__file__), "data") -POINTS_DATA = os.path.join(TEST_DATA_DIR, "points.txt") -CITIES_DATA = os.path.join(TEST_DATA_DIR, "cities.txt") - - -@pytest.fixture(scope="module") -def projection(): - """ - The projection system. - """ - return "x4i" - - -@pytest.fixture(scope="module") -def region(): - """ - The data region. - """ - return [0, 5, 0, 2.5] - - -@pytest.mark.mpl_image_compare -def test_text_single_line_of_text(region, projection): - """ - Place a single line text of text at some x, y location. - """ - fig = Figure() - fig.text( - region=region, - projection=projection, - x=1.2, - y=2.4, - text="This is a line of text", - ) - return fig - - -@pytest.mark.mpl_image_compare -def test_text_multiple_lines_of_text(region, projection): - """ - Place multiple lines of text at their respective x, y locations. - """ - fig = Figure() - fig.text( - region=region, - projection=projection, - x=[1.2, 1.6], - y=[0.6, 0.3], - text=["This is a line of text", "This is another line of text"], - ) - return fig - - -def test_text_without_text_input(region, projection): - """ - Run text by passing in x and y, but no text. - """ - fig = Figure() - with pytest.raises(GMTInvalidInput): - fig.text(region=region, projection=projection, x=1.2, y=2.4) - - -@pytest.mark.mpl_image_compare -def test_text_input_single_filename(): - """ - Run text by passing in one filename to textfiles. - """ - fig = Figure() - fig.text(region=[10, 70, -5, 10], textfiles=POINTS_DATA) - return fig - - -@pytest.mark.mpl_image_compare -def test_text_input_remote_filename(): - """ - Run text by passing in a remote filename to textfiles. - """ - fig = Figure() - fig.text(region=[0, 6.5, 0, 6.5], textfiles="@Table_5_11.txt") - return fig - - -@pytest.mark.mpl_image_compare -def test_text_input_multiple_filenames(): - """ - Run text by passing in multiple filenames to textfiles. - """ - fig = Figure() - fig.text(region=[10, 70, -30, 10], textfiles=[POINTS_DATA, CITIES_DATA]) - return fig - - -def test_text_nonexistent_filename(): - """ - Run text by passing in a list of filenames with one that does not exist. - """ - fig = Figure() - with pytest.raises(GMTCLibError): - fig.text(region=[10, 70, -5, 10], textfiles=[POINTS_DATA, "notexist.txt"]) - - -@pytest.mark.mpl_image_compare -def test_text_position(region): - """ - Print text at center middle (CM) and eight other positions - (Top/Middle/Bottom x Left/Centre/Right). - """ - fig = Figure() - fig.text(region=region, projection="x1c", frame="a", position="CM", text="C M") - for position in ("TL", "TC", "TR", "ML", "MR", "BL", "BC", "BR"): - fig.text(position=position, text=position) - return fig - - -def test_text_xy_with_position_fails(region): - """ - Run text by providing both x/y pairs and position arguments. - """ - fig = Figure() - with pytest.raises(GMTInvalidInput): - fig.text( - region=region, projection="x1c", x=1.2, y=2.4, position="MC", text="text" - ) - - -@pytest.mark.mpl_image_compare -def test_text_position_offset_with_line(region): - """ - Print text at centre middle (CM) and eight other positions - (Top/Middle/Bottom x Left/Centre/Right), offset by 0.5 cm, with a line - drawn from the original to the shifted point. - """ - fig = Figure() - fig.text(region=region, projection="x1c", frame="a", position="CM", text="C M") - for position in ("TL", "TC", "TR", "ML", "MR", "BL", "BC", "BR"): - fig.text(position=position, text=position, offset="j0.5c+v") - return fig - - -@pytest.mark.mpl_image_compare -def test_text_angle_30(region, projection): - """ - Print text at 30 degrees counter-clockwise from horizontal. - """ - fig = Figure() - fig.text( - region=region, - projection=projection, - x=1.2, - y=2.4, - text="text angle 30 degrees", - angle=30, - ) - return fig - - -@pytest.mark.mpl_image_compare -def test_text_font_bold(region, projection): - """ - Print text with a bold font. - """ - fig = Figure() - fig.text( - region=region, - projection=projection, - x=1.2, - y=2.4, - text="text in bold", - font="Helvetica-Bold", - ) - return fig - - -@pytest.mark.mpl_image_compare -def test_text_fill(region, projection): - """ - Print text with blue color fill. - """ - fig = Figure() - fig.text( - region=region, - projection=projection, - x=1.2, - y=1.2, - text="blue fill around text", - fill="blue", - ) - return fig - - -@pytest.mark.mpl_image_compare -def test_text_pen(region, projection): - """ - Print text with thick green dashed pen. - """ - fig = Figure() - fig.text( - region=region, - projection=projection, - x=1.2, - y=1.2, - text="green pen around text", - pen="thick,green,dashed", - ) - return fig - - -@pytest.mark.mpl_image_compare -def test_text_round_clearance(region, projection): - """ - Print text with round rectangle box clearance. - """ - fig = Figure() - fig.text( - region=region, - projection=projection, - x=1.2, - y=1.2, - text="clearance around text", - clearance="90%+tO", - pen="default,black,dashed", - ) - return fig - - -@pytest.mark.mpl_image_compare -def test_text_justify_bottom_right_and_top_left(region, projection): - """ - Print text justified at bottom right and top left. - """ - fig = Figure() - fig.text( - region=region, - projection=projection, - x=1.2, - y=0.2, - text="text justified bottom right", - justify="BR", - ) - fig.text( - region=region, - projection=projection, - x=1.2, - y=0.2, - text="text justified top left", - justify="TL", - ) - return fig - - -@pytest.mark.mpl_image_compare -def test_text_justify_parsed_from_textfile(): - """ - Print text justified based on a column from textfile, using justify=True - boolean operation. - - Loosely based on "All great-circle paths lead to Rome" - gallery example at - https://gmt.soest.hawaii.edu/doc/latest/gallery/ex23.html - """ - fig = Figure() - fig.text( - region="g", - projection="H90/9i", - justify=True, - textfiles=CITIES_DATA, - D="j0.45/0+vred", # draw red-line from xy point to text label (city name) - ) - return fig - - -@pytest.mark.mpl_image_compare -def test_text_angle_font_justify_from_textfile(): - """ - Print text with x, y, angle, font, justify, and text arguments parsed from - the textfile. - """ - fig = Figure() - with GMTTempFile(suffix=".txt") as tempfile: - with open(tempfile.name, "w") as tmpfile: - tmpfile.write("114 0.5 30 22p,Helvetica-Bold,black LM BORNEO") - fig.text( - region=[113, 117.5, -0.5, 3], - projection="M5c", - frame="a", - textfiles=tempfile.name, - angle=True, - font=True, - justify=True, - ) - return fig - - -@check_figures_equal() -def test_text_transparency(): - """ - Add texts with a constant transparency. - """ - x = np.arange(1, 10) - y = np.arange(11, 20) - text = [f"TEXT-{i}-{j}" for i, j in zip(x, y)] - - fig_ref, fig_test = Figure(), Figure() - # Use single-character arguments for the reference image - with GMTTempFile() as tmpfile: - np.savetxt(tmpfile.name, np.c_[x, y, text], fmt="%s") - fig_ref.basemap(R="0/10/10/20", J="X10c", B="") - fig_ref.text(textfiles=tmpfile.name, t=50) - - fig_test.basemap(region=[0, 10, 10, 20], projection="X10c", frame=True) - fig_test.text(x=x, y=y, text=text, transparency=50) - - return fig_ref, fig_test - - -@check_figures_equal() -def test_text_varying_transparency(): - """ - Add texts with varying transparency. - """ - x = np.arange(1, 10) - y = np.arange(11, 20) - text = [f"TEXT-{i}-{j}" for i, j in zip(x, y)] - transparency = np.arange(10, 100, 10) - - fig_ref, fig_test = Figure(), Figure() - # Use single-character arguments for the reference image - with GMTTempFile() as tmpfile: - np.savetxt(tmpfile.name, np.c_[x, y, transparency, text], fmt="%s") - fig_ref.basemap(R="0/10/10/20", J="X10c", B="") - fig_ref.text(textfiles=tmpfile.name, t="") - - fig_test.basemap(region=[0, 10, 10, 20], projection="X10c", frame=True) - fig_test.text(x=x, y=y, text=text, transparency=transparency) - - return fig_ref, fig_test - - -@check_figures_equal() -def test_text_nonstr_text(): - """ - Input text is in non-string type (e.g., int, float) - """ - fig_ref, fig_test = Figure(), Figure() - - # Use single-character arguments and input files for the reference image - with GMTTempFile(suffix=".txt") as tempfile: - with open(tempfile.name, "w") as tmpfile: - tmpfile.write("1 1 1.0\n2 2 2.0\n3 3 3.0\n4 4 4.0\n") - fig_ref.text(R="0/10/0/10", J="X10c", B="", textfiles=tempfile.name) - - fig_test.text( - region=[0, 10, 0, 10], - projection="X10c", - frame=True, - x=[1, 2, 3, 4], - y=[1, 2, 3, 4], - text=[1, 2, 3.0, 4.0], - ) - - return fig_ref, fig_test +# pylint: disable=redefined-outer-name +""" +Tests text. +""" +import os + +import numpy as np +import pytest +from pygmt import Figure +from pygmt.exceptions import GMTCLibError, GMTInvalidInput +from pygmt.helpers import GMTTempFile +from pygmt.helpers.testing import check_figures_equal + +TEST_DATA_DIR = os.path.join(os.path.dirname(__file__), "data") +POINTS_DATA = os.path.join(TEST_DATA_DIR, "points.txt") +CITIES_DATA = os.path.join(TEST_DATA_DIR, "cities.txt") + + +@pytest.fixture(scope="module") +def projection(): + """ + The projection system. + """ + return "x4i" + + +@pytest.fixture(scope="module") +def region(): + """ + The data region. + """ + return [0, 5, 0, 2.5] + + +@pytest.mark.mpl_image_compare +def test_text_single_line_of_text(region, projection): + """ + Place a single line text of text at some x, y location. + """ + fig = Figure() + fig.text( + region=region, + projection=projection, + x=1.2, + y=2.4, + text="This is a line of text", + ) + return fig + + +@pytest.mark.mpl_image_compare +def test_text_multiple_lines_of_text(region, projection): + """ + Place multiple lines of text at their respective x, y locations. + """ + fig = Figure() + fig.text( + region=region, + projection=projection, + x=[1.2, 1.6], + y=[0.6, 0.3], + text=["This is a line of text", "This is another line of text"], + ) + return fig + + +def test_text_without_text_input(region, projection): + """ + Run text by passing in x and y, but no text. + """ + fig = Figure() + with pytest.raises(GMTInvalidInput): + fig.text(region=region, projection=projection, x=1.2, y=2.4) + + +@pytest.mark.mpl_image_compare +def test_text_input_single_filename(): + """ + Run text by passing in one filename to textfiles. + """ + fig = Figure() + fig.text(region=[10, 70, -5, 10], textfiles=POINTS_DATA) + return fig + + +@pytest.mark.mpl_image_compare +def test_text_input_remote_filename(): + """ + Run text by passing in a remote filename to textfiles. + """ + fig = Figure() + fig.text(region=[0, 6.5, 0, 6.5], textfiles="@Table_5_11.txt") + return fig + + +@pytest.mark.mpl_image_compare +def test_text_input_multiple_filenames(): + """ + Run text by passing in multiple filenames to textfiles. + """ + fig = Figure() + fig.text(region=[10, 70, -30, 10], textfiles=[POINTS_DATA, CITIES_DATA]) + return fig + + +def test_text_nonexistent_filename(): + """ + Run text by passing in a list of filenames with one that does not exist. + """ + fig = Figure() + with pytest.raises(GMTCLibError): + fig.text(region=[10, 70, -5, 10], textfiles=[POINTS_DATA, "notexist.txt"]) + + +@pytest.mark.mpl_image_compare +def test_text_position(region): + """ + Print text at center middle (CM) and eight other positions + (Top/Middle/Bottom x Left/Centre/Right). + """ + fig = Figure() + fig.text(region=region, projection="x1c", frame="a", position="CM", text="C M") + for position in ("TL", "TC", "TR", "ML", "MR", "BL", "BC", "BR"): + fig.text(position=position, text=position) + return fig + + +def test_text_xy_with_position_fails(region): + """ + Run text by providing both x/y pairs and position arguments. + """ + fig = Figure() + with pytest.raises(GMTInvalidInput): + fig.text( + region=region, projection="x1c", x=1.2, y=2.4, position="MC", text="text" + ) + + +@pytest.mark.mpl_image_compare +def test_text_position_offset_with_line(region): + """ + Print text at centre middle (CM) and eight other positions + (Top/Middle/Bottom x Left/Centre/Right), offset by 0.5 cm, with a line + drawn from the original to the shifted point. + """ + fig = Figure() + fig.text(region=region, projection="x1c", frame="a", position="CM", text="C M") + for position in ("TL", "TC", "TR", "ML", "MR", "BL", "BC", "BR"): + fig.text(position=position, text=position, offset="j0.5c+v") + return fig + + +@pytest.mark.mpl_image_compare +def test_text_angle_30(region, projection): + """ + Print text at 30 degrees counter-clockwise from horizontal. + """ + fig = Figure() + fig.text( + region=region, + projection=projection, + x=1.2, + y=2.4, + text="text angle 30 degrees", + angle=30, + ) + return fig + + +@pytest.mark.mpl_image_compare +def test_text_font_bold(region, projection): + """ + Print text with a bold font. + """ + fig = Figure() + fig.text( + region=region, + projection=projection, + x=1.2, + y=2.4, + text="text in bold", + font="Helvetica-Bold", + ) + return fig + + +@pytest.mark.mpl_image_compare +def test_text_fill(region, projection): + """ + Print text with blue color fill. + """ + fig = Figure() + fig.text( + region=region, + projection=projection, + x=1.2, + y=1.2, + text="blue fill around text", + fill="blue", + ) + return fig + + +@pytest.mark.mpl_image_compare +def test_text_pen(region, projection): + """ + Print text with thick green dashed pen. + """ + fig = Figure() + fig.text( + region=region, + projection=projection, + x=1.2, + y=1.2, + text="green pen around text", + pen="thick,green,dashed", + ) + return fig + + +@pytest.mark.mpl_image_compare +def test_text_round_clearance(region, projection): + """ + Print text with round rectangle box clearance. + """ + fig = Figure() + fig.text( + region=region, + projection=projection, + x=1.2, + y=1.2, + text="clearance around text", + clearance="90%+tO", + pen="default,black,dashed", + ) + return fig + + +@pytest.mark.mpl_image_compare +def test_text_justify_bottom_right_and_top_left(region, projection): + """ + Print text justified at bottom right and top left. + """ + fig = Figure() + fig.text( + region=region, + projection=projection, + x=1.2, + y=0.2, + text="text justified bottom right", + justify="BR", + ) + fig.text( + region=region, + projection=projection, + x=1.2, + y=0.2, + text="text justified top left", + justify="TL", + ) + return fig + + +@pytest.mark.mpl_image_compare +def test_text_justify_parsed_from_textfile(): + """ + Print text justified based on a column from textfile, using justify=True + boolean operation. + + Loosely based on "All great-circle paths lead to Rome" + gallery example at + https://gmt.soest.hawaii.edu/doc/latest/gallery/ex23.html + """ + fig = Figure() + fig.text( + region="g", + projection="H90/9i", + justify=True, + textfiles=CITIES_DATA, + D="j0.45/0+vred", # draw red-line from xy point to text label (city name) + ) + return fig + + +@pytest.mark.mpl_image_compare +def test_text_angle_font_justify_from_textfile(): + """ + Print text with x, y, angle, font, justify, and text arguments parsed from + the textfile. + """ + fig = Figure() + with GMTTempFile(suffix=".txt") as tempfile: + with open(tempfile.name, "w") as tmpfile: + tmpfile.write("114 0.5 30 22p,Helvetica-Bold,black LM BORNEO") + fig.text( + region=[113, 117.5, -0.5, 3], + projection="M5c", + frame="a", + textfiles=tempfile.name, + angle=True, + font=True, + justify=True, + ) + return fig + + +@check_figures_equal() +def test_text_transparency(): + """ + Add texts with a constant transparency. + """ + x = np.arange(1, 10) + y = np.arange(11, 20) + text = [f"TEXT-{i}-{j}" for i, j in zip(x, y)] + + fig_ref, fig_test = Figure(), Figure() + # Use single-character arguments for the reference image + with GMTTempFile() as tmpfile: + np.savetxt(tmpfile.name, np.c_[x, y, text], fmt="%s") + fig_ref.basemap(R="0/10/10/20", J="X10c", B="") + fig_ref.text(textfiles=tmpfile.name, t=50) + + fig_test.basemap(region=[0, 10, 10, 20], projection="X10c", frame=True) + fig_test.text(x=x, y=y, text=text, transparency=50) + + return fig_ref, fig_test + + +@check_figures_equal() +def test_text_varying_transparency(): + """ + Add texts with varying transparency. + """ + x = np.arange(1, 10) + y = np.arange(11, 20) + text = [f"TEXT-{i}-{j}" for i, j in zip(x, y)] + transparency = np.arange(10, 100, 10) + + fig_ref, fig_test = Figure(), Figure() + # Use single-character arguments for the reference image + with GMTTempFile() as tmpfile: + np.savetxt(tmpfile.name, np.c_[x, y, transparency, text], fmt="%s") + fig_ref.basemap(R="0/10/10/20", J="X10c", B="") + fig_ref.text(textfiles=tmpfile.name, t="") + + fig_test.basemap(region=[0, 10, 10, 20], projection="X10c", frame=True) + fig_test.text(x=x, y=y, text=text, transparency=transparency) + + return fig_ref, fig_test + + +@check_figures_equal() +def test_text_nonstr_text(): + """ + Input text is in non-string type (e.g., int, float) + """ + fig_ref, fig_test = Figure(), Figure() + + # Use single-character arguments and input files for the reference image + with GMTTempFile(suffix=".txt") as tempfile: + with open(tempfile.name, "w") as tmpfile: + tmpfile.write("1 1 1.0\n2 2 2.0\n3 3 3.0\n4 4 4.0\n") + fig_ref.text(R="0/10/0/10", J="X10c", B="", textfiles=tempfile.name) + + fig_test.text( + region=[0, 10, 0, 10], + projection="X10c", + frame=True, + x=[1, 2, 3, 4], + y=[1, 2, 3, 4], + text=[1, 2, 3.0, 4.0], + ) + + return fig_ref, fig_test diff --git a/pygmt/tests/test_which.py b/pygmt/tests/test_which.py index caac58fe36d..db3c4533640 100644 --- a/pygmt/tests/test_which.py +++ b/pygmt/tests/test_which.py @@ -1,27 +1,27 @@ -""" -Tests for pygmt.which. -""" -import os - -import pytest -from pygmt import which -from pygmt.helpers import unique_name - - -def test_which(): - """ - Make sure which returns file paths for @files correctly without errors. - """ - for fname in ["tut_quakes.ngdc", "tut_bathy.nc"]: - cached_file = which(f"@{fname}", download="c") - assert os.path.exists(cached_file) - assert os.path.basename(cached_file) == fname - - -def test_which_fails(): - """ - which should fail with a FileNotFoundError. - """ - bogus_file = unique_name() - with pytest.raises(FileNotFoundError): - which(bogus_file) +""" +Tests for pygmt.which. +""" +import os + +import pytest +from pygmt import which +from pygmt.helpers import unique_name + + +def test_which(): + """ + Make sure which returns file paths for @files correctly without errors. + """ + for fname in ["tut_quakes.ngdc", "tut_bathy.nc"]: + cached_file = which(f"@{fname}", download="c") + assert os.path.exists(cached_file) + assert os.path.basename(cached_file) == fname + + +def test_which_fails(): + """ + which should fail with a FileNotFoundError. + """ + bogus_file = unique_name() + with pytest.raises(FileNotFoundError): + which(bogus_file) diff --git a/pygmt/tests/test_x2sys_cross.py b/pygmt/tests/test_x2sys_cross.py index 43d766cadd3..f9c7b9c1977 100644 --- a/pygmt/tests/test_x2sys_cross.py +++ b/pygmt/tests/test_x2sys_cross.py @@ -1,209 +1,209 @@ -# pylint: disable=unused-argument -""" -Tests for x2sys_cross. -""" -import os -from tempfile import TemporaryDirectory - -import numpy as np -import numpy.testing as npt -import pandas as pd -import pytest -from pygmt import x2sys_cross, x2sys_init -from pygmt.datasets import load_sample_bathymetry -from pygmt.exceptions import GMTInvalidInput -from pygmt.helpers import data_kind - - -@pytest.fixture(name="mock_x2sys_home") -def fixture_mock_x2sys_home(monkeypatch): - """ - Set the X2SYS_HOME environment variable to the current working directory - for the test session. - """ - monkeypatch.setenv("X2SYS_HOME", os.getcwd()) - - -@pytest.fixture(scope="module", name="tracks") -def fixture_tracks(): - """ - Load track data from the sample bathymetry file. - """ - dataframe = load_sample_bathymetry() - dataframe.columns = ["x", "y", "z"] # longitude, latitude, bathymetry - return [dataframe.query(expr="z > -20")] # reduce size of dataset - - -def test_x2sys_cross_input_file_output_file(mock_x2sys_home): - """ - Run x2sys_cross by passing in a filename, and output internal crossovers to - an ASCII txt file. - """ - with TemporaryDirectory(prefix="X2SYS", dir=os.getcwd()) as tmpdir: - tag = os.path.basename(tmpdir) - x2sys_init(tag=tag, fmtfile="xyz", force=True) - outfile = os.path.join(tmpdir, "tmp_coe.txt") - output = x2sys_cross( - tracks=["@tut_ship.xyz"], tag=tag, coe="i", outfile=outfile, verbose="i" - ) - - assert output is None # check that output is None since outfile is set - assert os.path.exists(path=outfile) # check that outfile exists at path - _ = pd.read_csv(outfile, sep="\t", header=2) # ensure ASCII text file loads ok - - return output - - -def test_x2sys_cross_input_file_output_dataframe(mock_x2sys_home): - """ - Run x2sys_cross by passing in a filename, and output internal crossovers to - a pandas.DataFrame. - """ - with TemporaryDirectory(prefix="X2SYS", dir=os.getcwd()) as tmpdir: - tag = os.path.basename(tmpdir) - x2sys_init(tag=tag, fmtfile="xyz", force=True) - output = x2sys_cross(tracks=["@tut_ship.xyz"], tag=tag, coe="i", verbose="i") - - assert isinstance(output, pd.DataFrame) - assert output.shape == (14294, 12) - columns = list(output.columns) - assert columns[:6] == ["x", "y", "i_1", "i_2", "dist_1", "dist_2"] - assert columns[6:] == ["head_1", "head_2", "vel_1", "vel_2", "z_X", "z_M"] - - return output - - -def test_x2sys_cross_input_dataframe_output_dataframe(mock_x2sys_home, tracks): - """ - Run x2sys_cross by passing in one dataframe, and output external crossovers - to a pandas.DataFrame. - """ - with TemporaryDirectory(prefix="X2SYS", dir=os.getcwd()) as tmpdir: - tag = os.path.basename(tmpdir) - x2sys_init(tag=tag, fmtfile="xyz", force=True) - - output = x2sys_cross(tracks=tracks, tag=tag, coe="i", verbose="i") - - assert isinstance(output, pd.DataFrame) - assert output.shape == (14, 12) - columns = list(output.columns) - assert columns[:6] == ["x", "y", "i_1", "i_2", "dist_1", "dist_2"] - assert columns[6:] == ["head_1", "head_2", "vel_1", "vel_2", "z_X", "z_M"] - assert output.dtypes["i_1"].type == np.object_ - assert output.dtypes["i_2"].type == np.object_ - - return output - - -def test_x2sys_cross_input_two_dataframes(mock_x2sys_home): - """ - Run x2sys_cross by passing in two pandas.DataFrame tables with a time - column, and output external crossovers to a pandas.DataFrame. - """ - with TemporaryDirectory(prefix="X2SYS", dir=os.getcwd()) as tmpdir: - tag = os.path.basename(tmpdir) - x2sys_init( - tag=tag, fmtfile="xyz", suffix="xyzt", units=["de", "se"], force=True - ) - - # Add a time row to the x2sys fmtfile - with open(file=os.path.join(tmpdir, "xyz.fmt"), mode="a") as fmtfile: - fmtfile.write("time\ta\tN\t0\t1\t0\t%g\n") - - # Create pandas.DataFrame track tables - tracks = [] - for i in range(2): - np.random.seed(seed=i) - track = pd.DataFrame(data=np.random.rand(10, 3), columns=("x", "y", "z")) - track["time"] = pd.date_range(start=f"2020-{i}1-01", periods=10, freq="ms") - tracks.append(track) - - output = x2sys_cross(tracks=tracks, tag=tag, coe="e", verbose="i") - - assert isinstance(output, pd.DataFrame) - assert output.shape == (30, 12) - columns = list(output.columns) - assert columns[:6] == ["x", "y", "t_1", "t_2", "dist_1", "dist_2"] - assert columns[6:] == ["head_1", "head_2", "vel_1", "vel_2", "z_X", "z_M"] - assert output.dtypes["t_1"].type == np.datetime64 - assert output.dtypes["t_2"].type == np.datetime64 - - -def test_x2sys_cross_input_two_filenames(mock_x2sys_home): - """ - Run x2sys_cross by passing in two filenames, and output external crossovers - to a pandas.DataFrame. - """ - with TemporaryDirectory(prefix="X2SYS", dir=os.getcwd()) as tmpdir: - tag = os.path.basename(tmpdir) - x2sys_init(tag=tag, fmtfile="xyz", force=True) - - # Create temporary xyz files - for i in range(2): - np.random.seed(seed=i) - with open(os.path.join(os.getcwd(), f"track_{i}.xyz"), mode="w") as fname: - np.savetxt(fname=fname, X=np.random.rand(10, 3)) - - output = x2sys_cross( - tracks=["track_0.xyz", "track_1.xyz"], tag=tag, coe="e", verbose="i" - ) - - assert isinstance(output, pd.DataFrame) - assert output.shape == (24, 12) - columns = list(output.columns) - assert columns[:6] == ["x", "y", "i_1", "i_2", "dist_1", "dist_2"] - assert columns[6:] == ["head_1", "head_2", "vel_1", "vel_2", "z_X", "z_M"] - _ = [os.remove(f"track_{i}.xyz") for i in range(2)] # cleanup track files - - return output - - -def test_x2sys_cross_invalid_tracks_input_type(tracks): - """ - Run x2sys_cross using tracks input that is not a pandas.DataFrame (matrix) - or str (file) type, which would raise a GMTInvalidInput error. - """ - invalid_tracks = tracks[0].to_xarray().z - assert data_kind(invalid_tracks) == "grid" - with pytest.raises(GMTInvalidInput): - x2sys_cross(tracks=[invalid_tracks]) - - -def test_x2sys_cross_region_interpolation_numpoints(mock_x2sys_home): - """ - Test that x2sys_cross's region (R), interpolation (l) and numpoints (W) - arguments work. - """ - with TemporaryDirectory(prefix="X2SYS", dir=os.getcwd()) as tmpdir: - tag = os.path.basename(tmpdir) - x2sys_init(tag=tag, fmtfile="xyz", force=True) - output = x2sys_cross( - tracks=["@tut_ship.xyz"], - tag=tag, - coe="i", - region=[245, 250, 20, 25], - interpolation="a", # Akima spline interpolation - numpoints=5, # Use up to 5 data points in interpolation - ) - - assert isinstance(output, pd.DataFrame) - assert output.shape == (3867, 12) - # Check crossover errors (z_X) and mean value of observables (z_M) - npt.assert_allclose(output.z_X.mean(), -139.2, rtol=1e-4) - npt.assert_allclose(output.z_M.mean(), -2890.465813) - - -def test_x2sys_cross_trackvalues(mock_x2sys_home): - """ - Test that x2sys_cross's trackvalues (Z) argument work. - """ - with TemporaryDirectory(prefix="X2SYS", dir=os.getcwd()) as tmpdir: - tag = os.path.basename(tmpdir) - x2sys_init(tag=tag, fmtfile="xyz", force=True) - output = x2sys_cross(tracks=["@tut_ship.xyz"], tag=tag, trackvalues=True) - - assert isinstance(output, pd.DataFrame) - assert output.shape == (14294, 12) - # Check mean of track 1 values (z_1) and track 2 values (z_2) - npt.assert_allclose(output.z_1.mean(), -2420.569767) - npt.assert_allclose(output.z_2.mean(), -2400.357549) +# pylint: disable=unused-argument +""" +Tests for x2sys_cross. +""" +import os +from tempfile import TemporaryDirectory + +import numpy as np +import numpy.testing as npt +import pandas as pd +import pytest +from pygmt import x2sys_cross, x2sys_init +from pygmt.datasets import load_sample_bathymetry +from pygmt.exceptions import GMTInvalidInput +from pygmt.helpers import data_kind + + +@pytest.fixture(name="mock_x2sys_home") +def fixture_mock_x2sys_home(monkeypatch): + """ + Set the X2SYS_HOME environment variable to the current working directory + for the test session. + """ + monkeypatch.setenv("X2SYS_HOME", os.getcwd()) + + +@pytest.fixture(scope="module", name="tracks") +def fixture_tracks(): + """ + Load track data from the sample bathymetry file. + """ + dataframe = load_sample_bathymetry() + dataframe.columns = ["x", "y", "z"] # longitude, latitude, bathymetry + return [dataframe.query(expr="z > -20")] # reduce size of dataset + + +def test_x2sys_cross_input_file_output_file(mock_x2sys_home): + """ + Run x2sys_cross by passing in a filename, and output internal crossovers to + an ASCII txt file. + """ + with TemporaryDirectory(prefix="X2SYS", dir=os.getcwd()) as tmpdir: + tag = os.path.basename(tmpdir) + x2sys_init(tag=tag, fmtfile="xyz", force=True) + outfile = os.path.join(tmpdir, "tmp_coe.txt") + output = x2sys_cross( + tracks=["@tut_ship.xyz"], tag=tag, coe="i", outfile=outfile, verbose="i" + ) + + assert output is None # check that output is None since outfile is set + assert os.path.exists(path=outfile) # check that outfile exists at path + _ = pd.read_csv(outfile, sep="\t", header=2) # ensure ASCII text file loads ok + + return output + + +def test_x2sys_cross_input_file_output_dataframe(mock_x2sys_home): + """ + Run x2sys_cross by passing in a filename, and output internal crossovers to + a pandas.DataFrame. + """ + with TemporaryDirectory(prefix="X2SYS", dir=os.getcwd()) as tmpdir: + tag = os.path.basename(tmpdir) + x2sys_init(tag=tag, fmtfile="xyz", force=True) + output = x2sys_cross(tracks=["@tut_ship.xyz"], tag=tag, coe="i", verbose="i") + + assert isinstance(output, pd.DataFrame) + assert output.shape == (14294, 12) + columns = list(output.columns) + assert columns[:6] == ["x", "y", "i_1", "i_2", "dist_1", "dist_2"] + assert columns[6:] == ["head_1", "head_2", "vel_1", "vel_2", "z_X", "z_M"] + + return output + + +def test_x2sys_cross_input_dataframe_output_dataframe(mock_x2sys_home, tracks): + """ + Run x2sys_cross by passing in one dataframe, and output external crossovers + to a pandas.DataFrame. + """ + with TemporaryDirectory(prefix="X2SYS", dir=os.getcwd()) as tmpdir: + tag = os.path.basename(tmpdir) + x2sys_init(tag=tag, fmtfile="xyz", force=True) + + output = x2sys_cross(tracks=tracks, tag=tag, coe="i", verbose="i") + + assert isinstance(output, pd.DataFrame) + assert output.shape == (14, 12) + columns = list(output.columns) + assert columns[:6] == ["x", "y", "i_1", "i_2", "dist_1", "dist_2"] + assert columns[6:] == ["head_1", "head_2", "vel_1", "vel_2", "z_X", "z_M"] + assert output.dtypes["i_1"].type == np.object_ + assert output.dtypes["i_2"].type == np.object_ + + return output + + +def test_x2sys_cross_input_two_dataframes(mock_x2sys_home): + """ + Run x2sys_cross by passing in two pandas.DataFrame tables with a time + column, and output external crossovers to a pandas.DataFrame. + """ + with TemporaryDirectory(prefix="X2SYS", dir=os.getcwd()) as tmpdir: + tag = os.path.basename(tmpdir) + x2sys_init( + tag=tag, fmtfile="xyz", suffix="xyzt", units=["de", "se"], force=True + ) + + # Add a time row to the x2sys fmtfile + with open(file=os.path.join(tmpdir, "xyz.fmt"), mode="a") as fmtfile: + fmtfile.write("time\ta\tN\t0\t1\t0\t%g\n") + + # Create pandas.DataFrame track tables + tracks = [] + for i in range(2): + np.random.seed(seed=i) + track = pd.DataFrame(data=np.random.rand(10, 3), columns=("x", "y", "z")) + track["time"] = pd.date_range(start=f"2020-{i}1-01", periods=10, freq="ms") + tracks.append(track) + + output = x2sys_cross(tracks=tracks, tag=tag, coe="e", verbose="i") + + assert isinstance(output, pd.DataFrame) + assert output.shape == (30, 12) + columns = list(output.columns) + assert columns[:6] == ["x", "y", "t_1", "t_2", "dist_1", "dist_2"] + assert columns[6:] == ["head_1", "head_2", "vel_1", "vel_2", "z_X", "z_M"] + assert output.dtypes["t_1"].type == np.datetime64 + assert output.dtypes["t_2"].type == np.datetime64 + + +def test_x2sys_cross_input_two_filenames(mock_x2sys_home): + """ + Run x2sys_cross by passing in two filenames, and output external crossovers + to a pandas.DataFrame. + """ + with TemporaryDirectory(prefix="X2SYS", dir=os.getcwd()) as tmpdir: + tag = os.path.basename(tmpdir) + x2sys_init(tag=tag, fmtfile="xyz", force=True) + + # Create temporary xyz files + for i in range(2): + np.random.seed(seed=i) + with open(os.path.join(os.getcwd(), f"track_{i}.xyz"), mode="w") as fname: + np.savetxt(fname=fname, X=np.random.rand(10, 3)) + + output = x2sys_cross( + tracks=["track_0.xyz", "track_1.xyz"], tag=tag, coe="e", verbose="i" + ) + + assert isinstance(output, pd.DataFrame) + assert output.shape == (24, 12) + columns = list(output.columns) + assert columns[:6] == ["x", "y", "i_1", "i_2", "dist_1", "dist_2"] + assert columns[6:] == ["head_1", "head_2", "vel_1", "vel_2", "z_X", "z_M"] + _ = [os.remove(f"track_{i}.xyz") for i in range(2)] # cleanup track files + + return output + + +def test_x2sys_cross_invalid_tracks_input_type(tracks): + """ + Run x2sys_cross using tracks input that is not a pandas.DataFrame (matrix) + or str (file) type, which would raise a GMTInvalidInput error. + """ + invalid_tracks = tracks[0].to_xarray().z + assert data_kind(invalid_tracks) == "grid" + with pytest.raises(GMTInvalidInput): + x2sys_cross(tracks=[invalid_tracks]) + + +def test_x2sys_cross_region_interpolation_numpoints(mock_x2sys_home): + """ + Test that x2sys_cross's region (R), interpolation (l) and numpoints (W) + arguments work. + """ + with TemporaryDirectory(prefix="X2SYS", dir=os.getcwd()) as tmpdir: + tag = os.path.basename(tmpdir) + x2sys_init(tag=tag, fmtfile="xyz", force=True) + output = x2sys_cross( + tracks=["@tut_ship.xyz"], + tag=tag, + coe="i", + region=[245, 250, 20, 25], + interpolation="a", # Akima spline interpolation + numpoints=5, # Use up to 5 data points in interpolation + ) + + assert isinstance(output, pd.DataFrame) + assert output.shape == (3867, 12) + # Check crossover errors (z_X) and mean value of observables (z_M) + npt.assert_allclose(output.z_X.mean(), -139.2, rtol=1e-4) + npt.assert_allclose(output.z_M.mean(), -2890.465813) + + +def test_x2sys_cross_trackvalues(mock_x2sys_home): + """ + Test that x2sys_cross's trackvalues (Z) argument work. + """ + with TemporaryDirectory(prefix="X2SYS", dir=os.getcwd()) as tmpdir: + tag = os.path.basename(tmpdir) + x2sys_init(tag=tag, fmtfile="xyz", force=True) + output = x2sys_cross(tracks=["@tut_ship.xyz"], tag=tag, trackvalues=True) + + assert isinstance(output, pd.DataFrame) + assert output.shape == (14294, 12) + # Check mean of track 1 values (z_1) and track 2 values (z_2) + npt.assert_allclose(output.z_1.mean(), -2420.569767) + npt.assert_allclose(output.z_2.mean(), -2400.357549) diff --git a/pygmt/tests/test_x2sys_init.py b/pygmt/tests/test_x2sys_init.py index c91435c0eef..2e03e4af496 100644 --- a/pygmt/tests/test_x2sys_init.py +++ b/pygmt/tests/test_x2sys_init.py @@ -1,56 +1,56 @@ -# pylint: disable=unused-argument -""" -Tests for x2sys_init. -""" -import os -from tempfile import TemporaryDirectory - -import pytest -from pygmt import x2sys_init - - -@pytest.fixture(name="mock_x2sys_home") -def fixture_mock_x2sys_home(monkeypatch): - """ - Set the X2SYS_HOME environment variable to the current working directory - for the test session. - """ - monkeypatch.setenv("X2SYS_HOME", os.getcwd()) - - -def test_x2sys_init_region_spacing(mock_x2sys_home): - """ - Test that x2sys_init's region (R) and spacing (I) sequence arguments accept - a list properly. - """ - with TemporaryDirectory(prefix="X2SYS", dir=os.getcwd()) as tmpdir: - tag = os.path.basename(tmpdir) - x2sys_init( - tag=tag, fmtfile="xyz", force=True, region=[0, 10, 20, 30], spacing=[5, 5] - ) - - with open(os.path.join(tmpdir, f"{tag}.tag"), "r") as tagpath: - tail_line = tagpath.readlines()[-1] - assert "-R0/10/20/30" in tail_line - assert "-I5/5" in tail_line - - -def test_x2sys_init_units_gap(mock_x2sys_home): - """ - Test that x2sys_init's units (N) and gap (W) arguments accept a list - properly. - """ - with TemporaryDirectory(prefix="X2SYS", dir=os.getcwd()) as tmpdir: - tag = os.path.basename(tmpdir) - x2sys_init( - tag=tag, - fmtfile="xyz", - force=True, - units=["de", "se"], - gap=["tseconds", "de"], - ) - - with open(os.path.join(tmpdir, f"{tag}.tag"), "r") as tagpath: - tail_line = tagpath.readlines()[-1] - assert "-Nse -Nde" in tail_line - assert "-Wtseconds -Wde" in tail_line +# pylint: disable=unused-argument +""" +Tests for x2sys_init. +""" +import os +from tempfile import TemporaryDirectory + +import pytest +from pygmt import x2sys_init + + +@pytest.fixture(name="mock_x2sys_home") +def fixture_mock_x2sys_home(monkeypatch): + """ + Set the X2SYS_HOME environment variable to the current working directory + for the test session. + """ + monkeypatch.setenv("X2SYS_HOME", os.getcwd()) + + +def test_x2sys_init_region_spacing(mock_x2sys_home): + """ + Test that x2sys_init's region (R) and spacing (I) sequence arguments accept + a list properly. + """ + with TemporaryDirectory(prefix="X2SYS", dir=os.getcwd()) as tmpdir: + tag = os.path.basename(tmpdir) + x2sys_init( + tag=tag, fmtfile="xyz", force=True, region=[0, 10, 20, 30], spacing=[5, 5] + ) + + with open(os.path.join(tmpdir, f"{tag}.tag"), "r") as tagpath: + tail_line = tagpath.readlines()[-1] + assert "-R0/10/20/30" in tail_line + assert "-I5/5" in tail_line + + +def test_x2sys_init_units_gap(mock_x2sys_home): + """ + Test that x2sys_init's units (N) and gap (W) arguments accept a list + properly. + """ + with TemporaryDirectory(prefix="X2SYS", dir=os.getcwd()) as tmpdir: + tag = os.path.basename(tmpdir) + x2sys_init( + tag=tag, + fmtfile="xyz", + force=True, + units=["de", "se"], + gap=["tseconds", "de"], + ) + + with open(os.path.join(tmpdir, f"{tag}.tag"), "r") as tagpath: + tail_line = tagpath.readlines()[-1] + assert "-Nse -Nde" in tail_line + assert "-Wtseconds -Wde" in tail_line diff --git a/pyproject.toml b/pyproject.toml index b147a80a911..7a7bcd69639 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -1,11 +1,11 @@ -[tool.coverage.run] -omit = ["*/tests/*", "*pygmt/__init__.py"] - -[tool.pytest.ini_options] -minversion = "6.0" -addopts = "--verbose --durations=0 --durations-min=0.2 --doctest-modules --mpl --mpl-results-path=results" - -[tool.isort] -profile = "black" -skip_gitignore = true -known_third_party = "pygmt" +[tool.coverage.run] +omit = ["*/tests/*", "*pygmt/__init__.py"] + +[tool.pytest.ini_options] +minversion = "6.0" +addopts = "--verbose --durations=0 --durations-min=0.2 --doctest-modules --mpl --mpl-results-path=results" + +[tool.isort] +profile = "black" +skip_gitignore = true +known_third_party = "pygmt" diff --git a/requirements.txt b/requirements.txt index ffdf6724f8e..552e7924417 100644 --- a/requirements.txt +++ b/requirements.txt @@ -1,6 +1,6 @@ -# Required packages -numpy -pandas -xarray -netCDF4 -packaging +# Required packages +numpy +pandas +xarray +netCDF4 +packaging diff --git a/setup.py b/setup.py index 45685e36ada..6e1eff8f171 100644 --- a/setup.py +++ b/setup.py @@ -1,62 +1,62 @@ -""" -Build and install the project. -""" -from setuptools import find_packages, setup - -NAME = "pygmt" -FULLNAME = "PyGMT" -AUTHOR = "The PyGMT Developers" -AUTHOR_EMAIL = "leouieda@gmail.com" -MAINTAINER = AUTHOR -MAINTAINER_EMAIL = AUTHOR_EMAIL -LICENSE = "BSD License" -URL = "https://github.com/GenericMappingTools/pygmt" -DESCRIPTION = "A Python interface for the Generic Mapping Tools" -KEYWORDS = "" -with open("README.rst") as f: - LONG_DESCRIPTION = "".join(f.readlines()) - -PACKAGES = find_packages(exclude=["doc"]) -SCRIPTS = [] -PACKAGE_DATA = {"pygmt.tests": ["data/*", "baseline/*"]} - -CLASSIFIERS = [ - "Development Status :: 3 - Alpha", - "Intended Audience :: Science/Research", - "Intended Audience :: Developers", - "Intended Audience :: Education", - "Topic :: Scientific/Engineering", - "Topic :: Software Development :: Libraries", - "Programming Language :: Python :: 3.7", - "Programming Language :: Python :: 3.8", - "Programming Language :: Python :: 3.9", - "License :: OSI Approved :: {}".format(LICENSE), -] -PLATFORMS = "Any" -INSTALL_REQUIRES = ["numpy", "pandas", "xarray", "netCDF4", "packaging"] -# Configuration for setuptools-scm -SETUP_REQUIRES = ["setuptools_scm"] -USE_SCM_VERSION = {"local_scheme": "node-and-date", "fallback_version": "unknown"} - -if __name__ == "__main__": - setup( - name=NAME, - fullname=FULLNAME, - description=DESCRIPTION, - long_description=LONG_DESCRIPTION, - use_scm_version=USE_SCM_VERSION, - author=AUTHOR, - author_email=AUTHOR_EMAIL, - maintainer=MAINTAINER, - maintainer_email=MAINTAINER_EMAIL, - license=LICENSE, - url=URL, - platforms=PLATFORMS, - scripts=SCRIPTS, - packages=PACKAGES, - package_data=PACKAGE_DATA, - classifiers=CLASSIFIERS, - keywords=KEYWORDS, - install_requires=INSTALL_REQUIRES, - setup_requires=SETUP_REQUIRES, - ) +""" +Build and install the project. +""" +from setuptools import find_packages, setup + +NAME = "pygmt" +FULLNAME = "PyGMT" +AUTHOR = "The PyGMT Developers" +AUTHOR_EMAIL = "leouieda@gmail.com" +MAINTAINER = AUTHOR +MAINTAINER_EMAIL = AUTHOR_EMAIL +LICENSE = "BSD License" +URL = "https://github.com/GenericMappingTools/pygmt" +DESCRIPTION = "A Python interface for the Generic Mapping Tools" +KEYWORDS = "" +with open("README.rst") as f: + LONG_DESCRIPTION = "".join(f.readlines()) + +PACKAGES = find_packages(exclude=["doc"]) +SCRIPTS = [] +PACKAGE_DATA = {"pygmt.tests": ["data/*", "baseline/*"]} + +CLASSIFIERS = [ + "Development Status :: 3 - Alpha", + "Intended Audience :: Science/Research", + "Intended Audience :: Developers", + "Intended Audience :: Education", + "Topic :: Scientific/Engineering", + "Topic :: Software Development :: Libraries", + "Programming Language :: Python :: 3.7", + "Programming Language :: Python :: 3.8", + "Programming Language :: Python :: 3.9", + "License :: OSI Approved :: {}".format(LICENSE), +] +PLATFORMS = "Any" +INSTALL_REQUIRES = ["numpy", "pandas", "xarray", "netCDF4", "packaging"] +# Configuration for setuptools-scm +SETUP_REQUIRES = ["setuptools_scm"] +USE_SCM_VERSION = {"local_scheme": "node-and-date", "fallback_version": "unknown"} + +if __name__ == "__main__": + setup( + name=NAME, + fullname=FULLNAME, + description=DESCRIPTION, + long_description=LONG_DESCRIPTION, + use_scm_version=USE_SCM_VERSION, + author=AUTHOR, + author_email=AUTHOR_EMAIL, + maintainer=MAINTAINER, + maintainer_email=MAINTAINER_EMAIL, + license=LICENSE, + url=URL, + platforms=PLATFORMS, + scripts=SCRIPTS, + packages=PACKAGES, + package_data=PACKAGE_DATA, + classifiers=CLASSIFIERS, + keywords=KEYWORDS, + install_requires=INSTALL_REQUIRES, + setup_requires=SETUP_REQUIRES, + )