Skip to content

Commit b3aa16d

Browse files
weiji14seisman
andauthored
Initialize a GMTDataArrayAccessor (#500)
An xarray accessor for GMT specific information! Currently holds the gridline/pixel registration and cartesian/geographic type properties. Created a new 'Metadata' section in the API docs, and moved info and grdinfo here. Allow for overriding registration and coordinate type, and added several unit tests including roundtrip setting/getting to bring code coverage up to scratch. Co-Authored-By: Dongdong Tian <[email protected]>
1 parent b74bc3e commit b3aa16d

File tree

5 files changed

+155
-6
lines changed

5 files changed

+155
-6
lines changed

.github/workflows/ci_tests.yaml

+3-3
Original file line numberDiff line numberDiff line change
@@ -67,7 +67,7 @@ jobs:
6767
path: |
6868
~/.gmt/cache
6969
~/.gmt/server
70-
key: cache-gmt-${{ runner.os }}-${{ github.ref }}-20200710-2
70+
key: cache-gmt-${{ runner.os }}-${{ github.ref }}-20200711
7171
restore-keys: cache-gmt-${{ runner.os }}-refs/heads/master-
7272

7373
# Workaround for the timeouts of 'gmt which' on Linux and Windows
@@ -80,7 +80,7 @@ jobs:
8080
for data in earth_relief_01d_p.grd earth_relief_01d_g.grd earth_relief_30m_p.grd earth_relief_30m_g.grd earth_relief_10m_p.grd earth_relief_10m_g.grd; do
8181
wget --no-check-certificate https://oceania.generic-mapping-tools.org/server/earth/earth_relief/${data} -P ~/.gmt/server/earth/earth_relief/
8282
done
83-
for data in ridge.txt Table_5_11.txt tut_bathy.nc tut_quakes.ngdc tut_ship.xyz usgs_quakes_22.txt; do
83+
for data in ridge.txt Table_5_11.txt test.dat.nc tut_bathy.nc tut_quakes.ngdc tut_ship.xyz usgs_quakes_22.txt; do
8484
wget --no-check-certificate https://oceania.generic-mapping-tools.org/cache/${data} -P ~/.gmt/cache/
8585
done
8686
if: steps.cache.outputs.cache-hit != 'true' && runner.os != 'macOS'
@@ -90,7 +90,7 @@ jobs:
9090
shell: bash -l {0}
9191
run: |
9292
gmt which -Ga @earth_relief_10m_p @earth_relief_10m_g @earth_relief_30m_p @earth_relief_30m_g @earth_relief_01d_p @earth_relief_01d_g
93-
gmt which -Ga @ridge.txt @Table_5_11.txt @tut_bathy.nc @tut_quakes.ngdc @tut_ship.xyz @usgs_quakes_22.txt
93+
gmt which -Ga @ridge.txt @Table_5_11.txt @test.dat.nc @tut_bathy.nc @tut_quakes.ngdc @tut_ship.xyz @usgs_quakes_22.txt
9494
if: steps.cache.outputs.cache-hit != 'true' && runner.os == 'macOS'
9595

9696
# Install the package that we want to test

doc/api/index.rst

+13-2
Original file line numberDiff line numberDiff line change
@@ -62,7 +62,6 @@ Operations on tabular data:
6262
:toctree: generated
6363

6464
blockmedian
65-
info
6665
surface
6766

6867
Operations on grids:
@@ -71,7 +70,6 @@ Operations on grids:
7170
:toctree: generated
7271

7372
grdcut
74-
grdinfo
7573
grdtrack
7674

7775
GMT Defaults
@@ -84,6 +82,19 @@ Operations on GMT defaults:
8482

8583
config
8684

85+
Metadata
86+
--------
87+
88+
Getting metadata from tabular or grid data:
89+
90+
.. autosummary::
91+
:toctree: generated
92+
93+
GMTDataArrayAccessor
94+
info
95+
grdinfo
96+
97+
8798
Miscellaneous
8899
-------------
89100

pygmt/__init__.py

+1-1
Original file line numberDiff line numberDiff line change
@@ -18,7 +18,7 @@
1818
from .gridding import surface
1919
from .sampling import grdtrack
2020
from .mathops import makecpt
21-
from .modules import config, info, grdinfo, which
21+
from .modules import GMTDataArrayAccessor, config, info, grdinfo, which
2222
from .gridops import grdcut
2323
from . import datasets
2424

pygmt/modules.py

+70
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,8 @@
11
"""
22
Non-plot GMT modules.
33
"""
4+
import xarray as xr
5+
46
from .clib import Session
57
from .helpers import (
68
build_arg_string,
@@ -214,3 +216,71 @@ def __exit__(self, exc_type, exc_value, traceback):
214216
)
215217
with Session() as lib:
216218
lib.call_module("set", arg_str)
219+
220+
221+
@xr.register_dataarray_accessor("gmt")
222+
class GMTDataArrayAccessor:
223+
"""
224+
This is the GMT extension for :class:`xarray.DataArray`.
225+
226+
You can access various GMT specific metadata about your grid as follows:
227+
228+
>>> from pygmt.datasets import load_earth_relief
229+
>>> # Use the global Earth relief grid with 1 degree spacing
230+
>>> grid = load_earth_relief(resolution="01d")
231+
232+
>>> # See if grid uses Gridline (0) or Pixel (1) registration
233+
>>> grid.gmt.registration
234+
1
235+
>>> # See if grid uses Cartesian (0) or Geographic (1) coordinate system
236+
>>> grid.gmt.gtype
237+
1
238+
"""
239+
240+
def __init__(self, xarray_obj):
241+
self._obj = xarray_obj
242+
try:
243+
self._source = self._obj.encoding["source"] # filepath to NetCDF source
244+
# From the shortened summary information of `grdinfo`,
245+
# get grid registration in column 10, and grid type in column 11
246+
self._registration, self._gtype = map(
247+
int, grdinfo(self._source, C="n", o="10,11").split()
248+
)
249+
except KeyError:
250+
self._registration = 0 # Default to Gridline registration
251+
self._gtype = 0 # Default to Cartesian grid type
252+
253+
@property
254+
def registration(self):
255+
"""
256+
Registration type of the grid, either Gridline (0) or Pixel (1).
257+
"""
258+
return self._registration
259+
260+
@registration.setter
261+
def registration(self, value):
262+
if value in (0, 1):
263+
self._registration = value
264+
else:
265+
raise GMTInvalidInput(
266+
f"Invalid grid registration value: {value}, should be a boolean of "
267+
"either 0 for Gridline registration or 1 for Pixel registration"
268+
)
269+
270+
@property
271+
def gtype(self):
272+
"""
273+
Coordinate system type of the grid, either Cartesian (0) or Geographic
274+
(1).
275+
"""
276+
return self._gtype
277+
278+
@gtype.setter
279+
def gtype(self, value):
280+
if value in (0, 1):
281+
self._gtype = value
282+
else:
283+
raise GMTInvalidInput(
284+
f"Invalid coordinate system type: {value}, should be a boolean of "
285+
"either 0 for Cartesian or 1 for Geographic"
286+
)

pygmt/tests/test_accessor.py

+68
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,68 @@
1+
"""
2+
Test the behaviour of the GMTDataArrayAccessor class
3+
"""
4+
import pytest
5+
import xarray as xr
6+
7+
from .. import which
8+
from ..exceptions import GMTInvalidInput
9+
10+
11+
def test_accessor_gridline_cartesian():
12+
"""
13+
Check that a grid returns a registration value of 0 when Gridline
14+
registered, and a gtype value of 1 when using Geographic coordinates.
15+
"""
16+
fname = which(fname="@test.dat.nc", download="a")
17+
grid = xr.open_dataarray(fname)
18+
assert grid.gmt.registration == 0 # gridline registration
19+
assert grid.gmt.gtype == 0 # cartesian coordinate type
20+
21+
22+
def test_accessor_pixel_geographic():
23+
"""
24+
Check that a grid returns a registration value of 1 when Pixel registered,
25+
and a gtype value of 0 when using Cartesian coordinates.
26+
"""
27+
fname = which(fname="@earth_relief_01d_p", download="a")
28+
grid = xr.open_dataarray(fname)
29+
assert grid.gmt.registration == 1 # pixel registration
30+
assert grid.gmt.gtype == 1 # geographic coordinate type
31+
32+
33+
def test_accessor_set_pixel_registration():
34+
"""
35+
Check that we can set a grid to be Pixel registered with a registration
36+
value of 1.
37+
"""
38+
grid = xr.DataArray(data=[[0.1, 0.2], [0.3, 0.4]])
39+
assert grid.gmt.registration == 0 # default to gridline registration
40+
grid.gmt.registration = 1 # set to pixel registration
41+
assert grid.gmt.registration == 1 # ensure changed to pixel registration
42+
43+
44+
def test_accessor_set_geographic_cartesian_roundtrip():
45+
"""
46+
Check that we can set a grid to switch between the default Cartesian
47+
coordinate type using a gtype of 1, set it to Geographic 0, and then back
48+
to Cartesian again 1.
49+
"""
50+
grid = xr.DataArray(data=[[0.1, 0.2], [0.3, 0.4]])
51+
assert grid.gmt.gtype == 0 # default to cartesian coordinate type
52+
grid.gmt.gtype = 1 # set to geographic type
53+
assert grid.gmt.gtype == 1 # ensure changed to geographic coordinate type
54+
grid.gmt.gtype = 0 # set back to cartesian type
55+
assert grid.gmt.gtype == 0 # ensure changed to cartesian coordinate type
56+
57+
58+
def test_accessor_set_non_boolean():
59+
"""
60+
Check that setting non boolean values on registration and gtype do not work
61+
"""
62+
grid = xr.DataArray(data=[[0.1, 0.2], [0.3, 0.4]])
63+
64+
with pytest.raises(GMTInvalidInput):
65+
grid.gmt.registration = "2"
66+
67+
with pytest.raises(GMTInvalidInput):
68+
grid.gmt.gtype = 2

0 commit comments

Comments
 (0)