Markers
The Annotation API lets you place individual interactive markers, circles, lines, and polygons on the map with built-in tap and drag callbacks.
Five European landmark markers. Tap any to see its name.
When to use the Annotation API
Use annotations when you have fewer than ~50 features and need individual interactivity (tap callbacks, draggable). For large datasets use Style Layers.
Prerequisites
Annotations only work once the style has loaded and if their type is part of
the widget's annotationOrder. See Prerequisites.
Add a symbol (icon + text)
iconImage references an image from the active style's sprite, or one you
registered yourself with addImage / addImageFromAsset. There are no icons
that exist in every style, so register your own first (see
Custom image markers below) and reference it by name:
await addImageFromAsset(controller, 'my-pin', 'assets/markers/pin.png');
final symbol = await controller.addSymbol(
const SymbolOptions(
geometry: LatLng(48.8566, 2.3522),
iconImage: 'my-pin', // a name you registered with addImage
iconSize: 1.0,
textField: 'Paris',
textOffset: Offset(0, 1.5),
textAnchor: 'top',
textSize: 14,
textColor: '#1a1a2e',
textHaloColor: '#ffffff',
textHaloWidth: 2,
),
);
Icons are style-dependent
iconImage must name an image the map actually has. If you reference a name
that isn't in the style's sprite and wasn't registered with addImage, the
symbol renders nothing and the console logs "image … could not be loaded".
Many styles (including the MapLibre demo style) ship no general-purpose
marker sprite, so register your own image rather than assuming a built-in
name exists.
Tap callback
Update a symbol
await controller.updateSymbol(
symbol,
const SymbolOptions(
iconColor: '#2ECC71', // change color
textField: 'Paris, FR', // change label
),
);
Remove a symbol
Add multiple symbols at once
final symbols = await controller.addSymbols([
const SymbolOptions(geometry: LatLng(48.86, 2.35), iconImage: 'my-pin'),
const SymbolOptions(geometry: LatLng(51.50, -0.13), iconImage: 'my-pin'),
const SymbolOptions(geometry: LatLng(52.52, 13.40), iconImage: 'my-pin'),
]);
Where icons come from
iconImage resolves against the images the map currently has:
- The active style's sprite — some styles bundle a named icon set (e.g. a
style built on the Maki icons exposes
marker-15,restaurant-15, …). These names only exist if that style includes them; they are not guaranteed. - Images you register at runtime with
addImage/addImageFromAsset— always available regardless of the style. This is the portable choice.
Because the MapLibre demo style (and many others) ship no general-purpose marker sprite, the examples here register their own image rather than relying on a built-in name.
Custom image markers
Register your image once with addImage() (in onStyleLoadedCallback), then reference it by name as the iconImage:
final bytes = await rootBundle.load('assets/markers/pin.png');
await controller.addImage('my-pin', bytes.buffer.asUint8List());
await controller.addSymbol(
const SymbolOptions(
geometry: LatLng(48.8566, 2.3522),
iconImage: 'my-pin',
iconSize: 1.0,
),
);
Other annotation types
// Circle
final circle = await controller.addCircle(
CircleOptions(
geometry: const LatLng(48.86, 2.35),
circleRadius: 20,
circleColor: '#296CA8',
circleOpacity: 0.5,
),
);
controller.onCircleTapped.add((Circle c) { ... });
// Line
final line = await controller.addLine(
LineOptions(
geometry: const [LatLng(48.86, 2.35), LatLng(51.50, -0.13)],
lineColor: '#E74C3C',
lineWidth: 3,
),
);
// Fill (polygon)
final fill = await controller.addFill(
FillOptions(
geometry: const [
[LatLng(48.7, 2.2), LatLng(49.0, 2.2), LatLng(49.0, 2.5), LatLng(48.7, 2.5)],
],
fillColor: '#296CA8',
fillOpacity: 0.3,
),
);