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.
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,
),
);