Skip to content
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

Add barbs plot #3710

Merged
merged 18 commits into from
Aug 10, 2021
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
62 changes: 62 additions & 0 deletions docs/gallery_code/meteorology/plot_wind_barbs.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,62 @@
"""
Plotting Wind Direction Using Barbs
===================================

This example demonstrates using barbs to plot wind speed contours and wind
direction barbs from wind vector component input data. The vector components
are co-located in space in this case.

The magnitude of the wind in the original data is low and so doesn't illustrate
the full range of barbs. The wind is scaled to simulate a storm that better
illustrates the range of barbs that are available.
"""

import matplotlib.pyplot as plt

import iris
import iris.plot as iplt
import iris.quickplot as qplt


def main():
# Load the u and v components of wind from a pp file
infile = iris.sample_data_path("wind_speed_lake_victoria.pp")

uwind = iris.load_cube(infile, "x_wind")
vwind = iris.load_cube(infile, "y_wind")

uwind.convert_units("knot")
vwind.convert_units("knot")

# To illustrate the full range of barbs, scale the wind speed up to pretend
# that a storm is passing over
magnitude = (uwind ** 2 + vwind ** 2) ** 0.5
magnitude.convert_units("knot")
max_speed = magnitude.collapsed(
("latitude", "longitude"), iris.analysis.MAX
).data
max_desired = 65

uwind = uwind / max_speed * max_desired
vwind = vwind / max_speed * max_desired

# Create a cube containing the wind speed
windspeed = (uwind ** 2 + vwind ** 2) ** 0.5
windspeed.rename("windspeed")
windspeed.convert_units("knot")

plt.figure()

# Plot the wind speed as a contour plot
qplt.contourf(windspeed)

# Add wind barbs except for the outermost values which overhang the edge
# of the plot if left
iplt.barbs(uwind[1:-1, 1:-1], vwind[1:-1, 1:-1], pivot="middle", length=6)

plt.title("Wind speed during a simulated storm")
qplt.show()


if __name__ == "__main__":
main()
30 changes: 30 additions & 0 deletions docs/gallery_tests/test_plot_wind_barbs.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,30 @@
# Copyright Iris contributors
#
# This file is part of Iris and is released under the LGPL license.
# See COPYING and COPYING.LESSER in the root of the repository for full
# licensing details.

# Import Iris tests first so that some things can be initialised before
# importing anything else.
import iris.tests as tests # isort:skip

from .gallerytest_util import (
add_gallery_to_path,
fail_any_deprecation_warnings,
show_replaced_by_check_graphic,
)


class TestWindBarbs(tests.GraphicsTest):
"""Test the wind_barbs example code."""

def test_wind_barbs(self):
with fail_any_deprecation_warnings():
with add_gallery_to_path():
import plot_wind_barbs
with show_replaced_by_check_graphic(self):
plot_wind_barbs.main()


if __name__ == "__main__":
tests.main()
3 changes: 3 additions & 0 deletions docs/src/whatsnew/latest.rst
Original file line number Diff line number Diff line change
Expand Up @@ -42,6 +42,9 @@ This document explains the changes made to Iris for this release
the primary coordinate being plotted against is a vertical coordinate. E.g.
``iris.plot.plot(z_cube)`` will produce a z-vs-phenomenon plot, where before
it would have produced a phenomenon-vs-z plot. (:pull:`3906`)
#. `@jonseddon`_ added :meth:`iris.plot.barbs` to provide a convenient way to
use :func:`matplotlib.pyplot.barbs` with Iris cubes. A gallery example was
included to illustrate the new method's use. (:pull:`3710`)

#. `@bjlittle`_ introduced :func:`iris.common.metadata.hexdigest` to the
public API. Previously it was a private function introduced in ``v3.0.0``.
Expand Down
53 changes: 51 additions & 2 deletions lib/iris/plot.py
Original file line number Diff line number Diff line change
Expand Up @@ -1418,6 +1418,55 @@ def _vector_component_args(x_points, y_points, u_data, *args, **kwargs):
return ((x_points, y_points, u_data, v_data), kwargs)


def barbs(u_cube, v_cube, *args, **kwargs):
"""
Draws a barb plot from two vector component cubes. Triangles, full-lines
and half-lines represent increments of 50, 10 and 5 respectively.

Args:

* u_cube, v_cube : (:class:`~iris.cube.Cube`)
u and v vector components. Must have same shape and units.
If the cubes have geographic coordinates, the values are treated as
true distance differentials, e.g. windspeeds, and *not* map coordinate
vectors. The components are aligned with the North and East of the
cube coordinate system.

.. Note::

At present, if u_cube and v_cube have geographic coordinates, then they
must be in a lat-lon coordinate system, though it may be a rotated one.
To transform wind values between coordinate systems, use
:func:`iris.analysis.cartography.rotate_grid_vectors`.
To transform coordinate grid points, you will need to create
2-dimensional arrays of x and y values. These can be transformed with
:meth:`cartopy.crs.CRS.transform_points`.

Kwargs:

* coords: (list of :class:`~iris.coords.Coord` or string)
Coordinates or coordinate names. Use the given coordinates as the axes
for the plot. The order of the given coordinates indicates which axis
to use for each, where the first element is the horizontal
axis of the plot and the second element is the vertical axis
of the plot.

* axes: the :class:`matplotlib.axes.Axes` to use for drawing.
Defaults to the current axes if none provided.

See :func:`matplotlib.pyplot.barbs` for details of other valid
keyword arguments.

"""
#
# TODO: check u + v cubes for compatibility.
#
kwargs["_v_data"] = v_cube.data
return _draw_2d_from_points(
"barbs", _vector_component_args, u_cube, *args, **kwargs
)


def quiver(u_cube, v_cube, *args, **kwargs):
"""
Draws an arrow plot from two vector component cubes.
Expand All @@ -1431,12 +1480,12 @@ def quiver(u_cube, v_cube, *args, **kwargs):
vectors. The components are aligned with the North and East of the
cube coordinate system.

.. Note:
.. Note::

At present, if u_cube and v_cube have geographic coordinates, then they
must be in a lat-lon coordinate system, though it may be a rotated one.
To transform wind values between coordinate systems, use
:func:`iris.analysis.cartography.rotate_vectors`.
:func:`iris.analysis.cartography.rotate_grid_vectors`.
To transform coordinate grid points, you will need to create
2-dimensional arrays of x and y values. These can be transformed with
:meth:`cartopy.crs.CRS.transform_points`.
Expand Down
36 changes: 32 additions & 4 deletions lib/iris/tests/integration/plot/test_vector_plots.py
Original file line number Diff line number Diff line change
Expand Up @@ -24,16 +24,16 @@
if tests.MPL_AVAILABLE:
import matplotlib.pyplot as plt

from iris.plot import quiver
from iris.plot import barbs, quiver


@tests.skip_plot
class MixinVectorPlotCases:
"""
Test examples mixin, used by separate quiver + streamplot classes.
Test examples mixin, used by separate barb, quiver + streamplot classes.

NOTE: at present for quiver only, as streamplot does not support arbitrary
coordinates.
NOTE: at present for barb and quiver only, as streamplot does not support
arbitrary coordinates.

"""

Expand Down Expand Up @@ -193,6 +193,34 @@ def test_circular_longitude(self):
self.plot("circular", u_cube, v_cube, coords=("longitude", "latitude"))


class TestBarbs(MixinVectorPlotCases, tests.GraphicsTest):
def setUp(self):
super().setUp()

@staticmethod
def _nonlatlon_xyuv():
# Increase the range of wind speeds used in the barbs test to test more
# barbs shapes than just circles
x, y, u, v = MixinVectorPlotCases._nonlatlon_xyuv()
scale_factor = 50
u *= scale_factor
v *= scale_factor
return x, y, u, v

@staticmethod
def _latlon_uv_cubes(grid_cube):
# Increase the range of wind speeds used in the barbs test to test all
# barbs shapes
u_cube, v_cube = MixinVectorPlotCases._latlon_uv_cubes(grid_cube)
scale_factor = 30
u_cube.data *= scale_factor
v_cube.data *= scale_factor
return u_cube, v_cube

def plot_function_to_test(self):
return barbs


class TestQuiver(MixinVectorPlotCases, tests.GraphicsTest):
def setUp(self):
super().setUp()
Expand Down
19 changes: 19 additions & 0 deletions lib/iris/tests/results/imagerepo.json
Original file line number Diff line number Diff line change
Expand Up @@ -143,6 +143,10 @@
"https://scitools.github.io/test-iris-imagehash/images/v4/fa8172d0847ecd2bc913939c36846c714933799cc3cc8727e67639f939996a58.png",
"https://scitools.github.io/test-iris-imagehash/images/v4/fa8172c6857ecd38cb3392ce36c564311931d85ec64e9787719a39993c316e66.png"
],
"gallery_tests.test_plot_wind_barbs.TestWindBarbs.test_wind_barbs.0": [
"https://scitools.github.io/test-iris-imagehash/images/v4/e9e960e996169316c1fe9e96c29e36739e13c07c3d61c07f39a13921c07f3e21.png",
"https://scitools.github.io/test-iris-imagehash/images/v4/e9e161e996169316c1fe9e96c29e36739e13c07c3d61c07f39813929c07f3f01.png"
],
"gallery_tests.test_plot_wind_speed.TestWindSpeed.test_plot_wind_speed.0": [
"https://scitools.github.io/test-iris-imagehash/images/v4/bcf924fb9306930ce12ccf97c73236b28ecec4cd3e29847b18e639e6c14f1a09.png",
"https://scitools.github.io/test-iris-imagehash/images/v4/e9e960e996169306c1fe9e96c29e36739e13c06c3d61c07f39a139e1c07f3f01.png"
Expand Down Expand Up @@ -173,6 +177,21 @@
"iris.tests.integration.plot.test_plot_2d_coords.Test2dContour.test_2d_coords_contour.0": [
"https://scitools.github.io/test-iris-imagehash/images/v4/b4b2643ecb05cb43b0f23d80c53c4e1d3e5990eb1f81c19f2f983cb1c4ff3e42.png"
],
"iris.tests.integration.plot.test_vector_plots.TestBarbs.test_2d_plain_latlon.0": [
"https://scitools.github.io/test-iris-imagehash/images/v4/eb036726c47c9273918e6e2c6f216336787590eb969a165890ee6c676925b3b3.png"
],
"iris.tests.integration.plot.test_vector_plots.TestBarbs.test_2d_plain_latlon_on_polar_map.0": [
"https://scitools.github.io/test-iris-imagehash/images/v4/e66d673c999031cd6667663398dc332c676364e798959336636660d933998666.png"
],
"iris.tests.integration.plot.test_vector_plots.TestBarbs.test_2d_rotated_latlon.0": [
"https://scitools.github.io/test-iris-imagehash/images/v4/eba037a4c479c273b2963f2c6f6126966865d86f969e33c9b1706c26692793b0.png"
],
"iris.tests.integration.plot.test_vector_plots.TestBarbs.test_non_latlon_1d_coords.0": [
"https://scitools.github.io/test-iris-imagehash/images/v4/a7ac334934d2e65c72596325b343338cb41c92d9c5b36f65330d379692ca6d6c.png"
],
"iris.tests.integration.plot.test_vector_plots.TestBarbs.test_non_latlon_2d_coords.0": [
"https://scitools.github.io/test-iris-imagehash/images/v4/a7acb36134d2e676627963259343330cb43e92d9c5336e67330d379292ca6d6c.png"
],
"iris.tests.integration.plot.test_vector_plots.TestQuiver.test_2d_plain_latlon.0": [
"https://scitools.github.io/test-iris-imagehash/images/v4/fb8d4f21c472b27e919d2e216f216b3178e69c7e961ab39a84696c616d245b94.png"
],
Expand Down