🏢

Mapbox Newsletter WEEKLY TIPSの解説 -「建物を3Dで表示」

2023/08/17に公開

はじめに

この記事は、先日配信されたMapbox NewsletterのWEEKLY TIPSで紹介されていた「建物を3Dで表示」についての解説です。このサンプルではfill-extrusionレイヤーを用いた建物の3D表現方法を例示しています。また、Newsletterの購読はこちらからお申し込みいただけます。

コードを確認

まずExamplesのコードを見に行きましょう。

日本語サイト

英語サイト

基本的に同じコードですが、英語版はスタイルがMapbox Light v11にアップグレードされているのでこちらを使用します。Mapbox Light v10ではデフォルトのプロジェクションがWebメルカトルであるのに対し、Mapbox Light v11ではGlobe(3D表示された地球)なので、印象がかなり異なります。

HTML/CSS

まずHTMLを見ていきましょう。

以下は地図を表示するエレメントです。

<div id="map"></div>

Mapの作成

次にJavaScriptのコードを見ていきます。以下のコードはいつも通り、Mapオブジェクトを作成しています。containerで地図を表示するHTMLエレメントのidを指定します。

const map = new mapboxgl.Map({
  // Choose from Mapbox's core styles, or make your own style with Mapbox Studio
  style: 'mapbox://styles/mapbox/light-v11',
  center: [-74.0066, 40.7135],
  zoom: 15.5,
  pitch: 45,
  bearing: -17.6,
  container: 'map',
  antialias: true
});

fill-extrusionレイヤーの追加

レイヤーの追加はloadイベントのコールバックの中で行います。

map.on('style.load', () => {/*ここ*/});

このサンプルではloadではなくstyle.loadイベントを使用しています。loadイベントはスタイルが読み込まれたあと、次の描画のタイミングで発火するのに対し、style.loadイベントはスタイルが読み込まれた直後に発火するという違いがあります。つまり、loadを使用すると地図を表示したあと一瞬の間が空いてaddLayerが実行されるため、レイヤーが追加される瞬間が見えることがあります。それに対しstyle.loadは最初からaddLayerされているように見えます。

レイヤー追加場所の探索

まず最初にレイヤー追加場所を探索しています。map.getStyle().layersでLight v11のすべてのレイヤーを取得します。このとき取得されるレイヤーの配列はスタイルの一番下のレイヤーから順番に入っています。

そしてlayers.findでシンボルレイヤーかつtext-fieldを持っているレイヤーを探しています。要するに、何かしらテキストラベルを表示しているレイヤーの中で一番最初に見つかるもの(一番下にあるもの)を探しています。具体的にはroad-label-simpleというレイヤーが見つかります。

// Insert the layer beneath any symbol layer.
const layers = map.getStyle().layers;
const labelLayerId = layers.find(
  (layer) => layer.type === 'symbol' && layer.layout['text-field']
).id;

レイヤー作成前半戦

そしてaddLayerfill-extrusionレイヤーを追加します。建物の高さ情報はMapbox Streets v8タイルセットのbuildingレイヤーheightmin_heighというプロパティとして格納されています。さらにextrudeというプロパティもあります。このプロパティは高さ情報が有効であるかどうかを表しています。

以下のようにStudioでBuidlingレイヤーのプロパティを確認するとわかりやすいと思います。

studio

また、以下のようにLight v11ではStreets v8タイルセットをcompositeというソース名で使用しています。

light-v11

ということで、addLayerの前半部分は「compositeソースのbuildingレイヤーを使用してfill-extrusionレイヤーを作成。ただし、extrudeプロパティがtrueのものに限る」ということを指示しています。

map.addLayer(
  {
    'id': 'add-3d-buildings',
    'source': 'composite',
    'source-layer': 'building',
    'filter': ['==', 'extrude', 'true'],
    'type': 'fill-extrusion',
    'minzoom': 15,

レイヤー作成後半戦

続いて後半部分を見ていきます。fill-extrusion-colorは建物の色、fill-extrusion-opacityは建物の不透明度を指定します。fill-extrusion-heightは建物の高さ、fill-extrusion-baseは建物の描画を始める高さを指定します。

少しわかりにくいですが、fill-extrusion-base20fill-extrusion-height50の場合、「地上から20mから50mの部分を建物として描画する」という指示になります。つまり、高さ30mの建物が20m空中に浮いている状態です。

また、このサンプルではfill-extrusion-basefill-extrusion-heightに少し複雑なExpressionsを使用しています。interpolateは補間命令で、ここではzoomに応じてlinear(線形)に補間するようにしています。さらにズーム15のとき0、ズーム15.05のときにheightまたはmin_heightの値を使用するように指定しています。線形補間されるのでズームが[15,15.05]の区間では[0,height]または[0,min_height]の値が使用されます。英語のコメントにもありますが、線形補間することでズームに応じて建物がいい感じに地面から立ち上がってくるような視覚効果があります。

    'paint': {
      'fill-extrusion-color': '#aaa',
  
      // Use an 'interpolate' expression to
      // add a smooth transition effect to
      // the buildings as the user zooms in.
      'fill-extrusion-height': [
        'interpolate',
        ['linear'],
        ['zoom'],
        15,
        0,
        15.05,
        ['get', 'height']
      ],
      'fill-extrusion-base': [
        'interpolate',
        ['linear'],
        ['zoom'],
        15,
        0,
        15.05,
        ['get', 'min_height']
      ],
      'fill-extrusion-opacity': 0.6
    }
  },

最後にaddLayerの第二引数に先程探したレイヤーのIDを指定します。これにより、labelLayerIdの下にfill-extrusionレイヤーが追加されます。

  labelLayerId
);

まとめ

サンプルを通じてfill-extrusionの使い方がご理解いただけたかと思います。また、extrusionについては以下の記事も合わせてご参照ください。

おまけ

先日、Mapbox Standard StyleとMapbox GL JS v3.0.0-beta.1が公開されました。3D Tilesがサポートされているため、以下のようにextrusionでは難しかった東京タワーもきれいに表現されています。

これ以外にもGL JS v3では様々な機能がサポートされているので、ぜひ一度お試しください。

GitHubで編集を提案
マップボックス・ジャパン合同会社

Discussion