Route Path Map — plotnine

A geographic map visualization showing a connected path or route between sequential waypoints. Unlike scatter maps that display discrete points, this plot connects coordinates in order to reveal journeys, tracks, and navigation paths. Ideal for GPS data, delivery routes, and travel visualization where the sequence and continuity of movement matters.

Route Path Map rendered with plotnine

Python source (plotnine)

""" anyplot.ai
map-route-path: Route Path Map
Library: plotnine 0.15.4 | Python 3.13.13
Quality: 87/100 | Updated: 2026-05-21
"""

import os

import numpy as np
import pandas as pd
from plotnine import (
    aes,
    coord_fixed,
    element_blank,
    element_line,
    element_rect,
    element_text,
    geom_path,
    geom_point,
    geom_polygon,
    ggplot,
    labs,
    scale_color_cmap,
    theme,
    theme_minimal,
)


# Theme tokens
THEME = os.getenv("ANYPLOT_THEME", "light")
PAGE_BG = "#FAF8F1" if THEME == "light" else "#1A1A17"
ELEVATED_BG = "#FFFDF6" if THEME == "light" else "#242420"
INK = "#1A1A17" if THEME == "light" else "#F0EFE8"
INK_SOFT = "#4A4A44" if THEME == "light" else "#B8B7B0"
PARK_FILL = "#C8E6C9" if THEME == "light" else "#1B3A1F"
PARK_COLOR = "#4CAF50" if THEME == "light" else "#388E3C"
LAKE_FILL = "#81D4FA" if THEME == "light" else "#0D3B52"
LAKE_COLOR = "#0288D1" if THEME == "light" else "#01579B"

# Data
np.random.seed(42)

base_lat = 47.82
base_lon = -121.75
n_points = 200
t = np.linspace(0, 2 * np.pi, n_points)

radius_lat = 0.08 + 0.02 * np.sin(3 * t)
radius_lon = 0.12 + 0.03 * np.cos(2 * t)
noise_lat = np.random.normal(0, 0.002, n_points)
noise_lon = np.random.normal(0, 0.003, n_points)

lat = base_lat + radius_lat * np.sin(t) + noise_lat
lon = base_lon + radius_lon * np.cos(t) + noise_lon

df_trail = pd.DataFrame({"lat": lat, "lon": lon, "sequence": range(n_points)})
df_trail["lat_smooth"] = df_trail["lat"].rolling(window=3, center=True, min_periods=1).mean()
df_trail["lon_smooth"] = df_trail["lon"].rolling(window=3, center=True, min_periods=1).mean()
df_trail["progress"] = df_trail["sequence"] / (n_points - 1) * 100

start_point = df_trail.iloc[[0]].copy()
end_point = df_trail.iloc[[-1]].copy()

park_boundary_lon = [
    base_lon - 0.20,
    base_lon - 0.18,
    base_lon - 0.10,
    base_lon + 0.05,
    base_lon + 0.15,
    base_lon + 0.18,
    base_lon + 0.15,
    base_lon + 0.05,
    base_lon - 0.08,
    base_lon - 0.18,
    base_lon - 0.20,
]
park_boundary_lat = [
    base_lat - 0.05,
    base_lat + 0.05,
    base_lat + 0.12,
    base_lat + 0.14,
    base_lat + 0.10,
    base_lat,
    base_lat - 0.10,
    base_lat - 0.12,
    base_lat - 0.10,
    base_lat - 0.08,
    base_lat - 0.05,
]
df_park = pd.DataFrame(
    {"lon": park_boundary_lon, "lat": park_boundary_lat, "order": range(len(park_boundary_lon)), "area": "park"}
)

lake_t = np.linspace(0, 2 * np.pi, 20)
lake_lon = base_lon + 0.03 + 0.025 * np.cos(lake_t)
lake_lat = base_lat + 0.02 + 0.015 * np.sin(lake_t)
df_lake = pd.DataFrame({"lon": lake_lon, "lat": lake_lat, "order": range(len(lake_t)), "area": "lake"})

# Plot
plot = (
    ggplot()
    + geom_polygon(aes(x="lon", y="lat"), data=df_park, fill=PARK_FILL, color=PARK_COLOR, size=0.5, alpha=0.7)
    + geom_polygon(aes(x="lon", y="lat"), data=df_lake, fill=LAKE_FILL, color=LAKE_COLOR, size=0.4, alpha=0.8)
    + geom_path(
        aes(x="lon_smooth", y="lat_smooth", color="progress"), data=df_trail, size=2.5, alpha=0.85, lineend="round"
    )
    + geom_point(
        aes(x="lon_smooth", y="lat_smooth"),
        data=start_point,
        color="#2E7D32",
        fill="#4CAF50",
        size=5,
        shape="o",
        stroke=1.5,
    )
    + geom_point(
        aes(x="lon_smooth", y="lat_smooth"),
        data=end_point,
        color="#C62828",
        fill="#EF5350",
        size=5,
        shape="s",
        stroke=1.5,
    )
    + scale_color_cmap(cmap_name="viridis", name="Trail Progress (%)")
    + coord_fixed(ratio=1.3)
    + labs(title="map-route-path · python · plotnine · anyplot.ai", x="Longitude (°)", y="Latitude (°)")
    + theme_minimal()
    + theme(
        figure_size=(8, 4.5),
        plot_background=element_rect(fill=PAGE_BG, color=PAGE_BG),
        panel_background=element_rect(fill=PAGE_BG),
        panel_grid_major=element_line(color=INK, size=0.3, alpha=0.10),
        panel_grid_minor=element_blank(),
        panel_border=element_rect(color=INK_SOFT, fill=None),
        axis_title=element_text(size=10, color=INK),
        axis_text=element_text(size=8, color=INK_SOFT),
        axis_line=element_line(color=INK_SOFT),
        plot_title=element_text(size=12, color=INK),
        legend_title=element_text(size=8, color=INK),
        legend_text=element_text(size=8, color=INK_SOFT),
        legend_background=element_rect(fill=ELEVATED_BG, color=INK_SOFT),
        legend_position="right",
    )
)

# Save
plot.save(f"plot-{THEME}.png", dpi=400, width=8, height=4.5, units="in", verbose=False)

Part of Route Path Map on anyplot.ai.

Other implementations