Source code for pygmt.src.grd2xyz

"""
grd2xyz - Convert grid to data table
"""
import warnings

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,
    fmt_docstring,
    kwargs_to_strings,
    use_alias,
)

__doctest_skip__ = ["grd2xyz"]


[docs]@fmt_docstring @use_alias( C="cstyle", R="region", V="verbose", W="weight", Z="convention", b="binary", d="nodata", f="coltypes", h="header", o="outcols", s="skiprows", ) @kwargs_to_strings(R="sequence", o="sequence_comma") def grd2xyz(grid, output_type="pandas", outfile=None, **kwargs): r""" Convert grid to data table. Read a grid and output xyz-triplets as a :class:`numpy.ndarray`, :class:`pandas.DataFrame`, or ASCII file. Full option list at :gmt-docs:`grd2xyz.html` {aliases} Parameters ---------- grid : str or xarray.DataArray The file name of the input grid or the grid loaded as a :class:`xarray.DataArray`. This is the only required parameter. 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``) outfile : str The file name for the output ASCII file. cstyle : str [**f**\|\ **i**]. Replace the x- and y-coordinates on output with the corresponding column and row numbers. These start at 0 (C-style counting); append **f** to start at 1 (Fortran-style counting). Alternatively, append **i** to write just the two columns *index* and *z*, where *index* is the 1-D indexing that GMT uses when referring to grid nodes. {R} Adding ``region`` will select a subsection of the grid. If this subsection exceeds the boundaries of the grid, only the common region will be output. weight : str [**a**\ [**+u**\ *unit*]\|\ *weight*]. Write out *x,y,z,w*\ , where *w* is the supplied *weight* (or 1 if not supplied) [Default writes *x,y,z* only]. Choose **a** to compute weights equal to the area each node represents. For Cartesian grids this is simply the product of the *x* and *y* increments (except for gridline-registered grids at all sides [half] and corners [quarter]). For geographic grids we default to a length unit of **k**. Change this by appending **+u**\ *unit*. For such grids, the area varies with latitude and also sees special cases for gridline-registered layouts at sides, corners, and poles. {V} convention : str [*flags*]. Write a 1-column ASCII [or binary] table. Output will be organized according to the specified ordering convention contained in *flags*. If data should be written by rows, make *flags* start with **T** (op) if first row is y = ymax or **B** (ottom) if first row is y = ymin. Then, append **L** or **R** to indicate that first element should start at left or right end of row. Likewise for column formats: start with **L** or **R** to position first column, and then append **T** or **B** to position first element in a row. For gridline registered grids: If grid is periodic in x but the written data should not contain the (redundant) column at x = xmax, append **x**. For grid periodic in y, skip writing the redundant row at y = ymax by appending **y**. If the byte-order needs to be swapped, append **w**. Select one of several data types (all binary except **a**): * **a** ASCII representation of a single item per record * **c** int8_t, signed 1-byte character * **u** uint8_t, unsigned 1-byte character * **h** int16_t, short 2-byte integer * **H** uint16_t, unsigned short 2-byte integer * **i** int32_t, 4-byte integer * **I** uint32_t, unsigned 4-byte integer * **l** int64_t, long (8-byte) integer * **L** uint64_t, unsigned long (8-byte) integer * **f** 4-byte floating point single precision * **d** 8-byte floating point double precision Default format is scanline orientation of ASCII numbers: **TLa**. {b} {d} {f} {h} {o} {s} Returns ------- ret : pandas.DataFrame or numpy.ndarray or None Return type depends on ``outfile`` and ``output_type``: - 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``) Example ------- >>> import pygmt >>> # Load a grid of @earth_relief_30m data, with an x-range of 10 to 30, >>> # and a y-range of 15 to 25 >>> grid = pygmt.datasets.load_earth_relief( ... resolution="30m", region=[10, 30, 15, 25] ... ) >>> # Create a pandas DataFrame with the xyz data from an input grid >>> xyz_dataframe = pygmt.grd2xyz(grid=grid, output_type="pandas") >>> xyz_dataframe.head(n=2) lon lat elevation 0 10.25 24.75 903.5 1 10.75 24.75 820.0 """ if output_type not in ["numpy", "pandas", "file"]: raise GMTInvalidInput( "Must specify 'output_type' either as 'numpy', 'pandas' or 'file'." ) if outfile is not None and output_type != "file": msg = ( f"Changing 'output_type' of grd2xyz 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" elif outfile is None and output_type == "file": raise GMTInvalidInput("Must specify 'outfile' for ASCII output.") if kwargs.get("o") is not None and output_type == "pandas": raise GMTInvalidInput( "If 'outcols' is specified, 'output_type' must be either 'numpy'" "or 'file'." ) # Set the default column names for the pandas dataframe header dataframe_header = ["x", "y", "z"] # Let output pandas column names match input DataArray dimension names if isinstance(grid, xr.DataArray) and output_type == "pandas": # Reverse the dims because it is rows, columns ordered. dataframe_header = [grid.dims[1], grid.dims[0], grid.name] with GMTTempFile() as tmpfile: with Session() as lib: file_context = lib.virtualfile_from_data(check_kind="raster", data=grid) with file_context as infile: if outfile is None: outfile = tmpfile.name lib.call_module( "grd2xyz", build_arg_string(kwargs, infile=infile, outfile=outfile) ) # Read temporary csv output to a pandas table if outfile == tmpfile.name: # if user did not set outfile, return pd.DataFrame result = pd.read_csv( tmpfile.name, sep="\t", names=dataframe_header, comment=">" ) elif outfile != tmpfile.name: # return None if outfile set, output in outfile result = None if output_type == "numpy": result = result.to_numpy() return result