---
title: "3D model set the position relative to another 3D model | 3D JS | JavaScript maps SDK"
description: "Position 3D models relative to other models or coordinates."
---

> For AI agents: a documentation index is available at [/llms.txt](https://docs.maptiler.com/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](https://docs.maptiler.com/sdk-js/modules/3d/) 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"`.

[Model credit](#fn:1)

### NPM module setup

```bash
npm install --save @maptiler/sdk @maptiler/3d three
```

#### main.js

```js
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);
  });
}
```

#### index.html

```html
<!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>
```

#### style.css

```css
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);
}
```

### Basic JavaScript setup

```html
<html lang="en">

<head>
  <meta charset="UTF-8">
  <meta http-equiv="X-UA-Compatible" content="IE=edge">
  <meta name="viewport" content="width=device-width, initial-scale=1.0">
  <title>MapTiler 3D | moveBy and setPositionRelativeTo</title>
  <script src="https://cdn.jsdelivr.net/npm/lil-gui@0.19"></script>
  <script src="https://cdn.maptiler.com/maptiler-sdk-js/v4.2.0/maptiler-sdk.umd.min.js"></script>
  <link href="https://cdn.maptiler.com/maptiler-sdk-js/v4.2.0/maptiler-sdk.css" rel="stylesheet" />
  <!-- Importing MapTiler 3D js -->
  <script src="https://cdn.maptiler.com/maptiler-3d/v4.0.1/maptiler-3d.umd.min.js"></script>
  <style>
    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);
    }
  </style>
</head>

<body>
  <script type="importmap">
    {
      "imports": {
      "three": "https://cdn.jsdelivr.net/npm/three@0.172.0/build/three.module.js",
      "three/addons/": "https://cdn.jsdelivr.net/npm/three@0.172.0/examples/jsm/"
      }
    }
  </script>
  <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">
    import * as THREE from 'three';
    maptilersdk.config.apiKey = 'YOUR_MAPTILER_API_KEY_HERE';

    const center = [2.35357, 48.85845];
    const modelAltitude = 200;

    const map = new maptilersdk.Map({
      container: document.getElementById('map'),
      hash: true,
      style: maptilersdk.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 maptiler3d.Layer3D("layer3d");
      map.addLayer(layer3D);

      layer3D.setAmbientLight({ intensity: 1 });
      layer3D.addPointLight("point-light", {
        intensity: 30,
        lngLat: center,
        altitude: 1000,
        altitudeReference: maptiler3d.AltitudeReference.MEAN_SEA_LEVEL,
      });

      // First Item3D at center (anchor)
      const anchor = layer3D.addMesh("anchor", createAxesMeshes(), {
        lngLat: center,
        altitude: modelAltitude,
        altitudeReference: maptiler3d.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);
      });
    }
  </script>
</body>

</html>
```

[Low-Poly Biplane](https://skfb.ly/oG9qS) by ElectrikGoat0395 is licensed under [Creative Commons Attribution](https://creativecommons.org/licenses/by/4.0/) [↩](#fnref:1)

## Related examples

### [3D model pitch](https://docs.maptiler.com/sdk-js/examples/3d-js-pitch/)

Examples

Rotate 3D models by setting the pitch tilt property.

### [3D model events](https://docs.maptiler.com/sdk-js/examples/3d-js-events/)

Examples

Listen for mouse events on 3D models in maps.

### [3D models intersections](https://docs.maptiler.com/sdk-js/examples/3d-js-intersections/)

Examples

Detect intersections between 3D models using MapTiler JS module.

### [Animate a 3D model](https://docs.maptiler.com/sdk-js/examples/3d-js-animated/)

Examples

Animate 3D models and display them on a map.
