コンテンツにスキップ

Style PLATEAU buildings

Styling one PLATEAU Shinjuku tileset with Tiles3DStyle: color steps, a gradient, alpha, show, and regExp.

plateau_style.html
<!DOCTYPE html>
<html lang="en">
<head>
  <meta charset="UTF-8">
  <meta name="viewport" content="width=device-width, initial-scale=1">
  <title>Style PLATEAU buildings</title>
  <meta property="og:description" content="Styling one PLATEAU Shinjuku tileset with Tiles3DStyle: color steps, a gradient, alpha, show, and regExp." />
  <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 select, .map-overlay-inner input[type=range], .map-overlay-inner input[type=text] {
        width: 100%;
        box-sizing: border-box;
    }
    .legend span {
        display: inline-block;
        width: 10px;
        height: 10px;
        margin-right: 5px;
    }
  </style>
</head>
<body>
<div id="mapray-container"></div>
<div class="map-overlay top">
    <div class="map-overlay-inner">
        <fieldset>
            <label>Style</label>
            <select id="mode_select">
                <option value="steps" selected>Color in steps</option>
                <option value="gradient">Color as a gradient</option>
                <option value="alpha">Alpha</option>
                <option value="show">Hide shorter buildings</option>
                <option value="name">Name match</option>
            </select>
        </fieldset>
        <fieldset id="alpha_box" hidden>
            <label>Opacity: <span id="alpha_value"></span></label>
            <input id="alpha_slider" type="range" min="0.05" max="1" step="0.05" value="0.35">
        </fieldset>
        <fieldset id="height_box" hidden>
            <label>Minimum height: <span id="height_value"></span></label>
            <input id="height_slider" type="range" min="0" max="150" step="10" value="60">
        </fieldset>
        <fieldset id="name_box" hidden>
            <label>Name (regular expression)</label>
            <input id="name_input" type="text" value="都庁|ホテル">
            <button id="search_button" type="button">Apply</button>
        </fieldset>
        <fieldset>
            <label>Legend</label>
            <div id="legend" class="legend"></div>
        </fieldset>
    </div>
</div>
</body>
</html>
<script>
  // One Shinjuku tileset. The select switches style expressions, not the data.
  const HEIGHT = "bldg:measuredHeight";
  const NAME = "gml:name";
  // A missing attribute is undefined in 3D Tiles 1.1 and null in 3D Tiles 1.0, and a comparison with either is a type error.
  const present = key => "${" + key + "} !== undefined && ${" + key + "} !== null";

  const STEP_LEGEND = [ [ "#cb181d", "100 m or taller" ], [ "#fcae91", "30 - 100 m" ], [ "#9ecae1", "under 30 m" ], [ "#bbbbbb", "unknown" ] ];
  const GRADIENT_LEGEND = [ [ "#ffffb2", "0 m" ], [ "#de806c", "100 m" ], [ "#bd0026", "200 m or taller" ], [ "#bbbbbb", "unknown" ] ];

  function colorSteps() {
      return { color: { conditions: [
          [ "!(" + present( HEIGHT ) + ")", "color('#bbbbbb')" ],
          [ "${" + HEIGHT + "} >= 100",     "color('#cb181d')" ],
          [ "${" + HEIGHT + "} >= 30",      "color('#fcae91')" ],
          [ "true",                         "color('#9ecae1')" ],
      ] } };
  }

  function colorGradient() {
      // mix( a, b, t ) interpolates the colors, and clamp() keeps t within 0 - 1.
      return { color: { conditions: [
          [ "!(" + present( HEIGHT ) + ")", "color('#bbbbbb')" ],
          [ "true", "mix( color('#ffffb2'), color('#bd0026'), clamp( ${" + HEIGHT + "} / 200, 0, 1 ) )" ],
      ] } };
  }

  function colorAlpha( alpha ) {
      return { color: "color('#7fb5ff', " + alpha + ")" };
  }

  function showTaller( min_height ) {
      const show = min_height > 0 ? present( HEIGHT ) + " && ${" + HEIGHT + "} >= " + min_height : "true";
      return { show: show, color: "color('#d0d0d0')" };
  }

  // The pattern goes into the expression as a string literal, so a backslash and a single quote are escaped.
  function nameStyle( pattern ) {
      const literal = "'" + pattern.replace( /\\/g, "\\\\" ).replace( /'/g, "\\'" ) + "'";
      return { color: { conditions: [
          [ present( NAME ) + " && regExp(" + literal + ").test(${" + NAME + "})", "color('#ff7f00')" ],
          [ "true",                                                                  "color('#dddddd')" ],
      ] } };
  }

  function showLegend( entries ) {
      const legend = document.getElementById( 'legend' );
      legend.innerHTML = "";
      for ( const [ color, text ] of entries ) {
          const line = document.createElement( "div" );
          const swatch = document.createElement( "span" );
          swatch.style.backgroundColor = color;
          line.appendChild( swatch );
          line.appendChild( document.createTextNode( text ) );
          legend.appendChild( line );
      }
      if ( entries.length === 0 ) {
          legend.textContent = "-";
      }
  }

  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() )
    } );
  // South-west of the Nishi-Shinjuku skyscrapers in Tokyo JP
  uiviewer.setCameraPosition({
    longitude: 139.686,
    latitude: 35.684,
    height: 500
  });
  // Tokyo Metropolitan Government Building
  uiviewer.setLookAtPosition({
    longitude: 139.6925,
    latitude: 35.6905,
    height: 100
  });
  // PLATEAU Shinjuku-ku buildings, LOD2 without textures (OGC 3D Tiles 1.0: b3dm content)
  const provider = new mapray.StandardTiles3DProvider({
    url: "https://assets.cms.plateau.reearth.io/assets/2f/d4f9a7-13ad-43cb-b984-033803199eab/13104_shinjuku-ku_pref_2025_citygml_1_op_bldg_3dtiles_13104_shinjuku-ku_lod2_no_texture/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年度)」(国土交通省)" ]
  });
  const scene = uiviewer.viewer.b3d_collection.createTiles3DScene(provider, {
    show_attributions: true,
    vertical_offset: -37   // ellipsoidal height to elevation (geoid undulation around Tokyo)
  });
  // Untextured LOD2 tiles are a flat gray, so REPLACE shows the style color, including its alpha.
  scene.setStyleColorBlendMode( mapray.Tiles3DScene.StyleColorBlendMode.REPLACE );

  const mode_select = document.getElementById( 'mode_select' );
  const alpha_slider = document.getElementById( 'alpha_slider' );
  const height_slider = document.getElementById( 'height_slider' );
  const name_input = document.getElementById( 'name_input' );

  function applyStyle() {
      const mode = mode_select.value;
      document.getElementById( 'alpha_box' ).hidden = mode !== "alpha";
      document.getElementById( 'height_box' ).hidden = mode !== "show";
      document.getElementById( 'name_box' ).hidden = mode !== "name";
      document.getElementById( 'alpha_value' ).textContent = Math.round( alpha_slider.value * 100 ) + " %";
      document.getElementById( 'height_value' ).textContent = height_slider.value + " m";

      if ( mode === "steps" ) {
          scene.setStyle( mapray.Tiles3DStyle.fromJson( colorSteps() ) );
          showLegend( STEP_LEGEND );
          return;
      }
      if ( mode === "gradient" ) {
          scene.setStyle( mapray.Tiles3DStyle.fromJson( colorGradient() ) );
          showLegend( GRADIENT_LEGEND );
          return;
      }
      if ( mode === "alpha" ) {
          scene.setStyle( mapray.Tiles3DStyle.fromJson( colorAlpha( Number( alpha_slider.value ) ) ) );
          showLegend( [] );
          return;
      }
      if ( mode === "show" ) {
          scene.setStyle( mapray.Tiles3DStyle.fromJson( showTaller( Number( height_slider.value ) ) ) );
          showLegend( [] );
          return;
      }
      try {
          // The pattern is compiled when a feature is evaluated, so an invalid one is rejected here.
          const style = mapray.Tiles3DStyle.fromJson( nameStyle( name_input.value ) );
          style.evaluate( { [NAME]: "" } );
          scene.setStyle( style );
          showLegend( [ [ "#ff7f00", "name matches" ], [ "#dddddd", "other buildings" ] ] );
      }
      catch ( error ) {
          showLegend( [ [ "#dddddd", "invalid or unsupported pattern" ] ] );
      }
  }

  mode_select.addEventListener( 'change', applyStyle );
  alpha_slider.addEventListener( 'input', () => { document.getElementById( 'alpha_value' ).textContent = Math.round( alpha_slider.value * 100 ) + " %"; } );
  alpha_slider.addEventListener( 'change', applyStyle );
  height_slider.addEventListener( 'input', () => { document.getElementById( 'height_value' ).textContent = height_slider.value + " m"; } );
  height_slider.addEventListener( 'change', applyStyle );
  document.getElementById( 'search_button' ).addEventListener( 'click', applyStyle );
  name_input.addEventListener( 'keydown', event => { if ( event.key === "Enter" ) applyStyle(); } );
  applyStyle();
</script>

Info

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