Multiple maps using subplots¶
Drawing multiple maps in the same figure is possible using matplotlib’s subplots. There are several ways to use them, and depending on the complexity of the desired figure, one or other is better:
Creating the axis using subplot directly with add_subplot
Creating the subplots with pylab.subplots
Using subplot2grid
Creating Inset locators
Using add_subplot¶
This is the prefered way to add subplots in most of the examples:
import matplotlib.pyplot as plt
from mpl_toolkits.basemap import Basemap
fig = plt.figure()
ax = fig.add_subplot(211)
ax.set_title("Hammer projection")
map = Basemap(projection='hammer', lon_0 = 10, lat_0 = 50)
map.drawmapboundary(fill_color='aqua')
map.fillcontinents(color='coral',lake_color='aqua')
map.drawcoastlines()
ax = fig.add_subplot(212)
ax.set_title("Robinson projection")
map = Basemap(projection='robin', lon_0 = 10, lat_0 = 50)
map.drawmapboundary(fill_color='aqua')
map.fillcontinents(color='coral',lake_color='aqua')
map.drawcoastlines()
plt.show()
- Before calling the basemap constructor, the fig.add_subplot method is called. The three numbers are:
# The number of rows in the final figure # The number of columns in the final figure # Which axis (subplot) to use, counting from the one top-left axis, as explained at this StackOverflow question
Once the axis is created, the map created later will use it automatically (although the ax argument can be passed to use the selected axis)
A title can be added to each subplot using set_title()
Generating the subplots at the beginning with plt.subplots¶
Using the add_subplot is a bit confusing in my opinion. If the basemap instance is created without the ax argument, the possibility of coding bugs is very high. So, to create the plots at the beginning and using them later, pyplot.subplots can be used:
import matplotlib.pyplot as plt
from mpl_toolkits.basemap import Basemap
fig, axes = plt.subplots(2, 1)
axes[0].set_title("Hammer projection")
map = Basemap(projection='hammer', lon_0 = 10, lat_0 = 50, ax=axes[0])
map.drawmapboundary(fill_color='aqua')
map.fillcontinents(color='coral',lake_color='aqua')
map.drawcoastlines()
axes[1].set_title("Robinson projection")
map = Basemap(projection='robin', lon_0 = 10, lat_0 = 50, ax=axes[1])
map.drawmapboundary(fill_color='aqua')
map.fillcontinents(color='coral',lake_color='aqua')
map.drawcoastlines()
plt.show()
The arguments passed to the subplots method, are the number of rows and columns to be created
The subplots method returns figure object, and a list of the created axes (subplots), where the first element is the one at the top-left position
When creating the basemap instance, the ax argument must be passed, using the created axes
The result is the same as in the previous example
Using subplot2grid¶
When the number of subplots is bigger, or the subplots must have different sizes, subplot2grid or gridspec can be used. Here is an example with subplor2grid:
import matplotlib.pyplot as plt
from mpl_toolkits.basemap import Basemap
from matplotlib.path import Path
import matplotlib.patches as patches
fig = plt.figure()
ax1 = plt.subplot2grid((2,2), (0,0))
ax2 = plt.subplot2grid((2,2), (1,0))
ax3 = plt.subplot2grid((2,2), (0,1), rowspan=2)
map1 = Basemap(projection='ortho', lon_0 = 0, lat_0 = 40, ax=ax1)
map1.drawmapboundary(fill_color='#9999FF')
map1.fillcontinents(color='#ddaa66',lake_color='#9999FF')
map1.drawcoastlines()
map2 = Basemap(projection='cyl', llcrnrlon=-15,llcrnrlat=30,urcrnrlon=15.,urcrnrlat=50., resolution='i', ax=ax2)
map2.drawmapboundary(fill_color='#9999FF')
map2.fillcontinents(color='#ddaa66',lake_color='#9999FF')
map2.drawcoastlines()
map3 = Basemap(llcrnrlon= -1., llcrnrlat=37.5, urcrnrlon=4.5, urcrnrlat=44.5,
resolution='i', projection='tmerc', lat_0 = 39.5, lon_0 = 3, ax=ax3)
map3.drawmapboundary(fill_color='#9999FF')
map3.fillcontinents(color='#ddaa66',lake_color='#9999FF')
map3.drawcoastlines()
#Drawing the zoom rectangles:
lbx1, lby1 = map1(*map2(map2.xmin, map2.ymin, inverse= True))
ltx1, lty1 = map1(*map2(map2.xmin, map2.ymax, inverse= True))
rtx1, rty1 = map1(*map2(map2.xmax, map2.ymax, inverse= True))
rbx1, rby1 = map1(*map2(map2.xmax, map2.ymin, inverse= True))
verts1 = [
(lbx1, lby1), # left, bottom
(ltx1, lty1), # left, top
(rtx1, rty1), # right, top
(rbx1, rby1), # right, bottom
(lbx1, lby1), # ignored
]
codes2 = [Path.MOVETO,
Path.LINETO,
Path.LINETO,
Path.LINETO,
Path.CLOSEPOLY,
]
path = Path(verts1, codes2)
patch = patches.PathPatch(path, facecolor='r', lw=2)
ax1.add_patch(patch)
lbx2, lby2 = map2(*map3(map3.xmin, map3.ymin, inverse= True))
ltx2, lty2 = map2(*map3(map3.xmin, map3.ymax, inverse= True))
rtx2, rty2 = map2(*map3(map3.xmax, map3.ymax, inverse= True))
rbx2, rby2 = map2(*map3(map3.xmax, map3.ymin, inverse= True))
verts2 = [
(lbx2, lby2), # left, bottom
(ltx2, lty2), # left, top
(rtx2, rty2), # right, top
(rbx2, rby2), # right, bottom
(lbx2, lby2), # ignored
]
codes2 = [Path.MOVETO,
Path.LINETO,
Path.LINETO,
Path.LINETO,
Path.CLOSEPOLY,
]
path = Path(verts2, codes2)
patch = patches.PathPatch(path, facecolor='r', lw=2)
ax2.add_patch(patch)
plt.show()
- Each subplot is created with the method subplot2grid. The three possible arguments are:
# The output matrix shape, in a sequence with two elements, the y size and x size # The position of the created subplot in the output matrix # The rowspan or colspan. As in the html tables, the cells can occuy more than one position in the output matrix. The rowspan and colspan arguments can set it, as in the example, where the second column has only one cell occupying two rows
- In the example, the layout is used to show different zoom levels. Each level is indicated with a polygon on the previous map. To create these indicators:
- The position of each corner of the bounding box is calculated
The corners can be retrieved using the xmin, xmax, ymin, ymax fields of the basemap instance (the next map, since this will indicate the zoomed area)
Since the fields are in the projected units, the basemap instance with inverse argument is used. See the Using the Basemap instance to convert units section to see how does it work
The calculated longitudes and latitudes of each corner is passed to the current map instance to get the coordinates in the current map projection. Since we have the values in a sequence, an asterisk is used to unpack them
Once the points are known, the polygon is created using the Path class
Inset locators¶
A small map inside the main map can be created using Inset locators, explained in an other section. The result is better than just creating a small subplot inside the main plot: