How can I show the km line on the / matplotlib graph?

How can I show the km line for an enlarged section of the map, or insert on the image, or as a line on the plot side?

eg. something like a 50 km strip on the side (left) or insert in mi (right):

kJwH9.jpgCztgd.jpg

(sources: 1 , 2 )

(release: cartopy # 490 )

+7
python matplotlib cartopy
source share
3 answers

Here's the Cartopy scale scale feature that I wrote for my own use, which uses a simpler version of pp-mo's answer: Edit: modified code to create a new projection centered so that the scale scale is parallel to the axes for many coordinate systems, including some spelling and large maps, and eliminating the need to specify a utm system. Also added a code to calculate the length of the scale, if it is not 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 else.

Usage example:

 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() 

Cyprus map with scale

+6
source share

I think that for this there is no simple solution for pots: you have to do it yourself using graphic elements.

A few years ago, I wrote some adaptive code to add a scale scale to an arbitrary scale OS grid map.
Actually, I donโ€™t think you wanted to, but it shows the necessary methods:

 def add_osgb_scalebar(ax, at_x=(0.1, 0.4), at_y=(0.05, 0.075), max_stripes=5): """ Add a scalebar to a GeoAxes of type cartopy.crs.OSGB (only). Args: * at_x : (float, float) target axes X coordinates (0..1) of box (= left, right) * at_y : (float, float) axes Y coordinates (0..1) of box (= lower, upper) * max_stripes typical/maximum number of black+white regions """ # ensure axis is an OSGB map (meaning coords are just metres) assert isinstance(ax.projection, ccrs.OSGB) # fetch axes coordinate mins+maxes x0, x1 = ax.get_xlim() y0, y1 = ax.get_ylim() # set target rectangle in-visible-area (aka 'Axes') coordinates ax0, ax1 = at_x ay0, ay1 = at_y # choose exact X points as sensible grid ticks with Axis 'ticker' helper x_targets = [x0 + ax * (x1 - x0) for ax in (ax0, ax1)] ll = mpl.ticker.MaxNLocator(nbins=max_stripes, steps=[1,2,4,5,10]) x_vals = ll.tick_values(*x_targets) # grab min+max for limits xl0, xl1 = x_vals[0], x_vals[-1] # calculate Axes Y coordinates of box top+bottom yl0, yl1 = [y0 + ay * (y1 - y0) for ay in [ay0, ay1]] # calculate Axes Y distance of ticks + label margins y_margin = (yl1-yl0)*0.25 # fill black/white 'stripes' and draw their boundaries fill_colors = ['black', 'white'] i_color = 0 for xi0, xi1 in zip(x_vals[:-1],x_vals[1:]): # fill region plt.fill((xi0, xi1, xi1, xi0, xi0), (yl0, yl0, yl1, yl1, yl0), fill_colors[i_color]) # draw boundary plt.plot((xi0, xi1, xi1, xi0, xi0), (yl0, yl0, yl1, yl1, yl0), 'black') i_color = 1 - i_color # add short tick lines for x in x_vals: plt.plot((x, x), (yl0, yl0-y_margin), 'black') # add a scale legend 'Km' font_props = mfonts.FontProperties(size='medium', weight='bold') plt.text( 0.5 * (xl0 + xl1), yl1 + y_margin, 'Km', verticalalignment='bottom', horizontalalignment='center', fontproperties=font_props) # add numeric labels for x in x_vals: plt.text(x, yl0 - 2 * y_margin, '{:g}'.format((x - xl0) * 0.001), verticalalignment='top', horizontalalignment='center', fontproperties=font_props) 

Sure, isn't it?
You might think that for this you could add some kind of โ€œfloating-axis objectโ€ to deliver automatic self-expansion graphics, but I couldn't decide how to do it (and, I think, I still couldn't โ€œt )

NTN

+1
source share

Here's a refined version of @Siyh's answer, which adds:

  • automatic UTM zone selection
  • buffer behind text / bar that is displayed on the background
  • North arrow

Notes:

  • If you do not use UTM for your axes, the panel will be drawn crooked.
  • The north arrow suggests that the north is up.

the code:

 import os import cartopy.crs as ccrs from math import floor import matplotlib.pyplot as plt from matplotlib import patheffects import matplotlib if os.name == 'nt': matplotlib.rc('font', family='Arial') else: # might need tweaking, must support black triangle for N arrow matplotlib.rc('font', family='DejaVu Sans') def utm_from_lon(lon): """ utm_from_lon - UTM zone for a longitude Not right for some polar regions (Norway, Svalbard, Antartica) :param float lon: longitude :return: UTM zone number :rtype: int """ return floor( ( lon + 180 ) / 6) + 1 def scale_bar(ax, proj, length, location=(0.5, 0.05), linewidth=3, units='km', m_per_unit=1000): """ http://stackoverflow.com/a/35705477/1072212 ax is the axes to draw the scalebar on. proj is the projection the axes are in location is center of the scalebar in axis coordinates ie. 0.5 is the middle of the plot length is the length of the scalebar in km. linewidth is the thickness of the scalebar. units is the name of the unit m_per_unit is the number of meters in a unit """ # find lat/lon center to find best UTM zone x0, x1, y0, y1 = ax.get_extent(proj.as_geodetic()) # Projection in metres utm = ccrs.UTM(utm_from_lon((x0+x1)/2)) # Get the extent of the plotted area in coordinates in metres x0, x1, y0, y1 = ax.get_extent(utm) # Turn the specified scalebar location into coordinates in metres sbcx, sbcy = x0 + (x1 - x0) * location[0], y0 + (y1 - y0) * location[1] # Generate the x coordinate for the ends of the scalebar bar_xs = [sbcx - length * m_per_unit/2, sbcx + length * m_per_unit/2] # buffer for scalebar buffer = [patheffects.withStroke(linewidth=5, foreground="w")] # Plot the scalebar with buffer ax.plot(bar_xs, [sbcy, sbcy], transform=utm, color='k', linewidth=linewidth, path_effects=buffer) # buffer for text buffer = [patheffects.withStroke(linewidth=3, foreground="w")] # Plot the scalebar label t0 = ax.text(sbcx, sbcy, str(length) + ' ' + units, transform=utm, horizontalalignment='center', verticalalignment='bottom', path_effects=buffer, zorder=2) left = x0+(x1-x0)*0.05 # Plot the N arrow t1 = ax.text(left, sbcy, u'\u25B2\nN', transform=utm, horizontalalignment='center', verticalalignment='bottom', path_effects=buffer, zorder=2) # Plot the scalebar without buffer, in case covered by text buffer ax.plot(bar_xs, [sbcy, sbcy], transform=utm, color='k', linewidth=linewidth, zorder=3) if __name__ == '__main__': ax = plt.axes(projection=ccrs.Mercator()) plt.title('Cyprus') ax.set_extent([31, 35.5, 34, 36], ccrs.Geodetic()) ax.stock_img() ax.coastlines(resolution='10m') scale_bar(ax, ccrs.Mercator(), 100) # 100 km scale bar # or to use m instead of km # scale_bar(ax, ccrs.Mercator(), 100000, m_per_unit=1, units='m') # or to use miles instead of km # scale_bar(ax, ccrs.Mercator(), 60, m_per_unit=1609.34, units='miles') plt.show() 

Demo image of Cyprus map with a scale.

+1
source share

All Articles