Basemap in 3D¶
Even though many people don’t like them, maps with 3d elements can be created using basemap and the matplotlib mplot3d toolkit.
Creating a basic map¶
The most important thing to know when starting with 3d matplotlib plots is that the Axes3D class has to be used. To add geographical data to the map, the method add_collection3d will be used:
import matplotlib.pyplot as plt
from mpl_toolkits.mplot3d import Axes3D
from mpl_toolkits.basemap import Basemap
map = Basemap()
fig = plt.figure()
ax = Axes3D(fig)
'''
ax.azim = 270
ax.elev = 90
ax.dist = 5
'''
ax.add_collection3d(map.drawcoastlines(linewidth=0.25))
ax.add_collection3d(map.drawcountries(linewidth=0.35))
plt.show()
The ax variable is in this example, an Axes3D instance. All the methods will be used from this instance, so they need to support 3D operations, which doesn’t occur in many cases on the basemap methods
The commented block shows how to rotate the resulting map so the view is better
To draw lines, just use the add_collection3d method with the output of any of the basemap methods that return an matplotlib.patches.LineCollection object, such as drawcountries
Filling the polygons¶
Unfortunately, the basemap fillcontinents method doesn’t return an object supported by add_collection3d (PolyCollection, LineColleciton, PatchCollection), but a list of matplotlib.patches.Polygon objects.
The solution, of course, is to create a list of PolyCollection:
import matplotlib.pyplot as plt
from mpl_toolkits.mplot3d import Axes3D
from mpl_toolkits.basemap import Basemap
from matplotlib.collections import PolyCollection
map = Basemap()
fig = plt.figure()
ax = Axes3D(fig)
ax.azim = 270
ax.elev = 50
ax.dist = 8
ax.add_collection3d(map.drawcoastlines(linewidth=0.25))
ax.add_collection3d(map.drawcountries(linewidth=0.35))
polys = []
for polygon in map.landpolygons:
polys.append(polygon.get_coords())
lc = PolyCollection(polys, edgecolor='black',
facecolor='#DDDDDD', closed=False)
ax.add_collection3d(lc)
plt.show()
The coast lines and the countries are drawn as in the previous example
To create the PolyCollection, the polygons are needed, but the Basemap object has it in the field landpolygons. (There are others for the rest of the included polygons, such as the countries)
For each of the polygons, the coordinates can be retrieved as a list of floats using the get_coords method. (he are _geoslib.Polygon objects)
Once a list of coordinates list is created, the PoilyCollection can be built
The PolyCollection is added using add_collection3d, as we did with the lines
If the original polygons are added using fillcontinents, matplotlib says that doesn’t has the methods to convert it to 3D
Adding 3D bars¶
Creating a 3D map hasn’t got sense if no 3D data is drawn on it. The Axes3D class has the bar3d method that draws 3D bars. It can be added on the map using the 3rd dimension:
import matplotlib.pyplot as plt
from mpl_toolkits.mplot3d import Axes3D
from mpl_toolkits.basemap import Basemap
from matplotlib.collections import PolyCollection
import numpy as np
map = Basemap(llcrnrlon=-20,llcrnrlat=0,urcrnrlon=15,urcrnrlat=50,)
fig = plt.figure()
ax = Axes3D(fig)
ax.set_axis_off()
ax.azim = 270
ax.dist = 7
polys = []
for polygon in map.landpolygons:
polys.append(polygon.get_coords())
lc = PolyCollection(polys, edgecolor='black',
facecolor='#DDDDDD', closed=False)
ax.add_collection3d(lc)
ax.add_collection3d(map.drawcoastlines(linewidth=0.25))
ax.add_collection3d(map.drawcountries(linewidth=0.35))
lons = np.array([-13.7, -10.8, -13.2, -96.8, -7.99, 7.5, -17.3, -3.7])
lats = np.array([9.6, 6.3, 8.5, 32.7, 12.5, 8.9, 14.7, 40.39])
cases = np.array([1971, 7069, 6073, 4, 6, 20, 1, 1])
deaths = np.array([1192, 2964, 1250, 1, 5, 8, 0, 0])
places = np.array(['Guinea', 'Liberia', 'Sierra Leone','United States', 'Mali', 'Nigeria', 'Senegal', 'Spain'])
x, y = map(lons, lats)
ax.bar3d(x, y, np.zeros(len(x)), 2, 2, deaths, color= 'r', alpha=0.8)
plt.show()
The map is zoomed to fit the needs of the Ebola cases dataset
The axes are eliminated with the method set_axis_off
The bar3d needs the x, y and z positions, plus the delta x, y and z. To be properly drawn, the z position must be 0, and the delta z, the final value