Search code examples
pythoncartopy

Cartopy: Loop through multiple extents without regenerating map image/Deleting Gridliner Object


I am trying to plot source and receiver points on a map in cartopy, where I zoom into the map based on the locations of the source and receiver. For each combination of source and receiver, I have a new map, with a new extent. I would like to find a way where I can download the map image once, and then just change the extent each time so I don't have to load in the map image every time (I have to make thousands of these). All of the events take place in one country, so I am hoping to just download the map of the whole country and then just change the extent for each loop through.

I have actually partially solved this problem, but I ran into an issue with the grid. Initially, I first set the extent to the whole country, loaded in the image, and then saved it. Then I could reset the extent for each source receiver pair, plot the two points, and then save the image. Afterwards, I just needed to erase each of the two points, and then I could set the extent another time.

However, once I added in the grid with latitude and longitude labels, I ran into an issue. I can't erase the grid and labels after each iteration, so they end up just layering on top of eachother, and the labels start to move all over the figure. The only way I can get rid of the grid is by clearing the axis, but that erases the map image.

My question: is there a way to just erase a gridliner object (not hide the labels and grid but actually delete)? If not, is there a different way to load in the image in the beginning so I can clear the axis each time, but not have to completely reload in a new tiler?

import matplotlib.pyplot as plt
import cartopy.crs as ccrs
from cartopy.io.img_tiles import Stamen

whole_extent = [18,42,8,36]
tiler = Stamen('terrain-background')

fig = plt.figure(figsize=(13.75,10))
ax1 = plt.axes(projection=ccrs.PlateCarree())
ax1.set_extent(whole_extent, crs=ccrs.PlateCarree())
ax1.add_image(tiler,6)
#Need to save initial figure to lock in initial picture, otherwise it locks in the extent from the first loop and any map that goes outside the extent of the first loop is white space
plt.savefig('plots/001_WholeFigure.png')


eventlist=[['A',22,15,30,30],['B',30,15,30,18],['C',38,24,21,15],['D',25,12,28,16],['E',38,32,21,11]]

for i in eventlist:
    eventname = i[0]
    slon = i[1] #source lon
    slat = i[2] #source lat
    lon = i[3] #receiver lon
    lat = i[4] #receiver lat

    print(f'{eventname} in progress')
    
    # Sets the zoomed in extent based on source/receiver locations
    if lon > slon:
        lonmin = slon - 2
        lonmax = lon + 2
    else:
        lonmin = lon - 2
        lonmax = slon + 2

    if lat > slat:
        latmin = slat -2
        latmax = lat + 2
    else:
        latmin = lat - 2
        latmax = slat + 2

    zoomextent = [lonmin,lonmax,latmin,latmax]


    # MAP
    ax1.set_extent(zoomextent, crs=ccrs.PlateCarree())
    receiverloc = ax1.plot(lon,lat,'r*',label='Receiver')
    sourceloc = ax1.plot(slon,slat,'k^',label='Source')
    text = ax1.text(slon+0.1,slat+0.1,eventname)
    legend = ax1.legend(loc="upper right")
    
    gl = ax1.gridlines(crs=ccrs.PlateCarree(),draw_labels=True,
        linewidth=2,color='gray',alpha=0.5,linestyle='--')
    gl.bottom_labels = gl.right_labels = False

    plt.savefig(f'plots/{eventname}.png')

    # Delete the two markers and the text so they don't show up on next loop
    receiverloc.pop(0).remove()
    sourceloc.pop(0).remove()
    text.remove()

I have uploaded two of the images of the plots here, and you can see the problem in 'E'. I can't figure out how to erase the grid, and clearing the axis deletes the map image. A E


Solution

  • Well it's not a great solution since it relies on some private attributes, but this works to add at the end of your loop:

    # Remove all artists created by the gridliner
    for artist in (gl.xline_artists + gl.yline_artists +
                   gl.bottom_label_artists + gl.top_label_artists +
                   gl.left_label_artists + gl.right_label_artists):
        artist.remove()
    
    # Remove the created gridliner from the list
    ax1._gridliners.remove(gl)
    
    # Try to re-connect to the draw event, which since it's already
    # connected will return the existing connection id
    cid = gl.axes.figure.canvas.mpl_connect('draw_event', gl._draw_event)
    
    # Disconnect using that cid
    gl.axes.figure.canvas.mpl_disconnect(cid)
    

    This effectively undoes what ax.gridlines() and creating a Gridliner instance do.