Sam Hooke

Jupyter: plot multiple layers on a single map

TL;DR: Create a map (m) in a Jupyter Notebook by calling m = explore(...) on a geopandas.GeoDataFrame. Add multiple layers to the map by passing the m object back in for each layer, e.g. m = explore(m=m, ...).

Otherwise, follow through step-by-step as we create a Jupyter notebook with a kernel, load in some data from Excel, and then plot it as multiple layers on an interactive map.

Setting up Jupyter in VS Code §

Creating the kernel for Jupyter §

Either create the kernel via the Select Kernel option in VS Code, or manually create your own custom kernel:

python3 -m venv .venv
source .venv/bin/activate
pip install ipykernel

Selecting a custom kernel from VS Code §

In VS Code, go to:

Select KernelInstall/Enable selected extensionsPython Environments…

The venv created above should be listed.

If your Jupyter kernel is not listed, restart VS Code.

Preparing the data §

Load data from Excel into Jupyter notebook §

# pip install pandas openpyxl
import pandas as pd
pdf = pd.read_excel("path/to/data.xlsx")
pdf.head()
PosLatPosLonPosAltVelocity
052.1234560.1234560.0000000.0
152.1234550.12345510.0000002.0
252.1234440.12344420.0000004.0
352.1233330.12333330.0000008.0
452.1222220.12222220.0000004.0
552.1111110.11111110.0000002.0

5 x 6000 columns

Ensure VS Code displays plots inline §

VS Code should automatically display plots inline, but I found that was not always the case. This ✨magic✨ bit of code ensures VS Code will plot inline:

get_ipython().run_line_magic("matplotlib", "inline")

Plot the raw data (not on a map) §

As a basic check, just perform a scatter plot of the data:

pdf.plot(x="PosLon", y="PosLat", kind="scatter")

Trim the data §

If there are data points at the beginning and/or end of the data frame which we do not want to plot, they can be removed:

start = 100
end = 5900
pdf = pdf.loc[start:end]

Sample the data (keep every Nth row) §

If the data frame is very big, it may be useful to only plot every 100th point:

nth = 100
pdf = pdf.loc[::nth, :]

Plotting multiple layers on a single map §

Plot a single dataframe on a map §

Now we can plot the data on a map:

# pip install folium matplotlib mapclassify
import geopandas as gpd
from shapely.geometry import Point

# Convert the pdf object into a list of Point objects called geometry.
geometry = [Point(xy) for xy in zip(pdf["PosLon"], pdf["PosLat"])]

# Create a gdf (Geo Data Frame) using the pdf object and the geometry. Specify
# WGS84 using the EPSG identifier 4326.
gdf = gpd.GeoDataFrame(data=pdf, geometry=geometry, crs="EPSG:4326")

# Create a map from the gdf, and plot the "PosAlt" column with the name
# "Altitude (m)".
m = gdf.explore(column="PosAlt", cmap="plasma", name="Altitude (m)")

# Return the map so Jupyter will display it inline.
m

Plot another dataframe on the same map §

Now we can plot more data on the same map:

# Create a map from the gdf, and plot the "Velocity" column with the name
# "Velocity (m/s)".
# Note:
# * The kwarg `m=m`: THIS IS IMPORTANT! It means we add to the existing map.
# * The kwarg `show=True`: This means the dataframe is initially hidden. This
#   will make sense after the next code snippet.
m = gdf.explore(m=m, column="Velocity", cmap="plasma", name="Velocity (m/s)", show=False)
m

We can then add a widget to the map which lets the users toggle show for each layer:

import folium
folium.LayerControl().add_to(m)

We’ve added two layers:

  • Altitude (m), which defaulted to show=True.
  • Velocity (m/s), which we explicitly set to show=False.

So the map will show the velocity layer by default, but the user can toggle each layer independently for visibility.

Summary §

  • When calling explore on a GeoDataFrame to create a map:
    • Create the first layer as normal, and store the resulting map object m.
    • For all subsequent layers, use the kwarg m=m to pass in the previous layer, and build upon the existing map object.
  • (Optional) Set show=True/False so that layers are initially visible/hidden as desired.
  • (Optional) Add a folium.LayerControl to provide users a widget for toggling layer visibility.

Full example §

Putting it all together:

# pip install pandas geopandas matplotlib folium mapclassify
import geopandas as gpd
from shapely.geometry import Point
import folium

pdf = ...  # Your pandas.DataFrame object

# The points to plot
geometry = [Point(xy) for xy in zip(pdf["PosLon"], pdf["PosLat"])]
gdf = gpd.GeoDataFrame(data=pdf, geometry=geometry, crs="EPSG:4326")

# The layers
m = gdf.explore(column="PosAlt", cmap="plasma", name="Altitude (m)", show=True)
m = gdf.explore(m=m, column="Velocity", cmap="plasma", name="Velocity (m/s)", show=False)
m = gdf.explore(m=m, column="Layer3", cmap="plasma", name="Layer3 (blah)", show=False)
m = gdf.explore(m=m, column="Layer4", cmap="plasma", name="Layer4 (blah)", show=False)
m = gdf.explore(m=m, column="Layer5", cmap="plasma", name="Layer4 (blah)", show=False) # etc...

folium.LayerControl().add_to(m)

Further reading §