Annotations allow you to add interactive elements at specific geographic coordinates on your map. This guide explains how to create and customise annotations with MapLayr.
Add a CoordinateAnnotationLayer inside your MapView, telling it where each annotation goes and how it looks:
import { MapView, CoordinateAnnotationLayer } from '@maplayr/react-native';
const rides = [
{ id: '1', name: 'Daeva', coordinates: { latitude: 52.8952, longitude: -1.8431 } },
{ id: '2', name: "Eagle's Flight", coordinates: { latitude: 52.8982, longitude: -1.8463 } },
];
<MapView style={styles.map} map={mapLoadResult.map}>
<CoordinateAnnotationLayer
data={rides}
keyExtractor={(ride) => ride.id}
coordinates={(ride) => ride.coordinates}
renderAnnotation={({ item }) => (
<View style={styles.annotation}>
<Text>{item.name}</Text>
</View>
)}
/>
</MapView>
renderAnnotation can return any component, and each annotation is sized by its content and styles.
Multiple CoordinateAnnotationLayers can be added to the MapView, though typically only one is required.
By default an annotation is centred on its coordinates. anchor sets which point of the annotation
sits on the coordinates instead. Each axis is a percentage of the annotation's size, or a number of
points from its left or top edge. An annotation that points down at its coordinates is anchored at
its bottom centre:
<CoordinateAnnotationLayer anchor={{ x: '50%', y: '100%' }} ... />
Points keep one part of an annotation on its coordinates however the rest is sized. For a 26 point icon with a label below it, this anchors on the centre of the icon:
<CoordinateAnnotationLayer anchor={{ x: '50%', y: 13 }} ... />
Pass a function to anchor each item differently.
Selection is controlled by your component. A tap calls onSelect or onDeselect with the item, and
the layer shows whatever selectedKeys says on the next render, so hold the selection in state.
renderAnnotation receives isSelected so the annotation can change how it looks:
const [selectedId, setSelectedId] = useState<string | null>(null);
<CoordinateAnnotationLayer
data={rides}
keyExtractor={(ride) => ride.id}
coordinates={(ride) => ride.coordinates}
selectedKeys={selectedId ? [selectedId] : []}
onSelect={(ride) => setSelectedId(ride.id)}
onDeselect={(ride) => setSelectedId((current) => (current === ride.id ? null : current))}
renderAnnotation={({ item, isSelected }) =>
<RideAnnotation ride={item} selected={isSelected} />
}
/>
By default only one annotation can be selected at a time: tapping an annotation selects it in place
of the previous one, and tapping the map clears the selection. With allowsMultipleSelection, each
tap adds or removes that annotation from the selection:
const [selectedIds, setSelectedIds] = useState<string[]>([]);
<CoordinateAnnotationLayer
allowsMultipleSelection
selectedKeys={selectedIds}
onSelect={(ride) => setSelectedIds((ids) => [...ids, ride.id])}
onDeselect={(ride) => setSelectedIds((ids) => ids.filter((id) => id !== ride.id))}
...
/>
The map decides selection from where a tap lands, whatever the annotation renders. Touchable
components inside an annotation, such as Pressable, still work, and a tap on one also counts as a
tap on its annotation: it selects the annotation, or with allowsMultipleSelection adds or removes
it from the selection.
An annotation's component may be reused for a different item, so render it only from item and isSelected:
data, updating the item it belongs to.key={item.id}.[item.id].An annotation re-renders only when its item, its selection, or one of renderAnnotation,
coordinates and anchor changes. With many annotations, keep those stable, with useCallback
or a constant, and replace only the items that change, so that a change re-renders only the
annotations it affects.
Give items whose annotations are built differently their own annotationType, which helps the layer render them efficiently.