Navvo
Guides

Show a live traffic layer

Render real-time congestion on your map, loaded efficiently by tile-cover.

Show a live traffic layer

Overlay real-time congestion — sourced from Navvo's own probe network — on your map. The pattern: compute the tiles covering your viewport, fetch each, and render the colored lines. Uses Traffic.

Scope required: traffic

Option A — GeoJSON tiles (style it yourself)

1. Get the tiles covering the viewport

GET /traffic/lines/tile-cover returns the exact tile URLs for a bbox:

const bounds = map.getBounds(); // Navvo Maps
const bbox = [bounds.getWest(), bounds.getSouth(), bounds.getEast(), bounds.getNorth()].join(",");

const cover = await (await fetch(
  "https://navvo.io/api/v1/traffic/lines/tile-cover?" + new URLSearchParams({ bbox, zoom: "14" }),
  { headers: { "x-api-key": KEY } }
)).json();
// cover.tiles -> [{ z, x, y, url }]

2. Fetch and merge the tiles

const features = [];
for (const t of cover.tiles) {
  const fc = await (await fetch(`https://navvo.io${t.url}`, { headers: { "x-api-key": KEY } })).json();
  if (fc.hasData) features.push(...fc.features);
}
const geojson = { type: "FeatureCollection", features };

3. Render with congestion colors

Each feature carries a color property keyed to its congestion level:

map.addSource("traffic", { type: "geojson", data: geojson });
map.addLayer({
  id: "traffic",
  type: "line",
  source: "traffic",
  paint: {
    "line-color": ["get", "color"],
    "line-width": ["interpolate", ["linear"], ["zoom"], 10, 1.5, 16, 5],
  },
});

Refresh every ~60 seconds (the layer descriptor's refreshSeconds).

Option B — Vector tiles (native Navvo Maps source)

The telemetry overlay also serves binary vector tiles, which Navvo Maps can consume directly without manual fetching:

map.addSource("traffic", {
  type: "vector",
  tiles: ["https://navvo.io/api/v1/telemetry/traffic-overlay/vector/{z}/{x}/{y}.pbf?mode=current"],
  minzoom: 8,
  maxzoom: 16,
});
map.addLayer({
  id: "traffic",
  type: "line",
  source: "traffic",
  "source-layer": "navvo_traffic",
  paint: {
    "line-width": 3,
    "line-color": [
      "match", ["get", "congestion"],
      "light", "#188038",
      "moderate", "#D9A441",
      "heavy", "#D94B4B",
      "blocked", "#8B1A1A",
      "#188038",
    ],
  },
});

Typical (predicted) traffic

For "what traffic usually looks like" rather than right now, use the predicted line tiles (GET /traffic/predicted/tiles/{z}/{x}/{y}) or the overlay with mode=typical.

To fill in the roads that have no live traffic in the current view, GET /traffic/analysed/grid returns the usual traffic at this hour for every geohash cell of the view, each with a liveLevel on the same colour scale as the live layer. Colour each road segment by the cell its midpoint falls in, and label the result as traffic history, not live.

A summary badge

For a single congestion read-out over an area, GET /traffic/summary returns a congestionScore, dominant level, and a per-level breakdown:

const s = await (await fetch(
  "https://navvo.io/api/v1/traffic/summary?bbox=" + bbox, { headers: { "x-api-key": KEY } }
)).json();
// s.conditions.summary -> "Slow moving" (live) or "Usually light traffic at this hour" (no live data)
// s.conditions.summaryAr -> the same in Arabic
Live congestion only covers roads with recent Navvo probe coverage. Where none is in the view, the summary answers from traffic history instead, with conditions.basis: "historical": show its freshnessLabel ("From traffic history — no live data in this view") rather than calling it live. hasData is false only when there is nothing at all.
Want to improve the traffic data your app sees? Contribute anonymous trips via Telemetry — see the Fleet & logistics guide.
Copyright © 2026