pythonmatplotlibgridcartopypolar-coordinates

grid line labeling (labels around the map's border) for a South Polar Stereo projection


I'm using the South Polar Stereographic projection to create a map of Antarctica. I've been able to create the gridlines using Cartopy, but the labels for the gridlines are not circular around the map, which is what I want. Is there a way to make the gridline labels follow the circular pattern of the South Polar Stereographic projection? Here's the code I'm currently using:

import cartopy.crs as ccrs
import cartopy.feature as cfeature
import numpy as np
import matplotlib.pyplot as plt
import matplotlib.patches as  mpath

fig = plt.figure(figsize=(12,8))
ax = plt.axes(projection=ccrs.SouthPolarStereo())
ax.set_extent([-180, 180, -90, -30], ccrs.PlateCarree())
ax.add_feature(cfeature.LAND, color='darkgrey')
ax.add_feature(cfeature.OCEAN, color='lightblue')
ax.add_feature(cfeature.COASTLINE, linewidth=1.25)

# Draw meridian lines with labels around circular boundary
ax.gridlines(crs=ccrs.PlateCarree(), draw_labels=True, linewidth=1, \
                xlocs=range(-180,171,10), ylocs=[], \
                color='gray', alpha=0.5, linestyle='--', zorder=10)
# Draw concentric circles (but hide labels) for the parallels of the latitude
ax.gridlines(crs=ccrs.PlateCarree(), draw_labels=False, linewidth=1, \
                xlocs=[], ylocs=None, \
                color='gray', alpha=0.5, linestyle='--', zorder=10)

# Add circular boundary
theta = np.linspace(0, 2*np.pi, 100)
center, radius = [0.5, 0.5], 0.5
verts = np.vstack([np.sin(theta), np.cos(theta)]).T
circle = mpath.Path(verts * radius + center)

ax.set_boundary(circle, transform=ax.transAxes)


plt.tight_layout()
plt.show()

The output looks like

enter image description here

What I am expecting:

enter image description here


Solution

  • To my knowledge, there is no shortcut for this kind of plot. Special instructions are needed in many places in the code. You can see comments in the code that I added in various places.

    Complete code

    import cartopy.crs as ccrs
    import cartopy.feature as cfeature
    import numpy as np
    import matplotlib.pyplot as plt
    import matplotlib.patches as  mpath #for circular boundary
    
    nonproj = ccrs.PlateCarree()  #for plain lat/long degree system
    
    fig = plt.figure(figsize=(7,7))
    ax = plt.axes(projection=ccrs.SouthPolarStereo())
    ax.set_extent([-180, 180, -90, -30], ccrs.PlateCarree())
    ax.add_feature(cfeature.LAND, color='darkgrey')
    ax.add_feature(cfeature.OCEAN, color='lightblue')
    ax.add_feature(cfeature.COASTLINE, linewidth=1.25)
    
    # Make the gridlines circular
    gls1 = ax.gridlines(crs=ccrs.PlateCarree(), draw_labels=False, linewidth=1, \
                    xlocs=range(-180,171,10), ylocs=[], \
                    color='gray', alpha=0.5, linestyle='--', zorder=10)
    gls2 = ax.gridlines(crs=ccrs.PlateCarree(), draw_labels=False, linewidth=1, \
                    xlocs=[], ylocs=None, \
                    color='gray', alpha=0.5, linestyle='--', zorder=10)
    
    # Add circular boundary
    theta = np.linspace(0, 2*np.pi, 100)
    center, radius = [0.5, 0.5], 0.5
    verts = np.vstack([np.sin(theta), np.cos(theta)]).T
    circle = mpath.Path(verts * radius + center)
    
    def plot_text(p1, p2, ax, ang_d, txt):
        """
        Plot text string at a location defined by 2 points (p1,p2)
        The string is rotated by an angle 'ang_d' 
        usage:
        plot_text([90, -31], [90, -50], ax, 45, "45-deg_text")
        By swatchai
        """
        # Locations to plot text
        l1 = np.array((p1[0], p1[1]))
        l2 = np.array((p2[0], p2[1]))
    
        # Plot text
        th1 = ax.text(l1[0], l1[1], txt, fontsize=10, \
                      transform=nonproj, \
                      ha="center", \
                      rotation=ang_d, rotation_mode='anchor')
    
    
    # Plot text labels outside circular boundary
    for lon in range(-180,180,10):
        lat = -33  # determined by inspection
    
        a1, a2 = -29.5, -39  #text anchor for general use ...
        #... need adjustments in some cases
    
        if lon>=90 and lon<=170:
            plot_text([lon, a1+2.35], [lon, a2], ax, -lon-180, str(lon)+"°E")  
            # Special rotation+shift
        elif lon<-90 and lon>=-170:
            # Need a1+2 to move texts in line with others
            plot_text([lon, a1+2.5], [lon, a2], ax, -lon+180, str(-lon)+"°W")  
            # Special rotation+shift
        elif lon > 0:
            plot_text([lon, a1], [lon, a2], ax, -lon, str(lon)+"°E")
        elif lon==0:
            plot_text([lon, a1], [lon, a2], ax, lon, str(lon)+"°")
        elif lon==-180:
            plot_text([lon, a1+2.2], [lon, a2], ax, lon+180, str(-lon)+"°")
        else:
            plot_text([lon, a1], [lon, a2], ax, -lon, str(-lon)+"°W")
            pass
    
    ax.set_boundary(circle, transform=ax.transAxes)
    
    plt.tight_layout()
    plt.show()
    

    Output:

    spolar