cartopy

Cartopy: add tick markers outside plot


I have the following code and would like to add tick markers on the (outside of the) axes. I have found this example, but struggle to adapt it to work with my code. Ideally, I don't want to have to add the tick locations manually. Any help is highly appreciated!

import matplotlib.pyplot as plt
from cartopy.crs import Mercator, PlateCarre

lon_min = -50
lon_max = 40
lat_max = 30
lat_min = -70    
fontsize=8

ax = plt.axes(projection = Mercator(latitude_true_scale = (lat_min+lat_max)/2))
ax.set_extent([lon_min, lon_max, lat_min, lat_max], crs=PlateCarree())

# Make gridlines
gl = ax.gridlines(draw_labels=True,
                  x_inline=False,
                  y_inline=False, 
                  crs=PlateCarree(), 
                  visible=True)

# Only show ticks on lower and left axes
gl.bottom_labels = True
gl.left_labels   = True
gl.top_labels    = False
gl.right_labels  = False

# Adjust rotation and size of tick labels
gl.xlabel_style['size']=fontsize
gl.xlabel_style['rotation']=0
gl.xlabel_style['ha'] = 'center'

gl.ylabel_style['size']=fontsize
gl.ylabel_style['rotation']=90
gl.ylabel_style['ha'] = 'center'

enter image description here


Solution

  • The GridLiner object internally uses the LatitudeLocator and LongitudeLocator to work out which gridlines should be drawn if the source was PlateCarree.

    If we subclass these locators then we can have them work out the desired tick spacing in PlateCarree space and then map back to Mercator space for drawing. This means we can now use them with the standard Matplotlib x- and y-axis, rather than creating the Cartopy GridLiner instance. We then get ticks drawn by default. This approach is not very generalisable since it relies on the fact we have two rectangular projections. It also also perhaps a little over-engineered.

    import matplotlib.pyplot as plt
    import numpy as np
    
    from cartopy.crs import Mercator, PlateCarree
    from cartopy.mpl.ticker import LongitudeFormatter, LatitudeFormatter, LongitudeLocator, LatitudeLocator
    
    lon_min = -50
    lon_max = 40
    lat_max = 30
    lat_min = -70
    
    axes_proj = Mercator(latitude_true_scale = (lat_min+lat_max)/2)
    source_proj = crs=PlateCarree()
    
    ax = plt.axes(projection=axes_proj)
    ax.set_extent([lon_min, lon_max, lat_min, lat_max], source_proj)
    
    class ProjectedLongitudeLocator(LongitudeLocator):
        def tick_values(self, vmin, vmax):
            # Transform axis min/max to source projection
            source_lims = source_proj.transform_points(axes_proj, np.array([vmin, vmax]), np.zeros(2))
    
            # Calculate tick position in source projection space
            source_tick_values = super().tick_values(*source_lims[:, 0])
    
            # Transform tick positions back to the map projection space
            return axes_proj.transform_points(source_proj, source_tick_values, np.zeros_like(source_tick_values))[:, 0]
    
    
    class ProjectedLatitudeLocator(LatitudeLocator):
        def tick_values(self, vmin, vmax):
            # Transform axis min/max to source projection
            source_lims = source_proj.transform_points(axes_proj, np.zeros(2), np.array([vmin, vmax]))
    
            # Calculate tick position in source projection space
            source_tick_values = super().tick_values(*source_lims[:, 1])
    
            # Transform tick positions back to the map projection space
            tick_values = axes_proj.transform_points(source_proj, np.zeros_like(source_tick_values), np.array(source_tick_values))[:, 1]
    
            # Return ticks that are with the map projection's limits
            valid = np.logical_and(tick_values <= axes_proj.y_limits[1], tick_values >= axes_proj.y_limits[0])
            return tick_values[valid]
    
    
    # Set the locators and formatters for each axis
    ax.xaxis.set_major_locator(ProjectedLongitudeLocator())
    ax.yaxis.set_major_locator(ProjectedLatitudeLocator())
    
    lon_formatter = LongitudeFormatter(zero_direction_label=True)
    lat_formatter = LatitudeFormatter()
    ax.xaxis.set_major_formatter(lon_formatter)
    ax.yaxis.set_major_formatter(lat_formatter)
    
    # Cartopy makes axises invisible by default
    ax.xaxis.set_visible(True)
    ax.yaxis.set_visible(True)
    
    # Optionally show gridlines
    ax.grid(True, linestyle='dashed')
    
    plt.show()
    

    enter image description here