コンテンツにスキップ

Control 3D Tiles quality and memory

Tuning the level of detail, the drawing distance, the texture size and compression and the cache limit of OGC 3D Tiles (PLATEAU Ikeda), and showing the GPU memory they use.

control_3dtiles_quality.html
<!DOCTYPE html>
<html lang="en">
<head>
  <meta charset="UTF-8">
  <meta name="viewport" content="width=device-width, initial-scale=1">
  <title>Control 3D Tiles quality and memory</title>
  <meta property="og:description" content="Tuning the level of detail, the drawing distance, the texture size and compression and the cache limit of OGC 3D Tiles (PLATEAU Ikeda), and showing the GPU memory they use." />
  <script src="./v0.10.7/mapray.min.js"></script>
  <script src="./v0.10.7/maprayui.min.js"></script>
  <link rel="stylesheet" href="./v0.10.7/mapray.css">
  <style>
    body {margin: 0; padding: 0;}
    html, body, div#mapray-container { height: 100%; }
    .map-overlay {
        font: 12px/20px "Robot", sans-serif;
        position: absolute;
        width: 300px;
        top: 0;
        left: 0;
        padding: 10px;
    }
    .map-overlay .map-overlay-inner {
        background-color: #fff;
        color: #000;
        box-shadow: 0 1px 2px rgba(0,0,0,0.1);
        border-radius: 3px;
        padding: 10px;
        margin-bottom: 10px;
    }
    .map-overlay-inner fieldset {
        border: none;
        padding: 0;
        margin: 0 0 10px;
    }
    .map-overlay-inner fieldset:last-child {
        margin: 0;
    }
    .map-overlay-inner label {
        display: block;
        font-weight: bold;
        margin: 0 0 5px;
    }
    .map-overlay-inner input[type=range] {
        width: 100%;
    }
  </style>
</head>
<body>
<div id="mapray-container"></div>
<div class="map-overlay top">
    <div class="map-overlay-inner">
        <fieldset>
            <label>Screen space error threshold: <span id="sse_value"></span></label>
            <input id="sse_slider" type="range" min="1" max="128" step="1" value="19">
        </fieldset>
        <fieldset>
            <label>Maximum distance: <span id="distance_value"></span></label>
            <input id="distance_slider" type="range" min="1000" max="10000" step="500" value="10000">
        </fieldset>
        <fieldset>
            <label>Cache limit</label>
            <select id="cache_select">
                <option value="Infinity" selected>Unlimited</option>
                <option value="536870912">512 MiB</option>
                <option value="134217728">128 MiB</option>
            </select>
        </fieldset>
        <fieldset>
            <label>Applied when tiles are loaded</label>
            Max texture size
            <select id="texture_size_select">
                <option value="0" selected>Unlimited</option>
                <option value="1024">1024 px</option>
                <option value="256">256 px</option>
            </select><br>
            <input id="compression_check" type="checkbox" checked> Texture compression<br>
            <button id="reload_button" type="button">Reload the tiles</button>
        </fieldset>
        <fieldset>
            <label>GPU memory of loaded tiles</label>
            <span id="gpu_info">-</span>
        </fieldset>
        <fieldset>
            <label>Textures in the data</label>
            <span id="texture_info">-</span>
        </fieldset>
    </div>
</div>
</body>
</html>
<script>
  const MIB = 1024 * 1024;

  const cloudApi = new mapray.cloud.CloudApiV2({
        tokenType: mapray.cloud.CloudApi.TokenType.ACCESS_TOKEN,
        token: "<YOUR_MAPRAY_ACCESS_TOKEN>"
    });
    // The StandardUIView in the ui package includes mouse-based camera manipulation.
    const uiviewer = new maprayui.StandardUIViewer( "mapray-container", {
        dem_provider: new mapray.StandardDemProvider( cloudApi.getDefaultDemAsResource() ),
        atmosphere: new mapray.Atmosphere()
    } );
  // West of Ikeda Station in Osaka JP
  uiviewer.setCameraPosition({
    longitude: 135.4200,
    latitude: 34.8180,
    height: 500
  });
  // Ikeda Station
  uiviewer.setLookAtPosition({
    longitude: 135.4282,
    latitude: 34.8215,
    height: 40
  });

  const sse_slider = document.getElementById( 'sse_slider' );
  const distance_slider = document.getElementById( 'distance_slider' );
  const cache_select = document.getElementById( 'cache_select' );
  const texture_size_select = document.getElementById( 'texture_size_select' );
  const compression_check = document.getElementById( 'compression_check' );

  let scene = null;

  // Texture size and compression take effect only for tiles loaded afterwards, so the scene is created again to apply them.
  function createScene() {
      if ( scene ) {
          uiviewer.viewer.b3d_collection.removeScene( scene );
      }
      // PLATEAU Ikeda-shi buildings, LOD2 with textures (OGC 3D Tiles 1.1)
      const provider = new mapray.StandardTiles3DProvider({
        url: "https://assets.cms.plateau.reearth.io/assets/fc/727d0a-d7f1-4b37-b0a5-dbb5caddcb98/27204_ikeda-shi_city_2025_citygml_1_op_bldg_3dtiles_lod2/tileset.json"
      }, {
        // PLATEAU tiles carry no copyright string, so credit the dataset as the PLATEAU terms of use require (Public Data License 1.0, compatible with CC BY 4.0).
        attributions: [ "出典:「3D都市モデル(Project PLATEAU)池田市(2025年度)」(国土交通省)" ]
      });
      scene = uiviewer.viewer.b3d_collection.createTiles3DScene( provider, {
          show_attributions: true,
          vertical_offset: -37,   // ellipsoidal height to elevation (geoid undulation around Ikeda)
          texture_compression: compression_check.checked
      } );
      scene.setMaxTextureSize( Number( texture_size_select.value ) );
      applyRuntimeSettings();
  }

  // These settings take effect immediately on the tiles already loaded.
  function applyRuntimeSettings() {
      // A larger threshold stops the subdivision at coarser tiles.
      scene.setSseThreshold( Number( sse_slider.value ) );
      const max_distance = Number( distance_slider.value );
      scene.setDistanceRange( 0, max_distance >= Number( distance_slider.max ) ? Number.POSITIVE_INFINITY : max_distance );
      // Over the limit, tiles that have not been drawn for a while are released and fetched again when needed.
      scene.setMaxCacheBytes( Number( cache_select.value ) );

      document.getElementById( 'sse_value' ).textContent = sse_slider.value;
      document.getElementById( 'distance_value' ).textContent =
          max_distance >= Number( distance_slider.max ) ? "unlimited" : ( max_distance / 1000 ) + " km";
  }

  function showStatistics() {
      // getGpuResourceInfo() walks the tile tree, so it is polled instead of being called every frame.
      const gpu = scene.getGpuResourceInfo();
      document.getElementById( 'gpu_info' ).textContent =
          "textures " + ( gpu.texture_bytes / MIB ).toFixed( 1 ) + " MiB (" + gpu.texture_count + "), " +
          "buffers " + ( gpu.buffer_bytes / MIB ).toFixed( 1 ) + " MiB, meshes " + gpu.mesh_count +
          ", requesting " + scene.getNumRequested();
      // Only the tiles loaded so far are known; the sizes are those before downscaling.
      const textures = scene.getTextureSizeInfo();
      const image_count = textures.sizes.reduce( ( sum, entry ) => sum + entry.count, 0 );
      document.getElementById( 'texture_info' ).textContent = image_count === 0 ? "none loaded yet" :
          image_count + " images, longest side " + textures.longest_side + " px, downscaled " + textures.downscaled_count;
  }

  sse_slider.addEventListener( 'input', applyRuntimeSettings );
  distance_slider.addEventListener( 'input', applyRuntimeSettings );
  cache_select.addEventListener( 'change', applyRuntimeSettings );
  document.getElementById( 'reload_button' ).addEventListener( 'click', createScene );

  createScene();
  setInterval( showStatistics, 1000 );
</script>

Info

このサンプルコードは、<YOUR_MAPRAY_ACCESS_TOKEN>を、あなたのMapray CloudのアカウントのOrganization Tokenに置き換えるまで、期待通りに動作しません。