Skip to content

Commit

Permalink
Traitlets validation of MapPanel's 'area' trait
Browse files Browse the repository at this point in the history
TraitError thrown when bad inputs are provided to 'area' trait. Additionally, code to parse the 'area' string is removed from draw() and put in a new method _parse_area_string().
  • Loading branch information
23ccozad committed Jun 11, 2021
1 parent dbc74c0 commit daec5df
Showing 1 changed file with 48 additions and 11 deletions.
59 changes: 48 additions & 11 deletions src/metpy/plots/declarative.py
Original file line number Diff line number Diff line change
Expand Up @@ -11,8 +11,8 @@
import matplotlib.pyplot as plt
import numpy as np
import pandas as pd
from traitlets import (Any, Bool, Float, HasTraits, Instance, Int, List, observe, Tuple,
Unicode, Union)
from traitlets import (Any, Bool, Float, HasTraits, Instance, Int, List, observe, TraitError,
Tuple, Unicode, Union, validate)

from . import ctables
from . import wx_symbols
Expand Down Expand Up @@ -651,6 +651,28 @@ class MapPanel(Panel):
This trait sets a user-defined title that will plot at the top center of the figure.
"""

@validate('area')
def _valid_area(self, proposal):
"""Check that the proposed area string or tuple is a valid value."""
area = proposal['value']

# If string, check that the string exists in list of areas ('global' is special case)
if isinstance(area, str):
region = self._parse_area_string(area)[0]
if region.lower() not in _areas.keys() and region != 'global':
raise TraitError('"' + area + '" is not a valid string area.')
# If tuple, check that the latitudes and longitudes make sense
elif isinstance(area, tuple):
west_lon, east_lon, south_lat, north_lat = area
valid_west = -180 <= west_lon <= 180
valid_east = -180 <= east_lon <= 180
valid_south = -90 <= south_lat <= 90
valid_north = -90 <= north_lat <= 90
if not valid_west or not valid_east or not valid_south or not valid_north:
raise TraitError(str(area) + ' is not a valid latitude/longitude extent.')

return area

@observe('plots')
def _plots_changed(self, change):
"""Handle when our collection of plots changes."""
Expand Down Expand Up @@ -736,6 +758,27 @@ def _zoom_extent(extent, zoom):

return (new_west_lon, new_east_lon, new_south_lat, new_north_lat)

def _parse_area_string(self, area=None):
"""Split the area string into the 'region' and the 'modifier'.
For example, if area is 'co++', this will return 'co' and '++'.
"""
# If an area is not provided, assume we want to parse with self.area
if area is None:
area = self.area

# Find where to split area string so that we are left with 'region' and 'modifier'
first_pos_found = [area.find('+'), area.find('-')]
has_modifier = first_pos_found != [-1, -1]
if has_modifier:
split_pos = min([i for i in first_pos_found if i >= 0])
region = area[:split_pos]
modifier = area[split_pos:]
return region, modifier
else:
return area, ''

@property
def ax(self):
"""Get the :class:`matplotlib.axes.Axes` to draw on.
Expand Down Expand Up @@ -775,17 +818,11 @@ def draw(self):
self.ax.set_global()
elif self.area is not None:
# Get extent from specified area and zoom in/out with '+' or '-' suffix
if isinstance(self.area, str) and ('+' in self.area or '-' in self.area):
pos = [self.area.find('+'), self.area.find('-')]
split_pos = min([i for i in pos if i > 0])
area = self.area[:split_pos]
modifier = self.area[split_pos:]
extent = _areas[area.lower()]
if isinstance(self.area, str):
region, modifier = self._parse_area_string()
extent = _areas[region.lower()]
zoom = Counter(modifier)['+'] - Counter(modifier)['-']
extent = self._zoom_extent(extent, zoom)
# Get extent from specified area
elif isinstance(self.area, str):
extent = _areas[self.area.lower()]
# Otherwise, assume we have a tuple to use as the extent
else:
extent = self.area
Expand Down

0 comments on commit daec5df

Please sign in to comment.