コンテンツにスキップ

3D Tiles errors and device support

Checking whether the device supports 3D Tiles, reading what a tileset uses, and receiving load errors and the loss of rendering (PLATEAU Kumagaya trees).

handle_3dtiles_errors_and_device.html
<!DOCTYPE html>
<html lang="en">
<head>
  <meta charset="UTF-8">
  <meta name="viewport" content="width=device-width, initial-scale=1">
  <title>3D Tiles errors and device support</title>
  <meta property="og:description" content="Checking whether the device supports 3D Tiles, reading what a tileset uses, and receiving load errors and the loss of rendering (PLATEAU Kumagaya trees)." />
  <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: 340px;
        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 button {
        display: block;
        margin: 0 0 4px;
    }
    #error_log div {
        border-top: 1px solid #ddd;
        word-break: break-all;
    }
  </style>
</head>
<body>
<div id="mapray-container"></div>
<div class="map-overlay top">
    <div class="map-overlay-inner">
        <fieldset>
            <label>This device</label>
            <span id="device_info">-</span>
        </fieldset>
        <fieldset>
            <label>Load</label>
            <button id="load_trees" type="button">PLATEAU trees in Kumagaya</button>
            <button id="load_missing" type="button">A tileset that does not exist</button>
            <button id="load_unsupported" type="button">A tileset that requires an unsupported extension</button>
            <button id="lose_context" type="button">Simulate losing the GPU</button>
        </fieldset>
        <fieldset>
            <label>Tileset</label>
            <span id="tileset_info">-</span>
        </fieldset>
        <fieldset>
            <label>Errors</label>
            <div id="error_log">none</div>
        </fieldset>
    </div>
</div>
</body>
</html>
<script>
  const TREES_URL = "https://assets.cms.plateau.reearth.io/assets/71/586e8c-1be5-4eb9-81c5-2144beca3048/11202_kumagaya-shi_pref_2025_citygml_1_op_veg_SolitaryVegetationObject_3dtiles_lod2/tileset.json";
  const MISSING_URL = "https://assets.cms.plateau.reearth.io/assets/00/does-not-exist/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).
  const PLATEAU_CREDIT = "出典:「3D都市モデル(Project PLATEAU)熊谷市(2025年度)」(国土交通省)";

  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()
    } );
  // South-west of the trees around Kumagaya Station in Saitama JP
  uiviewer.setCameraPosition({
    longitude: 139.3850,
    latitude: 36.1400,
    height: 250
  });
  uiviewer.setLookAtPosition({
    longitude: 139.3905,
    latitude: 36.1455,
    height: 30
  });

  // The SDK only reports what it found. Whether to load 3D Tiles on a limited device is the application's decision.
  const capabilities = uiviewer.viewer.device_capabilities;
  const support = capabilities.features.tiles3d;
  document.getElementById( 'device_info' ).textContent =
      "tier " + capabilities.tier + " (confidence " + capabilities.confidence + "), " +
      "3D Tiles " + support.status + ( support.reasons.length > 0 ? " because of " + support.reasons.join( ", " ) : "" );

  const error_log = document.getElementById( 'error_log' );
  let error_count = 0;
  function logError( text ) {
      if ( error_count++ === 0 ) {
          error_log.textContent = "";
      }
      const line = document.createElement( "div" );
      line.textContent = text;
      error_log.prepend( line );
  }

  // The viewer stops drawing when the GPU is lost and does not recover, so the page has to be reloaded.
  uiviewer.viewer.addEventListenerForRenderingLost( info => {
      logError( "rendering lost: " + info.reason + " (" + info.detail + "). Reload the page." );
  } );

  let scene = null;

  function loadTileset( url, options = {} ) {
      if ( support.status !== mapray.DeviceProfile.FeatureStatus.SUPPORTED &&
           !window.confirm( "3D Tiles may not work on this device (" + support.reasons.join( ", " ) + "). Load them anyway?" ) ) {
          return;
      }
      if ( scene ) {
          uiviewer.viewer.b3d_collection.removeScene( scene );
      }
      document.getElementById( 'tileset_info' ).textContent = "loading...";
      scene = uiviewer.viewer.b3d_collection.createTiles3DScene( new mapray.StandardTiles3DProvider( { url }, { attributions: [ PLATEAU_CREDIT ] } ), {
          show_attributions: true,
          ...options
      } );
      const this_scene = scene;
      let stopped = false;
      // Each problem is reported once with a fixed code; fatal ones stop the scene.
      scene.addEventListenerForLoadError( info => {
          logError( info.code + ( info.fatal ? " (fatal)" : "" ) + ": " + info.message + ( info.feature ? " [" + info.feature + "]" : "" ) );
          if ( info.fatal && this_scene === scene && !scene.tileset_info ) {
              stopped = true;
              document.getElementById( 'tileset_info' ).textContent = "not loaded";
          }
      } );
      // tileset_info stays undefined until tileset.json has been read.
      const showInfo = () => {
          if ( this_scene !== scene || stopped ) {
              return;
          }
          const info = scene.tileset_info;
          if ( !info ) {
              setTimeout( showInfo, 250 );
              return;
          }
          document.getElementById( 'tileset_info' ).textContent =
              "3D Tiles " + info.version +
              ", extensions used: " + ( info.extensions_used.join( ", " ) || "none" ) +
              ", required: " + ( info.extensions_required.join( ", " ) || "none" ) +
              ", unsupported: " + info.unsupported.length;
      };
      showInfo();
  }

  // A tileset made on the spot that requires an extension this SDK does not read
  function unsupportedTilesetUrl() {
      const json = {
          asset: { version: "1.1" },
          extensionsUsed: [ "3DTILES_bounding_volume_S2" ],
          extensionsRequired: [ "3DTILES_bounding_volume_S2" ],
          geometricError: 100,
          root: {
              boundingVolume: { region: [ 2.4327, 0.6306, 2.4330, 0.6309, 0, 100 ] },
              geometricError: 0,
              refine: "REPLACE"
          }
      };
      return URL.createObjectURL( new Blob( [ JSON.stringify( json ) ], { type: "application/json" } ) );
  }

  document.getElementById( 'load_trees' ).addEventListener( 'click', () => {
      loadTileset( TREES_URL, { vertical_offset: -41 } );   // ellipsoidal height to elevation (geoid undulation around Kumagaya)
  } );
  document.getElementById( 'load_missing' ).addEventListener( 'click', () => loadTileset( MISSING_URL ) );
  document.getElementById( 'load_unsupported' ).addEventListener( 'click', () => loadTileset( unsupportedTilesetUrl() ) );
  document.getElementById( 'lose_context' ).addEventListener( 'click', () => {
      // Only for this demonstration: WEBGL_lose_context makes the browser drop the WebGL context of the viewer's canvas.
      uiviewer.viewer.canvas_element.getContext( "webgl2" ).getExtension( "WEBGL_lose_context" )?.loseContext();
  } );

  loadTileset( TREES_URL, { vertical_offset: -41 } );
</script>

Info

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