-
Notifications
You must be signed in to change notification settings - Fork 228
Wrap project #1122
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
Merged
Merged
Wrap project #1122
Changes from 23 commits
Commits
Show all changes
31 commits
Select commit
Hold shift + click to select a range
d67cfdc
Wrap project
claudiodsf 666adfe
Merge branch 'main' into project
dd31234
Fix a couple sphinx warnings
8345f2a
Format math in docstring
7a2dafe
Format
975ac71
Add a first test for project
04b019b
Use kwargs_to_strings for center parameter
d7eb759
Expand table-like input options for project
efc7879
Add additional tests
737cdea
Change maptypeunits to unit to agree with grdproject alias
360cc98
Format
cc94581
Update flatearth to flat_earth to follow convention
c748e06
Update parameter name in tests
089de6a
Update rotation pole to pole according to gmt.jl
1741923
Apply suggestions from code review
e3657c1
Merge branch 'main' into project
9e2df3b
[format-command] fixes
actions-bot 92f6a65
Add column names testing for project 'generate'
ca14b63
Update pygmt/src/project.py
174bab6
Merge branch 'main' into project
b4a770a
Update flags to convention in pygmt/project.py
3f552ac
Update flags to convention in test_project.py
7216577
Format pygmt/src/project.py
8a37c54
Remove comment about case-sensitive parameters
4ed080d
Apply suggestions from code review
b2de68c
Standardize tense in docstring
90df309
Update docstring for generate parameter
54ea674
Merge branch 'main' into project
6c1bd81
Fix whitespace
ef8326f
Update pygmt/src/project.py
8232b67
Merge branch 'main' into project
File filter
Filter by extension
Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
There are no files selected for viewing
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -49,6 +49,7 @@ | |
info, | ||
makecpt, | ||
nearneighbor, | ||
project, | ||
sph2grd, | ||
sphdistance, | ||
sphinterpolate, | ||
|
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,256 @@ | ||
""" | ||
project - Project data onto lines or great circles, or generate tracks. | ||
""" | ||
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( | ||
A="azimuth", | ||
C="center", | ||
E="endpoint", | ||
F="convention", | ||
G="generate", | ||
L="length", | ||
N="flat_earth", | ||
Q="unit", | ||
S="sort", | ||
T="pole", | ||
V="verbose", | ||
W="width", | ||
Z="ellipse", | ||
f="coltypes", | ||
) | ||
@kwargs_to_strings(E="sequence", L="sequence", T="sequence", W="sequence", C="sequence") | ||
def project(data=None, x=None, y=None, z=None, outfile=None, **kwargs): | ||
r""" | ||
Project data onto lines or great circles, or generate tracks. | ||
|
||
Project reads arbitrary :math:`(x, y [, z])` data and returns any | ||
combination of :math:`(x, y, z, p, q, r, s)`, where :math:`(p, q)` are the | ||
coordinates in the projection, :math:`(r, s)` is the position in the | ||
:math:`(x, y)` coordinate system of the point on the profile (:math:`q = 0` | ||
path) closest to :math:`(x, y)`, and :math:`z` is all remaining columns in | ||
the input (beyond the required :math:`x` and :math:`y` columns). | ||
|
||
Alternatively, :doc:`pygmt.project` may be used to generate | ||
maxrjones marked this conversation as resolved.
Show resolved
Hide resolved
|
||
:math:`(r, s, p)` triples at equal increments along a profile using the | ||
``generate`` parameter. In this case, the value of ``data`` is ignored | ||
(you can use, e.g., ``data=None``). | ||
|
||
Projections are defined in any (but only) one of three ways: | ||
|
||
1. By a ``center`` and an ``azimuth`` in degrees clockwise from North. | ||
2. By a ``center`` and ``endpoint`` of the projection path. | ||
3. By a ``center`` and a ``pole`` position. | ||
|
||
To spherically project data along a great circle path, an oblique | ||
coordinate system is created which has its equator along that path, and the | ||
zero meridian through the Center. Then the oblique longitude (:math:`p`) | ||
corresponds to the distance from the Center along the great circle, and the | ||
oblique latitude (:math:`q`) corresponds to the distance perpendicular to | ||
the great circle path. When moving in the increasing (:math:`p`) direction, | ||
(toward B or in the azimuth direction), the positive (:math:`q`) direction | ||
is to your left. If a Pole has been specified, then the positive | ||
(:math:`q`) direction is toward the pole. | ||
|
||
To specify an oblique projection, use the ``pole`` option to set | ||
the pole. Then the equator of the projection is already determined and the | ||
``center`` option is used to locate the :math:`p = 0` meridian. The center | ||
*cx/cy* will be taken as a point through which the :math:`p = 0` meridian | ||
passes. If you do not care to choose a particular point, use the South pole | ||
(*cx* = 0, *cy* = -90). | ||
|
||
Data can be selectively windowed by using the ``length`` and ``width`` | ||
options. If ``width`` is used, the projection width is set to use only | ||
data with :math:`w_{{min}} < q < w_{{max}}`. If ``length`` is set, then | ||
the length is set to use only those data with | ||
:math:`l_{{min}} < p < l_{{max}}`. If the ``endpoint`` option | ||
has been used to define the projection, then ``length="w"`` may be used to | ||
window the length of the projection to exactly the span from O to B. | ||
|
||
Flat Earth (Cartesian) coordinate transformations can also be made. Set | ||
``flat_earth=True`` and remember that azimuth is clockwise from North (the | ||
y axis), NOT the usual cartesian theta, which is counterclockwise from the | ||
x axis. azimuth = 90 - theta. | ||
|
||
No assumptions are made regarding the units for | ||
:math:`x, y, r, s, p, q, dist, l_{{min}}, l_{{max}}, w_{{min}}, w_{{max}}`. | ||
If -Q is selected, map units are assumed and :math:`x, y, r, s` must be in | ||
degrees and :math:`p, q, dist, l_{{min}}, l_{{max}}, w_{{min}}, w_{{max}}` | ||
will be in km. | ||
|
||
Calculations of specific great-circle and geodesic distances or for | ||
back-azimuths or azimuths are better done using :gmt-docs:`mapproject` as | ||
project is strictly spherical. | ||
|
||
:doc:`pygmt.project` is case sensitive: use lower case for the | ||
**xyzpqrs** letters in ``convention``. | ||
maxrjones marked this conversation as resolved.
Show resolved
Hide resolved
|
||
|
||
{aliases} | ||
|
||
Parameters | ||
---------- | ||
data : str or {table-like} | ||
Pass in (x, y, z) or (longitude, latitude, elevation) values by | ||
providing a file name to an ASCII data table, a 2D | ||
{table-classes}. | ||
|
||
center : str or list | ||
*cx*/*cy*. | ||
*cx/cy* sets the origin of the projection, in Definition 1 or 2. If | ||
Definition 3 is used, then *cx/cy* are the coordinates of a | ||
point through which the oblique zero meridian (:math:`p = 0`) should | ||
pass. The *cx/cy* is not required to be 90 degrees from the pole. | ||
|
||
azimuth : float or str | ||
defines the azimuth of the projection (Definition 1). | ||
|
||
endpoint : str or list | ||
*bx*/*by*. | ||
*bx/by* defines the end point of the projection path (Definition 2). | ||
maxrjones marked this conversation as resolved.
Show resolved
Hide resolved
|
||
|
||
convention : str | ||
Specify your desired output using any combination of **xyzpqrs**, in | ||
any order [Default is **xypqrsz**]. Do not space between the letters. | ||
Use lower case. The output will be columns of values corresponding to | ||
your ``convention``. The **z** flag is special and refers to all | ||
numerical columns beyond the leading **x** and **y** in your input | ||
record. The **z** flag also includes any trailing text (which is | ||
placed at the end of the record regardless of the order of **z** in | ||
``convention``). **Note**: If ``generate`` is True, then the output | ||
order is hardwired to be **rsp** and ``convention`` is not allowed. | ||
|
||
generate : str | ||
*dist* [/*colat*][**+c**\|\ **h**]. | ||
Generate mode. No input is read and the value of ``data`` is ignored | ||
(you can use, e.g., ``data=None``). Create :math:`(r, s, p)` output | ||
maxrjones marked this conversation as resolved.
Show resolved
Hide resolved
|
||
data every *dist* units of :math:`p`. See `unit` option. | ||
Alternatively, append */colat* for a small circle instead [Default is a | ||
colatitude of 90, i.e., a great circle]. If setting a pole with | ||
``pole`` and you want the small circle to go through *cx*/*cy*, | ||
append **+c** to compute the required colatitude. Use ``center`` and | ||
``endpoint`` to generate a circle that goes through the center and end | ||
point. Note, in this case the center and end point cannot be farther | ||
apart than :math:`2|\mbox{{colat}}|`. Finally, if you append **+h** | ||
then we will report the position of the pole as part of the segment | ||
header [Default is no header]. | ||
|
||
length : str or list | ||
[**w**\|\ *l_min*/*l_max*]. | ||
Length controls. Project only those data whose *p* coordinate is | ||
within :math:`l_{{min}} < p < l_{{max}}`. If ``endpoint`` has been set, | ||
maxrjones marked this conversation as resolved.
Show resolved
Hide resolved
|
||
then you may alternatively use **w** to stay within the distance from | ||
``center`` to ``endpoint``. | ||
|
||
flat_earth : bool | ||
If `True`, Make a Cartesian coordinate transformation in the plane. | ||
[Default uses spherical trigonometry.] | ||
maxrjones marked this conversation as resolved.
Show resolved
Hide resolved
|
||
|
||
unit : bool | ||
If `True`, project assumes :math:`x, y, r, s` are in degrees while | ||
:math:`p, q, dist, l_{{min}}, l_{{max}}, w_{{min}}, {{w_max}}` are in | ||
km. If not set (or ``False``), then all these are assumed to be in the | ||
same units. | ||
maxrjones marked this conversation as resolved.
Show resolved
Hide resolved
|
||
|
||
sort : bool | ||
Sort the output into increasing :math:`p` order. Useful when projecting | ||
random data into a sequential profile. | ||
|
||
pole : str or list | ||
*px*/*py*. | ||
*px/py* sets the position of the rotation pole of the projection. | ||
maxrjones marked this conversation as resolved.
Show resolved
Hide resolved
|
||
(Definition 3). | ||
|
||
{V} | ||
|
||
width : str or list | ||
*w_min*/*w_max*. | ||
Width controls. Project only those data whose :math:`q` coordinate is | ||
maxrjones marked this conversation as resolved.
Show resolved
Hide resolved
|
||
within :math:`w_{{min}} < q < w_{{max}}`. | ||
|
||
ellipse : str | ||
*major*/*minor*/*azimuth* [**+e**\|\ **n**]. | ||
Used in conjunction with ``center`` (sets its center) and ``generate`` | ||
(sets the distance increment) to create the coordinates of an ellipse | ||
with *major* and *minor* axes given in km (unless ``flat_earth`` is | ||
given for a Cartesian ellipse) and the *azimuth* of the major axis in | ||
degrees. Append **+e** to adjust the increment set via ``generate`` so | ||
that the the ellipse has equal distance increments [Default uses the | ||
given increment and closes the ellipse]. Instead, append **+n** to set | ||
a specific number of unique equidistant data via ``generate``. For | ||
degenerate ellipses you can just supply a single *diameter* instead. A | ||
geographic diameter may be specified in any desired unit other than km | ||
by appending the unit (e.g., 3d for degrees) [Default is km]; if so we | ||
assume the increment is also given in the same unit. **Note**: | ||
For the Cartesian ellipse (which requires ``flat_earth``), we expect | ||
*direction* counter-clockwise from the horizontal instead of an | ||
*azimuth*. | ||
maxrjones marked this conversation as resolved.
Show resolved
Hide resolved
|
||
|
||
outfile : str | ||
The file name for the output ASCII file. | ||
|
||
{f} | ||
|
||
Returns | ||
------- | ||
track: pandas.DataFrame or None | ||
Return type depends on whether the ``outfile`` parameter is set: | ||
|
||
- :class:`pandas.DataFrame` table with (x, y, ..., newcolname) if | ||
``outfile`` is not set | ||
- None if ``outfile`` is set (output will be stored in file set | ||
by ``outfile``) | ||
""" | ||
|
||
if "C" not in kwargs: | ||
raise GMTInvalidInput("The `center` parameter must be specified.") | ||
if "G" not in kwargs and data is None: | ||
raise GMTInvalidInput( | ||
"The `data` parameter must be specified unless `generate` is used." | ||
) | ||
if "G" in kwargs and "F" in kwargs: | ||
raise GMTInvalidInput( | ||
"The `convention` parameter is not allowed with `generate`." | ||
) | ||
|
||
with GMTTempFile(suffix=".csv") as tmpfile: | ||
if outfile is None: # Output to tmpfile if outfile is not set | ||
outfile = tmpfile.name | ||
with Session() as lib: | ||
if "G" not in kwargs: | ||
# Choose how data will be passed into the module | ||
table_context = lib.virtualfile_from_data( | ||
check_kind="vector", data=data, x=x, y=y, z=z, required_z=False | ||
) | ||
|
||
# Run project on the temporary (csv) data table | ||
with table_context as infile: | ||
arg_str = " ".join( | ||
[infile, build_arg_string(kwargs), "->" + outfile] | ||
) | ||
else: | ||
arg_str = " ".join([build_arg_string(kwargs), "->" + outfile]) | ||
lib.call_module(module="project", args=arg_str) | ||
|
||
# if user did not set outfile, return pd.DataFrame | ||
if outfile == tmpfile.name: | ||
if "G" in kwargs: | ||
column_names = list("rsp") | ||
result = pd.read_csv(tmpfile.name, sep="\t", names=column_names) | ||
else: | ||
result = pd.read_csv(tmpfile.name, sep="\t", header=None, comment=">") | ||
# return None if outfile set, output in outfile | ||
elif outfile != tmpfile.name: | ||
result = None | ||
|
||
return result |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,90 @@ | ||
""" | ||
Tests for project. | ||
""" | ||
import os | ||
|
||
import numpy as np | ||
import numpy.testing as npt | ||
import pandas as pd | ||
import pytest | ||
import xarray as xr | ||
from pygmt import project | ||
from pygmt.exceptions import GMTInvalidInput | ||
from pygmt.helpers import GMTTempFile | ||
|
||
|
||
@pytest.fixture(scope="module", name="dataframe") | ||
def fixture_dataframe(): | ||
""" | ||
Create a DataFrame for the project tests. | ||
""" | ||
return pd.DataFrame(data={"x": [0], "y": [0]}) | ||
|
||
|
||
def test_project_generate(): | ||
""" | ||
Run project by passing in center and endpoint as input. | ||
""" | ||
output = project(center=[0, -1], endpoint=[0, 1], flat_earth=True, generate=0.5) | ||
assert isinstance(output, pd.DataFrame) | ||
assert output.shape == (5, 3) | ||
npt.assert_allclose(output.iloc[1], [3.061617e-17, -0.5, 0.5]) | ||
pd.testing.assert_index_equal( | ||
left=output.columns, right=pd.Index(data=["r", "s", "p"]) | ||
) | ||
|
||
|
||
@pytest.mark.parametrize("array_func", [np.array, pd.DataFrame, xr.Dataset]) | ||
def test_project_input_matrix(array_func, dataframe): | ||
""" | ||
Run project by passing in a matrix as input. | ||
""" | ||
table = array_func(dataframe) | ||
output = project(data=table, center=[0, -1], azimuth=45, flat_earth=True) | ||
assert isinstance(output, pd.DataFrame) | ||
assert output.shape == (1, 6) | ||
npt.assert_allclose( | ||
output.iloc[0], | ||
[0.000000, 0.000000, 0.707107, 0.707107, 0.500000, -0.500000], | ||
rtol=1e-5, | ||
) | ||
|
||
|
||
def test_project_output_filename(dataframe): | ||
""" | ||
Run project by passing in a pandas.DataFrame, and output to an ASCII txt | ||
file. | ||
""" | ||
with GMTTempFile() as tmpfile: | ||
output = project( | ||
data=dataframe, | ||
center=[0, -1], | ||
azimuth=45, | ||
flat_earth=True, | ||
outfile=tmpfile.name, | ||
) | ||
assert output is None # check that output is None since outfile is set | ||
assert os.path.exists(path=tmpfile.name) # check that outfile exists at path | ||
output = pd.read_csv(tmpfile.name, sep="\t", header=None) | ||
assert output.shape == (1, 6) | ||
npt.assert_allclose( | ||
output.iloc[0], | ||
[0.000000, 0.000000, 0.707107, 0.707107, 0.500000, -0.500000], | ||
rtol=1e-5, | ||
) | ||
|
||
|
||
def test_project_incorrect_parameters(): | ||
maxrjones marked this conversation as resolved.
Show resolved
Hide resolved
|
||
""" | ||
Run project by providing incorrect parameters such as 1) no `center`; 2) no | ||
`data` or `generate`; and 3) `generate` with `convention`. | ||
""" | ||
with pytest.raises(GMTInvalidInput): | ||
# No `center` | ||
project(azimuth=45) | ||
with pytest.raises(GMTInvalidInput): | ||
# No `data` or `generate` | ||
project(center=[0, -1], azimuth=45, flat_earth=True) | ||
with pytest.raises(GMTInvalidInput): | ||
# Using `generate` with `convention` | ||
project(center=[0, -1], generate=0.5, convention="xypqrsz") |
Add this suggestion to a batch that can be applied as a single commit.
This suggestion is invalid because no changes were made to the code.
Suggestions cannot be applied while the pull request is closed.
Suggestions cannot be applied while viewing a subset of changes.
Only one suggestion per line can be applied in a batch.
Add this suggestion to a batch that can be applied as a single commit.
Applying suggestions on deleted lines is not supported.
You must change the existing code in this line in order to create a valid suggestion.
Outdated suggestions cannot be applied.
This suggestion has been applied or marked resolved.
Suggestions cannot be applied from pending reviews.
Suggestions cannot be applied on multi-line comments.
Suggestions cannot be applied while the pull request is queued to merge.
Suggestion cannot be applied right now. Please check back later.
Uh oh!
There was an error while loading. Please reload this page.