From e2f66faaac614311220ded112d19f7439442ae34 Mon Sep 17 00:00:00 2001 From: Wei Ji Date: Tue, 15 Dec 2020 01:16:53 +1300 Subject: [PATCH 01/30] Wrap triangulate Wrapping the triangulate function which does "Delaunay triangulation or Voronoi partitioning and gridding of Cartesian data" under gridding.py. Original GMT documentation can be found at https://docs.generic-mapping-tools.org/6.1/triangulate.html. Aliased outgrid (G), spacing (I), projection (J), region (R), verbose (V), registration (r). Also included 8 unit tests modified from `surface`. --- doc/api/index.rst | 1 + pygmt/__init__.py | 2 +- pygmt/gridding.py | 101 ++++++++++++++++++++++++- pygmt/tests/test_triangulate.py | 128 ++++++++++++++++++++++++++++++++ 4 files changed, 229 insertions(+), 3 deletions(-) create mode 100644 pygmt/tests/test_triangulate.py diff --git a/doc/api/index.rst b/doc/api/index.rst index 0598fd103f8..ed24c37214a 100644 --- a/doc/api/index.rst +++ b/doc/api/index.rst @@ -65,6 +65,7 @@ Operations on tabular data: blockmedian surface + triangulate Operations on grids: diff --git a/pygmt/__init__.py b/pygmt/__init__.py index 0fb12c6652a..83d107b355f 100644 --- a/pygmt/__init__.py +++ b/pygmt/__init__.py @@ -15,7 +15,7 @@ from .session_management import begin as _begin, end as _end from .figure import Figure from .filtering import blockmedian -from .gridding import surface +from .gridding import surface, triangulate from .sampling import grdtrack from .mathops import makecpt from .modules import GMTDataArrayAccessor, config, info, grdinfo, which diff --git a/pygmt/gridding.py b/pygmt/gridding.py index 2801cd4a406..8573f0b3735 100644 --- a/pygmt/gridding.py +++ b/pygmt/gridding.py @@ -1,19 +1,20 @@ """ GMT modules for Gridding of Data Tables """ +import pandas as pd import xarray as xr from .clib import Session +from .exceptions import GMTInvalidInput from .helpers import ( + GMTTempFile, build_arg_string, data_kind, dummy_context, fmt_docstring, - GMTTempFile, kwargs_to_strings, use_alias, ) -from .exceptions import GMTInvalidInput @fmt_docstring @@ -97,3 +98,99 @@ def surface(x=None, y=None, z=None, data=None, **kwargs): result = None return result + + +@fmt_docstring +@use_alias( + G="outgrid", I="spacing", J="projection", R="region", V="verbose", r="registration" +) +@kwargs_to_strings(R="sequence") +def triangulate(x=None, y=None, z=None, data=None, **kwargs): + """ + Delaunay triangulation or Voronoi partitioning and gridding of Cartesian + data. + + Triangulate reads in x,y[,z] data and performs Delaunay triangulation, + i.e., it find how the points should be connected to give the most + equilateral triangulation possible. If a map projection (give *region* + and *projection*) is chosen then it is applied before the triangulation + is calculated. + + Must provide either *data* or *x*, *y*, and *z*. + + Full option list at :gmt-docs:`triangulate.html` + + {aliases} + + Parameters + ---------- + x/y/z : np.ndarray + Arrays of x and y coordinates and values z of the data points. + data : str or np.ndarray + Either a data file name or a 2d numpy array with the tabular data. + projection : str + Select map projection. + region + ``'xmin/xmax/ymin/ymax[+r][+uunit]'``. + Specify the region of interest. + spacing : str + ``'xinc[unit][+e|n][/yinc[unit][+e|n]]'``. + x_inc [and optionally y_inc] is the grid spacing. + outgrid : bool or str + Use triangulation to grid the data onto an even grid (specified with + *region* and *spacing*). Set to True, or pass in the name of the output + grid file. The interpolation is performed in the original coordinates, + so if your triangles are close to the poles you are better off + projecting all data to a local coordinate system before using + *triangulate* (this is true of all gridding routines) or instead + select *sphtriangulate*. + {V} + {registration} + Only valid with *outgrid*. + + Returns + ------- + ret: xarray.DataArray or None + Return type depends on whether the outgrid parameter is set: + + - pandas.DataFrame if outgrid is None (default) + - xarray.DataArray if outgrid is True + - None if outgrid is a str (grid output will be stored in outgrid) + + """ + kind = data_kind(data, x, y, z) + if kind == "vectors" and z is None: + raise GMTInvalidInput("Must provide z with x and y.") + + with GMTTempFile(suffix=".nc") as tmpfile: + with Session() as lib: + if kind == "file": + file_context = dummy_context(data) + elif kind == "matrix": + file_context = lib.virtualfile_from_matrix(data) + elif kind == "vectors": + file_context = lib.virtualfile_from_vectors(x, y, z) + else: + raise GMTInvalidInput(f"Unrecognized data type: {type(data)}") + + with file_context as infile: + if "G" not in kwargs: # table output if outgrid is unset + kwargs.update({">": tmpfile.name}) + else: # NetCDF or xarray.DataArray output if outgrid is set + if kwargs["G"] == "": # xarray.DataArray output if outgrid=True + kwargs.update({"G": tmpfile.name}) + outgrid = kwargs["G"] + arg_str = " ".join([infile, build_arg_string(kwargs)]) + lib.call_module(module="triangulate", args=arg_str) + + try: + if outgrid == tmpfile.name: # if user did not set outfile, return DataArray + with xr.open_dataarray(outgrid) as dataarray: + result = dataarray.load() + _ = result.gmt # load GMTDataArray accessor information + elif outgrid != tmpfile.name: # if user sets an outgrid, return None + result = None + except UnboundLocalError: # if outgrid unset, return pd.DataFrame + result = pd.read_csv(tmpfile.name, sep="\t", header=None) + + return result diff --git a/pygmt/tests/test_triangulate.py b/pygmt/tests/test_triangulate.py new file mode 100644 index 00000000000..5d45ed3c00f --- /dev/null +++ b/pygmt/tests/test_triangulate.py @@ -0,0 +1,128 @@ +""" +Tests for triangulate +""" +import os + +import pandas as pd +import pytest +import xarray as xr + +from .. import triangulate, which +from ..datasets import load_sample_bathymetry +from ..exceptions import GMTInvalidInput +from ..helpers import data_kind + +TEST_DATA_DIR = os.path.join(os.path.dirname(__file__), "data") +TEMP_GRID = os.path.join(TEST_DATA_DIR, "tmp_grid.nc") + + +@pytest.fixture(scope="module", name="ship_data") +def fixture_ship_data(): + """ + Load the grid data from the sample bathymetry file + """ + ship_data = load_sample_bathymetry() + return ship_data + + +def test_triangulate_input_file(): + """ + Run triangulate by passing in a filename + """ + fname = which("@tut_ship.xyz", download="c") + output = triangulate(data=fname) + assert isinstance(output, pd.DataFrame) + assert output.shape == (161935, 3) + return output + + +def test_triangulate_input_data_array(ship_data): + """ + Run triangulate by passing in a numpy array into data + """ + data = ship_data.to_numpy() + output = triangulate(data=data) + assert isinstance(output, pd.DataFrame) + assert output.shape == (161935, 3) + return output + + +def test_triangulate_input_xyz(ship_data): + """ + Run triangulate by passing in x, y, z numpy.ndarrays individually + """ + output = triangulate( + x=ship_data.longitude, + y=ship_data.latitude, + z=ship_data.bathymetry, + ) + assert isinstance(output, pd.DataFrame) + assert output.shape == (161935, 3) + return output + + +def test_triangulate_input_xy_no_z(ship_data): + """ + Run triangulate by passing in x and y, but no z + """ + with pytest.raises(GMTInvalidInput): + triangulate(x=ship_data.longitude, y=ship_data.latitude) + + +def test_triangulate_wrong_kind_of_input(ship_data): + """ + Run triangulate using grid input that is not file/matrix/vectors + """ + data = ship_data.bathymetry.to_xarray() # convert pandas.Series to xarray.DataArray + assert data_kind(data) == "grid" + with pytest.raises(GMTInvalidInput): + triangulate(data=data) + + +def test_triangulate_with_outgrid_true(ship_data): + """ + Run triangulate with outgrid=True and see it load into an xarray.DataArray + """ + data = ship_data.to_numpy() + output = triangulate( + data=data, spacing="5m", region=[245, 255, 20, 30], outgrid=True + ) + assert isinstance(output, xr.DataArray) + assert output.shape == (121, 121) + return output + + +def test_triangulate_with_outgrid_param(ship_data): + """ + Run triangulate with the -Goutputfile.nc parameter + """ + data = ship_data.to_numpy() + try: + output = triangulate( + data=data, spacing="5m", region=[245, 255, 20, 30], outgrid=TEMP_GRID + ) + assert output is None # check that output is None since outgrid is set + assert os.path.exists(path=TEMP_GRID) # check that outgrid exists at path + with xr.open_dataarray(TEMP_GRID) as grid: + assert isinstance(grid, xr.DataArray) # ensure netcdf grid loads ok + assert grid.shape == (121, 121) + finally: + os.remove(path=TEMP_GRID) + return output + + +def test_triangulate_short_aliases(ship_data): + """ + Run triangulate using short aliases -I for spacing, -R for region, -G for + outgrid + """ + data = ship_data.to_numpy() + try: + output = triangulate(data=data, I="5m", R=[245, 255, 20, 30], G=TEMP_GRID) + assert output is None # check that output is None since outgrid is set + assert os.path.exists(path=TEMP_GRID) # check that outgrid exists at path + with xr.open_dataarray(TEMP_GRID) as grid: + assert isinstance(grid, xr.DataArray) # ensure netcdf grid loads ok + finally: + os.remove(path=TEMP_GRID) + return output From 3f4090e60c12ba4b40e8b757ef1f199a04b1ff1a Mon Sep 17 00:00:00 2001 From: Wei Ji <23487320+weiji14@users.noreply.github.com> Date: Wed, 11 Aug 2021 12:07:01 +1200 Subject: [PATCH 02/30] Refactor triangulate to use virtualfile_from_data Modernizing the code implementation to keep up to date with PyGMT v0.4.1. Also refreshing the unit tests a bit. --- pygmt/src/triangulate.py | 36 ++++++-------- pygmt/tests/test_triangulate.py | 88 +++++++++++---------------------- 2 files changed, 44 insertions(+), 80 deletions(-) diff --git a/pygmt/src/triangulate.py b/pygmt/src/triangulate.py index b4059f3705a..d5eac84391f 100644 --- a/pygmt/src/triangulate.py +++ b/pygmt/src/triangulate.py @@ -5,12 +5,9 @@ import pandas as pd import xarray as xr from pygmt.clib import Session -from pygmt.exceptions import GMTInvalidInput from pygmt.helpers import ( GMTTempFile, build_arg_string, - data_kind, - dummy_context, fmt_docstring, kwargs_to_strings, use_alias, @@ -22,7 +19,7 @@ G="outgrid", I="spacing", J="projection", R="region", V="verbose", r="registration" ) @kwargs_to_strings(R="sequence") -def triangulate(x=None, y=None, z=None, data=None, **kwargs): +def triangulate(table=None, x=None, y=None, z=None, **kwargs): """ Delaunay triangulation or Voronoi partitioning and gridding of Cartesian data. @@ -33,7 +30,7 @@ def triangulate(x=None, y=None, z=None, data=None, **kwargs): and *projection*) is chosen then it is applied before the triangulation is calculated. - Must provide either *data* or *x*, *y*, and *z*. + Must provide either *table* or *x*, *y*, and *z*. Full option list at :gmt-docs:`triangulate.html` @@ -43,8 +40,10 @@ def triangulate(x=None, y=None, z=None, data=None, **kwargs): ---------- x/y/z : np.ndarray Arrays of x and y coordinates and values z of the data points. - data : str or np.ndarray - Either a data file name or a 2d numpy array with the tabular data. + table : str or {table-like} + Pass in (x, y, z) or (longitude, latitude, elevation) values by + providing a file name to an ASCII data table, a 2D + {table-classes}. projection : str Select map projection. region @@ -74,26 +73,19 @@ def triangulate(x=None, y=None, z=None, data=None, **kwargs): - xarray.DataArray if outgrid is True - None if outgrid is a str (grid output will be stored in outgrid) """ - kind = data_kind(data, x, y, z) - if kind == "vectors" and z is None: - raise GMTInvalidInput("Must provide z with x and y.") - with GMTTempFile(suffix=".nc") as tmpfile: with Session() as lib: - if kind == "file": - file_context = dummy_context(data) - elif kind == "matrix": - file_context = lib.virtualfile_from_matrix(data) - elif kind == "vectors": - file_context = lib.virtualfile_from_vectors(x, y, z) - else: - raise GMTInvalidInput(f"Unrecognized data type: {type(data)}") - - with file_context as infile: + # Choose how data will be passed into the module + table_context = lib.virtualfile_from_data( + check_kind="vector", data=table, x=x, y=y, z=z + ) + with table_context as infile: if "G" not in kwargs: # table output if outgrid is unset kwargs.update({">": tmpfile.name}) else: # NetCDF or xarray.DataArray output if outgrid is set - if kwargs["G"] == "": # xarray.DataArray output if outgrid=True + if ( + kwargs["G"] is True + ): # xarray.DataArray output if outgrid is True kwargs.update({"G": tmpfile.name}) outgrid = kwargs["G"] arg_str = " ".join([infile, build_arg_string(kwargs)]) diff --git a/pygmt/tests/test_triangulate.py b/pygmt/tests/test_triangulate.py index db41337b756..4959ad61c77 100644 --- a/pygmt/tests/test_triangulate.py +++ b/pygmt/tests/test_triangulate.py @@ -6,122 +6,94 @@ import pandas as pd import pytest import xarray as xr -from pygmt import triangulate, which +from pygmt import triangulate from pygmt.datasets import load_sample_bathymetry from pygmt.exceptions import GMTInvalidInput -from pygmt.helpers import data_kind +from pygmt.helpers import GMTTempFile, data_kind -TEST_DATA_DIR = os.path.join(os.path.dirname(__file__), "data") -TEMP_GRID = os.path.join(TEST_DATA_DIR, "tmp_grid.nc") - -@pytest.fixture(scope="module", name="ship_data") -def fixture_ship_data(): +@pytest.fixture(scope="module", name="dataframe") +def fixture_dataframe(): """ - Load the grid data from the sample bathymetry file. + Load the table data from the sample bathymetry dataset. """ - ship_data = load_sample_bathymetry() - return ship_data + return load_sample_bathymetry() def test_triangulate_input_file(): """ Run triangulate by passing in a filename. """ - fname = which("@tut_ship.xyz", download="c") - output = triangulate(data=fname) + output = triangulate(table="@tut_ship.xyz") assert isinstance(output, pd.DataFrame) assert output.shape == (161935, 3) - return output -def test_triangulate_input_data_array(ship_data): +def test_triangulate_input_data_array(dataframe): """ Run triangulate by passing in a numpy array into data. """ - data = ship_data.to_numpy() - output = triangulate(data=data) + data = dataframe.to_numpy() + output = triangulate(table=data) assert isinstance(output, pd.DataFrame) assert output.shape == (161935, 3) - return output -def test_triangulate_input_xyz(ship_data): +def test_triangulate_input_xyz(dataframe): """ Run triangulate by passing in x, y, z numpy.ndarrays individually. """ output = triangulate( - x=ship_data.longitude, - y=ship_data.latitude, - z=ship_data.bathymetry, + x=dataframe.longitude, + y=dataframe.latitude, + z=dataframe.bathymetry, ) assert isinstance(output, pd.DataFrame) assert output.shape == (161935, 3) - return output -def test_triangulate_input_xy_no_z(ship_data): +def test_triangulate_input_xy_no_z(dataframe): """ Run triangulate by passing in x and y, but no z. """ - with pytest.raises(GMTInvalidInput): - triangulate(x=ship_data.longitude, y=ship_data.latitude) + output = triangulate(x=dataframe.longitude, y=dataframe.latitude) + assert isinstance(output, pd.DataFrame) + assert output.shape == (161935, 3) -def test_triangulate_wrong_kind_of_input(ship_data): +def test_triangulate_wrong_kind_of_input(dataframe): """ Run triangulate using grid input that is not file/matrix/vectors. """ - data = ship_data.bathymetry.to_xarray() # convert pandas.Series to xarray.DataArray + data = dataframe.bathymetry.to_xarray() # convert pandas.Series to xarray.DataArray assert data_kind(data) == "grid" with pytest.raises(GMTInvalidInput): - triangulate(data=data) + triangulate(table=data) -def test_triangulate_with_outgrid_true(ship_data): +def test_triangulate_with_outgrid_true(dataframe): """ Run triangulate with outgrid=True and see it load into an xarray.DataArray. """ - data = ship_data.to_numpy() + data = dataframe.to_numpy() output = triangulate( - data=data, spacing="5m", region=[245, 255, 20, 30], outgrid=True + table=data, spacing="5m", region=[245, 255, 20, 30], outgrid=True ) assert isinstance(output, xr.DataArray) assert output.shape == (121, 121) - return output -def test_triangulate_with_outgrid_param(ship_data): +def test_triangulate_with_outgrid_param(dataframe): """ Run triangulate with the -Goutputfile.nc parameter. """ - data = ship_data.to_numpy() - try: + data = dataframe.to_numpy() + with GMTTempFile(suffix=".nc") as tmpfile: output = triangulate( - data=data, spacing="5m", region=[245, 255, 20, 30], outgrid=TEMP_GRID + table=data, spacing="5m", region=[245, 255, 20, 30], outgrid=tmpfile.name ) assert output is None # check that output is None since outgrid is set - assert os.path.exists(path=TEMP_GRID) # check that outgrid exists at path - with xr.open_dataarray(TEMP_GRID) as grid: + assert os.path.exists(path=tmpfile.name) # check that outgrid exists + with xr.open_dataarray(tmpfile.name) as grid: assert isinstance(grid, xr.DataArray) # ensure netcdf grid loads ok assert grid.shape == (121, 121) - finally: - os.remove(path=TEMP_GRID) - return output - - -def test_triangulate_short_aliases(ship_data): - """ - Run triangulate using short aliases -I for spacing, -R for region, -G for - outgrid. - """ - data = ship_data.to_numpy() - try: - output = triangulate(data=data, I="5m", R=[245, 255, 20, 30], G=TEMP_GRID) - assert output is None # check that output is None since outgrid is set - assert os.path.exists(path=TEMP_GRID) # check that outgrid exists at path - with xr.open_dataarray(TEMP_GRID) as grid: - assert isinstance(grid, xr.DataArray) # ensure netcdf grid loads ok - finally: - os.remove(path=TEMP_GRID) - return output From bd6bf57c5cd2c6baf209deaa5b639b6ec3487027 Mon Sep 17 00:00:00 2001 From: Wei Ji <23487320+weiji14@users.noreply.github.com> Date: Fri, 3 Sep 2021 13:58:33 +1200 Subject: [PATCH 03/30] Refactor triangulate implementation to use pygmt.io.load_dataarray --- pygmt/src/triangulate.py | 9 ++------- 1 file changed, 2 insertions(+), 7 deletions(-) diff --git a/pygmt/src/triangulate.py b/pygmt/src/triangulate.py index d5eac84391f..d32ccfde6ef 100644 --- a/pygmt/src/triangulate.py +++ b/pygmt/src/triangulate.py @@ -3,7 +3,6 @@ Cartesian data. """ import pandas as pd -import xarray as xr from pygmt.clib import Session from pygmt.helpers import ( GMTTempFile, @@ -12,6 +11,7 @@ kwargs_to_strings, use_alias, ) +from pygmt.io import load_dataarray @fmt_docstring @@ -92,12 +92,7 @@ def triangulate(table=None, x=None, y=None, z=None, **kwargs): lib.call_module(module="triangulate", args=arg_str) try: - if outgrid == tmpfile.name: # if user did not set outfile, return DataArray - with xr.open_dataarray(outgrid) as dataarray: - result = dataarray.load() - _ = result.gmt # load GMTDataArray accessor information - elif outgrid != tmpfile.name: # if user sets an outgrid, return None - result = None + result = load_dataarray(outgrid) if outgrid == tmpfile.name else None except UnboundLocalError: # if outgrid unset, return pd.DataFrame result = pd.read_csv(tmpfile.name, sep="\t", header=None) From cceb2a62311f0d363b84b5bc1507da74fa25c6f4 Mon Sep 17 00:00:00 2001 From: Wei Ji <23487320+weiji14@users.noreply.github.com> Date: Fri, 3 Sep 2021 14:06:39 +1200 Subject: [PATCH 04/30] Alias binary(b), nodata(d), find(e), coltypes(f), header(h), incols(i) --- pygmt/src/triangulate.py | 19 ++++++++++++++++++- 1 file changed, 18 insertions(+), 1 deletion(-) diff --git a/pygmt/src/triangulate.py b/pygmt/src/triangulate.py index d32ccfde6ef..2540e74f64a 100644 --- a/pygmt/src/triangulate.py +++ b/pygmt/src/triangulate.py @@ -16,7 +16,18 @@ @fmt_docstring @use_alias( - G="outgrid", I="spacing", J="projection", R="region", V="verbose", r="registration" + G="outgrid", + I="spacing", + J="projection", + R="region", + V="verbose", + b="binary", + d="nodata", + e="find", + f="coltypes", + h="header", + i="incols", + r="registration", ) @kwargs_to_strings(R="sequence") def triangulate(table=None, x=None, y=None, z=None, **kwargs): @@ -61,6 +72,12 @@ def triangulate(table=None, x=None, y=None, z=None, **kwargs): *triangulate* (this is true of all gridding routines) or instead select *sphtriangulate*. {V} + {b} + {d} + {e} + {f} + {h} + {i} {r} Only valid with *outgrid*. From cfb95724e7ec0435cf9878e367e1735e1890428f Mon Sep 17 00:00:00 2001 From: Wei Ji <23487320+weiji14@users.noreply.github.com> Date: Sat, 11 Sep 2021 09:32:11 +1200 Subject: [PATCH 05/30] Apply suggestions from code review Co-authored-by: Will Schlitzer Co-authored-by: Meghan Jones --- pygmt/src/triangulate.py | 29 ++++++++++++----------------- 1 file changed, 12 insertions(+), 17 deletions(-) diff --git a/pygmt/src/triangulate.py b/pygmt/src/triangulate.py index 2540e74f64a..99e8a7baf13 100644 --- a/pygmt/src/triangulate.py +++ b/pygmt/src/triangulate.py @@ -36,12 +36,12 @@ def triangulate(table=None, x=None, y=None, z=None, **kwargs): data. Triangulate reads in x,y[,z] data and performs Delaunay triangulation, - i.e., it find how the points should be connected to give the most + i.e., it finds how the points should be connected to give the most equilateral triangulation possible. If a map projection (give *region* and *projection*) is chosen then it is applied before the triangulation is calculated. - Must provide either *table* or *x*, *y*, and *z*. + Must provide either ``table`` or ``x``, ``y``, and ``z``. Full option list at :gmt-docs:`triangulate.html` @@ -55,17 +55,12 @@ def triangulate(table=None, x=None, y=None, z=None, **kwargs): Pass in (x, y, z) or (longitude, latitude, elevation) values by providing a file name to an ASCII data table, a 2D {table-classes}. - projection : str - Select map projection. - region - ``'xmin/xmax/ymin/ymax[+r][+uunit]'``. - Specify the region of interest. - spacing : str - ``'xinc[unit][+e|n][/yinc[unit][+e|n]]'``. - x_inc [and optionally y_inc] is the grid spacing. + {J} + {R} + {I} outgrid : bool or str Use triangulation to grid the data onto an even grid (specified with - *region* and *spacing*). Set to True, or pass in the name of the output + ``region`` and ``spacing``). Set to ``True``, or pass in the name of the output grid file. The interpolation is performed in the original coordinates, so if your triangles are close to the poles you are better off projecting all data to a local coordinate system before using @@ -79,16 +74,16 @@ def triangulate(table=None, x=None, y=None, z=None, **kwargs): {h} {i} {r} - Only valid with *outgrid*. + Only valid with ``outgrid``. Returns ------- - ret: xarray.DataArray or None - Return type depends on whether the outgrid parameter is set: + ret: pandas.DataFrame or xarray.DataArray or None + Return type depends on whether the ``outgrid`` parameter is set: - - pandas.DataFrame if outgrid is None (default) - - xarray.DataArray if outgrid is True - - None if outgrid is a str (grid output will be stored in outgrid) + - pandas.DataFrame if ``outgrid`` is None (default) + - xarray.DataArray if ``outgrid`` is True + - None if ``outgrid`` is a str (grid output will be stored in ``outgrid``) """ with GMTTempFile(suffix=".nc") as tmpfile: with Session() as lib: From 9e67eef73672aec57e4bcbe77ec0d058b30051ba Mon Sep 17 00:00:00 2001 From: Wei Ji <23487320+weiji14@users.noreply.github.com> Date: Sat, 11 Sep 2021 16:53:27 +1200 Subject: [PATCH 06/30] Wrap docstrings to 79 characters --- pygmt/src/triangulate.py | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/pygmt/src/triangulate.py b/pygmt/src/triangulate.py index 99e8a7baf13..5154325fc90 100644 --- a/pygmt/src/triangulate.py +++ b/pygmt/src/triangulate.py @@ -60,10 +60,10 @@ def triangulate(table=None, x=None, y=None, z=None, **kwargs): {I} outgrid : bool or str Use triangulation to grid the data onto an even grid (specified with - ``region`` and ``spacing``). Set to ``True``, or pass in the name of the output - grid file. The interpolation is performed in the original coordinates, - so if your triangles are close to the poles you are better off - projecting all data to a local coordinate system before using + ``region`` and ``spacing``). Set to ``True``, or pass in the name of + the output grid file. The interpolation is performed in the original + coordinates, so if your triangles are close to the poles you are better + off projecting all data to a local coordinate system before using *triangulate* (this is true of all gridding routines) or instead select *sphtriangulate*. {V} @@ -83,7 +83,7 @@ def triangulate(table=None, x=None, y=None, z=None, **kwargs): - pandas.DataFrame if ``outgrid`` is None (default) - xarray.DataArray if ``outgrid`` is True - - None if ``outgrid`` is a str (grid output will be stored in ``outgrid``) + - None if ``outgrid`` is a str (grid output is stored in ``outgrid``) """ with GMTTempFile(suffix=".nc") as tmpfile: with Session() as lib: From d00d34756b1fb2b9e704c0608af3960264624563 Mon Sep 17 00:00:00 2001 From: Wei Ji <23487320+weiji14@users.noreply.github.com> Date: Fri, 11 Mar 2022 16:02:24 -0500 Subject: [PATCH 07/30] Rename the parameter 'table' to 'data' As per https://github.com/GenericMappingTools/pygmt/issues/1479. --- pygmt/src/triangulate.py | 8 ++++---- pygmt/tests/test_triangulate.py | 10 +++++----- 2 files changed, 9 insertions(+), 9 deletions(-) diff --git a/pygmt/src/triangulate.py b/pygmt/src/triangulate.py index 5154325fc90..bc3e577f94d 100644 --- a/pygmt/src/triangulate.py +++ b/pygmt/src/triangulate.py @@ -30,7 +30,7 @@ r="registration", ) @kwargs_to_strings(R="sequence") -def triangulate(table=None, x=None, y=None, z=None, **kwargs): +def triangulate(data=None, x=None, y=None, z=None, **kwargs): """ Delaunay triangulation or Voronoi partitioning and gridding of Cartesian data. @@ -41,7 +41,7 @@ def triangulate(table=None, x=None, y=None, z=None, **kwargs): and *projection*) is chosen then it is applied before the triangulation is calculated. - Must provide either ``table`` or ``x``, ``y``, and ``z``. + Must provide either ``data`` or ``x``, ``y``, and ``z``. Full option list at :gmt-docs:`triangulate.html` @@ -51,7 +51,7 @@ def triangulate(table=None, x=None, y=None, z=None, **kwargs): ---------- x/y/z : np.ndarray Arrays of x and y coordinates and values z of the data points. - table : str or {table-like} + data : str or {table-like} Pass in (x, y, z) or (longitude, latitude, elevation) values by providing a file name to an ASCII data table, a 2D {table-classes}. @@ -89,7 +89,7 @@ def triangulate(table=None, x=None, y=None, z=None, **kwargs): with Session() as lib: # Choose how data will be passed into the module table_context = lib.virtualfile_from_data( - check_kind="vector", data=table, x=x, y=y, z=z + check_kind="vector", data=data, x=x, y=y, z=z ) with table_context as infile: if "G" not in kwargs: # table output if outgrid is unset diff --git a/pygmt/tests/test_triangulate.py b/pygmt/tests/test_triangulate.py index 4959ad61c77..c7d00c89004 100644 --- a/pygmt/tests/test_triangulate.py +++ b/pygmt/tests/test_triangulate.py @@ -24,7 +24,7 @@ def test_triangulate_input_file(): """ Run triangulate by passing in a filename. """ - output = triangulate(table="@tut_ship.xyz") + output = triangulate(data="@tut_ship.xyz") assert isinstance(output, pd.DataFrame) assert output.shape == (161935, 3) @@ -34,7 +34,7 @@ def test_triangulate_input_data_array(dataframe): Run triangulate by passing in a numpy array into data. """ data = dataframe.to_numpy() - output = triangulate(table=data) + output = triangulate(data=data) assert isinstance(output, pd.DataFrame) assert output.shape == (161935, 3) @@ -68,7 +68,7 @@ def test_triangulate_wrong_kind_of_input(dataframe): data = dataframe.bathymetry.to_xarray() # convert pandas.Series to xarray.DataArray assert data_kind(data) == "grid" with pytest.raises(GMTInvalidInput): - triangulate(table=data) + triangulate(data=data) def test_triangulate_with_outgrid_true(dataframe): @@ -77,7 +77,7 @@ def test_triangulate_with_outgrid_true(dataframe): """ data = dataframe.to_numpy() output = triangulate( - table=data, spacing="5m", region=[245, 255, 20, 30], outgrid=True + data=data, spacing="5m", region=[245, 255, 20, 30], outgrid=True ) assert isinstance(output, xr.DataArray) assert output.shape == (121, 121) @@ -90,7 +90,7 @@ def test_triangulate_with_outgrid_param(dataframe): data = dataframe.to_numpy() with GMTTempFile(suffix=".nc") as tmpfile: output = triangulate( - table=data, spacing="5m", region=[245, 255, 20, 30], outgrid=tmpfile.name + data=data, spacing="5m", region=[245, 255, 20, 30], outgrid=tmpfile.name ) assert output is None # check that output is None since outgrid is set assert os.path.exists(path=tmpfile.name) # check that outgrid exists From df1b179d641873836af89662aa70aa379a6a22f1 Mon Sep 17 00:00:00 2001 From: Wei Ji <23487320+weiji14@users.noreply.github.com> Date: Fri, 11 Mar 2022 21:39:41 -0500 Subject: [PATCH 08/30] Refactor test_triangulate to use Table_5_11_mean.xyz instead of tut_ship --- pygmt/tests/test_triangulate.py | 27 +++++++++++++++------------ 1 file changed, 15 insertions(+), 12 deletions(-) diff --git a/pygmt/tests/test_triangulate.py b/pygmt/tests/test_triangulate.py index c7d00c89004..7d92ace05cc 100644 --- a/pygmt/tests/test_triangulate.py +++ b/pygmt/tests/test_triangulate.py @@ -6,7 +6,7 @@ import pandas as pd import pytest import xarray as xr -from pygmt import triangulate +from pygmt import triangulate, which from pygmt.datasets import load_sample_bathymetry from pygmt.exceptions import GMTInvalidInput from pygmt.helpers import GMTTempFile, data_kind @@ -17,16 +17,19 @@ def fixture_dataframe(): """ Load the table data from the sample bathymetry dataset. """ - return load_sample_bathymetry() + fname = which("@Table_5_11_mean.xyz", download="c") + return pd.read_csv( + fname, sep=r"\s+", header=None, names=["x", "y", "z"], skiprows=1 + ) def test_triangulate_input_file(): """ Run triangulate by passing in a filename. """ - output = triangulate(data="@tut_ship.xyz") + output = triangulate(data="@Table_5_11_mean.xyz") assert isinstance(output, pd.DataFrame) - assert output.shape == (161935, 3) + assert output.shape == (67, 3) def test_triangulate_input_data_array(dataframe): @@ -36,7 +39,7 @@ def test_triangulate_input_data_array(dataframe): data = dataframe.to_numpy() output = triangulate(data=data) assert isinstance(output, pd.DataFrame) - assert output.shape == (161935, 3) + assert output.shape == (67, 3) def test_triangulate_input_xyz(dataframe): @@ -44,28 +47,28 @@ def test_triangulate_input_xyz(dataframe): Run triangulate by passing in x, y, z numpy.ndarrays individually. """ output = triangulate( - x=dataframe.longitude, - y=dataframe.latitude, - z=dataframe.bathymetry, + x=dataframe.x, + y=dataframe.y, + z=dataframe.z, ) assert isinstance(output, pd.DataFrame) - assert output.shape == (161935, 3) + assert output.shape == (67, 3) def test_triangulate_input_xy_no_z(dataframe): """ Run triangulate by passing in x and y, but no z. """ - output = triangulate(x=dataframe.longitude, y=dataframe.latitude) + output = triangulate(x=dataframe.x, y=dataframe.y) assert isinstance(output, pd.DataFrame) - assert output.shape == (161935, 3) + assert output.shape == (67, 3) def test_triangulate_wrong_kind_of_input(dataframe): """ Run triangulate using grid input that is not file/matrix/vectors. """ - data = dataframe.bathymetry.to_xarray() # convert pandas.Series to xarray.DataArray + data = dataframe.z.to_xarray() # convert pandas.Series to xarray.DataArray assert data_kind(data) == "grid" with pytest.raises(GMTInvalidInput): triangulate(data=data) From 0653be83a6a6ee30c1095d759d9109a3f3e487cd Mon Sep 17 00:00:00 2001 From: Wei Ji <23487320+weiji14@users.noreply.github.com> Date: Fri, 11 Mar 2022 22:21:34 -0500 Subject: [PATCH 09/30] Refactor test_triangulate_with_outgrid to use xr.testing.assert_allclose --- pygmt/tests/test_triangulate.py | 32 +++++++++++++++++++++++--------- 1 file changed, 23 insertions(+), 9 deletions(-) diff --git a/pygmt/tests/test_triangulate.py b/pygmt/tests/test_triangulate.py index 7d92ace05cc..4ab6e0a7068 100644 --- a/pygmt/tests/test_triangulate.py +++ b/pygmt/tests/test_triangulate.py @@ -20,6 +20,18 @@ def fixture_dataframe(): fname = which("@Table_5_11_mean.xyz", download="c") return pd.read_csv( fname, sep=r"\s+", header=None, names=["x", "y", "z"], skiprows=1 + )[:10] + + +@pytest.fixture(scope="module", name="expected_grid") +def fixture_grid_result(): + """ + Load the expected triangulate grid result. + """ + return xr.DataArray( + data=[[779.6264, 752.1539, 749.38776], [771.2882, 726.9792, 722.1368]], + coords=dict(y=[5, 6], x=[2, 3, 4]), + dims=["y", "x"], ) @@ -74,29 +86,31 @@ def test_triangulate_wrong_kind_of_input(dataframe): triangulate(data=data) -def test_triangulate_with_outgrid_true(dataframe): +def test_triangulate_with_outgrid_true(dataframe, expected_grid): """ Run triangulate with outgrid=True and see it load into an xarray.DataArray. """ data = dataframe.to_numpy() - output = triangulate( - data=data, spacing="5m", region=[245, 255, 20, 30], outgrid=True - ) + output = triangulate(data=data, spacing=1, region=[2, 4, 5, 6], outgrid=True) assert isinstance(output, xr.DataArray) - assert output.shape == (121, 121) + assert output.gmt.registration == 0 # Gridline registration + assert output.gmt.gtype == 0 # Cartesian type + xr.testing.assert_allclose(a=output, b=expected_grid) -def test_triangulate_with_outgrid_param(dataframe): +def test_triangulate_with_outgrid_param(dataframe, expected_grid): """ Run triangulate with the -Goutputfile.nc parameter. """ data = dataframe.to_numpy() with GMTTempFile(suffix=".nc") as tmpfile: output = triangulate( - data=data, spacing="5m", region=[245, 255, 20, 30], outgrid=tmpfile.name + data=data, spacing=1, region=[2, 4, 5, 6], outgrid=tmpfile.name ) assert output is None # check that output is None since outgrid is set assert os.path.exists(path=tmpfile.name) # check that outgrid exists with xr.open_dataarray(tmpfile.name) as grid: - assert isinstance(grid, xr.DataArray) # ensure netcdf grid loads ok - assert grid.shape == (121, 121) + assert isinstance(grid, xr.DataArray) + assert grid.gmt.registration == 0 # Gridline registration + assert grid.gmt.gtype == 0 # Cartesian type + xr.testing.assert_allclose(a=grid, b=expected_grid) From 01607b94a448f5de5a019292dad284d39343c751 Mon Sep 17 00:00:00 2001 From: Wei Ji <23487320+weiji14@users.noreply.github.com> Date: Fri, 11 Mar 2022 22:38:37 -0500 Subject: [PATCH 10/30] Refactor test_triangulate_input_xyz to use pd.testing.assert_frame_equal --- pygmt/tests/test_triangulate.py | 60 +++++++++++++++++++-------------- 1 file changed, 35 insertions(+), 25 deletions(-) diff --git a/pygmt/tests/test_triangulate.py b/pygmt/tests/test_triangulate.py index 4ab6e0a7068..f3bbac3fa03 100644 --- a/pygmt/tests/test_triangulate.py +++ b/pygmt/tests/test_triangulate.py @@ -3,6 +3,7 @@ """ import os +import numpy as np import pandas as pd import pytest import xarray as xr @@ -23,6 +24,30 @@ def fixture_dataframe(): )[:10] +@pytest.fixture(scope="module", name="expected_dataframe") +def fixture_dataframe_result(): + """ + Load the expected triangulate dataframe result. + """ + return pd.DataFrame( + data=[ + [7, 8, 2], + [8, 7, 9], + [7, 1, 0], + [1, 7, 2], + [1, 2, 4], + [8, 3, 2], + [9, 5, 3], + [5, 9, 6], + [5, 4, 3], + [4, 5, 6], + [4, 6, 1], + [3, 4, 2], + [9, 3, 8], + ] + ) + + @pytest.fixture(scope="module", name="expected_grid") def fixture_grid_result(): """ @@ -35,45 +60,30 @@ def fixture_grid_result(): ) -def test_triangulate_input_file(): - """ - Run triangulate by passing in a filename. - """ - output = triangulate(data="@Table_5_11_mean.xyz") - assert isinstance(output, pd.DataFrame) - assert output.shape == (67, 3) - - -def test_triangulate_input_data_array(dataframe): +@pytest.mark.parametrize("array_func", [np.array, xr.Dataset]) +def test_triangulate_input_table_matrix(array_func, dataframe, expected_dataframe): """ Run triangulate by passing in a numpy array into data. """ - data = dataframe.to_numpy() - output = triangulate(data=data) - assert isinstance(output, pd.DataFrame) - assert output.shape == (67, 3) + table = array_func(dataframe) + output = triangulate(data=table) + pd.testing.assert_frame_equal(left=output, right=expected_dataframe) -def test_triangulate_input_xyz(dataframe): +def test_triangulate_input_xyz(dataframe, expected_dataframe): """ Run triangulate by passing in x, y, z numpy.ndarrays individually. """ - output = triangulate( - x=dataframe.x, - y=dataframe.y, - z=dataframe.z, - ) - assert isinstance(output, pd.DataFrame) - assert output.shape == (67, 3) + output = triangulate(x=dataframe.x, y=dataframe.y, z=dataframe.z) + pd.testing.assert_frame_equal(left=output, right=expected_dataframe) -def test_triangulate_input_xy_no_z(dataframe): +def test_triangulate_input_xy_no_z(dataframe, expected_dataframe): """ Run triangulate by passing in x and y, but no z. """ output = triangulate(x=dataframe.x, y=dataframe.y) - assert isinstance(output, pd.DataFrame) - assert output.shape == (67, 3) + pd.testing.assert_frame_equal(left=output, right=expected_dataframe) def test_triangulate_wrong_kind_of_input(dataframe): From 2ee8e17dcd603678e22920bc1e0acd3dd7dadcbf Mon Sep 17 00:00:00 2001 From: Wei Ji <23487320+weiji14@users.noreply.github.com> Date: Fri, 11 Mar 2022 22:44:15 -0500 Subject: [PATCH 11/30] Remove unused load_sample_bathymetry import --- pygmt/tests/test_triangulate.py | 1 - 1 file changed, 1 deletion(-) diff --git a/pygmt/tests/test_triangulate.py b/pygmt/tests/test_triangulate.py index f3bbac3fa03..918678eacc8 100644 --- a/pygmt/tests/test_triangulate.py +++ b/pygmt/tests/test_triangulate.py @@ -8,7 +8,6 @@ import pytest import xarray as xr from pygmt import triangulate, which -from pygmt.datasets import load_sample_bathymetry from pygmt.exceptions import GMTInvalidInput from pygmt.helpers import GMTTempFile, data_kind From 0850d7488f03ae69b685cdbda5e63e207177e62f Mon Sep 17 00:00:00 2001 From: Wei Ji <23487320+weiji14@users.noreply.github.com> Date: Sat, 12 Mar 2022 01:14:51 -0500 Subject: [PATCH 12/30] Implement regular_grid and delaunay_triples staticmethod for triangulate --- doc/api/index.rst | 2 + pygmt/src/triangulate.py | 404 ++++++++++++++++++++++++++------ pygmt/tests/test_triangulate.py | 51 ++-- 3 files changed, 363 insertions(+), 94 deletions(-) diff --git a/doc/api/index.rst b/doc/api/index.rst index 57d397ecebc..5287a36836e 100644 --- a/doc/api/index.rst +++ b/doc/api/index.rst @@ -125,6 +125,8 @@ Operations on tabular data sphinterpolate surface triangulate + triangulate.regular_grid + triangulate.delaunay_triples xyz2grd Operations on raster data diff --git a/pygmt/src/triangulate.py b/pygmt/src/triangulate.py index bc3e577f94d..8c6113dbfbc 100644 --- a/pygmt/src/triangulate.py +++ b/pygmt/src/triangulate.py @@ -14,98 +14,350 @@ from pygmt.io import load_dataarray -@fmt_docstring -@use_alias( - G="outgrid", - I="spacing", - J="projection", - R="region", - V="verbose", - b="binary", - d="nodata", - e="find", - f="coltypes", - h="header", - i="incols", - r="registration", -) -@kwargs_to_strings(R="sequence") -def triangulate(data=None, x=None, y=None, z=None, **kwargs): +class triangulate: # pylint: disable=invalid-name """ Delaunay triangulation or Voronoi partitioning and gridding of Cartesian data. Triangulate reads in x,y[,z] data and performs Delaunay triangulation, i.e., it finds how the points should be connected to give the most - equilateral triangulation possible. If a map projection (give *region* - and *projection*) is chosen then it is applied before the triangulation - is calculated. - - Must provide either ``data`` or ``x``, ``y``, and ``z``. - - Full option list at :gmt-docs:`triangulate.html` - - {aliases} - - Parameters - ---------- - x/y/z : np.ndarray - Arrays of x and y coordinates and values z of the data points. - data : str or {table-like} - Pass in (x, y, z) or (longitude, latitude, elevation) values by - providing a file name to an ASCII data table, a 2D - {table-classes}. - {J} - {R} - {I} - outgrid : bool or str - Use triangulation to grid the data onto an even grid (specified with - ``region`` and ``spacing``). Set to ``True``, or pass in the name of - the output grid file. The interpolation is performed in the original - coordinates, so if your triangles are close to the poles you are better - off projecting all data to a local coordinate system before using - *triangulate* (this is true of all gridding routines) or instead - select *sphtriangulate*. - {V} - {b} - {d} - {e} - {f} - {h} - {i} - {r} - Only valid with ``outgrid``. - - Returns - ------- - ret: pandas.DataFrame or xarray.DataArray or None - Return type depends on whether the ``outgrid`` parameter is set: - - - pandas.DataFrame if ``outgrid`` is None (default) - - xarray.DataArray if ``outgrid`` is True - - None if ``outgrid`` is a str (grid output is stored in ``outgrid``) + equilateral triangulation possible. If a map projection (give *region* and + *projection*) is chosen then it is applied before the triangulation is + calculated. By default, the output is triplets of point id numbers that + make up each triangle. The id numbers refer to the points position (line + number, starting at 0 for the first line) in the input file. If **outgrid** + and **spacing** are set a grid will be calculated based on the surface + defined by the planar triangles. The actual algorithm used in the + triangulations is either that of Watson [1982] [Default] or Shewchuk [1996] + (if installed; type **gmt triangulate -** on the command line to see which + method is selected). This choice is made during the GMT installation. + Furthermore, if the Shewchuk algorithm is installed then you can also + perform the calculation of Voronoi polygons and optionally grid your data + via the natural nearest neighbor algorithm. **Note**: For geographic data + with global or very large extent you should consider + :gmt-docs:`sphtriangulate ` instead since + **triangulate** is a Cartesian or small-geographic area operator and is + unaware of periodic or polar boundary conditions. """ - with GMTTempFile(suffix=".nc") as tmpfile: + + @staticmethod + @fmt_docstring + @use_alias( + G="outgrid", + I="spacing", + J="projection", + R="region", + V="verbose", + b="binary", + d="nodata", + e="find", + f="coltypes", + h="header", + i="incols", + r="registration", + s="skiprows", + w="wrap", + ) + @kwargs_to_strings(R="sequence") + def _triangulate( + data=None, x=None, y=None, z=None, output_type=None, outfile=None, **kwargs + ): + """ + Delaunay triangulation or Voronoi partitioning and gridding of + Cartesian data. + + Must provide ``outfile`` or ``outgrid``. + + Full option list at :gmt-docs:`triangulate.html` + + {aliases} + + Parameters + ---------- + x/y/z : np.ndarray + Arrays of x and y coordinates and values z of the data points. + data : str or {table-like} + Pass in (x, y, z) or (longitude, latitude, elevation) values by + providing a file name to an ASCII data table, a 2D + {table-classes}. + {J} + {R} + {I} + outgrid : bool or str + The name of the output netCDF file with extension .nc to store the + grid in. The interpolation is performed in the original + coordinates, so if your triangles are close to the poles you are + better off projecting all data to a local coordinate system before + using *triangulate* (this is true of all gridding routines) or + instead select :gmt-docs:`sphtriangulate `. + outfile : str or bool or None + The name of the output ASCII file to store the results of the + histogram equalization in. + output_type: str + Determines the output type. Use "file", "xarray", "pandas", or + "numpy". + {V} + {b} + {d} + {e} + {f} + {h} + {i} + {r} + Only valid with ``outgrid``. + {s} + {w} + + Returns + ------- + ret: numpy.ndarray or pandas.DataFrame or xarray.DataArray or None + Return type depends on the ``output_type`` parameter: + + - numpy.ndarray if ``output_type`` is "numpy" + - pandas.DataFrame if ``output_type`` is "pandas" + - xarray.DataArray if ``output_type`` is "xarray"" + - None if ``output_type`` is "file" (output is stored in + ``outgrid`` or ``outfile``) + """ with Session() as lib: # Choose how data will be passed into the module table_context = lib.virtualfile_from_data( - check_kind="vector", data=data, x=x, y=y, z=z + check_kind="vector", data=data, x=x, y=y, z=z, required_z=False ) with table_context as infile: if "G" not in kwargs: # table output if outgrid is unset - kwargs.update({">": tmpfile.name}) + kwargs.update({">": outfile}) else: # NetCDF or xarray.DataArray output if outgrid is set - if ( - kwargs["G"] is True - ): # xarray.DataArray output if outgrid is True - kwargs.update({"G": tmpfile.name}) outgrid = kwargs["G"] arg_str = " ".join([infile, build_arg_string(kwargs)]) lib.call_module(module="triangulate", args=arg_str) - try: - result = load_dataarray(outgrid) if outgrid == tmpfile.name else None - except UnboundLocalError: # if outgrid unset, return pd.DataFrame - result = pd.read_csv(tmpfile.name, sep="\t", header=None) + if output_type == "file": + return None + if output_type == "xarray": + return load_dataarray(outgrid) + + result = pd.read_csv(outfile, sep="\t", header=None) + if output_type == "numpy": + return result.to_numpy() + return result + + @staticmethod + @fmt_docstring + @kwargs_to_strings(R="sequence") + def regular_grid( # pylint: disable=too-many-arguments,too-many-locals + data=None, + x=None, + y=None, + z=None, + outgrid=None, + spacing=None, + projection=None, + region=None, + verbose=None, + binary=None, + nodata=None, + find=None, + coltypes=None, + header=None, + incols=None, + registration=None, + skiprows=None, + wrap=None, + **kwargs + ): + """ + Delaunay triangle based gridding of Cartesian data. + + Reads in x,y[,z] data and performs Delaunay triangulation, i.e., it + finds how the points should be connected to give the most equilateral + triangulation possible. If a map projection (give *region* and + *projection*) is chosen then it is applied before the triangulation is + calculated. By setting **outgrid** and **spacing**, a grid will be + calculated based on the surface defined by the planar triangles. The + actual algorithm used in the triangulations is either that of Watson + [1982] [Default] or Shewchuk [1996] (if installed; type + **gmt triangulate -** on the command line to see which method is + selected). This choice is made during the GMT installation. + Furthermore, if the Shewchuk algorithm is installed then you can also + perform the calculation of Voronoi polygons and optionally grid your + data via the natural nearest neighbor algorithm. **Note**: For + geographic data with global or very large extent you should consider + :gmt-docs:`sphtriangulate ` instead since + **triangulate** is a Cartesian or small-geographic area operator and is + unaware of periodic or polar boundary conditions. + + Must provide either ``data`` or ``x``, ``y``, and ``z``. - return result + Full option list at :gmt-docs:`triangulate.html` + + Parameters + ---------- + x/y/z : np.ndarray + Arrays of x and y coordinates and values z of the data points. + data : str or {table-like} + Pass in (x, y, z) or (longitude, latitude, elevation) values by + providing a file name to an ASCII data table, a 2D + {table-classes}. + {J} + {R} + {I} + outgrid : str or bool or None + The name of the output netCDF file with extension .nc to store the + grid in. The interpolation is performed in the original + coordinates, so if your triangles are close to the poles you are + better off projecting all data to a local coordinate system before + using *triangulate* (this is true of all gridding routines) or + instead select :gmt-docs:`sphtriangulate `. + {V} + {b} + {d} + {e} + {f} + {h} + {i} + {r} + {s} + {w} + + Returns + ------- + ret: xarray.DataArray or None + Return type depends on whether the ``outgrid`` parameter is set: + + - xarray.DataArray if ``outgrid`` is True or None (default) + - None if ``outgrid`` is a str (grid output is stored in + ``outgrid``) + """ + # Return an xarray.DataArray if ``outgrid`` is not set + with GMTTempFile(suffix=".nc") as tmpfile: + if isinstance(outgrid, str): + output_type = "file" + else: + output_type = "xarray" + outgrid = tmpfile.name + return triangulate._triangulate( + data=data, + x=x, + y=y, + z=z, + output_type=output_type, + outgrid=outgrid, + spacing=spacing, + projection=projection, + region=region, + verbose=verbose, + binary=binary, + nodata=nodata, + find=find, + coltypes=coltypes, + header=header, + incols=incols, + registration=registration, + skiprows=skiprows, + wrap=wrap, + **kwargs + ) + + @staticmethod + @fmt_docstring + @kwargs_to_strings(R="sequence") + def delaunay_triples( # pylint: disable=too-many-arguments,too-many-locals + data=None, + x=None, + y=None, + z=None, + output_type="pandas", + outfile=None, + projection=None, + region=None, + verbose=None, + binary=None, + nodata=None, + find=None, + coltypes=None, + header=None, + incols=None, + skiprows=None, + wrap=None, + **kwargs + ): + """ + Delaunay triangle based gridding of Cartesian data. + + Reads in x,y[,z] data and performs Delaunay triangulation, i.e., it + finds how the points should be connected to give the most equilateral + triangulation possible. If a map projection (give *region* and + *projection*) is chosen then it is applied before the triangulation is + calculated. The actual algorithm used in the triangulations is either + that of Watson [1982] [Default] or Shewchuk [1996] (if installed; type + **gmt triangulate -** on the command line to see which method is + selected). This choice is made during the GMT installation. + Furthermore, if the Shewchuk algorithm is installed then you can also + perform the calculation of Voronoi polygons and optionally grid your + data via the natural nearest neighbor algorithm. **Note**: For + geographic data with global or very large extent you should consider + :gmt-docs:`sphtriangulate ` instead since + **triangulate** is a Cartesian or small-geographic area operator and is + unaware of periodic or polar boundary conditions. + + Must provide either ``data`` or ``x``, ``y``, and ``z``. + + Full option list at :gmt-docs:`triangulate.html` + + Parameters + ---------- + x/y/z : np.ndarray + Arrays of x and y coordinates and values z of the data points. + data : str or {table-like} + Pass in (x, y, z) or (longitude, latitude, elevation) values by + providing a file name to an ASCII data table, a 2D + {table-classes}. + {J} + {R} + outfile : str or bool or None + The name of the output ASCII file to store the results of the + histogram equalization in. + {V} + {b} + {d} + {e} + {f} + {h} + {i} + {s} + {w} + + Returns + ------- + ret: pandas.DataFrame or None + Return type depends on the ``outfile`` parameter: + + - pandas.DataFrame if ``outfile`` is True or None + - None if ``outfile`` is a str (file output is stored in + ``outfile``) + """ + # Return a pandas.DataFrame if ``outfile`` is not set + with GMTTempFile(suffix=".txt") as tmpfile: + if output_type != "file": + outfile = tmpfile.name + return triangulate._triangulate( + data=data, + x=x, + y=y, + z=z, + output_type=output_type, + outfile=outfile, + projection=projection, + region=region, + verbose=verbose, + binary=binary, + nodata=nodata, + find=find, + coltypes=coltypes, + header=header, + incols=incols, + skiprows=skiprows, + wrap=wrap, + **kwargs + ) diff --git a/pygmt/tests/test_triangulate.py b/pygmt/tests/test_triangulate.py index 918678eacc8..cc11163efa0 100644 --- a/pygmt/tests/test_triangulate.py +++ b/pygmt/tests/test_triangulate.py @@ -60,60 +60,75 @@ def fixture_grid_result(): @pytest.mark.parametrize("array_func", [np.array, xr.Dataset]) -def test_triangulate_input_table_matrix(array_func, dataframe, expected_dataframe): +def test_delaunay_triples_input_table_matrix(array_func, dataframe, expected_dataframe): """ - Run triangulate by passing in a numpy array into data. + Run triangulate.delaunay_triples by passing in a numpy.array or + xarray.Dataset. """ table = array_func(dataframe) - output = triangulate(data=table) + output = triangulate.delaunay_triples(data=table) pd.testing.assert_frame_equal(left=output, right=expected_dataframe) -def test_triangulate_input_xyz(dataframe, expected_dataframe): +def test_delaunay_triples_input_xyz(dataframe, expected_dataframe): """ - Run triangulate by passing in x, y, z numpy.ndarrays individually. + Run triangulate.delaunay_triples by passing in x, y, z numpy.ndarrays + individually. """ - output = triangulate(x=dataframe.x, y=dataframe.y, z=dataframe.z) + output = triangulate.delaunay_triples(x=dataframe.x, y=dataframe.y, z=dataframe.z) pd.testing.assert_frame_equal(left=output, right=expected_dataframe) -def test_triangulate_input_xy_no_z(dataframe, expected_dataframe): +def test_delaunay_triples_input_xy_no_z(dataframe, expected_dataframe): """ - Run triangulate by passing in x and y, but no z. + Run triangulate.delaunay_triples by passing in x and y, but no z. """ - output = triangulate(x=dataframe.x, y=dataframe.y) + output = triangulate.delaunay_triples(x=dataframe.x, y=dataframe.y) pd.testing.assert_frame_equal(left=output, right=expected_dataframe) -def test_triangulate_wrong_kind_of_input(dataframe): +def test_delaunay_triples_wrong_kind_of_input(dataframe): """ - Run triangulate using grid input that is not file/matrix/vectors. + Run triangulate.delaunay_triples using grid input that is not + file/matrix/vectors. """ data = dataframe.z.to_xarray() # convert pandas.Series to xarray.DataArray assert data_kind(data) == "grid" with pytest.raises(GMTInvalidInput): - triangulate(data=data) + triangulate.delaunay_triples(data=data) -def test_triangulate_with_outgrid_true(dataframe, expected_grid): +def test_delaunay_triples_ndarray_output(dataframe, expected_dataframe): """ - Run triangulate with outgrid=True and see it load into an xarray.DataArray. + Test triangulate.delaunay_triples with "numpy" output type. + """ + output = triangulate.delaunay_triples(data=dataframe, output_type="numpy") + assert isinstance(output, np.ndarray) + np.testing.assert_allclose(actual=output, desired=expected_dataframe.to_numpy()) + + +def test_regular_grid_with_outgrid_true(dataframe, expected_grid): + """ + Run triangulate.regular_grid with outgrid=True and see it load into an + xarray.DataArray. """ data = dataframe.to_numpy() - output = triangulate(data=data, spacing=1, region=[2, 4, 5, 6], outgrid=True) + output = triangulate.regular_grid( + data=data, spacing=1, region=[2, 4, 5, 6], outgrid=True + ) assert isinstance(output, xr.DataArray) assert output.gmt.registration == 0 # Gridline registration assert output.gmt.gtype == 0 # Cartesian type xr.testing.assert_allclose(a=output, b=expected_grid) -def test_triangulate_with_outgrid_param(dataframe, expected_grid): +def test_regular_grid_with_outgrid_param(dataframe, expected_grid): """ - Run triangulate with the -Goutputfile.nc parameter. + Run triangulate.regular_grid with the -Goutputfile.nc parameter. """ data = dataframe.to_numpy() with GMTTempFile(suffix=".nc") as tmpfile: - output = triangulate( + output = triangulate.regular_grid( data=data, spacing=1, region=[2, 4, 5, 6], outgrid=tmpfile.name ) assert output is None # check that output is None since outgrid is set From 4db68124aa1ebc6d63f4c3b110983747b0615b32 Mon Sep 17 00:00:00 2001 From: Wei Ji <23487320+weiji14@users.noreply.github.com> Date: Sat, 12 Mar 2022 08:12:31 -0500 Subject: [PATCH 13/30] Let list inputs to spacing (I) and incols (i) work Use I="sequence" and i="sequence_comma". --- pygmt/src/triangulate.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pygmt/src/triangulate.py b/pygmt/src/triangulate.py index 8c6113dbfbc..e35f57da0c4 100644 --- a/pygmt/src/triangulate.py +++ b/pygmt/src/triangulate.py @@ -58,7 +58,7 @@ class triangulate: # pylint: disable=invalid-name s="skiprows", w="wrap", ) - @kwargs_to_strings(R="sequence") + @kwargs_to_strings(I="sequence", R="sequence", i="sequence_comma") def _triangulate( data=None, x=None, y=None, z=None, output_type=None, outfile=None, **kwargs ): From 05a3a08d3a8e54f350859e8549d248abafa7e105 Mon Sep 17 00:00:00 2001 From: Wei Ji <23487320+weiji14@users.noreply.github.com> Date: Sat, 12 Mar 2022 08:19:51 -0500 Subject: [PATCH 14/30] Ensure triangulate.delaunay_triples output_type is valid Must be either one of numpy, pandas or file --- pygmt/src/triangulate.py | 7 +++++++ pygmt/tests/test_triangulate.py | 8 ++++++++ 2 files changed, 15 insertions(+) diff --git a/pygmt/src/triangulate.py b/pygmt/src/triangulate.py index e35f57da0c4..28da0bd26e3 100644 --- a/pygmt/src/triangulate.py +++ b/pygmt/src/triangulate.py @@ -4,6 +4,7 @@ """ import pandas as pd from pygmt.clib import Session +from pygmt.exceptions import GMTInvalidInput from pygmt.helpers import ( GMTTempFile, build_arg_string, @@ -337,6 +338,12 @@ def delaunay_triples( # pylint: disable=too-many-arguments,too-many-locals - None if ``outfile`` is a str (file output is stored in ``outfile``) """ + # Return a pandas.DataFrame if ``outfile`` is not set + if output_type not in ["numpy", "pandas", "file"]: + raise GMTInvalidInput( + "Must specify 'output_type' either as 'numpy', 'pandas' or 'file'." + ) + # Return a pandas.DataFrame if ``outfile`` is not set with GMTTempFile(suffix=".txt") as tmpfile: if output_type != "file": diff --git a/pygmt/tests/test_triangulate.py b/pygmt/tests/test_triangulate.py index cc11163efa0..f06f9e5776d 100644 --- a/pygmt/tests/test_triangulate.py +++ b/pygmt/tests/test_triangulate.py @@ -107,6 +107,14 @@ def test_delaunay_triples_ndarray_output(dataframe, expected_dataframe): np.testing.assert_allclose(actual=output, desired=expected_dataframe.to_numpy()) +def test_delaunay_triples_invalid_format(dataframe): + """ + Test that triangulate.delaunay_triples fails with incorrect format. + """ + with pytest.raises(GMTInvalidInput): + triangulate.delaunay_triples(data=dataframe, output_type=1) + + def test_regular_grid_with_outgrid_true(dataframe, expected_grid): """ Run triangulate.regular_grid with outgrid=True and see it load into an From 4a42b1af3cf0648e84873cc78862b33999e090f8 Mon Sep 17 00:00:00 2001 From: Wei Ji <23487320+weiji14@users.noreply.github.com> Date: Sat, 12 Mar 2022 08:24:55 -0500 Subject: [PATCH 15/30] Autocorrect output_type to 'file' if outfile parameter is set --- pygmt/src/triangulate.py | 19 +++++++++++++++---- pygmt/tests/test_triangulate.py | 14 ++++++++++++++ 2 files changed, 29 insertions(+), 4 deletions(-) diff --git a/pygmt/src/triangulate.py b/pygmt/src/triangulate.py index 28da0bd26e3..b73c0817787 100644 --- a/pygmt/src/triangulate.py +++ b/pygmt/src/triangulate.py @@ -2,6 +2,8 @@ triangulate - Delaunay triangulation or Voronoi partitioning and gridding of Cartesian data. """ +import warnings + import pandas as pd from pygmt.clib import Session from pygmt.exceptions import GMTInvalidInput @@ -165,7 +167,7 @@ def regular_grid( # pylint: disable=too-many-arguments,too-many-locals registration=None, skiprows=None, wrap=None, - **kwargs + **kwargs, ): """ Delaunay triangle based gridding of Cartesian data. @@ -257,7 +259,7 @@ def regular_grid( # pylint: disable=too-many-arguments,too-many-locals registration=registration, skiprows=skiprows, wrap=wrap, - **kwargs + **kwargs, ) @staticmethod @@ -281,7 +283,7 @@ def delaunay_triples( # pylint: disable=too-many-arguments,too-many-locals incols=None, skiprows=None, wrap=None, - **kwargs + **kwargs, ): """ Delaunay triangle based gridding of Cartesian data. @@ -344,6 +346,15 @@ def delaunay_triples( # pylint: disable=too-many-arguments,too-many-locals "Must specify 'output_type' either as 'numpy', 'pandas' or 'file'." ) + if isinstance(outfile, str) and output_type != "file": + msg = ( + f"Changing 'output_type' from '{output_type}' to 'file' " + "since 'outfile' parameter is set. Please use output_type='file' " + "to silence this warning." + ) + warnings.warn(message=msg, category=RuntimeWarning, stacklevel=2) + output_type = "file" + # Return a pandas.DataFrame if ``outfile`` is not set with GMTTempFile(suffix=".txt") as tmpfile: if output_type != "file": @@ -366,5 +377,5 @@ def delaunay_triples( # pylint: disable=too-many-arguments,too-many-locals incols=incols, skiprows=skiprows, wrap=wrap, - **kwargs + **kwargs, ) diff --git a/pygmt/tests/test_triangulate.py b/pygmt/tests/test_triangulate.py index f06f9e5776d..1f35e10ed0e 100644 --- a/pygmt/tests/test_triangulate.py +++ b/pygmt/tests/test_triangulate.py @@ -107,6 +107,20 @@ def test_delaunay_triples_ndarray_output(dataframe, expected_dataframe): np.testing.assert_allclose(actual=output, desired=expected_dataframe.to_numpy()) +def test_delaunay_triples_outfile(dataframe, expected_dataframe): + """ + Test triangulate.delaunay_triples with ``outfile``. + """ + with GMTTempFile(suffix=".txt") as tmpfile: + with pytest.warns(RuntimeWarning) as record: + result = triangulate.delaunay_triples(data=dataframe, outfile=tmpfile.name) + assert len(record) == 1 # check that only one warning was raised + assert result is None # return value is None + assert os.path.exists(path=tmpfile.name) + temp_df = pd.read_csv(filepath_or_buffer=tmpfile.name, sep="\t", header=None) + pd.testing.assert_frame_equal(left=temp_df, right=expected_dataframe) + + def test_delaunay_triples_invalid_format(dataframe): """ Test that triangulate.delaunay_triples fails with incorrect format. From 7ae5f6ac1b3eb0896c78482c1d12892b40d60985 Mon Sep 17 00:00:00 2001 From: Wei Ji <23487320+weiji14@users.noreply.github.com> Date: Sat, 12 Mar 2022 08:32:03 -0500 Subject: [PATCH 16/30] Prevent delaunay triples from setting header for non-file output --- pygmt/src/triangulate.py | 3 +++ pygmt/tests/test_triangulate.py | 2 ++ 2 files changed, 5 insertions(+) diff --git a/pygmt/src/triangulate.py b/pygmt/src/triangulate.py index b73c0817787..0218a3b5b76 100644 --- a/pygmt/src/triangulate.py +++ b/pygmt/src/triangulate.py @@ -355,6 +355,9 @@ def delaunay_triples( # pylint: disable=too-many-arguments,too-many-locals warnings.warn(message=msg, category=RuntimeWarning, stacklevel=2) output_type = "file" + if header is not None and output_type != "file": + raise GMTInvalidInput("'header' is only allowed with output_type='file'.") + # Return a pandas.DataFrame if ``outfile`` is not set with GMTTempFile(suffix=".txt") as tmpfile: if output_type != "file": diff --git a/pygmt/tests/test_triangulate.py b/pygmt/tests/test_triangulate.py index 1f35e10ed0e..edd07c0b8f8 100644 --- a/pygmt/tests/test_triangulate.py +++ b/pygmt/tests/test_triangulate.py @@ -127,6 +127,8 @@ def test_delaunay_triples_invalid_format(dataframe): """ with pytest.raises(GMTInvalidInput): triangulate.delaunay_triples(data=dataframe, output_type=1) + with pytest.raises(GMTInvalidInput): + triangulate.delaunay_triples(data=dataframe, output_type="pandas", header="o+c") def test_regular_grid_with_outgrid_true(dataframe, expected_grid): From 39c85b09586c384faa7af2096bbc936b58afeeaa Mon Sep 17 00:00:00 2001 From: Wei Ji <23487320+weiji14@users.noreply.github.com> Date: Sat, 12 Mar 2022 11:57:29 -0500 Subject: [PATCH 17/30] Allow only str or None inputs to outgrid parameter Xref https://github.com/GenericMappingTools/pygmt/issues/1807 --- pygmt/src/triangulate.py | 11 ++++++++--- pygmt/tests/test_triangulate.py | 17 ++++++++++++----- 2 files changed, 20 insertions(+), 8 deletions(-) diff --git a/pygmt/src/triangulate.py b/pygmt/src/triangulate.py index 0218a3b5b76..1b570bd721a 100644 --- a/pygmt/src/triangulate.py +++ b/pygmt/src/triangulate.py @@ -205,7 +205,7 @@ def regular_grid( # pylint: disable=too-many-arguments,too-many-locals {J} {R} {I} - outgrid : str or bool or None + outgrid : str or None The name of the output netCDF file with extension .nc to store the grid in. The interpolation is performed in the original coordinates, so if your triangles are close to the poles you are @@ -228,7 +228,7 @@ def regular_grid( # pylint: disable=too-many-arguments,too-many-locals ret: xarray.DataArray or None Return type depends on whether the ``outgrid`` parameter is set: - - xarray.DataArray if ``outgrid`` is True or None (default) + - xarray.DataArray if ``outgrid`` is None (default) - None if ``outgrid`` is a str (grid output is stored in ``outgrid``) """ @@ -236,9 +236,14 @@ def regular_grid( # pylint: disable=too-many-arguments,too-many-locals with GMTTempFile(suffix=".nc") as tmpfile: if isinstance(outgrid, str): output_type = "file" - else: + elif outgrid is None: output_type = "xarray" outgrid = tmpfile.name + else: + raise GMTInvalidInput( + "'outgrid' should be a proper file name or `None`" + ) + return triangulate._triangulate( data=data, x=x, diff --git a/pygmt/tests/test_triangulate.py b/pygmt/tests/test_triangulate.py index edd07c0b8f8..a660c5a146d 100644 --- a/pygmt/tests/test_triangulate.py +++ b/pygmt/tests/test_triangulate.py @@ -131,15 +131,13 @@ def test_delaunay_triples_invalid_format(dataframe): triangulate.delaunay_triples(data=dataframe, output_type="pandas", header="o+c") -def test_regular_grid_with_outgrid_true(dataframe, expected_grid): +def test_regular_grid_no_outgrid(dataframe, expected_grid): """ - Run triangulate.regular_grid with outgrid=True and see it load into an + Run triangulate.regular_grid with no set outgrid and see it load into an xarray.DataArray. """ data = dataframe.to_numpy() - output = triangulate.regular_grid( - data=data, spacing=1, region=[2, 4, 5, 6], outgrid=True - ) + output = triangulate.regular_grid(data=data, spacing=1, region=[2, 4, 5, 6]) assert isinstance(output, xr.DataArray) assert output.gmt.registration == 0 # Gridline registration assert output.gmt.gtype == 0 # Cartesian type @@ -162,3 +160,12 @@ def test_regular_grid_with_outgrid_param(dataframe, expected_grid): assert grid.gmt.registration == 0 # Gridline registration assert grid.gmt.gtype == 0 # Cartesian type xr.testing.assert_allclose(a=grid, b=expected_grid) + + +def test_regular_grid_invalid_format(dataframe): + """ + Test that triangulate.regular_grid fails with outgrid that is not None or + a proper file name. + """ + with pytest.raises(GMTInvalidInput): + triangulate.regular_grid(data=dataframe, outgrid=True) From 9c17913545d69136b828e13324042bf2b8cef767 Mon Sep 17 00:00:00 2001 From: Wei Ji <23487320+weiji14@users.noreply.github.com> Date: Sat, 12 Mar 2022 12:16:33 -0500 Subject: [PATCH 18/30] Format test_regular_grid_invalid_format docstring --- pygmt/tests/test_triangulate.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/pygmt/tests/test_triangulate.py b/pygmt/tests/test_triangulate.py index a660c5a146d..5688487f322 100644 --- a/pygmt/tests/test_triangulate.py +++ b/pygmt/tests/test_triangulate.py @@ -164,8 +164,8 @@ def test_regular_grid_with_outgrid_param(dataframe, expected_grid): def test_regular_grid_invalid_format(dataframe): """ - Test that triangulate.regular_grid fails with outgrid that is not None or - a proper file name. + Test that triangulate.regular_grid fails with outgrid that is not None or a + proper file name. """ with pytest.raises(GMTInvalidInput): triangulate.regular_grid(data=dataframe, outgrid=True) From ed0ba2c30a7a86871537ef663b06e48c4c184c93 Mon Sep 17 00:00:00 2001 From: Wei Ji <23487320+weiji14@users.noreply.github.com> Date: Sat, 12 Mar 2022 08:32:03 -0500 Subject: [PATCH 19/30] Revert "Prevent delaunay triples from setting header for non-file output" This reverts commit 7ae5f6ac1b3eb0896c78482c1d12892b40d60985. --- pygmt/src/triangulate.py | 3 --- pygmt/tests/test_triangulate.py | 2 -- 2 files changed, 5 deletions(-) diff --git a/pygmt/src/triangulate.py b/pygmt/src/triangulate.py index 1b570bd721a..c835d7737a6 100644 --- a/pygmt/src/triangulate.py +++ b/pygmt/src/triangulate.py @@ -360,9 +360,6 @@ def delaunay_triples( # pylint: disable=too-many-arguments,too-many-locals warnings.warn(message=msg, category=RuntimeWarning, stacklevel=2) output_type = "file" - if header is not None and output_type != "file": - raise GMTInvalidInput("'header' is only allowed with output_type='file'.") - # Return a pandas.DataFrame if ``outfile`` is not set with GMTTempFile(suffix=".txt") as tmpfile: if output_type != "file": diff --git a/pygmt/tests/test_triangulate.py b/pygmt/tests/test_triangulate.py index 5688487f322..ec835f9316e 100644 --- a/pygmt/tests/test_triangulate.py +++ b/pygmt/tests/test_triangulate.py @@ -127,8 +127,6 @@ def test_delaunay_triples_invalid_format(dataframe): """ with pytest.raises(GMTInvalidInput): triangulate.delaunay_triples(data=dataframe, output_type=1) - with pytest.raises(GMTInvalidInput): - triangulate.delaunay_triples(data=dataframe, output_type="pandas", header="o+c") def test_regular_grid_no_outgrid(dataframe, expected_grid): From d69c4631b9ba4e4cc894a0e46bd4428935df4138 Mon Sep 17 00:00:00 2001 From: Wei Ji <23487320+weiji14@users.noreply.github.com> Date: Sat, 12 Mar 2022 13:57:17 -0500 Subject: [PATCH 20/30] Remove region (R) parameter from delaunay_triples --- pygmt/src/triangulate.py | 2 -- 1 file changed, 2 deletions(-) diff --git a/pygmt/src/triangulate.py b/pygmt/src/triangulate.py index c835d7737a6..a4ec2ee735b 100644 --- a/pygmt/src/triangulate.py +++ b/pygmt/src/triangulate.py @@ -278,7 +278,6 @@ def delaunay_triples( # pylint: disable=too-many-arguments,too-many-locals output_type="pandas", outfile=None, projection=None, - region=None, verbose=None, binary=None, nodata=None, @@ -372,7 +371,6 @@ def delaunay_triples( # pylint: disable=too-many-arguments,too-many-locals output_type=output_type, outfile=outfile, projection=projection, - region=region, verbose=verbose, binary=binary, nodata=nodata, From 4417da17165dec0be4ddad40954865f11e8d099d Mon Sep 17 00:00:00 2001 From: Wei Ji <23487320+weiji14@users.noreply.github.com> Date: Sat, 12 Mar 2022 14:21:45 -0500 Subject: [PATCH 21/30] Use gmt get GMT_TRIANGULATE to check whether Watson or Shewchuk is used --- pygmt/src/triangulate.py | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/pygmt/src/triangulate.py b/pygmt/src/triangulate.py index a4ec2ee735b..505f15cb377 100644 --- a/pygmt/src/triangulate.py +++ b/pygmt/src/triangulate.py @@ -32,8 +32,8 @@ class triangulate: # pylint: disable=invalid-name and **spacing** are set a grid will be calculated based on the surface defined by the planar triangles. The actual algorithm used in the triangulations is either that of Watson [1982] [Default] or Shewchuk [1996] - (if installed; type **gmt triangulate -** on the command line to see which - method is selected). This choice is made during the GMT installation. + (if installed; type **gmt get GMT_TRIANGULATE** on the command line to see + which method is selected). This choice is made during the GMT installation. Furthermore, if the Shewchuk algorithm is installed then you can also perform the calculation of Voronoi polygons and optionally grid your data via the natural nearest neighbor algorithm. **Note**: For geographic data @@ -180,7 +180,7 @@ def regular_grid( # pylint: disable=too-many-arguments,too-many-locals calculated based on the surface defined by the planar triangles. The actual algorithm used in the triangulations is either that of Watson [1982] [Default] or Shewchuk [1996] (if installed; type - **gmt triangulate -** on the command line to see which method is + **gmt get GMT_TRIANGULATE** on the command line to see which method is selected). This choice is made during the GMT installation. Furthermore, if the Shewchuk algorithm is installed then you can also perform the calculation of Voronoi polygons and optionally grid your @@ -298,7 +298,7 @@ def delaunay_triples( # pylint: disable=too-many-arguments,too-many-locals *projection*) is chosen then it is applied before the triangulation is calculated. The actual algorithm used in the triangulations is either that of Watson [1982] [Default] or Shewchuk [1996] (if installed; type - **gmt triangulate -** on the command line to see which method is + **gmt get GMT_TRIANGULATE** on the command line to see which method is selected). This choice is made during the GMT installation. Furthermore, if the Shewchuk algorithm is installed then you can also perform the calculation of Voronoi polygons and optionally grid your From 02ac1d8dc0e5328ec784625ea92304976d8739da Mon Sep 17 00:00:00 2001 From: Wei Ji <23487320+weiji14@users.noreply.github.com> Date: Sun, 13 Mar 2022 10:49:40 -0400 Subject: [PATCH 22/30] State that Shewchuk is the default triangulation algorithm As per https://github.com/GenericMappingTools/gmt/pull/6438 --- pygmt/src/triangulate.py | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/pygmt/src/triangulate.py b/pygmt/src/triangulate.py index 505f15cb377..20850ed980d 100644 --- a/pygmt/src/triangulate.py +++ b/pygmt/src/triangulate.py @@ -31,7 +31,7 @@ class triangulate: # pylint: disable=invalid-name number, starting at 0 for the first line) in the input file. If **outgrid** and **spacing** are set a grid will be calculated based on the surface defined by the planar triangles. The actual algorithm used in the - triangulations is either that of Watson [1982] [Default] or Shewchuk [1996] + triangulations is either that of Watson [1982] or Shewchuk [1996] [Default] (if installed; type **gmt get GMT_TRIANGULATE** on the command line to see which method is selected). This choice is made during the GMT installation. Furthermore, if the Shewchuk algorithm is installed then you can also @@ -179,7 +179,7 @@ def regular_grid( # pylint: disable=too-many-arguments,too-many-locals calculated. By setting **outgrid** and **spacing**, a grid will be calculated based on the surface defined by the planar triangles. The actual algorithm used in the triangulations is either that of Watson - [1982] [Default] or Shewchuk [1996] (if installed; type + [1982] or Shewchuk [1996] [Default] (if installed; type **gmt get GMT_TRIANGULATE** on the command line to see which method is selected). This choice is made during the GMT installation. Furthermore, if the Shewchuk algorithm is installed then you can also @@ -297,7 +297,7 @@ def delaunay_triples( # pylint: disable=too-many-arguments,too-many-locals triangulation possible. If a map projection (give *region* and *projection*) is chosen then it is applied before the triangulation is calculated. The actual algorithm used in the triangulations is either - that of Watson [1982] [Default] or Shewchuk [1996] (if installed; type + that of Watson [1982] or Shewchuk [1996] [Default] (if installed; type **gmt get GMT_TRIANGULATE** on the command line to see which method is selected). This choice is made during the GMT installation. Furthermore, if the Shewchuk algorithm is installed then you can also From 9996fe82e2276ac7ddae70a27290ee31b7f313fa Mon Sep 17 00:00:00 2001 From: Wei Ji <23487320+weiji14@users.noreply.github.com> Date: Sun, 13 Mar 2022 12:18:53 -0400 Subject: [PATCH 23/30] Update docstring with better new formatting conventions Also removed mention of Voronoi polygons since not directly implemented yet. Co-Authored-By: Meghan Jones --- pygmt/src/triangulate.py | 62 +++++++++++++++++++--------------------- 1 file changed, 30 insertions(+), 32 deletions(-) diff --git a/pygmt/src/triangulate.py b/pygmt/src/triangulate.py index 20850ed980d..15251fec689 100644 --- a/pygmt/src/triangulate.py +++ b/pygmt/src/triangulate.py @@ -24,23 +24,22 @@ class triangulate: # pylint: disable=invalid-name Triangulate reads in x,y[,z] data and performs Delaunay triangulation, i.e., it finds how the points should be connected to give the most - equilateral triangulation possible. If a map projection (give *region* and - *projection*) is chosen then it is applied before the triangulation is - calculated. By default, the output is triplets of point id numbers that + equilateral triangulation possible. If a map projection (give ``region`` + and ``projection``) is chosen then it is applied before the triangulation + is calculated. By default, the output is triplets of point id numbers that make up each triangle. The id numbers refer to the points position (line - number, starting at 0 for the first line) in the input file. If **outgrid** - and **spacing** are set a grid will be calculated based on the surface + number, starting at 0 for the first line) in the input file. If ``outgrid`` + and ``spacing`` are set a grid will be calculated based on the surface defined by the planar triangles. The actual algorithm used in the - triangulations is either that of Watson [1982] or Shewchuk [1996] [Default] - (if installed; type **gmt get GMT_TRIANGULATE** on the command line to see - which method is selected). This choice is made during the GMT installation. - Furthermore, if the Shewchuk algorithm is installed then you can also - perform the calculation of Voronoi polygons and optionally grid your data - via the natural nearest neighbor algorithm. **Note**: For geographic data - with global or very large extent you should consider - :gmt-docs:`sphtriangulate ` instead since - **triangulate** is a Cartesian or small-geographic area operator and is - unaware of periodic or polar boundary conditions. + triangulations is either that of Watson [1982] or Shewchuk [1996] [Default + is Shewchuk if installed; type ``gmt get GMT_TRIANGULATE`` on the command + line to see which method is selected]. Furthermore, if the Shewchuk + algorithm is installed then you can also perform the calculation of Voronoi + polygons and optionally grid your data via the natural nearest neighbor + algorithm. **Note**: For geographic data with global or very large extent + you should consider :gmt-docs:`sphtriangulate ` + instead since ``triangulate`` is a Cartesian or small-geographic area + operator and is unaware of periodic or polar boundary conditions. """ @staticmethod @@ -91,7 +90,7 @@ def _triangulate( grid in. The interpolation is performed in the original coordinates, so if your triangles are close to the poles you are better off projecting all data to a local coordinate system before - using *triangulate* (this is true of all gridding routines) or + using ``triangulate`` (this is true of all gridding routines) or instead select :gmt-docs:`sphtriangulate `. outfile : str or bool or None The name of the output ASCII file to store the results of the @@ -176,22 +175,24 @@ def regular_grid( # pylint: disable=too-many-arguments,too-many-locals finds how the points should be connected to give the most equilateral triangulation possible. If a map projection (give *region* and *projection*) is chosen then it is applied before the triangulation is - calculated. By setting **outgrid** and **spacing**, a grid will be + calculated. By setting ``outgrid`` and ``spacing``, a grid will be calculated based on the surface defined by the planar triangles. The actual algorithm used in the triangulations is either that of Watson - [1982] or Shewchuk [1996] [Default] (if installed; type - **gmt get GMT_TRIANGULATE** on the command line to see which method is + [1982] or Shewchuk [1996] [Default is Shewchuk if installed; type + ``gmt get GMT_TRIANGULATE`` on the command line to see which method is selected). This choice is made during the GMT installation. Furthermore, if the Shewchuk algorithm is installed then you can also perform the calculation of Voronoi polygons and optionally grid your data via the natural nearest neighbor algorithm. **Note**: For geographic data with global or very large extent you should consider :gmt-docs:`sphtriangulate ` instead since - **triangulate** is a Cartesian or small-geographic area operator and is + ``triangulate`` is a Cartesian or small-geographic area operator and is unaware of periodic or polar boundary conditions. Must provide either ``data`` or ``x``, ``y``, and ``z``. + Must provide ``region`` and ``spacing``. + Full option list at :gmt-docs:`triangulate.html` Parameters @@ -210,7 +211,7 @@ def regular_grid( # pylint: disable=too-many-arguments,too-many-locals grid in. The interpolation is performed in the original coordinates, so if your triangles are close to the poles you are better off projecting all data to a local coordinate system before - using *triangulate* (this is true of all gridding routines) or + using ``triangulate`` (this is true of all gridding routines) or instead select :gmt-docs:`sphtriangulate `. {V} {b} @@ -294,18 +295,15 @@ def delaunay_triples( # pylint: disable=too-many-arguments,too-many-locals Reads in x,y[,z] data and performs Delaunay triangulation, i.e., it finds how the points should be connected to give the most equilateral - triangulation possible. If a map projection (give *region* and - *projection*) is chosen then it is applied before the triangulation is - calculated. The actual algorithm used in the triangulations is either - that of Watson [1982] or Shewchuk [1996] [Default] (if installed; type - **gmt get GMT_TRIANGULATE** on the command line to see which method is - selected). This choice is made during the GMT installation. - Furthermore, if the Shewchuk algorithm is installed then you can also - perform the calculation of Voronoi polygons and optionally grid your - data via the natural nearest neighbor algorithm. **Note**: For - geographic data with global or very large extent you should consider + triangulation possible. If a map projection (give ``region`` and + ``projection``) is chosen then it is applied before the triangulation + is calculated. The actual algorithm used in the triangulations is + either that of Watson [1982] or Shewchuk [1996] [Default if installed; + type ``gmt get GMT_TRIANGULATE`` on the command line to see which + method is selected). **Note**: For geographic data with global or very + large extent you should consider :gmt-docs:`sphtriangulate ` instead since - **triangulate** is a Cartesian or small-geographic area operator and is + ``triangulate`` is a Cartesian or small-geographic area operator and is unaware of periodic or polar boundary conditions. Must provide either ``data`` or ``x``, ``y``, and ``z``. From b2786061c8b4d81cacef52464ec26c7a364dae56 Mon Sep 17 00:00:00 2001 From: Wei Ji <23487320+weiji14@users.noreply.github.com> Date: Sun, 13 Mar 2022 12:22:00 -0400 Subject: [PATCH 24/30] A few more docstring fixes --- pygmt/src/triangulate.py | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/pygmt/src/triangulate.py b/pygmt/src/triangulate.py index 15251fec689..944e19cb9c8 100644 --- a/pygmt/src/triangulate.py +++ b/pygmt/src/triangulate.py @@ -173,9 +173,9 @@ def regular_grid( # pylint: disable=too-many-arguments,too-many-locals Reads in x,y[,z] data and performs Delaunay triangulation, i.e., it finds how the points should be connected to give the most equilateral - triangulation possible. If a map projection (give *region* and - *projection*) is chosen then it is applied before the triangulation is - calculated. By setting ``outgrid`` and ``spacing``, a grid will be + triangulation possible. If a map projection (give ``region`` and + ``projection``) is chosen then it is applied before the triangulation + is calculated. By setting ``outgrid`` and ``spacing``, a grid will be calculated based on the surface defined by the planar triangles. The actual algorithm used in the triangulations is either that of Watson [1982] or Shewchuk [1996] [Default is Shewchuk if installed; type From d3fb5c628f6198883602be819db706c08e29ea8a Mon Sep 17 00:00:00 2001 From: Wei Ji <23487320+weiji14@users.noreply.github.com> Date: Sun, 13 Mar 2022 12:37:17 -0400 Subject: [PATCH 25/30] Handle output types better Co-authored-by: Meghan Jones --- pygmt/src/triangulate.py | 6 ++---- 1 file changed, 2 insertions(+), 4 deletions(-) diff --git a/pygmt/src/triangulate.py b/pygmt/src/triangulate.py index 944e19cb9c8..463e916805f 100644 --- a/pygmt/src/triangulate.py +++ b/pygmt/src/triangulate.py @@ -62,7 +62,7 @@ class triangulate: # pylint: disable=invalid-name ) @kwargs_to_strings(I="sequence", R="sequence", i="sequence_comma") def _triangulate( - data=None, x=None, y=None, z=None, output_type=None, outfile=None, **kwargs + data=None, x=None, y=None, z=None, *, output_type, outfile=None, **kwargs ): """ Delaunay triangulation or Voronoi partitioning and gridding of @@ -127,10 +127,8 @@ def _triangulate( check_kind="vector", data=data, x=x, y=y, z=z, required_z=False ) with table_context as infile: - if "G" not in kwargs: # table output if outgrid is unset + if (outgrid := kwargs.get("G")) is None: # table output if outgrid is unset kwargs.update({">": outfile}) - else: # NetCDF or xarray.DataArray output if outgrid is set - outgrid = kwargs["G"] arg_str = " ".join([infile, build_arg_string(kwargs)]) lib.call_module(module="triangulate", args=arg_str) From 1f53bebee529e8bc965190b09d9a60fb74fc1102 Mon Sep 17 00:00:00 2001 From: Wei Ji <23487320+weiji14@users.noreply.github.com> Date: Sun, 13 Mar 2022 12:41:02 -0400 Subject: [PATCH 26/30] Format fix --- pygmt/src/triangulate.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/pygmt/src/triangulate.py b/pygmt/src/triangulate.py index 463e916805f..c21f18c59f3 100644 --- a/pygmt/src/triangulate.py +++ b/pygmt/src/triangulate.py @@ -127,7 +127,8 @@ def _triangulate( check_kind="vector", data=data, x=x, y=y, z=z, required_z=False ) with table_context as infile: - if (outgrid := kwargs.get("G")) is None: # table output if outgrid is unset + # table output if outgrid is unset, else output to outgrid + if (outgrid := kwargs.get("G")) is None: kwargs.update({">": outfile}) arg_str = " ".join([infile, build_arg_string(kwargs)]) lib.call_module(module="triangulate", args=arg_str) From 00d3b71a7b5169a2e9e241a9325477ccf94514f5 Mon Sep 17 00:00:00 2001 From: Wei Ji <23487320+weiji14@users.noreply.github.com> Date: Sun, 13 Mar 2022 15:28:37 -0400 Subject: [PATCH 27/30] More triangulate docstring modifications Co-Authored-By: Meghan Jones --- pygmt/src/triangulate.py | 51 ++++++++++++++++++++++++---------------- 1 file changed, 31 insertions(+), 20 deletions(-) diff --git a/pygmt/src/triangulate.py b/pygmt/src/triangulate.py index c21f18c59f3..af02fe8177a 100644 --- a/pygmt/src/triangulate.py +++ b/pygmt/src/triangulate.py @@ -36,10 +36,14 @@ class triangulate: # pylint: disable=invalid-name line to see which method is selected]. Furthermore, if the Shewchuk algorithm is installed then you can also perform the calculation of Voronoi polygons and optionally grid your data via the natural nearest neighbor - algorithm. **Note**: For geographic data with global or very large extent - you should consider :gmt-docs:`sphtriangulate ` - instead since ``triangulate`` is a Cartesian or small-geographic area - operator and is unaware of periodic or polar boundary conditions. + algorithm. + + Note + ---- + For geographic data with global or very large extent you should consider + :gmt-docs:`sphtriangulate ` instead since + ``triangulate`` is a Cartesian or small-geographic area operator and is + unaware of periodic or polar boundary conditions. """ @staticmethod @@ -179,14 +183,10 @@ def regular_grid( # pylint: disable=too-many-arguments,too-many-locals actual algorithm used in the triangulations is either that of Watson [1982] or Shewchuk [1996] [Default is Shewchuk if installed; type ``gmt get GMT_TRIANGULATE`` on the command line to see which method is - selected). This choice is made during the GMT installation. + selected]. This choice is made during the GMT installation. Furthermore, if the Shewchuk algorithm is installed then you can also perform the calculation of Voronoi polygons and optionally grid your - data via the natural nearest neighbor algorithm. **Note**: For - geographic data with global or very large extent you should consider - :gmt-docs:`sphtriangulate ` instead since - ``triangulate`` is a Cartesian or small-geographic area operator and is - unaware of periodic or polar boundary conditions. + data via the natural nearest neighbor algorithm. Must provide either ``data`` or ``x``, ``y``, and ``z``. @@ -199,7 +199,7 @@ def regular_grid( # pylint: disable=too-many-arguments,too-many-locals x/y/z : np.ndarray Arrays of x and y coordinates and values z of the data points. data : str or {table-like} - Pass in (x, y, z) or (longitude, latitude, elevation) values by + Pass in (x, y[, z]) or (longitude, latitude[, elevation]) values by providing a file name to an ASCII data table, a 2D {table-classes}. {J} @@ -231,6 +231,13 @@ def regular_grid( # pylint: disable=too-many-arguments,too-many-locals - xarray.DataArray if ``outgrid`` is None (default) - None if ``outgrid`` is a str (grid output is stored in ``outgrid``) + + Note + ---- + For geographic data with global or very large extent you should + consider :gmt-docs:`sphtriangulate ` instead since + ``triangulate`` is a Cartesian or small-geographic area operator and is + unaware of periodic or polar boundary conditions. """ # Return an xarray.DataArray if ``outgrid`` is not set with GMTTempFile(suffix=".nc") as tmpfile: @@ -299,11 +306,7 @@ def delaunay_triples( # pylint: disable=too-many-arguments,too-many-locals is calculated. The actual algorithm used in the triangulations is either that of Watson [1982] or Shewchuk [1996] [Default if installed; type ``gmt get GMT_TRIANGULATE`` on the command line to see which - method is selected). **Note**: For geographic data with global or very - large extent you should consider - :gmt-docs:`sphtriangulate ` instead since - ``triangulate`` is a Cartesian or small-geographic area operator and is - unaware of periodic or polar boundary conditions. + method is selected). Must provide either ``data`` or ``x``, ``y``, and ``z``. @@ -334,12 +337,20 @@ def delaunay_triples( # pylint: disable=too-many-arguments,too-many-locals Returns ------- - ret: pandas.DataFrame or None - Return type depends on the ``outfile`` parameter: + ret : pandas.DataFrame or numpy.ndarray or None + Return type depends on ``outfile`` and ``output_type``: - - pandas.DataFrame if ``outfile`` is True or None - - None if ``outfile`` is a str (file output is stored in + - None if ``outfile`` is set (output will be stored in file set by ``outfile``) + - :class:`pandas.DataFrame` or :class:`numpy.ndarray` if ``outfile`` is + not set (depends on ``output_type``) + + Note + ---- + For geographic data with global or very large extent you should + consider :gmt-docs:`sphtriangulate ` instead since + ``triangulate`` is a Cartesian or small-geographic area operator and is + unaware of periodic or polar boundary conditions. """ # Return a pandas.DataFrame if ``outfile`` is not set if output_type not in ["numpy", "pandas", "file"]: From 4ce402b38718aab81bf56f5aa18550f399fc8038 Mon Sep 17 00:00:00 2001 From: Wei Ji <23487320+weiji14@users.noreply.github.com> Date: Sun, 13 Mar 2022 15:32:27 -0400 Subject: [PATCH 28/30] Wrap line to 79 characters --- pygmt/src/triangulate.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/pygmt/src/triangulate.py b/pygmt/src/triangulate.py index af02fe8177a..ac825b92fef 100644 --- a/pygmt/src/triangulate.py +++ b/pygmt/src/triangulate.py @@ -342,8 +342,8 @@ def delaunay_triples( # pylint: disable=too-many-arguments,too-many-locals - None if ``outfile`` is set (output will be stored in file set by ``outfile``) - - :class:`pandas.DataFrame` or :class:`numpy.ndarray` if ``outfile`` is - not set (depends on ``output_type``) + - :class:`pandas.DataFrame` or :class:`numpy.ndarray` if + ``outfile`` is not set (depends on ``output_type``) Note ---- From 3006668fd74202e8fd2c38cb0763db7c0c6b87d3 Mon Sep 17 00:00:00 2001 From: Wei Ji <23487320+weiji14@users.noreply.github.com> Date: Sun, 13 Mar 2022 15:35:05 -0400 Subject: [PATCH 29/30] Actually document the output_type parameter for delaunay_triples --- pygmt/src/triangulate.py | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/pygmt/src/triangulate.py b/pygmt/src/triangulate.py index ac825b92fef..afe69cdcf2e 100644 --- a/pygmt/src/triangulate.py +++ b/pygmt/src/triangulate.py @@ -325,6 +325,13 @@ def delaunay_triples( # pylint: disable=too-many-arguments,too-many-locals outfile : str or bool or None The name of the output ASCII file to store the results of the histogram equalization in. + output_type : str + Determine the format the xyz data will be returned in [Default is + ``pandas``]: + + - ``numpy`` - :class:`numpy.ndarray` + - ``pandas``- :class:`pandas.DataFrame` + - ``file`` - ASCII file (requires ``outfile``) {V} {b} {d} From 085620e94370db317843283ed1b7d0240f32f687 Mon Sep 17 00:00:00 2001 From: Wei Ji <23487320+weiji14@users.noreply.github.com> Date: Mon, 14 Mar 2022 07:34:28 -0400 Subject: [PATCH 30/30] Remove kwargs_to_strings decorator from methods Co-authored-by: Dongdong Tian --- pygmt/src/triangulate.py | 2 -- 1 file changed, 2 deletions(-) diff --git a/pygmt/src/triangulate.py b/pygmt/src/triangulate.py index afe69cdcf2e..e50eb086269 100644 --- a/pygmt/src/triangulate.py +++ b/pygmt/src/triangulate.py @@ -149,7 +149,6 @@ def _triangulate( @staticmethod @fmt_docstring - @kwargs_to_strings(R="sequence") def regular_grid( # pylint: disable=too-many-arguments,too-many-locals data=None, x=None, @@ -276,7 +275,6 @@ def regular_grid( # pylint: disable=too-many-arguments,too-many-locals @staticmethod @fmt_docstring - @kwargs_to_strings(R="sequence") def delaunay_triples( # pylint: disable=too-many-arguments,too-many-locals data=None, x=None,