Handling Empty GeoDataFrames in Prettymaps: Edge Case Prevention and Best Practices

Prettymaps silently skips empty GeoDataFrames by checking gdf.empty before projection, transformation, and layer inclusion, returning empty GeometryCollections that downstream functions iterate over safely.

When building minimalist or custom map visualizations with Prettymaps, you may encounter GeoDataFrames (GDFs) that contain no geometry data. The library's pipeline assumes valid geometry for operations like coordinate projection, boundary calculation, and layer rendering. This article examines how Prettymaps guards against empty GDF crashes—including where those checks occur in the source code, how they protect the rendering pipeline, and practical patterns for safe custom layer integration.

Where Prettymaps Checks for Empty GeoDataFrames

The Prettymaps source code contains explicit empty-GDF guards in three critical locations. Understanding these checkpoints helps you predict behavior when working with custom data.

Projection Guard in gdf_to_shapely

In prettymaps/draw.py, the gdf_to_shapely function performs the primary safety check before coordinate transformation:

if not gdf.empty and gdf.crs is not None:
    # CRS projection occurs here

    gdf = ox.projection.project_gdf(gdf)

This guard at lines 283–284 prevents osmnx from attempting to project an empty frame. If the GDF is empty, the function proceeds directly to geometry collection creation, returning an empty shapely.geometry.GeometryCollection that downstream code handles gracefully.

Layer Assembly Guard in get_gdfs

The prettymaps/fetch.py module contains two additional emptiness checks during data retrieval. First, in the layer assembly loop (lines 300–301):

if not gdf.empty:
    gdfs[layer] = gdf

Empty layers are silently omitted from the returned dictionary. This means your gdfs dict may not contain every key you requested—an important consideration when accessing layers by name later.

Elevation Guard in obtain_elevation

The elevation fetching logic (lines 343–344) applies the same pattern:

if not gdf.empty:
    # SRTM elevation download proceeds

Since elevation extraction requires a valid perimeter polygon, this guard prevents API calls that would fail or return meaningless data.

Implicit Safety in transform_gdfs

The transform_gdfs function in draw.py contains no explicit empty check. Instead, it relies on the upstream gdf_to_shapely guard—empty GDFs arrive as empty GeometryCollections, which the transformation loop processes safely using pattern matching like for shape in geometries.geoms if hasattr(geometries, 'geoms') else [geometries].

How the Empty-GDF Guard Works Internally

The protection mechanism operates through three coordinated stages:

  1. Bypass Projection — ox.projection.project_gdf(gdf) requires both a CRS and at least one geometry. The guard skips this call entirely for empty frames, avoiding the "NoneType has no attribute 'bounds'" error.

  2. Return Empty Geometry — gdf_to_shapely constructs a GeometryCollection([]) that maintains type compatibility with non-empty results. Drawing functions iterate over this collection with zero iterations, producing no rendered output but no exception either.

  3. Filter Layer Dictionary — By excluding empty GDFs from gdfs in fetch.py, the subsequent draw_layers loop never attempts to style or render missing data.

Practical Patterns for Safe Empty-GeoDataFrame Handling

These code patterns demonstrate defensive programming when integrating custom data with Prettymaps.

Verify Layer Existence Before Access

from prettymaps import get_gdfs, plot

gdfs = get_gdfs(query="Remote area with no waterways", layers={"waterway": {}})

# Defensive access pattern

if "waterway" in gdfs and not gdfs["waterway"].empty:
    waterway_area = gdfs["waterway"].geometry.area.sum()
else:
    waterway_area = 0

Create Placeholder Geometry for Required Layers

import geopandas as gp
from shapely.geometry import Point
from prettymaps import plot

# Custom layer that might be empty

custom_gdf = gp.GeoDataFrame(geometry=[], crs="EPSG:4326")

if custom_gdf.empty:
    # Insert minimal valid geometry to preserve pipeline flow

    custom_gdf = gp.GeoDataFrame(
        geometry=[Point(0, 0)],  # Tiny placeholder

        crs="EPSG:4326"
    )

plot(
    query="Porto Alegre, Brazil",
    layers={"custom": {"facecolor": "blue", "alpha": 0.5}},
    gdfs={"custom": custom_gdf},
)

Use GPX Paths Safely

The prettymaps/gpx.py module already handles empty tracks—read_track returns None when no lines are found. This integrates seamlessly:

from prettymaps import plot

# Empty or invalid GPX files are skipped without error

plot(
    query="Berlin, Germany",
    gpx="empty_track.gpx",  # No tracks? No problem.

)

Conditional Hillshade Drawing

import matplotlib.pyplot as plt
from prettymaps import get_gdfs, draw_hillshade

gdfs = get_gdfs(
    query="Mountain region",
    layers={},
    perimeter_radius=1000
)

# Explicit check before elevation-dependent rendering

if not gdfs["perimeter"].empty:
    fig, ax = plt.subplots(figsize=(10, 10))
    draw_hillshade(layers={}, gdfs=gdfs, ax=ax)

Summary

  • Prettymaps checks gdf.empty in draw.py and fetch.py before projection, transformation, and layer dictionary creation
  • Empty GDFs become empty GeometryCollections that iterate safely in downstream drawing functions
  • Missing layer keys are possible—the gdfs dictionary excludes empty layers entirely
  • Placeholder geometry can substitute for empty required layers when pipeline continuity matters
  • GPX empty-track handling is built in via None returns from read_track

Frequently Asked Questions

What error occurs if Prettymaps doesn't check for empty GeoDataFrames?

Without the empty guard, osmnx.projection.project_gdf() raises an AttributeError when accessing .bounds or .total_bounds on an empty geometry column. Other operations trigger IndexError when attempting to access row 0 of zero-length data. The guards at lines 283–284 of draw.py and 300–301 of fetch.py prevent these exceptions.

Why does my layer dictionary missing a key I requested?

Prettymaps silently omits empty layers in fetch.py at lines 300–301. If your query returns no matching OSM features for a layer (e.g., "waterway" in a desert region), that key won't exist in the returned dictionary. Always check key membership with if "layer" in gdfs before access.

Can I force Prettymaps to render an empty layer?

Yes—insert placeholder geometry before passing to plot(). Create a GeoDataFrame with a single Point or empty Polygon at your target coordinates. This preserves the layer key in gdfs and allows styling parameters to apply, though nothing visible renders from the zero-area geometry.

Does the GPX empty-track behavior differ from other layers?

GPX handling uses None returns rather than empty GeoDataFrames. The read_track function in prettymaps/gpx.py returns None for files without valid track segments, which plot() interprets as "no GPX layer to add." This differs from OSM layers, which use empty GDF detection—both patterns achieve crash-free operation but through different type signatures.

Have a question about this repo?

These articles cover the highlights, but your codebase questions are specific. Give your agent direct access to the source. Share this with your agent to get started:

Share the following with your agent to get started:
curl -s "https://instagit.com/install.md"

Works with
Claude Codex Cursor VS Code OpenClaw Any MCP Client

Maintain an open-source project? Get it listed too →