Map Data Loading and Rendering

The application efficiently loads, filters, and renders stop markers on the interactive map using spatial queries, caching, and marker diffing.

flowchart LR V["Viewport Change"] --> C{"Cache Valid?"} C -->|Yes| R["Render from Cache"] C -->|No| A["API Request"] A --> P["PostGIS Query<br/>ST_Intersects"] P --> D["Diff Markers"] D --> R

Data Pipeline

Step Component Purpose
1 PostGIS Spatial index queries via GiST
2 API /api/data with viewport bounds
3 Cache Avoid redundant requests
4 Diff Update only changed markers

PostGIS Spatial Query

SELECT * FROM stops_matched
WHERE ST_Intersects(
    geom,
    ST_MakeEnvelope(min_lon, min_lat, max_lon, max_lat, 4326)
)

The real /api/data query also includes a second branch for matched rows whose OSM point is inside the viewport even when the ATLAS-side display geometry is outside it. The geom column has a GiST spatial index for the primary lookup path.

Zoom Level Modes

Zoom Mode Behavior
< 14 Overview / Low Zoom Unmatched ATLAS only (if no filters) or capped filtered results
14–15 Capped / Mid Zoom All types (or filters), limited to a capped count to maintain performance
≥ 16 Full / Uncapped Zoom All matched markers in viewport naturally rendered without clipping

Zoom Banner Policy

The application intelligently restricts the number of rendered markers to maintain performance, and communicates this state via the map's zoom banner.

  • High Zoom (≥ 16): The banner is hidden. The data isn't artificially capped.
  • Mid-Zoom (14-15): Results are capped (currently 1,800 matches). If filters are inactive, the banner advises the user to zoom in. If filters are active, it indicates that results are capped (e.g., Showing first 1800 filtered results...). If the real query size naturally drops below the cap, the banner is hidden.
  • Low Zoom (< 14): By default, the map runs an "Overview mode" displaying only unmatched ATLAS components to save resources. The banner indicates this mode. If explicit active filters are applied, the map respects the filter instead of Overview mode, applies a heavy cap, and updates the banner accordingly.
  • Top N Filters: Bypasses all zoom-based warning banners, since the data slice is targeted and strictly constrained natively.

Frontend Caching

Cache reuse logic:

  • Zoom in + previous NOT capped → Reuse (new viewport is subset)
  • Zoom out or pan beyond bounds → Fetch new data
  • Filters changed → Fetch new data

Implementation details:

  • viewport cache stores bounds, stable non-bbox key, zoom, data, and capped state
  • cache key is built from filter params plus mode and excludes bbox-only params
  • the cache is invalidated explicitly on filter changes via invalidateViewportCache()

Cache ownership boundaries:

  • this cache applies only to /api/data viewport payload reuse in the browser
  • /api/global_stats uses a separate backend cache (in-process LRU) and is not served from viewport cache
  • both caches are driven by the same active filter semantics, so requests stay logically aligned even though cache layers are separate

Marker Diffing

Instead of recreating all markers:

  1. Track markers by key (atlas-{sloid} or osm-{node_id})
  2. Existing: Update position with setLatLng()
  3. New: Create and add to map
  4. Stale: Remove from map

Benefits: No flickering, smooth transitions, efficient DOM operations

Canvas vs DOM Markers

Zoom Marker Type Implementation
< 18 CircleMarker Canvas-based, lightweight
≥ 18 L.Marker DOM-based, supports icons

At zoom 18, markers are recreated to switch rendering modes.

Labeled Marker Policy (D / P / S)

When marker rendering is in DOM mode (zoom >= 18), selected marker categories render a lettered icon:

Side Condition Label
ATLAS ATLAS stop is member of duplicate_group_sloids D
OSM osm_node_type = platform P
OSM osm_node_type = railway_station S

All other marker categories keep the standard circle style.

Marker Colors

Type Color CSS Class
ATLAS (unmatched) Green atlas-marker
OSM (unmatched) Orange osm-marker
Matched (ATLAS) Green with indicator matched-atlas
Matched (OSM) Orange with indicator matched-osm

Line Rendering Thresholds

Line Type Min Zoom
ATLAS↔OSM matched connection lines 13
OSM group / trio connector lines 17

Overlap Handling

The MarkerClusterManager handles stops at identical coordinates:

// Coordinates rounded to tolerance grid
const key = `${Math.round(lat * 1e6)}_${Math.round(lon * 1e6)}`;

// Overlapping markers offset in circle pattern
const angle = (index / total) * 2 * Math.PI;
const offset = radius * { x: Math.cos(angle), y: Math.sin(angle) };

This prevents markers from stacking invisibly.

Map Layers

The interactive map provides 3 layer options.

Layer URL
OpenStreetMap tile.openstreetmap.org
Transport tile.memomaps.de
Satellite arcgisonline.com

Code Reference

File Purpose
backend/blueprints/data.py API endpoint
static/js/pages/main.js Map initialization
static/js/components/map-renderer.js Marker rendering
matching_and_import_db/database/importer.py Geometry import

Related Documentation

Data update running in background
Preparing update... | Phase: initializing
Data update in progress
Core data is being refreshed. Use this time to read the documentation.
Elapsed: -- ETA: -- Phase: idle