Mapbox Newsletter WEEKLY TIPSの解説 -「カスタムスタイルレイヤーを追加」
はじめに
この記事は、先日配信されたMapbox NewsletterのWEEKLY TIPSで紹介されていた「カスタムスタイルレイヤーを追加」についての解説です。このサンプルではCustomLayerInterfaceの使い方を紹介しています。また、Newsletterの購読はこちらからお申し込みいただけます。
以下が本サンプルのデモです。
CustomLayerInterfaceとは
地理空間データを表示する際、通常はMapbox GL JSで定義されているレイヤー(Symbolレイヤー、Circleレイヤー等)を使用します。それに加えて、Mapbox GL JSではユーザーが自由にレイヤーを定義することができます。その際に使用するのがCustomLayerInterfaceです。
Mapbox GL JSはWeb GLを使用して地図を描画しますが、CustomLayerInterfaceではWeb GLを直接操作することで様々なレイヤーを表現します。たとえばdeck.glはMapbox GL JSをサポートする様々なレイヤーがありますが、CustomLayerInterfaceを用いて実現しています。
自作レイヤーを作る際にはCustomLayerInterfaceを継承した(または、同じメソッドを持つ)クラスを定義します。onAddメソッドではレイヤー作成時にまとめて行うWeb GLの初期化処理を記述し、renderメソッドでは描画の際に実行するWeb GLのコードを記述します。
コードを確認
まずExamplesのコードを見に行きましょう。
日本語サイト
英語サイト
基本的に同じコードですが、英語版はスタイルがMapbox Light v11にアップグレードされているのでこちらを使用します。Mapbox Light v10ではデフォルトのプロジェクションがWebメルカトルであるのに対し、Mapbox Light v11ではGlobe(3D表示された地球)なので、印象がかなり異なります。また、英語版はMapbox GL JS v3が使用されています。
HTML
まずHTMLを見ていきましょう。
以下は地図を表示するエレメントです。
<div id="map"></div>
Mapの作成
JavaScriptのコードを見ていきます。以下のコードはいつも通り、Mapオブジェクトを作成しています。containerで地図を表示するHTMLエレメントのidを指定します。
const map = new mapboxgl.Map({
container: 'map',
zoom: 3,
center: [7.5, 58],
// Choose from Mapbox's core styles, or make your own style with Mapbox Studio
style: 'mapbox://styles/mapbox/light-v11',
antialias: true, // create the gl context with MSAA antialiasing, so custom layers are antialiased
projection: 'mercator'
});
レイヤーの作成
途中を飛ばして最後の部分を確認します。highlightLayerは自作レイヤーの定義を含むレイヤーです。Symbolレイヤー等と同じ様にMap#addLayerメソッドで作成します。第2引数にbuildingレイヤーを指定しているので、自作レイヤーはこのレイヤーの下に描画されます。
map.on('load', () => {
map.addLayer(highlightLayer, 'building');
});
拡大してみると以下のようにビルの下に自作レイヤーが描画されているのがわかります。

自作レイヤーの定義
それではhighlightLayerの中身を見ていきましょう。
idとtypeを指定するのは他のレイヤーと同じです。CustomLayerInterfaceのときはcustomを指定します。
id: 'highlight',
type: 'custom',
onAdd
以下のonAddの中身を見ていきます。
onAdd: function (map, gl) {
...
},
まず、頂点シェーダとフラグメントシェーダのソースを作成しています。頂点シェーダのu_matrixはカメラ行列が入っています。また、フラグメントシェーダは赤色を指定しています。
// create GLSL source for vertex shader
const vertexSource = `
uniform mat4 u_matrix;
attribute vec2 a_pos;
void main() {
gl_Position = u_matrix * vec4(a_pos, 0.0, 1.0);
}`;
// create GLSL source for fragment shader
const fragmentSource = `
void main() {
gl_FragColor = vec4(1.0, 0.0, 0.0, 0.5);
}`;
各シェーダからプログラムを作成しています。
// create a vertex shader
const vertexShader = gl.createShader(gl.VERTEX_SHADER);
gl.shaderSource(vertexShader, vertexSource);
gl.compileShader(vertexShader);
// create a fragment shader
const fragmentShader = gl.createShader(gl.FRAGMENT_SHADER);
gl.shaderSource(fragmentShader, fragmentSource);
gl.compileShader(fragmentShader);
// link the two shaders into a WebGL program
this.program = gl.createProgram();
gl.attachShader(this.program, vertexShader);
gl.attachShader(this.program, fragmentShader);
gl.linkProgram(this.program);
頂点シェーダで使用しているa_posという属性を取得し、this.aPosに保存します。後ほどrenderの内部で使用します。
this.aPos = gl.getAttribLocation(this.program, 'a_pos');
MercatorCoordinate.fromLngLatで軽度・緯度からWeb GL上での頂点座標を計算しています。
// define vertices of the triangle to be rendered in the custom style layer
const helsinki = mapboxgl.MercatorCoordinate.fromLngLat({
lng: 25.004,
lat: 60.239
});
const berlin = mapboxgl.MercatorCoordinate.fromLngLat({
lng: 13.403,
lat: 52.562
});
const kyiv = mapboxgl.MercatorCoordinate.fromLngLat({
lng: 30.498,
lat: 50.541
});
バッファを作成し、先程の頂点をコピーしています。
// create and initialize a WebGLBuffer to store vertex and color data
this.buffer = gl.createBuffer();
gl.bindBuffer(gl.ARRAY_BUFFER, this.buffer);
gl.bufferData(
gl.ARRAY_BUFFER,
new Float32Array([
helsinki.x,
helsinki.y,
berlin.x,
berlin.y,
kyiv.x,
kyiv.y
]),
gl.STATIC_DRAW
);
render
以下のrenderの中身を見ていきます。
render: function (gl, matrix) {
...
}
実行するプログラムとして先ほど作成したプログラムを指定します。
gl.useProgram(this.program);
ユニフォームu_matrixにrenderの引数のmatrixを設定します。これは頂点シェーダで使用されるカメラ行列です。
gl.uniformMatrix4fv(
gl.getUniformLocation(this.program, 'u_matrix'),
false,
matrix
);
頂点座標を格納したバッファーの設定です。
gl.bindBuffer(gl.ARRAY_BUFFER, this.buffer);
gl.enableVertexAttribArray(this.aPos);
gl.vertexAttribPointer(this.aPos, 2, gl.FLOAT, false, 0, 0);
ブレンドの設定です。この設定をすることで、自作レイヤーに隠れている部分が自然な雰囲気で表現されます。
gl.enable(gl.BLEND);
gl.blendFunc(gl.SRC_ALPHA, gl.ONE_MINUS_SRC_ALPHA);
最後に描画を指示します。
gl.drawArrays(gl.TRIANGLE_STRIP, 0, 3);
まとめ
CustomLayerInterfaceを使用するとWeb GLを直接制御でき、任意のレイヤーを作成できることがわかりました。
Discussion