Skip to content

Commit 1079460

Browse files
weiji14leouieda
authored andcommitted
Wrapper for the surface module (#245)
surface is a module for gridding using splines in tension. Adds a `surface` function that does the gridding. Add a `datasets.load_sample_bathymetry` function to get an example data for testing. Output is directed to a temporary file which is then loaded with xarray. Fixes #243
1 parent 9ad7384 commit 1079460

File tree

7 files changed

+253
-2
lines changed

7 files changed

+253
-2
lines changed

doc/api/index.rst

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -48,6 +48,7 @@ Operations on tabular data:
4848
:toctree: generated
4949

5050
info
51+
surface
5152

5253
Operations on grids:
5354

@@ -80,6 +81,7 @@ and store them in the GMT cache folder.
8081

8182
datasets.load_earth_relief
8283
datasets.load_usgs_quakes
84+
datasets.load_sample_bathymetry
8385
datasets.load_japan_quakes
8486

8587

gmt/__init__.py

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,7 @@
1313
# Import modules to make the high-level GMT Python API
1414
from .session_management import begin as _begin, end as _end
1515
from .figure import Figure
16+
from .gridding import surface
1617
from .modules import info, grdinfo, which
1718
from . import datasets
1819

gmt/datasets/__init__.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
"""
22
Load sample data included with GMT (downloaded from the GMT cache server).
33
"""
4-
from .tutorial import load_japan_quakes, load_usgs_quakes
4+
from .tutorial import load_japan_quakes, load_sample_bathymetry, load_usgs_quakes
55
from .earth_relief import load_earth_relief

gmt/datasets/tutorial.py

Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -38,6 +38,29 @@ def load_japan_quakes():
3838
return data
3939

4040

41+
def load_sample_bathymetry():
42+
"""
43+
Load a table of ship observations of bathymetry off Baja California as a
44+
pandas.DataFrame.
45+
46+
This is the ``@tut_ship.xyz`` dataset used in the GMT tutorials.
47+
48+
The data are downloaded to a cache directory (usually ``~/.gmt/cache``) the
49+
first time you invoke this function. Afterwards, it will load the data from
50+
the cache. So you'll need an internet connection the first time around.
51+
52+
Returns
53+
-------
54+
data : pandas.Dataframe
55+
The data table. Columns are longitude, latitude, and bathymetry.
56+
"""
57+
fname = which("@tut_ship.xyz", download="c")
58+
data = pd.read_csv(
59+
fname, sep="\t", header=None, names=["longitude", "latitude", "bathymetry"]
60+
)
61+
return data
62+
63+
4164
def load_usgs_quakes():
4265
"""
4366
Load a table of global earthquakes form the USGS as a pandas.Dataframe.

gmt/gridding.py

Lines changed: 93 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,93 @@
1+
"""
2+
GMT modules for Gridding of Data Tables
3+
"""
4+
import xarray as xr
5+
6+
from .clib import Session
7+
from .helpers import (
8+
build_arg_string,
9+
fmt_docstring,
10+
GMTTempFile,
11+
use_alias,
12+
data_kind,
13+
dummy_context,
14+
)
15+
from .exceptions import GMTInvalidInput
16+
17+
18+
@fmt_docstring
19+
@use_alias(I="spacing", R="region", G="outfile")
20+
def surface(x=None, y=None, z=None, data=None, **kwargs):
21+
"""
22+
Grids table data using adjustable tension continuous curvature splines.
23+
24+
Surface reads randomly-spaced (x,y,z) triples and produces gridded values z(x,y)
25+
by solving:
26+
27+
(1 - T) * L (L (z)) + T * L (z) = 0
28+
29+
where T is a tension factor between 0 and 1, and L indicates the Laplacian operator.
30+
31+
Takes a matrix, xyz triples, or a file name as input.
32+
33+
Must provide either *data* or *x*, *y*, and *z*.
34+
35+
{gmt_module_docs}
36+
37+
Parameters
38+
----------
39+
x, y, z : 1d arrays
40+
Arrays of x and y coordinates and values z of the data points.
41+
data : str or 2d array
42+
Either a data file name or a 2d numpy array with the tabular data.
43+
44+
spacing (I) :
45+
``'xinc[unit][+e|n][/yinc[unit][+e|n]]'``.
46+
x_inc [and optionally y_inc] is the grid spacing.
47+
48+
region (R) : str or list
49+
``'xmin/xmax/ymin/ymax[+r][+uunit]'``.
50+
Specify the region of interest.
51+
52+
outfile (G) : str
53+
Optional. The file name for the output netcdf file with extension .nc
54+
to store the grid in.
55+
56+
{aliases}
57+
58+
Returns
59+
-------
60+
ret: xarray.DataArray or None
61+
Return type depends on whether the outfile (G) parameter is set:
62+
63+
- xarray.DataArray if outfile (G) is not set
64+
- None if outfile (G) is set (grid output will be stored in outfile)
65+
"""
66+
kind = data_kind(data, x, y, z)
67+
if kind == "vectors" and z is None:
68+
raise GMTInvalidInput("Must provide z with x and y.")
69+
70+
with GMTTempFile(suffix=".nc") as tmpfile:
71+
with Session() as lib:
72+
if kind == "file":
73+
file_context = dummy_context(data)
74+
elif kind == "matrix":
75+
file_context = lib.virtualfile_from_matrix(data)
76+
elif kind == "vectors":
77+
file_context = lib.virtualfile_from_vectors(x, y, z)
78+
else:
79+
raise GMTInvalidInput("Unrecognized data type: {}".format(type(data)))
80+
with file_context as infile:
81+
if "G" not in kwargs.keys(): # if outfile is unset, output to tmpfile
82+
kwargs.update({"G": tmpfile.name})
83+
outfile = kwargs["G"]
84+
arg_str = " ".join([infile, build_arg_string(kwargs)])
85+
lib.call_module(module="surface", args=arg_str)
86+
87+
if outfile == tmpfile.name: # if user did not set outfile, return DataArray
88+
with xr.open_dataset(outfile) as dataset:
89+
result = dataset.load()
90+
elif outfile != tmpfile.name: # if user sets an outfile, return None
91+
result = None
92+
93+
return result

gmt/tests/test_datasets.py

Lines changed: 19 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,12 @@
55
import numpy.testing as npt
66
import pytest
77

8-
from ..datasets import load_japan_quakes, load_earth_relief, load_usgs_quakes
8+
from ..datasets import (
9+
load_japan_quakes,
10+
load_earth_relief,
11+
load_sample_bathymetry,
12+
load_usgs_quakes,
13+
)
914
from ..exceptions import GMTInvalidInput
1015

1116

@@ -22,6 +27,19 @@ def test_japan_quakes():
2227
assert summary.loc["max", "day"] == 31
2328

2429

30+
def test_sample_bathymetry():
31+
"Check that the @tut_ship.xyz dataset loads without errors"
32+
data = load_sample_bathymetry()
33+
assert data.shape == (82970, 3)
34+
summary = data.describe()
35+
assert summary.loc["min", "longitude"] == 245.0
36+
assert summary.loc["max", "longitude"] == 254.705
37+
assert summary.loc["min", "latitude"] == 20.0
38+
assert summary.loc["max", "latitude"] == 29.99131
39+
assert summary.loc["min", "bathymetry"] == -7708.0
40+
assert summary.loc["max", "bathymetry"] == -9.0
41+
42+
2543
def test_usgs_quakes():
2644
"Check that the dataset loads without errors"
2745
data = load_usgs_quakes()

gmt/tests/test_surface.py

Lines changed: 114 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,114 @@
1+
"""
2+
Tests for surface
3+
"""
4+
import os
5+
6+
import xarray as xr
7+
import pytest
8+
9+
from .. import surface
10+
from .. import which
11+
from ..datasets import load_sample_bathymetry
12+
from ..exceptions import GMTInvalidInput
13+
from ..helpers import data_kind
14+
15+
TEST_DATA_DIR = os.path.join(os.path.dirname(__file__), "data")
16+
TEMP_GRID = os.path.join(TEST_DATA_DIR, "tmp_grid.nc")
17+
18+
19+
def test_surface_input_file():
20+
"""
21+
Run surface by passing in a filename
22+
"""
23+
fname = which("@tut_ship.xyz", download="c")
24+
output = surface(data=fname, spacing="5m", region="245/255/20/30")
25+
assert isinstance(output, xr.Dataset)
26+
return output
27+
28+
29+
def test_surface_input_data_array():
30+
"""
31+
Run surface by passing in a numpy array into data
32+
"""
33+
ship_data = load_sample_bathymetry()
34+
data = ship_data.values # convert pandas.DataFrame to numpy.ndarray
35+
output = surface(data=data, spacing="5m", region="245/255/20/30")
36+
assert isinstance(output, xr.Dataset)
37+
return output
38+
39+
40+
def test_surface_input_xyz():
41+
"""
42+
Run surface by passing in x, y, z numpy.ndarrays individually
43+
"""
44+
ship_data = load_sample_bathymetry()
45+
output = surface(
46+
x=ship_data.longitude,
47+
y=ship_data.latitude,
48+
z=ship_data.bathymetry,
49+
spacing="5m",
50+
region="245/255/20/30",
51+
)
52+
assert isinstance(output, xr.Dataset)
53+
return output
54+
55+
56+
def test_surface_input_xy_no_z():
57+
"""
58+
Run surface by passing in x and y, but no z
59+
"""
60+
ship_data = load_sample_bathymetry()
61+
with pytest.raises(GMTInvalidInput):
62+
surface(
63+
x=ship_data.longitude,
64+
y=ship_data.latitude,
65+
spacing="5m",
66+
region="245/255/20/30",
67+
)
68+
69+
70+
def test_surface_wrong_kind_of_input():
71+
"""
72+
Run surface using grid input that is not file/matrix/vectors
73+
"""
74+
ship_data = load_sample_bathymetry()
75+
data = ship_data.bathymetry.to_xarray() # convert pandas.Series to xarray.DataArray
76+
assert data_kind(data) == "grid"
77+
with pytest.raises(GMTInvalidInput):
78+
surface(data=data, spacing="5m", region="245/255/20/30")
79+
80+
81+
def test_surface_with_outfile_param():
82+
"""
83+
Run surface with the -Goutputfile.nc parameter
84+
"""
85+
ship_data = load_sample_bathymetry()
86+
data = ship_data.values # convert pandas.DataFrame to numpy.ndarray
87+
try:
88+
output = surface(
89+
data=data, spacing="5m", region="245/255/20/30", outfile=TEMP_GRID
90+
)
91+
assert output is None # check that output is None since outfile is set
92+
assert os.path.exists(path=TEMP_GRID) # check that outfile exists at path
93+
grid = xr.open_dataset(TEMP_GRID)
94+
assert isinstance(grid, xr.Dataset) # check that netcdf grid loaded properly
95+
finally:
96+
os.remove(path=TEMP_GRID)
97+
return output
98+
99+
100+
def test_surface_short_aliases():
101+
"""
102+
Run surface using short aliases -I for spacing, -R for region, -G for outfile
103+
"""
104+
ship_data = load_sample_bathymetry()
105+
data = ship_data.values # convert pandas.DataFrame to numpy.ndarray
106+
try:
107+
output = surface(data=data, I="5m", R="245/255/20/30", G=TEMP_GRID)
108+
assert output is None # check that output is None since outfile is set
109+
assert os.path.exists(path=TEMP_GRID) # check that outfile exists at path
110+
grid = xr.open_dataset(TEMP_GRID)
111+
assert isinstance(grid, xr.Dataset) # check that netcdf grid loaded properly
112+
finally:
113+
os.remove(path=TEMP_GRID)
114+
return output

0 commit comments

Comments
 (0)