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, ...)
.
- For those in a hurry, jump straight to plotting multiple layers on a single map
- Or, skip the waffle and look at the full example.
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 Kernel → Install/Enable selected extensions → Python 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()
PosLat | PosLon | PosAlt | Velocity | |
---|---|---|---|---|
0 | 52.123456 | 0.123456 | 0.000000 | 0.0 |
1 | 52.123455 | 0.123455 | 10.000000 | 2.0 |
2 | 52.123444 | 0.123444 | 20.000000 | 4.0 |
3 | 52.123333 | 0.123333 | 30.000000 | 8.0 |
4 | 52.122222 | 0.122222 | 20.000000 | 4.0 |
5 | 52.111111 | 0.111111 | 10.000000 | 2.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 toshow=True
.Velocity (m/s)
, which we explicitly set toshow=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 aGeoDataFrame
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.
- Create the first layer as normal, and store the resulting 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 §
geopandas.GeoDataFrame
akagdf
.geopandas.GeoDataFrame.explore
, which uses Leaflet.pandas.DataFrame
akapdf
.shapely.Point
.- An example map in the folium docs.