diff --git a/.github/workflows/ci_tests.yaml b/.github/workflows/ci_tests.yaml index 73d63170f40..252f96f7135 100644 --- a/.github/workflows/ci_tests.yaml +++ b/.github/workflows/ci_tests.yaml @@ -43,8 +43,7 @@ jobs: fail-fast: false matrix: python-version: [3.6, 3.7, 3.8] - #os: [ubuntu-latest, macOS-latest, windows-latest] - os: [ubuntu-latest, macOS-latest] + os: [ubuntu-latest, macOS-latest, windows-latest] # environmental variables used in coverage env: OS: ${{ matrix.os }} @@ -112,9 +111,9 @@ jobs: run: make test PYTEST_EXTRA="-r P" # Build the documentation - #- name: Build the documentation - # shell: bash -l {0} - # run: make -C doc clean all + - name: Build the documentation + shell: bash -l {0} + run: make -C doc clean all # Upload coverage to Codecov - name: Upload coverage to Codecov diff --git a/.stickler.yml b/.stickler.yml deleted file mode 100644 index eac60bfe1a4..00000000000 --- a/.stickler.yml +++ /dev/null @@ -1,12 +0,0 @@ -linters: - flake8: - python: 3 - enable: true - ignore: E203, E266, E501, W503, F401, E741 - max-line-length: 88 - shellcheck: - shell: bash - csslint: - enable: false -files: - ignore: ['versioneer.py', 'pygmt/_version.py', 'doc/conf.py'] diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index 9c0560beb47..9ccb25bc1c1 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -156,7 +156,7 @@ Guidelines for a good tutorial: * Explain the options and features in as much detail as possible. The gallery has concise examples while the tutorials are detailed and full of text. -Note that the `Figure.plot` function needs to be called for a plot to be inserted into +Note that the `Figure.show()` function needs to be called for a plot to be inserted into the documentation. diff --git a/examples/gallery/grid/track_sampling.py b/examples/gallery/grid/track_sampling.py index 66bd9cb2316..0c01d099eab 100644 --- a/examples/gallery/grid/track_sampling.py +++ b/examples/gallery/grid/track_sampling.py @@ -24,7 +24,7 @@ fig = pygmt.Figure() # Plot the earth relief grid on Cylindrical Stereographic projection, masking land areas -fig.basemap(region="g", frame=True, projection="Cyl_stere/150/-20/8i") +fig.basemap(region="d", frame=True, projection="Cyl_stere/8i") fig.grdimage(grid=grid, cmap="gray") fig.coast(land="#666666") # Plot using circles (c) of 0.15cm, the sampled bathymetry points diff --git a/pygmt/figure.py b/pygmt/figure.py index 60532228b3c..2991b7e8a2c 100644 --- a/pygmt/figure.py +++ b/pygmt/figure.py @@ -298,7 +298,7 @@ def shift_origin(self, xshift=None, yshift=None): Shift plot origin in x and/or y directions. This method shifts plot origin relative to the current origin by - (*xshift*,*yshift*) and optionally append the length unit (**c**, + (*xshift*, *yshift*) and optionally append the length unit (**c**, **i**, or **p**). Prepend **a** to shift the origin back to the original position after @@ -308,7 +308,7 @@ def shift_origin(self, xshift=None, yshift=None): move the origin relative to its current location. Detailed usage at - :gmt-docs:`GMT_Docs.html#plot-positioning-and-layout-the-x-y-options` + :gmt-docs:`cookbook/options.html#plot-positioning-and-layout-the-x-y-options` Parameters ---------- @@ -316,6 +316,14 @@ def shift_origin(self, xshift=None, yshift=None): Shift plot origin in x direction. yshift : str Shift plot origin in y direction. + + Notes + ----- + For GMT 6.1.0, this function can't be used as the first plotting + function of :meth:`pygmt.Figure`, since it relies the *region* and + *projection* settings from previous commands. + + .. TODO: Remove the notes when PyGMT bumps to GMT>=6.1.1. """ self._preprocess() args = ["-T"] diff --git a/pygmt/gridding.py b/pygmt/gridding.py index 375d45461ed..9ddaa2155c9 100644 --- a/pygmt/gridding.py +++ b/pygmt/gridding.py @@ -90,6 +90,7 @@ def surface(x=None, y=None, z=None, data=None, **kwargs): if outfile == tmpfile.name: # if user did not set outfile, return DataArray with xr.open_dataarray(outfile) as dataarray: result = dataarray.load() + _ = result.gmt # load GMTDataArray accessor information elif outfile != tmpfile.name: # if user sets an outfile, return None result = None diff --git a/pygmt/gridops.py b/pygmt/gridops.py index 400a8d6f219..122aa42340e 100644 --- a/pygmt/gridops.py +++ b/pygmt/gridops.py @@ -47,8 +47,8 @@ def grdcut(grid, **kwargs): Parameters ---------- - grid : str - The name of the input grid file. + 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. @@ -94,12 +94,7 @@ def grdcut(grid, **kwargs): if kind == "file": file_context = dummy_context(grid) elif kind == "grid": - raise NotImplementedError( - "xarray.DataArray is not supported as the input grid yet!" - ) - # file_context = lib.virtualfile_from_grid(grid) - # See https://github.com/GenericMappingTools/gmt/pull/3532 - # for a feature request. + file_context = lib.virtualfile_from_grid(grid) else: raise GMTInvalidInput("Unrecognized data type: {}".format(type(grid))) @@ -113,6 +108,7 @@ def grdcut(grid, **kwargs): 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 diff --git a/pygmt/helpers/decorators.py b/pygmt/helpers/decorators.py index 518717851cf..bee5b78e17a 100644 --- a/pygmt/helpers/decorators.py +++ b/pygmt/helpers/decorators.py @@ -180,7 +180,13 @@ def use_alias(**aliases): R = bla J = meh >>> my_module(region='bla', projection='meh') R = bla J = meh - + >>> my_module( + ... region='bla', projection='meh', J="bla" + ... ) # doctest: +NORMALIZE_WHITESPACE + Traceback (most recent call last): + ... + pygmt.exceptions.GMTInvalidInput: + Arguments in short-form (J) and long-form (projection) can't coexist """ def alias_decorator(module_func): @@ -194,6 +200,10 @@ def new_module(*args, **kwargs): New module that parses and replaces the registered aliases. """ for arg, alias in aliases.items(): + if alias in kwargs and arg in kwargs: + raise GMTInvalidInput( + f"Arguments in short-form ({arg}) and long-form ({alias}) can't coexist" + ) if alias in kwargs: kwargs[arg] = kwargs.pop(alias) return module_func(*args, **kwargs) diff --git a/pygmt/tests/test_grdcut.py b/pygmt/tests/test_grdcut.py index d434934bff0..7617d5156b5 100644 --- a/pygmt/tests/test_grdcut.py +++ b/pygmt/tests/test_grdcut.py @@ -12,6 +12,12 @@ from ..helpers import GMTTempFile +@pytest.fixture(scope="module", name="grid") +def fixture_grid(): + "Load the grid data from the sample earth_relief file" + return load_earth_relief(registration="pixel") + + def test_grdcut_file_in_file_out(): "grduct an input grid file, and output to a grid file" with GMTTempFile(suffix=".nc") as tmpfile: @@ -26,6 +32,8 @@ def test_grdcut_file_in_dataarray_out(): "grdcut an input grid file, and output as DataArray" outgrid = grdcut("@earth_relief_01d", region="0/180/0/90") assert isinstance(outgrid, xr.DataArray) + assert outgrid.gmt.registration == 1 # Pixel registration + assert outgrid.gmt.gtype == 1 # Geographic type # check information of the output grid # the '@earth_relief_01d' is in pixel registration, so the grid range is # not exactly 0/180/0/90 @@ -39,23 +47,30 @@ def test_grdcut_file_in_dataarray_out(): assert outgrid.sizes["lon"] == 180 -def test_grdcut_dataarray_in_file_out(): - "grdcut an input DataArray, and output to a grid file" - # Not supported yet. - # See https://github.com/GenericMappingTools/gmt/pull/3532 - - -def test_grdcut_dataarray_in_dataarray_out(): +def test_grdcut_dataarray_in_file_out(grid): "grdcut an input DataArray, and output to a grid file" - # Not supported yet. - # See https://github.com/GenericMappingTools/gmt/pull/3532 + with GMTTempFile(suffix=".nc") as tmpfile: + result = grdcut(grid, outgrid=tmpfile.name, region="0/180/0/90") + assert result is None # grdcut returns None if output to a file + result = grdinfo(tmpfile.name, C=True) + assert result == "0 180 0 90 -8182 5651.5 1 1 180 90 1 1\n" -def test_grdcut_dataarray_in_fail(): - "Make sure that grdcut fails correctly if DataArray is the input grid" - with pytest.raises(NotImplementedError): - grid = load_earth_relief() - grdcut(grid, region="0/180/0/90") +def test_grdcut_dataarray_in_dataarray_out(grid): + "grdcut an input DataArray, and output as DataArray" + outgrid = grdcut(grid, region="0/180/0/90") + assert isinstance(outgrid, xr.DataArray) + # check information of the output grid + # the '@earth_relief_01d' is in pixel registration, so the grid range is + # not exactly 0/180/0/90 + assert outgrid.coords["lat"].data.min() == 0.5 + assert outgrid.coords["lat"].data.max() == 89.5 + assert outgrid.coords["lon"].data.min() == 0.5 + assert outgrid.coords["lon"].data.max() == 179.5 + assert outgrid.data.min() == -8182.0 + assert outgrid.data.max() == 5651.5 + assert outgrid.sizes["lat"] == 90 + assert outgrid.sizes["lon"] == 180 def test_grdcut_fails(): diff --git a/pygmt/tests/test_image.py b/pygmt/tests/test_image.py index f20fed3eba7..1bc0e1ffeea 100644 --- a/pygmt/tests/test_image.py +++ b/pygmt/tests/test_image.py @@ -2,6 +2,7 @@ Tests image. """ import os +import sys import pytest @@ -11,6 +12,7 @@ TEST_IMG = os.path.join(os.path.dirname(__file__), "baseline", "test_logo.png") +@pytest.mark.skipif(sys.platform == "win32", reason="crashes on Windows") @pytest.mark.mpl_image_compare def test_image(): "Place images on map" diff --git a/pygmt/tests/test_surface.py b/pygmt/tests/test_surface.py index 1a6c3af9aae..08ad8628244 100644 --- a/pygmt/tests/test_surface.py +++ b/pygmt/tests/test_surface.py @@ -23,6 +23,8 @@ def test_surface_input_file(): fname = which("@tut_ship.xyz", download="c") output = surface(data=fname, spacing="5m", region=[245, 255, 20, 30]) assert isinstance(output, xr.DataArray) + assert output.gmt.registration == 0 # Gridline registration + assert output.gmt.gtype == 0 # Cartesian type return output