GenericMappingTools / pygmt

A Python interface for the Generic Mapping Tools.
https://www.pygmt.org
BSD 3-Clause "New" or "Revised" License
747 stars 216 forks source link

Support passing in degrees, minutes, seconds (DMS) geographical coordinates #647

Closed seisman closed 3 years ago

seisman commented 3 years ago

Description of the desired feature

In GMT, geographical coordinates can be given in floating points or many different geographical formats (e.g., 23:30:30E), but PyGMT doesn't support these common geographical formats yet.

Here is a script to show what it looks like:

import pygmt

fig = pygmt.Figure()
fig.basemap(region=[-10, 10, -10, 10], projection='M10c', frame=True)
fig.plot(x=5.0, y=5.0, style='c0.5c', color='red')
fig.plot(x='3:30W', y='5:30W', style='c0.5c', color='blue')
fig.show()

Expected output:

image

Actual output

Traceback (most recent call last):
  File "/Users/seisman/Gits/gmt/pygmt/pygmt/clib/session.py", line 728, in _check_dtype_and_dim
    array = np.asarray(array, dtype=np.datetime64)
  File "/Users/seisman/.anaconda/lib/python3.8/site-packages/numpy/core/_asarray.py", line 85, in asarray
    return array(a, dtype, copy=False, order=order)
ValueError: Error parsing datetime string "3:30W" at position 1

The above exception was the direct cause of the following exception:

Traceback (most recent call last):
  File "test.py", line 6, in <module>
    fig.plot(x='3:30W', y='5:30W', style='c0.5c', color='blue')
  File "/Users/seisman/Gits/gmt/pygmt/pygmt/helpers/decorators.py", line 270, in new_module
    return module_func(*args, **kwargs)
  File "/Users/seisman/Gits/gmt/pygmt/pygmt/helpers/decorators.py", line 411, in new_module
    return module_func(*args, **kwargs)
  File "/Users/seisman/Gits/gmt/pygmt/pygmt/base_plotting.py", line 720, in plot
    with file_context as fname:
  File "/Users/seisman/.anaconda/lib/python3.8/contextlib.py", line 113, in __enter__
    return next(self.gen)
  File "/Users/seisman/Gits/gmt/pygmt/pygmt/clib/session.py", line 1174, in virtualfile_from_vectors
    self.put_vector(dataset, column=col, vector=array)
  File "/Users/seisman/Gits/gmt/pygmt/pygmt/clib/session.py", line 778, in put_vector
    gmt_type = self._check_dtype_and_dim(vector, ndim=1)
  File "/Users/seisman/Gits/gmt/pygmt/pygmt/clib/session.py", line 730, in _check_dtype_and_dim
    raise GMTInvalidInput(
pygmt.exceptions.GMTInvalidInput: Unsupported numpy data type '<class 'numpy.str_'>'.

Possible solutions

Here are two possible solutions in my mind:

  1. Currently, GMT_Put_Vector doesn't support string-like geographical coordinates. It could be a missing feature of the GMT API. With the new data type added, PyGMT still needs to determine if a string is a valid geographical coordinate before passing to GMT.
  2. PyGMT may provide a utility function to convert these geographical formats into floating points data. In this case, we don't need the feature in the GMT API. However, if I understand it correctly, geographical coordinates like 3:30W is still a little different from floating points -3.5. For 3:30W, GMT knows the input is geographical coordinates and may have more automatic settings for geographical coordinates.

Are you willing to help implement and maintain this feature? Yes, but contributions are welcome.

weiji14 commented 3 years ago

Just noting that this has a bit of overlap with #633 [which is about passing in string types (e.g. DMS coordinates) to the x and y parameter of some functions].

seisman commented 3 years ago

Ping @PaulWessel. How does GMT convert DMS strings to float? Is it possible to allow external programs to pass DMS strings via GMT_Put_Vector? Or is it easier or better to convert DMS strings to float numbers first?

PaulWessel commented 3 years ago

This is done during proccessing of ASCII data records. I am sure we could implement something similar to what we did with DATETIME. Maybe need some other flags like GMT_TEXT_LONGITUDE etc.

PaulWessel commented 3 years ago

I see two separate ways of how to do this, with some different benefits:

  1. Add two new types that _GMT_PutVector will recognize, such as GMT_TEXTLON and GMT_TEXTLAT. Then the implementation is very similar to the GMT_DATETIME from #633.
  2. Alternatively, change the values for the bit-flags GMT_IS_LON, GMT_IS_LAT and GMT_IS_ABSTIME from gmt_dev.h to gmt_resources.h and use with the data type GMT_TEXT e.g., GMT_TEXT|GMT_IS_LON.

In either case we should also allow GMT_TEXT to pass Cartesian coordinates via text, e.g. "3.1415", "1.2e-05". Unless you can see some perceived benefit of option 2, I think option 1 is straightforward, does not have any side-effects (like redefining the value of GMT_IS_LON) and simply adds two now vector "types" to the mix. Any comment?

PaulWessel commented 3 years ago

One thing this proposal does not address is if you want the converted data to be a double, float, int, etc. [double]. For that we would need to do something slight more complicated like GMT_FLOAT|GMT_TEXTLAT. I think that would be better, and wee should implement that for GMT_DATETIME as well.