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に置き換えるまで、期待通りに動作しません。