---
title: "Level of detail control | JavaScript maps SDK"
description: "Modify tile level of detail at high pitch angles."
---

> 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.

# Level of detail control

Adjust how many distinct zoom levels are visible on screen and manage the tile density budget at high pitch angles.

This example demonstrates how to use the `setSourceTileLodParams()` method to optimize map tile loading and rendering performance when the camera is heavily tilted (high pitch), displaying tile boundaries to visualize the Level of Detail (LOD) transitions.

### NPM module setup

```bash
npm install --save @maptiler/sdk
```

#### main.js

```js
import { Map, config } from '@maptiler/sdk';
import '@maptiler/sdk/dist/maptiler-sdk.css';

config.apiKey = 'YOUR_MAPTILER_API_KEY_HERE';

const map = new Map({
  container: 'map',
  zoom: 12,
  pitch: 77,
  center: [0, 0],
  style: {
    version: 8,
    sources: {
      numbers: {
        type: 'raster',
        url: 'https://demotiles.maplibre.org/debug-tiles/number/tiles.json',
        tileSize: 256,
        maxzoom: 19
      }
    },
    layers: [
      {
        id: 'numbers',
        type: 'raster',
        source: 'numbers'
      }
    ]
  },
  maxZoom: 22,
  maxPitch: 85
});

function setLodParamsFromUi() {
  const maxZoomVal = document.getElementById('max-zoom-levels-slider').value;
  const tileCountVal = document.getElementById('tile-count-ratio-slider').value;

  // Update UI labels
  document.getElementById('max-zoom-levels-value').textContent = maxZoomVal;
  document.getElementById('tile-count-ratio-value').textContent = tileCountVal;

  // Apply parameters to our debug tiles source 'numbers'
  try {
    map.setSourceTileLodParams(maxZoomVal, tileCountVal);
  } catch (e) {
    console.warn("setSourceTileLodParams failed for source 'numbers':", e);
  }
}

map.on('load', () => {
  document.getElementById('max-zoom-levels-slider').addEventListener('input', setLodParamsFromUi);
  document.getElementById('tile-count-ratio-slider').addEventListener('input', setLodParamsFromUi);
  // Initial parameters setup
  setLodParamsFromUi();
});
```

#### index.html

```html
<!doctype html>
<html lang="en">
  <head>
    <meta charset="UTF-8" />
    <meta name="robots" content="noindex" />
    <title>Level of Detail Control</title>
    <link rel="stylesheet" href="./style.css" />
  </head>
  <body>
    <div id="map"></div>

    <div class="control-panel">
      <h3>LOD Parameters</h3>

      <div class="control-row">
        <div class="control-label-wrapper">
          <label class="control-label" for="max-zoom-levels-slider"
            >Max Zoom Levels On Screen</label
          >
          <span id="max-zoom-levels-value" class="control-value">9.0</span>
        </div>
        <input type="range" id="max-zoom-levels-slider" min="1" max="11" step="1.0" value="9.0" />
      </div>

      <div class="control-row">
        <div class="control-label-wrapper">
          <label class="control-label" for="tile-count-ratio-slider"
            >Tile Count Max/Min Ratio</label
          >
          <span id="tile-count-ratio-value" class="control-value">3.0</span>
        </div>
        <input type="range" id="tile-count-ratio-slider" min="1" max="10" step="0.1" value="3.0" />
      </div>
    </div>
    <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%;
}

.control-panel {
  font-family:
    -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, "Helvetica Neue", Arial, sans-serif;
  position: absolute;
  top: 15px;
  left: 15px;
  color: #333;
  background: rgba(255, 255, 255, 0.95);
  padding: 15px;
  border-radius: 8px;
  box-shadow: 0 4px 12px rgba(0, 0, 0, 0.15);
  z-index: 10;
  width: 280px;
  max-width: calc(100% - 30px);
}

.control-panel h3 {
  margin: 0 0 12px 0;
  font-size: 14px;
  font-weight: 600;
  color: #111;
  border-bottom: 1px solid #eee;
  padding-bottom: 8px;
}

.control-row {
  display: flex;
  flex-direction: column;
  margin-bottom: 12px;
}

.control-row:last-child {
  margin-bottom: 0;
}

.control-label-wrapper {
  display: flex;
  justify-content: space-between;
  align-items: center;
  margin-bottom: 4px;
}

.control-label {
  font-size: 11px;
  font-weight: 500;
  color: #555;
}

.control-value {
  font-size: 11px;
  font-weight: bold;
  color: #000;
}

.control-row input[type="range"] {
  width: 100%;
  margin: 4px 0;
}
```

### 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>Level of Detail Control</title>
    <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"
    />
    <style>
      body {
        margin: 0;
        padding: 0;
      }

      #map {
        position: absolute;
        top: 0;
        bottom: 0;
        width: 100%;
      }

      .control-panel {
        font-family:
          -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, "Helvetica Neue", Arial, sans-serif;
        position: absolute;
        top: 15px;
        left: 15px;
        color: #333;
        background: rgba(255, 255, 255, 0.95);
        padding: 15px;
        border-radius: 8px;
        box-shadow: 0 4px 12px rgba(0, 0, 0, 0.15);
        z-index: 10;
        width: 280px;
        max-width: calc(100% - 30px);
      }

      .control-panel h3 {
        margin: 0 0 12px 0;
        font-size: 14px;
        font-weight: 600;
        color: #111;
        border-bottom: 1px solid #eee;
        padding-bottom: 8px;
      }

      .control-row {
        display: flex;
        flex-direction: column;
        margin-bottom: 12px;
      }

      .control-row:last-child {
        margin-bottom: 0;
      }

      .control-label-wrapper {
        display: flex;
        justify-content: space-between;
        align-items: center;
        margin-bottom: 4px;
      }

      .control-label {
        font-size: 11px;
        font-weight: 500;
        color: #555;
      }

      .control-value {
        font-size: 11px;
        font-weight: bold;
        color: #000;
      }

      .control-row input[type="range"] {
        width: 100%;
        margin: 4px 0;
      }
    </style>
  </head>

  <body>
    <div id="map"></div>

    <div class="control-panel">
      <h3>LOD Parameters</h3>

      <div class="control-row">
        <div class="control-label-wrapper">
          <label class="control-label" for="max-zoom-levels-slider"
            >Max Zoom Levels On Screen</label
          >
          <span id="max-zoom-levels-value" class="control-value">9.0</span>
        </div>
        <input type="range" id="max-zoom-levels-slider" min="1" max="11" step="1.0" value="9.0" />
      </div>

      <div class="control-row">
        <div class="control-label-wrapper">
          <label class="control-label" for="tile-count-ratio-slider"
            >Tile Count Max/Min Ratio</label
          >
          <span id="tile-count-ratio-value" class="control-value">3.0</span>
        </div>
        <input type="range" id="tile-count-ratio-slider" min="1" max="10" step="0.1" value="3.0" />
      </div>
    </div>

    <script>
      maptilersdk.config.apiKey = "YOUR_MAPTILER_API_KEY_HERE";

      const map = new maptilersdk.Map({
        container: "map",
        zoom: 12,
        pitch: 77,
        center: [0, 0],
        style: {
          version: 8,
          sources: {
            numbers: {
              type: "raster",
              url: "https://demotiles.maplibre.org/debug-tiles/number/tiles.json",
              tileSize: 256,
              maxzoom: 19,
            },
          },
          layers: [
            {
              id: "numbers",
              type: "raster",
              source: "numbers",
            },
          ],
        },
        maxZoom: 22,
        maxPitch: 85,
      });

      function setLodParamsFromUi() {
        const maxZoomVal = document.getElementById("max-zoom-levels-slider").value;
        const tileCountVal = document.getElementById("tile-count-ratio-slider").value;

        // Update UI labels
        document.getElementById("max-zoom-levels-value").textContent = maxZoomVal;
        document.getElementById("tile-count-ratio-value").textContent = tileCountVal;

        // Apply parameters to our debug tiles source 'numbers'
        try {
          map.setSourceTileLodParams(maxZoomVal, tileCountVal);
        } catch (e) {
          console.warn("setSourceTileLodParams failed for source 'numbers':", e);
        }
      }

      map.on("load", () => {
        document
          .getElementById("max-zoom-levels-slider")
          .addEventListener("input", setLodParamsFromUi);
        document
          .getElementById("tile-count-ratio-slider")
          .addEventListener("input", setLodParamsFromUi);
        // Initial parameters setup
        setLodParamsFromUi();
      });
    </script>
  </body>
</html>
```

## Learn more

### How it works

When the map is pitched and the horizon is visible, tiles in the distance appear smaller and closer to the horizon. By default, the rendering engine balances performance and quality by loading fewer, lower-resolution tiles in the distance.

With `setSourceTileLodParams()`, you can override this behavior:

-   **Max Zoom Levels On Screen** (`maxZoomLevelsOnScreen`): Limits the variety of distinct zoom levels rendered simultaneously. Lowering this value reduces the “depth” of zoom levels rendered, which can save network requests and memory, but distant areas will look less detailed.
-   **Tile Count Max/Min Ratio** (`tileCountMaxMinRatio`): Controls the budget of tiles. A higher ratio allows more tiles to be loaded across different zoom levels, while a lower ratio restricts them, forcing distant tiles to stay at a lower zoom level to save resources.

## Related examples

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

Tutorials

Create and display 3D terrain maps on web pages.

### [Minimap control](https://docs.maptiler.com/sdk-js/examples/control-minimap/)

Tutorials

Display an overview minimap control to aid map navigation.

### [Add a raster tile source](https://docs.maptiler.com/sdk-js/examples/map-tiles/)

Examples

Add a third-party raster source to the map.

### [Raster layer](https://docs.maptiler.com/sdk-js/examples/raster-layer/)

Tutorials

Add a raster image overlay to the map.
