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

后端 未结 5 1108
温柔的废话
温柔的废话 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:14

    I think there is no easy potted solution available for this : you must draw it out yourself using graphics elements.

    Some ages ago, I wrote some adaptive code to add a scalebar to an OS grid map of arbitrary scale.
    Not really what you wanted, I think, but it shows the necessary techniques:

    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)
    

    Messy though, isn't it ?
    You'd think it might be possible to add some kind of 'floating Axis object' for this, to deliver an automatic self-rescaling graphic, but I couldn't work out a way of doing that (and I guess I still couldn't).

    HTH

提交回复
热议问题