Creating beautiful geographical maps with Python

Sometimes, you want to plot your data on a world map. In Python, we can make use of Basemap from the Matplotlib toolkits to do just that!

This post will give you a basic rundown of some of the basic functions to get you started.

What you need

Required:
- matplotlib
- mpl_toolkits.basemap

Optional:
- pandas

Building a basic map

First, let’s create a Basemap object instance. We can specify some useful constructor arguments to configure the map.

We can specify the resolution of the coastlines and lakes using the resolution argument. Default to c for crude, l for low, i for intermediate, h for high, and f for full.

We can specify the projection of the map using the projection argument, which describes what transformations to employ to represent the two-dimensional curved surface of the globe on a plane. The manual shows a list of projections available.

We can also specify which part of the globe to plot by defining the coordinates of a bounding box. The llcrnrlon and llcrnrlat defines the lower-left corner geographical longitude and latitude. The urcrnrlon and urcrnrlat defines the upper-right corner geographical longitude and latitude.

import matplotlib.pyplot as plt
from mpl_toolkits.basemap import Basemap

fig, ax = plt.subplots(figsize=(14, 10))

# Drawing the base map
# The lat long bounding box bounds the United States
map = Basemap(resolution="i", projection="merc", 
              llcrnrlat=22, llcrnrlon=-130, urcrnrlat=54, urcrnrlon=-60)

# This is just so we can see something on the plot
# We will explain this later
map.drawcoastlines()

plt.show()

Basemap

Map background

After creating a map instance, the map is still empty, we need to draw the background of the map. By calling different map background methods, we can draw the coastlines, country lines, state lines, fill in colors, etc. Here, we will go through a few common and useful ones.

drawcoastlines

This draws the coastlines of the map.

import matplotlib.pyplot as plt
from mpl_toolkits.basemap import Basemap

fig, ax = plt.subplots(figsize=(14, 10))

map = Basemap(resolution="i")
map.drawcoastlines()

plt.show()

drawcoastlines

drawcountries

This draws the country lines of the map.

import matplotlib.pyplot as plt
from mpl_toolkits.basemap import Basemap

fig, ax = plt.subplots(figsize=(14, 10))

map = Basemap(resolution="i")
map.drawcountries()

plt.show()

drawcountries

drawstates

This draws the Americans and Australian state lines.

import matplotlib.pyplot as plt
from mpl_toolkits.basemap import Basemap

fig, ax = plt.subplots(figsize=(14, 10))

map = Basemap(resolution="i")
map.drawstates()

plt.show()

drawstates

drawrivers

This draws the rivers on the map.

import matplotlib.pyplot as plt
from mpl_toolkits.basemap import Basemap

fig, ax = plt.subplots(figsize=(14, 10))

map = Basemap(resolution="i")
map.drawrivers()

plt.show()

drawrivers

drawlsmask

This fills in the colors of the ocean, lakes, and lands on the map.

import matplotlib.pyplot as plt
from mpl_toolkits.basemap import Basemap

fig, ax = plt.subplots(figsize=(14, 10))

map = Basemap(resolution="i")
map.drawlsmask(land_color="#ECCB98", ocean_color="#7FCDFF")

plt.show()

drawlsmask

shadedrelief

Use the shaded relief image to plot the lands and oceans.

import matplotlib.pyplot as plt
from mpl_toolkits.basemap import Basemap

fig, ax = plt.subplots(figsize=(14, 10))

map = Basemap(resolution="i")
map.shadedrelief()

plt.show()

shadedrelief

Combining the methods

Here is a sample of a filled-in map for the sections below.

import matplotlib.pyplot as plt
from mpl_toolkits.basemap import Basemap

fig, ax = plt.subplots(figsize=(14, 10))

map = Basemap(resolution="i", projection="merc", 
              llcrnrlat=22, llcrnrlon=-130, urcrnrlat=54, urcrnrlon=-60)
map.drawcoastlines()
map.drawcountries(linewidth = 2)
map.drawstates(color='0.2')
map.drawlsmask(land_color="#ECCB98", 
               ocean_color="#7FCDFF")

plt.show()

Map Background

There is a lot more you can do with the map background, which you can check out from the manual.

Plotting the data

Now that we have the basis of a map set up, we can begin plotting any data that we want.

Plotting the number of flights per airport

Here, I have a pandas DataFrame of the U.S. domestic flight records from Jan 2019 to Mar 2019. The DataFrame consists of the IATA code (a unique identifier of the airports), the latitude and longitude of each airport, and the number of flights that departed from each airport in these three months.

Data

Our goal here is to plot each airport as a dot on the map and vary the size of the dots based on the number of flights departing from the airport.

First, we need to convert the longitude and latitude into the x and y coordinates for the plot. To this end, we should use the map object that we created to convert the longitude and latitude into the appropriate plot coordinates.

# Converting the longitude and latitude into x and y coordinates
x, y = map(lon, lat)

To plot the coordinates, we can call the plot method with the map object. We may indicate plot arguments to stylize the plot in a similar way to Matplotlib.

map.plot(x, y)

Here is how I plotted each airport and the number of flights.

import math
import matplotlib.pyplot as plt
from mpl_toolkits.basemap import Basemap

# Setting up colors and legends
colors = ["#3d85c6", "#6aa84f", "#f1c232", "#e69138", "#cc0000"]
labels = []
for i in range(5):
    s = 10**i
    e = 10**(i+1)-1
    labels.append(f"{s} - {e}")

# Plot map
fig, ax = plt.subplots(figsize=(14, 10))
map = Basemap(resolution="i", projection="merc", 
              llcrnrlat=22, llcrnrlon=-130, urcrnrlat=54, urcrnrlon=-60)
map.drawcoastlines()
map.drawcountries(linewidth = 2)
map.drawstates(color="0.2")
map.drawlsmask(land_color="#ECCB98", 
               ocean_color="#7FCDFF")

# Iterate each airport
for i, row in airport_count_df.iterrows():
    # Convert longitude and latitude into plot coordinates
    x, y = map(row["LON"], row["LAT"])
    idx = int(math.log10(row["FLIGHT_COUNT"]))
    map.plot(x, y, color=colors[idx], marker="o", markersize=idx*1.3+5, 
             markeredgewidth=1, markeredgecolor="#000000", label=labels[idx], zorder=5)

# Create legends
handles, labels = plt.gca().get_legend_handles_labels()
by_label = dict(zip(labels, handles))
plt.legend(by_label.values(), by_label.keys(), loc=1, 
           title="Number Of Flights", title_fontsize=12, prop={"size": 12})

plt.show()

Map

Plotting the number of flights between airports

Here, I have a pandas DataFrame of the U.S. domestic flight records from Jan 2019 to Mar 2019. The DataFrame consists of the flight origin and destination airport IATA codes (a unique identifier of the airports), the flight origin and destination latitude and longitude, and the number of flights that flew between the two airports in these three months.

Data

Using this, we can plot the routes between airports. To this end, our plot method should take a list of coordinates to plot a line. Here is the full code.

import matplotlib.pyplot as plt
from mpl_toolkits.basemap import Basemap

# Set up colors and labels
colors = ["#3d85c6", "#6aa84f", "#f1c232", "#e69138", "#cc0000"]
size_range = [1, 10, 100, 1000, 2000]
labels = ["1 - 9", "10 - 99", "100 - 999", "1000 - 1999", ">= 2000"]

# Plot map
fig, ax = plt.subplots(figsize=(14, 10))
map = Basemap(resolution="i", projection="merc", 
              llcrnrlat=22, llcrnrlon=-130, urcrnrlat=54, urcrnrlon=-60)
map.drawcoastlines()
map.drawcountries(linewidth = 2)
map.drawstates(color="0.2")
map.drawlsmask(land_color="#ECCB98", 
               ocean_color="#7FCDFF")

# Iterate each airport
for i, row in airport_count_df.iterrows():
    x, y = map(row["LON"], row["LAT"])
    map.plot(x, y, color="#cc0000", marker="o", markersize=7, 
             markeredgewidth=1, markeredgecolor="#000000", zorder=5)

# Iterate each route
for i, row in flight_route_df.iterrows():
    x, y = map([row["ORIGIN_LON"], row["DEST_LON"]], 
               [row["ORIGIN_LAT"], row["DEST_LAT"]])
    idx = [i for i, size in enumerate(size_range) if row["FLIGHT_COUNT"] >= size][-1]
    map.plot(x, y, color=colors[idx], 
             linewidth=idx*0.6+0.4, alpha=0.5, label=labels[idx], zorder=5)

# Create legends
handles, labels = plt.gca().get_legend_handles_labels()
by_label = dict(zip(labels, handles))
plt.legend(by_label.values(), by_label.keys(), loc=1, 
           title="Number Of Flights", title_fontsize=14, prop={"size": 14})

plt.show()

Map

For more plotting options, please take a look at the documentation.

Conclusion

Hope this post helps you gain a quick understanding of how to plot your map data. You can do so much more with this tool, I encourage you to explore the documentation and see what you can come up with. A good plot tells a good story, in both senses of the sentence.