Join us online - 19th of November
Save your spot
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();
});

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.

How to add annotations to a splat model

Add annotations

Examples

Add interactive labelled pins and popups to a splat model.

Customize annotation pins and colors

Pin styles and categories

Examples

Style annotations with colors, icons, numbers, and custom popup placement.

Track and update visited annotation states

Track visited annotations

Examples

Mark annotations as visited and update pin icons at runtime.

Synchronize annotations with a sidebar list

Sync annotations with sidebar

Examples

Connect 3D model annotations bidirectionally with an external sidebar list.

Was this helpful?