1

Matplotlibs basemap module hast the ability to draw a background for a map, with inbuilt functions for some basic maps and the ability to tie into arcgis maps.

Both options work well, but are really slow (see also here), especially the arcgis option.

For a single plot, this is not a big issue, but with multiple plots (around 500 in my case) this becomes quite the waiting exercise, plus, I wouldn't want to send hundreds of requests to arcgis, that always fetch the identical map anyways.

But how do I set up/download a background map once, and keep using it in a loop that is plotting many versions of it?

I have adapted the great circle example from the documentation with an etopo background a a loop through some colors for a test:

from mpl_toolkits.basemap import Basemap
import numpy as np
import matplotlib.pyplot as plt
# create new figure, axes instances.
fig=plt.figure()
ax=fig.add_axes([0.1,0.1,0.8,0.8])
# setup mercator map projection.
m = Basemap(llcrnrlon=-100.,llcrnrlat=20.,urcrnrlon=20.,urcrnrlat=60.,\
            rsphere=(6378137.00,6356752.3142),\
            resolution='l',projection='merc',\
            lat_0=40.,lon_0=-20.,lat_ts=20.)
# nylat, nylon are lat/lon of New York
nylat = 40.78; nylon = -73.98
# lonlat, lonlon are lat/lon of London.
lonlat = 51.53; lonlon = 0.08
# draw great circle route between NY and London
m.drawcoastlines()
m.etopo()
# draw parallels
m.drawparallels(np.arange(10,90,20),labels=[1,1,0,1])
# draw meridians
m.drawmeridians(np.arange(-180,180,30),labels=[1,1,0,1])

colors = ('r','b','k','y')
for p_color in colors:
    m.drawgreatcircle(nylon,nylat,lonlon,lonlat,linewidth=2,color=p_color)
    filename = '%s.png' % p_color
    plt.savefig(filename, dpi=100)

The above works, but only because I'm not closing the plot. With my real figures, I'll quickly run out of memory. If I add a plt.close() at the end of the loop, I loose my background map. Same when I put the the setup of the plot into the loop:

from mpl_toolkits.basemap import Basemap
import numpy as np
import matplotlib.pyplot as plt
# create new figure, axes instances.
# setup mercator map projection.
m = Basemap(llcrnrlon=-100.,llcrnrlat=20.,urcrnrlon=20.,urcrnrlat=60.,\
            rsphere=(6378137.00,6356752.3142),\
            resolution='l',projection='merc',\
            lat_0=40.,lon_0=-20.,lat_ts=20.)
# nylat, nylon are lat/lon of New York
nylat = 40.78; nylon = -73.98
# lonlat, lonlon are lat/lon of London.
lonlat = 51.53; lonlon = 0.08
# draw great circle route between NY and London
m.drawcoastlines()
m.etopo()
# draw parallels
m.drawparallels(np.arange(10,90,20),labels=[1,1,0,1])
# draw meridians
m.drawmeridians(np.arange(-180,180,30),labels=[1,1,0,1])

colors = ('r','b','k','y')
for p_color in colors:
    fig=plt.figure()
    ax=fig.add_axes([0.1,0.1,0.8,0.8])
    m.drawgreatcircle(nylon,nylat,lonlon,lonlat,linewidth=2,color=p_color)
    filename = '%s.png' % p_color
    plt.savefig(filename, dpi=100)
    plt.close()

It only appears to work (ignoring the first option with its memory issues) when I do the whole m.Basemap…, m.etopo stuff into the loop, but this is way too slow.

How can I setup ´m´ once, and keep using it?

I could probably MacGyver something by plotting the background map once, plotting my data onto a transparent background and then combine it with imagemagick, but I feel like there should be a better solution.

JC_CL
  • 2,346
  • 6
  • 23
  • 36

1 Answers1

1

I guess the idea would be to keep the basemap plot and not change it. In the loop add the artist you want to have changing from image to image, save the figure, then remove the artist.

from mpl_toolkits.basemap import Basemap
import numpy as np
import matplotlib.pyplot as plt
# create new figure, axes instances.
# setup mercator map projection.
fig=plt.figure()
ax=fig.add_axes([0.1,0.1,0.8,0.8])
m = Basemap(llcrnrlon=-100.,llcrnrlat=20.,urcrnrlon=20.,urcrnrlat=60.,\
            rsphere=(6378137.00,6356752.3142),\
            resolution='l',projection='merc',\
            lat_0=40.,lon_0=-20.,lat_ts=20.)
# nylat, nylon are lat/lon of New York
nylat = 40.78; nylon = -73.98
# lonlat, lonlon are lat/lon of London.
lonlat = 51.53; lonlon = 0.08
# draw great circle route between NY and London
m.drawcoastlines()
m.etopo()
# draw parallels
m.drawparallels(np.arange(10,90,20),labels=[1,1,0,1])
# draw meridians
m.drawmeridians(np.arange(-180,180,30),labels=[1,1,0,1])

colors = ('r','b','k','y')
for p_color in colors:
    gc = m.drawgreatcircle(nylon,nylat,lonlon,lonlat,linewidth=2,color=p_color)
    filename = 'output{}.png'.format(p_color)
    plt.savefig(filename, dpi=100)
    # remove previous plot from axes
    for line in gc:
        line.remove()
    del gc
ImportanceOfBeingErnest
  • 321,279
  • 53
  • 665
  • 712
  • That works, butrequires adaption for my real figure. Instead of `m.drawgreatcircle`, I'm using `m.scatter`, so its a `'PathCollection' object` that I have to remove and it seems like a simple `del gc` does the trick. However I'm a bit lost at setting up (and removing) additional elements. I have a `plt.colorbar` in it, which, when added in the loop just gets added over and over. Outside the loop, it defaults to the `jet` colormap with no relationship to the real figure. A simple `cb = plt.colorbar`; `del cb` in the loop results in `ValueError: left cannot be >= right`, but works typed solo. – JC_CL Sep 22 '17 at 08:09
  • I cannot interprete your comment fully; do you need further help with the colorbar or is it just a comment about the colorbar not being easily changeable? – ImportanceOfBeingErnest Sep 22 '17 at 08:12
  • Both. I'm just wondering why `del gc` works with the PathCollection, but `del cb` for the colorbar does not. Working on another comment with more right now, I think I fixed it. – JC_CL Sep 22 '17 at 08:19
  • The colorbar is more complex as its sitting in its own axes. Simply deleting it, is not an option. You would instead create a colorbar axes `cax = fig.add_axes([...])` outside the loop, fill it with the colorbar inside the loop, `fig.colorbar(scatter, cax=cax)` and after saving, clear the colorbar axes, `cax.clear()`. – ImportanceOfBeingErnest Sep 22 '17 at 08:25
  • Sometimes comments are just too short. heres a continuation: According to https://stackoverflow.com/questions/5263034/remove-colorbar-from-figure-in-matplotlib setting up the colorbar with `cb=plt.colorbar()` as I guessed anyways is 'correct', but to remove it, its `cb.remove()`. So essentially, when I adapt the example to the real data, its something like `gc=m.scatter()…cb=plt.colorbar()…plt.savefig()…del gc…cb.remove()` Unless I'm doing somethiing stupid, I'd suggest to edit a hint to also remove other stuff into the answer. – JC_CL Sep 22 '17 at 08:25
  • And another comment: 'my' solution seems easier than working with `fig.colorbar(scatter, cax=cax)`. Is it just that, or am I overlooking something? – JC_CL Sep 22 '17 at 08:27
  • What matters here is the memory, right? Did you compare both in terms of memory usage? Otherwise, "easy" is pretty subjective, so feel free to find any solution more easy than another. ;-) – ImportanceOfBeingErnest Sep 22 '17 at 08:32
  • Neither seems to be taking so much that I care, so I guess both are fine. Can you add a remark that one also has to remove other elements (if applicable) as discussed in the comments to your answer? I feel this should be added for the sake of completeness. – JC_CL Sep 22 '17 at 08:45
  • 1
    I honestly feel that is would be obvious that those parts of the plot that should not stay in it in the next loop step need to be removed beforehands. That is what the sentence "..., then remove the artist." says, where artist can be anything you like. – ImportanceOfBeingErnest Sep 22 '17 at 08:57