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

> Learn how prettymaps handles empty GeoDataFrames with edge case prevention and best practices. Ensure safe downstream processing by understanding gdf.empty checks.

- Repository: [Marcelo de Oliveira Rosa Prates/prettymaps](https://github.com/marceloprates/prettymaps)
- Tags: best-practices
- Published: 2026-08-20

---

**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`](https://github.com/marceloprates/prettymaps/blob/main/prettymaps/draw.py), the `gdf_to_shapely` function performs the primary safety check before coordinate transformation:

```python
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`](https://github.com/marceloprates/prettymaps/blob/main/prettymaps/fetch.py) module contains two additional emptiness checks during data retrieval. First, in the layer assembly loop (lines 300–301):

```python
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:

```python
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`](https://github.com/marceloprates/prettymaps/blob/main/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`](https://github.com/marceloprates/prettymaps/blob/main/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

```python
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

```python
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`](https://github.com/marceloprates/prettymaps/blob/main/prettymaps/gpx.py) module already handles empty tracks—`read_track` returns `None` when no lines are found. This integrates seamlessly:

```python
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

```python
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`](https://github.com/marceloprates/prettymaps/blob/main/draw.py) and [`fetch.py`](https://github.com/marceloprates/prettymaps/blob/main/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`](https://github.com/marceloprates/prettymaps/blob/main/draw.py) and 300–301 of [`fetch.py`](https://github.com/marceloprates/prettymaps/blob/main/fetch.py) prevent these exceptions.

### Why does my layer dictionary missing a key I requested?

Prettymaps **silently omits empty layers** in [`fetch.py`](https://github.com/marceloprates/prettymaps/blob/main/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`](https://github.com/marceloprates/prettymaps/blob/main/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.