Unidata/MetPy

View on GitHub
src/metpy/plots/cartopy_utils.py

Summary

Maintainability
A
0 mins
Test Coverage
# Copyright (c) 2018,2019 MetPy Developers.
# Distributed under the terms of the BSD 3-Clause License.
# SPDX-License-Identifier: BSD-3-Clause
"""Cartopy specific mapping utilities."""

try:
    from cartopy.feature import Feature, Scaler

    from ..cbook import get_test_data

    class MetPyMapFeature(Feature):
        """A simple interface to MetPy-included shapefiles."""

        def __init__(self, name, scale, **kwargs):
            """Create MetPyMapFeature instance."""
            import cartopy.crs as ccrs
            super().__init__(ccrs.PlateCarree(globe=ccrs.Globe('NAD83')), **kwargs)
            self.name = name

            if isinstance(scale, str):
                scale = Scaler(scale)
            self.scaler = scale

        def geometries(self):
            """Return an iterator of (shapely) geometries for this feature."""
            import cartopy.io.shapereader as shapereader

            # Ensure that the associated files are in the cache
            fname = f'{self.name}_{self.scaler.scale}'
            for extension in ['.cpg', '.dbf', '.prj', '.shx']:
                get_test_data(fname + extension, as_file_obj=False)
            path = get_test_data(fname + '.shp', as_file_obj=False)
            return iter(tuple(shapereader.Reader(path).geometries()))

        def intersecting_geometries(self, extent):
            """Return geometries that intersect the extent."""
            self.scaler.scale_from_extent(extent)
            return super().intersecting_geometries(extent)

        def with_scale(self, new_scale):
            """
            Return a copy of the feature with a new scale.

            Parameters
            ----------
            new_scale
                The new dataset scale, i.e. one of '500k', '5m', or '20m'.
                Corresponding to 1:500,000, 1:5,000,000, and 1:20,000,000
                respectively.

            """
            return MetPyMapFeature(self.name, new_scale, **self.kwargs)

    USCOUNTIES = MetPyMapFeature('us_counties', '20m', facecolor='None', edgecolor='black')

    USSTATES = MetPyMapFeature('us_states', '20m', facecolor='None', edgecolor='black')
except ImportError:
    # If no Cartopy is present, we just don't have map features
    pass

__all__ = ['USCOUNTIES', 'USSTATES']


def import_cartopy():
    """Import CartoPy; return a stub if unable.

    This allows code requiring CartoPy to fail at use time rather than import time.
    """
    try:
        import cartopy.crs as ccrs
        return ccrs
    except ImportError:
        return CartopyStub()


class CartopyStub:
    """Fail if a CartoPy attribute is accessed."""

    def __getattr__(self, name):
        """Raise an error on any attribute access."""
        raise AttributeError(f'Cannot use {name} without Cartopy installed.')