How to load offline .terrain tiles from cesium-terrain-builder in CesiumJS?

Hello!

I used cesium-terrain-builder to generate .terrain tiles from a .geotiff. Now I want to use them in offline CesiumJS, but I’m not sure how to properly connect them.

I’m running a local Python (Flask) server to serve the files:

from flask import Flask, send_from_directory
import os

app = Flask(name, static_folder=“.”, static_url_path=“”)

@app.route(“/”)
def root():
return app.send_static_file(“index.html”)

@app.route(“/tiles/<int:z>/<int:x>/<int:y>.jpeg”)
def tiles(z, x, y):
return send_from_directory(“tiles/{}/{}”.format(z, x), f"{y}.jpeg")

if name == “main”:
app.run(host=“0.0.0.0”, port=8003, debug=False, use_reloader=False)

The question is: how exactly do I load these .terrain tiles in CesiumJS offline?Most tutorials I find are for online services (Cesium Ion, AGI), but I can’t find a clear example for serving local terrain tiles.

I guess I should use CesiumTerrainProvider or something similar, but I don’t know the correct way to point it to my tiles so that CesiumJS can render them.

Any help or example code would be greatly appreciated

What does your index.html serve?

This is my offline CesiumJS map. I’m just learning it, sorry if there are any silly mistakes.

<!DOCTYPE html>
<html lang="ru">
<head>
  <meta charset="UTF-8" />
  <title>Cesium Offline</title>
  <script src="Cesium/Cesium.js"></script>
  <link href="Cesium/Widgets/widgets.css" rel="stylesheet" />
  <style>
    html, body, #cesiumContainer { width:100%; height:100%; margin:0; padding:0; overflow:hidden; }
  </style>
</head>
<body>
  <div id="cesiumContainer"></div>
<script>
    // Координаты запуска карты
    const ALMATY_LON = 76.980671, ALMATY_LAT = 43.353934;
    const ORIGIN = window.location.origin;
    const TILE_ROOT = ORIGIN + "/tiles"; // http://localhost:8003/tiles
    function makeProvider(opts) {
      const yToken = opts.useTMS ? "{reverseY}" : "{y}";
      const url = `${TILE_ROOT}/{z}/{x}/${yToken}.jpeg`;
      return new Cesium.UrlTemplateImageryProvider({
        url,
        tilingScheme: opts.useGeographic
          ? new Cesium.GeographicTilingScheme()
          : new Cesium.WebMercatorTilingScheme(),
        minimumLevel: opts.minZ,
        maximumLevel: opts.maxZ,
        tileWidth: opts.tileSize,
        tileHeight: opts.tileSize
      });
    }
    function flyKazakhstan(heightMeters) {
      viewer.camera.flyTo({
        destination: Cesium.Cartesian3.fromDegrees(ALMATY_LON, ALMATY_LAT, heightMeters ?? 1000) // высота камеры
      });
    }
    // VIEWER - основная часть Cesium (карта, сцена, камера и т.д.)
    const viewer = new Cesium.Viewer("cesiumContainer", {
      baseLayerPicker: false,
      geocoder: false,
      timeline: false,
      animation: false,
      requestRenderMode: false,
      navigationHelpButton: false,   
      fullscreenButton: false       
    });
    viewer.scene.globe.baseColor = Cesium.Color.fromCssColorString('#00151C'); // цвет не програженных областей
    viewer._cesiumWidget._creditContainer.style.display = "none"; // скрыть логотип Cesium
    // Физуальная кастомизация карты
    //viewer.scene.globe.enableLighting = true; // освещение по времени суток
    viewer.clock.shouldAnimate = true;  // анимация времени
    //viewer.scene.globe.depthTestAgainstTerrain = true; // скрытие объектов за рельефом
    viewer.scene.skyAtmosphere.show = true; //Атмосфера и небо
    viewer.scene.skyBox.show = true;  // Атмосфера и небо (по умолчанию true)
    viewer.scene.fog.enabled = true; // Туман
    viewer.scene.fog.density = 0.001;  // Туман (плотность (меньше — прозрачнее))
    viewer.scene.fog.minimumBrightness = 0.03;  // Туман (минимальная яркость для предотвращения полного затемнения)
    viewer.scene.sun = new Cesium.Sun(); // Солнце
    viewer.scene.moon = new Cesium.Moon(); // Луна
    viewer.scene.highDynamicRange = true; // HDR
    //viewer.scene.postProcessStages.bloom.enabled = true; // Свечение (bloom)
    //viewer.scene.postProcessStages.bloom.uniforms.bloomIntensity = 1.0;  // Интенсивность свечения
    // Загружаем границы Казахстана из локального geojson
    //Cesium.GeoJsonDataSource.load("/borders/kazakhstan.geojson", { // Границы Казхастана
    Cesium.GeoJsonDataSource.load("/borders/world.json", { // Границы мира во круг Казахстана
      stroke: Cesium.Color.YELLOW,   // цвет линии границы
      fill: Cesium.Color.fromAlpha(Cesium.Color.YELLOW, 0.05), // заливка (прозрачная)
      strokeWidth: 0,
      clampToGround: true
    }).then(function(dataSource) {
      viewer.dataSources.add(dataSource);
    });
    const prov = makeProvider({ useTMS: false, useGeographic: false, minZ: 1, maxZ: 17, tileSize: 256 });
    viewer.imageryLayers.removeAll();
    viewer.imageryLayers.addImageryProvider(prov);
    // Перемещения по прописанным координатам (Алматы)
    flyKazakhstan(1000);
    // Подсказки консоли
    console.log("Страница origin:", ORIGIN);
    console.log("Тайлы берём с:", TILE_ROOT);
    console.log("Важно: страница и тайлы должны быть с ОДНОГО origin (тот же хост и порт).");
  </script>
<script src="/models/TANK.js"></script> <!-- 3D модель -->
</body>
</html>
  1. You must have a server that serves generated terrain tiles.
  2. Then you must construct a CesiumTerrainProvider using the URL of your server.
  3. Next, you need to set the terrainProvider property of the viewer you created.

I really appreciate your help. Would you mind sharing a sample code snippet to make things clearer?