Skip to content

Commit

Permalink
Further improve unknown projection error message
Browse files Browse the repository at this point in the history
  • Loading branch information
lukelbd committed Oct 15, 2021
1 parent 60bb13d commit 5975388
Show file tree
Hide file tree
Showing 4 changed files with 67 additions and 51 deletions.
11 changes: 8 additions & 3 deletions proplot/axes/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -27,9 +27,14 @@
# Register projections with package prefix to avoid conflicts
# NOTE: We integrate with cartopy and basemap rather than using matplotlib's
# native projection system. Therefore axes names are not part of public API.
CLASSES = {} # track valid names
for _cls in (CartesianAxes, PolarAxes, _BasemapAxes, _CartopyAxes, ThreeAxes):
_cls_dict = {} # track valid names
for _cls in (CartesianAxes, PolarAxes, _CartopyAxes, _BasemapAxes, ThreeAxes):
for _name in (_cls._name, *_cls._name_aliases):
with context._state_context(_cls, name='proplot_' + _name):
mproj.register_projection(_cls)
CLASSES[_name] = _cls
_cls_dict[_name] = _cls
_cls_table = '\n'.join(
' ' + key + ' ' * (max(map(len, _cls_dict)) - len(key) + 7)
+ ('GeoAxes' if cls.__name__[:1] == '_' else cls.__name__)
for key, cls in _cls_dict.items()
)
18 changes: 9 additions & 9 deletions proplot/axes/geo.py
Original file line number Diff line number Diff line change
Expand Up @@ -406,20 +406,20 @@ def __init__(self, *args, **kwargs):
*args
Passed to `matplotlib.axes.Axes`.
autoextent : bool, optional
*For cartopy axes only*. Whether to automatically adjust map bounds based
on plotted content or enforce a global map extent (or a map bounded at the
equator for polar projections). The extent can subsequently by adjusted
with the `~GeoAxes.format` keywords `lonlim`, `latlim`, and `boundinglat`,
or with `~cartopy.mpl.geoaxes.GeoAxes.set_extent`.
*For cartopy axes only*. Whether to automatically adjust map bounds
based on plotted content or enforce a global map extent (or a map bounded
at the equator for polar projections). The extent can subsequently by
adjusted with the `~GeoAxes.format` keywords `lonlim`, `latlim`, and
`boundinglat`, or with `~cartopy.mpl.geoaxes.GeoAxes.set_extent`.
Default is :rc:`cartopy.autoextent`.
circular : bool, optional
*For cartopy axes only*. Whether to bound polar projections with circles
rather than squares. Note that outer gridline labels cannot be added to
circularly bounded polar projections. Default is :rc:`cartopy.circular`.
map_projection : `~mpl_toolkits.basemap.Basemap` or `~cartopy.crs.Projection`
The cartopy or basemap projection instance. This is passed automatically
when calling axes-creation commands
like `~proplot.figure.Figure.add_subplot`.
map_projection : `~cartopy.crs.Projection` or `~mpl_toolkits.basemap.Basemap`
The cartopy or basemap projection instance. This is
passed automatically when calling axes-creation
commands like `~proplot.figure.Figure.add_subplot`.
%(geo.format)s
Other parameters
Expand Down
80 changes: 45 additions & 35 deletions proplot/constructor.py
Original file line number Diff line number Diff line change
Expand Up @@ -293,6 +293,13 @@
+ ', '.join(map(repr, PROJS_MISSING))
+ ' . Please consider updating cartopy.'
)
PROJS_TABLE = (
'The known cartopy projection classes are:\n'
+ '\n'.join(
' ' + key + ' ' * (max(map(len, PROJS)) - len(key) + 10) + cls.__name__
for key, cls in PROJS.items()
)
)

# Geographic feature properties
FEATURES_CARTOPY = { # positional arguments passed to NaturalEarthFeature
Expand Down Expand Up @@ -1393,6 +1400,7 @@ def Proj(name, basemap=None, **kwargs):
basemap = _not_none(basemap, rc['basemap'])
is_crs = Projection is not object and isinstance(name, Projection)
is_basemap = Basemap is not object and isinstance(name, Basemap)
include_axes = kwargs.pop('include_axes', False) # for error message
if is_crs or is_basemap:
proj = name
package = 'cartopy' if is_crs else 'basemap'
Expand Down Expand Up @@ -1429,18 +1437,18 @@ def Proj(name, basemap=None, **kwargs):
if 'latlim' in kwargs:
kwargs['llcrnrlat'], kwargs['urcrnrlat'] = kwargs.pop('latlim')
name = PROJ_ALIASES.get(name, name)
kwproj = PROJ_DEFAULTS.get(name, {}).copy()
kwproj.update(kwargs)
kwproj.setdefault('fix_aspect', True)
if kwproj.get('lon_0', 0) > 0:
kwproj['lon_0'] -= 360
proj_kw = PROJ_DEFAULTS.get(name, {}).copy()
proj_kw.update(kwargs)
proj_kw.setdefault('fix_aspect', True)
if proj_kw.get('lon_0', 0) > 0:
proj_kw['lon_0'] -= 360
if name[:2] in ('np', 'sp'):
kwproj.setdefault('round', True)
proj_kw.setdefault('round', True)
if name == 'geos':
kwproj.setdefault('rsphere', (6378137.00, 6356752.3142))
proj_kw.setdefault('rsphere', (6378137.00, 6356752.3142))
reso = _not_none(
reso=kwproj.pop('reso', None),
resolution=kwproj.pop('resolution', None),
reso=proj_kw.pop('reso', None),
resolution=proj_kw.pop('resolution', None),
default=rc['reso']
)
if reso in RESOS_BASEMAP:
Expand All @@ -1451,43 +1459,45 @@ def Proj(name, basemap=None, **kwargs):
+ ', '.join(map(repr, RESOS_BASEMAP))
+ '.'
)
kwproj.update({'resolution': reso, 'projection': name})
proj_kw.update({'resolution': reso, 'projection': name})
try:
proj = mbasemap.Basemap(**kwproj) # will raise helpful warning
proj = mbasemap.Basemap(**proj_kw) # will raise helpful warning
except ValueError as err:
msg = str(err)
msg = msg.replace('projection', 'basemap projection')
raise ValueError(msg) from None
message = str(err)
message = message.strip()
message = message.replace('projection', 'basemap projection')
message = message.replace('supported', 'known')
if include_axes:
from . import axes as paxes # avoid circular imports
message = message.replace('projection.', 'projection or axes subclass.')
message += '\nThe known axes subclasses are:\n' + paxes._cls_table
raise ValueError(message) from None

# Cartopy
# NOTE: Error message matches basemap invalid projection message
else:
import cartopy.crs as ccrs # noqa: F401
package = 'cartopy'
kwproj = {
PROJ_ALIASES_KW.get(key, key): value
for key, value in kwargs.items()
}
if name in PROJS:
proj_kw = {PROJ_ALIASES_KW.get(key, key): value for key, value in kwargs.items()} # noqa: E501
if 'boundinglat' in proj_kw:
raise ValueError('"boundinglat" must be passed to the ax.format() command for cartopy axes.') # noqa: E501
try:
crs = PROJS[name]
else:
maxlen = max(map(len, PROJS))
raise ValueError(
f'{name!r} is an unknown cartopy projection class.\n'
'The known cartopy projection classes are:\n'
+ '\n'.join(
' ' + key + ' ' * (maxlen - len(key) + 6) + crs.__name__
for key, crs in PROJS.items()
)
except KeyError:
message = f'{name!r} is an unknown cartopy projection class.\n'
message += 'The known cartopy projection classes are:\n'
message += '\n'.join(
' ' + key + ' ' * (max(map(len, PROJS)) - len(key) + 10) + cls.__name__
for key, cls in PROJS.items()
)
if include_axes:
from . import axes as paxes # avoid circular imports
message = message.replace('projection.', 'projection or axes subclass.')
message += '\nThe known axes subclasses are:\n' + paxes._cls_table
raise ValueError(message) from None
if name == 'geos': # fix common mistake
kwproj.pop('central_latitude', None)
if 'boundinglat' in kwproj:
raise ValueError(
'"boundinglat" must be passed to the ax.format() command '
'for cartopy axes.'
)
proj = crs(**kwproj)
proj_kw.pop('central_latitude', None)
proj = crs(**proj_kw)

proj._proj_package = package
return proj
Expand Down
9 changes: 5 additions & 4 deletions proplot/figure.py
Original file line number Diff line number Diff line change
Expand Up @@ -789,14 +789,15 @@ def _parse_proj(
and constructor.Basemap is object
):
raise ValueError(
f'Invalid projection name {proj!r}. Valid axes subclasses are '
+ ', '.join(map(repr, paxes.CLASSES)) + '. If you are trying to '
+ 'create a geographic axes then cartopy or basemap must be installed.'
f'Invalid projection name {proj!r}. If you are trying to create a '
'GeoAxes with a cartopy.crs.Projection or mpl_toolkits.basemap.Basemap '
'projection then cartopy or basemap must be installed. Otherwise the '
f'known axes subclasses are:\n{paxes._cls_table}'
)
# Search geographic projections
# NOTE: Also raises errors due to unexpected projection type
if name is None:
proj = constructor.Proj(proj, basemap=basemap, **proj_kw)
proj = constructor.Proj(proj, basemap=basemap, include_axes=True, **proj_kw)
name = proj._proj_package
kwargs['map_projection'] = proj

Expand Down

0 comments on commit 5975388

Please sign in to comment.