Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Wrap grdvolume #1299

Merged
merged 32 commits into from
Oct 25, 2021
Merged
Show file tree
Hide file tree
Changes from 29 commits
Commits
Show all changes
32 commits
Select commit Hold shift + click to select a range
efa0519
Create grdvolume.py
willschlitzer May 26, 2021
19f2fe7
add grdvolume to imports
willschlitzer May 26, 2021
d3ed614
add unit and slice parameters
willschlitzer May 26, 2021
68fab7c
add formatting to return array or DataFrame
willschlitzer May 26, 2021
07860a9
Merge branch 'master' into wrap-grdvolume
willschlitzer May 26, 2021
89eb397
add test_grdvolume.py
willschlitzer Jun 15, 2021
57633c3
add test_grdvolume_format
willschlitzer Jun 15, 2021
97adf31
add test_grdvolume_invalid_format
willschlitzer Jun 15, 2021
75ff3a2
add docstring for grdvolume
willschlitzer Jun 15, 2021
c94288f
update data_format docstring
willschlitzer Jun 15, 2021
7eb90b5
update data_format docstring to give one-line description
willschlitzer Jun 15, 2021
71ff162
Merge branch 'main' into wrap-grdvolume
willschlitzer Aug 22, 2021
64279fb
update table output format
willschlitzer Aug 25, 2021
d4e33ac
run make format and make check
willschlitzer Aug 25, 2021
c4f9ac9
remove unused numpy import
willschlitzer Aug 25, 2021
d32ef36
Merge branch 'main' into wrap-grdvolume
willschlitzer Oct 5, 2021
00178bb
Apply suggestions from code review
willschlitzer Oct 19, 2021
d636c58
fixing syntax error
willschlitzer Oct 20, 2021
e45ecc0
adding contour docstring
willschlitzer Oct 20, 2021
0e8195c
change accepted formats for contour in docstring
willschlitzer Oct 20, 2021
b80edb4
change size of grid file for tests
willschlitzer Oct 20, 2021
c493776
Apply suggestions from code review
willschlitzer Oct 21, 2021
7be5047
fix spacing
willschlitzer Oct 21, 2021
b4ae9db
changing test coordinates to be on land
willschlitzer Oct 21, 2021
d900d7c
Merge branch 'main' into wrap-grdvolume
willschlitzer Oct 21, 2021
eb02866
add test_grdvolume_no_output
willschlitzer Oct 21, 2021
d7c98cd
add test_grdvolume_outgrid
willschlitzer Oct 21, 2021
0509f02
add test_grdvolume_no_outfile
willschlitzer Oct 21, 2021
5150a85
Apply suggestions from code review
willschlitzer Oct 23, 2021
dd478e9
Update pygmt/src/grdvolume.py
willschlitzer Oct 23, 2021
52b22c7
Merge branch 'main' into wrap-grdvolume
willschlitzer Oct 23, 2021
ece4f8f
Apply suggestions from code review
willschlitzer Oct 25, 2021
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions doc/api/index.rst
Original file line number Diff line number Diff line change
Expand Up @@ -104,6 +104,7 @@ Operations on grids:
grdproject
grdsample
grdtrack
grdvolume

Crossover analysis with x2sys:

Expand Down
1 change: 1 addition & 0 deletions pygmt/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -46,6 +46,7 @@
grdproject,
grdsample,
grdtrack,
grdvolume,
info,
makecpt,
nearneighbor,
Expand Down
1 change: 1 addition & 0 deletions pygmt/src/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,7 @@
from pygmt.src.grdsample import grdsample
from pygmt.src.grdtrack import grdtrack
from pygmt.src.grdview import grdview
from pygmt.src.grdvolume import grdvolume
from pygmt.src.histogram import histogram
from pygmt.src.image import image
from pygmt.src.info import info
Expand Down
105 changes: 105 additions & 0 deletions pygmt/src/grdvolume.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,105 @@
"""
grdvolume - Calculate grid volume and area constrained by a contour.
"""
import pandas as pd
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,
)


@fmt_docstring
@use_alias(
C="contour",
R="region",
S="unit",
V="verbose",
)
@kwargs_to_strings(C="sequence", R="sequence")
def grdvolume(grid, output_type="pandas", outfile=None, **kwargs):
r"""
Determine the volume between the surface of a grid and a plane.

Read a 2-D grid file and calculate the volume contained below the surface
and above the plane specified by the given contour (or zero if not given)
and return the contour, area, volume, and maximum mean height
(volume/area). Alternatively, a range of contours can be specified to
return the volume and area inside the contour for all contour values.

willschlitzer marked this conversation as resolved.
Show resolved Hide resolved
Full option list at :gmt-docs:`grdvolume.html`

{aliases}

Parameters
----------
grid : str or xarray.DataArray
The file name of the input grid or the grid loaded as a DataArray.
This is the only required parameter.
willschlitzer marked this conversation as resolved.
Show resolved Hide resolved
output_type : str
Determine the format the xyz data will be returned in [Default is
willschlitzer marked this conversation as resolved.
Show resolved Hide resolved
``pandas``]:

- ``numpy`` - :class:`numpy.ndarray`
- ``pandas``- :class:`pandas.DataFrame`
- ``file`` - ASCII file (requires ``outfile``)
outfile : str
The file name for the output ASCII file.
contour : str or int or float or list
*cval*\|\ *low/high/delta*\|\ **r**\ *low/high*\|\ **r**\ *cval*.
Find area, volume and mean height (volume/area) inside and above the
*cval* contour. Alternatively, search using all contours from *low* to
*high* in steps of *delta*. [Default returns area, volume and mean
height of the entire grid]. The area is measured in the plane of the
contour. Adding the **r** prefix computes the volume below the grid
surface and above the planes defined by *low* and *high*, or below
*cval* and grid's minimum. Note that this is an *outside* volume
whilst the other forms compute an *inside* (below the surface) area
volume. Use this form to compute for example the volume of water
between two contours. If no *contour* is given then there is no contour
and the entire grid area, volume and the mean height is returned and
*cval* will be reported as 0.
{R}
{V}

Returns
-------
ret : pandas.DataFrame or numpy.ndarray or None
ret : pandas.DataFrame or numpy.ndarray or None
willschlitzer marked this conversation as resolved.
Show resolved Hide resolved
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`` [Default is
class:`pandas.DataFrame`])
"""
if output_type not in ["numpy", "pandas", "file"]:
raise GMTInvalidInput(
"""Must specify format as either numpy, pandas, or file."""
)
if output_type == "file" and outfile is None:
raise GMTInvalidInput("""Must specify outfile for ASCII output.""")

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
arg_str = " ".join([infile, build_arg_string(kwargs), "->" + outfile])
lib.call_module("grdvolume", arg_str)

# 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", header=None, 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
112 changes: 112 additions & 0 deletions pygmt/tests/test_grdvolume.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,112 @@
"""
Tests for grdvolume.
"""
import os

import numpy as np
import numpy.testing as npt
import pandas as pd
import pytest
from pygmt import grdvolume
from pygmt.datasets import load_earth_relief
from pygmt.exceptions import GMTInvalidInput
from pygmt.helpers import GMTTempFile


@pytest.fixture(scope="module", name="grid")
def fixture_grid():
"""
Load the grid data from the sample earth_relief file.
"""
return load_earth_relief(resolution="01d", region=[-100, -95, 34, 39])


@pytest.fixture(scope="module", name="data")
def fixture_data():
"""
Load the expected grdvolume data result as a numpy array.
"""
data = np.array(
[
[
2.00000000e02,
1.59920815e11,
3.16386172e13,
1.97839269e02,
],
[
2.50000000e02,
1.44365835e11,
2.38676788e13,
1.65327751e02,
],
[
3.00000000e02,
1.23788259e11,
1.71278707e13,
1.38364259e02,
],
[
3.50000000e02,
9.79597525e10,
1.15235913e13,
1.17635978e02,
],
[
4.00000000e02,
7.26646663e10,
7.22303463e12,
9.94022955e01,
],
]
)
return data


def test_grdvolume_format(grid):
"""
Test that correct formats are returned.
"""
grdvolume_default = grdvolume(grid=grid)
assert isinstance(grdvolume_default, pd.DataFrame)
grdvolume_array = grdvolume(grid=grid, output_type="numpy")
assert isinstance(grdvolume_array, np.ndarray)
grdvolume_df = grdvolume(grid=grid, output_type="pandas")
assert isinstance(grdvolume_df, pd.DataFrame)


def test_grdvolume_invalid_format(grid):
"""
Test that grdvolume fails with incorrect output_type argument.
"""
with pytest.raises(GMTInvalidInput):
grdvolume(grid=grid, output_type=1)


def test_grdvolume_no_outfile(grid):
"""
Test that grdvolume fails when output_type set to 'file' but no outfile is
specified.
"""
with pytest.raises(GMTInvalidInput):
grdvolume(grid=grid, output_type="file")


def test_grdvolume_no_outgrid(grid, data):
"""
Test the expected output of grdvolume with no output file set.
"""
test_output = grdvolume(grid=grid, contour=[200, 400, 50], output_type="numpy")
npt.assert_allclose(test_output, data)


def test_grdvolume_outgrid(grid):
"""
Test the expected output of grdvolume with an output file set.
"""
with GMTTempFile(suffix=".csv") as tmpfile:
result = grdvolume(
grid=grid, contour=[200, 400, 50], output_type="file", outfile=tmpfile.name
)
assert result is None # return value is None
assert os.path.exists(path=tmpfile.name) # check that outfile exists