Skip to content

Commit 6f930ac

Browse files
willschlitzerMeghan Jonesweiji14seisman
authored
Wrap grdvolume (#1299)
Co-authored-by: Meghan Jones <[email protected]> Co-authored-by: Wei Ji <[email protected]> Co-authored-by: Dongdong Tian <[email protected]>
1 parent 9cbd8f9 commit 6f930ac

File tree

5 files changed

+218
-0
lines changed

5 files changed

+218
-0
lines changed

doc/api/index.rst

+1
Original file line numberDiff line numberDiff line change
@@ -104,6 +104,7 @@ Operations on grids:
104104
grdproject
105105
grdsample
106106
grdtrack
107+
grdvolume
107108

108109
Crossover analysis with x2sys:
109110

pygmt/__init__.py

+1
Original file line numberDiff line numberDiff line change
@@ -46,6 +46,7 @@
4646
grdproject,
4747
grdsample,
4848
grdtrack,
49+
grdvolume,
4950
info,
5051
makecpt,
5152
nearneighbor,

pygmt/src/__init__.py

+1
Original file line numberDiff line numberDiff line change
@@ -24,6 +24,7 @@
2424
from pygmt.src.grdsample import grdsample
2525
from pygmt.src.grdtrack import grdtrack
2626
from pygmt.src.grdview import grdview
27+
from pygmt.src.grdvolume import grdvolume
2728
from pygmt.src.histogram import histogram
2829
from pygmt.src.image import image
2930
from pygmt.src.info import info

pygmt/src/grdvolume.py

+103
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,103 @@
1+
"""
2+
grdvolume - Calculate grid volume and area constrained by a contour.
3+
"""
4+
import pandas as pd
5+
from pygmt.clib import Session
6+
from pygmt.exceptions import GMTInvalidInput
7+
from pygmt.helpers import (
8+
GMTTempFile,
9+
build_arg_string,
10+
fmt_docstring,
11+
kwargs_to_strings,
12+
use_alias,
13+
)
14+
15+
16+
@fmt_docstring
17+
@use_alias(
18+
C="contour",
19+
R="region",
20+
S="unit",
21+
V="verbose",
22+
)
23+
@kwargs_to_strings(C="sequence", R="sequence")
24+
def grdvolume(grid, output_type="pandas", outfile=None, **kwargs):
25+
r"""
26+
Determine the volume between the surface of a grid and a plane.
27+
28+
Read a 2-D grid file and calculate the volume contained below the surface
29+
and above the plane specified by the given contour (or zero if not given)
30+
and return the contour, area, volume, and maximum mean height
31+
(volume/area). Alternatively, a range of contours can be specified to
32+
return the volume and area inside the contour for all contour values.
33+
34+
Full option list at :gmt-docs:`grdvolume.html`
35+
36+
{aliases}
37+
38+
Parameters
39+
----------
40+
grid : str or xarray.DataArray
41+
The file name of the input grid or the grid loaded as a DataArray.
42+
output_type : str
43+
Determine the format the output 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+
contour : str or int or float or list
52+
*cval*\|\ *low/high/delta*\|\ **r**\ *low/high*\|\ **r**\ *cval*.
53+
Find area, volume and mean height (volume/area) inside and above the
54+
*cval* contour. Alternatively, search using all contours from *low* to
55+
*high* in steps of *delta*. [Default returns area, volume and mean
56+
height of the entire grid]. The area is measured in the plane of the
57+
contour. Adding the **r** prefix computes the volume below the grid
58+
surface and above the planes defined by *low* and *high*, or below
59+
*cval* and grid's minimum. Note that this is an *outside* volume
60+
whilst the other forms compute an *inside* (below the surface) area
61+
volume. Use this form to compute for example the volume of water
62+
between two contours. If no *contour* is given then there is no contour
63+
and the entire grid area, volume and the mean height is returned and
64+
*cval* will be reported as 0.
65+
{R}
66+
{V}
67+
68+
Returns
69+
-------
70+
ret : pandas.DataFrame or numpy.ndarray or None
71+
Return type depends on ``outfile`` and ``output_type``:
72+
73+
- None if ``outfile`` is set (output will be stored in file set by
74+
``outfile``)
75+
- :class:`pandas.DataFrame` or :class:`numpy.ndarray` if ``outfile``
76+
is not set (depends on ``output_type`` [Default is
77+
class:`pandas.DataFrame`])
78+
"""
79+
if output_type not in ["numpy", "pandas", "file"]:
80+
raise GMTInvalidInput(
81+
"""Must specify format as either numpy, pandas, or file."""
82+
)
83+
if output_type == "file" and outfile is None:
84+
raise GMTInvalidInput("""Must specify outfile for ASCII output.""")
85+
86+
with GMTTempFile() as tmpfile:
87+
with Session() as lib:
88+
file_context = lib.virtualfile_from_data(check_kind="raster", data=grid)
89+
with file_context as infile:
90+
if outfile is None:
91+
outfile = tmpfile.name
92+
arg_str = " ".join([infile, build_arg_string(kwargs), "->" + outfile])
93+
lib.call_module("grdvolume", arg_str)
94+
95+
# Read temporary csv output to a pandas table
96+
if outfile == tmpfile.name: # if user did not set outfile, return pd.DataFrame
97+
result = pd.read_csv(tmpfile.name, sep="\t", header=None, comment=">")
98+
elif outfile != tmpfile.name: # return None if outfile set, output in outfile
99+
result = None
100+
101+
if output_type == "numpy":
102+
result = result.to_numpy()
103+
return result

pygmt/tests/test_grdvolume.py

+112
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,112 @@
1+
"""
2+
Tests for grdvolume.
3+
"""
4+
import os
5+
6+
import numpy as np
7+
import numpy.testing as npt
8+
import pandas as pd
9+
import pytest
10+
from pygmt import grdvolume
11+
from pygmt.datasets import load_earth_relief
12+
from pygmt.exceptions import GMTInvalidInput
13+
from pygmt.helpers import GMTTempFile
14+
15+
16+
@pytest.fixture(scope="module", name="grid")
17+
def fixture_grid():
18+
"""
19+
Load the grid data from the sample earth_relief file.
20+
"""
21+
return load_earth_relief(resolution="01d", region=[-100, -95, 34, 39])
22+
23+
24+
@pytest.fixture(scope="module", name="data")
25+
def fixture_data():
26+
"""
27+
Load the expected grdvolume data result as a numpy array.
28+
"""
29+
data = np.array(
30+
[
31+
[
32+
2.00000000e02,
33+
1.59920815e11,
34+
3.16386172e13,
35+
1.97839269e02,
36+
],
37+
[
38+
2.50000000e02,
39+
1.44365835e11,
40+
2.38676788e13,
41+
1.65327751e02,
42+
],
43+
[
44+
3.00000000e02,
45+
1.23788259e11,
46+
1.71278707e13,
47+
1.38364259e02,
48+
],
49+
[
50+
3.50000000e02,
51+
9.79597525e10,
52+
1.15235913e13,
53+
1.17635978e02,
54+
],
55+
[
56+
4.00000000e02,
57+
7.26646663e10,
58+
7.22303463e12,
59+
9.94022955e01,
60+
],
61+
]
62+
)
63+
return data
64+
65+
66+
def test_grdvolume_format(grid):
67+
"""
68+
Test that correct formats are returned.
69+
"""
70+
grdvolume_default = grdvolume(grid=grid)
71+
assert isinstance(grdvolume_default, pd.DataFrame)
72+
grdvolume_array = grdvolume(grid=grid, output_type="numpy")
73+
assert isinstance(grdvolume_array, np.ndarray)
74+
grdvolume_df = grdvolume(grid=grid, output_type="pandas")
75+
assert isinstance(grdvolume_df, pd.DataFrame)
76+
77+
78+
def test_grdvolume_invalid_format(grid):
79+
"""
80+
Test that grdvolume fails with incorrect output_type argument.
81+
"""
82+
with pytest.raises(GMTInvalidInput):
83+
grdvolume(grid=grid, output_type=1)
84+
85+
86+
def test_grdvolume_no_outfile(grid):
87+
"""
88+
Test that grdvolume fails when output_type set to 'file' but no outfile is
89+
specified.
90+
"""
91+
with pytest.raises(GMTInvalidInput):
92+
grdvolume(grid=grid, output_type="file")
93+
94+
95+
def test_grdvolume_no_outgrid(grid, data):
96+
"""
97+
Test the expected output of grdvolume with no output file set.
98+
"""
99+
test_output = grdvolume(grid=grid, contour=[200, 400, 50], output_type="numpy")
100+
npt.assert_allclose(test_output, data)
101+
102+
103+
def test_grdvolume_outgrid(grid):
104+
"""
105+
Test the expected output of grdvolume with an output file set.
106+
"""
107+
with GMTTempFile(suffix=".csv") as tmpfile:
108+
result = grdvolume(
109+
grid=grid, contour=[200, 400, 50], output_type="file", outfile=tmpfile.name
110+
)
111+
assert result is None # return value is None
112+
assert os.path.exists(path=tmpfile.name) # check that outfile exists

0 commit comments

Comments
 (0)