Skip to content

Draw a circle

Replicates the MapLibre example Draw a circle. The fill and outline layers and their shared source are YAML; the source starts empty. The polygon is computed at runtime from a centre and a radius — what turf.circle does upstream, in a few lines — and handed to the layers with updateLayerData(). A slider in a corner slot recomputes it.

Drag the slider to change the radius
draw-a-circle.yaml
# 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/draw-a-circle/
# The YAML half: an OpenStreetMap raster basemap and the two layers that
# style the circle. The circle polygon is computed at runtime from a centre
# and a radius (what turf.circle does upstream) and handed to the layer —
# see draw-a-circle.js. A precomputed polygon would make this pure YAML.
type: map
id: draw-a-circle
config:
center: [2.3454, 48.8452]
zoom: 13
maxZoom: 18
mapStyle: "https://demotiles.maplibre.org/style.json"
sources:
osm:
type: raster
tiles: ["https://tile.openstreetmap.org/{z}/{x}/{y}.png"]
tileSize: 256
maxzoom: 19
attribution: "© OpenStreetMap Contributors"
location-radius:
type: geojson
data: { type: FeatureCollection, features: [] }
layers:
- id: osm
type: raster
source: osm
- id: location-radius
type: fill
source: location-radius
paint:
fill-color: "#8CCFFF"
fill-opacity: 0.5
- id: location-radius-outline
type: line
source: location-radius
paint:
line-color: "#0094ff"
line-width: 3

This markup goes inside <ml-map>; children with a slot ride its corner system.

draw-a-circle.html
<div slot="top-left" class="gallery-panel">
<label>Radius <strong data-radius-readout>1.0 km</strong></label>
<input type="range" data-radius min="0.2" max="3" step="0.1" value="1" />
</div>
draw-a-circle.js
// The JavaScript half: compute geometry in the page, hand it to the layer.
// circle() is what turf.circle does — 64 points at a fixed great-circle
// distance from the centre — and updateLayerData() fills the (initially
// empty) source both YAML layers share. The slider recomputes it live.
const mapEl = document.querySelector("ml-map");
const slider = document.querySelector("[data-radius]");
const readout = document.querySelector("[data-radius-readout]");
const center = [2.3454, 48.8452];
function circle([lng, lat], radiusKm, steps = 64) {
const R = 6371.0088; // mean Earth radius, km
const d = radiusKm / R;
const φ1 = (lat * Math.PI) / 180;
const λ1 = (lng * Math.PI) / 180;
const ring = [];
for (let i = 0; i <= steps; i++) {
const θ = (-2 * Math.PI * (i % steps)) / steps; // counter-clockwise, closed
const φ2 = Math.asin(Math.sin(φ1) * Math.cos(d) + Math.cos(φ1) * Math.sin(d) * Math.cos(θ));
const λ2 = λ1 + Math.atan2(Math.sin(θ) * Math.sin(d) * Math.cos(φ1), Math.cos(d) - Math.sin(φ1) * Math.sin(φ2));
ring.push([(λ2 * 180) / Math.PI, (φ2 * 180) / Math.PI]);
}
return { type: "Feature", properties: { radiusKm }, geometry: { type: "Polygon", coordinates: [ring] } };
}
function draw() {
const km = Number(slider.value);
readout.textContent = `${km.toFixed(1)} km`;
mapEl.updateLayerData("location-radius", circle(center, km));
}
mapEl.mapReady().then(() => {
draw();
slider.addEventListener("input", draw);
});
  • A circle that never changes needs no JS: precompute the polygon and paste it into data: — the page is then pure YAML.
  • The two layers share one named source, so one updateLayerData call redraws both.