From 29acaef295822b5be8b79033c7e64e96251d9eed Mon Sep 17 00:00:00 2001 From: Dongdong Tian Date: Tue, 29 Sep 2020 20:50:52 -0400 Subject: [PATCH 01/20] Refactor three xfail grdcontour tests (#637) Refractor three grdcontour tests using the @check_figures_equal decorator so that they no longer xfail. Co-authored-by: Wei Ji <23487320+weiji14@users.noreply.github.com> --- pygmt/tests/test_grdcontour.py | 45 +++++++++++++++++----------------- pygmt/tests/test_grdimage.py | 2 +- 2 files changed, 24 insertions(+), 23 deletions(-) diff --git a/pygmt/tests/test_grdcontour.py b/pygmt/tests/test_grdcontour.py index 2b45f2622d6..1ff3b3a2cb3 100644 --- a/pygmt/tests/test_grdcontour.py +++ b/pygmt/tests/test_grdcontour.py @@ -9,6 +9,7 @@ from .. import Figure from ..exceptions import GMTInvalidInput from ..datasets import load_earth_relief +from ..helpers.testing import check_figures_equal TEST_DATA_DIR = os.path.join(os.path.dirname(__file__), "data") @@ -21,49 +22,49 @@ def fixture_grid(): return load_earth_relief(registration="gridline") -@pytest.mark.xfail( - reason="Baseline image not updated to use earth relief grid in GMT 6.1.0", -) -@pytest.mark.mpl_image_compare +@check_figures_equal() def test_grdcontour(grid): """Plot a contour image using an xarray grid with fixed contour interval """ - fig = Figure() - fig.grdcontour(grid, interval="1000", projection="W0/6i") - return fig + 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 -@pytest.mark.xfail( - reason="Baseline image not updated to use earth relief grid in GMT 6.1.0", -) -@pytest.mark.mpl_image_compare +@check_figures_equal() def test_grdcontour_labels(grid): """Plot a contour image using a xarray grid with contour labels and alternate colors """ - fig = Figure() - fig.grdcontour( - grid, + fig_ref, fig_test = Figure(), Figure() + kwargs = dict( interval="1000", annotation="5000", projection="W0/6i", pen=["a1p,red", "c0.5p,black"], label_placement="d3i", ) - return fig + fig_ref.grdcontour("@earth_relief_01d_g", **kwargs) + fig_test.grdcontour(grid, **kwargs) + return fig_ref, fig_test -@pytest.mark.xfail( - reason="Baseline image not updated to use earth relief grid in GMT 6.1.0", -) -@pytest.mark.mpl_image_compare +@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)) - fig = Figure() - fig.grdcontour(grid_, interval="1000", projection="M6i") - return fig + 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 diff --git a/pygmt/tests/test_grdimage.py b/pygmt/tests/test_grdimage.py index d86798178f3..8b9fac9acd8 100644 --- a/pygmt/tests/test_grdimage.py +++ b/pygmt/tests/test_grdimage.py @@ -71,7 +71,7 @@ def test_grdimage_file(): @pytest.mark.xfail(reason="Upstream bug in GMT 6.1.1") @check_figures_equal() -def test_grdimage_xarray_shading(grid, fig_ref, fig_test): +def test_grdimage_xarray_shading(grid): """ Test that shading works well for xarray. See https://github.com/GenericMappingTools/pygmt/issues/364 From c191d3e09a43a0536b2ae14a7c8380969f4646a4 Mon Sep 17 00:00:00 2001 From: Dongdong Tian Date: Tue, 29 Sep 2020 21:26:55 -0400 Subject: [PATCH 02/20] Remove testing with GMT 6.1 branch (#582) GMT 6.1.1 is already released, and the 6.1 branch won't be updated. --- .github/workflows/ci_tests_dev.yaml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/ci_tests_dev.yaml b/.github/workflows/ci_tests_dev.yaml index 8e08e5cf836..e73021b00bd 100644 --- a/.github/workflows/ci_tests_dev.yaml +++ b/.github/workflows/ci_tests_dev.yaml @@ -21,7 +21,7 @@ jobs: matrix: python-version: [3.8] os: [ubuntu-20.04, macOS-10.15] - gmt_git_ref: [6.1, master] + gmt_git_ref: [master] env: # LD_LIBRARY_PATH: ${{ github.workspace }}/gmt/lib:$LD_LIBRARY_PATH GMT_INSTALL_DIR: ${{ github.workspace }}/gmt-install-dir From 1bf0a305dab83434e1f9cea8ead6af964af47da5 Mon Sep 17 00:00:00 2001 From: Dongdong Tian Date: Sun, 4 Oct 2020 17:04:38 -0400 Subject: [PATCH 03/20] Add blackdoc to format Python codes in docstrings (#641) * Add blackdoc to format Python codes in docstrings * Run "makeformat" to format codes in docstrings --- .github/workflows/ci_tests.yaml | 2 +- CONTRIBUTING.md | 6 ++-- Makefile | 7 ++-- environment.yml | 1 + pygmt/base_plotting.py | 2 +- pygmt/clib/conversion.py | 17 +++++----- pygmt/clib/session.py | 57 ++++++++++++++++++--------------- pygmt/figure.py | 10 +++--- pygmt/helpers/decorators.py | 36 ++++++++++++--------- pygmt/helpers/tempfile.py | 2 +- pygmt/helpers/utils.py | 21 ++++++++---- requirements-dev.txt | 1 + 12 files changed, 93 insertions(+), 69 deletions(-) diff --git a/.github/workflows/ci_tests.yaml b/.github/workflows/ci_tests.yaml index b4818fd89a1..6be5427ebb0 100644 --- a/.github/workflows/ci_tests.yaml +++ b/.github/workflows/ci_tests.yaml @@ -28,7 +28,7 @@ jobs: python-version: 3.7 - name: Install packages - run: pip install black flake8 pylint + run: pip install black blackdoc flake8 pylint - name: Formatting check (black and flake8) run: make check diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index 919b381dc26..19dd0d6f6d8 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -245,8 +245,8 @@ directory). ### Code style -We use [Black](https://github.com/ambv/black) to format the code so we don't have to -think about it. +We use [Black](https://github.com/ambv/black) and [blackdoc](https://github.com/keewis/blackdoc) +to format the code so we don't have to think about it. Black 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: @@ -265,7 +265,7 @@ common errors. The [`Makefile`](Makefile) contains rules for running both checks: ```bash -make check # Runs flake8 and black (in check mode) +make check # Runs flake8, black and blackdoc (in check mode) make lint # Runs pylint, which is a bit slower ``` diff --git a/Makefile b/Makefile index d6672c19f5e..9f5fdd40e6b 100644 --- a/Makefile +++ b/Makefile @@ -6,6 +6,7 @@ PYTEST_ARGS=--cov=$(PROJECT) --cov-config=../.coveragerc \ --doctest-modules -v --mpl --mpl-results-path=results \ --pyargs ${PYTEST_EXTRA} BLACK_FILES=$(PROJECT) setup.py doc/conf.py examples +BLACKDOC_OPTIONS=--line-length 79 FLAKE8_FILES=$(PROJECT) setup.py doc/conf.py LINT_FILES=$(PROJECT) setup.py doc/conf.py @@ -14,8 +15,8 @@ help: @echo "" @echo " install install in editable mode" @echo " test run the test suite (including doctests) and report coverage" - @echo " format run black to automatically format the code" - @echo " check run code style and quality checks (black and flake8)" + @echo " format run black and blackdoc to automatically format the code" + @echo " check run code style and quality checks (black, blackdoc and flake8)" @echo " lint run pylint for a deeper (and slower) quality check" @echo " clean clean up build and generated files" @echo "" @@ -36,9 +37,11 @@ test: format: black $(BLACK_FILES) + blackdoc $(BLACKDOC_OPTIONS) $(BLACK_FILES) check: black --check $(BLACK_FILES) + blackdoc --check $(BLACKDOC_OPTIONS) $(BLACK_FILES) flake8 $(FLAKE8_FILES) lint: diff --git a/environment.yml b/environment.yml index 0590f52b8ca..39de11039a6 100644 --- a/environment.yml +++ b/environment.yml @@ -19,6 +19,7 @@ dependencies: - pytest-mpl - coverage - black + - blackdoc - pylint - flake8 - sphinx=2.2.1 diff --git a/pygmt/base_plotting.py b/pygmt/base_plotting.py index e02eb03d310..0f7a20ad7f3 100644 --- a/pygmt/base_plotting.py +++ b/pygmt/base_plotting.py @@ -48,7 +48,7 @@ def _preprocess(self, **kwargs): # pylint: disable=no-self-use -------- >>> base = BasePlotting() - >>> base._preprocess(resolution='low') + >>> base._preprocess(resolution="low") {'resolution': 'low'} """ diff --git a/pygmt/clib/conversion.py b/pygmt/clib/conversion.py index 8dd8adbeb93..e851211050d 100644 --- a/pygmt/clib/conversion.py +++ b/pygmt/clib/conversion.py @@ -47,7 +47,7 @@ def dataarray_to_matrix(grid): >>> from pygmt.datasets import load_earth_relief >>> # Use the global Earth relief grid with 1 degree spacing - >>> grid = load_earth_relief(resolution='01d') + >>> grid = load_earth_relief(resolution="01d") >>> matrix, region, inc = dataarray_to_matrix(grid) >>> print(region) [-180.0, 180.0, -90.0, 90.0] @@ -61,7 +61,7 @@ def dataarray_to_matrix(grid): 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, region, inc = dataarray_to_matrix(grid[10:41, 30:101]) >>> matrix.flags.c_contiguous True >>> print(matrix.shape) @@ -71,7 +71,7 @@ def dataarray_to_matrix(grid): >>> 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, region, inc = dataarray_to_matrix(grid[10:41:2, 30:101:2]) >>> matrix.flags.c_contiguous True >>> print(matrix.shape) @@ -231,11 +231,12 @@ def kwargs_to_ctypes_array(argument, kwargs, dtype): -------- >>> import ctypes as ct - >>> value = kwargs_to_ctypes_array('bla', {'bla': [10, 10]}, ct.c_long*2) + >>> 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) + ... "swallow", {"bla": 1, "foo": [20, 30]}, ct.c_int * 2 + ... ) >>> print(should_be_none) None @@ -313,9 +314,9 @@ def array_to_datetime(array): >>> # Mixed datetime types >>> x = [ - ... "2018-01-01", - ... np.datetime64("2018-01-01"), - ... datetime.datetime(2018, 1, 1), + ... "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'], diff --git a/pygmt/clib/session.py b/pygmt/clib/session.py index d5ce3a8a4cb..73cead5b47d 100644 --- a/pygmt/clib/session.py +++ b/pygmt/clib/session.py @@ -270,8 +270,9 @@ def get_libgmt_func(self, name, argtypes=None, restype=None): >>> 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) + ... func = lib.get_libgmt_func( + ... "GMT_Destroy_Session", argtypes=[c_void_p], restype=c_int + ... ) >>> type(func) ._FuncPtr'> @@ -702,15 +703,15 @@ def _check_dtype_and_dim(self, array, ndim): -------- >>> import numpy as np - >>> data = np.array([1, 2, 3], dtype='float64') + >>> 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') + >>> data = np.ones((5, 2), dtype="float32") >>> with Session() as ses: ... gmttype = ses._check_dtype_and_dim(data, ndim=2) - ... gmttype == ses['GMT_FLOAT'] + ... gmttype == ses["GMT_FLOAT"] True """ @@ -1022,23 +1023,23 @@ def open_virtual_file(self, family, geometry, direction, data): >>> 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' + ... family = "GMT_IS_DATASET|GMT_VIA_VECTOR" + ... geometry = "GMT_IS_POINT" ... dataset = lib.create_data( ... family=family, ... geometry=geometry, - ... mode='GMT_CONTAINER_ONLY', + ... 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) + ... 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) + ... args = "{} ->{}".format(vfile, ofile.name) + ... lib.call_module("info", args) ... print(ofile.read().strip()) : N = 5 <0/4> <5/9> @@ -1133,7 +1134,7 @@ def virtualfile_from_vectors(self, *vectors): ... # Send the output to a file so that we can read it ... with GMTTempFile() as fout: ... ses.call_module( - ... 'info', '{} ->{}'.format(fin, fout.name) + ... "info", "{} ->{}".format(fin, fout.name) ... ) ... print(fout.read().strip()) : N = 3 <1/3> <4/6> <7/9> @@ -1245,7 +1246,7 @@ def virtualfile_from_matrix(self, matrix): ... # Send the output to a file so that we can read it ... with GMTTempFile() as fout: ... ses.call_module( - ... 'info', '{} ->{}'.format(fin, fout.name) + ... "info", "{} ->{}".format(fin, fout.name) ... ) ... print(fout.read().strip()) : N = 4 <0/9> <1/10> <2/11> @@ -1314,7 +1315,7 @@ def virtualfile_from_grid(self, grid): >>> from pygmt.datasets import load_earth_relief >>> from pygmt.helpers import GMTTempFile - >>> data = load_earth_relief(resolution='01d') + >>> data = load_earth_relief(resolution="01d") >>> print(data.shape) (180, 360) >>> print(data.lon.values.min(), data.lon.values.max()) @@ -1327,8 +1328,8 @@ def virtualfile_from_grid(self, grid): ... 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) + ... 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 @@ -1378,22 +1379,27 @@ def extract_region(self): >>> import pygmt >>> fig = pygmt.Figure() - >>> fig.coast(region=[0, 10, -20, -10], projection="M6i", frame=True, - ... land='black') + >>> 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])) + >>> 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') + >>> 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])) + >>> 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 @@ -1401,11 +1407,12 @@ def extract_region(self): region to multiples of 5): >>> fig = pygmt.Figure() - >>> fig.coast(region='US.HI+r5', projection="M6i", frame=True, - ... land='black') + >>> 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])) + >>> print(", ".join(["{:.2f}".format(x) for x in wesn])) -165.00, -150.00, 15.00, 25.00 """ diff --git a/pygmt/figure.py b/pygmt/figure.py index d32588ae85d..7f91492aad3 100644 --- a/pygmt/figure.py +++ b/pygmt/figure.py @@ -43,21 +43,21 @@ class Figure(BasePlotting): -------- >>> fig = Figure() - >>> fig.basemap(region=[0, 360, -90, 90], projection='W7i', frame=True) + >>> 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') + >>> os.path.exists("my-figure.png") True - >>> os.remove('my-figure.png') + >>> 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) + >>> 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)) + >>> print(", ".join("{:.2f}".format(i) for i in fig.region)) 122.94, 145.82, 20.53, 45.52 """ diff --git a/pygmt/helpers/decorators.py b/pygmt/helpers/decorators.py index 376952891b4..28e8fafd896 100644 --- a/pygmt/helpers/decorators.py +++ b/pygmt/helpers/decorators.py @@ -154,7 +154,7 @@ def fmt_docstring(module_func): -------- >>> @fmt_docstring - ... @use_alias(R='region', J='projection') + ... @use_alias(R="region", J="projection") ... def gmtinfo(**kwargs): ... ''' ... My nice module. @@ -230,19 +230,19 @@ def use_alias(**aliases): Examples -------- - >>> @use_alias(R='region', J='projection') + >>> @use_alias(R="region", J="projection") ... def my_module(**kwargs): - ... print('R =', kwargs['R'], 'J =', kwargs['J']) - >>> my_module(R='bla', J='meh') + ... print("R =", kwargs["R"], "J =", kwargs["J"]) + >>> my_module(R="bla", J="meh") R = bla J = meh - >>> my_module(region='bla', J='meh') + >>> my_module(region="bla", J="meh") R = bla J = meh - >>> my_module(R='bla', projection='meh') + >>> my_module(R="bla", projection="meh") R = bla J = meh - >>> my_module(region='bla', projection='meh') + >>> my_module(region="bla", projection="meh") R = bla J = meh >>> my_module( - ... region='bla', projection='meh', J="bla" + ... region="bla", projection="meh", J="bla" ... ) # doctest: +NORMALIZE_WHITESPACE Traceback (most recent call last): ... @@ -309,21 +309,25 @@ def kwargs_to_strings(convert_bools=True, **conversions): -------- >>> @kwargs_to_strings( - ... R='sequence', i='sequence_comma', files='sequence_space' + ... 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('}') + ... 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)) + ... 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') + >>> module(R="5/6/7/8") {'R': '5/6/7/8'} >>> module(P=True) {'P': ''} diff --git a/pygmt/helpers/tempfile.py b/pygmt/helpers/tempfile.py index a17293eb460..7dd1b7c3710 100644 --- a/pygmt/helpers/tempfile.py +++ b/pygmt/helpers/tempfile.py @@ -45,7 +45,7 @@ class GMTTempFile: >>> 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') + ... np.savetxt(tmpfile.name, (x, y, z), fmt="%.1f") ... lines = tmpfile.read() ... print(lines) ... nx, ny, nz = tmpfile.loadtxt(unpack=True, dtype=float) diff --git a/pygmt/helpers/utils.py b/pygmt/helpers/utils.py index 5004e1b24cf..79b65fc0048 100644 --- a/pygmt/helpers/utils.py +++ b/pygmt/helpers/utils.py @@ -51,7 +51,7 @@ def data_kind(data, x=None, y=None, z=None): '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) + >>> data_kind(data="my-data-file.txt", x=None, y=None) 'file' >>> data_kind(data=xr.DataArray(np.random.rand(4, 3))) 'grid' @@ -94,7 +94,7 @@ def dummy_context(arg): Examples -------- - >>> with dummy_context('some argument') as temp: + >>> with dummy_context("some argument") as temp: ... print(temp) some argument @@ -127,11 +127,18 @@ def build_arg_string(kwargs): Examples -------- - >>> print(build_arg_string(dict(R='1/2/3/4', J="X4i", P='', E=200))) + >>> print(build_arg_string(dict(R="1/2/3/4", J="X4i", P="", E=200))) -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')))) + >>> 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 """ @@ -164,7 +171,7 @@ def is_nonstr_iter(value): Examples -------- - >>> is_nonstr_iter('abc') + >>> is_nonstr_iter("abc") False >>> is_nonstr_iter(10) False diff --git a/requirements-dev.txt b/requirements-dev.txt index 76352e17224..f6a53ef0310 100644 --- a/requirements-dev.txt +++ b/requirements-dev.txt @@ -7,6 +7,7 @@ pytest-cov pytest-mpl coverage black +blackdoc pylint flake8 sphinx=2.2.1 From b1ef6bf9eeb87003f5d78a2e9e757ec428b7f020 Mon Sep 17 00:00:00 2001 From: Wei Ji <23487320+weiji14@users.noreply.github.com> Date: Thu, 8 Oct 2020 14:33:36 +1300 Subject: [PATCH 04/20] Add Liam's ROSES 2020 PyGMT talk to website (#643) Update main README and Overview page to include the PyGMT talk Liam gave at Remote Online Sessions for Emerging Seismologists (ROSES) 2020. --- README.rst | 16 ++++++++++++++++ doc/overview.rst | 11 +++++++++++ 2 files changed, 27 insertions(+) diff --git a/README.rst b/README.rst index d0c04e6e224..0db6ff736a3 100644 --- a/README.rst +++ b/README.rst @@ -38,6 +38,22 @@ PyGMT .. 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 ---------- diff --git a/doc/overview.rst b/doc/overview.rst index 7296bb8a19d..0262017724a 100644 --- a/doc/overview.rst +++ b/doc/overview.rst @@ -35,6 +35,17 @@ 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. From 96a32b9563c8abd3581377918e0b96998f4144f2 Mon Sep 17 00:00:00 2001 From: Dongdong Tian Date: Thu, 8 Oct 2020 00:59:11 -0400 Subject: [PATCH 05/20] Disallow passing arguments like -XNone to GMT (#639) Arguments like -XNone are invalid for GMT. This PR updates the function build_arg_string and checks if any value is None. If yes, remove the argument. --- pygmt/helpers/utils.py | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) diff --git a/pygmt/helpers/utils.py b/pygmt/helpers/utils.py index 79b65fc0048..a72dcd6bb51 100644 --- a/pygmt/helpers/utils.py +++ b/pygmt/helpers/utils.py @@ -127,7 +127,11 @@ def build_arg_string(kwargs): Examples -------- - >>> print(build_arg_string(dict(R="1/2/3/4", J="X4i", P="", E=200))) + >>> 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( @@ -147,6 +151,8 @@ def build_arg_string(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])) From 3613b1982790cb9d4c416b816b0c447d83fbf8cf Mon Sep 17 00:00:00 2001 From: Dongdong Tian Date: Sat, 10 Oct 2020 23:15:19 -0400 Subject: [PATCH 06/20] Add mini-galleries for methods and functions (#648) This PR updates the autosummary templates, to show mini-galleries of all methods and functions at the end of the documentation page. --- doc/_templates/autosummary/function.rst | 1 + doc/_templates/autosummary/method.rst | 11 +++++++++++ 2 files changed, 12 insertions(+) create mode 100644 doc/_templates/autosummary/method.rst diff --git a/doc/_templates/autosummary/function.rst b/doc/_templates/autosummary/function.rst index 6a6cfd4d443..e4ae37539e8 100644 --- a/doc/_templates/autosummary/function.rst +++ b/doc/_templates/autosummary/function.rst @@ -4,6 +4,7 @@ .. autofunction:: {{ objname }} +.. include:: backreferences/{{ fullname }}.examples .. raw:: html diff --git a/doc/_templates/autosummary/method.rst b/doc/_templates/autosummary/method.rst new file mode 100644 index 00000000000..9c5d7313e32 --- /dev/null +++ b/doc/_templates/autosummary/method.rst @@ -0,0 +1,11 @@ +{{ fullname | escape | underline }} + +.. currentmodule:: {{ module }} + +.. automethod:: {{ objname }} + +.. include:: backreferences/{{ fullname }}.examples + +.. raw:: html + +
From a6a09198404899d4d4f672cbab366b36a903249f Mon Sep 17 00:00:00 2001 From: Dongdong Tian Date: Wed, 14 Oct 2020 21:27:04 -0400 Subject: [PATCH 07/20] Mark xarray shading test fail for GMT<=6.1.1 (#649) The xarray shading issue in #364 was fixed by upstream GMT in GenericMappingTools/gmt#4328. This PR updates the pytest xfail condition so that the xarray shading test is expected to xfail only for GMT<=6.1.1. --- pygmt/tests/test_grdimage.py | 11 +++++++++-- 1 file changed, 9 insertions(+), 2 deletions(-) diff --git a/pygmt/tests/test_grdimage.py b/pygmt/tests/test_grdimage.py index 8b9fac9acd8..c4658dc38e7 100644 --- a/pygmt/tests/test_grdimage.py +++ b/pygmt/tests/test_grdimage.py @@ -4,12 +4,16 @@ import numpy as np import pytest import xarray as xr +from packaging.version import Version -from .. import Figure +from .. import Figure, clib from ..datasets import load_earth_relief from ..exceptions import GMTInvalidInput from ..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(): @@ -69,7 +73,10 @@ def test_grdimage_file(): return fig -@pytest.mark.xfail(reason="Upstream bug in GMT 6.1.1") +@pytest.mark.xfail( + reason="Upstream bug in GMT 6.1.1", + condition=gmt_version <= Version("6.1.1"), +) @check_figures_equal() def test_grdimage_xarray_shading(grid): """ From 0453ea0322ebd869de8b99a2474fe3f498f7f82a Mon Sep 17 00:00:00 2001 From: Conor Bacon Date: Thu, 15 Oct 2020 03:34:49 +0100 Subject: [PATCH 08/20] Add tutorial for pygmt.Figure.text (#480) Add tutorial detailing how to add text annotations to PyGMT figures. Includes changing the font style, justification, angle, and fill. Example showcases Borneo island and the surrounding seas. * Simplify text positioning section to use e.g. TL, TC, TR, etc * Make the examples.txt file on the fly * Use `fill` instead of G, and point advanced users to GMT cookbook * Some more stylistic fixes to the text documentation Co-Authored-By: Dongdong Tian Co-Authored-By: Conor Bacon Co-authored-by: Wei Ji --- doc/index.rst | 1 + examples/tutorials/text.py | 143 +++++++++++++++++++++++++++++++++++++ 2 files changed, 144 insertions(+) create mode 100644 examples/tutorials/text.py diff --git a/doc/index.rst b/doc/index.rst index b57c05e992d..4c3fd3d34e3 100644 --- a/doc/index.rst +++ b/doc/index.rst @@ -32,6 +32,7 @@ projections/index.rst tutorials/coastlines.rst tutorials/plot.rst + tutorials/text.rst .. toctree:: :maxdepth: 2 diff --git a/examples/tutorials/text.py b/examples/tutorials/text.py new file mode 100644 index 00000000000..403f7ec8e72 --- /dev/null +++ b/examples/tutorials/text.py @@ -0,0 +1,143 @@ +""" +Plotting text +============= + +It is often useful to add annotations to a map plot. This is handled by +:meth:`pygmt.Figure.text`. +""" + +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`` arguments to specify the annotation text and position in the +# projection frame. ``text`` accepts 'str' types, while ``x``, and ``y`` +# accepts either 'int'/'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`` argument. +# +# 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/justiry 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`` argument +# -------------------- +# +# ``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`` argument +# ------------------ +# ``angle`` is an optional argument 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`` argument +# ----------------- +# +# ``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! From 713ea727ceb16289bce2f2e1030bd5ba466c2ded Mon Sep 17 00:00:00 2001 From: Dongdong Tian Date: Thu, 15 Oct 2020 14:51:43 -0400 Subject: [PATCH 09/20] Let plot() accept record-by-record transparency (#626) plot() can accept a list of transparency so that each symbol can have its own transparency level. --- pygmt/base_plotting.py | 8 +++ pygmt/tests/test_plot.py | 102 ++++++++++++++++++++++++++++++++++++++- 2 files changed, 109 insertions(+), 1 deletion(-) diff --git a/pygmt/base_plotting.py b/pygmt/base_plotting.py index 0f7a20ad7f3..46c1b429ae7 100644 --- a/pygmt/base_plotting.py +++ b/pygmt/base_plotting.py @@ -15,6 +15,7 @@ fmt_docstring, use_alias, kwargs_to_strings, + is_nonstr_iter, ) @@ -684,6 +685,9 @@ def plot(self, x=None, y=None, data=None, sizes=None, direction=None, **kwargs): {p} {t} + *transparency* can also be a 1d array to set varying transparency + for symbols. + """ kwargs = self._preprocess(**kwargs) @@ -706,6 +710,10 @@ def plot(self, x=None, y=None, data=None, sizes=None, direction=None, **kwargs): ) 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": diff --git a/pygmt/tests/test_plot.py b/pygmt/tests/test_plot.py index 561d86fde4a..29000faee31 100644 --- a/pygmt/tests/test_plot.py +++ b/pygmt/tests/test_plot.py @@ -13,6 +13,8 @@ from .. import Figure from ..exceptions import GMTInvalidInput +from ..helpers import GMTTempFile +from ..helpers.testing import check_figures_equal TEST_DATA_DIR = os.path.join(os.path.dirname(__file__), "data") @@ -128,7 +130,7 @@ def test_plot_projection(data): @pytest.mark.mpl_image_compare def test_plot_colors(data, region): - "Plot the data using z as sizes" + "Plot the data using z as colors" fig = Figure() fig.plot( x=data[:, 0], @@ -194,6 +196,104 @@ def test_plot_colors_sizes_proj(data, region): 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 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" From 203e6470866c82f41b2f5fbff376a6a6872a241c Mon Sep 17 00:00:00 2001 From: Dongdong Tian Date: Thu, 15 Oct 2020 15:36:42 -0400 Subject: [PATCH 10/20] Refactor xfail tests to avoid storing baseline images (#603) This PR refactors the xfail/fail tests to let the tests pass and avoid storing baseline images. The baseline images are generated by calling the same modules using plain text or grid files as input, and using single-character arguments. * Refactor basemap tests * Refactor legend tests * Refactor coast tests * Refactor colorbar tests * Refactor makecpt tests * Refactor logo tests * Refactor grdcontour tests * Refactor plot tests Co-authored-by: Wei Ji <23487320+weiji14@users.noreply.github.com> --- pygmt/tests/test_basemap.py | 15 +++++++------ pygmt/tests/test_coast.py | 16 ++++++++------ pygmt/tests/test_colorbar.py | 18 +++++++++------- pygmt/tests/test_grdcontour.py | 30 +++++++++++++++++--------- pygmt/tests/test_legend.py | 39 ++++++++++++++++++++++------------ pygmt/tests/test_logo.py | 27 +++++++++++++++-------- pygmt/tests/test_makecpt.py | 17 +++++++++------ pygmt/tests/test_plot.py | 18 ++++++++++++---- 8 files changed, 114 insertions(+), 66 deletions(-) diff --git a/pygmt/tests/test_basemap.py b/pygmt/tests/test_basemap.py index 989c181d809..bc0d43059b1 100644 --- a/pygmt/tests/test_basemap.py +++ b/pygmt/tests/test_basemap.py @@ -4,6 +4,7 @@ import pytest from .. import Figure +from ..helpers.testing import check_figures_equal from ..exceptions import GMTInvalidInput @@ -54,15 +55,15 @@ def test_basemap_power_axis(): return fig -@pytest.mark.xfail( - reason="Baseline image not updated to use earth relief grid in GMT 6.1.0", -) -@pytest.mark.mpl_image_compare +@check_figures_equal() def test_basemap_polar(): "Create a polar basemap plot" - fig = Figure() - fig.basemap(R="0/360/0/1000", J="P6i", B="afg") - return fig + 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 @pytest.mark.mpl_image_compare diff --git a/pygmt/tests/test_coast.py b/pygmt/tests/test_coast.py index 7b44ca2619c..1f83f94d71e 100644 --- a/pygmt/tests/test_coast.py +++ b/pygmt/tests/test_coast.py @@ -4,6 +4,7 @@ import pytest from .. import Figure +from ..helpers.testing import check_figures_equal @pytest.mark.mpl_image_compare @@ -25,15 +26,16 @@ def test_coast(): return fig -@pytest.mark.xfail( - reason="Baseline image not updated to use earth relief grid in GMT 6.1.0", -) -@pytest.mark.mpl_image_compare +@check_figures_equal() def test_coast_iceland(): "Test passing in R as a list" - fig = Figure() - fig.coast(R=[-30, -10, 60, 65], J="m1c", B=True, G="p28+r100") - return fig + fig_ref, fig_test = Figure(), Figure() + # Use single-character arguments for the reference image + fig_ref.coast(R="-30/-10/60/65", J="m1c", B="", G="p28+r100") + fig_test.coast( + region=[-30, -10, 60, 65], projection="m1c", frame=True, land="p28+r100" + ) + return fig_ref, fig_test @pytest.mark.mpl_image_compare diff --git a/pygmt/tests/test_colorbar.py b/pygmt/tests/test_colorbar.py index 593126208cc..f03002eb078 100644 --- a/pygmt/tests/test_colorbar.py +++ b/pygmt/tests/test_colorbar.py @@ -4,6 +4,7 @@ import pytest from .. import Figure +from ..helpers.testing import check_figures_equal @pytest.mark.mpl_image_compare @@ -37,18 +38,19 @@ def test_colorbar_positioned_using_map_coordinates(): return fig -@pytest.mark.xfail( - reason="Baseline image not updated to use earth relief grid in GMT 6.1.0", -) -@pytest.mark.mpl_image_compare +@check_figures_equal() def test_colorbar_positioned_using_justification_code(): """ Create colorbar at Top Center inside the map frame with length 2cm. """ - fig = Figure() - fig.basemap(region=[2, 4, 6, 8], projection="t0/2c", frame=True) - fig.colorbar(cmap="rainbow", position="jTC+w2c") - return fig + fig_ref, fig_test = Figure(), Figure() + # Use single-character arguments for the reference image + fig_ref.basemap(R="2/4/6/8", J="t0/2c", B="") + fig_ref.colorbar(C="rainbow", D="jTC+w2c") + + fig_test.basemap(region=[2, 4, 6, 8], projection="t0/2c", frame=True) + fig_test.colorbar(cmap="rainbow", position="jTC+w2c") + return fig_ref, fig_test @pytest.mark.mpl_image_compare diff --git a/pygmt/tests/test_grdcontour.py b/pygmt/tests/test_grdcontour.py index 1ff3b3a2cb3..fee5e50d9db 100644 --- a/pygmt/tests/test_grdcontour.py +++ b/pygmt/tests/test_grdcontour.py @@ -82,14 +82,23 @@ def test_grdcontour_file(): return fig -@pytest.mark.xfail( - reason="Baseline image not updated to use earth relief grid in GMT 6.1.0", -) -@pytest.mark.mpl_image_compare +@check_figures_equal() def test_grdcontour_interval_file_full_opts(): """ Plot based on external contour level file """ - fig = Figure() - comargs = { + 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", @@ -97,11 +106,12 @@ def test_grdcontour_interval_file_full_opts(): "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"]) - fig.grdcontour(**comargs, limit=(-25000, -1), pen=["a1p,blue", "c0.5p,blue"]) - - fig.grdcontour(**comargs, limit="0", pen=["a1p,black", "c0.5p,black"]) - return fig + return fig_ref, fig_test def test_grdcontour_fails(): diff --git a/pygmt/tests/test_legend.py b/pygmt/tests/test_legend.py index 1fa98d6733a..2044e7bb2a7 100644 --- a/pygmt/tests/test_legend.py +++ b/pygmt/tests/test_legend.py @@ -6,6 +6,7 @@ from .. import Figure from ..exceptions import GMTInvalidInput from ..helpers import GMTTempFile +from ..helpers.testing import check_figures_equal @pytest.mark.mpl_image_compare @@ -44,32 +45,42 @@ def test_legend_default_position(): return fig -@pytest.mark.xfail( - reason="Baseline image not updated to use earth relief grid in GMT 6.1.0", -) -@pytest.mark.mpl_image_compare +@check_figures_equal() def test_legend_entries(): """ Test different marker types/shapes. """ + fig_ref, fig_test = Figure(), Figure() - fig = Figure() - - fig.basemap(projection="x1i", region=[0, 7, 3, 7], frame=True) + # 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.plot( + 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", - l="Apples", + 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") + 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 + return fig_ref, fig_test @pytest.mark.mpl_image_compare diff --git a/pygmt/tests/test_logo.py b/pygmt/tests/test_logo.py index d4185f115c2..7fc40c77715 100644 --- a/pygmt/tests/test_logo.py +++ b/pygmt/tests/test_logo.py @@ -5,23 +5,32 @@ from .. import Figure from ..exceptions import GMTInvalidInput +from ..helpers.testing import check_figures_equal -@pytest.mark.mpl_image_compare +@check_figures_equal() def test_logo(): "Plot a GMT logo of a 2 inch width as a stand-alone plot" - fig = Figure() - fig.logo(D="x0/0+w2i") - return fig + 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 -@pytest.mark.mpl_image_compare +@check_figures_equal() def test_logo_on_a_map(): "Plot a GMT logo in the upper right corner of a map" - fig = Figure() - fig.coast(region=[-90, -70, 0, 20], projection="M6i", land="chocolate", frame=True) - fig.logo(D="jTR+o0.1i/0.1i+w3i", F=True) - return fig + 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 def test_logo_fails(): diff --git a/pygmt/tests/test_makecpt.py b/pygmt/tests/test_makecpt.py index 105b6e75ed4..3c1716a1f8d 100644 --- a/pygmt/tests/test_makecpt.py +++ b/pygmt/tests/test_makecpt.py @@ -10,6 +10,7 @@ from ..datasets import load_earth_relief from ..exceptions import GMTInvalidInput from ..helpers import GMTTempFile +from ..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") @@ -62,19 +63,21 @@ def test_makecpt_to_plot_grid(grid): return fig -@pytest.mark.xfail( - reason="Baseline image not updated to use earth relief grid in GMT 6.1.0", -) -@pytest.mark.mpl_image_compare +@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 """ - fig = Figure() + # 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.grdimage(grid, projection="W0/6i") - return fig + fig_test.grdimage(grid, projection="W0/6i") + return fig_ref, fig_test def test_makecpt_output_to_cpt_file(): diff --git a/pygmt/tests/test_plot.py b/pygmt/tests/test_plot.py index 29000faee31..196cf46037f 100644 --- a/pygmt/tests/test_plot.py +++ b/pygmt/tests/test_plot.py @@ -128,11 +128,21 @@ def test_plot_projection(data): return fig -@pytest.mark.mpl_image_compare +@check_figures_equal() def test_plot_colors(data, region): "Plot the data using z as colors" - fig = Figure() - fig.plot( + 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], @@ -142,7 +152,7 @@ def test_plot_colors(data, region): cmap="cubhelix", frame="af", ) - return fig + return fig_ref, fig_test @pytest.mark.mpl_image_compare From 0eab91fe66285892459b508823b770f00a4c8b71 Mon Sep 17 00:00:00 2001 From: Dongdong Tian Date: Thu, 15 Oct 2020 16:59:16 -0400 Subject: [PATCH 11/20] Add more tests for xarray grid shading (#650) This PR adds more tests for xarray grid shading. Valid `shading` values are: - boolean - a constant intensity - some modifiers to automatically derive an intensity grid from input grid - an intensity grid with modifiers - an intensity grid in the (-1,+1) range This PR covers the first 4 cases only. --- pygmt/tests/test_grdimage.py | 15 ++++++++++++--- 1 file changed, 12 insertions(+), 3 deletions(-) diff --git a/pygmt/tests/test_grdimage.py b/pygmt/tests/test_grdimage.py index c4658dc38e7..7a29bc0a572 100644 --- a/pygmt/tests/test_grdimage.py +++ b/pygmt/tests/test_grdimage.py @@ -78,10 +78,19 @@ def test_grdimage_file(): condition=gmt_version <= Version("6.1.1"), ) @check_figures_equal() -def test_grdimage_xarray_shading(grid): +@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. - See https://github.com/GenericMappingTools/pygmt/issues/364 + + 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( @@ -89,7 +98,7 @@ def test_grdimage_xarray_shading(grid): frame=True, projection="Cyl_stere/6i", cmap="geo", - shading=True, + shading=shading, ) fig_ref.grdimage("@earth_relief_01d_g", **kwargs) From e23030820e13f568ab0c3cf73221a6a97606d742 Mon Sep 17 00:00:00 2001 From: Dongdong Tian Date: Thu, 15 Oct 2020 22:28:50 -0400 Subject: [PATCH 12/20] Improve documentation of Figure.logo() (#651) * Improve documentation of Figure.logo() - Fix a broken link to gmtlogo documentation - Add option **-V** - Remove option **-p** (not listed in the documentation) - Add option **-S** (new in GMT 6.0) - Option **-D** is no longer required (tested with GMT>=6.1.1) - Remove the test for **-D** Co-authored-by: Wei Ji <23487320+weiji14@users.noreply.github.com> --- pygmt/base_plotting.py | 21 ++++++++++++++------- pygmt/tests/test_logo.py | 11 ----------- 2 files changed, 14 insertions(+), 18 deletions(-) diff --git a/pygmt/base_plotting.py b/pygmt/base_plotting.py index 46c1b429ae7..13cebbc8d20 100644 --- a/pygmt/base_plotting.py +++ b/pygmt/base_plotting.py @@ -887,25 +887,26 @@ def basemap(self, **kwargs): @use_alias( R="region", J="projection", - U="timestamp", D="position", F="box", + S="style", + U="timestamp", + V="verbose", X="xshift", Y="yshift", - p="perspective", t="transparency", ) @kwargs_to_strings(R="sequence", p="sequence") def logo(self, **kwargs): """ - Place the GMT graphics logo on a map. + 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:`logo.html` + Full option list at :gmt-docs:`gmtlogo.html`. {aliases} @@ -919,15 +920,21 @@ def logo(self, **kwargs): box : bool or str Without further options, 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} - {p} {t} """ kwargs = self._preprocess(**kwargs) - if "D" not in kwargs: - raise GMTInvalidInput("Option D must be specified.") with Session() as lib: lib.call_module("logo", build_arg_string(kwargs)) diff --git a/pygmt/tests/test_logo.py b/pygmt/tests/test_logo.py index 7fc40c77715..6cfc7f4cd0e 100644 --- a/pygmt/tests/test_logo.py +++ b/pygmt/tests/test_logo.py @@ -1,10 +1,7 @@ """ Tests for fig.logo """ -import pytest - from .. import Figure -from ..exceptions import GMTInvalidInput from ..helpers.testing import check_figures_equal @@ -31,11 +28,3 @@ def test_logo_on_a_map(): ) fig_test.logo(position="jTR+o0.1i/0.1i+w3i", box=True) return fig_ref, fig_test - - -def test_logo_fails(): - "Make sure logo raises an exception when D is not given" - fig = Figure() - with pytest.raises(GMTInvalidInput): - fig.logo() - return fig From 060b7233c766822dd8fddcc08ac5613eb30f270b Mon Sep 17 00:00:00 2001 From: Dongdong Tian Date: Tue, 20 Oct 2020 17:34:39 -0400 Subject: [PATCH 13/20] Add a gallery example for varying transparent points (#654) Co-authored-by: Wei Ji <23487320+weiji14@users.noreply.github.com> --- examples/gallery/plot/points-transparency.py | 25 ++++++++++++++++++++ 1 file changed, 25 insertions(+) create mode 100644 examples/gallery/plot/points-transparency.py diff --git a/examples/gallery/plot/points-transparency.py b/examples/gallery/plot/points-transparency.py new file mode 100644 index 00000000000..be6b0f56e36 --- /dev/null +++ b/examples/gallery/plot/points-transparency.py @@ -0,0 +1,25 @@ +""" +Points with varying transparency +-------------------------------- + +Points can be plotted with different transparency levels by passing in an array to the +``transparency`` argument 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() From 3b64511098c439dc9ec90f6a75c62a637a27446d Mon Sep 17 00:00:00 2001 From: Dongdong Tian Date: Thu, 22 Oct 2020 07:29:15 -0400 Subject: [PATCH 14/20] Fix Github to GitHub (#659) We should use "github" or "GitHub", not "Github". --- .github/PULL_REQUEST_TEMPLATE.md | 2 +- .github/workflows/cache_data.yaml | 4 ++-- .github/workflows/ci_tests.yaml | 4 ++-- .github/workflows/ci_tests_dev.yaml | 4 ++-- .travis.yml | 2 +- CONTRIBUTING.md | 6 +++--- MAINTENANCE.md | 20 ++++++++++---------- README.rst | 8 ++++---- doc/_templates/breadcrumbs.html | 2 +- doc/changes.rst | 8 ++++---- doc/install.rst | 4 ++-- versioneer.py | 2 +- 12 files changed, 33 insertions(+), 33 deletions(-) diff --git a/.github/PULL_REQUEST_TEMPLATE.md b/.github/PULL_REQUEST_TEMPLATE.md index 808b806cb3d..41edaf59d5a 100644 --- a/.github/PULL_REQUEST_TEMPLATE.md +++ b/.github/PULL_REQUEST_TEMPLATE.md @@ -4,7 +4,7 @@ - + Fixes # diff --git a/.github/workflows/cache_data.yaml b/.github/workflows/cache_data.yaml index 8682712faa7..9f4d779c713 100644 --- a/.github/workflows/cache_data.yaml +++ b/.github/workflows/cache_data.yaml @@ -37,8 +37,8 @@ jobs: @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 + # Upload the downloaded files as artifacts to GitHub + - name: Upload artifacts to GitHub uses: actions/upload-artifact@v2 with: name: gmt-cache diff --git a/.github/workflows/ci_tests.yaml b/.github/workflows/ci_tests.yaml index 6be5427ebb0..5d941d2bb3b 100644 --- a/.github/workflows/ci_tests.yaml +++ b/.github/workflows/ci_tests.yaml @@ -89,8 +89,8 @@ jobs: shell: bash -l {0} run: conda list - # Download cached remote files (artifacts) from Github - - name: Download remote data from Github + # Download cached remote files (artifacts) from GitHub + - name: Download remote data from GitHub uses: dawidd6/action-download-artifact@v2.6.3 with: workflow: cache_data.yaml diff --git a/.github/workflows/ci_tests_dev.yaml b/.github/workflows/ci_tests_dev.yaml index e73021b00bd..98790ab96aa 100644 --- a/.github/workflows/ci_tests_dev.yaml +++ b/.github/workflows/ci_tests_dev.yaml @@ -61,8 +61,8 @@ jobs: env: GMT_GIT_REF: ${{ matrix.gmt_git_ref }} - # Download cached remote files (artifacts) from Github - - name: Download remote data from Github + # Download cached remote files (artifacts) from GitHub + - name: Download remote data from GitHub uses: dawidd6/action-download-artifact@v2.6.3 with: workflow: cache_data.yaml diff --git a/.travis.yml b/.travis.yml index aa23e5e261d..ab57e494a9c 100644 --- a/.travis.yml +++ b/.travis.yml @@ -18,7 +18,7 @@ branches: env: global: # Encrypted variables - # Github Token for pushing the built docs (GH_TOKEN) + # GitHub Token for pushing the built docs (GH_TOKEN) - secure: "QII0477v0mmCCW3qSNXLCOtqraJaCICtSghiyrxYsuUdJTrXzXBNhX2KLIjcKYXOK1HdwYOFGf8xBVLl44clHlAW7R32ecEGeTJizr0yqTBvT3rNG1Xb7+E6jdXqrIs//PmPRaF8zOZxPl1SJKDK4jJpCx5HnAflg7wl/6tQLD6K3/dQ6FG2s3UKsc8o4qchOiEfxYhOuKo3jt2S0HdsNAQFw3mFHCCrclxDr3llSQtWSY0mirZnta7AI4nMvzxl2nUhdHEpxgzIjWxCWLAwmj3/NxLz0VSgNCtl2bNYk6AYrc5RcANGk2fcYaZr9mTU3Aax60S4389B39Pq95hBN21jYdbw9vCN810dYpTUk2siLysx8gF6r2JWEF8SskXlF79r3phtaFTMOS4GqeiuwjifZeaLAL/H1PTQFDDG/UKEwBpLuzrPMDw/84iRtyWKqWR/f14YdKhH4YAkcOuRglEXiI/1A0qWKiZ1iZfky8Tys+wN5nyss23w/JeYXVgBdTkNzvp3diFWK8+Wl9j3HYpX9LlEHJwASA1wHLL85t4ToymgLjo9gvLvwzB7T+fWNtEbh4ELbvI7jaKrvir8uSGYy4bGbfRclh5CktD//mTLhDyAsQDS8obF/Ri9mVqFzjK6417ORfu8qnpXU+mIHPRBoKvpS2WqnPtSwF8KPv8=" # TWINE_PASSWORD to deploy to PyPI - secure: "md4fgPt9RC/sCoN5//5PcNHLUd9gWQGewV5hFpWW88MRTjxTng1Zfs8r7SqlF2AkEEepFfyzq0BEe9c3FMAnFbec3KmqdlQen4V8xDbLrcTlvkPlTrYGbAScUvdhhqojB//hMHoTD4KvxAv9CiUwFBO4hCMmj2buWHUbV9Ksu5WCW9mF/gkt/hIuYAU6Mbwt8PiYyMgUpzMHO1vruofcWRaVnvKwmBqHB0ae86D4/drpwn4CWjlM12WUnphT2bssiyPkw24FZtCN6kPVta6bLZKBxu0bZpw2vbXuUG+Yh19Q4mp8wNYT3XSHJf8Hl5LfujF48+cLWu+6rlCkdcelyVylhWLFc3rGOONAv4G8jWW2yNSz/bLQfJnMpd81fQEu5eySmFxB7mdB0uyKpvIG1jMJQ73LlYKakKLAPdYhMFyQAHoX9gvCE3S4QR95DBMi5gM/pZubOCcMLdjPHB5JKpJHSjxbOzyVwgmsUIEgd5Bi2vZvvYQXn1plk4xpQ3PhXc+/gi33bzY89mKcfOn0HJ2pD1vLqDXRCBsMCakoLZ0JB/6bacaz4FngbsGWuQ+I1cz20lJGL/MSi9bW1G7Uoidt3GXXWDmXrWt70vIXlLIxr8XV0Mu/rPbauGgWE+ZSYEfvdM5sP+FNF7vQ5de+Fkvzg5Z3tTfR+O1W+d7+vM4=" diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index 19dd0d6f6d8..c3f21b41eb1 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -70,7 +70,7 @@ 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*. +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. @@ -92,7 +92,7 @@ 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). + `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 @@ -428,7 +428,7 @@ Some things that will increase the chance that your pull request is accepted qui Pull requests will automatically have tests run by TravisCI. This includes running both the unit tests as well as code linters. -Github will show the status of these checks on the pull request. +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/MAINTENANCE.md b/MAINTENANCE.md index bdeae96f04b..0060289e26d 100644 --- a/MAINTENANCE.md +++ b/MAINTENANCE.md @@ -11,7 +11,7 @@ If you want to make a contribution to the project, see the * *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 +* *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 TravisCI** so you shouldn't have to make commits here. @@ -40,12 +40,12 @@ The main advantages of this are: ## Continuous Integration -We use Github Actions and TravisCI continuous integration (CI) services to +We use GitHub Actions and TravisCI continuous integration (CI) services to build and test the project on Linux, macOS and Windows. They rely on the `requirements.txt` file to install required dependencies using conda and the `Makefile` to run the tests and checks. -### Github Actions +### GitHub Actions There are 3 configuration files located in `.github/workflows`: @@ -59,7 +59,7 @@ It is also scheduled to run daily on the *master* branch. This is only triggered when a review is requested or re-requested on a PR. It is also scheduled to run daily on the *master* branch. -3. `cache_data.yaml` (Caches GMT remote data files needed for Github Actions CI) +3. `cache_data.yaml` (Caches GMT remote data files needed for GitHub Actions CI) This is scheduled to run every Sunday at 12 noon. If new remote files are needed urgently, maintainers can manually uncomment @@ -85,7 +85,7 @@ submit pull requests to that repository. ## Continuous Documentation -We use the [Zeit Now for Github integration](https://zeit.co/github) to preview changes +We use the [Zeit Now for GitHub integration](https://zeit.co/github) to preview changes made to our documentation website every time we make a commit in a pull request. The integration service has a configuration file `now.json`, with a list of options to change the default behaviour at https://zeit.co/docs/configuration. @@ -103,10 +103,10 @@ 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 +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 +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. @@ -122,7 +122,7 @@ publishing the actual release notes at https://www.pygmt.org/latest/changes.html 2. Edit the changes list to remove any trivial changes (updates to the README, typo fixes, CI configuration, etc). -3. Replace the PR number in the commit titles with a link to the Github PR page. +3. Replace the PR number in the commit titles with a link to the GitHub PR page. Use ``sed -i.bak -E 's$\(#([0-9]*)\)$(`#\1 `__)$g' changes.rst`` to make the change automatically. 4. Copy the remaining changes to `doc/changes.rst` under a new section for the @@ -142,7 +142,7 @@ publishing the actual release notes at https://www.pygmt.org/latest/changes.html ### Check the README syntax -Github is a bit forgiving when it comes to the RST syntax in the README but PyPI is not. +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: @@ -167,7 +167,7 @@ this new folder. ### Archiving on Zenodo -Grab a zip file from the Github release and upload to Zenodo using the previously +Grab a zip file from the GitHub release and upload to Zenodo using the previously reserved DOI. ### Updating the conda package diff --git a/README.rst b/README.rst index 0db6ff736a3..6d4004527fd 100644 --- a/README.rst +++ b/README.rst @@ -66,7 +66,7 @@ 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 `__ +`issues on GitHub `__ or by posting on our `Discourse forum `__. About @@ -96,7 +96,7 @@ Project goals Contacting Us ------------- -* Most discussion happens `on Github +* Most discussion happens `on GitHub `__. Feel free to `open an issue `__ or comment on any open issue or pull request. @@ -150,7 +150,7 @@ Who we are 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 +file on GitHub for a list of the people involved and a definition of the term "PyGMT Developers". @@ -186,7 +186,7 @@ Other Python wrappers for GMT: Documentation for other versions -------------------------------- * `Development `__ (reflects the *master* branch on - Github) + GitHub) * `Latest release `__ * `v0.2.0 `__ * `v0.1.2 `__ diff --git a/doc/_templates/breadcrumbs.html b/doc/_templates/breadcrumbs.html index ee8e5f31028..35fe1383d3f 100644 --- a/doc/_templates/breadcrumbs.html +++ b/doc/_templates/breadcrumbs.html @@ -1,4 +1,4 @@ -{# Extend the RTD template to include "Edit on Github" and option to download +{# Extend the RTD template to include "Edit on GitHub" and option to download notebook generated pages from nbsphinx #} {% extends "!breadcrumbs.html" %} diff --git a/doc/changes.rst b/doc/changes.rst index 61f015f9162..ce0473bcd95 100644 --- a/doc/changes.rst +++ b/doc/changes.rst @@ -59,10 +59,10 @@ Maintenance: * Eliminate unnecessary jobs from Travis CI (`#567 `__) and Azure Pipelines (`#513 `__) * Improve the workflow to test both GMT master (`#485 `__) and 6.1 branches (`#554 `__) * Automatically cancel in-progress CI runs of old commits (`#544 `__) -* Remove the Stickler CI configuration file (`#538 `__), run style checks using Github Actions (`#519 `__) -* Cache GMT remote data as artifacts on Github (`#530 `__) +* Remove the Stickler CI configuration file (`#538 `__), run style checks using GitHub Actions (`#519 `__) +* Cache GMT remote data as artifacts on GitHub (`#530 `__) * Let pytest generate both HTML and XML coverage reports (`#512 `__) -* Run Continuous Integration tests on Github Actions (`#475 `__) +* Run Continuous Integration tests on GitHub Actions (`#475 `__) Contributors: @@ -225,7 +225,7 @@ Bug Fixes: Maintenance: * Quickfix Zeit Now miniconda installer link to anaconda.com (`#413 `__) -* Fix Github Pages deployment from Travis (`#410 `__) +* Fix GitHub Pages deployment from Travis (`#410 `__) * Update and clean TravisCI configuration (`#404 `__) * Quickfix min elevation for new SRTM15+V2.1 earth relief grids (`#401 `__) * Wrap docstrings to 79 chars and check with flake8 (`#384 `__) diff --git a/doc/install.rst b/doc/install.rst index 60f5032e8a0..ce87d390843 100644 --- a/doc/install.rst +++ b/doc/install.rst @@ -9,7 +9,7 @@ Installing We welcome any feedback and ideas! Let us know by submitting - `issues on Github `__ + `issues on GitHub `__ or by posting on our `Discourse forum `__. @@ -98,7 +98,7 @@ or use ``pip`` to install from `PyPI `__:: pip install pygmt -Alternatively, you can install the development version from the Github repository:: +Alternatively, you can install the development version from the GitHub repository:: pip install https://github.com/GenericMappingTools/pygmt/archive/master.zip diff --git a/versioneer.py b/versioneer.py index d3db64315bc..06bd328e436 100644 --- a/versioneer.py +++ b/versioneer.py @@ -165,7 +165,7 @@ ## Known Limitations Some situations are known to cause problems for Versioneer. This details the -most significant ones. More can be found on Github +most significant ones. More can be found on GitHub [issues page](https://github.com/warner/python-versioneer/issues). ### Subprojects From bbab46e2541cdc3e8c2ae3993420ee83d7efa100 Mon Sep 17 00:00:00 2001 From: Dongdong Tian Date: Thu, 22 Oct 2020 19:32:19 -0400 Subject: [PATCH 15/20] Add common alias "verbose" (V) to all functions (#662) --- pygmt/base_plotting.py | 22 ++++++++++++++++++++++ pygmt/filtering.py | 4 +++- pygmt/gridding.py | 4 +++- pygmt/gridops.py | 3 +++ pygmt/mathops.py | 12 +++++++++++- pygmt/modules.py | 10 ++++++++-- pygmt/sampling.py | 4 +++- 7 files changed, 53 insertions(+), 6 deletions(-) diff --git a/pygmt/base_plotting.py b/pygmt/base_plotting.py index 13cebbc8d20..6b709932798 100644 --- a/pygmt/base_plotting.py +++ b/pygmt/base_plotting.py @@ -69,6 +69,7 @@ def _preprocess(self, **kwargs): # pylint: disable=no-self-use G="land", S="water", U="timestamp", + V="verbose", X="xshift", Y="yshift", p="perspective", @@ -130,6 +131,7 @@ def coast(self, **kwargs): water : str Select filling or clipping of “wet” areas. {U} + {V} shorelines : str ``'[level/]pen'`` Draw shorelines [Default is no shorelines]. Append pen attributes. @@ -152,6 +154,7 @@ def coast(self, **kwargs): F="box", G="truncate", W="scale", + V="verbose", X="xshift", Y="yshift", p="perspective", @@ -217,6 +220,7 @@ def colorbar(self, **kwargs): scale : float Multiply all z-values in the CPT by the provided scale. By default the CPT is used as is. + {V} {XY} {p} {t} @@ -238,6 +242,7 @@ def colorbar(self, **kwargs): R="region", S="resample", U="timestamp", + V="verbose", W="pen", l="label", X="xshift", @@ -292,6 +297,7 @@ def grdcontour(self, grid, **kwargs): {B} {G} {U} + {V} {W} {XY} label : str @@ -489,6 +495,7 @@ def grdimage(self, grid, **kwargs): Wm="meshpen", Wf="facadepen", I="shading", + V="verbose", X="xshift", Y="yshift", p="perspective", @@ -565,6 +572,7 @@ def grdview(self, grid, **kwargs): intensity, and ambient arguments for that module, or just give ``+d`` to select the default arguments (``+a-45+nt1+m0``). + {V} {XY} {p} {t} @@ -608,6 +616,7 @@ def grdview(self, grid, **kwargs): l="label", C="cmap", U="timestamp", + V="verbose", X="xshift", Y="yshift", p="perspective", @@ -679,6 +688,7 @@ def plot(self, x=None, y=None, data=None, sizes=None, direction=None, **kwargs): quoted lines). {W} {U} + {V} {XY} label : str Add a legend entry for the symbol or line being plotted. @@ -741,6 +751,7 @@ def plot(self, x=None, y=None, data=None, sizes=None, direction=None, **kwargs): i="columns", l="label", C="levels", + V="verbose", X="xshift", Y="yshift", p="perspective", @@ -802,6 +813,7 @@ def contour(self, x=None, y=None, z=None, data=None, **kwargs): 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} {p} {t} @@ -835,6 +847,7 @@ def contour(self, x=None, y=None, z=None, data=None, **kwargs): Td="rose", Tm="compass", U="timestamp", + V="verbose", X="xshift", Y="yshift", p="perspective", @@ -872,6 +885,7 @@ def basemap(self, **kwargs): Draws a map magnetic rose on the map at the location defined by the reference and anchor points {U} + {V} {XY} {p} {t} @@ -945,6 +959,7 @@ def logo(self, **kwargs): D="position", F="box", M="monochrome", + V="verbose", X="xshift", Y="yshift", p="perspective", @@ -983,6 +998,7 @@ def image(self, imagefile, **kwargs): monochrome : bool Convert color image to monochrome grayshades using the (television) YIQ-transformation. + {V} {XY} {p} {t} @@ -998,6 +1014,7 @@ def image(self, imagefile, **kwargs): J="projection", D="position", F="box", + V="verbose", X="xshift", Y="yshift", p="perspective", @@ -1037,6 +1054,7 @@ def legend(self, spec=None, position="JTR+jTR+o0.2c", box="+gwhite+p1p", **kwarg rectangular border around the legend using **MAP_FRAME_PEN**. By default, uses '+gwhite+p1p' which draws a box around the legend using a 1 point black pen and adds a white background. + {V} {XY} {p} {t} @@ -1067,6 +1085,7 @@ def legend(self, spec=None, position="JTR+jTR+o0.2c", box="+gwhite+p1p", **kwarg C="clearance", D="offset", G="fill", + V="verbose", W="pen", X="xshift", Y="yshift", @@ -1178,6 +1197,7 @@ def text( Sets the pen used to draw a rectangle around the text string (see *clearance*) [Default is width = default, color = black, style = solid]. + {V} {XY} {p} {t} @@ -1235,6 +1255,7 @@ def text( J="projection", B="frame", C="offset", + V="verbose", X="xshift", Y="yshift", p="perspective", @@ -1335,6 +1356,7 @@ def meca( {J} {R} {B} + {V} {XY} {p} {t} diff --git a/pygmt/filtering.py b/pygmt/filtering.py index 37275fd7edd..adae7524576 100644 --- a/pygmt/filtering.py +++ b/pygmt/filtering.py @@ -17,7 +17,7 @@ @fmt_docstring -@use_alias(I="spacing", R="region") +@use_alias(I="spacing", R="region", V="verbose") @kwargs_to_strings(R="sequence") def blockmedian(table, outfile=None, **kwargs): """ @@ -50,6 +50,8 @@ def blockmedian(table, outfile=None, **kwargs): outfile : str Required if 'table' is a file. The file name for the output ASCII file. + {V} + Returns ------- output : pandas.DataFrame or None diff --git a/pygmt/gridding.py b/pygmt/gridding.py index 9ddaa2155c9..2801cd4a406 100644 --- a/pygmt/gridding.py +++ b/pygmt/gridding.py @@ -17,7 +17,7 @@ @fmt_docstring -@use_alias(I="spacing", R="region", G="outfile") +@use_alias(I="spacing", R="region", G="outfile", V="verbose") @kwargs_to_strings(R="sequence") def surface(x=None, y=None, z=None, data=None, **kwargs): """ @@ -58,6 +58,8 @@ def surface(x=None, y=None, z=None, data=None, **kwargs): Optional. The file name for the output netcdf file with extension .nc to store the grid in. + {V} + Returns ------- ret: xarray.DataArray or None diff --git a/pygmt/gridops.py b/pygmt/gridops.py index 122aa42340e..9c499b72db1 100644 --- a/pygmt/gridops.py +++ b/pygmt/gridops.py @@ -25,6 +25,7 @@ J="projection", N="extend", S="circ_subregion", + V="verbose", Z="z_subregion", ) @kwargs_to_strings(R="sequence") @@ -79,6 +80,8 @@ def grdcut(grid, **kwargs): considering the range of the core subset for further reduction of the area. + {V} + Returns ------- ret: xarray.DataArray or None diff --git a/pygmt/mathops.py b/pygmt/mathops.py index 971edbc2645..a4a61c13d42 100644 --- a/pygmt/mathops.py +++ b/pygmt/mathops.py @@ -7,7 +7,15 @@ @fmt_docstring -@use_alias(C="cmap", T="series", G="truncate", H="output", I="reverse", Z="continuous") +@use_alias( + C="cmap", + T="series", + G="truncate", + H="output", + I="reverse", + V="verbose", + Z="continuous", +) @kwargs_to_strings(T="sequence", G="sequence") def makecpt(**kwargs): """ @@ -52,6 +60,8 @@ def makecpt(**kwargs): input CPT remains untouched, in the second case it is only scaled to match the range z_min/z_max. + {V} + """ with Session() as lib: if "H" not in kwargs.keys(): # if no output is set diff --git a/pygmt/modules.py b/pygmt/modules.py index 477bc99221b..c733bb58790 100644 --- a/pygmt/modules.py +++ b/pygmt/modules.py @@ -17,6 +17,7 @@ @fmt_docstring +@use_alias(V="verbose") def grdinfo(grid, **kwargs): """ Get information about a grid. @@ -30,6 +31,8 @@ def grdinfo(grid, **kwargs): grid : str or xarray.DataArray The file name of the input grid or the grid loaded as a DataArray. + {V} + Returns ------- info : str @@ -55,7 +58,7 @@ def grdinfo(grid, **kwargs): @fmt_docstring -@use_alias(C="per_column", I="spacing", T="nearest_multiple") +@use_alias(C="per_column", I="spacing", T="nearest_multiple", V="verbose") def info(table, **kwargs): """ Get information about data tables. @@ -94,6 +97,8 @@ def info(table, **kwargs): 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} + Returns ------- output : np.ndarray or str @@ -137,7 +142,7 @@ def info(table, **kwargs): @fmt_docstring -@use_alias(G="download") +@use_alias(G="download", V="verbose") def which(fname, **kwargs): """ Find the full path to specified files. @@ -165,6 +170,7 @@ def which(fname, **kwargs): 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 ------- diff --git a/pygmt/sampling.py b/pygmt/sampling.py index 891b8676a60..1ae93db24f3 100644 --- a/pygmt/sampling.py +++ b/pygmt/sampling.py @@ -16,7 +16,7 @@ @fmt_docstring -@use_alias(n="interpolation") +@use_alias(n="interpolation", V="verbose") def grdtrack(points, grid, newcolname=None, outfile=None, **kwargs): """ Sample grids at specified (x,y) locations. @@ -55,6 +55,8 @@ def grdtrack(points, grid, newcolname=None, outfile=None, **kwargs): Required if 'points' is a file. The file name for the output ASCII file. + {V} + {n} Returns From 2c977f59ed2fc121bea9569c1b95c4a4eb922d4b Mon Sep 17 00:00:00 2001 From: Dongdong Tian Date: Thu, 22 Oct 2020 20:54:18 -0400 Subject: [PATCH 16/20] Add "no_clip" to plot, text, contour and meca (#661) Co-authored-by: Wei Ji <23487320+weiji14@users.noreply.github.com> --- pygmt/base_plotting.py | 28 ++++++++++++++++++++++++++-- 1 file changed, 26 insertions(+), 2 deletions(-) diff --git a/pygmt/base_plotting.py b/pygmt/base_plotting.py index 6b709932798..396b0c86d31 100644 --- a/pygmt/base_plotting.py +++ b/pygmt/base_plotting.py @@ -611,6 +611,7 @@ def grdview(self, grid, **kwargs): B="frame", S="style", G="color", + N="no_clip", W="pen", i="columns", l="label", @@ -683,6 +684,18 @@ def plot(self, x=None, y=None, data=None, sizes=None, direction=None, **kwargs): ``'[x|y|X|Y][+a][+cl|f][+n][+wcap][+ppen]'``. Draw symmetrical error bars. {G} + 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 option 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). @@ -748,6 +761,7 @@ def plot(self, x=None, y=None, data=None, sizes=None, direction=None, **kwargs): G="label_placement", W="pen", L="triangular_mesh_pen", + N="no_clip", i="columns", l="label", C="levels", @@ -798,8 +812,9 @@ def contour(self, x=None, y=None, z=None, data=None, **kwargs): Color the triangles using CPT triangular_mesh_pen : str Pen to draw the underlying triangulation (default none) - N : bool - Do not clip contours + no_clip : bool + Do NOT clip contours or image at the boundaries [Default will clip + to fit inside region]. Q : float or str Do not draw contours with less than cut number of points. ``'[cut[unit]][+z]'`` @@ -1085,6 +1100,7 @@ def legend(self, spec=None, position="JTR+jTR+o0.2c", box="+gwhite+p1p", **kwarg C="clearance", D="offset", G="fill", + N="no_clip", V="verbose", W="pen", X="xshift", @@ -1197,6 +1213,9 @@ def text( 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 False, i.e. will + clip]. {V} {XY} {p} @@ -1255,6 +1274,7 @@ def text( J="projection", B="frame", C="offset", + N="no_clip", V="verbose", X="xshift", Y="yshift", @@ -1353,6 +1373,10 @@ def meca( 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} From 3b4199acccb35bb04beeb74fd78fde3f6117f5b4 Mon Sep 17 00:00:00 2001 From: Dongdong Tian Date: Thu, 22 Oct 2020 21:22:45 -0400 Subject: [PATCH 17/20] Add instructions to run specific tests (#660) Runing the full tests using make test is very slow. For most PRs, we may work on only a small fraction of the codes, and it's common that we only need to run specific tests. This PR adds instructions for running specific tests. Co-authored-by: Wei Ji <23487320+weiji14@users.noreply.github.com> --- CONTRIBUTING.md | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index c3f21b41eb1..8c6ac9362f5 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -308,6 +308,14 @@ 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 --verbose --mpl --mpl-results-path=results --doctest_modules pygmt/tests/NAME_OF_TEST_FILE.py + +or run tests which contain names that match a specific keyword expression: + + pytest --verbose --mpl --mpl-results-path=results --doctest_modules -k KEYWORD pygmt/tests + ### Testing plots Writing an image-based test is only slightly more difficult than a simple test. From 75ec6d0b7a65cd7741ef3f2e6607f9a74262102b Mon Sep 17 00:00:00 2001 From: Dongdong Tian Date: Fri, 23 Oct 2020 19:13:38 -0400 Subject: [PATCH 18/20] Let all tests pass for GMT 6.1.1 and master (#668) * Skip the xarray shading test on macOS * Mark two info tests xfail before the bug is fixed * Avoid checking undocumented status code --- pygmt/tests/test_clib.py | 9 ++------- pygmt/tests/test_grdimage.py | 5 +++-- pygmt/tests/test_info.py | 8 +------- 3 files changed, 6 insertions(+), 16 deletions(-) diff --git a/pygmt/tests/test_clib.py b/pygmt/tests/test_clib.py index 0e70bd945eb..45dcc5da6db 100644 --- a/pygmt/tests/test_clib.py +++ b/pygmt/tests/test_clib.py @@ -161,13 +161,8 @@ def test_call_module_error_message(): try: lib.call_module("info", "bogus-data.bla") except GMTCLibError as error: - msg = "\n".join( - [ - "Module 'info' failed with status code 71:", - "gmtinfo [ERROR]: Cannot find file bogus-data.bla", - ] - ) - assert str(error) == msg + 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(): diff --git a/pygmt/tests/test_grdimage.py b/pygmt/tests/test_grdimage.py index 7a29bc0a572..c784760be8f 100644 --- a/pygmt/tests/test_grdimage.py +++ b/pygmt/tests/test_grdimage.py @@ -1,6 +1,7 @@ """ Test Figure.grdimage """ +import sys import numpy as np import pytest import xarray as xr @@ -73,9 +74,9 @@ def test_grdimage_file(): return fig -@pytest.mark.xfail( +@pytest.mark.skip( reason="Upstream bug in GMT 6.1.1", - condition=gmt_version <= Version("6.1.1"), + condition=gmt_version <= Version("6.1.1") and sys.platform == "darwin", ) @check_figures_equal() @pytest.mark.parametrize( diff --git a/pygmt/tests/test_info.py b/pygmt/tests/test_info.py index 92e7616adc6..e4ce734f7e6 100644 --- a/pygmt/tests/test_info.py +++ b/pygmt/tests/test_info.py @@ -8,17 +8,13 @@ import pandas as pd import pytest import xarray as xr -from packaging.version import Version -from .. import clib, info +from .. import info from ..exceptions import GMTInvalidInput TEST_DATA_DIR = os.path.join(os.path.dirname(__file__), "data") POINTS_DATA = os.path.join(TEST_DATA_DIR, "points.txt") -with clib.Session() as _lib: - gmt_version = Version(_lib.info["version"]) - def test_info(): "Make sure info works on file name inputs" @@ -43,7 +39,6 @@ def test_info_dataframe(): @pytest.mark.xfail( - condition=gmt_version <= Version("6.1.1"), 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", ) @@ -63,7 +58,6 @@ def test_info_pandas_dataframe_time_column(): @pytest.mark.xfail( - condition=gmt_version <= Version("6.1.1"), 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", ) From d313015725e65424a89350550b1b6f8024e170aa Mon Sep 17 00:00:00 2001 From: Dongdong Tian Date: Fri, 23 Oct 2020 22:21:50 -0400 Subject: [PATCH 19/20] Add slash command "/format" to automatically format PRs (#646) This PR adds a special github action to enable slash commands in comments. With the feature enabled, anyone, who have written permission to a pull request, can write /format at the first line of a comment to trigger the "format" action, which can format the codes automatically. Co-authored-by: Wei Ji <23487320+weiji14@users.noreply.github.com> --- .github/PULL_REQUEST_TEMPLATE.md | 4 ++ .github/workflows/format-command.yml | 47 ++++++++++++++++++++ .github/workflows/slash-command-dispatch.yml | 24 ++++++++++ CONTRIBUTING.md | 7 +-- 4 files changed, 79 insertions(+), 3 deletions(-) create mode 100644 .github/workflows/format-command.yml create mode 100644 .github/workflows/slash-command-dispatch.yml diff --git a/.github/PULL_REQUEST_TEMPLATE.md b/.github/PULL_REQUEST_TEMPLATE.md index 41edaf59d5a..2a5392b2b68 100644 --- a/.github/PULL_REQUEST_TEMPLATE.md +++ b/.github/PULL_REQUEST_TEMPLATE.md @@ -15,3 +15,7 @@ Fixes # - [ ] 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. + +**Notes** + +- You can write `/format` in the first line of a comment to lint the code automatically diff --git a/.github/workflows/format-command.yml b/.github/workflows/format-command.yml new file mode 100644 index 00000000000..c0a320ec204 --- /dev/null +++ b/.github/workflows/format-command.yml @@ -0,0 +1,47 @@ +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@v1 + + # Install formatting tools + - name: Install formatting tools + run: pip install black blackdoc flake8 + + # Run "make format" and commit the change to the PR branch + - name: Commit to the PR branch if any changes + run: | + make format + 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/slash-command-dispatch.yml b/.github/workflows/slash-command-dispatch.yml new file mode 100644 index 00000000000..85a489ac211 --- /dev/null +++ b/.github/workflows/slash-command-dispatch.yml @@ -0,0 +1,24 @@ +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 + issue-type: pull-request diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index 8c6ac9362f5..539ef51da3b 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -255,9 +255,10 @@ Before committing, run it to automatically format your code: make format ``` -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. +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 From 2291371004f1114fa5c677dc92c95f69f1cffd83 Mon Sep 17 00:00:00 2001 From: carocamargo <47150926+carocamargo@users.noreply.github.com> Date: Sat, 24 Oct 2020 11:21:15 +0200 Subject: [PATCH 20/20] Wrap grdfilter (#616) Wrapping the grdfilter function which "Filters a grid in the space (or time) domain" under gridops.py. Original GMT documentation can be found at https://docs.generic-mapping-tools.org/6.1/grdfilter.html. Aliased distance (D), filter (F), outgrid (G), spacing (I), nans (N), region (R), toggle (T), verbose (V). Also added two usage examples in the doctest. Co-authored-by: Wei Ji <23487320+weiji14@users.noreply.github.com> Co-authored-by: Dongdong Tian --- doc/api/index.rst | 1 + pygmt/__init__.py | 2 +- pygmt/gridops.py | 141 ++++++++++++++++++++++++++++++++++++++++++++++ 3 files changed, 143 insertions(+), 1 deletion(-) diff --git a/doc/api/index.rst b/doc/api/index.rst index c4f199b3dbc..09e822bc87a 100644 --- a/doc/api/index.rst +++ b/doc/api/index.rst @@ -71,6 +71,7 @@ Operations on grids: :toctree: generated grdcut + grdfilter grdtrack Crossover analysis with x2sys: diff --git a/pygmt/__init__.py b/pygmt/__init__.py index 512865264b0..4a38fe40ad6 100644 --- a/pygmt/__init__.py +++ b/pygmt/__init__.py @@ -19,7 +19,7 @@ from .sampling import grdtrack from .mathops import makecpt from .modules import GMTDataArrayAccessor, config, info, grdinfo, which -from .gridops import grdcut +from .gridops import grdcut, grdfilter from .x2sys import x2sys_init, x2sys_cross from . import datasets diff --git a/pygmt/gridops.py b/pygmt/gridops.py index 9c499b72db1..845fbee65ff 100644 --- a/pygmt/gridops.py +++ b/pygmt/gridops.py @@ -116,3 +116,144 @@ def grdcut(grid, **kwargs): result = None # if user sets an outgrid, return None return result + + +@fmt_docstring +@use_alias( + D="distance", + F="filter", + G="outgrid", + I="spacing", + N="nans", + R="region", + T="toggle", + V="verbose", +) +@kwargs_to_strings(R="sequence") +def grdfilter(grid, **kwargs): + """ + 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 + ``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 + Example: F='m600' for a median filter with width of 600 + 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[+e|n][/yinc[+e|n]]``. + x_inc [and optionally y_inc] 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} + + Returns + ------- + ret: xarray.DataArray or None + Return type depends on whether the *outgrid* parameter is set: + - xarray.DataArray if *outgrid* is not set + - None if *outgrid* is set (grid output will be stored in *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") + + """ + kind = data_kind(grid) + + with GMTTempFile(suffix=".nc") as tmpfile: + with Session() as lib: + if kind == "file": + file_context = dummy_context(grid) + elif kind == "grid": + file_context = lib.virtualfile_from_grid(grid) + else: + raise GMTInvalidInput("Unrecognized data type: {}".format(type(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