Update GeoJSON polygons
Replicates the MapLibre example Update GeoJSON polygons. The YAML declares one named GeoJSON source and the two layers that split it by geometry type. The page seeds five rectangles with feature ids, then animates them with the source’s incremental updateData(), which patches only the features it names instead of re-tiling the whole collection as setData() (and updateLayerData()) does.
The YAML half
Section titled “The YAML half”# yaml-language-server: $schema=https://docs.maplibre-yaml.org/schema/latest/map.schema.json# Gallery twin of https://maplibre.org/maplibre-gl-js/docs/examples/update-geojson-polygons/# The YAML half: one named GeoJSON source and the two layers that split# it by geometry type. The page seeds five rectangles with feature ids,# then moves them with the source's incremental updateData() — only the# changed features are re-tiled — see update-geojson-polygons.js.type: mapid: update-geojson-polygonsconfig: center: [-68.13734351262877, 45.137451890638886] zoom: 5 mapStyle: "https://demotiles.maplibre.org/style.json"sources: rectangles: type: geojson data: { type: FeatureCollection, features: [] }layers: - id: rectangles type: fill source: rectangles filter: ["==", ["geometry-type"], "Polygon"] paint: fill-color: ["get", "color"] fill-opacity: 0.8 - id: rectangles-label type: symbol source: rectangles filter: ["==", ["geometry-type"], "Point"] layout: text-field: ["get", "label"] text-font: ["Noto Sans Regular"] text-size: 14 text-allow-overlap: true text-ignore-placement: true paint: text-color: "#ffffff"The JavaScript half
Section titled “The JavaScript half”// The JavaScript half: GeoJSONSource.updateData(), the incremental API.// setData() (and <ml-map>'s updateLayerData()) re-tiles the whole// collection; updateData() patches features by id, which is what makes a// per-frame animation of many features cheap.const mapEl = document.querySelector("ml-map");
const rectangles = Array.from({ length: 5 }, (_, i) => ({ id: i, x: -68.13 + (Math.random() - 0.5) * 5, y: 45.13 + (Math.random() - 0.5) * 5, vx: (Math.random() - 0.5) * 0.05, vy: (Math.random() - 0.5) * 0.05, w: 0.5 + Math.random(), h: 0.5 + Math.random(), rotation: Math.random() * 2 * Math.PI, rotationSpeed: (Math.random() - 0.5) * 0.1, color: "#008888", every: Math.round(Math.random() * 100 + 200), // frames between colour changes}));
const randomColor = () => `#${Math.floor(Math.random() * 0xffffff).toString(16).padStart(6, "0")}`;
function rectangleGeometry({ x, y, w, h, rotation }) { const c = Math.cos(rotation); const s = Math.sin(rotation); const ring = [[-w / 2, -h / 2], [w / 2, -h / 2], [w / 2, h / 2], [-w / 2, h / 2]] .map(([dx, dy]) => [x + dx * c - dy * s, y + dx * s + dy * c]); return { type: "Polygon", coordinates: [[...ring, ring[0]]] };}
mapEl.mapReady().then((map) => { map.showTileBoundaries = true; // watch which tiles each update touches const source = map.getSource("rectangles"); const zoom = () => map.getZoom().toFixed(1);
// Seed: features need ids for updateData() to address them. source.setData({ type: "FeatureCollection", features: rectangles.flatMap((r) => [ { type: "Feature", id: r.id, properties: { color: r.color }, geometry: rectangleGeometry(r) }, { type: "Feature", id: `${r.id}_label`, properties: { label: zoom() }, geometry: { type: "Point", coordinates: [r.x, r.y] } }, ]), });
let count = 0; function animate() { count++; const update = rectangles.flatMap((r) => { r.x += r.vx; r.y += r.vy; r.rotation += r.rotationSpeed; if (r.x < -75 || r.x > -60) r.vx *= -1; if (r.y < 40 || r.y > 50) r.vy *= -1; if (count % r.every === 0) r.color = randomColor(); return [ { id: r.id, newGeometry: rectangleGeometry(r), addOrUpdateProperties: [{ key: "color", value: r.color }] }, { id: `${r.id}_label`, newGeometry: { type: "Point", coordinates: [r.x, r.y] }, addOrUpdateProperties: [{ key: "label", value: zoom() }] }, ]; }); source.updateData({ update }); requestAnimationFrame(animate); } animate();});What to notice
Section titled “What to notice”updateDataaddresses features by their top-levelid— the seed data must carry ids.- Named sources keep their YAML name as the MapLibre source id, so
map.getSource("rectangles")finds it directly.