Skip to content

feat: map overhaul — pydeck heatmap, scatter, and 3D column layers #26

@jschloman

Description

@jschloman

Summary

Replace the current Swarm map with a multi-layer pydeck visualization using a CARTO dark basemap. Three composable layers toggled from the sidebar.

Depends on: #23 (theme foundation)

Basemap

No API key required. Use CARTO's free dark tile style:

CARTO_DARK = "https://basemaps.cartocdn.com/gl/dark-matter-gl-style/style.json"

Layer 1 — HeatmapLayer (density)

Show the geographic density of all check-ins as a smooth heatmap. Default layer when many check-ins exist.

pdk.Layer(
    "HeatmapLayer",
    data=checkins_df,
    get_position=["lng", "lat"],
    get_weight="visit_count",
    radius_pixels=60,
    intensity=1.2,
    threshold=0.25,
    color_range=[
        [12, 17, 32, 0],       # transparent (cold)
        [99, 102, 241, 120],   # indigo
        [34, 211, 238, 200],   # cyan
        [244, 114, 182, 255],  # pink (hot)
    ],
)

Layer 2 — ScatterplotLayer (individual venues)

Individual venue dots with tooltip showing venue name and visit count.

pdk.Layer(
    "ScatterplotLayer",
    data=venues_df,
    get_position=["lng", "lat"],
    get_color="[99, 102, 241, 200]",
    get_radius="radius",           # scaled by visit_count
    radius_min_pixels=4,
    radius_max_pixels=24,
    pickable=True,
)

Tooltip: {"text": "{place_name}\n{visit_count} visits"}

Layer 3 — ColumnLayer (3D visit frequency)

3D columns extruded by visit count, best viewed at 45° pitch. Toggle on from a sidebar checkbox.

pdk.Layer(
    "ColumnLayer",
    data=grid_df,              # venues aggregated to a ~100m grid
    get_position=["lng", "lat"],
    get_elevation="visit_count",
    elevation_scale=30,
    radius=80,
    get_fill_color="[99, 102, 241, 180]",
    pickable=True,
    auto_highlight=True,
)

Sidebar controls (new)

  • Layer selector: st.radio("Map style", ["Heatmap", "Venues", "3D"])
  • Pitch toggle: st.checkbox("3D view (45° tilt)") — sets ViewState(pitch=45) vs pitch=0

View state

Default: fit to bounding box of all check-ins. Compute center_lat, center_lng, and zoom from the data rather than hardcoding.

Acceptance criteria

  • All three layers render correctly with CARTO dark basemap (no API key)
  • Tooltip shows venue name and visit count on hover
  • Sidebar controls switch between layers without full page reload
  • View auto-fits to loaded check-in data
  • Graceful empty state when no Swarm data loaded
  • ruff check ., mypy, and pytest all pass

Metadata

Metadata

Assignees

No one assigned

    Labels

    enhancementNew feature or request

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions