For AI agents: a documentation index is available at /llms.txt. Markdown versions of all pages are available by appending .md to any URL.
Filter annotations by category at runtime
This example demonstrates how to filter 3D Gaussian splat annotations by category at runtime using the setVisible() and setColor() methods.
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: [9.59401, 59.22317],
zoom: 17.5,
pitch: 50,
bearing: -15,
});
let splatModel;
map.on("load", () => {
splatModel = new SplatModel({
model: "YOUR_MAPTILER_SPLAT_MODEL_ID_HERE",
revealParams: { enabled: true },
});
map.addSplatModel(splatModel);
});
const CATEGORY_COLORS = {
structure: "#3174ff",
installations: "#e69439",
safety: "#2bb673",
};
const DIMMED_COLOR = [150, 150, 150, 0.35];
const poiData = [
{
id: "framing",
pos: { x: -0.35, z: -0.45 },
icon: "🏗️",
label: "Steel Frame",
category: "structure",
popup: "<h3>Steel Framework</h3><p>Primary structural steel columns and rafters assembly.</p>",
},
{
id: "foundation",
pos: { x: 0.25, z: 0.35 },
icon: "🧱",
label: "Foundation Slab",
category: "structure",
popup:
"<h3>Reinforced Concrete Slab</h3><p>Industrial ground floor slab curing inspection.</p>",
},
{
id: "hvac",
pos: { x: 0.05, z: -0.15 },
icon: "💨",
label: "HVAC Ductwork",
category: "installations",
popup: "<h3>HVAC Installation</h3><p>Main ventilation air ducts and extraction lines.</p>",
},
{
id: "electrical",
pos: { x: -0.55, z: 0.2 },
icon: "⚡",
label: "Electrical Panel",
category: "installations",
popup:
"<h3>Main Switchgear Panel</h3><p>High-voltage cable conduit trays and distribution boards.</p>",
},
{
id: "fire_safety",
pos: { x: 0.45, z: -0.5 },
icon: "🧯",
label: "Fire Point",
category: "safety",
popup:
"<h3>Emergency Fire Point</h3><p>Site fire extinguisher stand and alarm trigger point.</p>",
},
{
id: "ppe_point",
pos: { x: -0.1, z: 0.6 },
icon: "🦺",
label: "PPE Station",
category: "safety",
popup:
"<h3>PPE Mandatory Zone</h3><p>Hard hat and high-vis vest required beyond this checkpoint.</p>",
},
];
const allAnnotations = [];
let currentCategory = "all";
let isDimMode = false;
function applyFilter() {
for (const ann of allAnnotations) {
const data = ann.getData();
const matches = currentCategory === "all" || data.category === currentCategory;
if (isDimMode) {
ann.setVisible(true);
ann.setColor(matches ? data.defaultColor : DIMMED_COLOR);
} else {
ann.setVisible(matches);
ann.setColor(data.defaultColor);
}
}
}
map.on("splatmodel:load", () => {
for (const item of poiData) {
const color = CATEGORY_COLORS[item.category] || "#3174ff";
const ann = splatModel.addAnnotation({
pos: item.pos,
icon: item.icon,
label: item.label,
color,
popup: item.popup,
data: { category: item.category, defaultColor: color },
});
if (ann) {
allAnnotations.push(ann);
}
}
});
document.querySelectorAll(".filter-buttons button").forEach((btn) => {
btn.addEventListener("click", () => {
document
.querySelectorAll(".filter-buttons button")
.forEach((b) => b.classList.remove("active"));
btn.classList.add("active");
currentCategory = btn.getAttribute("data-cat");
applyFilter();
});
});
document.getElementById("dimModeCheckbox").addEventListener("change", (e) => {
isDimMode = e.target.checked;
applyFilter();
});
<!doctype html>
<html lang="en">
<head>
<meta charset="UTF-8" />
<meta name="robots" content="noindex" />
<title>Filter annotations by category at runtime</title>
<link rel="stylesheet" href="./style.css" />
</head>
<body>
<div class="filter-panel">
<div class="filter-buttons">
<button class="active" data-cat="all">All</button>
<button data-cat="structure">Structure</button>
<button data-cat="installations">Installations</button>
<button data-cat="safety">Safety</button>
</div>
<div class="filter-options">
<input type="checkbox" id="dimModeCheckbox" />
<label for="dimModeCheckbox">Dim inactive pins instead of hiding</label>
</div>
</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;
}
.filter-panel {
position: absolute;
top: 16px;
left: 50%;
transform: translateX(-50%);
background: rgba(255, 255, 255, 0.95);
backdrop-filter: blur(4px);
padding: 10px 18px;
border-radius: 12px;
box-shadow: 0 4px 16px rgba(0, 0, 0, 0.15);
z-index: 10;
display: flex;
flex-direction: column;
align-items: center;
gap: 8px;
}
.filter-buttons {
display: flex;
gap: 6px;
}
.filter-buttons button {
font-size: 13px;
padding: 4px 12px;
border-radius: 20px;
border: 1px solid #3174ff;
background: transparent;
color: #3174ff;
cursor: pointer;
}
.filter-buttons button.active {
background: #3174ff;
color: #fff;
}
.filter-options {
font-size: 12px;
color: #555;
display: flex;
align-items: center;
gap: 6px;
}
Attaching categories to custom data
When creating annotations, you can pass arbitrary application metadata through the data property. The SDK stores this object untouched:
splatModel.addAnnotation({
pos: { x: -0.35, z: -0.45 },
icon: "🏗️",
label: "Steel Frame",
data: { category: "structure", defaultColor: "#3174ff" },
});
Retrieve the stored data object at any point using annotation.getData().
Hiding unmatched annotations
To remove pins from view entirely when a filter is applied, call annotation.setVisible(false):
function filterByCategory(selectedCategory) {
for (const annotation of allAnnotations) {
const { category } = annotation.getData();
const isMatch = selectedCategory === "all" || category === selectedCategory;
annotation.setVisible(isMatch);
}
}
Hiding an annotation automatically closes its popup if it is open, triggering "annotation:inactive" cleanly without leaving dangling UI elements.
Dimming annotations with transparency
If you want to preserve spatial context rather than hiding pins completely, you can keep all annotations visible and dim inactive items using an RGBA color array:
const DIMMED_COLOR = [150, 150, 150, 0.35];
function dimInactive(selectedCategory) {
for (const annotation of allAnnotations) {
const { category, defaultColor } = annotation.getData();
const isMatch = selectedCategory === "all" || category === selectedCategory;
annotation.setVisible(true);
annotation.setColor(isMatch ? defaultColor : DIMMED_COLOR);
}
}
Learn more
Check out the MapTiler GeoSplats SDK reference to explore the full set of methods for SplatAnnotation.
Related examples
Pin styles and categories
ExamplesStyle annotations with colors, icons, numbers, and custom popup placement.
Sync annotations with sidebar
ExamplesConnect 3D model annotations bidirectionally with an external sidebar list.