Mapbox Newsletter WEEKLY TIPSの解説 -「建物を3Dで表示」
はじめに
この記事は、先日配信された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;
レイヤー作成前半戦
そしてaddLayer
でfill-extrusion
レイヤーを追加します。建物の高さ情報はMapbox Streets v8タイルセットのbuilding
レイヤーにheight
やmin_heigh
というプロパティとして格納されています。さらにextrude
というプロパティもあります。このプロパティは高さ情報が有効であるかどうかを表しています。
以下のようにStudioでBuidlingレイヤーのプロパティを確認するとわかりやすいと思います。
また、以下のようにLight v11ではStreets v8タイルセットをcomposite
というソース名で使用しています。
ということで、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-base
が20
、fill-extrusion-height
が50
の場合、「地上から20mから50mの部分を建物として描画する」という指示になります。つまり、高さ30mの建物が20m空中に浮いている状態です。
また、このサンプルではfill-extrusion-base
とfill-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では様々な機能がサポートされているので、ぜひ一度お試しください。
Discussion