Creating Maps#

Summary

This section focuses on visualizing spatial data, combining data from various sources and spatial data types into informative maps with elements like legends and titles. We will also explore creating interactive maps within Jupyter that allow for zooming and panning.

Data retrieval#

Data Retrieval

Make sure you ran notebook 301_accessing_data.ipynb at least once to retrieve and setup the data needed for this notebook (follow this link).

To visualize the data, load it using the geopandas package as demonstrated previously. The .plot() function provides a straightforward way to visualize your GeoDataFrame.

import geopandas as gp
from pathlib import Path

INPUT = Path.cwd().parents[0] / "00_data"
gdb_path = INPUT / "Biotopwerte_Dresden_2018.gdb"
gdf = gp.read_file(gdb_path, layer="Biotopwerte_Dresden_2018")
/opt/conda/envs/worker_env/lib/python3.13/site-packages/pyogrio/raw.py:198: RuntimeWarning: organizePolygons() received a polygon with more than 100 parts. The processing may be really slow.  You can skip the processing by setting METHOD=SKIP, or only make it analyze counter-clock wise parts by setting METHOD=ONLY_CCW if you can assume that the outline of holes is counter-clock wise defined
  return ogr_read(

Basic plotting with geopandas#

Using the .plot() function without any additional arguments will generate a simple, static plot of your spatial data with default styling.

filter_db = gdf[gdf['Shape_Area'] < 1000]
filter_db.plot()
<Axes: >
../_images/569c70f978c3ec1686352826cff7bd98b7f875dbd6f417e494ce47ea76a86b27.png

Customizing static plots#

To enhance the visualization, the .plot() function offers several parameters for customization:

  • linewidth: Controls the thickness of the feature boundaries.

  • color: Sets the fill color of the geometric shapes.

  • edgecolor: Defines the color of the boundaries of the shapes.

  • ax.set_title(): Sets the title of the plot.

  • ax.set_axis_off(): Hides the plot axes.

ax = filter_db.plot(linewidth=0.5, edgecolor='red')
ax.set_title("Dresden - Shapes with Area < 1000")
ax.set_axis_off()
../_images/37d713272e52102c55487047e5e880b8c489f47a7b81674128d709526e81da8a.png

Advanced plot customization with matplotlib#

For more fine-grained control over the plot’s appearance, it’s recommended to create a matplotlib figure and axes objects explicitly.

import matplotlib.pyplot as plt

Then by defining different parameters for figure and subplots the visualization are customized.

Key matplotlib components:

  • Figure (fig): The top-level container for all plot elements.

  • Subplot (ax): An individual plotting area within the figure.

  • nrows, ncols: Define the grid of subplots within the figure.

  • figsize: Specifies the size of the figure in inches (width, height).

  • dpi: Sets the resolution of the figure (dots per inch).

The ax parameter in the .plot() function is used to specify which subplot the GeoDataFrame should be plotted onto.

How the ax parameter works

When creating multiple subplots, the ax parameter directs the plot to a specific subplot. For example:

fig, ax = plt.subplots(nrows=2, ncols=3) # Creates 6 subplots

ax1 = ax[0, 0]  # First row, first column
ax2 = ax[0, 1]  # First row, second column
ax3 = ax[0, 2]  # First row, third column
ax4 = ax[1, 0]  # Second row, first column
ax5 = ax[1, 1]  # Second row, second column
ax6 = ax[1, 2]  # Second row, third column

# Example plotting onto specific subplots:
# filter_db.plot(ax=ax3, linewidth=0.5, edgecolor='red')
# ax3.set_title("Figure 3")
# filter_db.plot(ax=ax4, linewidth=0.3, edgecolor='blue')
# ax4.set_title("Figure 4")
../_images/1985155414d91ca6b371ad2a52f9cac535e958e67d9eeed402507caa5d97966f.png

Single subplot example:

fig, ax = plt.subplots(
    nrows=1, ncols=1, figsize=(12, 18), dpi=300)
ax = filter_db.plot(ax=ax, linewidth=0.5, edgecolor='orange')
ax.set_title("Dresden - Shapes with Area < 1000")
ax.set_axis_off()
../_images/9186a1af84e309d8826b6e8b8c541a96a27f34df6b980cf39454d8dea725619e.png

Plotting selected features#

You can plot specific rows from your GeoDataFrame using the .loc and .iloc indexers.

loc vs iloc

  • .loc[]: Selects rows based on their index labels.

  • .iloc[]: Selects rows based on their integer position (starting from 0).

Extracting and plotting specific rows

Extract one row from dataset:

gdf_rows = gdf.loc[[100]] 
ax=gdf_rows.plot(color='purple')
ax.set_axis_off()
../_images/a716a0f9b813ec8ed98e89d4dd5527fefac3b01e7be078f09c131f581e17e0e1.png

Extract a list of rows from dataset:

gdf_rows = gdf.loc[[100,101,123,148]] 
ax=gdf_rows.plot(color='purple')
ax.set_axis_off()
../_images/8a26e39d8cd12ec71f40b8298f9f21767960acc616c81cdbdd2d83b54cd8d64a.png

Extract a range of rows from dataset:

gdf_rows = gdf.loc[100:111]
ax=gdf_rows.plot(color='magenta')
ax.set_axis_off()
../_images/bca839a1f3b75e8e7cbd91f81dd7d623699105b7ac9ebe925d85e1ecba69c856.png

Plotting features within a bounding box#

You can focus your map on a specific geographic area by defining a bounding box.

Defining the bounding box

A bounding box is defined by four values:

  • lonmin: Minimum longitude (left boundary)

  • latmin: Minimum latitude (lower boundary)

  • lonmax: Maximum longitude (right boundary)

  • latmax: Maximum latitude (upper boundary)

These values can be defined manually or extracted from a subset of your data using the .total_bounds attribute.

lonmin, latmin, lonmax, latmax = gdf[100:110].total_bounds # rows 100 to 110
print(lonmin, latmin, lonmax, latmax)
409469.5248999996 5654741.2675 416922.3970999997 5656956.8463

Filtering features within the bounding box

To select geometries that completely fall within the defined bounding box, you can use boolean indexing based on the .bounds attribute of the geometry column:

gdf_filtered = gdf[
    (gdf.geometry.bounds.minx >= lonmin) & 
    (gdf.geometry.bounds.maxx <= lonmax) & 
    (gdf.geometry.bounds.miny >= latmin) & 
    (gdf.geometry.bounds.maxy <= latmax)]

ax = gdf_filtered.plot(color='skyblue',edgecolor='navy')
ax.set_title("Filtered data that completely fall within the defined bounding box")
ax.set_axis_off()
../_images/5b85e71c9f7297b2fda830305b844113b07ed93f904cbc5401195c6f534a737a.png

Tip

Another method for displaying features within a bounding box involves the clipping function, which will be discussed in the “Clipping Data” (https://stag.training.fdz.ioer.info/notebooks/307_spatial_clipping.html#clipping-based-on-the-bounding-box) section.

Styling plots with legends and colormaps#

To create more informative maps, you can style the features based on their attribute values using colormaps and add legends.

  • column: shows the column that is mapped to color.

  • cmap: defines the color map.

  • legend = True: adds the legend.

  • legend_kwds: adds the legend label and defines the orientation.

Plotting based on string values

fig, ax = plt.subplots(
    nrows=1, ncols=1, figsize=(12, 18), dpi=300)

ax = gdf_filtered.plot(
    ax=ax,
    column='Biotpkt2018', 
    cmap='viridis_r',
    legend=True,
    legend_kwds={
        "label": "Biodiversity value",
        "orientation": "horizontal", 
        "shrink": 0.3, "pad": 0.01})

ax.set_title('Biodiversity Values in Dresden Center',
             backgroundcolor='#70A401', 
             color='white', fontsize=20)
ax.set_axis_off()
../_images/d7448ec6dd5ed9d6430fd2a8ed1fe47dbd0b28a1eb513ca1bbf442cf087fade1.png

Saving the plot as an svg

OUTPUT = Path.cwd().parents[0] / "out"
OUTPUT.mkdir(exist_ok=True)

fig.savefig(
    Path.cwd().parents[0] / "out" / "biodiversity_dresden.svg", format='svg',
    bbox_inches='tight', pad_inches=0.1, facecolor="white")

Plotting based on numeric values

fig, ax = plt.subplots(
    nrows=1, ncols=1, figsize=(12, 18), dpi=300)

ax = gdf_filtered.plot(
    ax=ax,
    column='CLC_st1', 
    cmap='Set3_r',
    legend=True,
    legend_kwds={
        "loc": "lower center",
        "bbox_to_anchor": (0.5, -0.15),
        "ncol": 6,
        "frameon": False,
        "fontsize": 10
    })

ax.set_title('Dresden Center Small Structure/Infrastructure Based on ATKIS',
             backgroundcolor='orange',
             color='white')
ax.set_axis_off()
# Ensure room below the plot
plt.tight_layout()
plt.subplots_adjust(bottom=0.2)  # Make room for legend
../_images/83a4d43ddc275d42515e13948f8f4f56b509f58093efb03662c131a2ac30f21c.png

Above, we use legend_kwds to modify the look and style of the default matplotlib legend. There are many parameters. We used:

  • loc: Legend position reference

  • bbox_to_anchor: Exact legend placement coordinates

  • ncol: Number of columns in legend

  • frameon: Show/hide legend box

  • fontsize: Size of legend text

Interactive filtering with a slider#

You can create interactive visualizations within Jupyter using widgets, allowing users to dynamically filter the displayed data.

import ipywidgets as widgets
from ipywidgets import interact

Define a function to filter and plot the data based on a slider value:

def ShapeArea(value):
    filtered_gdf = gdf[gdf['Shape_Area'] <= value]
    ax= filtered_gdf.plot()
    ax.set_axis_off()
    plt.show()  

Create an interactive slider linked to the ShapeArea function:

interact(ShapeArea, value=widgets.FloatSlider(min=20000, max=70000, step=0.1));
Hide code cell source
from IPython.display import HTML

HTML('''
<video autoplay loop muted style="border: 5px solid black; border-radius: 10px; max-width: 100%;">
  <source src="../_static/videos/Video3.webm" type="video/webm" />
  Your browser does not support the video tag.
</video>
<figcaption>Interactive filtering</figcaption>
''')
Interactive filtering

Adding a basemap for context#

The folium library ibrary allows you to create interactive web maps with basemaps from providers like OpenStreetMap.

import folium

Creating a basic folium map

The folium.Map() function creates the base map. The location parameter sets the initial center of the map (latitude, longitude), and zoom_start defines the initial zoom level.

basemap = folium.Map(location=[51.04723, 13.76009], zoom_start=13)

How to find coordinates and zoom levels

The easiest way to find the center coordinates and desired zoom level is to use online map services like MapTiler or OpenStreeMap. Zoom and pan to your area of interest, and then examine the URL for latitude, longitude, and zoom level information.

Example: In the URL 14.48/51.0496/13.738, 14.48 is the zoom level, 51.0496 is the latitude, and 13.738 is the longitude.

../_images/11.png

Fig. 14 Set coordinates and zoom levels using URL in MapTiler#

Adding geodata to the basemap

Use folium.GeoJson() to add your GeoDataFrame to the folium map.

folium.GeoJson(gdf_filtered).add_to(basemap)
<folium.features.GeoJson at 0x7d12dd0add30>

Displaying the interactive map

Calling the basemap object will display the interactive map within your Jupyter Notebook.

basemap
Make this Notebook Trusted to load map: File -> Trust Notebook