Reproducible Modifiable Inset Maps

Over the weekend, I starting having a look at generating static maps (rather than interactive web maps) using matplotlib/Basemap, with one eye on the reproducible educational materials production idea, and the ways in which Basemap might be useful to authors creating new materials that are capable of being reused and/or maintained, with modification.

One of the things that struck me was how authors may want to produce different sorts of map. Basemap has importers for several different flavours of map tile that can essentially be treated as map styles, so once you have defined your map, you should be able to tile it in different ways without having to redefine the map.

It’s also worth noting how easy it is to change the projection…

Another thing that struck me was how maps-on-maps (inset maps?) can often help provide a situate the wider geographical context of a region that is being presented in some detail.

I couldn’t offhand find an off the shelf function to create inset maps, so I hacked my own together:

There are quite a few hardwired defaults baked in and the generator could be parameterised in all sorts of ways (eg changing plot size, colour themes, etc.)

Also on my to do list for the basic maps is a simple way of adding things like great circle connectors between two points, adding clear named location points, etc etc.

You can find my work in progress/gettingstarted demos on this theme on Azure Notebooks here.

If you’re interested, here’s what I came up with for the inset maps… It’s longer than it needs to be because it incorporates various bits and pieces for rendering default / demo views if no actual regions are specified.

from operator import itemgetter

from mpl_toolkits.basemap import Basemap
import matplotlib.pyplot as plt
from mpl_toolkits.axes_grid1.inset_locator import zoomed_inset_axes
from mpl_toolkits.axes_grid1.inset_locator import mark_inset
import numpy as np

def createInsetMap(base=None, inset=None,
                   zoom=None, loc=1, baseresolution=None,
                   insetresolution=None, connector=True):

    ''' Create an inset map for a particular region. '''

    fig = plt.figure()
    ax = fig.add_subplot(111)

    #Create a basemap

    #Add some default logic to provide some demos
    #If no args, use a World map
    if base is None or base.lower=='world':
        baseargs={'projection':'cyl','lat_0':0, 'lon_0':0}
    #If set UK base, then we can add a more detailed inset...
    elif isinstance(base,str) and base.lower()=='uk':
        baseargs={'llcrnrlon':ukllcrnrlon,'llcrnrlat':ukllcrnrlat,
                  'urcrnrlon':ukurcrnrlon,'urcrnrlat':ukurcrnrlat,
                  'resolution':'l'}
    else:
        #should really check base is a dict
        baseargs=base

    if baseresolution is not None:
        baseargs.update({'resolution':baseresolution})

    map1=Basemap(**baseargs)

    map1.drawmapboundary(fill_color='#f8f8ff')
    map1.drawcoastlines()

    plt.xticks(visible=False)
    plt.yticks(visible=False)

    #Now define the inset map

    #This default is to make for some nice default demos
    #With no explicit settings, inset UK on World map
    if base is None and inset is None:
        insetargs={'llcrnrlon':ukllcrnrlon,'llcrnrlat':ukllcrnrlat,
                  'urcrnrlon':ukurcrnrlon,'urcrnrlat':ukurcrnrlat,
                  'resolution':'l'}
        zoom=10
        loc=3
    #If the base is UK, and no inset is selected, demo an IW inset
    elif (isinstance(base,str) and base.lower()=='uk') and inset is None:
        insetargs={'llcrnrlon':iwllcrnrlon,'llcrnrlat':iwllcrnrlat,
                   'urcrnrlon':iwurcrnrlon,'urcrnrlat':iwurcrnrlat,
                   'resolution':'h'}
        zoom=10
        loc=2
    else:
        #Should really check inset is a dict...
        insetargs=inset

    axins = zoomed_inset_axes(ax, zoom, loc=loc)

    #The following seem to be set automatically?
    #axins.set_xlim(llcrnrlon, llcrnrlat)
    #axins.set_ylim(llcrnrlat, urcrnrlat)

    if insetresolution is not None:
        insetargs.update({'resolution':insetresolution})

    map2 = Basemap(ax=axins,**insetargs)

    #map2.drawmapboundary(fill_color='#7777ff')
    map2.fillcontinents(color='#ddaabb', lake_color='#7777ff', zorder=0)
    map2.drawcoastlines()
    map2.drawcountries()

    #Add connector lines from marked area on large map to zoomed area
    if connector:
        mark_inset(ax, axins, loc1=2, loc2=4, fc="none", ec="0.5")

    plt.show()

So, what other fragments of workflow, or use case, might be handy when creating (reusable / modifiable) maps in an educational context?

PS inset diagrams/maps in ggplot2 / ggmaps. See also Inset maps with ggplot2.

Author: Tony Hirst

I'm a Senior Lecturer at The Open University, with an interest in #opendata policy and practice, as well as general web tinkering...

%d bloggers like this: