#!/usr/bin/env python # coding: utf-8 # #Labelling grid lines on a Lambert Conformal projection # # In response to the StackOverflow question here: http://stackoverflow.com/questions/27962953/cartopy-axis-label-workaround. # # Notes: # * The map boundary must be rectangular, otherwise the detection of which axis to label will be broken. # * Requires cartopy >= 0.12 # In[1]: from copy import copy get_ipython().run_line_magic('matplotlib', 'inline') import cartopy.crs as ccrs from cartopy.mpl.gridliner import LATITUDE_FORMATTER, LONGITUDE_FORMATTER import matplotlib.pyplot as plt import numpy as np import shapely.geometry as sgeom # In[2]: def find_side(ls, side): """ Given a shapely LineString which is assumed to be rectangular, return the line corresponding to a given side of the rectangle. """ minx, miny, maxx, maxy = ls.bounds points = {'left': [(minx, miny), (minx, maxy)], 'right': [(maxx, miny), (maxx, maxy)], 'bottom': [(minx, miny), (maxx, miny)], 'top': [(minx, maxy), (maxx, maxy)],} return sgeom.LineString(points[side]) def lambert_xticks(ax, ticks): """Draw ticks on the bottom x-axis of a Lambert Conformal projection.""" te = lambda xy: xy[0] lc = lambda t, n, b: np.vstack((np.zeros(n) + t, np.linspace(b[2], b[3], n))).T xticks, xticklabels = _lambert_ticks(ax, ticks, 'bottom', lc, te) ax.xaxis.tick_bottom() ax.set_xticks(xticks) ax.set_xticklabels([ax.xaxis.get_major_formatter()(xtick) for xtick in xticklabels]) def lambert_yticks(ax, ticks): """Draw ricks on the left y-axis of a Lamber Conformal projection.""" te = lambda xy: xy[1] lc = lambda t, n, b: np.vstack((np.linspace(b[0], b[1], n), np.zeros(n) + t)).T yticks, yticklabels = _lambert_ticks(ax, ticks, 'left', lc, te) ax.yaxis.tick_left() ax.set_yticks(yticks) ax.set_yticklabels([ax.yaxis.get_major_formatter()(ytick) for ytick in yticklabels]) def _lambert_ticks(ax, ticks, tick_location, line_constructor, tick_extractor): """Get the tick locations and labels for an axis of a Lambert Conformal projection.""" outline_patch = sgeom.LineString(ax.outline_patch.get_path().vertices.tolist()) axis = find_side(outline_patch, tick_location) n_steps = 30 extent = ax.get_extent(ccrs.PlateCarree()) _ticks = [] for t in ticks: xy = line_constructor(t, n_steps, extent) proj_xyz = ax.projection.transform_points(ccrs.Geodetic(), xy[:, 0], xy[:, 1]) xyt = proj_xyz[..., :2] ls = sgeom.LineString(xyt.tolist()) locs = axis.intersection(ls) if not locs: tick = [None] else: tick = tick_extractor(locs.xy) _ticks.append(tick[0]) # Remove ticks that aren't visible: ticklabels = copy(ticks) while True: try: index = _ticks.index(None) except ValueError: break _ticks.pop(index) ticklabels.pop(index) return _ticks, ticklabels # In[3]: # Create a Lambert Conformal projection: proj = ccrs.LambertConformal(central_longitude=13.3333, central_latitude=47.5, false_easting=400000, false_northing=400000, standard_parallels=(46, 49)) # Draw a set of axes with coastlines: fig = plt.figure(figsize=(9, 9), frameon=True) ax = fig.add_axes([0.08, 0.05, 0.8, 0.94], projection=proj) ax.set_extent([-5, 31, 42, 55], crs=ccrs.PlateCarree()) ax.coastlines(resolution='50m') # *must* call draw in order to get the axis boundary used to add ticks: fig.canvas.draw() # Define gridline locations and draw the lines using cartopy's built-in gridliner: xticks = [-110, -50, -40, -30, -20, -11, 0, 10, 20, 30, 40, 50] yticks = [10, 15, 20, 25, 30, 35, 40, 45, 50, 55, 60, 65, 70, 75, 80] ax.gridlines(xlocs=xticks, ylocs=yticks) # Label the end-points of the gridlines using the custom tick makers: ax.xaxis.set_major_formatter(LONGITUDE_FORMATTER) ax.yaxis.set_major_formatter(LATITUDE_FORMATTER) lambert_xticks(ax, xticks) lambert_yticks(ax, yticks)