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
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 # }}}
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