Exemple #1
0
def _subdivide_shapes(fc, max_length):

    shapes = []
    for feature in fc.features:
        # get the boundary of each shape
        shape = shapely.geometry.shape(feature['geometry'])
        if max_length is not None:
            # subdivide the shape if it's too coarse
            geom_type = shape.geom_type
            shape = subdivide_geom(shape, geom_type, max_length)
        shapes.append(shape)

    return shapes
    def plot(self,
             projection,
             maxLength=4.0,
             figsize=None,
             colors=None,
             dpi=200):
        '''
        Plot the features on a map using cartopy.

        Parameters
        ----------
        projection : str or ``cartopy.crs.Projection``
            A cartopy projection object or one of the internally defined
            map names:

                'cyl', 'merc', 'mill', 'mill2', 'moll', 'moll2', 'robin',
                'robin2', 'ortho', 'northpole', 'southpole', 'atlantic',
                'pacific', 'americas', 'asia'

        maxLength : float, optional
            Maximum allowed segment length after subdivision for smoother
            plotting (0.0 indicates skip subdivision)

        figsize : tuple of float
            Size of the figure in inches

        colors : list of str
            Colors to cycle through for the shapes

        dpi : int
            Dots per inch for the figure

        Returns
        -------
        fig : ``matplotlib.figure.Figure``
            The figure
        '''
        # Authors
        # -------
        # Xylar Asay-Davis, `Phillip J. Wolfram

        projectionName = 'custom'

        if isinstance(projection, str):
            projections = build_projections()
            projectionName = projection
            projection = projections[projectionName]

        if figsize is None:
            if projectionName in [
                    'cyl', 'merc', 'mill', 'mill2', 'moll', 'moll2', 'robin',
                    'robin2'
            ]:
                figsize = (12, 6)
            else:
                figsize = (12, 9)

        fig = plt.figure(figsize=figsize, dpi=dpi)
        (ax, projection) = plot_base(projectionName, projection)

        if colors is None:
            # use colorbrewer qualitative 7 data class colors,
            # "7-class Accent": http://colorbrewer2.org/
            colors = [
                '#7fc97f', '#beaed4', '#fdc086', '#ffff99', '#386cb0',
                '#f0027f', '#bf5b17'
            ]

        bounds = None

        for featureIndex, feature in enumerate(self.features):
            geomType = feature['geometry']['type']
            shape = shapely.geometry.shape(feature['geometry'])
            if (maxLength > 0.0):
                shape = subdivide_geom(shape, geomType, maxLength)

            refProjection = cartopy.crs.PlateCarree()

            color = colors[featureIndex % len(colors)]

            if geomType in ['Polygon', 'MultiPolygon']:
                props = {
                    'linewidth': 2.0,
                    'edgecolor': color,
                    'alpha': 0.4,
                    'facecolor': color
                }
            elif geomType in ['LineString', 'MultiLineString']:
                props = {
                    'linewidth': 4.0,
                    'edgecolor': color,
                    'alpha': 1.,
                    'facecolor': 'none'
                }

            if bounds is None:
                bounds = list(shape.bounds)
            else:
                # expand the bounding box
                bounds[:2] = np.minimum(bounds[:2], shape.bounds[:2])
                bounds[2:] = np.maximum(bounds[2:], shape.bounds[2:])

            if geomType == 'Point':
                ax.scatter(shape.coords[0][0],
                           shape.coords[0][1],
                           s=9,
                           transform=cartopy.crs.PlateCarree(),
                           marker='o',
                           color='blue',
                           edgecolor='blue')
            else:
                ax.add_geometries((shape, ), crs=refProjection, **props)

        box = shapely.geometry.box(*bounds)
        if (maxLength > 0.0):
            box = subdivide_geom(box, 'Polygon', maxLength)

        boxProjected = projection.project_geometry(box, src_crs=refProjection)
        try:
            x1, y1, x2, y2 = boxProjected.bounds
            ax.set_xlim([x1, x2])
            ax.set_ylim([y1, y2])
        except ValueError:
            print("Warning: bounding box could not be projected into "
                  "projection {}".format(projectionName))
            print("Defaulting to global bounds.")
            ax.set_global()

        fig.canvas.draw()
        plt.tight_layout(pad=4.)

        return fig
Exemple #3
0
def distance_from_geojson(fc, lon_grd, lat_grd, nn_search, max_length=None):
    # {{{
    """
    Get the distance for each point on a lon/lat grid from the closest point
    on the boundary of the geojson regions.

    Parameters
    ----------
    fc : geometrics_features.FeatureCollection
        The regions to be rasterized
    lon_grd : numpy.ndarray
        A 1D array of evenly spaced longitude values
    lat_grd : numpy.ndarray
        A 1D array of evenly spaced latitude values
    nn_search: {'kdtree', 'flann'}
        The method used to find the nearest point on the shape boundary
    max_length : float, optional
        The maximum distance (in degrees) between points on the boundary of the
        geojson region.  If the boundary is too coarse, it will be subdivided.

    Returns
    -------
    distance : numpy.ndarray
       A 2D field of distances to the shape boundary
    """
    print("Distance from geojson")
    print("---------------------")

    print("   Finding region boundaries")
    boundary_lon = []
    boundary_lat = []
    for feature in fc.features:
        # get the boundary of each shape
        shape = shapely.geometry.shape(feature['geometry']).boundary
        if max_length is not None:
            # subdivide the shape if it's too coarse
            geom_type = shape.geom_type
            shape = subdivide_geom(shape, geom_type, max_length)
        x, y = shape.coords.xy
        boundary_lon.extend(x)
        boundary_lat.extend(y)

    boundary_lon = np.array(boundary_lon)
    boundary_lat = np.array(boundary_lat)

    # Remove point at +/- 180 lon and +/- 90 lat because these are "fake".
    # Need a little buffer (0.01 degrees) to avoid misses due to rounding.
    mask = np.logical_not(
        np.logical_or(
            np.logical_or(boundary_lon <= -179.99, boundary_lon >= 179.99),
            np.logical_or(boundary_lat <= -89.99, boundary_lat >= 89.99)))

    boundary_lon = boundary_lon[mask]
    boundary_lat = boundary_lat[mask]

    print("    Mean boundary latitude: {0:.2f}".format(np.mean(boundary_lat)))

    # Convert coastline points to x,y,z and create kd-tree
    npoints = len(boundary_lon)
    boundary_xyz = np.zeros((npoints, 3))
    boundary_xyz[:, 0], boundary_xyz[:, 1], boundary_xyz[:, 2] = \
        lonlat2xyz(boundary_lon, boundary_lat)
    flann = None
    tree = None
    if nn_search == "kdtree":
        tree = spatial.KDTree(boundary_xyz)
    elif nn_search == "flann":
        flann = pyflann.FLANN()
        flann.build_index(boundary_xyz,
                          algorithm='kdtree',
                          target_precision=1.0,
                          random_seed=0)
    else:
        raise ValueError('Bad nn_search: expected kdtree or flann, got '
                         '{}'.format(nn_search))

    # Convert  backgound grid coordinates to x,y,z and put in a nx_grd x 3 array
    # for kd-tree query
    Lon_grd, Lat_grd = np.meshgrid(lon_grd, lat_grd)
    X_grd, Y_grd, Z_grd = lonlat2xyz(Lon_grd, Lat_grd)
    pts = np.vstack([X_grd.ravel(), Y_grd.ravel(), Z_grd.ravel()]).T

    # Find distances of background grid coordinates to the coast
    print("   Finding distance")
    start = timeit.default_timer()
    distance = None
    if nn_search == "kdtree":
        distance, _ = tree.query(pts)
    elif nn_search == "flann":
        _, distance = flann.nn_index(pts, checks=2000, random_seed=0)
        distance = np.sqrt(distance)
    end = timeit.default_timer()
    print("   Done")
    print("   {0:.0f} seconds".format(end - start))

    # Make distance array that corresponds with cell_width array
    distance = np.reshape(distance, Lon_grd.shape)

    return distance  # }}}
Exemple #4
0
def add_inset(fig,
              fc,
              latlonbuffer=45.,
              polarbuffer=5.,
              width=1.0,
              height=1.0,
              lowerleft=None,
              xbuffer=None,
              ybuffer=None,
              maxlength=1.):
    '''
    Plots an inset map showing the location of a transect or polygon.  Shapes
    are plotted on a polar grid if they are entirely poleward of +/-50 deg.
    latitude and with a lat/lon grid if not.

    Parameters
    ----------
    fig : ``matplotlib.figure.Figure``
        A matplotlib figure to add the inset to

    fc : ``geometric_features.FeatureCollection``
        A collection of regions, transects and/or points to plot in the inset

    latlonbuffer : float, optional
        The number of degrees lat/lon to use as a buffer around the shape(s)
        to plot if a lat/lon plot is used.

    polarbuffer : float, optional
        The number of degrees latitude to use as a buffer equatorward of the
        shape(s) in polar plots

    width, height : float, optional
        width and height in inches of the inset

    lowerleft : pair of floats, optional
        the location of the lower left corner of the axis in inches, default
        puts the inset in the upper right corner of ``fig``.

    xbuffer, ybuffer : float, optional
        right and top buffers from the top-right corner (in inches) if
        lowerleft is ``None``.

    maxlength : float or ``None``, optional
        Any segments longer than maxlength will be subdivided in the plot to
        ensure curvature.  If ``None``, no subdivision is performed.

    Returns
    -------
    inset : ``matplotlib.axes.Axes``
        The new inset axis
    '''
    # Authors
    # -------
    # Xylar Asay-Davis

    minLon, minLat, maxLon, maxLat = _get_bounds(fc)

    figsize = fig.get_size_inches()
    width /= figsize[0]
    height /= figsize[1]
    if lowerleft is None:
        if xbuffer is None:
            xbuffer = 0.1 * width
        else:
            xbuffer /= figsize[0]
        if ybuffer is None:
            ybuffer = xbuffer * figsize[0] / figsize[1]
        else:
            ybuffer /= figsize[1]
        lowerleft = [1.0 - width - xbuffer, 1.0 - height - ybuffer]
    else:
        lowerleft = [lowerleft[0] / figsize[0], lowerleft[1] / figsize[1]]
    bounds = [lowerleft[0], lowerleft[1], width, height]

    if maxLat <= -50:
        # an Antarctic-focused map makes the most sense
        inset = fig.add_axes(bounds, projection=ccrs.SouthPolarStereo())
        extent = [-180., 180., -90., max(-65., maxLat + polarbuffer)]
        _set_circular_boundary(inset)
        xlocator = mticker.FixedLocator(numpy.linspace(-180., 180., 9))
        ylocator = mticker.FixedLocator(numpy.linspace(-90., -50., 9))
    elif minLat >= 50:
        # an Arctic-focused map makes the most sense
        inset = fig.add_axes(bounds, projection=ccrs.NorthPolarStereo())
        extent = [-180, 180, min(65., minLat - polarbuffer), 90]
        _set_circular_boundary(inset)
        xlocator = mticker.FixedLocator(numpy.linspace(-180., 180., 9))
        ylocator = mticker.FixedLocator(numpy.linspace(50., 90., 9))
    else:
        inset = fig.add_axes(bounds, projection=ccrs.PlateCarree())
        extent = [
            max(-180., minLon - latlonbuffer),
            min(180., maxLon + latlonbuffer),
            max(-90., minLat - latlonbuffer),
            min(90., maxLat + latlonbuffer)
        ]
        xlocator = None
        ylocator = None

    # kind of like "top" justified -- graphics are toward the "north" end of
    # the subplot
    inset.set_anchor('N')

    inset.set_extent(extent, ccrs.PlateCarree())
    inset.add_feature(cartopy.feature.LAND, zorder=1)
    inset.add_feature(cartopy.feature.OCEAN, zorder=0)

    gl = inset.gridlines(crs=ccrs.PlateCarree(),
                         draw_labels=False,
                         linewidth=0.5,
                         color='gray',
                         alpha=0.5,
                         linestyle='--')

    if xlocator is not None:
        gl.xlocator = xlocator

    if ylocator is not None:
        gl.ylocator = ylocator

    for feature in fc.features:
        geomtype = feature['geometry']['type']
        shape = shapely.geometry.shape(feature['geometry'])
        if maxlength is not None:
            shape = subdivide_geom(shape, shape.geom_type, maxlength)
        if geomtype in ['Polygon', 'MultiPolygon']:
            inset.add_geometries((shape, ),
                                 crs=ccrs.PlateCarree(),
                                 edgecolor='blue',
                                 facecolor='blue',
                                 alpha=0.4,
                                 linewidth=1.)
        elif geomtype in ['Point', 'MultiPoint']:
            inset.add_geometries((shape, ),
                                 crs=ccrs.PlateCarree(),
                                 edgecolor='none',
                                 facecolor='none',
                                 alpha=1.,
                                 markersize=3.,
                                 markeredgecolor='k',
                                 markerfacecolor='k')
        else:
            inset.add_geometries((shape, ),
                                 crs=ccrs.PlateCarree(),
                                 edgecolor='k',
                                 facecolor='none',
                                 alpha=1.,
                                 linewidth=1.)
            # put a red point at the beginning and a blue point at the end
            # of the transect to help show the orientation
            begin = shape.coords[0]
            end = shape.coords[-1]
            inset.plot(begin[0],
                       begin[1],
                       color='r',
                       marker='o',
                       markersize=3.,
                       transform=ccrs.PlateCarree())
            inset.plot(end[0],
                       end[1],
                       color='g',
                       marker='o',
                       markersize=3.,
                       transform=ccrs.PlateCarree())

    return inset