Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Scale bar #490

Open
ThomasLecocq opened this issue Oct 7, 2014 · 20 comments · May be fixed by #1728
Open

Scale bar #490

ThomasLecocq opened this issue Oct 7, 2014 · 20 comments · May be fixed by #1728

Comments

@ThomasLecocq
Copy link
Contributor

Hi all

is there a way to add a scale bar in a corner of the plots ?

Thanks

Tom

@rhattersley rhattersley changed the title [feature] Scale bar ? Scale bar Oct 7, 2014
@rhattersley
Copy link
Member

Hi @ThomasLecocq. There's no simple way to do this right now. If there were, would it need to be interactive, or would a static version be OK to start with?

@ThomasLecocq
Copy link
Contributor Author

I'd say a static version would be a great start.. :)

@esc24
Copy link
Member

esc24 commented Oct 7, 2014

I think @pp-mo might have looked at this.

@pp-mo
Copy link
Member

pp-mo commented Oct 7, 2014

I think @pp-mo might have looked at this.

Found it !
(wasn't easy)
But it is quite clunky, and also rather old...
Take a look here : http://nbviewer.ipython.org/github/pp-mo/iris_example_code/blob/cartopy_scalebar/map_scalebar.ipynb

@ThomasLecocq
Copy link
Contributor Author

👍

@gauteh
Copy link

gauteh commented Sep 10, 2015

I'd really like to see something like this too.

@sebhahn
Copy link

sebhahn commented Oct 25, 2017

anybody working on this feature?

@bladeoflight16
Copy link

There's an existing library for adding a scalebar to matplotlib that might be of help: https://pypi.python.org/pypi/matplotlib-scalebar.

@eogit
Copy link

eogit commented Mar 27, 2018

This function may be useful:

# plot a scale bar with 4 subdivisions on the left side of the map
def scale_bar_left(ax, bars=4, length=None, location=(0.1, 0.05), linewidth=3, col='black'):
    """
    ax is the axes to draw the scalebar on.
    bars is the number of subdivisions of the bar (black and white chunks)
    length is the length of the scalebar in km.
    location is left side of the scalebar in axis coordinates.
    (ie. 0 is the left side of the plot)
    linewidth is the thickness of the scalebar.
    color is the color of the scale bar
    """
    # Get the limits of the axis in lat long
    llx0, llx1, lly0, lly1 = ax.get_extent(ccrs.PlateCarree())
    # Make tmc aligned to the left of the map,
    # vertically at scale bar location
    sbllx = llx0 + (llx1 - llx0) * location[0]
    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, sbx + length * 1000 / bars]
    # Plot the scalebar chunks
    barcol = 'white'
    for i in range(0, bars):
        # plot the chunk
        ax.plot(bar_xs, [sby, sby], transform=tmc, color=barcol, linewidth=linewidth)
        # alternate the colour
        if barcol == 'white':
            barcol = 'dimgrey'
        else:
            barcol = 'white'
        # Generate the x coordinate for the number
        bar_xt = sbx + i * length * 1000 / bars
        # Plot the scalebar label for that chunk
        ax.text(bar_xt, sby, str(round(i * length / bars)), transform=tmc,
                horizontalalignment='center', verticalalignment='bottom',
                color=col)
        # work out the position of the next chunk of the bar
        bar_xs[0] = bar_xs[1]
        bar_xs[1] = bar_xs[1] + length * 1000 / bars
    # Generate the x coordinate for the last number
    bar_xt = sbx + length * 1000
    # Plot the last scalebar label
    ax.text(bar_xt, sby, str(round(length)), transform=tmc,
            horizontalalignment='center', verticalalignment='bottom',
            color=col)
    # Plot the unit label below the bar
    bar_xt = sbx + length * 1000 / 2
    bar_yt = y0 + (y1 - y0) * (location[1] / 4)
    ax.text(bar_xt, bar_yt, 'km', transform=tmc, horizontalalignment='center',
            verticalalignment='bottom', color=col)

@mephph
Copy link

mephph commented Jul 16, 2018

I wrote something more flexible and accurate using the new geodesic module: https://stackoverflow.com/a/50674451/2676166 . I'm modifying it to allow alternating colours like most scale bars have, and a few other things. When it's working I'll make a pull request.

@theroggy
Copy link

theroggy commented Jun 8, 2021

The pull request looks to be nearly ready... seems like it might be coming soon?

@PhilipeRLeal
Copy link

The pull request looks to be nearly ready... seems like it might be coming soon?

Dear Theroggy,

which Pull Request are you referring to?

If you see some missing attributes, or potential errors, let us know so we may help solve them, once for all.

Sincerely,

@theroggy
Copy link

Hey PhilipeRLeal, I was referring to #1728

@eogit
Copy link

eogit commented Jun 10, 2021 via email

@raphaelquast
Copy link
Contributor

Hey all, since there has not been a lot of progress on this in the last year, I thought I mention what I've created for EOmaps (... an interactivity-layer on top of matplotlib/cartopy I've been developing for quite some time now)

Initially I've tried to have a look at the existing pull-requet (#1728) but since it shows 28 changed files I got lost and did not investigate this any further... I'm not sure what the status of #1728 is and whether or not it is feasible to propose a completely new implementation...

anyway, here's what I have:

Right now, the scalebar implementation is still bound to the features of EOmaps (to make it draggable etc.) but it should be able to adapted it as an independent, static scalebar to be used directly with cartopy.

The scalebar supports the following features:

  • it works in any cartopy projection
  • it is based on pyproj.geod - the scalebar simply follows geodesic lines
    • while some might think this is a strange choice, it greatly simplifies the implementation and at the same time allows to nicely visualize the adherent distortion of a projection
  • it updates itself dynamically if you drag it around on the map
  • it's fully customizable... you can adjust:
    • position and orientation
    • the way how the text is displayed (labelling-interval, font-properties, ...)
    • the appearance of the bounding box (size, facecolor, edgecolor, ...)
    • the sequence of the ruler segments (colors, repetition-cycle, ...)

... and here's how it looks like:

EOmaps example image 1

@PhilipeRLeal
Copy link

Dear all, I would like to know whether anyone needs any help or can give some support for adhering to RaphaelQuast's solution. Sincerely,

@dopplershift
Copy link
Contributor

In general, it looks great. Without even a draft PR, I can't evaluate what the consequences of trying to add it would be.

@PhilipeRLeal
Copy link

Before any attempt to merge the two packages (or at least the scalebar functionality into cartopy's), I believe that one must decide how the scalebar should be provided to the user. There seem to be two possibilities here: a) the scalebar will be added as an extension module to cartopy, and, therefore, one would have to import it directly from cartopy package, and instantiate the class with respect to an already instantiated matplotlib.geoaxes object; b) the scalebar instance would be instantiated from a matplotlib.geoaxes extention method, therefore, directly from the geoaxes instance itself. From a Geographic Information System (GIS) point of view, the second approach (option b) seems more logical; after all, a scalebar is an object linked (or makes reference) to a map (in this case, the matplotlib.geoaxes), and not the other way around. Assuming that this option (b) is the one to go, I believe that the scalebar integration into cartopy's package will be straightforward if the SOLID principles are followed correctly.

Please, let me know your thoughts in this regard.

@jolivetr
Copy link

jolivetr commented Jan 20, 2023

Option (b) sounds perfect. Simpler and probably easier to use for random people like me.

I hope my post revives the discussion (I haven't found a scalebar in cartopy yet...)

@scottstanie
Copy link

This package seems to work well for simple cases where you can specify the pixel size in SI units, and it adapts when you zoom in to the plot https://github.com/ppinard/matplotlib-scalebar

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Projects
None yet
Development

Successfully merging a pull request may close this issue.