Skip to content

Commit b2fd391

Browse files
seismanweiji14
andauthored
Support passing string type numbers, geographic coordinates and datetimes (#975)
Co-authored-by: Wei Ji <[email protected]>
1 parent 6f3650a commit b2fd391

File tree

3 files changed

+84
-13
lines changed

3 files changed

+84
-13
lines changed

doc/api/index.rst

+1
Original file line numberDiff line numberDiff line change
@@ -224,6 +224,7 @@ Low level access (these are mostly used by the :mod:`pygmt.clib` package):
224224
clib.Session.get_default
225225
clib.Session.create_data
226226
clib.Session.put_matrix
227+
clib.Session.put_strings
227228
clib.Session.put_vector
228229
clib.Session.write_data
229230
clib.Session.open_virtual_file

pygmt/clib/session.py

+13-13
Original file line numberDiff line numberDiff line change
@@ -60,6 +60,7 @@
6060
np.uint64: "GMT_ULONG",
6161
np.uint32: "GMT_UINT",
6262
np.datetime64: "GMT_DATETIME",
63+
np.str_: "GMT_TEXT",
6364
}
6465

6566

@@ -719,9 +720,7 @@ def _check_dtype_and_dim(self, array, ndim):
719720
"""
720721
# check the array has the given dimension
721722
if array.ndim != ndim:
722-
raise GMTInvalidInput(
723-
"Expected a numpy 1d array, got {}d.".format(array.ndim)
724-
)
723+
raise GMTInvalidInput(f"Expected a numpy 1d array, got {array.ndim}d.")
725724

726725
# check the array has a valid/known data type
727726
if array.dtype.type not in DTYPES:
@@ -745,7 +744,7 @@ def put_vector(self, dataset, column, vector):
745744
first. Use ``family='GMT_IS_DATASET|GMT_VIA_VECTOR'``.
746745
747746
Not at all numpy dtypes are supported, only: float64, float32, int64,
748-
int32, uint64, and uint32.
747+
int32, uint64, uint32, datetime64 and str_.
749748
750749
.. warning::
751750
The numpy array must be C contiguous in memory. If it comes from a
@@ -777,23 +776,24 @@ def put_vector(self, dataset, column, vector):
777776
)
778777

779778
gmt_type = self._check_dtype_and_dim(vector, ndim=1)
780-
if gmt_type == self["GMT_DATETIME"]:
779+
if gmt_type in (self["GMT_TEXT"], self["GMT_DATETIME"]):
781780
vector_pointer = (ctp.c_char_p * len(vector))()
782-
vector_pointer[:] = np.char.encode(
783-
np.datetime_as_string(array_to_datetime(vector))
784-
)
781+
if gmt_type == self["GMT_DATETIME"]:
782+
vector_pointer[:] = np.char.encode(
783+
np.datetime_as_string(array_to_datetime(vector))
784+
)
785+
else:
786+
vector_pointer[:] = np.char.encode(vector)
785787
else:
786788
vector_pointer = vector.ctypes.data_as(ctp.c_void_p)
787789
status = c_put_vector(
788790
self.session_pointer, dataset, column, gmt_type, vector_pointer
789791
)
790792
if status != 0:
791793
raise GMTCLibError(
792-
" ".join(
793-
[
794-
"Failed to put vector of type {}".format(vector.dtype),
795-
"in column {} of dataset.".format(column),
796-
]
794+
(
795+
f"Failed to put vector of type {vector.dtype} "
796+
f"in column {column} of dataset."
797797
)
798798
)
799799

pygmt/tests/test_clib_put_vector.py

+70
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@
22
Test the functions that put vector data into GMT.
33
"""
44
import itertools
5+
from datetime import datetime
56

67
import numpy as np
78
import numpy.testing as npt
@@ -90,6 +91,75 @@ def test_put_vector_mixed_dtypes():
9091
npt.assert_allclose(newy, y)
9192

9293

94+
def test_put_vector_string_dtype():
95+
"""
96+
Passing string type vectors to a dataset.
97+
"""
98+
# input string vectors: numbers, longitudes, latitudes, and datetimes
99+
vectors = np.array(
100+
[
101+
["10", "20.0", "-30.0", "3.5e1"],
102+
["10W", "30.50E", "30:30W", "40:30:30.500E"],
103+
["10N", "30.50S", "30:30N", "40:30:30.500S"],
104+
["2021-02-03", "2021-02-03T04", "2021-02-03T04:05:06.700", "T04:50:06.700"],
105+
]
106+
)
107+
# output vectors in double or string type
108+
# Notes:
109+
# 1. longitudes and latitudes are stored in double in GMT
110+
# 2. The default output format for datetime is YYYY-mm-ddTHH:MM:SS
111+
expected_vectors = [
112+
[10.0, 20.0, -30.0, 35],
113+
[-10, 30.5, -30.5, 40.508472],
114+
[10, -30.50, 30.5, -40.508472],
115+
[
116+
"2021-02-03T00:00:00",
117+
"2021-02-03T04:00:00",
118+
"2021-02-03T04:05:06",
119+
f"{datetime.utcnow().strftime('%Y-%m-%d')}T04:50:06",
120+
],
121+
]
122+
123+
# loop over all possible combinations of input types
124+
for i, j in itertools.combinations_with_replacement(range(4), r=2):
125+
with clib.Session() as lib:
126+
dataset = lib.create_data(
127+
family="GMT_IS_DATASET|GMT_VIA_VECTOR",
128+
geometry="GMT_IS_POINT",
129+
mode="GMT_CONTAINER_ONLY",
130+
dim=[2, 4, 1, 0], # columns, rows, layers, dtype
131+
)
132+
lib.put_vector(dataset, column=lib["GMT_X"], vector=vectors[i])
133+
lib.put_vector(dataset, column=lib["GMT_Y"], vector=vectors[j])
134+
# Turns out wesn doesn't matter for Datasets
135+
wesn = [0] * 6
136+
# Save the data to a file to see if it's being accessed correctly
137+
with GMTTempFile() as tmp_file:
138+
lib.write_data(
139+
"GMT_IS_VECTOR",
140+
"GMT_IS_POINT",
141+
"GMT_WRITE_SET",
142+
wesn,
143+
tmp_file.name,
144+
dataset,
145+
)
146+
# Load the data
147+
output = np.genfromtxt(
148+
tmp_file.name, dtype=None, names=("x", "y"), encoding=None
149+
)
150+
# check that the output is correct
151+
# Use npt.assert_allclose for numeric arrays
152+
# and npt.assert_array_equal for string arrays
153+
if i != 3:
154+
npt.assert_allclose(output["x"], expected_vectors[i])
155+
else:
156+
npt.assert_array_equal(output["x"], expected_vectors[i])
157+
if j != 3:
158+
npt.assert_allclose(output["y"], expected_vectors[j])
159+
else:
160+
npt.assert_array_equal(output["y"], expected_vectors[j])
161+
162+
93163
def test_put_vector_invalid_dtype():
94164
"""
95165
Check that it fails with an exception for invalid data types.

0 commit comments

Comments
 (0)