Live data & streaming
A layer can keep its data current while the page is open: by fetching its GeoJSON URL again on a timer (polling), or by receiving updates the server pushes (a stream). Polling suits a feed that changes every minute or so; a stream suits positions that change every second.
Quick start: poll a feed
Section titled “Quick start: poll a feed”Add a refresh: block to the source, beside its url:. This map fetches the
USGS feed of the last day’s earthquakes every minute:
type: mapid: live-earthquakesconfig: center: [-120, 37] zoom: 3 mapStyle: "https://tiles.openfreemap.org/styles/liberty"layers: - id: earthquakes type: circle source: type: geojson url: "https://earthquake.usgs.gov/earthquakes/feed/v1.0/summary/all_day.geojson" refresh: refreshInterval: 60000 # milliseconds: fetch the feed again every minute paint: circle-radius: ["interpolate", ["linear"], ["get", "mag"], 1, 3, 6, 20] circle-color: "#ef4444" circle-opacity: 0.7 circle-stroke-width: 1 circle-stroke-color: "#ffffff" interactive: hover: cursor: pointer click: popup: - h4: [{ property: title }]Each poll downloads the URL again and redraws the layer with what it
returns. Polling pauses while the browser tab is hidden and resumes when the
reader comes back. refreshInterval is in milliseconds, at least 1000.
- The feed must allow other sites to read it (CORS), as the USGS feeds do (Loading data › CORS).
- A named source under
sources:is polled once, however many layers draw it (named sources). - Live updates work on GeoJSON sources. A FlatGeobuf or GeoParquet file is
read once, and
refresh:orstream:on one is a validation error.
The YAML sets the interval; there is no method to pause or force a poll from
JavaScript. To replace the data yourself, call
updateLayerData(layerId, geojson) on <ml-map>.
Choose how updates combine
Section titled “Choose how updates combine”updateStrategy in the refresh: block says what happens to the features
already on the map when new ones arrive:
updateStrategy: | The map then shows | Use it for |
|---|---|---|
replace (default) | Only the new features | A feed that returns the whole dataset each time |
merge | The new features, plus every earlier one the update didn’t mention | Things with an id that move or change: vehicles, sensors |
append-window | Earlier and new features together, trimmed by count or age | Event streams: the last 100 events, the last hour |
Replace
Section titled “Replace”The default, used by the quick start: each response is the whole dataset.
merge matches features by updateKey, a property whose value is different
for every feature. A feature whose key is already on the map is replaced by
its new version; a new key is added; features the update doesn’t mention
stay. Here the USGS hour feed keeps every earthquake seen since the page
opened, even after it drops out of the feed’s last hour:
type: mapid: earthquakes-this-sessionconfig: center: [-120, 37] zoom: 3 mapStyle: "https://tiles.openfreemap.org/styles/liberty"layers: - id: earthquakes type: circle source: type: geojson url: "https://earthquake.usgs.gov/earthquakes/feed/v1.0/summary/all_hour.geojson" refresh: refreshInterval: 60000 updateStrategy: merge updateKey: url # a property that is different for every earthquake paint: circle-radius: ["interpolate", ["linear"], ["get", "mag"], 1, 3, 6, 20] circle-color: "#7c3aed" circle-opacity: 0.7updateKey names a property (inside properties). A feature without
that property is matched by its top-level GeoJSON id instead, and one with
neither is kept but not matched: each update’s unmatched features replace the
previous update’s, and the console says so once. (Core 0.9.0 matched only the
property and dropped other features; this example matches on the USGS url
property, which works on every version.) Validation refuses merge without
updateKey.
For your own feed, say a vehicle API, each response is a FeatureCollection
of points whose properties include the key:
source: type: geojson url: "https://api.example.com/vehicles" # your own endpoint refresh: refreshInterval: 5000 updateStrategy: merge updateKey: vehicleId{ "type": "FeatureCollection", "features": [ { "type": "Feature", "geometry": { "type": "Point", "coordinates": [-122.41, 37.79] }, "properties": { "vehicleId": "bus-12", "status": "active" } } ]}A vehicle that stops reporting keeps its last position on the map. To show
that, have the feed send it with a status such as offline and colour by
status, or use replace if the feed always lists every vehicle.
Append with a window
Section titled “Append with a window”append-window adds every update to what is there, then trims: windowSize
keeps the newest N features, and windowDuration (milliseconds) drops
features older than that, judged by the number in timestampField
(milliseconds since 1970, as USGS’s time is). It suits a stream:
source: type: geojson data: { type: FeatureCollection, features: [] } # empty until messages arrive stream: type: sse url: "https://stream.example.com/events" refresh: updateStrategy: append-window windowSize: 100 # keep the newest 100 features windowDuration: 3600000 # and none older than an hour timestampField: timeReceive updates from a stream
Section titled “Receive updates from a stream”With a stream, the server sends updates as they happen instead of waiting to
be asked. A stream: block opens a Server-Sent Events (type: sse) or
WebSocket (type: websocket) connection:
source: type: geojson url: "https://api.example.com/vehicles" # the current state, loaded first stream: type: sse # or websocket, with a wss:// url url: "https://api.example.com/vehicles/stream" refresh: updateStrategy: merge updateKey: vehicleId- Each message is one GeoJSON
FeatureCollection, as JSON text: an SSE event’sdata:or one WebSocket message. Send a single changed feature inside a collection of one. Anything else (a bareFeature, text that isn’t JSON) is skipped and reported as a data error. - The source needs a
refresh:block besidestream:. The stream connects only when one is there, and itsupdateStrategysays how each message is applied. Validation warns when it is missing. - The source needs somewhere to start: a
url:with the current state, ordata:with an emptyFeatureCollectionto start blank. Undermerge, messages merge into those starting features. - Reconnecting is automatic. After a dropped connection the stream
retries up to
reconnectMaxAttemptstimes (10), waitingreconnectDelay(1 second) and doubling the wait each time up toreconnectMaxDelay(30 seconds). An SSE stream is reconnected by the browser itself, which resumes it withLast-Event-IDafter the wait the server sends asretry:; those tries count towardreconnectMaxAttempts, and the delays apply once the browser gives up (an HTTP error, say).reconnect: falseturns it off. - The layer keeps its data when the stream is down. A drop the stream
is reconnecting from shows nothing. A stream that can’t connect at first,
that runs out of attempts, or that closes with
reconnect: falsekeeps the layer and the data it has, and the map’s notice says once that it isn’t getting live updates (a first connection that fails is still retried). The line goes when the next message arrives. - An SSE stream listens for the
messageevent unlesseventTypesnames others. A WebSocket can ask for sub-protocols.
SSE is the simpler choice for updates that only flow from the server; use a
WebSocket when your server already speaks it. Every stream: and refresh:
key, with its default, is in the sources reference:
stream: and
refresh:.
Reacting to updates
Section titled “Reacting to updates”Each poll or stream message that reaches the map fires the layer’s data events, so a page can show when the data last changed or that a request failed:
const mapEl = document.querySelector("ml-map");
mapEl.addEventListener("ml-map:layer-data-loaded", (event) => { const { layerId, featureCount } = event.detail; document.querySelector("#updated").textContent = `${featureCount} features, updated ${new Date().toLocaleTimeString()}`;});
mapEl.addEventListener("ml-map:layer-data-error", (event) => { const { layerId, error } = event.detail; console.error(`${layerId}:`, error.message);});A classified layer recomputes its classes after
each update. Every event is in <ml-map> events.
With the JavaScript API, the same events are
renderer.on("layer:data-loaded", …) and renderer.on("layer:data-error", …).
Troubleshooting
Section titled “Troubleshooting”- The layer never updates. Check the browser’s network tab for a request
every interval. No request:
refresh:is not inside the source (it sits besideurl:), or the tab was hidden. A request that fails: the feed blocks other sites (CORS), or it is down; the map’s notice andml-map:layer-data-errorsay which. - The stream never connects. The source has
stream:but norefresh:; validation warns “stream: connects only when the source also has a refresh: block”. - With
merge, a note says features “have noupdateKeyproperty and no top-levelid, soupdateStrategy: mergecan’t match them”. Check the key’s spelling; the note lists property names the features do have. (On core 0.9.0 the layer empties instead.) - Stream messages are ignored. Each message must be a whole
FeatureCollection(see above); the console shows “Received data is not a valid GeoJSON FeatureCollection”.
More: Troubleshooting.
Next steps
Section titled “Next steps”- Earthquake tracker: a complete polling map.
- Gallery: Add live realtime data, Update a feature in realtime.
- Popups, hover & controls: what a reader sees when they click a feature.
Complete example
Section titled “Complete example”The vehicle-tracking walkthrough is now Merge, with a feed that works as printed.
Best practices
Section titled “Best practices”Folded into the sections above: Choose how updates combine and Troubleshooting.