Skip to content

Commit cbe44aa

Browse files
willschlitzerMeghan Jonesweiji14seisman
authored
Wrap grd2xyz (#1284)
Co-authored-by: Meghan Jones <[email protected]> Co-authored-by: Wei Ji <[email protected]> Co-authored-by: Dongdong Tian <[email protected]>
1 parent d7c5564 commit cbe44aa

File tree

5 files changed

+217
-0
lines changed

5 files changed

+217
-0
lines changed

doc/api/index.rst

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -89,6 +89,7 @@ Operations on grids:
8989
.. autosummary::
9090
:toctree: generated
9191

92+
grd2xyz
9293
grdclip
9394
grdcut
9495
grdfill

pygmt/__init__.py

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -35,6 +35,7 @@
3535
blockmode,
3636
config,
3737
grd2cpt,
38+
grd2xyz,
3839
grdclip,
3940
grdcut,
4041
grdfill,

pygmt/src/__init__.py

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,7 @@
1010
from pygmt.src.config import config
1111
from pygmt.src.contour import contour
1212
from pygmt.src.grd2cpt import grd2cpt
13+
from pygmt.src.grd2xyz import grd2xyz
1314
from pygmt.src.grdclip import grdclip
1415
from pygmt.src.grdcontour import grdcontour
1516
from pygmt.src.grdcut import grdcut

pygmt/src/grd2xyz.py

Lines changed: 117 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,117 @@
1+
"""
2+
grd2xyz - Convert grid to data table
3+
"""
4+
import warnings
5+
6+
import pandas as pd
7+
import xarray as xr
8+
from pygmt.clib import Session
9+
from pygmt.exceptions import GMTInvalidInput
10+
from pygmt.helpers import (
11+
GMTTempFile,
12+
build_arg_string,
13+
fmt_docstring,
14+
kwargs_to_strings,
15+
use_alias,
16+
)
17+
18+
19+
@fmt_docstring
20+
@use_alias(
21+
R="region",
22+
V="verbose",
23+
o="outcols",
24+
)
25+
@kwargs_to_strings(R="sequence", o="sequence_comma")
26+
def grd2xyz(grid, output_type="pandas", outfile=None, **kwargs):
27+
r"""
28+
Convert grid to data table.
29+
30+
Read a grid and output xyz-triplets as a :class:`numpy.ndarray`,
31+
:class:`pandas.DataFrame`, or ASCII file.
32+
33+
Full option list at :gmt-docs:`grd2xyz.html`
34+
35+
{aliases}
36+
37+
Parameters
38+
----------
39+
grid : str or xarray.DataArray
40+
The file name of the input grid or the grid loaded as a
41+
:class:`xarray.DataArray`. This is the only required parameter.
42+
output_type : str
43+
Determine the format the xyz data will be returned in [Default is
44+
``pandas``]:
45+
46+
- ``numpy`` - :class:`numpy.ndarray`
47+
- ``pandas``- :class:`pandas.DataFrame`
48+
- ``file`` - ASCII file (requires ``outfile``)
49+
outfile : str
50+
The file name for the output ASCII file.
51+
{R}
52+
Adding ``region`` will select a subsection of the grid. If this
53+
subsection exceeds the boundaries of the grid, only the common region
54+
will be output.
55+
{V}
56+
{o}
57+
58+
Returns
59+
-------
60+
ret : pandas.DataFrame or numpy.ndarray or None
61+
Return type depends on ``outfile`` and ``output_type``:
62+
63+
- None if ``outfile`` is set (output will be stored in file set by
64+
``outfile``)
65+
- :class:`pandas.DataFrame` or :class:`numpy.ndarray` if ``outfile`` is
66+
not set (depends on ``output_type``)
67+
68+
"""
69+
if output_type not in ["numpy", "pandas", "file"]:
70+
raise GMTInvalidInput(
71+
"Must specify 'output_type' either as 'numpy', 'pandas' or 'file'."
72+
)
73+
74+
if outfile is not None and output_type != "file":
75+
msg = (
76+
f"Changing 'output_type' of grd2xyz from '{output_type}' to 'file' "
77+
"since 'outfile' parameter is set. Please use output_type='file' "
78+
"to silence this warning."
79+
)
80+
warnings.warn(message=msg, category=RuntimeWarning, stacklevel=2)
81+
output_type = "file"
82+
elif outfile is None and output_type == "file":
83+
raise GMTInvalidInput("Must specify 'outfile' for ASCII output.")
84+
85+
if "o" in kwargs and output_type == "pandas":
86+
raise GMTInvalidInput(
87+
"If 'outcols' is specified, 'output_type' must be either 'numpy'"
88+
"or 'file'."
89+
)
90+
91+
# Set the default column names for the pandas dataframe header
92+
dataframe_header = ["x", "y", "z"]
93+
# Let output pandas column names match input DataArray dimension names
94+
if isinstance(grid, xr.DataArray) and output_type == "pandas":
95+
# Reverse the dims because it is rows, columns ordered.
96+
dataframe_header = [grid.dims[1], grid.dims[0], grid.name]
97+
98+
with GMTTempFile() as tmpfile:
99+
with Session() as lib:
100+
file_context = lib.virtualfile_from_data(check_kind="raster", data=grid)
101+
with file_context as infile:
102+
if outfile is None:
103+
outfile = tmpfile.name
104+
arg_str = " ".join([infile, build_arg_string(kwargs), "->" + outfile])
105+
lib.call_module("grd2xyz", arg_str)
106+
107+
# Read temporary csv output to a pandas table
108+
if outfile == tmpfile.name: # if user did not set outfile, return pd.DataFrame
109+
result = pd.read_csv(
110+
tmpfile.name, sep="\t", names=dataframe_header, comment=">"
111+
)
112+
elif outfile != tmpfile.name: # return None if outfile set, output in outfile
113+
result = None
114+
115+
if output_type == "numpy":
116+
result = result.to_numpy()
117+
return result

pygmt/tests/test_grd2xyz.py

Lines changed: 97 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,97 @@
1+
"""
2+
Tests for grd2xyz.
3+
"""
4+
import os
5+
6+
import numpy as np
7+
import pandas as pd
8+
import pytest
9+
from pygmt import grd2xyz
10+
from pygmt.datasets import load_earth_relief
11+
from pygmt.exceptions import GMTInvalidInput
12+
from pygmt.helpers import GMTTempFile
13+
14+
15+
@pytest.fixture(scope="module", name="grid")
16+
def fixture_grid():
17+
"""
18+
Load the grid data from the sample earth_relief file.
19+
"""
20+
return load_earth_relief(resolution="01d", region=[-1, 1, 3, 5])
21+
22+
23+
def test_grd2xyz(grid):
24+
"""
25+
Make sure grd2xyz works as expected.
26+
"""
27+
xyz_data = grd2xyz(grid=grid, output_type="numpy")
28+
assert xyz_data.shape == (4, 3)
29+
30+
31+
def test_grd2xyz_format(grid):
32+
"""
33+
Test that correct formats are returned.
34+
"""
35+
lon = -0.5
36+
lat = 3.5
37+
orig_val = grid.sel(lon=lon, lat=lat).to_numpy()
38+
xyz_default = grd2xyz(grid=grid)
39+
xyz_val = xyz_default[(xyz_default["lon"] == lon) & (xyz_default["lat"] == lat)][
40+
"elevation"
41+
].to_numpy()
42+
assert isinstance(xyz_default, pd.DataFrame)
43+
assert orig_val.size == 1
44+
assert xyz_val.size == 1
45+
np.testing.assert_allclose(orig_val, xyz_val)
46+
xyz_array = grd2xyz(grid=grid, output_type="numpy")
47+
assert isinstance(xyz_array, np.ndarray)
48+
xyz_df = grd2xyz(grid=grid, output_type="pandas")
49+
assert isinstance(xyz_df, pd.DataFrame)
50+
assert list(xyz_df.columns) == ["lon", "lat", "elevation"]
51+
52+
53+
def test_grd2xyz_file_output(grid):
54+
"""
55+
Test that grd2xyz returns a file output when it is specified.
56+
"""
57+
with GMTTempFile(suffix=".xyz") as tmpfile:
58+
result = grd2xyz(grid=grid, outfile=tmpfile.name, output_type="file")
59+
assert result is None # return value is None
60+
assert os.path.exists(path=tmpfile.name) # check that outfile exists
61+
62+
63+
def test_grd2xyz_invalid_format(grid):
64+
"""
65+
Test that grd2xyz fails with incorrect format.
66+
"""
67+
with pytest.raises(GMTInvalidInput):
68+
grd2xyz(grid=grid, output_type=1)
69+
70+
71+
def test_grd2xyz_no_outfile(grid):
72+
"""
73+
Test that grd2xyz fails when a string output is set with no outfile.
74+
"""
75+
with pytest.raises(GMTInvalidInput):
76+
grd2xyz(grid=grid, output_type="file")
77+
78+
79+
def test_grd2xyz_outfile_incorrect_output_type(grid):
80+
"""
81+
Test that grd2xyz raises a warning when an outfile filename is set but the
82+
output_type is not set to 'file'.
83+
"""
84+
with pytest.warns(RuntimeWarning):
85+
with GMTTempFile(suffix=".xyz") as tmpfile:
86+
result = grd2xyz(grid=grid, outfile=tmpfile.name, output_type="numpy")
87+
assert result is None # return value is None
88+
assert os.path.exists(path=tmpfile.name) # check that outfile exists
89+
90+
91+
def test_grd2xyz_pandas_output_with_o(grid):
92+
"""
93+
Test that grd2xyz fails when outcols is set and output_type is set to
94+
'pandas'.
95+
"""
96+
with pytest.raises(GMTInvalidInput):
97+
grd2xyz(grid=grid, output_type="pandas", outcols="2")

0 commit comments

Comments
 (0)