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
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: