Skip to content
Documentation menu
docs / transit · raw .md

Buses: live positions, stops, lines and timetables

MapMap carries England's local buses as display data: where the buses are now, where the stops are, which lines call at them, and what the timetable says leaves next. Four pieces, one rule.

You wantUseData
Buses moving on a mapGET /v1/transit/vehicles or the SDK's TransitVehiclesLayerDfT Bus Open Data Service (BODS) live positions
Stops and lines drawn on a mapThe SDK's TransitStopsLayerNaPTAN stops, BODS timetable shapes
The stops near a pointGET /v1/transit/stops or the MCP tool nearby_stopsNaPTAN, with lines from BODS timetables
What leaves a stop nextGET /v1/transit/departures or the MCP tool next_departuresBODS timetables, paired with live positions

The rule: display, never prediction. Positions are drawn where the feed reports them. Departure times are the timetable's. A departure can show a bus seen on its line heading towards the stop, which is an observation of where that bus is, not a forecast of when it arrives. MapMap does not compute journey times, plan trips on public transport, or predict arrivals or delays, and nothing on this page should be presented as doing so.

Coverage. England's local bus and coach services: BODS is England's national bus data service. Stops just across the border are included where an England service calls at them. Scotland and Wales' own networks, rail, tram, metro and ferry are not included.

Live bus positions

GET /v1/transit/vehicles?bbox=west,south,east,north answers GeoJSON for a box of at most 4 square degrees, refreshed from the national feed every minute, with the feed's freshness on every answer (feed.status ok, stale past 180 seconds, or unknown; a stale feed still returns its buses, marked paused). Full parameters and fields: API reference.

ts
import { MapMapMap, TransitVehiclesLayer } from "@mapmap/maps";

const map = new MapMapMap({ container: "map", apiKey: "snk_..." });
const buses = new TransitVehiclesLayer(map, {
  onFeedStatus: ({ label }) => (pill.textContent = label), // "Live, 42 s ago"
});
buses.start();

The layer polls only the area on screen, every 30 seconds by default, and draws one poll behind so buses glide between fixes.

Stops and lines on a map

Two map tile archives, rebuilt every night and kept apart from the base map: transit-stops (layer stops, zoom 10 to 14) and transit-lines (layer lines, zoom 6 to 14). With an API key on the map, the layer reads them through the keyed tile API (https://api.mapmap.ai/tiles/transit-stops/tiles.json, metered like the base map tiles), which works from your own site. Self-hosting, or reading the archives directly, their current addresses are in the tiles index, https://tiles.mapmap.ai/index.json, under transit.

ts
import { MapMapMap, TransitStopsLayer } from "@mapmap/maps";

const map = new MapMapMap({ container: "map", apiKey: "snk_..." });
const transit = new TransitStopsLayer(map, {
  onStopClick: async (stop) => {
    // stop.id is the NaPTAN ATCO code; stop.lines the lines calling there
    const res = await fetch(
      `https://api.mapmap.ai/v1/transit/departures?stop=${stop.id}`,
      { headers: { Authorization: "Bearer snk_..." } },
    );
    showBoard(await res.json());
  },
});
await transit.ready; // false when no transit archives are published
  • Lines show from zoom 6, in the route's own colour where the timetable publishes one. A route the timetable gives no shape for is drawn dashed, stop to stop, so it never reads as the road the bus takes.
  • Stops show from zoom 11 with the MapMap transit glyph.
  • Both layers survive map.setStyle(...) and sit in the middle slot (above roads, below labels) unless you pass slot.
  • Options: stops, lines (both default true), slot, onStopClick, lineColour, stopColour, source ("auto", "api" or "tiles-host"), and stopsUrl/linesUrl for a copy you host yourself.

What @mapmap/maps exports for this, besides the layer itself:

ExportWhat it is
TransitStopsLayerOptionsThe options above
TransitStopFeatureWhat onStopClick hands you: id (ATCO code), name, indicator, locality, bearing, lines
transitStopFromProperties(props)Turns a stop feature's tile properties into a TransitStopFeature (parses lines)
transitTileJsonUrl(baseUrl, id, apiKey?)The keyed TileJSON URL for transit-stops or transit-lines on a gateway
transitUrlsFromIndex(index, indexUrl)The pmtiles:// URLs a tiles index announces under transit
transitArchiveUrl(url, indexUrl)One archive path resolved to a pmtiles:// URL
TRANSIT_TILES_INDEX_URLhttps://tiles.mapmap.ai/index.json
NAPTAN_SHORT_CREDIT"Bus stops: NaPTAN, DfT, OGL v3.0", the stops credit the layer adds
TRANSIT_LINE_FALLBACK_COLOURThe line colour where the timetable publishes none

Stop properties in the tiles: id (ATCO code), name, indicator, locality, bearing (NaPTAN compass point), lines (a JSON array as a string). Line properties: line, operator, noc (National Operator Code), colour (when published), direction (outbound, inbound or absent), route, and shape (gtfs, or stops for a stop-to-stop chain).

Stops near a point

sh
curl -s "https://api.mapmap.ai/v1/transit/stops?lat=51.5080&lon=-0.1281&radius_m=300" \
  -H "Authorization: Bearer $KEY"

radius_m 1 to 2,000 (default 500), limit 1 to 50 (default 10). Each stop carries its ATCO code, name, indicator, locality, position, distance and bearing from your point, and lines. Every answer carries coverage and attribution.

Departures at a stop

sh
curl -s "https://api.mapmap.ai/v1/transit/departures?stop=490013767T&limit=6" \
  -H "Authorization: Bearer $KEY"
json
{
  "stop": { "id": "490013767T", "name": "Trafalgar Square", "indicator": "Stop T", "lines": ["12", "88", "139", "159", "453"] },
  "basis": "timetable",
  "label": "Timetable times from Bus Open Data timetables. A live entry shows a vehicle on this line observed upstream of the stop. MapMap does not predict arrival times.",
  "departures": [
    { "line": "88", "destination": "Parliament Hill Fields", "operator": "GAHL",
      "scheduled_at": "2026-10-12T18:19:00+01:00", "trip": "VJ...",
      "live": { "vehicle": "YX13BKG", "distance_m": 62, "age_s": 154 } }
  ],
  "timetable_date": "2026-10-12",
  "coverage": "England bus stops (NaPTAN) with services from BODS timetables. Rail, tram and ferry are not included.",
  "attribution": "Contains public sector information licensed under the Open Government Licence v3.0. Source: NaPTAN, Department for Transport. ..."
}
  • at (RFC 3339, default now), window_minutes (1 to 180, default 60), limit (1 to 30, default 8). The index holds three service days from today; an at outside them is refused with the window named.
  • Show the label with the departures. It says what the times are and what a live entry is.
  • live appears when a bus on the same line, seen in the last five minutes, is within 3 km of the stop and its reported heading points towards it. Per line, the nearest such bus goes with the earliest departure. It is a heuristic over live positions, not a link between that bus and that timetabled journey.

For agents

The MCP tools nearby_stops and next_departures return the same answers, with label and coverage on every one. Pass them on: an agent must not present a timetable time or a live entry as an arrival time. See MCP.

Keys and metering

/v1/transit/vehicles, /v1/transit/stops and /v1/transit/departures are one Standard unit per request each, and a browser (maps) key may call them. The stops and lines tiles meter like the base map tiles when read through the keyed tile API. A live map polling every 30 seconds makes 2,880 calls a day per open page, so poll the visible area only and prefer a longer interval where the use allows.

Licence and credits

All of this is public sector information licensed under the Open Government Licence v3.0:

  • Stops: "Contains public sector information licensed under the Open Government Licence v3.0. Source: NaPTAN, Department for Transport."
  • Live positions and timetables: "Contains public sector information from the Bus Open Data Service, licensed under the Open Government Licence v3.0. Source: Department for Transport (data.bus-data.dft.gov.uk)."

Every API answer carries its attribution; the SDK layers add the short credits ("Bus stops: NaPTAN, DfT, OGL v3.0" and "Bus data: DfT Bus Open Data Service, OGL v3.0") to the map's one credit line. The transit data is served from its own files and paths and is never merged into the OpenStreetMap-derived base map.