For AI agents: a documentation index is available at /llms.txt. Markdown versions of all pages are available by appending .md to any URL.
Track and update visited annotation states
This example demonstrates how to use the SplatAnnotation runtime methods setColor(), setIcon(), and event listeners to track user engagement and mark houses as visited across a 6-house residential development.
NPM module setup
npm install --save @maptiler/geosplats
import { SplatModel, Map, config } from "@maptiler/geosplats";
import "@maptiler/geosplats/dist/maptiler-geosplats.css";
config.apiKey = "YOUR_MAPTILER_API_KEY_HERE";
const map = new Map({
apiKey: config.apiKey,
container: "map",
center: [-74.42418, 40.77947],
zoom: 18.2,
pitch: 52,
bearing: -30,
});
let splatModel;
map.on("load", () => {
splatModel = new SplatModel({
model: "YOUR_MAPTILER_SPLAT_MODEL_ID_HERE",
revealParams: { enabled: true },
});
map.addSplatModel(splatModel);
});
const VISITED_COLOR = "#e69439";
const visitedSet = new Set();
const annotationsList = [];
const houseConfigs = [
{
id: "house-1",
pos: { x: 0.05, z: -0.58 },
icon: "π‘",
label: "House 1",
popup:
"<h3>π‘ House 1</h3><p>Two-story colonial with private driveway and landscaped lawn.</p>",
},
{
id: "house-2",
pos: { x: 0.35, z: -0.35 },
icon: "π‘",
label: "House 2",
popup: "<h3>π‘ House 2</h3><p>Modern family residence featuring an open-concept patio.</p>",
},
{
id: "house-3",
pos: { x: -0.4, z: 0.05 },
icon: "π‘",
label: "House 3",
popup: "<h3>π‘ House 3</h3><p>Spacious 4-bedroom home with attached two-car garage.</p>",
},
{
id: "house-4",
pos: { x: 0.4, z: 0.05 },
icon: "π‘",
label: "House 4",
popup: "<h3>π‘ House 4</h3><p>Contemporary suburban home with south-facing solar roof.</p>",
},
{
id: "house-5",
pos: { x: -0.3, z: 0.42 },
icon: "π‘",
label: "House 5",
popup:
"<h3>π‘ House 5</h3><p>Corner lot cottage with mature oak trees and fenced backyard.</p>",
},
{
id: "house-6",
pos: { x: -0.55, z: -0.35 },
icon: "π‘",
label: "House 6",
popup:
"<h3>π‘ House 6</h3><p>Executive villa with walk-out basement and scenic garden view.</p>",
},
];
function updateCounter() {
const counter = document.getElementById("visitedCount");
if (counter) {
counter.textContent = `${visitedSet.size} / ${houseConfigs.length}`;
}
}
map.on("splatmodel:load", () => {
for (const config of houseConfigs) {
const annotation = splatModel.addAnnotation({
pos: config.pos,
icon: config.icon,
label: config.label,
popup: config.popup,
data: { id: config.id, defaultIcon: config.icon },
});
if (annotation) {
annotationsList.push(annotation);
}
}
});
// 1. Mark with visited color when clicked
map.on("splatmodel:annotation:click", ({ annotation }) => {
annotation.setColor(VISITED_COLOR);
const data = annotation.getData();
if (data && data.id) {
visitedSet.add(data.id);
updateCounter();
}
});
// 2. Change icon to checkmark once popup closes
map.on("splatmodel:annotation:inactive", ({ annotation }) => {
const data = annotation.getData();
if (data && visitedSet.has(data.id)) {
annotation.setIcon("βοΈ");
}
});
// 3. Reset all visited annotations
document.getElementById("resetBtn")?.addEventListener("click", () => {
visitedSet.clear();
for (const ann of annotationsList) {
const data = ann.getData();
ann.setColor(undefined);
if (data && data.defaultIcon) {
ann.setIcon(data.defaultIcon);
}
}
updateCounter();
});
<!doctype html>
<html lang="en">
<head>
<meta charset="UTF-8" />
<meta name="robots" content="noindex" />
<title>Track and update visited annotation states</title>
<link rel="stylesheet" href="./style.css" />
</head>
<body>
<div class="visited-panel">
<div class="title">Visited Houses: <span id="visitedCount">0 / 6</span></div>
<button id="resetBtn">Reset Visits</button>
</div>
<div id="map"></div>
<script type="module" src="./main.js"></script>
</body>
</html>
body {
font-family: Inter, sans-serif;
margin: 0;
overflow: hidden;
}
#map {
width: 100%;
height: 100vh;
}
.visited-panel {
position: absolute;
top: 16px;
left: 16px;
background: rgba(255, 255, 255, 0.95);
backdrop-filter: blur(4px);
padding: 12px 16px;
border-radius: 8px;
box-shadow: 0 4px 14px rgba(0, 0, 0, 0.15);
z-index: 10;
font-size: 13px;
}
.visited-panel .title {
font-weight: 600;
color: #333;
margin-bottom: 6px;
}
.visited-panel button {
background: #f8f9fa;
border: 1px solid #ced4da;
padding: 4px 10px;
border-radius: 4px;
cursor: pointer;
width: 100%;
font-size: 12px;
font-weight: 500;
}
.visited-panel button:hover {
background: #e9ecef;
}
Listening to annotation events
The GeoSplats SDK provides three key annotation lifecycle events:
"annotation:click": Fires when the user clicks a pin, right before its popup begins to open."annotation:active": Fires when the popup opens on screen."annotation:inactive": Fires when the popup closes (via the close button, clicking the map, or opening another popup).
Every event delivers the affected annotation handle in its payload.
Updating pin colors dynamically
When a user clicks a house annotation, call annotation.setColor() to change its background fill immediately to an in-progress color:
const VISITED_COLOR = "#e69439";
map.on("splatmodel:annotation:click", ({ annotation }) => {
annotation.setColor(VISITED_COLOR);
});
The pin animates smoothly and maintains its new color even when the camera moves.
Changing icons when popups close
When the user finishes reading a popup and closes it, the "annotation:inactive" event fires. Use annotation.setIcon() to replace the pinβs house icon with a completion badge like a checkmark:
map.on("splatmodel:annotation:inactive", ({ annotation }) => {
annotation.setIcon("βοΈ");
});
The pin automatically recalculates its geometry and resizes with a brief squash-and-stretch animation.
Resetting annotation properties
Calling annotation.setColor(undefined) removes the custom color and restores the default MapTiler blue theme:
// Reset pin to default theme
annotation.setColor(undefined);
// Restore original icon from custom data
const data = annotation.getData();
if (data?.defaultIcon) {
annotation.setIcon(data.defaultIcon);
}
Learn more
Check out the MapTiler GeoSplats SDK reference to learn more about the SplatAnnotation API and event parameters.
Related examples
Pin styles and categories
ExamplesStyle annotations with colors, icons, numbers, and custom popup placement.
Guided tour with annotations
ExamplesGuide users through model annotations with next and previous buttons.
Filter annotations by category
ExamplesFilter annotations by category using visibility toggle or color dimming.