Skip to content

Added basic contour functionality #212

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 15 commits into from
Aug 17, 2018
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions doc/api/index.rst
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,7 @@ Plotting data and laying out the map:
Figure.basemap
Figure.coast
Figure.plot
Figure.contour
Figure.grdimage
Figure.logo

Expand Down
77 changes: 77 additions & 0 deletions gmt/base_plotting.py
Original file line number Diff line number Diff line change
Expand Up @@ -270,6 +270,83 @@ def plot(self, x=None, y=None, data=None, sizes=None, direction=None, **kwargs):
arg_str = " ".join([fname, build_arg_string(kwargs)])
lib.call_module("plot", arg_str)

@fmt_docstring
@use_alias(
R="region",
J="projection",
B="frame",
S="skip",
G="label_placement",
W="pen",
L="triangular_mesh_pen",
i="columns",
C="levels",
)
@kwargs_to_strings(R="sequence", i="sequence_comma")
def contour(self, x=None, y=None, z=None, data=None, **kwargs):
"""
Contour table data by direct triangulation.

Takes a matrix, (x,y,z) pairs, or a file name as input and plots lines,
polygons, or symbols at those locations on a map.

Must provide either *data* or *x*, *y*, and *z*.

[TODO: Insert more documentation]

{gmt_module_docs}

{aliases}

Parameters
----------
x, y, z : 1d arrays
Arrays of x and y coordinates and values z of the data points.
data : str or 2d array
Either a data file name or a 2d numpy array with the tabular data.
{J}
{R}
A : bool or str
``'[m|p|x|y]'``
By default, geographic line segments are drawn as great circle
arcs. To draw them as straight lines, use *A*.
{B}
C : Contour file or level(s)
D : Dump contour coordinates
E : Network information
G : Placement of labels
I : Color the triangles using CPT
L : Pen to draw the underlying triangulation (default none)
N : Do not clip contours
Q : Minimum contour length
``'[p|t]'``
S : Skip input points outside region
``'[p|t]'``
{W}
X : Origin shift x
Y : Origin shift y


"""
kwargs = self._preprocess(**kwargs)

kind = data_kind(data, x, y, z)
if kind == "vectors" and z is None:
raise GMTInvalidInput("Must provided both x, y, and z.")

with Session() as lib:
# Choose how data will be passed in to the module
if kind == "file":
file_context = dummy_context(data)
elif kind == "matrix":
file_context = lib.virtualfile_from_matrix(data)
elif kind == "vectors":
file_context = lib.virtualfile_from_vectors(x, y, z)

with file_context as fname:
arg_str = " ".join([fname, build_arg_string(kwargs)])
lib.call_module("contour", arg_str)

@fmt_docstring
@use_alias(R="region", J="projection", B="frame")
@kwargs_to_strings(R="sequence")
Expand Down
9 changes: 6 additions & 3 deletions gmt/helpers/utils.py
Original file line number Diff line number Diff line change
Expand Up @@ -12,15 +12,15 @@
from ..exceptions import GMTInvalidInput


def data_kind(data, x=None, y=None):
def data_kind(data, x=None, y=None, z=None):
"""
Check what kind of data is provided to a module.

Possible types:

* a file name provided as 'data'
* a matrix provided as 'data'
* 1D arrays x and y
* 1D arrays x and y (and z, optionally)

Arguments should be ``None`` if not used. If doesn't fit any of these
categories (or fits more than one), will raise an exception.
Expand All @@ -31,6 +31,9 @@ def data_kind(data, x=None, y=None):
Data file name or numpy array.
x, y : 1d arrays or None
x and y columns as numpy arrays.
z : 1d array or None
z column as numpy array. To be used optionally when x and y
are given.

Returns
-------
Expand Down Expand Up @@ -67,7 +70,7 @@ def data_kind(data, x=None, y=None):
"""
if data is None and x is None and y is None:
raise GMTInvalidInput("No input data provided.")
if data is not None and (x is not None or y is not None):
if data is not None and (x is not None or y is not None or z is not None):
raise GMTInvalidInput("Too much data. Use either data or x and y.")
if data is None and (x is None or y is None):
raise GMTInvalidInput("Must provided both x and y.")
Expand Down
Binary file added gmt/tests/baseline/test_contour_from_file.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file added gmt/tests/baseline/test_contour_matrix.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file added gmt/tests/baseline/test_contour_vec.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
99 changes: 99 additions & 0 deletions gmt/tests/test_contour.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,99 @@
# pylint: disable=redefined-outer-name
"""
Tests contour.
"""
import os
from itertools import product

import pytest
import numpy as np

from .. import Figure
from ..exceptions import GMTInvalidInput


TEST_DATA_DIR = os.path.join(os.path.dirname(__file__), "data")
POINTS_DATA = os.path.join(TEST_DATA_DIR, "points.txt")


@pytest.fixture(scope="module")
def data():
"Load the point data from the test file"
return np.loadtxt(POINTS_DATA)


@pytest.fixture(scope="module")
def region():
"The data region"
return [10, 70, -5, 10]


def test_contour_fail_no_data(data):
"Should raise an exception if no data is given"
# Contour should raise an exception if no or not sufficient data
# is given
fig = Figure()
# Test all combinations where at least one data variable
# is not given:
for variable in product([None, data[:, 0]], repeat=3):
# Filter one valid configuration:
if not any(item is None for item in variable):
continue
with pytest.raises(GMTInvalidInput):
fig.contour(
x=variable[0],
y=variable[1],
z=variable[2],
region=region,
projection="X4i",
color="red",
frame="afg",
pen="",
)
# Should also fail if given too much data
with pytest.raises(GMTInvalidInput):
fig.contour(
x=data[:, 0],
y=data[:, 1],
z=data[:, 2],
data=data,
region=region,
projection="X4i",
style="c0.2c",
color="red",
frame="afg",
pen="",
)


@pytest.mark.mpl_image_compare
def test_contour_vec(region):
"Plot an x-centered gaussian kernel with different y scale"
fig = Figure()
x, y = np.meshgrid(
np.linspace(region[0], region[1]), np.linspace(region[2], region[3])
)
x = x.flatten()
y = y.flatten()
z = (x - 0.5 * (region[0] + region[1])) ** 2 + 4 * y ** 2
z = np.exp(-z / 10 ** 2 * np.log(2))
fig.contour(x=x, y=y, z=z, projection="X4i", region=region, frame="a", pen="")
return fig


@pytest.mark.mpl_image_compare
def test_contour_matrix(data, region):
"Plot data"
fig = Figure()
fig.contour(data=data, projection="X3i", region=region, frame="ag", pen="")
return fig


@pytest.mark.mpl_image_compare
def test_contour_from_file(region):
"Plot using the data file name instead of loaded data"
fig = Figure()
fig.contour(
data=POINTS_DATA, projection="X4i", region=region, frame="af", pen="#ffcb87"
)
return fig