For AI agents: a documentation index is available at /llms.txt. Markdown versions of all pages are available by appending .md to any URL.
3D model set the position relative to another 3D model
Use the MapTiler 3D JS module to set the position of una 3D model relative to another 3D model or a 3D position.
Apply a offset: offset.x = longitude direction, offset.y = altitude, offset.z = latitude.
Define the units: "meters" (default), "feet", "km", "miles".
NPM module setup
npm install --save @maptiler/sdk @maptiler/3d three
import { Map, MapStyle, config } from '@maptiler/sdk';
import '@maptiler/sdk/dist/maptiler-sdk.css';
import { Layer3D, AltitudeReference } from '@maptiler/3d';
import * as THREE from 'three';
config.apiKey = 'YOUR_MAPTILER_API_KEY_HERE';
const center = [2.35357, 48.85845];
const modelAltitude = 200;
const map = new Map({
container: document.getElementById('map'),
hash: true,
style: MapStyle.SATELLITE,
center: [2.35872, 48.86306],
zoom: 14.48,
bearing: 31.9,
pitch: 63,
maxPitch: 85,
projectionControl: true,
centerClampedToGround: false,
});
(async () => {
await map.onReadyAsync();
const layer3D = new Layer3D("layer3d");
map.addLayer(layer3D);
layer3D.setAmbientLight({ intensity: 1 });
layer3D.addPointLight("point-light", {
intensity: 30,
lngLat: center,
altitude: 1000,
altitudeReference: AltitudeReference.MEAN_SEA_LEVEL,
});
// First Item3D at center (anchor)
const anchor = layer3D.addMesh("anchor", createAxesMeshes(), {
lngLat: center,
altitude: modelAltitude,
altitudeReference: AltitudeReference.MEAN_SEA_LEVEL,
scale: 5,
sourceOrientation: maptiler3d.SourceOrientation.Y_UP,
});
let mousedown = false;
let startLngLat = new maptilersdk.LngLat(0, 0);
anchor.on("mousedown", (e) => {
map.dragPan.disable();
map.dragRotate.disable();
mousedown = true;
startLngLat = e.lngLat;
});
// we need to disable the drag pan and rotate when the mouse is up, otherwise the map will continue to pan and rotate when the mouse is released.
map.on("mouseup", (e) => {
if (mousedown) {
mousedown = false;
map.dragPan.enable();
map.dragRotate.enable();
}
});
// re-enable the drag pan and rotate when the mouse is up
anchor.on("mouseup", () => {
mousedown = false;
map.dragPan.enable();
map.dragRotate.enable();
});
map.on("mousemove", (e) => {
if (mousedown) {
// this is not an exact calculation but will suffice for the purposes of the demo.
const eventLngLat = e.lngLat;
const diff = new maptilersdk.LngLat(eventLngLat.lng - startLngLat.lng, eventLngLat.lat - startLngLat.lat);
const newLngLat = new maptilersdk.LngLat(anchor.lngLat.lng + diff.lng, anchor.lngLat.lat + diff.lat);
anchor.setLngLat(newLngLat);
shadowingAnchor.setPositionRelativeTo(anchor, { x: 100, y: 100, z: 100 }, "meters");
startLngLat = eventLngLat;
}
});
const shadowingAnchor = anchor.clone();
shadowingAnchor.setPositionRelativeTo(anchor, { x: 100, y: 100, z: 100 }, "meters");
shadowingAnchor.setRelativeScale(0.4);
addForm({ anchor, shadowingAnchor });
})()
function createAxesMeshes() {
const directions = ["x", "y", "z"];
const group = new THREE.Group();
for (const direction of directions) {
const arrow = new THREE.Group();
const color = direction === "x" ? 0xff0000 : direction === "y" ? 0x00ff00 : 0x0000ff;
const stem = new THREE.Mesh(new THREE.CylinderGeometry(0.1, 0.1, 1), new THREE.MeshStandardMaterial({ color: color }));
stem.position.set(0, 0.5, 0);
const head = new THREE.Mesh(new THREE.ConeGeometry(0.2, 1), new THREE.MeshStandardMaterial({ color: color }));
head.position.set(0, 1.5, 0);
arrow.add(stem);
arrow.add(head);
arrow.name = direction;
arrow.rotation.set(direction === "x" ? Math.PI / 2 : 0, direction === "y" ? Math.PI / 2 : 0, direction === "z" ? Math.PI / 2 : 0);
group.add(arrow);
}
const centralSphere = new THREE.Mesh(new THREE.SphereGeometry(0.25), new THREE.MeshStandardMaterial({ color: 0xffffff }));
group.add(centralSphere);
group.scale.set(10, 10, 10);
return group;
}
function addForm({ anchor, shadowingAnchor }) {
const form = document.getElementById("form");
form.addEventListener("submit", function (e) {
e.preventDefault();
const formData = new FormData(form);
const x = Number(formData.get("x"));
const y = Number(formData.get("y"));
const z = Number(formData.get("z"));
const units = formData.get("units");
anchor.moveBy({ x, y, z }, units);
shadowingAnchor.setPositionRelativeTo(anchor, { x: 100, y: 100, z: 100 }, units);
});
}
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="robots" content="noindex">
<title>MapTiler 3D | moveBy and setPositionRelativeTo</title>
<link rel="stylesheet" href="./style.css">
</head>
<body>
<div id="map"></div>
<div id="drag-instruction">
🖱️ Drag the axes of the larger object to move it directly
</div>
<form id="form">
<label>
x: <input type="number" name="x" value="0" step="1" />
</label>
<label>
y: <input type="number" name="y" value="0" step="1" />
</label>
<label>
z: <input type="number" name="z" value="0" step="1" />
</label>
<label>
<select name="units">
<option value="meters">meters</option>
<option value="feet">feet</option>
<option value="km">km</option>
<option value="miles">miles</option>
</select>
</label>
<button type="submit">Move anchor</button>
</form>
<script type="module" src="./main.js"></script>
</body>
</html>
body {
margin: 0;
padding: 0;
}
#map {
position: absolute;
top: 0;
bottom: 0;
width: 100%;
}
#form {
position: absolute;
top: 10px;
left: 10px;
z-index: 1000;
background: rgba(0, 0, 0, 0.75);
color: #eee;
padding: 12px 16px;
border-radius: 8px;
box-shadow: 0 2px 12px rgba(0, 0, 0, 0.3);
}
#form label {
display: inline-block;
margin-right: 8px;
margin-bottom: 4px;
}
#form input,
#form select {
background: rgba(255, 255, 255, 0.15);
color: #fff;
border: 1px solid rgba(255, 255, 255, 0.2);
border-radius: 4px;
padding: 4px 8px;
}
#form input[type="number"] {
width: 60px;
}
#form select {
width: 80px;
}
#form button {
background: rgba(255, 255, 255, 0.2);
color: #fff;
border: 1px solid rgba(255, 255, 255, 0.3);
border-radius: 4px;
padding: 6px 12px;
cursor: pointer;
}
#form button:hover {
background: rgba(255, 255, 255, 0.3);
}
#drag-instruction {
background: rgba(0, 0, 0, 0.75);
color: #eee;
padding: 8px 16px;
border-radius: 6px;
position: absolute;
top: 70px;
left: 10px;
z-index: 1001;
font-size: 1rem;
box-shadow: 0 2px 8px rgba(0, 0, 0, 0.3);
}
Low-Poly Biplane by ElectrikGoat0395 is licensed under Creative Commons Attribution ↩