Skip to content

Commit a93a3a5

Browse files
committed
Improve unknown projection error msg (closes #295)
1 parent cf994f5 commit a93a3a5

File tree

3 files changed

+73
-43
lines changed

3 files changed

+73
-43
lines changed

proplot/axes/__init__.py

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -27,7 +27,9 @@
2727
# Register projections with package prefix to avoid conflicts
2828
# NOTE: We integrate with cartopy and basemap rather than using matplotlib's
2929
# native projection system. Therefore axes names are not part of public API.
30+
CLASSES = {} # track valid names
3031
for _cls in (CartesianAxes, PolarAxes, _BasemapAxes, _CartopyAxes, ThreeAxes):
3132
for _name in (_cls._name, *_cls._name_aliases):
3233
with context._state_context(_cls, name='proplot_' + _name):
3334
mproj.register_projection(_cls)
35+
CLASSES[_name] = _cls

proplot/constructor.py

Lines changed: 31 additions & 33 deletions
Original file line numberDiff line numberDiff line change
@@ -1391,33 +1391,33 @@ def Proj(name, basemap=None, **kwargs):
13911391
.. _wintri: https://proj4.org/operations/projections/wintri.html
13921392
""" # noqa: E501
13931393
# Class instances
1394-
use_basemap = _not_none(basemap, rc['basemap'])
13951394
is_crs = CRS is not object and isinstance(name, CRS)
13961395
is_basemap = Basemap is not object and isinstance(name, Basemap)
1397-
include_axes = kwargs.pop('include_axes', None)
1396+
use_basemap = _not_none(basemap, rc['basemap'])
13981397
if is_crs or is_basemap:
13991398
proj = name
1400-
proj._proj_package = 'cartopy' if is_crs else 'basemap'
14011399
if basemap is not None:
14021400
kwargs['basemap'] = basemap
14031401
if kwargs:
14041402
warnings._warn_proplot(f'Ignoring Proj() keyword arg(s): {kwargs!r}.')
14051403

1406-
# Invalid
1404+
# Unknown
14071405
elif not isinstance(name, str):
14081406
raise ValueError(
14091407
f'Unexpected projection {name!r}. Must be PROJ string name, '
14101408
'cartopy.crs.Projection, or mpl_toolkits.basemap.Basemap.'
14111409
)
14121410

14131411
# Basemap
1412+
# NOTE: Known issue that basemap sometimes produces backwards maps:
1413+
# https://stackoverflow.com/q/56299971/4970632
1414+
# NOTE: We set rsphere to fix non-conda installed basemap issue:
1415+
# https://github.com/matplotlib/basemap/issues/361
1416+
# NOTE: Adjust lon_0 tof fix issues with Robinson (and related?) projections
1417+
# https://stackoverflow.com/questions/56299971/ (also triggers 'no room for axes')
1418+
# NOTE: Unlike cartopy, basemap resolution is configured
1419+
# on initialization and controls *all* features.
14141420
elif use_basemap:
1415-
# NOTE: Known issue that basemap sometimes produces backwards maps:
1416-
# https://stackoverflow.com/q/56299971/4970632
1417-
# NOTE: We set rsphere to fix non-conda installed basemap issue:
1418-
# https://github.com/matplotlib/basemap/issues/361
1419-
# NOTE: Unlike cartopy, basemap resolution is configured on
1420-
# initialization and controls *all* features.
14211421
import mpl_toolkits.basemap as mbasemap
14221422
if dependencies._version_mpl >= 3.3:
14231423
raise RuntimeError(
@@ -1434,9 +1434,6 @@ def Proj(name, basemap=None, **kwargs):
14341434
kwproj.update(kwargs)
14351435
kwproj.setdefault('fix_aspect', True)
14361436
if kwproj.get('lon_0', 0) > 0:
1437-
# Fix issues with Robinson (and related?) projections
1438-
# See: https://stackoverflow.com/questions/56299971/
1439-
# Get both this issue *and* 'no room for axes' issue
14401437
kwproj['lon_0'] -= 360
14411438
if name[:2] in ('np', 'sp'):
14421439
kwproj.setdefault('round', True)
@@ -1456,17 +1453,33 @@ def Proj(name, basemap=None, **kwargs):
14561453
+ '.'
14571454
)
14581455
kwproj.update({'resolution': reso, 'projection': name})
1459-
proj = mbasemap.Basemap(**kwproj)
1460-
proj._proj_package = 'basemap'
1456+
try:
1457+
proj = mbasemap.Basemap(**kwproj) # will raise helpful warning
1458+
except ValueError as err:
1459+
msg = str(err)
1460+
msg = msg.replace('projection', 'basemap projection')
1461+
raise ValueError(msg) from None
14611462

14621463
# Cartopy
1463-
elif name in PROJS:
1464-
import cartopy.crs # noqa: F401
1464+
# NOTE: Error message matches basemap invalid projection message
1465+
else:
1466+
import cartopy.crs as ccrs # noqa: F401
14651467
kwproj = {
14661468
PROJ_ALIASES_KW.get(key, key): value
14671469
for key, value in kwargs.items()
14681470
}
1469-
crs = PROJS.get(name, None)
1471+
if name in PROJS:
1472+
crs = PROJS[name]
1473+
else:
1474+
maxlen = max(map(len, PROJS))
1475+
raise ValueError(
1476+
f'{name!r} is an unknown cartopy projection class.\n'
1477+
'The known cartopy projection classes are:\n'
1478+
+ '\n'.join(
1479+
' ' + key + ' ' * (maxlen - len(key) + 6) + crs.__name__
1480+
for key, crs in PROJS.items()
1481+
)
1482+
)
14701483
if name == 'geos': # fix common mistake
14711484
kwproj.pop('central_latitude', None)
14721485
if 'boundinglat' in kwproj:
@@ -1475,21 +1488,6 @@ def Proj(name, basemap=None, **kwargs):
14751488
'for cartopy axes.'
14761489
)
14771490
proj = crs(**kwproj)
1478-
proj._proj_package = 'cartopy'
1479-
1480-
# Unknown
1481-
else:
1482-
options = tuple(PROJS)
1483-
if include_axes:
1484-
options += tuple(
1485-
proj.split('proplot_', 1)[1] for proj in mproj.get_projection_names()
1486-
if 'proplot_' in proj
1487-
)
1488-
raise ValueError(
1489-
f'Unknown projection {name!r}. Options are: '
1490-
+ ', '.join(map(repr, options))
1491-
+ '.'
1492-
)
14931491

14941492
return proj
14951493

proplot/figure.py

Lines changed: 40 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -400,6 +400,30 @@
400400
docstring._snippet_manager['figure.save'] = _save_docstring
401401

402402

403+
def _get_proj_package(basemap=None):
404+
"""
405+
Try to get the projection package.
406+
"""
407+
use_basemap = _not_none(basemap, rc['basemap'])
408+
if use_basemap:
409+
package = 'basemap'
410+
try:
411+
import mpl_toolkits.basemap # noqa: F401
412+
except ImportError:
413+
installed = False
414+
else:
415+
installed = True
416+
else:
417+
package = 'cartopy'
418+
try:
419+
import cartopy # noqa: F401
420+
except ImportError:
421+
installed = False
422+
else:
423+
installed = True
424+
return package, installed
425+
426+
403427
def _get_journal_size(preset):
404428
"""
405429
Return the width and height corresponding to the given preset.
@@ -772,22 +796,28 @@ def _parse_proj(
772796
raise ValueError('Matplotlib axes cannot be added to proplot figures.')
773797

774798
# Search axes projections
775-
if not isinstance(proj, str):
776-
name = None
777-
else:
778-
name = 'proplot_' + proj
799+
subclass = None
800+
if isinstance(proj, str):
779801
try:
780-
mproj.get_projection_class(name)
802+
mproj.get_projection_class('proplot_' + proj)
781803
except (KeyError, ValueError):
782-
name = None
804+
pass
805+
else:
806+
subclass = proj
783807

784808
# Redirect to basemap or cartopy projection
785-
if name is None:
786-
proj = constructor.Proj(proj, basemap=basemap, include_axes=True, **proj_kw)
787-
name = 'proplot_' + proj._proj_package
809+
if subclass is None:
810+
subclass, installed = _get_proj_package(basemap=basemap)
811+
if not installed:
812+
raise ValueError(
813+
f'Invalid projection name {proj!r}. Valid axes subclasses are '
814+
+ ', '.join(map(repr, paxes.CLASSES)) + '. If you are trying to '
815+
+ f'create a geographic axes then {subclass} must be installed.'
816+
)
817+
proj = constructor.Proj(proj, basemap=basemap, **proj_kw)
788818
kwargs['map_projection'] = proj
789819

790-
kwargs['projection'] = name
820+
kwargs['projection'] = 'proplot_' + subclass
791821
return kwargs
792822

793823
def _get_align_axes(self, side):

0 commit comments

Comments
 (0)