How can I show a km ruler on a cartopy / matplotlib plot?

后端 未结 5 1111
温柔的废话
温柔的废话 2020-12-25 14:52

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

5条回答
  •  再見小時候
    2020-12-25 15:17

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

提交回复
热议问题