How can I show a km ruler for a zoomed in section of a map, either inset in the image or as rulers on the side of the plot?
E.g. something like the 50 km bar on the
Here's the Cartopy scale bar function I wrote for my own use which uses simpler version of pp-mo's answer: Edit: modified code to create a new projection centred so that the scale bar is parallel to the axies for many coordinate systems, including some orthographic and larger maps, and removing the need to specify a utm system. Also added code to calculate a scale bar length if one wasn't specified.
import cartopy.crs as ccrs
import numpy as np
def scale_bar(ax, length=None, location=(0.5, 0.05), linewidth=3):
"""
ax is the axes to draw the scalebar on.
length is the length of the scalebar in km.
location is center of the scalebar in axis coordinates.
(ie. 0.5 is the middle of the plot)
linewidth is the thickness of the scalebar.
"""
#Get the limits of the axis in lat long
llx0, llx1, lly0, lly1 = ax.get_extent(ccrs.PlateCarree())
#Make tmc horizontally centred on the middle of the map,
#vertically at scale bar location
sbllx = (llx1 + llx0) / 2
sblly = lly0 + (lly1 - lly0) * location[1]
tmc = ccrs.TransverseMercator(sbllx, sblly)
#Get the extent of the plotted area in coordinates in metres
x0, x1, y0, y1 = ax.get_extent(tmc)
#Turn the specified scalebar location into coordinates in metres
sbx = x0 + (x1 - x0) * location[0]
sby = y0 + (y1 - y0) * location[1]
#Calculate a scale bar length if none has been given
#(Theres probably a more pythonic way of rounding the number but this works)
if not length:
length = (x1 - x0) / 5000 #in km
ndim = int(np.floor(np.log10(length))) #number of digits in number
length = round(length, -ndim) #round to 1sf
#Returns numbers starting with the list
def scale_number(x):
if str(x)[0] in ['1', '2', '5']: return int(x)
else: return scale_number(x - 10 ** ndim)
length = scale_number(length)
#Generate the x coordinate for the ends of the scalebar
bar_xs = [sbx - length * 500, sbx + length * 500]
#Plot the scalebar
ax.plot(bar_xs, [sby, sby], transform=tmc, color='k', linewidth=linewidth)
#Plot the scalebar label
ax.text(sbx, sby, str(length) + ' km', transform=tmc,
horizontalalignment='center', verticalalignment='bottom')
It has some limitations but is relatively simple so I hope you could see how to change it if you want something different.
Example usage:
import matplotlib.pyplot as plt
ax = plt.axes(projection=ccrs.Mercator())
plt.title('Cyprus')
ax.set_extent([31, 35.5, 34, 36], ccrs.Geodetic())
ax.coastlines(resolution='10m')
scale_bar(ax, 100)
plt.show()