Static-maps#
Static maps¶
Over the course of the last weeks, we have already become familiar with plotting
basic static maps using
geopandas.GeoDataFrame.plot()
, for
instance in lessons 2,
3, and 4. We also
learned that geopandas.GeoDataFrame.plot()
uses the matplotlib.pyplot
library, and that most of its arguments and options are accepted by
geopandas.
To refresh our memory about the basics of plotting maps, let's create a static accessibility map of the Helsinki metropolitan area, that also shows roads and metro lines (three layers, overlaid onto each other). Remember that the input data sets need to be in the same coordinate system!
Data¶
We will use three different data sets:
- the travel time to the Helsinki railway station we used in lesson
4, which is in
DATA_DIRECTORY / "helsinki_region_travel_times_to_railway_station"
, - the Helsinki Metro network, available via WFS from the city's map services, and
- a simplified network of the most important roads in the metropolitan region, also available via WFS from the same endpoint.
import pathlib
NOTEBOOK_PATH = pathlib.Path().resolve()
DATA_DIRECTORY = NOTEBOOK_PATH / "data"
import geopandas
import numpy
import ssl
ssl._create_default_https_context = ssl._create_unverified_context
accessibility_grid = geopandas.read_file(
DATA_DIRECTORY
/ "helsinki_region_travel_times_to_railway_station"
/ "helsinki_region_travel_times_to_railway_station.gpkg"
)
accessibility_grid["pt_r_t"] = accessibility_grid["pt_r_t"].replace(-1, numpy.nan)
WFS_BASE_URL = (
"https://kartta.hel.fi/ws/geoserver/avoindata/wfs"
"?service=wfs"
"&version=2.0.0"
"&request=GetFeature"
"&srsName=EPSG:3879"
"&typeName={layer:s}"
)
metro = (
geopandas.read_file(
WFS_BASE_URL.format(layer="avoindata:Seutukartta_liikenne_metro_rata")
)
.set_crs("EPSG:3879")
)
roads = (
geopandas.read_file(
WFS_BASE_URL.format(layer="avoindata:Seutukartta_liikenne_paatiet")
)
.set_crs("EPSG:3879")
)
Attention: Coordinate Reference Systems
Remember that different geo-data frames need to be in the same coordinate system before plotting them in the same map.
geopandas.GeoDataFrame.plot()
does not reproject data automatically.You can always check it with a simple
assert
statement.
assert accessibility_grid.crs == metro.crs == roads.crs, "Input data sets’ CRS differs"
--------------------------------------------------------------------------- AssertionError Traceback (most recent call last) Cell In[3], line 1 ----> 1 assert accessibility_grid.crs == metro.crs == roads.crs, "Input data sets’ CRS differs" AssertionError: Input data sets’ CRS differs
If multiple data sets do not share a common CRS, first, figure out which CRS they have assigned (if any!), then transform the data into a common reference system:
print(accessibility_grid.crs)
EPSG:3067
print(metro.crs)
EPSG:3879
print(roads.crs)
EPSG:3879
roads = roads.to_crs(accessibility_grid.crs)
metro = metro.to_crs(accessibility_grid.crs)
assert accessibility_grid.crs == metro.crs == roads.crs, "Input data sets’ CRS differs"
Plotting a multi-layer map¶
Hint: Check Your Understanding
Complete the next steps at your own pace (clear out the code cells first). Make sure to revisit previous lessons if you feel unsure how to complete a task.
- Visualize a multi-layer map using the
geopandas.GeoDataFrame.plot()
method;- First, plot the accessibility grid using a ‘quantiles’ classification scheme;
- Then, add roads network and metro lines in the same plot (remember the
ax
parameter).
Remember the following options that can be passed to plot()
:
- style the polygon layer:
- define a classification scheme using the
scheme
parameter - change the colour map using
cmap
- control the layer’s transparency the
alpha
parameter (0
is fully transparent,1
fully opaque)
- define a classification scheme using the
- style the line layers:
- adjust the line
colour using the
color
parameter - change the
linewidth
, as needed
- adjust the line
colour using the
The layers have different extents (roads
covers a much larger area). You can
use the axes’ (ax
) methods set_xlim()
and set_ylim()
to set the horizontal
and vertical extents of the map (e.g., to a geo-data frame’s total_bounds
).
import matplotlib.pyplot as plt
ax = accessibility_grid.plot(
figsize=(12, 8),
column="pt_r_t",
scheme="quantiles",
cmap="Spectral",
linewidth=0,
alpha=0.8
)
metro.plot(
ax=ax,
color="orange",
linewidth=2.5
)
roads.plot(
ax=ax,
color="grey",
linewidth=0.8
)
minx, miny, maxx, maxy = accessibility_grid.total_bounds
ax.set_xlim(minx, maxx)
ax.set_ylim(miny, maxy)
(6665250.00004393, 6698000.000038021)
Adding a legend¶
To plot a legend for a map, add the legend=True
parameter.
For figures without a classification scheme
, the legend consists of a colour
gradient bar. The legend is an instance of
matplotlib.pyplot.colorbar.Colorbar
,
and all arguments defined in legend_kwds
are passed through to it. See below
how to use the label
property to set the legend title:
ax = accessibility_grid.plot(
figsize=(12, 8),
column="pt_r_t",
cmap="Spectral",
linewidth=0,
alpha=0.8,
legend=True,
legend_kwds={"label": "Travel time (min)"}
)
Hint: Set Other
Colorbar
ParametersCheck out
matplotlib.pyplot.colorbar.Colorbar
’s documentation and experiment with other parameters! Anything you add to thelegend_kwds
dictionary will be passed to the color bar.
For figures that use a classification scheme
, on the other hand, plot()
creates a
matplotlib.legend.Legend
.
Again, legend_kwds
are passed through, but the parameters are slightly
different: for instance, use title
instead of label
to set the legend
title:
accessibility_grid.plot(
figsize=(12, 8),
column="pt_r_t",
scheme="quantiles",
cmap="Spectral",
linewidth=0,
alpha=0.8,
legend=True,
legend_kwds={"title": "Travel time (min)"}
)
<Axes: >
Hint: Set Other
Legend
ParametersCheck out
matplotlib.pyplot.legend.Legend
’s documentation, and experiment with other parameters! Anything you add to thelegend_kwds
dictionary will be passed to the legend.What
legend_kwds
keyword would spread the legend onto two columns?
Adding a base map¶
For better orientation, it is often helpful to add a base map to a map plot. A base map, for instance, from map providers such as OpenStreetMap or Stamen, adds streets, place names, and other contextual information.
The Python package contextily takes care of downloading the necessary map tiles and rendering them in a geopandas plot.
Caution: Web Mercator
Map tiles from online map providers are typically in Web Mercator projection (EPSG:3857). It is generally advisable to transform all other layers to
EPSG:3857
, too.
accessibility_grid = accessibility_grid.to_crs("EPSG:3857")
metro = metro.to_crs("EPSG:3857")
roads = roads.to_crs("EPSG:3857")
To add a base map to an existing plot, use the
contextily.add_basemap()
function, and supply the plot’s ax
object obtained in an earlier step.
import contextily
ax = accessibility_grid.plot(
figsize=(12, 8),
column="pt_r_t",
scheme="quantiles",
cmap="Spectral",
linewidth=0,
alpha=0.8,
legend=True,
legend_kwds={"title": "Travel time (min)"}
)
contextily.add_basemap(ax, source=contextily.providers.OpenStreetMap.Mapnik)