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

New PlotSurfaceAnalysis class to plot WPC surface analysis bulletins #3580

Merged
merged 15 commits into from
Aug 28, 2024
282 changes: 282 additions & 0 deletions src/metpy/plots/declarative.py
Original file line number Diff line number Diff line change
Expand Up @@ -20,8 +20,9 @@

from . import ctables, wx_symbols
from ._mpl import TextCollection
from .patheffects import (ColdFront, OccludedFront, WarmFront, StationaryFront)

Check failure on line 23 in src/metpy/plots/declarative.py

View workflow job for this annotation

GitHub Actions / Flake8

[flake8] reported by reviewdog 🐶 I001 isort found an import in the wrong position Raw Output: ./src/metpy/plots/declarative.py:23:1: I001 isort found an import in the wrong position
from .cartopy_utils import import_cartopy
from .station_plot import StationPlot

Check failure on line 25 in src/metpy/plots/declarative.py

View workflow job for this annotation

GitHub Actions / Flake8

[flake8] reported by reviewdog 🐶 I005 isort found an unexpected missing import Raw Output: ./src/metpy/plots/declarative.py:25:1: I005 isort found an unexpected missing import
from ..calc import reduce_point_density, smooth_n_point, zoom_xarray
from ..package_tools import Exporter
from ..units import units
Expand Down Expand Up @@ -1986,3 +1987,284 @@

# Finally, draw the label
self._draw_label(label, lon, lat, fontcolor, fontoutline, offset)

@exporter.export

Check failure on line 1991 in src/metpy/plots/declarative.py

View workflow job for this annotation

GitHub Actions / Flake8

[flake8] reported by reviewdog 🐶 E302 expected 2 blank lines, found 1 Raw Output: ./src/metpy/plots/declarative.py:1991:1: E302 expected 2 blank lines, found 1
class PlotSurfaceAnalysis(MetPyHasTraits):
"""Plot Surface Analysis Features.

This class visualizes Surface Analysis features, including the parsed WPC Surface
Analysis bulletins processed by the `parse_wpc_surface_bulletin()` function.
"""

parent = Instance(Panel)
_need_redraw = Bool(default_value=True)

geometry = Instance(collections.abc.Iterable, allow_none=False)
geometry.__doc__ = """A collection of Shapely objects to plot.

A collection of Shapely objects, such as the 'geometry' column from a bulletin parsed
with `parse_wpc_surface_bulletin()`. Acceptable Shapely objects are
``shapely.LineString``, and ``shapely.Point``.
"""

feature = Union([Instance(collections.abc.Iterable), Unicode()],
allow_none=False)
feature.__doc__ = """Collection of names of features to be plotted.

Collection of strings, each corresponding one-to-one with geometries, such as the
'features' column from a bulletin parsed with `parse_wpc_surface_bulletin()`.
Acceptable feature names include: 'HIGH', 'LOW', 'WARM', 'COLD', 'OCFNT', 'STNRY', 'TROF'.
"""

strength = Union([Instance(collections.abc.Iterable), Float()], default_value=[],
allow_none=True)
strength.__doc__ = """Collection of strengths corresponding to pressure systems.

Collection of floats, each corresponding one-to-one with pressure system features. Such
as the 'strength' column from a bulletin parsed with `parse_wpc_surface_bulletin()`.
"""

HIGH_color = Union([Unicode()], default_value='blue', allow_none=True)
HIGH_color.__doc__ = """Color for plotting high-pressure systems.

A single string (color name or hex code) used to plot label of high-pressure system and
their strength, if provided. Default value is 'blue'.
"""

LOW_color = Union([Unicode()], default_value='red', allow_none=True)
LOW_color.__doc__ = """Color for plotting low-pressure systems.

A single string (color name or hex code) used to plot label of low-pressure system and
their strength, if provided. Default value is 'red'.
"""

WARM_color = Union([Unicode()], default_value='red', allow_none=True)
WARM_color.__doc__ = """Color for plotting warm fronts.

A single string (color name or hex code) used to plot warm fronts. Default
color is 'red', which is used by `WarmFront()` class. `WARM_color` alternates
with `COLD_color` to plot stationary fronts.
"""

COLD_color = Union([Unicode()], default_value='blue', allow_none=True)
COLD_color.__doc__ = """Color for plotting cold fronts.

A single string (color name or hex code) used to plot cold fronts. Default
color is 'blue', which is used by `ColdFront()` class. `COLD_color` alternates
with `WARM_color` to plot stationary fronts.
"""

OCFNT_color = Union([Unicode()], default_value='purple', allow_none=True)
OCFNT_color.__doc__ = """Color for plotting occluded fronts.

A single string (color name or hex code) used to plot Occluded fronts. Default
color is 'purple', which is used by `OccludedFront()` class.
"""

TROF_color = Union([Unicode()], default_value='darkorange', allow_none=True)
TROF_color.__doc__ = """Color for plotting trough lines.

Check failure on line 2065 in src/metpy/plots/declarative.py

View workflow job for this annotation

GitHub Actions / Flake8

[codespell] reported by reviewdog 🐶 trough ==> through Raw Output: ./src/metpy/plots/declarative.py:2065: trough ==> through

A single string (color name or hex code) used to plot trough lines. Default

Check failure on line 2067 in src/metpy/plots/declarative.py

View workflow job for this annotation

GitHub Actions / Flake8

[codespell] reported by reviewdog 🐶 trough ==> through Raw Output: ./src/metpy/plots/declarative.py:2067: trough ==> through
color is 'darkorange'.
"""

HIGH_label = Union([Unicode()], default_value='H', allow_none=True)
HIGH_label.__doc__ = """Label used to plot high-pressure systems.

Single string used as marker to plot high-pressure systems. Default value is 'H'.
"""

LOW_label = Union([Unicode()], default_value='L', allow_none=True)
LOW_label.__doc__ = """Label used to plot low-pressure systems.

Single string used as marker to plot low-pressure systems. Default value is 'L'.
"""

TROF_linestyle = Union([Unicode()], default_value='dashed',
allow_none=True)
TROF_linestyle.__doc__ = """Linestyle of Trough lines.

Check failure on line 2085 in src/metpy/plots/declarative.py

View workflow job for this annotation

GitHub Actions / Flake8

[codespell] reported by reviewdog 🐶 Trough ==> Through Raw Output: ./src/metpy/plots/declarative.py:2085: Trough ==> Through

Single string, default value is 'dashed'.
Accept matplotlib linestyles: 'solid', 'dotted', 'dashdot'.
"""

label_fontsize = Union([Int(), Float(), Unicode()], default_value=10, allow_none=True)
label_fontsize.__doc__ = """Font sizes of pressure systems labels.

Accepts size in points or relative size. Allowed relative sizes are those of Matplotlib:
'xx-small', 'x-small', 'small', 'medium', 'large', 'x-large', 'xx-large'.
"""

TROF_linewidth = Union([Float()], default_value=2,
allow_none=True)
TROF_linewidth.__doc__ = """Stroke width for trough lines.

Check failure on line 2100 in src/metpy/plots/declarative.py

View workflow job for this annotation

GitHub Actions / Flake8

[codespell] reported by reviewdog 🐶 trough ==> through Raw Output: ./src/metpy/plots/declarative.py:2100: trough ==> through

A single integer or floating point value representing the size of the stroke width.
"""

FRONT_linewidth = Union([Float()], default_value=1,
allow_none=True)
TROF_linewidth.__doc__ = """Stroke width for front lines.

A single floating point value representing the size of the stroke width.
"""

FRONT_markersize = Union([Int(), Float(), Unicode()], default_value=3, allow_none=True)
FRONT_markersize.__doc__ = """Size of symbols in front lines.

Accepts size in points or relative size. Default value is 3. Allowed relative sizes are
those of Matplotlib: 'xx-small', 'x-small', 'small', 'medium', 'large',
'x-large', 'xx-large'.
"""

strength_offset = Union([Tuple()], default_value=(0,-1), allow_none=True)
strength_offset.__doc__ = """Offset between label of pressure system and its
corresponding strength.

Tuple representing the relative position of strength value with respect to label of
pressure system. Default value is (0,-1). Scaled by multiplying times 80% of
label_fontsize value.
"""

def _effect_map(self):
return {

Check warning on line 2130 in src/metpy/plots/declarative.py

View check run for this annotation

Codecov / codecov/patch

src/metpy/plots/declarative.py#L2130

Added line #L2130 was not covered by tests
'WARM': [WarmFront(size=self.FRONT_markersize, color=self.WARM_color)],
'COLD': [ColdFront(size=self.FRONT_markersize, color=self.COLD_color)],
'OCFNT': [OccludedFront(size=self.FRONT_markersize, color=self.OCFNT_color)],
'STNRY': [StationaryFront(size=self.FRONT_markersize,
colors=(self.WARM_color, self.COLD_color))],
'TROF': None
}


def _color_map(self):

Check failure on line 2140 in src/metpy/plots/declarative.py

View workflow job for this annotation

GitHub Actions / Flake8

[flake8] reported by reviewdog 🐶 E303 too many blank lines (2) Raw Output: ./src/metpy/plots/declarative.py:2140:5: E303 too many blank lines (2)
return {

Check warning on line 2141 in src/metpy/plots/declarative.py

View check run for this annotation

Codecov / codecov/patch

src/metpy/plots/declarative.py#L2141

Added line #L2141 was not covered by tests
'HIGH' : self.HIGH_color,
'LOW' : self.LOW_color,
'TROF' : self.TROF_color
}

def _linewidth_map(self):
return {

Check warning on line 2148 in src/metpy/plots/declarative.py

View check run for this annotation

Codecov / codecov/patch

src/metpy/plots/declarative.py#L2148

Added line #L2148 was not covered by tests
'WARM': self.FRONT_linewidth,
'COLD': self.FRONT_linewidth,
'OCFNT': self.FRONT_linewidth,
'STNRY': self.FRONT_linewidth,
'TROF': self.TROF_linewidth
}

def _label_map(self):
return {

Check warning on line 2157 in src/metpy/plots/declarative.py

View check run for this annotation

Codecov / codecov/patch

src/metpy/plots/declarative.py#L2157

Added line #L2157 was not covered by tests
'HIGH' : self.HIGH_label,
'LOW' : self.LOW_label
}

@property
def name(self):
"""Generate a name for the plot."""
# Unlike Plots2D and PlotObs, there are no other attributes (such as 'fields' or
# 'levels') from which to name the plot. A generic name is returned here in case the
# user does not provide their own title, in which case MapPanel.draw() looks here.
return 'Surface Analysis Plot'

Check warning on line 2168 in src/metpy/plots/declarative.py

View check run for this annotation

Codecov / codecov/patch

src/metpy/plots/declarative.py#L2168

Added line #L2168 was not covered by tests

def _draw_strengths(self, text, lon, lat, color, offset=None):
"""Draw strengths in the plot.

Parameters
----------
text : str
The strength's value
lon : float
Longitude at which to position the label
lat : float
Latitude at which to position the label
color : str
Name or hex code for the color of the text
offset : tuple (default: (0, 0))
A tuple containing the x- and y-offset of the label, respectively
"""
import math
if offset is None:
offset = tuple(x * self.label_fontsize * 0.8 for x in self.strength_offset)

Check warning on line 2188 in src/metpy/plots/declarative.py

View check run for this annotation

Codecov / codecov/patch

src/metpy/plots/declarative.py#L2186-L2188

Added lines #L2186 - L2188 were not covered by tests
else:
offset = offset
Fixed Show fixed Hide fixed
self.parent.ax.add_artist(TextCollection([lon], [lat], [str(text)],

Check warning on line 2191 in src/metpy/plots/declarative.py

View check run for this annotation

Codecov / codecov/patch

src/metpy/plots/declarative.py#L2190-L2191

Added lines #L2190 - L2191 were not covered by tests
va='center',
ha='center',
color=color,
offset=offset,
weight='demi',
size = math.floor(self.label_fontsize * 0.7),
transform=ccrs.PlateCarree()))

def _draw_labels(self, text, lon, lat, color, offset=(0,0)):
"""Draw labels in the plot.

Parameters
----------
text : str
The label's text
lon : float
Longitude at which to position the label
lat : float
Latitude at which to position the label
color : str
Name or hex code for the color of the text
offset : tuple (default: (0, 0))
A tuple containing the x- and y-offset of the label, respectively
"""
self.parent.ax.add_artist(TextCollection([lon], [lat], [str(text)],

Check warning on line 2216 in src/metpy/plots/declarative.py

View check run for this annotation

Codecov / codecov/patch

src/metpy/plots/declarative.py#L2216

Added line #L2216 was not covered by tests
va='center',
ha='center',
color=color,
offset=offset,
weight='demi',
size=self.label_fontsize,
transform=ccrs.PlateCarree()))

def draw(self):
"""Draw the plot."""
if self._need_redraw:
if getattr(self, 'handles', None) is None:
self._build()
self._need_redraw = False

Check warning on line 2230 in src/metpy/plots/declarative.py

View check run for this annotation

Codecov / codecov/patch

src/metpy/plots/declarative.py#L2227-L2230

Added lines #L2227 - L2230 were not covered by tests

def copy(self):
"""Return a copy of the plot."""
return copy.copy(self)

Check warning on line 2234 in src/metpy/plots/declarative.py

View check run for this annotation

Codecov / codecov/patch

src/metpy/plots/declarative.py#L2234

Added line #L2234 was not covered by tests

def _build(self):
"""Build the plot by calling needed plotting methods as necessary."""
from shapely.geometry import (LineString, Point)

Check failure on line 2238 in src/metpy/plots/declarative.py

View workflow job for this annotation

GitHub Actions / Flake8

[flake8] reported by reviewdog 🐶 I001 isort found an import in the wrong position Raw Output: ./src/metpy/plots/declarative.py:2238:1: I001 isort found an import in the wrong position

Check warning on line 2238 in src/metpy/plots/declarative.py

View check run for this annotation

Codecov / codecov/patch

src/metpy/plots/declarative.py#L2238

Added line #L2238 was not covered by tests

Check failure on line 2239 in src/metpy/plots/declarative.py

View workflow job for this annotation

GitHub Actions / Flake8

[flake8] reported by reviewdog 🐶 I005 isort found an unexpected missing import Raw Output: ./src/metpy/plots/declarative.py:2239:1: I005 isort found an unexpected missing import
# Ensure strength is a valid iterable
strengths = self.strength if len(self.strength) > 0 else cycle([None])

Check warning on line 2241 in src/metpy/plots/declarative.py

View check run for this annotation

Codecov / codecov/patch

src/metpy/plots/declarative.py#L2241

Added line #L2241 was not covered by tests

# Map plotting parameters
effect_map = self._effect_map()
color_map = self._color_map()
linewidth_map = self._linewidth_map()
label_map = self._label_map()

Check warning on line 2247 in src/metpy/plots/declarative.py

View check run for this annotation

Codecov / codecov/patch

src/metpy/plots/declarative.py#L2244-L2247

Added lines #L2244 - L2247 were not covered by tests

# Each Shapely object is plotted separately with its corresponding strength
# and customizable parameters
for geo_obj, strengthvalues, feature in zip(

Check warning on line 2251 in src/metpy/plots/declarative.py

View check run for this annotation

Codecov / codecov/patch

src/metpy/plots/declarative.py#L2251

Added line #L2251 was not covered by tests
self.geometry, strengths, self.feature):
kwargs = self.mpl_args.copy()

Check warning on line 2253 in src/metpy/plots/declarative.py

View check run for this annotation

Codecov / codecov/patch

src/metpy/plots/declarative.py#L2253

Added line #L2253 was not covered by tests
# Plot the Shapely object with the appropriate method and style
if isinstance(geo_obj, (LineString)):
kwargs.setdefault('linewidths', linewidth_map[feature])
kwargs.setdefault('facecolor', 'none')
kwargs.setdefault('crs', ccrs.PlateCarree())
kwargs.setdefault('path_effects', effect_map[feature])
if feature=='TROF':
kwargs.setdefault('edgecolor', color_map[feature])
kwargs.setdefault('linestyle', self.TROF_linestyle)
self.parent.ax.add_geometries([geo_obj], **kwargs)
elif isinstance(geo_obj, Point):
kwargs.setdefault('color', color_map[feature])
lon, lat = geo_obj.coords[0]
self._draw_labels(label_map[feature],lon,lat, **kwargs)

Check warning on line 2267 in src/metpy/plots/declarative.py

View check run for this annotation

Codecov / codecov/patch

src/metpy/plots/declarative.py#L2255-L2267

Added lines #L2255 - L2267 were not covered by tests
# Plot strengths if provided
if strengthvalues is not None:
self._draw_strengths(strengthvalues, lon, lat, **kwargs)

Check warning on line 2270 in src/metpy/plots/declarative.py

View check run for this annotation

Codecov / codecov/patch

src/metpy/plots/declarative.py#L2269-L2270

Added lines #L2269 - L2270 were not covered by tests
Loading