From 66c4b97320ce92fab9b70fde2791ad5054f0c71e Mon Sep 17 00:00:00 2001 From: Dongdong Tian Date: Fri, 13 Oct 2023 22:08:43 +0800 Subject: [PATCH 01/19] Refactor the data_kind and the virtualfile_to_data functions --- pygmt/clib/session.py | 35 ++++---- pygmt/helpers/__init__.py | 1 + pygmt/helpers/utils.py | 166 +++++++++++++++++++------------------- pygmt/src/contour.py | 2 +- 4 files changed, 99 insertions(+), 105 deletions(-) diff --git a/pygmt/clib/session.py b/pygmt/clib/session.py index 8db686812c1..0988d87728d 100644 --- a/pygmt/clib/session.py +++ b/pygmt/clib/session.py @@ -32,6 +32,7 @@ fmt_docstring, tempfile_from_geojson, tempfile_from_image, + validate_data_input, ) FAMILIES = [ @@ -1474,11 +1475,8 @@ def virtualfile_from_data( self, check_kind=None, data=None, - x=None, - y=None, - z=None, - extra_arrays=None, - required_z=False, + vectors=None, + ncols=2, required_data=True, ): """ @@ -1497,13 +1495,11 @@ def virtualfile_from_data( Any raster or vector data format. This could be a file name or path, a raster grid, a vector matrix/arrays, or other supported data input. - x/y/z : 1-D arrays or None - x, y, and z columns as numpy arrays. - extra_arrays : list of 1-D arrays - Optional. A list of numpy arrays in addition to x, y, and z. - All of these arrays must be of the same size as the x/y/z arrays. - required_z : bool - State whether the 'z' column is required. + vectors : list of 1-D arrays or None + A list of 1-D arrays. Each array will be a column in the table. + All of these arrays must be of the same size. + ncols : int + The minimum number of columns required for the data. required_data : bool Set to True when 'data' is required, or False when dealing with optional virtual files. [Default is True]. @@ -1537,8 +1533,13 @@ def virtualfile_from_data( ... : N = 3 <7/9> <4/6> <1/3> """ - kind = data_kind( - data, x, y, z, required_z=required_z, required_data=required_data + kind = data_kind(data, required=required_data) + validate_data_input( + data=data, + vectors=vectors, + ncols=ncols, + required_data=required_data, + kind=kind, ) if check_kind: @@ -1579,11 +1580,7 @@ def virtualfile_from_data( warnings.warn(message=msg, category=RuntimeWarning, stacklevel=2) _data = (data,) if not isinstance(data, pathlib.PurePath) else (str(data),) elif kind == "vectors": - _data = [np.atleast_1d(x), np.atleast_1d(y)] - if z is not None: - _data.append(np.atleast_1d(z)) - if extra_arrays: - _data.extend(extra_arrays) + _data = [np.atleast_1d(v) for v in vectors] elif kind == "matrix": # turn 2-D arrays into list of vectors try: # pandas.Series will be handled below like a 1-D numpy.ndarray diff --git a/pygmt/helpers/__init__.py b/pygmt/helpers/__init__.py index 5eb265e8002..71bc0582252 100644 --- a/pygmt/helpers/__init__.py +++ b/pygmt/helpers/__init__.py @@ -20,4 +20,5 @@ is_nonstr_iter, launch_external_viewer, non_ascii_to_octal, + validate_data_input, ) diff --git a/pygmt/helpers/utils.py b/pygmt/helpers/utils.py index 31629a6ea52..d6b7ab8ce5a 100644 --- a/pygmt/helpers/utils.py +++ b/pygmt/helpers/utils.py @@ -15,113 +15,125 @@ from pygmt.exceptions import GMTInvalidInput -def _validate_data_input( - data=None, x=None, y=None, z=None, required_z=False, required_data=True, kind=None +def validate_data_input( + data=None, vectors=None, ncols=2, required_data=True, kind=None ): """ - Check if the combination of data/x/y/z is valid. + Check if the data input is valid. Examples -------- - >>> _validate_data_input(data="infile") - >>> _validate_data_input(x=[1, 2, 3], y=[4, 5, 6]) - >>> _validate_data_input(x=[1, 2, 3], y=[4, 5, 6], z=[7, 8, 9]) - >>> _validate_data_input(data=None, required_data=False) - >>> _validate_data_input() + >>> validate_data_input(data="infile") + >>> validate_data_input(vectors=[[1, 2, 3], [4, 5, 6]], ncols=2) + >>> validate_data_input(vectors=[[1, 2, 3], [4, 5, 6], [7, 8, 9]], ncols=3) + >>> validate_data_input(data=None, required_data=False) + >>> validate_data_input() Traceback (most recent call last): ... pygmt.exceptions.GMTInvalidInput: No input data provided. - >>> _validate_data_input(x=[1, 2, 3]) + >>> validate_data_input(vectors=[[1, 2, 3], None], ncols=2) Traceback (most recent call last): ... - pygmt.exceptions.GMTInvalidInput: Must provide both x and y. - >>> _validate_data_input(y=[4, 5, 6]) + pygmt.exceptions.GMTInvalidInput: The 'y' column can't be None. + >>> validate_data_input(vectors=[None, [4, 5, 6]], ncols=2) Traceback (most recent call last): ... - pygmt.exceptions.GMTInvalidInput: Must provide both x and y. - >>> _validate_data_input(x=[1, 2, 3], y=[4, 5, 6], required_z=True) + pygmt.exceptions.GMTInvalidInput: The 'x' column can't be None. + >>> validate_data_input(vectors=[[1, 2, 3], [4, 5, 6], None], ncols=3) Traceback (most recent call last): ... - pygmt.exceptions.GMTInvalidInput: Must provide x, y, and z. + pygmt.exceptions.GMTInvalidInput: The 'z' column can't be None. >>> import numpy as np >>> import pandas as pd >>> import xarray as xr >>> data = np.arange(8).reshape((4, 2)) - >>> _validate_data_input(data=data, required_z=True, kind="matrix") + >>> validate_data_input(data=data, ncols=3, kind="matrix") Traceback (most recent call last): ... - pygmt.exceptions.GMTInvalidInput: data must provide x, y, and z columns. - >>> _validate_data_input( + pygmt.exceptions.GMTInvalidInput: data must have at least 3 columns. + >>> validate_data_input( ... data=pd.DataFrame(data, columns=["x", "y"]), - ... required_z=True, + ... ncols=3, ... kind="matrix", ... ) Traceback (most recent call last): ... - pygmt.exceptions.GMTInvalidInput: data must provide x, y, and z columns. - >>> _validate_data_input( + pygmt.exceptions.GMTInvalidInput: data must have at least 3 columns. + >>> validate_data_input( ... data=xr.Dataset(pd.DataFrame(data, columns=["x", "y"])), - ... required_z=True, + ... ncols=3, ... kind="matrix", ... ) Traceback (most recent call last): ... - pygmt.exceptions.GMTInvalidInput: data must provide x, y, and z columns. - >>> _validate_data_input(data="infile", x=[1, 2, 3]) + pygmt.exceptions.GMTInvalidInput: data must have at least 3 columns. + >>> validate_data_input(data="infile", vectors=[[1, 2, 3], None]) Traceback (most recent call last): ... - pygmt.exceptions.GMTInvalidInput: Too much data. Use either data or x/y/z. - >>> _validate_data_input(data="infile", y=[4, 5, 6]) + pygmt.exceptions.GMTInvalidInput: Too much data. Pass in either 'data' or 1-D arrays. # noqa: W505 + >>> validate_data_input(data="infile", vectors=[None, [4, 5, 6]]) Traceback (most recent call last): ... - pygmt.exceptions.GMTInvalidInput: Too much data. Use either data or x/y/z. - >>> _validate_data_input(data="infile", z=[7, 8, 9]) + pygmt.exceptions.GMTInvalidInput: Too much data. Pass in either 'data' or 1-D arrays. # noqa: W505 + >>> validate_data_input(data="infile", vectors=[None, None, [7, 8, 9]]) Traceback (most recent call last): ... - pygmt.exceptions.GMTInvalidInput: Too much data. Use either data or x/y/z. + pygmt.exceptions.GMTInvalidInput: Too much data. Pass in either 'data' or 1-D arrays. # noqa: W505 Raises ------ GMTInvalidInput If the data input is not valid. """ - if data is None: # data is None - if x is None and y is None: # both x and y are None - if required_data: # data is not optional - raise GMTInvalidInput("No input data provided.") - elif x is None or y is None: # either x or y is None - raise GMTInvalidInput("Must provide both x and y.") - if required_z and z is None: # both x and y are not None, now check z - raise GMTInvalidInput("Must provide x, y, and z.") - else: # data is not None - if x is not None or y is not None or z is not None: - raise GMTInvalidInput("Too much data. Use either data or x/y/z.") - # For 'matrix' kind, check if data has the required z column - if kind == "matrix" and required_z: + if kind is None: + kind = data_kind(data=data, required=required_data) + + if kind == "vectors": # From data_kind, we know that data is None + if vectors is None: + raise GMTInvalidInput("No input data provided.") + if len(vectors) < ncols: + raise GMTInvalidInput( + f"Requires {ncols} 1-D arrays but got {len(vectors)}." + ) + for i, v in enumerate(vectors[:ncols]): + if v is None: + if i < 3: + msg = f"The '{'xyz'[i]}' column can't be None." + else: + msg = "Column {i} can't be None." + raise GMTInvalidInput(msg) + else: + if vectors is not None and any(v is not None for v in vectors): + raise GMTInvalidInput("Too much data. Pass in either 'data' or 1-D arrays.") + if kind == "matrix": # check number of columns for matrix-like data if hasattr(data, "shape"): # np.ndarray or pd.DataFrame - if len(data.shape) == 1 and data.shape[0] < 3: - raise GMTInvalidInput("data must provide x, y, and z columns.") - if len(data.shape) > 1 and data.shape[1] < 3: - raise GMTInvalidInput("data must provide x, y, and z columns.") - if hasattr(data, "data_vars") and len(data.data_vars) < 3: # xr.Dataset - raise GMTInvalidInput("data must provide x, y, and z columns.") + if len(data.shape) == 1 and data.shape[0] < ncols: + raise GMTInvalidInput(f"data must have at least {ncols} columns.") + if len(data.shape) > 1 and data.shape[1] < ncols: + raise GMTInvalidInput(f"data must have at least {ncols} columns.") + if hasattr(data, "data_vars") and len(data.data_vars) < ncols: # xr.Dataset + raise GMTInvalidInput(f"data must have at least {ncols} columns.") -def data_kind(data=None, x=None, y=None, z=None, required_z=False, required_data=True): +def data_kind(data=None, required=True): """ - Check what kind of data is provided to a module. + Determine the kind of data that will be passed to a module. - Possible types: + It checks the type of the ``data`` argument and determines the kind of + data. Falls back to ``"vectors"`` if ``data`` is None but required. - * a file name provided as 'data' - * a pathlib.PurePath object provided as 'data' - * an xarray.DataArray object provided as 'data' - * a 2-D matrix provided as 'data' - * 1-D arrays x and y (and z, optionally) - * an optional argument (None, bool, int or float) provided as 'data' + Possible data kinds: - Arguments should be ``None`` if not used. If doesn't fit any of these - categories (or fits more than one), will raise an exception. + - ``'file'``: a file name or a pathlib.PurePath object providfed as 'data' + - ``'arg'``: an optional argument (None, bool, int or float) provided + as 'data' + - ``'grid'``: an xarray.DataArray with 2 dimensions provided as 'data' + - ``'image'``: an xarray.DataArray with 3 dimensions provided as 'data' + - ``'geojson'``: a geo-like Python object that implements + ``__geo_interface__`` (geopandas.GeoDataFrame or shapely.geometry) + provided as 'data' + - ``'matrix'``: a 2-D array provided as 'data' + - ``'vectors'``: a list of 1-D arrays provided as 'vectors' Parameters ---------- @@ -129,13 +141,7 @@ def data_kind(data=None, x=None, y=None, z=None, required_z=False, required_data Pass in either a file name or :class:`pathlib.Path` to an ASCII data table, an :class:`xarray.DataArray`, a 1-D/2-D {table-classes} or an option argument. - x/y : 1-D arrays or None - x and y columns as numpy arrays. - z : 1-D array or None - z column as numpy array. To be used optionally when x and y are given. - required_z : bool - State whether the 'z' column is required. - required_data : bool + required : bool Set to True when 'data' is required, or False when dealing with optional virtual files. [Default is True]. @@ -151,29 +157,28 @@ def data_kind(data=None, x=None, y=None, z=None, required_z=False, required_data >>> import numpy as np >>> import xarray as xr >>> import pathlib - >>> data_kind(data=None, x=np.array([1, 2, 3]), y=np.array([4, 5, 6])) + >>> data_kind(data=None) 'vectors' - >>> data_kind(data=np.arange(10).reshape((5, 2)), x=None, y=None) + >>> data_kind(data=np.arange(10).reshape((5, 2))) 'matrix' - >>> data_kind(data="my-data-file.txt", x=None, y=None) + >>> data_kind(data="my-data-file.txt") 'file' - >>> data_kind(data=pathlib.Path("my-data-file.txt"), x=None, y=None) + >>> data_kind(data=pathlib.Path("my-data-file.txt")) 'file' - >>> data_kind(data=None, x=None, y=None, required_data=False) + >>> data_kind(data=None, required=False) 'arg' - >>> data_kind(data=2.0, x=None, y=None, required_data=False) + >>> data_kind(data=2.0, required=False) 'arg' - >>> data_kind(data=True, x=None, y=None, required_data=False) + >>> data_kind(data=True, required=False) 'arg' >>> data_kind(data=xr.DataArray(np.random.rand(4, 3))) 'grid' >>> data_kind(data=xr.DataArray(np.random.rand(3, 4, 5))) 'image' """ - # determine the data kind if isinstance(data, (str, pathlib.PurePath)): kind = "file" - elif isinstance(data, (bool, int, float)) or (data is None and not required_data): + elif isinstance(data, (bool, int, float)) or (data is None and not required): kind = "arg" elif isinstance(data, xr.DataArray): kind = "image" if len(data.dims) == 3 else "grid" @@ -181,19 +186,10 @@ def data_kind(data=None, x=None, y=None, z=None, required_z=False, required_data # geo-like Python object that implements ``__geo_interface__`` # (geopandas.GeoDataFrame or shapely.geometry) kind = "geojson" - elif data is not None: + elif data is not None: # anything but None is taken as a matrix kind = "matrix" - else: + else: # fallback to vectors if data is None but required kind = "vectors" - _validate_data_input( - data=data, - x=x, - y=y, - z=z, - required_z=required_z, - required_data=required_data, - kind=kind, - ) return kind diff --git a/pygmt/src/contour.py b/pygmt/src/contour.py index 6aaf22b7cd6..ac34dcb5d95 100644 --- a/pygmt/src/contour.py +++ b/pygmt/src/contour.py @@ -116,7 +116,7 @@ def contour(self, data=None, x=None, y=None, z=None, **kwargs): with Session() as lib: file_context = lib.virtualfile_from_data( - check_kind="vector", data=data, x=x, y=y, z=z, required_z=True + check_kind="vector", data=data, vectors=[x, y, z], ncols=3 ) with file_context as fname: lib.call_module( From 78c28cdd52679b92dfb78e36e27a029bc30c56e7 Mon Sep 17 00:00:00 2001 From: Dongdong Tian Date: Sat, 14 Oct 2023 21:59:34 +0800 Subject: [PATCH 02/19] Update more functions --- pygmt/src/blockm.py | 2 +- pygmt/src/nearneighbor.py | 2 +- pygmt/src/plot.py | 19 ++++++++++++------- pygmt/src/plot3d.py | 25 ++++++++++++------------- pygmt/src/project.py | 2 +- pygmt/src/rose.py | 2 +- pygmt/src/sphdistance.py | 2 +- pygmt/src/surface.py | 2 +- pygmt/src/text.py | 13 ++++++++----- pygmt/src/triangulate.py | 2 +- pygmt/src/wiggle.py | 2 +- pygmt/src/xyz2grd.py | 2 +- 12 files changed, 41 insertions(+), 34 deletions(-) diff --git a/pygmt/src/blockm.py b/pygmt/src/blockm.py index bc896c853dc..d8462445ed6 100644 --- a/pygmt/src/blockm.py +++ b/pygmt/src/blockm.py @@ -44,7 +44,7 @@ def _blockm(block_method, data, x, y, z, outfile, **kwargs): with GMTTempFile(suffix=".csv") as tmpfile: with Session() as lib: table_context = lib.virtualfile_from_data( - check_kind="vector", data=data, x=x, y=y, z=z, required_z=True + check_kind="vector", data=data, vectors=[x, y, z], ncols=3 ) # Run blockm* on data table with table_context as infile: diff --git a/pygmt/src/nearneighbor.py b/pygmt/src/nearneighbor.py index 53aa9057dde..b53b8ec4a9f 100644 --- a/pygmt/src/nearneighbor.py +++ b/pygmt/src/nearneighbor.py @@ -150,7 +150,7 @@ def nearneighbor(data=None, x=None, y=None, z=None, **kwargs): with GMTTempFile(suffix=".nc") as tmpfile: with Session() as lib: table_context = lib.virtualfile_from_data( - check_kind="vector", data=data, x=x, y=y, z=z, required_z=True + check_kind="vector", data=data, vectors=[x, y, z], ncols=3 ) with table_context as infile: if (outgrid := kwargs.get("G")) is None: diff --git a/pygmt/src/plot.py b/pygmt/src/plot.py index 069ef5c7077..3e998122486 100644 --- a/pygmt/src/plot.py +++ b/pygmt/src/plot.py @@ -213,11 +213,13 @@ def plot(self, data=None, x=None, y=None, size=None, direction=None, **kwargs): # pylint: disable=too-many-locals kwargs = self._preprocess(**kwargs) # pylint: disable=protected-access - kind = data_kind(data, x, y) + kind = data_kind(data) + vectors = [x, y] + ncols = 2 - extra_arrays = [] if kwargs.get("S") is not None and kwargs["S"][0] in "vV" and direction is not None: - extra_arrays.extend(direction) + vectors.extend(direction) + ncols += 2 elif ( kwargs.get("S") is None and kind == "geojson" @@ -239,14 +241,16 @@ def plot(self, data=None, x=None, y=None, size=None, direction=None, **kwargs): raise GMTInvalidInput( "Can't use arrays for fill if data is matrix or file." ) - extra_arrays.append(kwargs["G"]) + vectors.append(kwargs["G"]) + ncols += 1 del kwargs["G"] if size is not None: if kind != "vectors": raise GMTInvalidInput( "Can't use arrays for 'size' if data is a matrix or file." ) - extra_arrays.append(size) + vectors.append(size) + ncols += 1 for flag in ["I", "t"]: if kwargs.get(flag) is not None and is_nonstr_iter(kwargs[flag]): @@ -254,12 +258,13 @@ def plot(self, data=None, x=None, y=None, size=None, direction=None, **kwargs): raise GMTInvalidInput( f"Can't use arrays for {plot.aliases[flag]} if data is matrix or file." ) - extra_arrays.append(kwargs[flag]) + vectors.append(kwargs[flag]) + ncols += 1 kwargs[flag] = "" with Session() as lib: file_context = lib.virtualfile_from_data( - check_kind="vector", data=data, x=x, y=y, extra_arrays=extra_arrays + check_kind="vector", data=data, vectors=vectors, ncols=ncols ) with file_context as fname: diff --git a/pygmt/src/plot3d.py b/pygmt/src/plot3d.py index 8999933ff6f..215b659ac8f 100644 --- a/pygmt/src/plot3d.py +++ b/pygmt/src/plot3d.py @@ -183,11 +183,13 @@ def plot3d( # pylint: disable=too-many-locals kwargs = self._preprocess(**kwargs) # pylint: disable=protected-access - kind = data_kind(data, x, y, z) + kind = data_kind(data) + vectors = [x, y, z] + ncols = 3 - extra_arrays = [] if kwargs.get("S") is not None and kwargs["S"][0] in "vV" and direction is not None: - extra_arrays.extend(direction) + vectors.extend(direction) + ncols += 2 elif ( kwargs.get("S") is None and kind == "geojson" @@ -209,14 +211,16 @@ def plot3d( raise GMTInvalidInput( "Can't use arrays for fill if data is matrix or file." ) - extra_arrays.append(kwargs["G"]) + vectors.append(kwargs["G"]) + ncols += 1 del kwargs["G"] if size is not None: if kind != "vectors": raise GMTInvalidInput( "Can't use arrays for 'size' if data is a matrix or a file." ) - extra_arrays.append(size) + ncols += 1 + vectors.append(size) for flag in ["I", "t"]: if kwargs.get(flag) is not None and is_nonstr_iter(kwargs[flag]): @@ -224,18 +228,13 @@ def plot3d( raise GMTInvalidInput( f"Can't use arrays for {plot3d.aliases[flag]} if data is matrix or file." ) - extra_arrays.append(kwargs[flag]) + vectors.append(kwargs[flag]) + ncols += 1 kwargs[flag] = "" with Session() as lib: file_context = lib.virtualfile_from_data( - check_kind="vector", - data=data, - x=x, - y=y, - z=z, - extra_arrays=extra_arrays, - required_z=True, + check_kind="vector", data=data, vectors=vectors, ncols=ncols ) with file_context as fname: diff --git a/pygmt/src/project.py b/pygmt/src/project.py index bdb2490c071..dd7f665fd4b 100644 --- a/pygmt/src/project.py +++ b/pygmt/src/project.py @@ -228,7 +228,7 @@ def project(data=None, x=None, y=None, z=None, outfile=None, **kwargs): with Session() as lib: if kwargs.get("G") is None: table_context = lib.virtualfile_from_data( - check_kind="vector", data=data, x=x, y=y, z=z, required_z=False + check_kind="vector", data=data, vectors=[x, y, z], ncols=3 ) # Run project on the temporary (csv) data table diff --git a/pygmt/src/rose.py b/pygmt/src/rose.py index c60ede7d61d..4a9b39d2cfd 100644 --- a/pygmt/src/rose.py +++ b/pygmt/src/rose.py @@ -203,7 +203,7 @@ def rose(self, data=None, length=None, azimuth=None, **kwargs): with Session() as lib: file_context = lib.virtualfile_from_data( - check_kind="vector", data=data, x=length, y=azimuth + check_kind="vector", data=data, vectors=[length, azimuth], ncols=2 ) with file_context as fname: diff --git a/pygmt/src/sphdistance.py b/pygmt/src/sphdistance.py index f6242b1f43d..46f2ee2abc2 100644 --- a/pygmt/src/sphdistance.py +++ b/pygmt/src/sphdistance.py @@ -120,7 +120,7 @@ def sphdistance(data=None, x=None, y=None, **kwargs): with GMTTempFile(suffix=".nc") as tmpfile: with Session() as lib: file_context = lib.virtualfile_from_data( - check_kind="vector", data=data, x=x, y=y + check_kind="vector", data=data, vectors=[x, y], ncols=2 ) with file_context as infile: if (outgrid := kwargs.get("G")) is None: diff --git a/pygmt/src/surface.py b/pygmt/src/surface.py index 80987f80de4..32b099c2af0 100644 --- a/pygmt/src/surface.py +++ b/pygmt/src/surface.py @@ -165,7 +165,7 @@ def surface(data=None, x=None, y=None, z=None, **kwargs): with GMTTempFile(suffix=".nc") as tmpfile: with Session() as lib: file_context = lib.virtualfile_from_data( - check_kind="vector", data=data, x=x, y=y, z=z, required_z=True + check_kind="vector", data=data, vectors=[x, y, z], ncols=3 ) with file_context as infile: if (outgrid := kwargs.get("G")) is None: diff --git a/pygmt/src/text.py b/pygmt/src/text.py index cd7dabcc3d5..f4a368aa16f 100644 --- a/pygmt/src/text.py +++ b/pygmt/src/text.py @@ -179,7 +179,7 @@ def text_( raise GMTInvalidInput( "Provide either position only, or x/y pairs, or textfiles." ) - kind = data_kind(textfiles, x, y, text) + kind = data_kind(textfiles) if kind == "vectors" and text is None: raise GMTInvalidInput("Must provide text with x/y pairs") else: @@ -221,22 +221,25 @@ def text_( if isinstance(position, str): kwargs["F"] += f"+c{position}+t{text}" - extra_arrays = [] + vectors = [x, y] + ncols = 2 # If an array of transparency is given, GMT will read it from # the last numerical column per data record. if kwargs.get("t") is not None and is_nonstr_iter(kwargs["t"]): - extra_arrays.append(kwargs["t"]) + vectors.append(kwargs["t"]) kwargs["t"] = "" + ncols += 1 # Append text at last column. Text must be passed in as str type. if kind == "vectors": - extra_arrays.append( + vectors.append( np.vectorize(non_ascii_to_octal)(np.atleast_1d(text).astype(str)) ) + ncols += 1 with Session() as lib: file_context = lib.virtualfile_from_data( - check_kind="vector", data=textfiles, x=x, y=y, extra_arrays=extra_arrays + check_kind="vector", data=textfiles, vectors=vectors, ncols=ncols ) with file_context as fname: lib.call_module(module="text", args=build_arg_string(kwargs, infile=fname)) diff --git a/pygmt/src/triangulate.py b/pygmt/src/triangulate.py index de77394cc9b..3f47926e90b 100644 --- a/pygmt/src/triangulate.py +++ b/pygmt/src/triangulate.py @@ -127,7 +127,7 @@ def _triangulate( """ with Session() as lib: table_context = lib.virtualfile_from_data( - check_kind="vector", data=data, x=x, y=y, z=z, required_z=False + check_kind="vector", data=data, vectors=[x, y, z], ncols=3 ) with table_context as infile: # table output if outgrid is unset, else output to outgrid diff --git a/pygmt/src/wiggle.py b/pygmt/src/wiggle.py index ff5dec107a1..2b2577a0b2c 100644 --- a/pygmt/src/wiggle.py +++ b/pygmt/src/wiggle.py @@ -122,7 +122,7 @@ def wiggle( with Session() as lib: file_context = lib.virtualfile_from_data( - check_kind="vector", data=data, x=x, y=y, z=z, required_z=True + check_kind="vector", data=data, vectors=[x, y, z], ncols=3 ) with file_context as fname: diff --git a/pygmt/src/xyz2grd.py b/pygmt/src/xyz2grd.py index fe510cc3316..b0cf1f51ff2 100644 --- a/pygmt/src/xyz2grd.py +++ b/pygmt/src/xyz2grd.py @@ -154,7 +154,7 @@ def xyz2grd(data=None, x=None, y=None, z=None, **kwargs): with GMTTempFile(suffix=".nc") as tmpfile: with Session() as lib: file_context = lib.virtualfile_from_data( - check_kind="vector", data=data, x=x, y=y, z=z, required_z=True + check_kind="vector", data=data, vectors=[x, y, z], ncols=3 ) with file_context as infile: if (outgrid := kwargs.get("G")) is None: From f37413b2aa03c24de6c9ea1e94053d7607b4d9b2 Mon Sep 17 00:00:00 2001 From: Dongdong Tian Date: Sun, 15 Oct 2023 19:14:33 +0800 Subject: [PATCH 03/19] Change ncols to names --- pygmt/clib/session.py | 9 +++--- pygmt/helpers/utils.py | 64 +++++++++++++++++++++++++-------------- pygmt/src/blockm.py | 2 +- pygmt/src/contour.py | 2 +- pygmt/src/nearneighbor.py | 2 +- pygmt/src/plot.py | 12 ++++---- pygmt/src/plot3d.py | 12 ++++---- pygmt/src/project.py | 2 +- pygmt/src/rose.py | 5 ++- pygmt/src/sphdistance.py | 2 +- pygmt/src/surface.py | 2 +- pygmt/src/text.py | 8 ++--- pygmt/src/triangulate.py | 2 +- pygmt/src/wiggle.py | 2 +- pygmt/src/xyz2grd.py | 2 +- 15 files changed, 75 insertions(+), 53 deletions(-) diff --git a/pygmt/clib/session.py b/pygmt/clib/session.py index 0988d87728d..c2a0ba12427 100644 --- a/pygmt/clib/session.py +++ b/pygmt/clib/session.py @@ -1476,7 +1476,7 @@ def virtualfile_from_data( check_kind=None, data=None, vectors=None, - ncols=2, + names=["x", "y"], required_data=True, ): """ @@ -1498,8 +1498,9 @@ def virtualfile_from_data( vectors : list of 1-D arrays or None A list of 1-D arrays. Each array will be a column in the table. All of these arrays must be of the same size. - ncols : int - The minimum number of columns required for the data. + names : list of str + A list of names for each of the columns. Must be of the same size + as the number of vectors. Default is ``["x", "y"]``. required_data : bool Set to True when 'data' is required, or False when dealing with optional virtual files. [Default is True]. @@ -1537,7 +1538,7 @@ def virtualfile_from_data( validate_data_input( data=data, vectors=vectors, - ncols=ncols, + names=names, required_data=required_data, kind=kind, ) diff --git a/pygmt/helpers/utils.py b/pygmt/helpers/utils.py index d6b7ab8ce5a..6926f1a1911 100644 --- a/pygmt/helpers/utils.py +++ b/pygmt/helpers/utils.py @@ -16,30 +16,49 @@ def validate_data_input( - data=None, vectors=None, ncols=2, required_data=True, kind=None + data=None, vectors=None, names=["x", "y"], required_data=True, kind=None ): """ Check if the data input is valid. + Parameters + ---------- + data : str, pathlib.PurePath, None, bool, xarray.DataArray or {table-like} + Pass in either a file name or :class:`pathlib.Path` to an ASCII data + table, an :class:`xarray.DataArray`, a 1-D/2-D + {table-classes} or an option argument. + vectors : list of 1-D arrays + A list of 1-D arrays with the data columns. + names : list of str + List of column names. + required_data : bool + Set to True when 'data' is required, or False when dealing with + optional virtual files. [Default is True]. + kind : str or None + The kind of data that will be passed to a module. If not given, it + will be determined by calling :func:`data_kind`. + Examples -------- >>> validate_data_input(data="infile") - >>> validate_data_input(vectors=[[1, 2, 3], [4, 5, 6]], ncols=2) - >>> validate_data_input(vectors=[[1, 2, 3], [4, 5, 6], [7, 8, 9]], ncols=3) + >>> validate_data_input(vectors=[[1, 2, 3], [4, 5, 6]], names="xy") + >>> validate_data_input( + ... vectors=[[1, 2, 3], [4, 5, 6], [7, 8, 9]], names="xyz" + ... ) >>> validate_data_input(data=None, required_data=False) >>> validate_data_input() Traceback (most recent call last): ... pygmt.exceptions.GMTInvalidInput: No input data provided. - >>> validate_data_input(vectors=[[1, 2, 3], None], ncols=2) + >>> validate_data_input(vectors=[[1, 2, 3], None], names="xy") Traceback (most recent call last): ... pygmt.exceptions.GMTInvalidInput: The 'y' column can't be None. - >>> validate_data_input(vectors=[None, [4, 5, 6]], ncols=2) + >>> validate_data_input(vectors=[None, [4, 5, 6]], names="xy") Traceback (most recent call last): ... pygmt.exceptions.GMTInvalidInput: The 'x' column can't be None. - >>> validate_data_input(vectors=[[1, 2, 3], [4, 5, 6], None], ncols=3) + >>> validate_data_input(vectors=[[1, 2, 3], [4, 5, 6], None], names="xyz") Traceback (most recent call last): ... pygmt.exceptions.GMTInvalidInput: The 'z' column can't be None. @@ -47,13 +66,13 @@ def validate_data_input( >>> import pandas as pd >>> import xarray as xr >>> data = np.arange(8).reshape((4, 2)) - >>> validate_data_input(data=data, ncols=3, kind="matrix") + >>> validate_data_input(data=data, names="xyz", kind="matrix") Traceback (most recent call last): ... pygmt.exceptions.GMTInvalidInput: data must have at least 3 columns. >>> validate_data_input( ... data=pd.DataFrame(data, columns=["x", "y"]), - ... ncols=3, + ... names="xyz", ... kind="matrix", ... ) Traceback (most recent call last): @@ -61,7 +80,7 @@ def validate_data_input( pygmt.exceptions.GMTInvalidInput: data must have at least 3 columns. >>> validate_data_input( ... data=xr.Dataset(pd.DataFrame(data, columns=["x", "y"])), - ... ncols=3, + ... names="xyz", ... kind="matrix", ... ) Traceback (most recent call last): @@ -91,28 +110,27 @@ def validate_data_input( if kind == "vectors": # From data_kind, we know that data is None if vectors is None: raise GMTInvalidInput("No input data provided.") - if len(vectors) < ncols: + if len(vectors) < len(names): raise GMTInvalidInput( - f"Requires {ncols} 1-D arrays but got {len(vectors)}." + f"Requires {len(names)} 1-D arrays but got {len(vectors)}." ) - for i, v in enumerate(vectors[:ncols]): + for i, v in enumerate(vectors[: len(names)]): if v is None: - if i < 3: - msg = f"The '{'xyz'[i]}' column can't be None." - else: - msg = "Column {i} can't be None." - raise GMTInvalidInput(msg) + raise GMTInvalidInput(f"Column {i} ('{names[i]}') can't be None.") else: if vectors is not None and any(v is not None for v in vectors): raise GMTInvalidInput("Too much data. Pass in either 'data' or 1-D arrays.") if kind == "matrix": # check number of columns for matrix-like data + msg = f"data must have at least {len(names)} columns.\n" + " ".join(names) if hasattr(data, "shape"): # np.ndarray or pd.DataFrame - if len(data.shape) == 1 and data.shape[0] < ncols: - raise GMTInvalidInput(f"data must have at least {ncols} columns.") - if len(data.shape) > 1 and data.shape[1] < ncols: - raise GMTInvalidInput(f"data must have at least {ncols} columns.") - if hasattr(data, "data_vars") and len(data.data_vars) < ncols: # xr.Dataset - raise GMTInvalidInput(f"data must have at least {ncols} columns.") + if len(data.shape) == 1 and data.shape[0] < len(names): + raise GMTInvalidInput(msg) + if len(data.shape) > 1 and data.shape[1] < len(names): + raise GMTInvalidInput(msg) + if hasattr(data, "data_vars") and len(data.data_vars) < len( + names + ): # xr.Dataset + raise GMTInvalidInput(msg) def data_kind(data=None, required=True): diff --git a/pygmt/src/blockm.py b/pygmt/src/blockm.py index d8462445ed6..bfe1be138dd 100644 --- a/pygmt/src/blockm.py +++ b/pygmt/src/blockm.py @@ -44,7 +44,7 @@ def _blockm(block_method, data, x, y, z, outfile, **kwargs): with GMTTempFile(suffix=".csv") as tmpfile: with Session() as lib: table_context = lib.virtualfile_from_data( - check_kind="vector", data=data, vectors=[x, y, z], ncols=3 + check_kind="vector", data=data, vectors=[x, y, z], names="xyz" ) # Run blockm* on data table with table_context as infile: diff --git a/pygmt/src/contour.py b/pygmt/src/contour.py index ac34dcb5d95..3e88c45aec5 100644 --- a/pygmt/src/contour.py +++ b/pygmt/src/contour.py @@ -116,7 +116,7 @@ def contour(self, data=None, x=None, y=None, z=None, **kwargs): with Session() as lib: file_context = lib.virtualfile_from_data( - check_kind="vector", data=data, vectors=[x, y, z], ncols=3 + check_kind="vector", data=data, vectors=[x, y, z], names="xyz" ) with file_context as fname: lib.call_module( diff --git a/pygmt/src/nearneighbor.py b/pygmt/src/nearneighbor.py index b53b8ec4a9f..d266cce075c 100644 --- a/pygmt/src/nearneighbor.py +++ b/pygmt/src/nearneighbor.py @@ -150,7 +150,7 @@ def nearneighbor(data=None, x=None, y=None, z=None, **kwargs): with GMTTempFile(suffix=".nc") as tmpfile: with Session() as lib: table_context = lib.virtualfile_from_data( - check_kind="vector", data=data, vectors=[x, y, z], ncols=3 + check_kind="vector", data=data, vectors=[x, y, z], names="xyz" ) with table_context as infile: if (outgrid := kwargs.get("G")) is None: diff --git a/pygmt/src/plot.py b/pygmt/src/plot.py index 3e998122486..5c7bfb20351 100644 --- a/pygmt/src/plot.py +++ b/pygmt/src/plot.py @@ -215,11 +215,11 @@ def plot(self, data=None, x=None, y=None, size=None, direction=None, **kwargs): kind = data_kind(data) vectors = [x, y] - ncols = 2 + names = ["x", "y"] if kwargs.get("S") is not None and kwargs["S"][0] in "vV" and direction is not None: vectors.extend(direction) - ncols += 2 + names.extend(["x2", "y2"]) elif ( kwargs.get("S") is None and kind == "geojson" @@ -242,7 +242,7 @@ def plot(self, data=None, x=None, y=None, size=None, direction=None, **kwargs): "Can't use arrays for fill if data is matrix or file." ) vectors.append(kwargs["G"]) - ncols += 1 + names.append("fill") del kwargs["G"] if size is not None: if kind != "vectors": @@ -250,7 +250,7 @@ def plot(self, data=None, x=None, y=None, size=None, direction=None, **kwargs): "Can't use arrays for 'size' if data is a matrix or file." ) vectors.append(size) - ncols += 1 + names.append("size") for flag in ["I", "t"]: if kwargs.get(flag) is not None and is_nonstr_iter(kwargs[flag]): @@ -259,12 +259,12 @@ def plot(self, data=None, x=None, y=None, size=None, direction=None, **kwargs): f"Can't use arrays for {plot.aliases[flag]} if data is matrix or file." ) vectors.append(kwargs[flag]) - ncols += 1 + names.append(plot.aliases[flag]) kwargs[flag] = "" with Session() as lib: file_context = lib.virtualfile_from_data( - check_kind="vector", data=data, vectors=vectors, ncols=ncols + check_kind="vector", data=data, vectors=vectors, names=names ) with file_context as fname: diff --git a/pygmt/src/plot3d.py b/pygmt/src/plot3d.py index 215b659ac8f..1cbce57bc6e 100644 --- a/pygmt/src/plot3d.py +++ b/pygmt/src/plot3d.py @@ -185,11 +185,11 @@ def plot3d( kind = data_kind(data) vectors = [x, y, z] - ncols = 3 + names = ["x", "y", "z"] if kwargs.get("S") is not None and kwargs["S"][0] in "vV" and direction is not None: vectors.extend(direction) - ncols += 2 + names.extend(["x2", "y2"]) elif ( kwargs.get("S") is None and kind == "geojson" @@ -212,15 +212,15 @@ def plot3d( "Can't use arrays for fill if data is matrix or file." ) vectors.append(kwargs["G"]) - ncols += 1 + names.append("fill") del kwargs["G"] if size is not None: if kind != "vectors": raise GMTInvalidInput( "Can't use arrays for 'size' if data is a matrix or a file." ) - ncols += 1 vectors.append(size) + names.append("size") for flag in ["I", "t"]: if kwargs.get(flag) is not None and is_nonstr_iter(kwargs[flag]): @@ -229,12 +229,12 @@ def plot3d( f"Can't use arrays for {plot3d.aliases[flag]} if data is matrix or file." ) vectors.append(kwargs[flag]) - ncols += 1 + names.append(plot3d.aliases[flag]) kwargs[flag] = "" with Session() as lib: file_context = lib.virtualfile_from_data( - check_kind="vector", data=data, vectors=vectors, ncols=ncols + check_kind="vector", data=data, vectors=vectors, names=names ) with file_context as fname: diff --git a/pygmt/src/project.py b/pygmt/src/project.py index dd7f665fd4b..c5ecd8cb83a 100644 --- a/pygmt/src/project.py +++ b/pygmt/src/project.py @@ -228,7 +228,7 @@ def project(data=None, x=None, y=None, z=None, outfile=None, **kwargs): with Session() as lib: if kwargs.get("G") is None: table_context = lib.virtualfile_from_data( - check_kind="vector", data=data, vectors=[x, y, z], ncols=3 + check_kind="vector", data=data, vectors=[x, y, z], names="xyz" ) # Run project on the temporary (csv) data table diff --git a/pygmt/src/rose.py b/pygmt/src/rose.py index 4a9b39d2cfd..01862152543 100644 --- a/pygmt/src/rose.py +++ b/pygmt/src/rose.py @@ -203,7 +203,10 @@ def rose(self, data=None, length=None, azimuth=None, **kwargs): with Session() as lib: file_context = lib.virtualfile_from_data( - check_kind="vector", data=data, vectors=[length, azimuth], ncols=2 + check_kind="vector", + data=data, + vectors=[length, azimuth], + names=["length", "azimuth"], ) with file_context as fname: diff --git a/pygmt/src/sphdistance.py b/pygmt/src/sphdistance.py index 46f2ee2abc2..a7179496531 100644 --- a/pygmt/src/sphdistance.py +++ b/pygmt/src/sphdistance.py @@ -120,7 +120,7 @@ def sphdistance(data=None, x=None, y=None, **kwargs): with GMTTempFile(suffix=".nc") as tmpfile: with Session() as lib: file_context = lib.virtualfile_from_data( - check_kind="vector", data=data, vectors=[x, y], ncols=2 + check_kind="vector", data=data, vectors=[x, y], names="xy" ) with file_context as infile: if (outgrid := kwargs.get("G")) is None: diff --git a/pygmt/src/surface.py b/pygmt/src/surface.py index 32b099c2af0..62341a52b95 100644 --- a/pygmt/src/surface.py +++ b/pygmt/src/surface.py @@ -165,7 +165,7 @@ def surface(data=None, x=None, y=None, z=None, **kwargs): with GMTTempFile(suffix=".nc") as tmpfile: with Session() as lib: file_context = lib.virtualfile_from_data( - check_kind="vector", data=data, vectors=[x, y, z], ncols=3 + check_kind="vector", data=data, vectors=[x, y, z], names="xyz" ) with file_context as infile: if (outgrid := kwargs.get("G")) is None: diff --git a/pygmt/src/text.py b/pygmt/src/text.py index f4a368aa16f..21e652bb8e4 100644 --- a/pygmt/src/text.py +++ b/pygmt/src/text.py @@ -222,24 +222,24 @@ def text_( kwargs["F"] += f"+c{position}+t{text}" vectors = [x, y] - ncols = 2 + names = ["x", "y"] # If an array of transparency is given, GMT will read it from # the last numerical column per data record. if kwargs.get("t") is not None and is_nonstr_iter(kwargs["t"]): vectors.append(kwargs["t"]) kwargs["t"] = "" - ncols += 1 + names.append("transparency") # Append text at last column. Text must be passed in as str type. if kind == "vectors": vectors.append( np.vectorize(non_ascii_to_octal)(np.atleast_1d(text).astype(str)) ) - ncols += 1 + names.append("text") with Session() as lib: file_context = lib.virtualfile_from_data( - check_kind="vector", data=textfiles, vectors=vectors, ncols=ncols + check_kind="vector", data=textfiles, vectors=vectors, names=names ) with file_context as fname: lib.call_module(module="text", args=build_arg_string(kwargs, infile=fname)) diff --git a/pygmt/src/triangulate.py b/pygmt/src/triangulate.py index 3f47926e90b..da980730638 100644 --- a/pygmt/src/triangulate.py +++ b/pygmt/src/triangulate.py @@ -127,7 +127,7 @@ def _triangulate( """ with Session() as lib: table_context = lib.virtualfile_from_data( - check_kind="vector", data=data, vectors=[x, y, z], ncols=3 + check_kind="vector", data=data, vectors=[x, y, z], names="xyz" ) with table_context as infile: # table output if outgrid is unset, else output to outgrid diff --git a/pygmt/src/wiggle.py b/pygmt/src/wiggle.py index 2b2577a0b2c..74cd5c34223 100644 --- a/pygmt/src/wiggle.py +++ b/pygmt/src/wiggle.py @@ -122,7 +122,7 @@ def wiggle( with Session() as lib: file_context = lib.virtualfile_from_data( - check_kind="vector", data=data, vectors=[x, y, z], ncols=3 + check_kind="vector", data=data, vectors=[x, y, z], names="xyz" ) with file_context as fname: diff --git a/pygmt/src/xyz2grd.py b/pygmt/src/xyz2grd.py index b0cf1f51ff2..e0b0d041353 100644 --- a/pygmt/src/xyz2grd.py +++ b/pygmt/src/xyz2grd.py @@ -154,7 +154,7 @@ def xyz2grd(data=None, x=None, y=None, z=None, **kwargs): with GMTTempFile(suffix=".nc") as tmpfile: with Session() as lib: file_context = lib.virtualfile_from_data( - check_kind="vector", data=data, vectors=[x, y, z], ncols=3 + check_kind="vector", data=data, vectors=[x, y, z], names="xyz" ) with file_context as infile: if (outgrid := kwargs.get("G")) is None: From 3de76667a175493dda96be524cff3bc063515b89 Mon Sep 17 00:00:00 2001 From: Dongdong Tian Date: Sun, 15 Oct 2023 20:28:07 +0800 Subject: [PATCH 04/19] Fix more tests --- pygmt/tests/test_clib.py | 22 +++++----------------- 1 file changed, 5 insertions(+), 17 deletions(-) diff --git a/pygmt/tests/test_clib.py b/pygmt/tests/test_clib.py index 9f2c73857cd..a537cd7306c 100644 --- a/pygmt/tests/test_clib.py +++ b/pygmt/tests/test_clib.py @@ -440,7 +440,7 @@ def test_virtualfile_from_data_required_z_matrix(array_func, kind): data = array_func(dataframe) with clib.Session() as lib: with lib.virtualfile_from_data( - data=data, required_z=True, check_kind="vector" + data=data, names="xyz", check_kind="vector" ) as vfile: with GMTTempFile() as outfile: lib.call_module("info", f"{vfile} ->{outfile.name}") @@ -463,9 +463,7 @@ def test_virtualfile_from_data_required_z_matrix_missing(): data = np.ones((5, 2)) with clib.Session() as lib: with pytest.raises(GMTInvalidInput): - with lib.virtualfile_from_data( - data=data, required_z=True, check_kind="vector" - ): + with lib.virtualfile_from_data(data=data, names="xyz", check_kind="vector"): pass @@ -481,10 +479,7 @@ def test_virtualfile_from_data_fail_non_valid_data(data): continue with clib.Session() as lib: with pytest.raises(GMTInvalidInput): - lib.virtualfile_from_data( - x=variable[0], - y=variable[1], - ) + lib.virtualfile_from_data(vectors=variable[:2]) # Test all combinations where at least one data variable # is not given in the x, y, z case: @@ -494,19 +489,12 @@ def test_virtualfile_from_data_fail_non_valid_data(data): continue with clib.Session() as lib: with pytest.raises(GMTInvalidInput): - lib.virtualfile_from_data( - x=variable[0], y=variable[1], z=variable[2], required_z=True - ) + lib.virtualfile_from_data(vectors=variable[:3], names="xyz") # Should also fail if given too much data with clib.Session() as lib: with pytest.raises(GMTInvalidInput): - lib.virtualfile_from_data( - x=data[:, 0], - y=data[:, 1], - z=data[:, 2], - data=data, - ) + lib.virtualfile_from_data(vectors=data[:, :3], data=data, names="xyz") def test_virtualfile_from_vectors(dtypes): From 93b91d041a9a925e1dccc233e0f4e727dcd88045 Mon Sep 17 00:00:00 2001 From: Dongdong Tian Date: Sun, 15 Oct 2023 23:43:30 +0800 Subject: [PATCH 05/19] Fix project --- pygmt/src/project.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/pygmt/src/project.py b/pygmt/src/project.py index c5ecd8cb83a..7eaf846accc 100644 --- a/pygmt/src/project.py +++ b/pygmt/src/project.py @@ -227,8 +227,9 @@ def project(data=None, x=None, y=None, z=None, outfile=None, **kwargs): outfile = tmpfile.name with Session() as lib: if kwargs.get("G") is None: + # passed three vectors but only x/y are required table_context = lib.virtualfile_from_data( - check_kind="vector", data=data, vectors=[x, y, z], names="xyz" + check_kind="vector", data=data, vectors=[x, y, z], names="xy" ) # Run project on the temporary (csv) data table From 1d6e568227531fd1224196ae0e2d70cf94c33a8f Mon Sep 17 00:00:00 2001 From: Dongdong Tian Date: Mon, 16 Oct 2023 13:26:05 +0800 Subject: [PATCH 06/19] Fix more tests --- pygmt/helpers/utils.py | 6 +++--- pygmt/tests/test_helpers.py | 21 --------------------- 2 files changed, 3 insertions(+), 24 deletions(-) diff --git a/pygmt/helpers/utils.py b/pygmt/helpers/utils.py index 6926f1a1911..f45d1d51375 100644 --- a/pygmt/helpers/utils.py +++ b/pygmt/helpers/utils.py @@ -53,15 +53,15 @@ def validate_data_input( >>> validate_data_input(vectors=[[1, 2, 3], None], names="xy") Traceback (most recent call last): ... - pygmt.exceptions.GMTInvalidInput: The 'y' column can't be None. + pygmt.exceptions.GMTInvalidInput: Column 1 ('y') can't be None. >>> validate_data_input(vectors=[None, [4, 5, 6]], names="xy") Traceback (most recent call last): ... - pygmt.exceptions.GMTInvalidInput: The 'x' column can't be None. + pygmt.exceptions.GMTInvalidInput: Column 0 ('x') can't be None. >>> validate_data_input(vectors=[[1, 2, 3], [4, 5, 6], None], names="xyz") Traceback (most recent call last): ... - pygmt.exceptions.GMTInvalidInput: The 'z' column can't be None. + pygmt.exceptions.GMTInvalidInput: Column 2 ('z') can't be None. >>> import numpy as np >>> import pandas as pd >>> import xarray as xr diff --git a/pygmt/tests/test_helpers.py b/pygmt/tests/test_helpers.py index b726897e12e..02871d62c84 100644 --- a/pygmt/tests/test_helpers.py +++ b/pygmt/tests/test_helpers.py @@ -3,7 +3,6 @@ """ import os -import numpy as np import pytest import xarray as xr from pygmt import Figure @@ -11,7 +10,6 @@ from pygmt.helpers import ( GMTTempFile, args_in_kwargs, - data_kind, kwargs_to_strings, unique_name, ) @@ -31,25 +29,6 @@ def test_load_static_earth_relief(): assert isinstance(data, xr.DataArray) -@pytest.mark.parametrize( - "data,x,y", - [ - (None, None, None), - ("data.txt", np.array([1, 2]), np.array([4, 5])), - ("data.txt", np.array([1, 2]), None), - ("data.txt", None, np.array([4, 5])), - (None, np.array([1, 2]), None), - (None, None, np.array([4, 5])), - ], -) -def test_data_kind_fails(data, x, y): - """ - Make sure data_kind raises exceptions when it should. - """ - with pytest.raises(GMTInvalidInput): - data_kind(data=data, x=x, y=y) - - def test_unique_name(): """ Make sure the names are really unique. From 6f9fc195107d8981ae20c368ae97c55f254557e2 Mon Sep 17 00:00:00 2001 From: Dongdong Tian Date: Mon, 16 Oct 2023 15:02:44 +0800 Subject: [PATCH 07/19] Fixes --- pygmt/src/project.py | 8 ++++++-- pygmt/src/triangulate.py | 6 +++++- 2 files changed, 11 insertions(+), 3 deletions(-) diff --git a/pygmt/src/project.py b/pygmt/src/project.py index 7eaf846accc..b85c783004e 100644 --- a/pygmt/src/project.py +++ b/pygmt/src/project.py @@ -222,14 +222,18 @@ def project(data=None, x=None, y=None, z=None, outfile=None, **kwargs): "The `convention` parameter is not allowed with `generate`." ) + # z is optional + vectors, names = [x, y], "xy" + if z is not None: + vectors.append(z) + with GMTTempFile(suffix=".csv") as tmpfile: if outfile is None: # Output to tmpfile if outfile is not set outfile = tmpfile.name with Session() as lib: if kwargs.get("G") is None: - # passed three vectors but only x/y are required table_context = lib.virtualfile_from_data( - check_kind="vector", data=data, vectors=[x, y, z], names="xy" + check_kind="vector", data=data, vectors=vectors, names=names ) # Run project on the temporary (csv) data table diff --git a/pygmt/src/triangulate.py b/pygmt/src/triangulate.py index da980730638..759b567a4c4 100644 --- a/pygmt/src/triangulate.py +++ b/pygmt/src/triangulate.py @@ -125,9 +125,13 @@ def _triangulate( - None if ``output_type`` is "file" (output is stored in ``outgrid`` or ``outfile``) """ + vectors, names = [x, y], "xy" + if z is not None: + vectors.append(z) + with Session() as lib: table_context = lib.virtualfile_from_data( - check_kind="vector", data=data, vectors=[x, y, z], names="xyz" + check_kind="vector", data=data, vectors=[x, y, z], names="xy" ) with table_context as infile: # table output if outgrid is unset, else output to outgrid From 0db21bca0293140db92428788442830e7647d6c2 Mon Sep 17 00:00:00 2001 From: Dongdong Tian Date: Tue, 17 Oct 2023 17:05:08 +0800 Subject: [PATCH 08/19] Fix triangulate --- 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 edf8b010ac0..f89904e1b3f 100644 --- a/pygmt/src/triangulate.py +++ b/pygmt/src/triangulate.py @@ -131,7 +131,7 @@ def _triangulate( with Session() as lib: table_context = lib.virtualfile_from_data( - check_kind="vector", data=data, vectors=[x, y, z], names="xy" + check_kind="vector", data=data, vectors=vectors, names=names ) with table_context as infile: # table output if outgrid is unset, else output to outgrid From 7cf52903a362fa0ac9c87e3115df1c47ba92acd0 Mon Sep 17 00:00:00 2001 From: Dongdong Tian Date: Tue, 17 Oct 2023 17:06:58 +0800 Subject: [PATCH 09/19] Fix text --- pygmt/src/text.py | 17 +++++++++++------ 1 file changed, 11 insertions(+), 6 deletions(-) diff --git a/pygmt/src/text.py b/pygmt/src/text.py index f23b166ddd9..91b60b7187a 100644 --- a/pygmt/src/text.py +++ b/pygmt/src/text.py @@ -198,24 +198,29 @@ def text_( ): kwargs.update({"F": ""}) - extra_arrays = [] - for arg, flag in [(angle, "+a"), (font, "+f"), (justify, "+j")]: + vectors = [x, y] + names = ["x", "y"] + for arg, flag, name in [ + (angle, "+a", "angle"), + (font, "+f", "font"), + (justify, "+j", "justify"), + ]: if arg is True: kwargs["F"] += flag elif is_nonstr_iter(arg): kwargs["F"] += flag if flag == "+a": # angle is numeric type - extra_arrays.append(np.atleast_1d(arg)) + vectors.append(np.atleast_1d(arg)) + names.append(name) else: # font or justify is str type - extra_arrays.append(np.atleast_1d(arg).astype(str)) + vectors.append(np.atleast_1d(arg).astype(str)) + names.append(name) elif isinstance(arg, (int, float, str)): kwargs["F"] += f"{flag}{arg}" if isinstance(position, str): kwargs["F"] += f"+c{position}+t{text}" - vectors = [x, y] - names = ["x", "y"] # If an array of transparency is given, GMT will read it from # the last numerical column per data record. if kwargs.get("t") is not None and is_nonstr_iter(kwargs["t"]): From b0b6d2a3efd5c80f9bae0e6d59dc21b72ce4fc4a Mon Sep 17 00:00:00 2001 From: Dongdong Tian Date: Tue, 17 Oct 2023 17:18:23 +0800 Subject: [PATCH 10/19] Fix more failing tests --- pygmt/helpers/utils.py | 3 +++ 1 file changed, 3 insertions(+) diff --git a/pygmt/helpers/utils.py b/pygmt/helpers/utils.py index f45d1d51375..aa3796800f8 100644 --- a/pygmt/helpers/utils.py +++ b/pygmt/helpers/utils.py @@ -70,6 +70,7 @@ def validate_data_input( Traceback (most recent call last): ... pygmt.exceptions.GMTInvalidInput: data must have at least 3 columns. + x y z >>> validate_data_input( ... data=pd.DataFrame(data, columns=["x", "y"]), ... names="xyz", @@ -78,6 +79,7 @@ def validate_data_input( Traceback (most recent call last): ... pygmt.exceptions.GMTInvalidInput: data must have at least 3 columns. + x y z >>> validate_data_input( ... data=xr.Dataset(pd.DataFrame(data, columns=["x", "y"])), ... names="xyz", @@ -86,6 +88,7 @@ def validate_data_input( Traceback (most recent call last): ... pygmt.exceptions.GMTInvalidInput: data must have at least 3 columns. + x y z >>> validate_data_input(data="infile", vectors=[[1, 2, 3], None]) Traceback (most recent call last): ... From fa875efff861d512501b81f4f9c3e9c7a9e712f4 Mon Sep 17 00:00:00 2001 From: Dongdong Tian Date: Tue, 17 Oct 2023 17:33:47 +0800 Subject: [PATCH 11/19] More fixes --- pygmt/helpers/utils.py | 6 +++--- pygmt/src/text.py | 2 ++ 2 files changed, 5 insertions(+), 3 deletions(-) diff --git a/pygmt/helpers/utils.py b/pygmt/helpers/utils.py index aa3796800f8..ddc4c66ca40 100644 --- a/pygmt/helpers/utils.py +++ b/pygmt/helpers/utils.py @@ -92,15 +92,15 @@ def validate_data_input( >>> validate_data_input(data="infile", vectors=[[1, 2, 3], None]) Traceback (most recent call last): ... - pygmt.exceptions.GMTInvalidInput: Too much data. Pass in either 'data' or 1-D arrays. # noqa: W505 + pygmt.exceptions.GMTInvalidInput: Too much data. Pass in either 'data' or 1-D arrays. >>> validate_data_input(data="infile", vectors=[None, [4, 5, 6]]) Traceback (most recent call last): ... - pygmt.exceptions.GMTInvalidInput: Too much data. Pass in either 'data' or 1-D arrays. # noqa: W505 + pygmt.exceptions.GMTInvalidInput: Too much data. Pass in either 'data' or 1-D arrays. >>> validate_data_input(data="infile", vectors=[None, None, [7, 8, 9]]) Traceback (most recent call last): ... - pygmt.exceptions.GMTInvalidInput: Too much data. Pass in either 'data' or 1-D arrays. # noqa: W505 + pygmt.exceptions.GMTInvalidInput: Too much data. Pass in either 'data' or 1-D arrays. Raises ------ diff --git a/pygmt/src/text.py b/pygmt/src/text.py index 91b60b7187a..87ecce5eb66 100644 --- a/pygmt/src/text.py +++ b/pygmt/src/text.py @@ -179,6 +179,8 @@ def text_( kind = data_kind(textfiles) if kind == "vectors" and text is None: raise GMTInvalidInput("Must provide text with x/y pairs") + if kind != "vectors" and text is not None: + raise GMTInvalidInput("Must provide text with x/y pairs") else: if x is not None or y is not None or textfiles is not None: raise GMTInvalidInput( From 2ee0df27d06297b7313cb13e15ccb5a2b4ede505 Mon Sep 17 00:00:00 2001 From: Dongdong Tian Date: Tue, 17 Oct 2023 18:12:06 +0800 Subject: [PATCH 12/19] Fix linting issues --- pygmt/helpers/utils.py | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/pygmt/helpers/utils.py b/pygmt/helpers/utils.py index ddc4c66ca40..8f8099184f8 100644 --- a/pygmt/helpers/utils.py +++ b/pygmt/helpers/utils.py @@ -92,15 +92,15 @@ def validate_data_input( >>> validate_data_input(data="infile", vectors=[[1, 2, 3], None]) Traceback (most recent call last): ... - pygmt.exceptions.GMTInvalidInput: Too much data. Pass in either 'data' or 1-D arrays. + ...GMTInvalidInput: Too much data. Pass in either 'data' or 1-D arrays. >>> validate_data_input(data="infile", vectors=[None, [4, 5, 6]]) Traceback (most recent call last): ... - pygmt.exceptions.GMTInvalidInput: Too much data. Pass in either 'data' or 1-D arrays. + ...GMTInvalidInput: Too much data. Pass in either 'data' or 1-D arrays. >>> validate_data_input(data="infile", vectors=[None, None, [7, 8, 9]]) Traceback (most recent call last): ... - pygmt.exceptions.GMTInvalidInput: Too much data. Pass in either 'data' or 1-D arrays. + ...GMTInvalidInput: Too much data. Pass in either 'data' or 1-D arrays. Raises ------ From d5c83408bdb59ae899566cf803bdf1535c2edaad Mon Sep 17 00:00:00 2001 From: Dongdong Tian Date: Tue, 17 Oct 2023 19:12:43 +0800 Subject: [PATCH 13/19] Fix linting issues --- pygmt/helpers/utils.py | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/pygmt/helpers/utils.py b/pygmt/helpers/utils.py index 8f8099184f8..9a243b7fb28 100644 --- a/pygmt/helpers/utils.py +++ b/pygmt/helpers/utils.py @@ -92,15 +92,15 @@ def validate_data_input( >>> validate_data_input(data="infile", vectors=[[1, 2, 3], None]) Traceback (most recent call last): ... - ...GMTInvalidInput: Too much data. Pass in either 'data' or 1-D arrays. + pygmt...GMTInvalidInput: Too much data. Use either 'data' or 1-D arrays. >>> validate_data_input(data="infile", vectors=[None, [4, 5, 6]]) Traceback (most recent call last): ... - ...GMTInvalidInput: Too much data. Pass in either 'data' or 1-D arrays. + pygmt...GMTInvalidInput: Too much data. Use either 'data' or 1-D arrays. >>> validate_data_input(data="infile", vectors=[None, None, [7, 8, 9]]) Traceback (most recent call last): ... - ...GMTInvalidInput: Too much data. Pass in either 'data' or 1-D arrays. + pygmt...GMTInvalidInput: Too much data. Use either 'data' or 1-D arrays. Raises ------ @@ -122,7 +122,7 @@ def validate_data_input( raise GMTInvalidInput(f"Column {i} ('{names[i]}') can't be None.") else: if vectors is not None and any(v is not None for v in vectors): - raise GMTInvalidInput("Too much data. Pass in either 'data' or 1-D arrays.") + raise GMTInvalidInput("Too much data. Use either 'data' or 1-D arrays.") if kind == "matrix": # check number of columns for matrix-like data msg = f"data must have at least {len(names)} columns.\n" + " ".join(names) if hasattr(data, "shape"): # np.ndarray or pd.DataFrame From 30bacb13bd10e8c13e2faf617826e1b20861b1d6 Mon Sep 17 00:00:00 2001 From: Dongdong Tian Date: Wed, 18 Oct 2023 10:10:56 +0800 Subject: [PATCH 14/19] Fix linting issues --- pygmt/clib/session.py | 2 +- pygmt/helpers/utils.py | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/pygmt/clib/session.py b/pygmt/clib/session.py index 82321f83b6a..052ae04ca0d 100644 --- a/pygmt/clib/session.py +++ b/pygmt/clib/session.py @@ -1474,7 +1474,7 @@ def virtualfile_from_data( check_kind=None, data=None, vectors=None, - names=["x", "y"], + names="xy", required_data=True, ): """ diff --git a/pygmt/helpers/utils.py b/pygmt/helpers/utils.py index 9a243b7fb28..0c25aaf251a 100644 --- a/pygmt/helpers/utils.py +++ b/pygmt/helpers/utils.py @@ -16,7 +16,7 @@ def validate_data_input( - data=None, vectors=None, names=["x", "y"], required_data=True, kind=None + data=None, vectors=None, names="xy", required_data=True, kind=None ): """ Check if the data input is valid. From 593f252178b9a5958dc8d82b90da78f6f59a509f Mon Sep 17 00:00:00 2001 From: Dongdong Tian Date: Fri, 20 Oct 2023 15:59:42 +0800 Subject: [PATCH 15/19] Update pygmt/clib/session.py --- pygmt/clib/session.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/pygmt/clib/session.py b/pygmt/clib/session.py index 052ae04ca0d..25260fb9581 100644 --- a/pygmt/clib/session.py +++ b/pygmt/clib/session.py @@ -1496,9 +1496,9 @@ def virtualfile_from_data( vectors : list of 1-D arrays or None A list of 1-D arrays. Each array will be a column in the table. All of these arrays must be of the same size. - names : list of str + names : str or list of str A list of names for each of the columns. Must be of the same size - as the number of vectors. Default is ``["x", "y"]``. + as the number of vectors. Default is ``"xy"``. required_data : bool Set to True when 'data' is required, or False when dealing with optional virtual files. [Default is True]. From 409337f7572fb3ed865c35596ae59ae41608d07d Mon Sep 17 00:00:00 2001 From: Dongdong Tian Date: Wed, 25 Oct 2023 08:52:28 +0800 Subject: [PATCH 16/19] Apply suggestions from code review Co-authored-by: Michael Grund <23025878+michaelgrund@users.noreply.github.com> --- pygmt/helpers/utils.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/pygmt/helpers/utils.py b/pygmt/helpers/utils.py index ff7ecb589b0..413993c6b1b 100644 --- a/pygmt/helpers/utils.py +++ b/pygmt/helpers/utils.py @@ -33,7 +33,7 @@ def validate_data_input( List of column names. required_data : bool Set to True when 'data' is required, or False when dealing with - optional virtual files. [Default is True]. + optional virtual files [Default is True]. kind : str or None The kind of data that will be passed to a module. If not given, it will be determined by calling :func:`data_kind`. @@ -145,7 +145,7 @@ def data_kind(data=None, required=True): Possible data kinds: - - ``'file'``: a file name or a pathlib.PurePath object providfed as 'data' + - ``'file'``: a file name or a pathlib.PurePath object provided as 'data' - ``'arg'``: an optional argument (None, bool, int or float) provided as 'data' - ``'grid'``: an xarray.DataArray with 2 dimensions provided as 'data' From 5c10fc47482687158aac5292c7f6adf6aab984b7 Mon Sep 17 00:00:00 2001 From: Dongdong Tian Date: Thu, 11 Jul 2024 18:05:37 +0800 Subject: [PATCH 17/19] Fix plot and plot3d --- pygmt/src/plot.py | 81 +++++++++++++++++++-------------------------- pygmt/src/plot3d.py | 81 +++++++++++++++++++-------------------------- 2 files changed, 68 insertions(+), 94 deletions(-) diff --git a/pygmt/src/plot.py b/pygmt/src/plot.py index ecf817d2b74..4d62db7ca4b 100644 --- a/pygmt/src/plot.py +++ b/pygmt/src/plot.py @@ -2,8 +2,6 @@ plot - Plot in two dimensions. """ -from pathlib import Path - from pygmt.clib import Session from pygmt.exceptions import GMTInvalidInput from pygmt.helpers import ( @@ -14,7 +12,6 @@ kwargs_to_strings, use_alias, ) -from pygmt.src.which import which @fmt_docstring @@ -210,50 +207,40 @@ def plot(self, data=None, x=None, y=None, size=None, direction=None, **kwargs): vectors = [x, y] names = ["x", "y"] - if kwargs.get("S") is not None and kwargs["S"][0] in "vV" and direction is not None: - vectors.extend(direction) - names.extend(["x2", "y2"]) - elif ( - kwargs.get("S") is None - and kind == "geojson" - and data.geom_type.isin(["Point", "MultiPoint"]).all() - ): # checking if the geometry of a geoDataFrame is Point or MultiPoint - kwargs["S"] = "s0.2c" - elif kwargs.get("S") is None and kind == "file" and str(data).endswith(".gmt"): - # checking that the data is a file path to set default style - try: - with Path.open(which(data), encoding="utf8") as file: - line = file.readline() - if "@GMULTIPOINT" in line or "@GPOINT" in line: - # if the file is gmt style and geometry is set to Point - kwargs["S"] = "s0.2c" - except FileNotFoundError: - pass - if is_nonstr_iter(kwargs.get("G")): - if kind != "vectors": - raise GMTInvalidInput( - "Can't use arrays for fill if data is matrix or file." - ) - vectors.append(kwargs["G"]) - names.append("fill") - del kwargs["G"] - if size is not None: - if kind != "vectors": - raise GMTInvalidInput( - "Can't use arrays for 'size' if data is a matrix or file." - ) - vectors.append(size) - names.append("size") - - for flag in ["I", "t"]: - if is_nonstr_iter(kwargs.get(flag)): - if kind != "vectors": - raise GMTInvalidInput( - f"Can't use arrays for {plot.aliases[flag]} if data is matrix or file." - ) - vectors.append(kwargs[flag]) - names.append(plot.aliases[flag]) - kwargs[flag] = "" + if kind == "vectors": # Add more columns for vectors input + # Parameters for vector styles + if ( + kwargs.get("S") is not None + and kwargs["S"][0] in "vV" + and is_nonstr_iter(direction) + ): + vectors.extend(direction) + names.extend(["x2", "y2"]) + # Fill + if is_nonstr_iter(kwargs.get("G")): + vectors.append(kwargs["G"]) + names.append("fill") + del kwargs["G"] + # Size + if is_nonstr_iter(size): + vectors.append(size) + names.append("size") + # Intensity and transparency + for flag in ["I", "t"]: + if is_nonstr_iter(kwargs.get(flag)): + vectors.append(kwargs[flag]) + names.append(plot.aliases[flag]) + kwargs[flag] = "" + else: + for name, value in [ + ("direction", direction), + ("fill", kwargs.get("G")), + ("size", size), + ("intensity", kwargs.get("I")), + ("transparency", kwargs.get("t")), + ]: + if is_nonstr_iter(value): + raise GMTInvalidInput(f"'{name}' can't be 1-D array if 'data' is used.") with Session() as lib: file_context = lib.virtualfile_in( diff --git a/pygmt/src/plot3d.py b/pygmt/src/plot3d.py index f85f8defccd..9b2e8da3610 100644 --- a/pygmt/src/plot3d.py +++ b/pygmt/src/plot3d.py @@ -2,8 +2,6 @@ plot3d - Plot in three dimensions. """ -from pathlib import Path - from pygmt.clib import Session from pygmt.exceptions import GMTInvalidInput from pygmt.helpers import ( @@ -14,7 +12,6 @@ kwargs_to_strings, use_alias, ) -from pygmt.src.which import which @fmt_docstring @@ -187,50 +184,40 @@ def plot3d( vectors = [x, y, z] names = ["x", "y", "z"] - if kwargs.get("S") is not None and kwargs["S"][0] in "vV" and direction is not None: - vectors.extend(direction) - names.extend(["x2", "y2"]) - elif ( - kwargs.get("S") is None - and kind == "geojson" - and data.geom_type.isin(["Point", "MultiPoint"]).all() - ): # checking if the geometry of a geoDataFrame is Point or MultiPoint - kwargs["S"] = "u0.2c" - elif kwargs.get("S") is None and kind == "file" and str(data).endswith(".gmt"): - # checking that the data is a file path to set default style - try: - with Path.open(which(data), encoding="utf8") as file: - line = file.readline() - if "@GMULTIPOINT" in line or "@GPOINT" in line: - # if the file is gmt style and geometry is set to Point - kwargs["S"] = "u0.2c" - except FileNotFoundError: - pass - if is_nonstr_iter(kwargs.get("G")): - if kind != "vectors": - raise GMTInvalidInput( - "Can't use arrays for fill if data is matrix or file." - ) - vectors.append(kwargs["G"]) - names.append("fill") - del kwargs["G"] - if size is not None: - if kind != "vectors": - raise GMTInvalidInput( - "Can't use arrays for 'size' if data is a matrix or a file." - ) - vectors.append(size) - names.append("size") - - for flag in ["I", "t"]: - if is_nonstr_iter(kwargs.get(flag)): - if kind != "vectors": - raise GMTInvalidInput( - f"Can't use arrays for {plot3d.aliases[flag]} if data is matrix or file." - ) - vectors.append(kwargs[flag]) - names.append(plot3d.aliases[flag]) - kwargs[flag] = "" + if kind == "vectors": # Add more columns for vectors input + # Parameters for vector styles + if ( + kwargs.get("S") is not None + and kwargs["S"][0] in "vV" + and is_nonstr_iter(direction) + ): + vectors.extend(direction) + names.extend(["x2", "y2", "z2"]) + # Fill + if is_nonstr_iter(kwargs.get("G")): + vectors.append(kwargs["G"]) + names.append("fill") + del kwargs["G"] + # Size + if is_nonstr_iter(size): + vectors.append(size) + names.append("size") + # Intensity and transparency + for flag in ["I", "t"]: + if is_nonstr_iter(kwargs.get(flag)): + vectors.append(kwargs[flag]) + names.append(plot3d.aliases[flag]) + kwargs[flag] = "" + else: + for name, value in [ + ("direction", direction), + ("fill", kwargs.get("G")), + ("size", size), + ("intensity", kwargs.get("I")), + ("transparency", kwargs.get("t")), + ]: + if is_nonstr_iter(value): + raise GMTInvalidInput(f"'{name}' can't be 1-D array if 'data' is used.") with Session() as lib: file_context = lib.virtualfile_in( From 525a35339d37752efe3300c6ac9a28d3568bcecc Mon Sep 17 00:00:00 2001 From: Dongdong Tian Date: Thu, 11 Jul 2024 18:09:00 +0800 Subject: [PATCH 18/19] Fix errors in merging the main branch --- pygmt/src/plot.py | 19 +++++++++++++++++-- pygmt/src/plot3d.py | 16 ++++++++++++++++ 2 files changed, 33 insertions(+), 2 deletions(-) diff --git a/pygmt/src/plot.py b/pygmt/src/plot.py index 4d62db7ca4b..bc25f726d56 100644 --- a/pygmt/src/plot.py +++ b/pygmt/src/plot.py @@ -2,16 +2,19 @@ plot - Plot in two dimensions. """ +from pathlib import Path + from pygmt.clib import Session from pygmt.exceptions import GMTInvalidInput from pygmt.helpers import ( - build_arg_string, + build_arg_list, data_kind, fmt_docstring, is_nonstr_iter, kwargs_to_strings, use_alias, ) +from pygmt.src import which @fmt_docstring @@ -241,6 +244,18 @@ def plot(self, data=None, x=None, y=None, size=None, direction=None, **kwargs): ]: if is_nonstr_iter(value): raise GMTInvalidInput(f"'{name}' can't be 1-D array if 'data' is used.") + # Set the default style if data has a geometry of Point or MultiPoint + if kwargs.get("S") is None: + if kind == "geojson" and data.geom_type.isin(["Point", "MultiPoint"]).all(): + kwargs["S"] = "s0.2c" + elif kind == "file" and str(data).endswith(".gmt"): # OGR_GMT file + try: + with Path(which(data)).open(encoding="utf-8") as file: + line = file.readline() + if "@GMULTIPOINT" in line or "@GPOINT" in line: + kwargs["S"] = "s0.2c" + except FileNotFoundError: + pass with Session() as lib: file_context = lib.virtualfile_in( @@ -248,4 +263,4 @@ def plot(self, data=None, x=None, y=None, size=None, direction=None, **kwargs): ) with file_context as fname: - lib.call_module(module="plot", args=build_arg_string(kwargs, infile=fname)) + lib.call_module(module="plot", args=build_arg_list(kwargs, infile=fname)) diff --git a/pygmt/src/plot3d.py b/pygmt/src/plot3d.py index 9b2e8da3610..d7fd54ba140 100644 --- a/pygmt/src/plot3d.py +++ b/pygmt/src/plot3d.py @@ -2,6 +2,8 @@ plot3d - Plot in three dimensions. """ +from pathlib import Path + from pygmt.clib import Session from pygmt.exceptions import GMTInvalidInput from pygmt.helpers import ( @@ -12,6 +14,7 @@ kwargs_to_strings, use_alias, ) +from pygmt.src import which @fmt_docstring @@ -219,6 +222,19 @@ def plot3d( if is_nonstr_iter(value): raise GMTInvalidInput(f"'{name}' can't be 1-D array if 'data' is used.") + # Set the default style if data has a geometry of Point or MultiPoint + if kwargs.get("S") is None: + if kind == "geojson" and data.geom_type.isin(["Point", "MultiPoint"]).all(): + kwargs["S"] = "u0.2c" + elif kind == "file" and str(data).endswith(".gmt"): # OGR_GMT file + try: + with Path(which(data)).open(encoding="utf-8") as file: + line = file.readline() + if "@GMULTIPOINT" in line or "@GPOINT" in line: + kwargs["S"] = "u0.2c" + except FileNotFoundError: + pass + with Session() as lib: file_context = lib.virtualfile_in( check_kind="vector", data=data, vectors=vectors, names=names From b55a9ad384e258ce0a00715081d26e255fa31fb6 Mon Sep 17 00:00:00 2001 From: Dongdong Tian Date: Sat, 20 Jul 2024 14:09:11 +0800 Subject: [PATCH 19/19] Fix merging issue --- pygmt/clib/session.py | 4 ++-- pygmt/helpers/__init__.py | 1 - 2 files changed, 2 insertions(+), 3 deletions(-) diff --git a/pygmt/clib/session.py b/pygmt/clib/session.py index 24ea1583f8f..dcab42d43b7 100644 --- a/pygmt/clib/session.py +++ b/pygmt/clib/session.py @@ -34,10 +34,10 @@ GMTVersionError, ) from pygmt.helpers import ( + _validate_data_input, data_kind, tempfile_from_geojson, tempfile_from_image, - validate_data_input, ) FAMILIES = [ @@ -1682,7 +1682,7 @@ def virtualfile_in( : N = 3 <7/9> <4/6> <1/3> """ kind = data_kind(data, required=required_data) - validate_data_input( + _validate_data_input( data=data, vectors=vectors, names=names, diff --git a/pygmt/helpers/__init__.py b/pygmt/helpers/__init__.py index 337ac1df530..862abbbdd64 100644 --- a/pygmt/helpers/__init__.py +++ b/pygmt/helpers/__init__.py @@ -23,6 +23,5 @@ is_nonstr_iter, launch_external_viewer, non_ascii_to_octal, - validate_data_input, ) from pygmt.helpers.validators import validate_output_table_type