From b69987f4a18a6afd47f8e65c15087656fd9febfe Mon Sep 17 00:00:00 2001 From: Wei Ji Date: Thu, 23 Jul 2020 21:40:31 +1200 Subject: [PATCH 01/13] Wrap x2sys_init and x2sys_cross Wrapping the x2sys_cross function (and x2sys_init), implemented under x2sys.py. Original GMT `x2sys_cross` documentation can be found at https://docs.generic-mapping-tools.org/6.1/supplements/x2sys/x2sys_cross.html. Sample test cases stored under test_x2sys_cross.py, and they uses the load_sample_bathymetry dataset. Aliased fmtfile (D) and force (F) for x2sys_init. Aliased tag (T) and coe (Q) for x2sys_cross. --- doc/api/index.rst | 8 ++ pygmt/__init__.py | 1 + pygmt/tests/test_x2sys_cross.py | 38 +++++++++ pygmt/x2sys.py | 142 ++++++++++++++++++++++++++++++++ 4 files changed, 189 insertions(+) create mode 100644 pygmt/tests/test_x2sys_cross.py create mode 100644 pygmt/x2sys.py diff --git a/doc/api/index.rst b/doc/api/index.rst index 467e88de8cc..53334b04384 100644 --- a/doc/api/index.rst +++ b/doc/api/index.rst @@ -72,6 +72,14 @@ Operations on grids: grdcut grdtrack +Crossover analysis with x2sys: + +.. autosummary:: + :toctree: generated + + x2sys_init + x2sys_cross + GMT Defaults ------------ diff --git a/pygmt/__init__.py b/pygmt/__init__.py index 56e770366e6..512865264b0 100644 --- a/pygmt/__init__.py +++ b/pygmt/__init__.py @@ -20,6 +20,7 @@ from .mathops import makecpt from .modules import GMTDataArrayAccessor, config, info, grdinfo, which from .gridops import grdcut +from .x2sys import x2sys_init, x2sys_cross from . import datasets diff --git a/pygmt/tests/test_x2sys_cross.py b/pygmt/tests/test_x2sys_cross.py new file mode 100644 index 00000000000..41b3f040d15 --- /dev/null +++ b/pygmt/tests/test_x2sys_cross.py @@ -0,0 +1,38 @@ +""" +Tests for x2sys_cross +""" +import os +from tempfile import TemporaryDirectory + +import pandas as pd +import pytest +import xarray as xr + +from .. import which, x2sys_cross, x2sys_init +from ..datasets import load_sample_bathymetry +from ..exceptions import GMTInvalidInput +from ..helpers import data_kind + + +@pytest.fixture +def mock_x2sys_home(monkeypatch): + monkeypatch.setenv("X2SYS_HOME", os.getcwd()) + + +def test_x2sys_cross_input_file(mock_x2sys_home): + """ + Run x2sys_cross by passing in a filename + """ + fname = which("@tut_ship.xyz", download="a") + with TemporaryDirectory(prefix="X2SYS", dir=os.getcwd()) as tmpdir: + tag = os.path.basename(tmpdir) + x2sys_init(tag=tag, fmtfile="xyz", force=True) + output = x2sys_cross(tracks=[fname], tag=tag, coe="i") + + assert isinstance(output, pd.DataFrame) + assert output.shape == (14294, 12) + columns = list(output.columns) + assert columns[:6] == ["x", "y", "i_1", "i_2", "dist_1", "dist_2"] + assert columns[6:] == ["head_1", "head_2", "vel_1", "vel_2", "z_X", "z_M"] + + return output diff --git a/pygmt/x2sys.py b/pygmt/x2sys.py new file mode 100644 index 00000000000..90b605aed24 --- /dev/null +++ b/pygmt/x2sys.py @@ -0,0 +1,142 @@ +""" +GMT supplementary X2SYS module for crossover analysis. +""" +import pandas as pd +import xarray as xr + +from .clib import Session +from .helpers import ( + build_arg_string, + fmt_docstring, + GMTTempFile, + use_alias, + data_kind, + dummy_context, +) +from .exceptions import GMTInvalidInput + + +@fmt_docstring +@use_alias(D="fmtfile", F="force") +def x2sys_init(tag, **kwargs): + """ + Initialize a new x2sys track database. + + x2sys_init is the starting point for anyone wishing to use x2sys; it + initializes a set of data bases that are particular to one kind of track + data. These data, their associated data bases, and key parameters are given + a short-hand notation called an x2sys TAG. The TAG keeps track of settings + such as file format, whether the data are geographic or not, and the + binning resolution for track indices. + + Before you can run x2sys_init you must set the environmental parameter + X2SYS_HOME to a directory where you have write permission, which is where + x2sys can keep track of your settings. + + Full option list at :gmt-docs:`supplements/x2sys/x2sys_init.html` + + {aliases} + + Parameters + ---------- + tag : str + The unique name of this data type x2sys TAG. + + fmtfile : str + Format definition file prefix for this data set [See Format Definition + Files for more information]. Specify full path if the file is not in + the current directory. + + Some file formats already have definition files premade. These include: + + - mgd77 (for plain ASCII MGD77 data files) + - mgd77+ (for enhanced MGD77+ netCDF files) + - gmt (for old mgg supplement binary files) + - xy (for plain ASCII x, y tables) + - xyz (same, with one z-column) + - geo (for plain ASCII longitude, latitude files) + - geoz (same, with one z-column). + + """ + with Session() as lib: + arg_str = " ".join([tag, build_arg_string(kwargs)]) + lib.call_module(module="x2sys_init", args=arg_str) + + +@fmt_docstring +@use_alias(T="tag", Q="coe") +def x2sys_cross(tracks=None, outfile=None, **kwargs): + """ + Calculate crossovers between track data files. + + x2sys_cross is used to determine all intersections between ("external + cross-overs") or within ("internal cross-overs") tracks (Cartesian or + geographic), and report the time, position, distance along track, heading + and speed along each track segment, and the crossover error (COE) and mean + values for all observables. The names of the tracks are passed on the + command line. By default, x2sys_cross will look for both external and + internal COEs. As an option, you may choose to project all data using one + of the map-projections prior to calculating the COE. + + Full option list at :gmt-docs:`supplements/x2sys/x2sys_cross.html` + + {aliases} + + Parameters + ---------- + tracks : pandas.DataFrame or str + Either a table with (x, y) or (lon, lat) values in the first two + columns, or a filename (e.g. csv, txt format). More columns may be + present. + + outfile : str + Optional. The file name for the output ASCII txt file to store the + table in. + + tag : str + Specify the x2sys TAG which identifies the attributes of this data + type. + + coe : str + Use **e** for external COEs only, and **i** for internal COEs only + [Default is all COEs]. + + Returns + ------- + crossover_errors : pandas.DataFrame or None + Table containing crossover error information. + Return type depends on whether the outfile parameter is set: + + - pandas.DataFrame table with (x, y, ..., etc) if outfile is not set + - None if outfile is set (track output will be stored in outfile) + + """ + # kinds = [data_kind(data=track) for track in tracks] + track = tracks[0] + kind = data_kind(track) + + with Session() as lib: + # for kind, track in zip(kinds, tracks): + if kind == "file": + file_context = dummy_context(track) + elif kind == "matrix": + file_context = lib.virtualfile_from_matrix(track.values) + + with GMTTempFile(suffix=".txt") as tmpfile: + with file_context as fname: + if outfile is None: + outfile = tmpfile.name + arg_str = " ".join([fname, build_arg_string(kwargs), "->" + outfile]) + lib.call_module(module="x2sys_cross", args=arg_str) + + # Read temporary csv output to a pandas table + if outfile == tmpfile.name: # if outfile isn't set, return pd.DataFrame + # Read the tab-separated ASCII table + # Header is on 2nd row, and we skip the 3rd row with a ">" + df = pd.read_csv(tmpfile.name, sep="\t", header=2, comment=">") + # Remove the "# " from "# x" in the first column + result = df.rename(columns={df.columns[0]: df.columns[0][2:]}) + elif outfile != tmpfile.name: # if outfile is set, output in outfile only + result = None + + return result From 5e4d419f865fbb98f5850ca11b24e146d507321d Mon Sep 17 00:00:00 2001 From: Wei Ji Date: Thu, 23 Jul 2020 22:23:59 +1200 Subject: [PATCH 02/13] Fix pylint style errors --- .pylintrc | 1 + pygmt/tests/test_x2sys_cross.py | 17 ++++++++++------- pygmt/x2sys.py | 4 ++-- 3 files changed, 13 insertions(+), 9 deletions(-) diff --git a/.pylintrc b/.pylintrc index 66b76a52b7c..6b5fa3de710 100644 --- a/.pylintrc +++ b/.pylintrc @@ -441,6 +441,7 @@ function-naming-style=snake_case good-names=i, j, k, + df, ex, Run, _, diff --git a/pygmt/tests/test_x2sys_cross.py b/pygmt/tests/test_x2sys_cross.py index 41b3f040d15..19185f32c3e 100644 --- a/pygmt/tests/test_x2sys_cross.py +++ b/pygmt/tests/test_x2sys_cross.py @@ -6,20 +6,23 @@ import pandas as pd import pytest -import xarray as xr from .. import which, x2sys_cross, x2sys_init -from ..datasets import load_sample_bathymetry -from ..exceptions import GMTInvalidInput -from ..helpers import data_kind +# from ..datasets import load_sample_bathymetry +# from ..exceptions import GMTInvalidInput -@pytest.fixture -def mock_x2sys_home(monkeypatch): + +@pytest.fixture(name="mock_x2sys_home") +def fixture_mock_x2sys_home(monkeypatch): + """ + Set the X2SYS_HOME environment variable to the current working directory + for the test session + """ monkeypatch.setenv("X2SYS_HOME", os.getcwd()) -def test_x2sys_cross_input_file(mock_x2sys_home): +def test_x2sys_cross_input_file(mock_x2sys_home): # pylint: disable=unused-argument """ Run x2sys_cross by passing in a filename """ diff --git a/pygmt/x2sys.py b/pygmt/x2sys.py index 90b605aed24..723abef6981 100644 --- a/pygmt/x2sys.py +++ b/pygmt/x2sys.py @@ -2,7 +2,6 @@ GMT supplementary X2SYS module for crossover analysis. """ import pandas as pd -import xarray as xr from .clib import Session from .helpers import ( @@ -13,7 +12,8 @@ data_kind, dummy_context, ) -from .exceptions import GMTInvalidInput + +# from .exceptions import GMTInvalidInput @fmt_docstring From 007e957063f77ab3bdb42d537f829b0e0136cf75 Mon Sep 17 00:00:00 2001 From: Wei Ji Date: Thu, 23 Jul 2020 23:25:24 +1200 Subject: [PATCH 03/13] Check that filename input/output works in x2sys_cross --- pygmt/tests/test_x2sys_cross.py | 26 ++++++++++++++++++++++++-- 1 file changed, 24 insertions(+), 2 deletions(-) diff --git a/pygmt/tests/test_x2sys_cross.py b/pygmt/tests/test_x2sys_cross.py index 19185f32c3e..3426d6395d5 100644 --- a/pygmt/tests/test_x2sys_cross.py +++ b/pygmt/tests/test_x2sys_cross.py @@ -22,9 +22,31 @@ def fixture_mock_x2sys_home(monkeypatch): monkeypatch.setenv("X2SYS_HOME", os.getcwd()) -def test_x2sys_cross_input_file(mock_x2sys_home): # pylint: disable=unused-argument +def test_x2sys_cross_input_file_output_file( + mock_x2sys_home, +): # pylint: disable=unused-argument """ - Run x2sys_cross by passing in a filename + Run x2sys_cross by passing in a filename and output to an ASCII txt file + """ + fname = which("@tut_ship.xyz", download="a") + with TemporaryDirectory(prefix="X2SYS", dir=os.getcwd()) as tmpdir: + tag = os.path.basename(tmpdir) + x2sys_init(tag=tag, fmtfile="xyz", force=True) + outfile = os.path.join(tmpdir, "tmp_coe.txt") + output = x2sys_cross(tracks=[fname], tag=tag, coe="i", outfile=outfile) + + assert output is None # check that output is None since outfile is set + assert os.path.exists(path=outfile) # check that outfile exists at path + _ = pd.read_csv(outfile, sep="\t", header=2) # ensure ASCII text file loads ok + + return output + + +def test_x2sys_cross_input_file_output_dataframe( + mock_x2sys_home, +): # pylint: disable=unused-argument + """ + Run x2sys_cross by passing in a filename and output to a pandas.DataFrame """ fname = which("@tut_ship.xyz", download="a") with TemporaryDirectory(prefix="X2SYS", dir=os.getcwd()) as tmpdir: From 4a5d5db9f63260018ab3adee92718631533d2ff6 Mon Sep 17 00:00:00 2001 From: Wei Ji Date: Fri, 24 Jul 2020 13:11:49 +1200 Subject: [PATCH 04/13] Alias verbose (V) --- pygmt/helpers/decorators.py | 11 +++++++++++ pygmt/x2sys.py | 8 ++++++-- 2 files changed, 17 insertions(+), 2 deletions(-) diff --git a/pygmt/helpers/decorators.py b/pygmt/helpers/decorators.py index bee5b78e17a..0441137abf7 100644 --- a/pygmt/helpers/decorators.py +++ b/pygmt/helpers/decorators.py @@ -36,6 +36,17 @@ color : str Select color or pattern for filling of symbols or polygons. Default is no fill.""", + "V": """\ + verbose : str + Select verbosity level [Default is w], which modulates the messages + written to stderr. Choose among 7 levels of verbosity: + 'q' - Quiet, not even fatal error messages are produced; + 'e' - Error messages only; + 'w' - Warnings [Default]; + 't' - Timings (report runtimes for time-intensive algorithms); + 'i' - Informational messages (same as verbose=True); + 'c' - Compatibility warnings; + 'd' - Debugging messages.""", "W": """\ pen : str Set pen attributes for lines or the outline of symbols.""", diff --git a/pygmt/x2sys.py b/pygmt/x2sys.py index 723abef6981..8fa9621f496 100644 --- a/pygmt/x2sys.py +++ b/pygmt/x2sys.py @@ -17,7 +17,7 @@ @fmt_docstring -@use_alias(D="fmtfile", F="force") +@use_alias(D="fmtfile", F="force", V="verbose") def x2sys_init(tag, **kwargs): """ Initialize a new x2sys track database. @@ -57,6 +57,8 @@ def x2sys_init(tag, **kwargs): - geo (for plain ASCII longitude, latitude files) - geoz (same, with one z-column). + {V} + """ with Session() as lib: arg_str = " ".join([tag, build_arg_string(kwargs)]) @@ -64,7 +66,7 @@ def x2sys_init(tag, **kwargs): @fmt_docstring -@use_alias(T="tag", Q="coe") +@use_alias(T="tag", Q="coe", V="verbose") def x2sys_cross(tracks=None, outfile=None, **kwargs): """ Calculate crossovers between track data files. @@ -101,6 +103,8 @@ def x2sys_cross(tracks=None, outfile=None, **kwargs): Use **e** for external COEs only, and **i** for internal COEs only [Default is all COEs]. + {V} + Returns ------- crossover_errors : pandas.DataFrame or None From 383134f6a482c0186304ed858b2a4c30b535844b Mon Sep 17 00:00:00 2001 From: Wei Ji Date: Fri, 24 Jul 2020 13:16:50 +1200 Subject: [PATCH 05/13] Pass in remote filename directly to x2sys_cross, turn on debug mode too --- pygmt/tests/test_x2sys_cross.py | 19 ++++++++----------- 1 file changed, 8 insertions(+), 11 deletions(-) diff --git a/pygmt/tests/test_x2sys_cross.py b/pygmt/tests/test_x2sys_cross.py index 3426d6395d5..6884cce9d2a 100644 --- a/pygmt/tests/test_x2sys_cross.py +++ b/pygmt/tests/test_x2sys_cross.py @@ -1,3 +1,4 @@ +# pylint: disable=unused-argument """ Tests for x2sys_cross """ @@ -7,7 +8,7 @@ import pandas as pd import pytest -from .. import which, x2sys_cross, x2sys_init +from .. import x2sys_cross, x2sys_init # from ..datasets import load_sample_bathymetry # from ..exceptions import GMTInvalidInput @@ -22,18 +23,17 @@ def fixture_mock_x2sys_home(monkeypatch): monkeypatch.setenv("X2SYS_HOME", os.getcwd()) -def test_x2sys_cross_input_file_output_file( - mock_x2sys_home, -): # pylint: disable=unused-argument +def test_x2sys_cross_input_file_output_file(mock_x2sys_home): """ Run x2sys_cross by passing in a filename and output to an ASCII txt file """ - fname = which("@tut_ship.xyz", download="a") with TemporaryDirectory(prefix="X2SYS", dir=os.getcwd()) as tmpdir: tag = os.path.basename(tmpdir) x2sys_init(tag=tag, fmtfile="xyz", force=True) outfile = os.path.join(tmpdir, "tmp_coe.txt") - output = x2sys_cross(tracks=[fname], tag=tag, coe="i", outfile=outfile) + output = x2sys_cross( + tracks=["@tut_ship.xyz"], tag=tag, coe="i", outfile=outfile, verbose="d" + ) assert output is None # check that output is None since outfile is set assert os.path.exists(path=outfile) # check that outfile exists at path @@ -42,17 +42,14 @@ def test_x2sys_cross_input_file_output_file( return output -def test_x2sys_cross_input_file_output_dataframe( - mock_x2sys_home, -): # pylint: disable=unused-argument +def test_x2sys_cross_input_file_output_dataframe(mock_x2sys_home): """ Run x2sys_cross by passing in a filename and output to a pandas.DataFrame """ - fname = which("@tut_ship.xyz", download="a") with TemporaryDirectory(prefix="X2SYS", dir=os.getcwd()) as tmpdir: tag = os.path.basename(tmpdir) x2sys_init(tag=tag, fmtfile="xyz", force=True) - output = x2sys_cross(tracks=[fname], tag=tag, coe="i") + output = x2sys_cross(tracks=["@tut_ship.xyz"], tag=tag, coe="i", verbose="d") assert isinstance(output, pd.DataFrame) assert output.shape == (14294, 12) From b5aa8a8686e6df0ea219ce4dd269063ffa91bcb3 Mon Sep 17 00:00:00 2001 From: Wei Ji Date: Sat, 25 Jul 2020 00:20:01 +1200 Subject: [PATCH 06/13] Let x2sys_file handle multiple input tracks and external crossovers --- pygmt/tests/test_x2sys_cross.py | 39 ++++++++++++++++++++++--- pygmt/x2sys.py | 50 ++++++++++++++++++++------------- 2 files changed, 65 insertions(+), 24 deletions(-) diff --git a/pygmt/tests/test_x2sys_cross.py b/pygmt/tests/test_x2sys_cross.py index 6884cce9d2a..61e78731c17 100644 --- a/pygmt/tests/test_x2sys_cross.py +++ b/pygmt/tests/test_x2sys_cross.py @@ -5,6 +5,7 @@ import os from tempfile import TemporaryDirectory +import numpy as np import pandas as pd import pytest @@ -25,14 +26,15 @@ def fixture_mock_x2sys_home(monkeypatch): def test_x2sys_cross_input_file_output_file(mock_x2sys_home): """ - Run x2sys_cross by passing in a filename and output to an ASCII txt file + Run x2sys_cross by passing in a filename, and output internal crossovers to + an ASCII txt file """ with TemporaryDirectory(prefix="X2SYS", dir=os.getcwd()) as tmpdir: tag = os.path.basename(tmpdir) x2sys_init(tag=tag, fmtfile="xyz", force=True) outfile = os.path.join(tmpdir, "tmp_coe.txt") output = x2sys_cross( - tracks=["@tut_ship.xyz"], tag=tag, coe="i", outfile=outfile, verbose="d" + tracks=["@tut_ship.xyz"], tag=tag, coe="i", outfile=outfile, verbose="i" ) assert output is None # check that output is None since outfile is set @@ -44,12 +46,13 @@ def test_x2sys_cross_input_file_output_file(mock_x2sys_home): def test_x2sys_cross_input_file_output_dataframe(mock_x2sys_home): """ - Run x2sys_cross by passing in a filename and output to a pandas.DataFrame + Run x2sys_cross by passing in a filename, and output internal crossovers to + a pandas.DataFrame """ with TemporaryDirectory(prefix="X2SYS", dir=os.getcwd()) as tmpdir: tag = os.path.basename(tmpdir) x2sys_init(tag=tag, fmtfile="xyz", force=True) - output = x2sys_cross(tracks=["@tut_ship.xyz"], tag=tag, coe="i", verbose="d") + output = x2sys_cross(tracks=["@tut_ship.xyz"], tag=tag, coe="i", verbose="i") assert isinstance(output, pd.DataFrame) assert output.shape == (14294, 12) @@ -58,3 +61,31 @@ def test_x2sys_cross_input_file_output_dataframe(mock_x2sys_home): assert columns[6:] == ["head_1", "head_2", "vel_1", "vel_2", "z_X", "z_M"] return output + + +def test_x2sys_cross_input_two_filenames(mock_x2sys_home): + """ + Run x2sys_cross by passing in two filenames, and output external crossovers + to a pandas.DataFrame + """ + with TemporaryDirectory(prefix="X2SYS", dir=os.getcwd()) as tmpdir: + tag = os.path.basename(tmpdir) + x2sys_init(tag=tag, fmtfile="xyz", force=True) + + # Create temporary xyz files + for i in range(2): + np.random.seed(seed=i) + with open(os.path.join(os.getcwd(), f"track_{i}.xyz"), mode="w") as fname: + np.savetxt(fname=fname, X=np.random.rand(10, 3)) + + output = x2sys_cross( + tracks=["track_0.xyz", "track_1.xyz"], tag=tag, coe="e", verbose="i" + ) + + assert isinstance(output, pd.DataFrame) + assert output.shape == (24, 12) + columns = list(output.columns) + assert columns[:6] == ["x", "y", "i_1", "i_2", "dist_1", "dist_2"] + assert columns[6:] == ["head_1", "head_2", "vel_1", "vel_2", "z_X", "z_M"] + + return output diff --git a/pygmt/x2sys.py b/pygmt/x2sys.py index 8fa9621f496..c13d134e5de 100644 --- a/pygmt/x2sys.py +++ b/pygmt/x2sys.py @@ -1,20 +1,22 @@ """ GMT supplementary X2SYS module for crossover analysis. """ +import contextlib + import pandas as pd from .clib import Session + +# from .exceptions import GMTInvalidInput from .helpers import ( - build_arg_string, - fmt_docstring, GMTTempFile, - use_alias, + build_arg_string, data_kind, dummy_context, + fmt_docstring, + use_alias, ) -# from .exceptions import GMTInvalidInput - @fmt_docstring @use_alias(D="fmtfile", F="force", V="verbose") @@ -86,10 +88,19 @@ def x2sys_cross(tracks=None, outfile=None, **kwargs): Parameters ---------- - tracks : pandas.DataFrame or str - Either a table with (x, y) or (lon, lat) values in the first two - columns, or a filename (e.g. csv, txt format). More columns may be - present. + tracks : pandas.DataFrame or str or list + Either a single pandas.DataFrame table with (x, y) or (lon, lat) values + in the first two columns, or a single filename (supported formats are + ASCII .txt, native binary, or COARDS netCDF 1-D .nc), or a combination + of tables and/or filenames in a list. More columns may also be present. + + If the filenames are missing their file extension, we will append the + suffix specified for this TAG. Track files will be searched for first + in the current directory and second in all directories listed in + $X2SYS_HOME/TAG/TAG_paths.txt (if it exists). [If $X2SYS_HOME is not + set it will default to $GMT_SHAREDIR/x2sys]. (Note: MGD77 files will + also be looked for via $MGD77_HOME/mgd77_paths.txt and *.gmt files will + be searched for via $GMT_SHAREDIR/mgg/gmtfile_paths). outfile : str Optional. The file name for the output ASCII txt file to store the @@ -115,22 +126,21 @@ def x2sys_cross(tracks=None, outfile=None, **kwargs): - None if outfile is set (track output will be stored in outfile) """ - # kinds = [data_kind(data=track) for track in tracks] - track = tracks[0] - kind = data_kind(track) - with Session() as lib: - # for kind, track in zip(kinds, tracks): - if kind == "file": - file_context = dummy_context(track) - elif kind == "matrix": - file_context = lib.virtualfile_from_matrix(track.values) + file_contexts = [] + for track in tracks: + kind = data_kind(track) + if kind == "file": + file_contexts.append(dummy_context(track)) + elif kind == "matrix": + file_contexts.append(lib.virtualfile_from_matrix(track.values)) with GMTTempFile(suffix=".txt") as tmpfile: - with file_context as fname: + with contextlib.ExitStack() as stack: + fnames = [stack.enter_context(c) for c in file_contexts] if outfile is None: outfile = tmpfile.name - arg_str = " ".join([fname, build_arg_string(kwargs), "->" + outfile]) + arg_str = " ".join([*fnames, build_arg_string(kwargs), "->" + outfile]) lib.call_module(module="x2sys_cross", args=arg_str) # Read temporary csv output to a pandas table From 4309346ef1643bff2f338b6e5c323dbedd3e0341 Mon Sep 17 00:00:00 2001 From: Wei Ji Date: Sat, 25 Jul 2020 11:02:41 +1200 Subject: [PATCH 07/13] NotImplementedError for pd.DataFrame, GMTInvalidInput for invalid types Support only filename inputs into x2sys_cross for now, because passing in matrix type virtualfiles into GMT C API doesn't work properly yet. --- pygmt/tests/test_x2sys_cross.py | 48 ++++++++++++++++++++++++++++++--- pygmt/x2sys.py | 17 ++++++------ 2 files changed, 54 insertions(+), 11 deletions(-) diff --git a/pygmt/tests/test_x2sys_cross.py b/pygmt/tests/test_x2sys_cross.py index 61e78731c17..4c1d149bd66 100644 --- a/pygmt/tests/test_x2sys_cross.py +++ b/pygmt/tests/test_x2sys_cross.py @@ -10,9 +10,9 @@ import pytest from .. import x2sys_cross, x2sys_init - -# from ..datasets import load_sample_bathymetry -# from ..exceptions import GMTInvalidInput +from ..datasets import load_sample_bathymetry +from ..exceptions import GMTInvalidInput +from ..helpers import data_kind @pytest.fixture(name="mock_x2sys_home") @@ -24,6 +24,15 @@ def fixture_mock_x2sys_home(monkeypatch): monkeypatch.setenv("X2SYS_HOME", os.getcwd()) +@pytest.fixture(scope="module", name="tracks") +def fixture_tracks(): + """ + Load track data from the sample bathymetry file + """ + df = load_sample_bathymetry() + return [df.query(expr="bathymetry > -20")] # reduce size of dataset + + def test_x2sys_cross_input_file_output_file(mock_x2sys_home): """ Run x2sys_cross by passing in a filename, and output internal crossovers to @@ -63,6 +72,28 @@ def test_x2sys_cross_input_file_output_dataframe(mock_x2sys_home): return output +def test_x2sys_cross_input_dataframe_output_dataframe(mock_x2sys_home, tracks): + """ + Run x2sys_cross by passing in one dataframe, and output external crossovers + to a pandas.DataFrame. Not actually implemented yet, wait for + https://github.com/GenericMappingTools/gmt/issues/3717 + """ + with TemporaryDirectory(prefix="X2SYS", dir=os.getcwd()) as tmpdir: + tag = os.path.basename(tmpdir) + x2sys_init(tag=tag, fmtfile="xyz", force=True) + + with pytest.raises(NotImplementedError): + _ = x2sys_cross(tracks=tracks, tag=tag, coe="i", verbose="i") + + # assert isinstance(output, pd.DataFrame) + # assert output.shape == (4, 12) + # columns = list(output.columns) + # assert columns[:6] == ["x", "y", "i_1", "i_2", "dist_1", "dist_2"] + # assert columns[6:] == ["head_1", "head_2", "vel_1", "vel_2", "z_X", "z_M"] + + # return output + + def test_x2sys_cross_input_two_filenames(mock_x2sys_home): """ Run x2sys_cross by passing in two filenames, and output external crossovers @@ -89,3 +120,14 @@ def test_x2sys_cross_input_two_filenames(mock_x2sys_home): assert columns[6:] == ["head_1", "head_2", "vel_1", "vel_2", "z_X", "z_M"] return output + + +def test_x2sys_cross_invalid_tracks_input_type(tracks): + """ + Run x2sys_cross using tracks input that is not a pandas.DataFrame (matrix) + or str (file) type, which would raise a GMTInvalidInput error. + """ + invalid_tracks = tracks[0].to_xarray().bathymetry + assert data_kind(invalid_tracks) == "grid" + with pytest.raises(GMTInvalidInput): + x2sys_cross(tracks=[invalid_tracks]) diff --git a/pygmt/x2sys.py b/pygmt/x2sys.py index c13d134e5de..eec9d4b35c5 100644 --- a/pygmt/x2sys.py +++ b/pygmt/x2sys.py @@ -6,8 +6,7 @@ import pandas as pd from .clib import Session - -# from .exceptions import GMTInvalidInput +from .exceptions import GMTInvalidInput from .helpers import ( GMTTempFile, build_arg_string, @@ -88,11 +87,10 @@ def x2sys_cross(tracks=None, outfile=None, **kwargs): Parameters ---------- - tracks : pandas.DataFrame or str or list - Either a single pandas.DataFrame table with (x, y) or (lon, lat) values - in the first two columns, or a single filename (supported formats are - ASCII .txt, native binary, or COARDS netCDF 1-D .nc), or a combination - of tables and/or filenames in a list. More columns may also be present. + tracks : str or list + A table or a list of tables with (x, y) or (lon, lat) values in the + first two columns. Supported formats are ASCII, native binary, or + COARDS netCDF 1-D data. More columns may also be present. If the filenames are missing their file extension, we will append the suffix specified for this TAG. Track files will be searched for first @@ -133,7 +131,10 @@ def x2sys_cross(tracks=None, outfile=None, **kwargs): if kind == "file": file_contexts.append(dummy_context(track)) elif kind == "matrix": - file_contexts.append(lib.virtualfile_from_matrix(track.values)) + raise NotImplementedError(f"{type(track)} inputs are not supported yet") + # file_contexts.append(lib.virtualfile_from_matrix(track.values)) + else: + raise GMTInvalidInput(f"Unrecognized data type: {type(track)}") with GMTTempFile(suffix=".txt") as tmpfile: with contextlib.ExitStack() as stack: From 9a2b40b7eac21f2344af4eb91665bb7d8c5da378 Mon Sep 17 00:00:00 2001 From: Wei Ji Date: Sun, 26 Jul 2020 11:12:19 +1200 Subject: [PATCH 08/13] Complete long aliases for x2sys_init Aliased suffix (E), discontinuity (G), spacing (I), units (I), region (R), gap (W), distcalc (j) for x2sys_init. Also checked that passing a list into region (R), spacing (I), units (N), and gap (W) works properly. --- pygmt/helpers/decorators.py | 29 +++++++++++---- pygmt/tests/test_x2sys_cross.py | 2 +- pygmt/tests/test_x2sys_init.py | 57 ++++++++++++++++++++++++++++++ pygmt/x2sys.py | 62 +++++++++++++++++++++++++++++++-- 4 files changed, 140 insertions(+), 10 deletions(-) create mode 100644 pygmt/tests/test_x2sys_init.py diff --git a/pygmt/helpers/decorators.py b/pygmt/helpers/decorators.py index 0441137abf7..d6cae793237 100644 --- a/pygmt/helpers/decorators.py +++ b/pygmt/helpers/decorators.py @@ -40,16 +40,31 @@ verbose : str Select verbosity level [Default is w], which modulates the messages written to stderr. Choose among 7 levels of verbosity: - 'q' - Quiet, not even fatal error messages are produced; - 'e' - Error messages only; - 'w' - Warnings [Default]; - 't' - Timings (report runtimes for time-intensive algorithms); - 'i' - Informational messages (same as verbose=True); - 'c' - Compatibility warnings; - 'd' - Debugging messages.""", + + - **q** - Quiet, not even fatal error messages are produced + - **e** - Error messages only + - **w** - Warnings [Default] + - **t** - Timings (report runtimes for time-intensive algorthms); + - **i** - Informational messages (same as "verbose=True") + - **c** - Compatibility warnings + - **d** - Debugging messages""", "W": """\ pen : str Set pen attributes for lines or the outline of symbols.""", + "j": """\ + distcalc : str + ``e|f|g``. + Determine how spherical distances are calculated. + + - **e** - Ellipsoidal (or geodesic) mode + - **f** - Flat Earth mode + - **g** - Great circle distance [Default] + + All spherical distance calculations depend on the current ellipsoid + (PROJ_ELLIPSOID), the definition of the mean radius + (PROJ_MEAN_RADIUS), and the specification of latitude type + (PROJ_AUX_LATITUDE). Geodesic distance calculations is also + controlled by method (PROJ_GEODESIC).""", "n": """\ interpolation : str ``[b|c|l|n][+a][+bBC][+c][+tthreshold]`` diff --git a/pygmt/tests/test_x2sys_cross.py b/pygmt/tests/test_x2sys_cross.py index 4c1d149bd66..5a5dd8019fc 100644 --- a/pygmt/tests/test_x2sys_cross.py +++ b/pygmt/tests/test_x2sys_cross.py @@ -89,7 +89,7 @@ def test_x2sys_cross_input_dataframe_output_dataframe(mock_x2sys_home, tracks): # assert output.shape == (4, 12) # columns = list(output.columns) # assert columns[:6] == ["x", "y", "i_1", "i_2", "dist_1", "dist_2"] - # assert columns[6:] == ["head_1", "head_2", "vel_1", "vel_2", "z_X", "z_M"] + # assert columns[6:] == ["head_1","head_2","vel_1","vel_2","z_X","z_M"] # return output diff --git a/pygmt/tests/test_x2sys_init.py b/pygmt/tests/test_x2sys_init.py new file mode 100644 index 00000000000..a6c608e57e1 --- /dev/null +++ b/pygmt/tests/test_x2sys_init.py @@ -0,0 +1,57 @@ +# pylint: disable=unused-argument +""" +Tests for x2sys_init +""" +import os +from tempfile import TemporaryDirectory + +import pytest + +from .. import x2sys_init + + +@pytest.fixture(name="mock_x2sys_home") +def fixture_mock_x2sys_home(monkeypatch): + """ + Set the X2SYS_HOME environment variable to the current working directory + for the test session + """ + monkeypatch.setenv("X2SYS_HOME", os.getcwd()) + + +def test_x2sys_init_region_spacing(mock_x2sys_home): + """ + Test that x2sys_init's region (R) and spacing (I) sequence arguments accept + a list properly + """ + with TemporaryDirectory(prefix="X2SYS", dir=os.getcwd()) as tmpdir: + tag = os.path.basename(tmpdir) + x2sys_init( + tag=tag, fmtfile="xyz", force=True, region=[0, 10, 20, 30], spacing=[5, 5] + ) + + with open(os.path.join(tmpdir, f"{tag}.tag"), "r") as tagpath: + tail_line = tagpath.readlines()[-1] + assert "-R0/10/20/30" in tail_line + assert "-I5/5" in tail_line + + +def test_x2sys_init_units_gap(mock_x2sys_home): + """ + Test that x2sys_init's units (N) and gap (W) arguments accept a list + properly + """ + with TemporaryDirectory(prefix="X2SYS", dir=os.getcwd()) as tmpdir: + tag = os.path.basename(tmpdir) + x2sys_init( + tag=tag, + fmtfile="xyz", + force=True, + units=["de", "se"], + gap=["tseconds", "de"], + ) + + with open(os.path.join(tmpdir, f"{tag}.tag"), "r") as tagpath: + tail_line = tagpath.readlines()[-1] + assert "-Nse -Nde" in tail_line + assert "-Wtseconds -Wde" in tail_line diff --git a/pygmt/x2sys.py b/pygmt/x2sys.py index eec9d4b35c5..d79ff40ed63 100644 --- a/pygmt/x2sys.py +++ b/pygmt/x2sys.py @@ -13,12 +13,25 @@ data_kind, dummy_context, fmt_docstring, + kwargs_to_strings, use_alias, ) @fmt_docstring -@use_alias(D="fmtfile", F="force", V="verbose") +@use_alias( + D="fmtfile", + E="suffix", + F="force", + G="discontinuity", + I="spacing", + N="units", + R="region", + V="verbose", + W="gap", + j="distcalc", +) +@kwargs_to_strings(I="sequence", R="sequence") def x2sys_init(tag, **kwargs): """ Initialize a new x2sys track database. @@ -58,8 +71,54 @@ def x2sys_init(tag, **kwargs): - geo (for plain ASCII longitude, latitude files) - geoz (same, with one z-column). + suffix : str + Specifies the file extension (suffix) for these data files. If not + given we use the format definition file prefix as the suffix (see + *fmtfile*). + + discontinuity : str + ``d|g`` + Selects geographical coordinates. Append **d** for discontinuity at the + Dateline (makes longitude go from -180 to + 180) or **g** for + discontinuity at Greenwich (makes longitude go from 0 to 360 + [Default]). If not given we assume the data are Cartesian. + + spacing : str or list + ``dx[/dy]`` + x_inc [and optionally y_inc] is the grid spacing. Append **m** to + indicate minutes or **s** to indicate seconds for geographic data. + These spacings refer to the binning used in the track bin-index data + base. + + units : str or list + ``d|sunit``. + Sets the units used for distance and speed when requested by other + programs. Append **d** for distance or **s** for speed, then give the + desired unit as: + + - **c** - Cartesian userdist or userdist/usertime + - **e** - meters or m/s + - **f** - feet or feet/s + - **k** - km or kms/hr + - **m** - miles or miles/hr + - **n** - nautical miles or knots + - **u** - survey feet or survey feet/s + + Default is ``units=["dk", "se"]`` (km and m/s) if *discontinuity* is + set, and ``units=["dc", "sc"]`` otherwise (Cartesian units). + + {R} {V} + gap : str or list + ``t|dgap``. + Give **t** or **d** and append the corresponding maximum time gap (in + user units; this is typically seconds [Infinity]), or distance (for + units, see *units*) gap [Infinity]) allowed between the two data points + immediately on either side of a crossover. If these limits are exceeded + then a data gap is assumed and no COE will be determined. + + {j} """ with Session() as lib: arg_str = " ".join([tag, build_arg_string(kwargs)]) @@ -122,7 +181,6 @@ def x2sys_cross(tracks=None, outfile=None, **kwargs): - pandas.DataFrame table with (x, y, ..., etc) if outfile is not set - None if outfile is set (track output will be stored in outfile) - """ with Session() as lib: file_contexts = [] From 822e06c864636d4580e1f4e8880fb2f257ca66f0 Mon Sep 17 00:00:00 2001 From: Wei Ji Date: Sun, 26 Jul 2020 12:56:35 +1200 Subject: [PATCH 09/13] Complete most long aliases for x2sys_cross Aliased combitable (A), runtimes(C), interpolation (I), region (R), speed(S), numpoints (W) for x2sys_cross. Also checked that setting the region (R), interpolation (I) and numpoints (W) args work properly. --- pygmt/tests/test_x2sys_cross.py | 25 ++++++++++ pygmt/x2sys.py | 84 +++++++++++++++++++++++++++++++-- 2 files changed, 104 insertions(+), 5 deletions(-) diff --git a/pygmt/tests/test_x2sys_cross.py b/pygmt/tests/test_x2sys_cross.py index 5a5dd8019fc..e2b4507fcdd 100644 --- a/pygmt/tests/test_x2sys_cross.py +++ b/pygmt/tests/test_x2sys_cross.py @@ -6,6 +6,7 @@ from tempfile import TemporaryDirectory import numpy as np +import numpy.testing as npt import pandas as pd import pytest @@ -131,3 +132,27 @@ def test_x2sys_cross_invalid_tracks_input_type(tracks): assert data_kind(invalid_tracks) == "grid" with pytest.raises(GMTInvalidInput): x2sys_cross(tracks=[invalid_tracks]) + + +def test_x2sys_cross_region_interpolation_numpoints(mock_x2sys_home): + """ + Test that x2sys_cross's region (R), interpolation (l) and numpoints (W) + arguments work. + """ + with TemporaryDirectory(prefix="X2SYS", dir=os.getcwd()) as tmpdir: + tag = os.path.basename(tmpdir) + x2sys_init(tag=tag, fmtfile="xyz", force=True) + output = x2sys_cross( + tracks=["@tut_ship.xyz"], + tag=tag, + coe="i", + region=[245, 250, 20, 25], + interpolation="a", # Akima spline interpolation + numpoints=5, # Use up to 5 data points in interpolation + ) + + assert isinstance(output, pd.DataFrame) + assert output.shape == (3867, 12) + # Check crossover errors (z_X) and mean value of observables (z_M) + npt.assert_allclose(output.z_X.mean(), -139.196212) + npt.assert_allclose(output.z_M.mean(), -2890.465813) diff --git a/pygmt/x2sys.py b/pygmt/x2sys.py index d79ff40ed63..563c3a4427b 100644 --- a/pygmt/x2sys.py +++ b/pygmt/x2sys.py @@ -126,7 +126,20 @@ def x2sys_init(tag, **kwargs): @fmt_docstring -@use_alias(T="tag", Q="coe", V="verbose") +@use_alias( + A="combitable", + C="runtimes", + # D="", + I="interpolation", + R="region", + S="speed", + T="tag", + Q="coe", + V="verbose", + W="numpoints", + # Z="", +) +@kwargs_to_strings(R="sequence") def x2sys_cross(tracks=None, outfile=None, **kwargs): """ Calculate crossovers between track data files. @@ -135,10 +148,9 @@ def x2sys_cross(tracks=None, outfile=None, **kwargs): cross-overs") or within ("internal cross-overs") tracks (Cartesian or geographic), and report the time, position, distance along track, heading and speed along each track segment, and the crossover error (COE) and mean - values for all observables. The names of the tracks are passed on the - command line. By default, x2sys_cross will look for both external and - internal COEs. As an option, you may choose to project all data using one - of the map-projections prior to calculating the COE. + values for all observables. By default, x2sys_cross will look for both + external and internal COEs. As an option, you may choose to project all + data using one of the map-projections prior to calculating the COE. Full option list at :gmt-docs:`supplements/x2sys/x2sys_cross.html` @@ -167,12 +179,74 @@ def x2sys_cross(tracks=None, outfile=None, **kwargs): Specify the x2sys TAG which identifies the attributes of this data type. + combitable : str + Only process the pair-combinations found in the file *combitable* + [Default process all possible combinations among the specified files]. + The file *combitable* is created by *x2sys_get*'s -L option + + runtimes : bool or str + Compute and append the processing run-time for each pair to the + progress message (use ``runtimes=True``). Pass in a filename (e.g. + ``runtimes="file.txt"``) to save these run-times to file. The idea here + is to use the knowledge of run-times to split the main process in a + number of sub-processes that can each be launched in a different + processor of your multi-core machine. See the MATLAB function + split_file4coes.m that lives in the x2sys supplement source code. + + D : bool or str + ``S|N``. + Control how geographic coordinates are handled (Cartesian data are + unaffected). By default, we determine if the data are closer to one + pole than the other, and then we use a cylindrical polar conversion to + avoid problems with longitude jumps. You can turn this off entirely + with -D and then the calculations uses the original data (we have + protections against longitude jumps). However, you can force the + selection of the pole for the projection by appending **S** or **N** + for the south or north pole, respectively. The conversion is used + because the algorithm used to find crossovers is inherently a + Cartesian algorithm that can run into trouble with data that has large + longitudinal range at higher latitudes. + + interpolation : str + ``l|a|c``. + Sets the interpolation mode for estimating values at the crossover. + Choose among: + + - **l** - Linear interpolation [Default]. + - **a** - Akima spline interpolation. + - **c** - Cubic spline interpolation. + coe : str Use **e** for external COEs only, and **i** for internal COEs only [Default is all COEs]. + {R} + + speed : str or list + ``l|u|hspeed``. + Defines window of track speeds. If speeds are outside this window we do + not calculate a COE. Specify: + + - **l** sets lower speed [Default is 0]. + - **u** sets upper speed [Default is Infinity]. + - **h** does not limit the speed but sets a lower speed below which \ + headings will not be computed (i.e., set to NaN) [Default calculates \ + headings regardless of speed]. + + For example, you can use ``speed=["l0", "u10", "h5"] to set a lower + speed of 0, upper speed of 10, and disable heading calculations for + speeds below 5. + {V} + numpoints : int + Give the maximum number of data points on either side of the crossover + to use in the spline interpolation [Default is 3]. + + Z : bool + Report the values of each track at the crossover [Default reports the + crossover value and the mean value]. + Returns ------- crossover_errors : pandas.DataFrame or None From 90776ddb91890575c684e4f55b0d9bd980541f99 Mon Sep 17 00:00:00 2001 From: Wei Ji Date: Sun, 26 Jul 2020 13:42:13 +1200 Subject: [PATCH 10/13] Fix slightly different z_X.mean() values on different platforms On Linux, z_X.mean() = -139.196212, On macOS/Windows, z_X.mean() = -139.20221. --- pygmt/tests/test_x2sys_cross.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pygmt/tests/test_x2sys_cross.py b/pygmt/tests/test_x2sys_cross.py index e2b4507fcdd..3eedfd4b6f0 100644 --- a/pygmt/tests/test_x2sys_cross.py +++ b/pygmt/tests/test_x2sys_cross.py @@ -154,5 +154,5 @@ def test_x2sys_cross_region_interpolation_numpoints(mock_x2sys_home): assert isinstance(output, pd.DataFrame) assert output.shape == (3867, 12) # Check crossover errors (z_X) and mean value of observables (z_M) - npt.assert_allclose(output.z_X.mean(), -139.196212) + npt.assert_allclose(output.z_X.mean(), -139.2, rtol=1e-4) npt.assert_allclose(output.z_M.mean(), -2890.465813) From f61e289fe21aee25e8ffc071f3d6e10e19557bb1 Mon Sep 17 00:00:00 2001 From: Wei Ji Date: Mon, 27 Jul 2020 13:38:03 +1200 Subject: [PATCH 11/13] Parse datetimes on the 3rd and 4th columns --- pygmt/tests/test_x2sys_cross.py | 4 +++- pygmt/x2sys.py | 9 +++++++-- 2 files changed, 10 insertions(+), 3 deletions(-) diff --git a/pygmt/tests/test_x2sys_cross.py b/pygmt/tests/test_x2sys_cross.py index 3eedfd4b6f0..a0c702dbcee 100644 --- a/pygmt/tests/test_x2sys_cross.py +++ b/pygmt/tests/test_x2sys_cross.py @@ -89,8 +89,10 @@ def test_x2sys_cross_input_dataframe_output_dataframe(mock_x2sys_home, tracks): # assert isinstance(output, pd.DataFrame) # assert output.shape == (4, 12) # columns = list(output.columns) - # assert columns[:6] == ["x", "y", "i_1", "i_2", "dist_1", "dist_2"] + # assert columns[:6] == ["x", "y", "t_1", "t_2", "dist_1", "dist_2"] # assert columns[6:] == ["head_1","head_2","vel_1","vel_2","z_X","z_M"] + # assert output.dtypes["t_1"].type == np.datetime64 + # assert output.dtypes["t_2"].type == np.datetime64 # return output diff --git a/pygmt/x2sys.py b/pygmt/x2sys.py index 563c3a4427b..4b57ec937bf 100644 --- a/pygmt/x2sys.py +++ b/pygmt/x2sys.py @@ -279,8 +279,13 @@ def x2sys_cross(tracks=None, outfile=None, **kwargs): # Read temporary csv output to a pandas table if outfile == tmpfile.name: # if outfile isn't set, return pd.DataFrame # Read the tab-separated ASCII table - # Header is on 2nd row, and we skip the 3rd row with a ">" - df = pd.read_csv(tmpfile.name, sep="\t", header=2, comment=">") + df = pd.read_csv( + tmpfile.name, + sep="\t", + header=2, # Column names are on 2nd row + comment=">", # Skip the 3rd row with a ">" + parse_dates=[2, 3], # Datetimes on 3rd and 4th column + ) # Remove the "# " from "# x" in the first column result = df.rename(columns={df.columns[0]: df.columns[0][2:]}) elif outfile != tmpfile.name: # if outfile is set, output in outfile only From 1803c98b932d3c4293067a76c4a589ca4c36038b Mon Sep 17 00:00:00 2001 From: Wei Ji Date: Mon, 27 Jul 2020 15:21:23 +1200 Subject: [PATCH 12/13] Alias override (D) and trackvalues (Z) for x2sys_cross Also checked that using trackvalues (Z) outputs z_1 and z_2 columns instead of z_X and z_M. --- pygmt/tests/test_x2sys_cross.py | 16 ++++++++++++++++ pygmt/x2sys.py | 12 ++++++------ 2 files changed, 22 insertions(+), 6 deletions(-) diff --git a/pygmt/tests/test_x2sys_cross.py b/pygmt/tests/test_x2sys_cross.py index a0c702dbcee..c33f2085084 100644 --- a/pygmt/tests/test_x2sys_cross.py +++ b/pygmt/tests/test_x2sys_cross.py @@ -158,3 +158,19 @@ def test_x2sys_cross_region_interpolation_numpoints(mock_x2sys_home): # Check crossover errors (z_X) and mean value of observables (z_M) npt.assert_allclose(output.z_X.mean(), -139.2, rtol=1e-4) npt.assert_allclose(output.z_M.mean(), -2890.465813) + + +def test_x2sys_cross_trackvalues(mock_x2sys_home): + """ + Test that x2sys_cross's trackvalues (Z) argument work. + """ + with TemporaryDirectory(prefix="X2SYS", dir=os.getcwd()) as tmpdir: + tag = os.path.basename(tmpdir) + x2sys_init(tag=tag, fmtfile="xyz", force=True) + output = x2sys_cross(tracks=["@tut_ship.xyz"], tag=tag, trackvalues=True) + + assert isinstance(output, pd.DataFrame) + assert output.shape == (14294, 12) + # Check mean of track 1 values (z_1) and track 2 values (z_2) + npt.assert_allclose(output.z_1.mean(), -2420.569767) + npt.assert_allclose(output.z_2.mean(), -2400.357549) diff --git a/pygmt/x2sys.py b/pygmt/x2sys.py index 4b57ec937bf..7a0716fa7a8 100644 --- a/pygmt/x2sys.py +++ b/pygmt/x2sys.py @@ -129,7 +129,7 @@ def x2sys_init(tag, **kwargs): @use_alias( A="combitable", C="runtimes", - # D="", + D="override", I="interpolation", R="region", S="speed", @@ -137,7 +137,7 @@ def x2sys_init(tag, **kwargs): Q="coe", V="verbose", W="numpoints", - # Z="", + Z="trackvalues", ) @kwargs_to_strings(R="sequence") def x2sys_cross(tracks=None, outfile=None, **kwargs): @@ -193,14 +193,14 @@ def x2sys_cross(tracks=None, outfile=None, **kwargs): processor of your multi-core machine. See the MATLAB function split_file4coes.m that lives in the x2sys supplement source code. - D : bool or str + override : bool or str ``S|N``. Control how geographic coordinates are handled (Cartesian data are unaffected). By default, we determine if the data are closer to one pole than the other, and then we use a cylindrical polar conversion to avoid problems with longitude jumps. You can turn this off entirely - with -D and then the calculations uses the original data (we have - protections against longitude jumps). However, you can force the + with *override* and then the calculations uses the original data (we + have protections against longitude jumps). However, you can force the selection of the pole for the projection by appending **S** or **N** for the south or north pole, respectively. The conversion is used because the algorithm used to find crossovers is inherently a @@ -243,7 +243,7 @@ def x2sys_cross(tracks=None, outfile=None, **kwargs): Give the maximum number of data points on either side of the crossover to use in the spline interpolation [Default is 3]. - Z : bool + trackvalues : bool Report the values of each track at the crossover [Default reports the crossover value and the mean value]. From e7f42fd161b0b6d6c3789adc90fde29e69109028 Mon Sep 17 00:00:00 2001 From: Wei Ji Date: Mon, 27 Jul 2020 22:40:42 +1200 Subject: [PATCH 13/13] Rename df to dataframe or table to stop pylint complaining --- .pylintrc | 1 - pygmt/tests/test_x2sys_cross.py | 4 ++-- pygmt/x2sys.py | 4 ++-- 3 files changed, 4 insertions(+), 5 deletions(-) diff --git a/.pylintrc b/.pylintrc index 6b5fa3de710..66b76a52b7c 100644 --- a/.pylintrc +++ b/.pylintrc @@ -441,7 +441,6 @@ function-naming-style=snake_case good-names=i, j, k, - df, ex, Run, _, diff --git a/pygmt/tests/test_x2sys_cross.py b/pygmt/tests/test_x2sys_cross.py index c33f2085084..23bcc499228 100644 --- a/pygmt/tests/test_x2sys_cross.py +++ b/pygmt/tests/test_x2sys_cross.py @@ -30,8 +30,8 @@ def fixture_tracks(): """ Load track data from the sample bathymetry file """ - df = load_sample_bathymetry() - return [df.query(expr="bathymetry > -20")] # reduce size of dataset + dataframe = load_sample_bathymetry() + return [dataframe.query(expr="bathymetry > -20")] # reduce size of dataset def test_x2sys_cross_input_file_output_file(mock_x2sys_home): diff --git a/pygmt/x2sys.py b/pygmt/x2sys.py index 7a0716fa7a8..4f55b23f3dc 100644 --- a/pygmt/x2sys.py +++ b/pygmt/x2sys.py @@ -279,7 +279,7 @@ def x2sys_cross(tracks=None, outfile=None, **kwargs): # Read temporary csv output to a pandas table if outfile == tmpfile.name: # if outfile isn't set, return pd.DataFrame # Read the tab-separated ASCII table - df = pd.read_csv( + table = pd.read_csv( tmpfile.name, sep="\t", header=2, # Column names are on 2nd row @@ -287,7 +287,7 @@ def x2sys_cross(tracks=None, outfile=None, **kwargs): parse_dates=[2, 3], # Datetimes on 3rd and 4th column ) # Remove the "# " from "# x" in the first column - result = df.rename(columns={df.columns[0]: df.columns[0][2:]}) + result = table.rename(columns={table.columns[0]: table.columns[0][2:]}) elif outfile != tmpfile.name: # if outfile is set, output in outfile only result = None