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 ↩