For AI agents: a documentation index is available at /llms.txt. Markdown versions of all pages are available by appending .md to any URL.
Position annotations relative to model or geography
This example explains the two coordinate systems supported by annotations in the MapTiler GeoSplats SDK: model-relative bounding box coordinates and geographic coordinates.
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 HOTEL = { lng: -80.27822, lat: 25.74077 };
const map = new Map({
apiKey: config.apiKey,
container: "map",
center: [HOTEL.lng, HOTEL.lat],
zoom: 17.5,
pitch: 76,
bearing: 100,
});
const TILE_SIZE_AT_EQUATOR_M = 40_075_016.686 / 2 ** 14;
function metersToY(meters, latitude) {
return meters / (TILE_SIZE_AT_EQUATOR_M * Math.cos((latitude * Math.PI) / 180));
}
let splatModel;
map.on("load", () => {
splatModel = new SplatModel({
model: "YOUR_MAPTILER_SPLAT_MODEL_ID_HERE",
revealParams: { enabled: true },
});
map.addSplatModel(splatModel);
});
map.on("splatmodel:load", () => {
// 1. Model-relative position: { x, z } normalized to the model bounding box [-1, 1]
splatModel.addAnnotation({
pos: { x: 0, z: 0 },
icon: "🎯",
label: "Model Center (0, 0)",
popup:
"<h3>Model Center</h3><p>Positioned at <code>{ x: 0, z: 0 }</code>, the normalized center of the model bounding box. Moves and rotates with the model.</p>",
});
// 2. Model-relative with height offset (y): hovers above the model collision surface
splatModel.addAnnotation({
pos: { x: -0.123, z: -0.123, y: metersToY(5, HOTEL.lat) },
icon: "🗼",
label: "Tower Rooftop (+5m)",
color: "#2bb673",
popup:
"<h3>Tower Rooftop</h3><p>Elevated 5 meters above the surface using <code>y: metersToY(5, lat)</code>.</p>",
});
// 3. Geographic position: { lng, lat } in WGS84 degrees
splatModel.addAnnotation({
pos: { lng: -80.279382, lat: 25.741509, y: metersToY(10, HOTEL.lat) },
icon: "🚏",
label: "Bus Stop (WGS84) (+10m)",
color: "#e69439",
popup:
"<h3>Bus Stop</h3><p>Fixed geographic coordinates <code>{ lng, lat }</code>. Anchored to the map and independent of model transforms.</p>",
});
// 4. Geographic position using { lon, lat }
splatModel.addAnnotation({
pos: { lon: -80.278575, lat: 25.741053 },
icon: "🚕",
label: "Taxi Stand (WGS84)",
color: "#e69439",
popup:
"<h3>Taxi Stand</h3><p>Real-world POI defined using <code>{ lon, lat }</code> coordinates.</p>",
});
});
<!doctype html>
<html lang="en">
<head>
<meta charset="UTF-8" />
<meta name="robots" content="noindex" />
<title>Position annotations relative to model or geography</title>
<link rel="stylesheet" href="./style.css" />
</head>
<body>
<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;
}
Model-relative coordinates
A model-relative position uses { x, z } coordinates normalized to the model’s bounding box:
{ x: 0, z: 0 }is the horizontal center of the bounding box.- Values of
-1and1represent the bounding edges (-1is left/bottom,1is right/top). - Values outside
[-1, 1]place the pin outside the bounding box.
splatModel.addAnnotation({
pos: { x: 0, z: 0 },
label: "Model Center",
});
Model-relative annotations automatically move, rotate, and scale whenever the model is repositioned, rotated, or dragged. Use them for architectural points of interest, entrances, or equipment attached to the structure.
Geographic coordinates
A geographic position uses WGS84 coordinates defined by { lng, lat } (or { lon, lat }):
splatModel.addAnnotation({
pos: { lng: -80.27857, lat: 25.74079 },
label: "Bus stop",
});
Geographic annotations remain fixed at their geographic coordinates on the map surface, even if the 3D splat model moves. Use them for permanent surrounding landmarks, public transit stops, parking facilities, or road connections.
Vertical height offset
By default, an annotation rests directly on whatever surface lies beneath it (either the model’s collision mesh or the base terrain).
The optional y offset lifts or lowers the pin relative to that surface. It is measured in SDK tile-relative world units, where 1 unit corresponds to the dimension of a zoom-14 tile (~2,446 m at the equator). To position an annotation by a specific height in meters, convert meters using the model’s latitude:
const TILE_SIZE_AT_EQUATOR_M = 40_075_016.686 / 2 ** 14;
function metersToY(meters, latitude) {
return meters / (TILE_SIZE_AT_EQUATOR_M * Math.cos((latitude * Math.PI) / 180));
}
// Hover 5 meters above the surface
splatModel.addAnnotation({
pos: { x: 0.05, z: -0.1, y: metersToY(5, 25.74) },
label: "Tower Rooftop (+5m)",
});
Learn more
Check out the MapTiler GeoSplats SDK reference for full type specifications of AnnotationPosition, ModelAnnotationPosition, and GeoAnnotationPosition.
Related examples
Pin styles and categories
ExamplesStyle annotations with colors, icons, numbers, and custom popup placement.
Rich HTML popups
ExamplesDisplay custom HTML cards in annotation popups with images, tables, and custom widths.
Add splat model
ExamplesLearn how to load a map using the GeoSplats SDK and add a photorealistic splat model. A splat model offer a modern alternative to 3D meshes, perfect for complex scenes.