GeoJSONレイヤー表示における各地図サービスの比較
はじめに
この記事はスタイルで比較する地図サービスのスピンオフ企画です。各地図サービス上でGeoJSONレイヤーを表示し、その違いを比較します。
Overlaid v.s. Interleaved
作成したレイヤーをどのようにベースマップにかぶせるか、という観点で一般的にOverlaidとInterleavedの二種類が存在します。deck.glの解説を見るとわかりやすいですが、Overlaidはレイヤーをベースマップの上にかぶせるのでベースマップが隠れます。それに対しInterleavedはベースマップの特定のレイヤーに作成したレイヤーを挟み込むため、例えばビルの下に表示するといったことが可能になります。各サービスでこれらにどのように対応しているかは注目ポイントの一つです。
GeoJSONの準備
GeoJSONの作成にはgeojson.ioというサービスが便利です。ブラウザに表示された地図上で図形を描くと、即座にGeoJSONに変換されます。今回は以下のように日比谷公園周辺で長方形のポリゴンを描画します。
出力されたGeoJSONは以下のとおりです。
{
type: "FeatureCollection",
features: [
{
type: "Feature",
properties: {},
geometry: {
coordinates: [
[
[139.75715452555397, 35.67501088740674],
[139.75715452555397, 35.672275911172164],
[139.7609465361483, 35.672275911172164],
[139.7609465361483, 35.67501088740674],
[139.75715452555397, 35.67501088740674]
]
],
type: "Polygon"
}
}
]
}
Mapbox GL JS
Mapbox GL JSではソースを追加し、そのソースを用いてスタイルを指定するレイヤーを作成します。ソースの追加はMap#addSource
、レイヤーの作成はMap#addLayer
を使用します。ここで重要なのがaddLayer
の第二引数です。リファレンスを見るとbeforeId?
とありますが、ここで指定したレイヤーの下にレイヤーを作成します。つまり、beforeId?
を指定することでInterleavedとして動作します。指定しない場合は一番上のレイヤーにかぶせる状態になるのでOverlaidとなります。今回のレイヤーではbuilding
レイヤーを指定しているのでその下にレイヤーが配置されます。
map.on("load", () => {
map.addSource("geojson_source", {
type: "geojson",
data: {
type: "FeatureCollection",
features: [
{
type: "Feature",
properties: {},
geometry: {
coordinates: [
[
[139.75715452555397, 35.67501088740674],
[139.75715452555397, 35.672275911172164],
[139.7609465361483, 35.672275911172164],
[139.7609465361483, 35.67501088740674],
[139.75715452555397, 35.67501088740674]
]
],
type: "Polygon"
}
}
]
}
});
map.addLayer(
{
id: "polygon_layer",
type: "fill",
source: "geojson_source",
paint: {
"fill-color": "#000088"
}
},
"building"
);
});
結果は以下のとおりです。日比谷公園内を見ると作成したレイヤーによって地面(緑色)や池が隠れています。しかし道路、建物、文字等は表示されています。building
レイヤーの下に配置したため、このように表示されています。Mapbox GL JSではベースマップのスタイルで作成したレイヤーがすべてそのまま扱えます。つまり、原理的にユーザーが作成したレイヤーと区別されないので、レイヤーの配置場所等が柔軟に設定できます。
Google Maps
Google MapsのMaps JavaScript API V3ではData#addGeoJsonでGeoJSONのデータを追加し、Data#setStyleでそのデータのスタイルを設定します。Mapbox GL JSのようにデータからレイヤーを作ってレイヤー毎にスタイルを設定するのではないことに注意が必要です。
ただしsetStyle
は単純なスタイル(Data.StyleOptions
)の他にData.StylingFunction
も使用できます。これはfeature
を引数とする関数を定義するのですが、feature
のプロパティ等に応じてスタイルを変更できるので(サンプル)、「1色しか設定できない」なんて言うことはありません。
ただし、Overlaidとして動作するため、たとえばポリゴンの不透明度が1の場合、ベースマップが完全に隠されてしまいます。
map.data.addGeoJson({
type: "FeatureCollection",
features: [
{
type: "Feature",
properties: {},
geometry: {
coordinates: [
[
[139.75715452555397, 35.67501088740674],
[139.75715452555397, 35.672275911172164],
[139.7609465361483, 35.672275911172164],
[139.7609465361483, 35.67501088740674],
[139.75715452555397, 35.67501088740674]
]
],
type: "Polygon"
}
}
]
});
map.data.setStyle({
fillColor: "#000088",
fillOpacity: 1.0,
});
結果は以下のとおりです。ベースマップの上にGeoJSONのレイヤーが表示されるため、ベースマップが隠れます。
WebGLオーバーレイ(Interleaved)
ベクターマップにおいて、WebGLオーバーレイという機能を使用すると、地図を描画しているWebGLを直接制御することができます。この機能を用いると、自動的にビル、文字列、POIアイコンの下に図形を描画するようです。
ただ、WebGLのコードを直接書くのはちょっとつらいのでdeck.glを使うと良いでしょう。deck.glは様々なレイヤーが定義されたライブラリで、Google Mapsに対してはWebGLオーバーレイとしてそれらのレイヤーが実装されています。したがって、deck.glのレイヤーを使うとWebGLのコーディングを意識することなくWebGLオーバーレイの機能が使用できます。今回は元データがGeoJSONなので、GeoJsonLayerを用いて以下のように記述することができます。
const deckOverlay = new deck.GoogleMapsOverlay({
layers: [
new deck.GeoJsonLayer({
id: "polygon_layer",
data: {
type: "FeatureCollection",
features: [
{
type: "Feature",
properties: {},
geometry: {
coordinates: [
[
[139.75715452555397, 35.67501088740674],
[139.75715452555397, 35.672275911172164],
[139.7609465361483, 35.672275911172164],
[139.7609465361483, 35.67501088740674],
[139.75715452555397, 35.67501088740674]
]
],
type: "Polygon"
}
}
]
},
filled: true,
getFillColor: [0, 0, 128],
})
]
});
deckOverlay.setMap(map);
結果は以下のとおりです。ポリゴンが建物やPOIアイコンの下に描画されているのがわかります。
HERE
GeoJSON
HEREはデフォルトでは詳細な日本の地図が使用できません(日本の地図の表示の仕方)。そこで、HEREに関してはベルリン中央駅周辺を囲むポリゴンを準備しました。
また、後述するH.data.geojson.Reader
メソッドは引数としてGeoJSONファイルのURLを必要とします。そこで、今回は以下のようにBLOB URLを返す関数を準備しました。
function getUrl() {
const jsonString = JSON.stringify({
type: "FeatureCollection",
features: [
{
type: "Feature",
properties: {},
geometry: {
coordinates: [
[
[13.36734443824733, 52.52632030796306],
[13.36734443824733, 52.52199517379691],
[13.374232158425343, 52.52199517379691],
[13.374232158425343, 52.52632030796306],
[13.36734443824733, 52.52632030796306]
]
],
type: "Polygon"
}
}
]
});
const blob = new Blob([jsonString], { type: "application/json" });
return URL.createObjectURL(blob);
}
GeoJSONの表示
まず、以下のライブラリを読み込む必要があります。
<script src="https://js.api.here.com/v3/3.1/mapsjs-data.js"></script>
次に、H.data.geojson.ReaderでGeoJSONデータを読み込み、その中でオプションとしてスタイルを設定します。スタイルの設定は引数にオブジェクト(データ)をとる関数を定義します。今回はオブジェクトがポリゴンなのでH.map.Polygon#setStyle
を呼び出すような関数を定義します。
H.Map#addLayer
を呼び出し、レイヤーとして追加します。
JavaScriptのコードは以下のとおりです。
const reader = new H.data.geojson.Reader(getUrl(), {
style: (obj) => {
obj.setStyle({
fillColor: "rgba(0, 0, 128, 1.0)"
});
}
});
reader.parse();
map.addLayer(reader.getLayer());
結果は以下のとおりです。Overlaidとしてベースマップの上にGeoJSONのレイヤーが表示されるため、ベースマップが隠れます。
また、詳細についてはHEREのExamplesのDisplay GeoJSON Dataをご参照ください。
Interleaved
一度ベースマップの特定のレイヤーを抜き出し、GeoJSONレイヤーの上に新たなレイヤーとして重ねることで擬似的にInterleavedとして動作させることができます。
ここではupdate
という関数を定義しています。まず最初にGeoJSONレイヤーを作成し、次にbuildings
を上から重ねる処理を行っています。スタイルがREADY
状態でないと正常に動作しないようなのでイベント関係の処理が入っています。
function update() {
if (style.getState() !== H.map.Style.State.READY) {
return;
}
style.removeEventListener('change', update);
const reader = new H.data.geojson.Reader(getUrl(), {
style: (obj) => {
obj.setStyle({
fillColor: "rgba(0, 0, 128, 1.0)"
});
}
});
reader.parse();
map.addLayer(reader.getLayer());
const buildings = new H.map.Style(style.extractConfig("buildings"));
const buildingsLayer = platform.getOMVService().createLayer(buildings);
map.addLayer(buildingsLayer);
}
結果は以下のとおりです。GeoJSONのレイヤーの上にbuildings
が表示されています。
詳細についてはHEREのExamplesのInterleave vector and object layersをご参照ください。
TomTom
TomTomは基本的にMapbox GL JSと同じです。ソースの追加はMap#addSource
、レイヤーの作成はMap#addLayer
を使用します。
また、TomTomも日本の詳細な地図が提供されていないため、ベルリン中央駅周辺を囲むポリゴンを使用しました。
map.on("load", () => {
map.addSource("geojson_source", {
type: "geojson",
data: {
type: "FeatureCollection",
features: [
{
type: "Feature",
properties: {},
geometry: {
coordinates: [
[
[13.36734443824733, 52.52632030796306],
[13.36734443824733, 52.52199517379691],
[13.374232158425343, 52.52199517379691],
[13.374232158425343, 52.52632030796306],
[13.36734443824733, 52.52632030796306]
]
],
type: "Polygon"
}
}
]
}
});
map.addLayer(
{
id: "polygon_layer",
type: "fill",
source: "geojson_source",
paint: {
"fill-color": "#000088"
}
},
'Other building'
);
});
結果は以下のとおりです。作成したレイヤーによって公園の地面(緑色)や川が隠れています。しかし道路、建物、文字等は表示されています。Other building
レイヤーの下に配置したため、このように表示されています。TomTomはMapbox GL JS同様ベースマップのスタイルで作成したレイヤーがすべてそのまま扱えます。つまり、原理的にユーザーが作成したレイヤーと区別されないので、レイヤーの配置場所等が柔軟に設定できます。
ArcGIS
ArcGISはGeoJSONLayer
でGeoJSONのレイヤーを作成します。HEREと同じようにGeoJSONのURLが必要であるため、BLOB URLを返すgetUrl()
を使用しました。
GeoJSONLayer
を使用するためには以下のライブラリを読み込む必要があります。
import GeoJSONLayer from "https://js.arcgis.com/4.26/@arcgis/core/layers/GeoJSONLayer.js";
以下のように作成したレイヤーをMap
クラスのコンストラクタに指定します。
const geojsonLayer = new GeoJSONLayer({
url: getUrl(),
renderer: {
type: "simple",
symbol: {
type: "simple-fill",
color: "#000088"
}
}
});
const map = new Map({
basemap: "arcgis-topographic",
layers: [geojsonLayer]
});
const view = new MapView({
container: "map",
map: map,
zoom: 17,
center: [139.7586677640881, 35.67369269880291]
});
結果は以下のとおりです。Overlaidとしてベースマップの上にGeoJSONのレイヤーが表示されるため、ベースマップが隠れます。
Interleaved
ベースマップはベースレイヤーとリファレンスレイヤーで構成することができます。この様なベースマップをmulti-layer basemapといいます。ユーザーが追加したレイヤーはベースレイヤーとリファレンスレイヤーの間に挿入されます。一般にベースレイヤーは地図、リファレンスレイヤーは文字だけから構成されます。例えばカーナビゲーションの用途で経路のラインを表示する際、道路名は表示させることができるので便利です。
ここではベースレイヤーにOpenStreetMap Light Gray Canvas Base (WGS84)、リファレンスレイヤーにOpenStreetMap Light Gray Canvas Reference (WGS84)を利用します。
まず、以下のライブラリを読み込みます。
import VectorTileLayer from "https://js.arcgis.com/4.26/@arcgis/core/layers/VectorTileLayer.js";
import Basemap from "https://js.arcgis.com/4.26/@arcgis/core/Basemap.js";
以下のようにBasemap
を作成します。
const vectorTileLayer = new VectorTileLayer({
portalItem: {
id: "6d9a3c4768bc4090931e9ed3d94fd385"
}
});
const vectorTileLayerRef = new VectorTileLayer({
portalItem: {
id: "ea59b79df5a24e4ebad9bb6db828a623"
}
});
const basemap = new Basemap({
baseLayers: [vectorTileLayer],
referenceLayers: [vectorTileLayerRef]
});
そして以下のようにMap
オブジェクトを作成します。
const map = new Map({
basemap,
layers: [geojsonLayer]
});
結果は以下のとおりです。GeoJSONレイヤーの上に文字が表示されていることがわかります。
MapTiler
MapTilerは基本的にMapbox GL JSと同じです。ソースの追加はMap#addSource
、レイヤーの作成はMap#addLayer
を使用します。
コードは以下のとおりです。
map.on("load", () => {
map.addSource("geojson_source", {
type: "geojson",
data: {
type: "FeatureCollection",
features: [
{
type: "Feature",
properties: {},
geometry: {
coordinates: [
[
[139.75715452555397, 35.67501088740674],
[139.75715452555397, 35.672275911172164],
[139.7609465361483, 35.672275911172164],
[139.7609465361483, 35.67501088740674],
[139.75715452555397, 35.67501088740674]
]
],
type: "Polygon"
}
}
]
}
});
map.addLayer(
{
id: "polygon_layer",
type: "fill",
source: "geojson_source",
paint: {
"fill-color": "#000088"
}
},
"building"
);
});
結果は以下のとおりです。作成したレイヤーによって公園の地面(緑色)や川が隠れています。しかし建物、文字等は表示されています。Other building
レイヤーの下に配置したため、このように表示されています。MapTilerはMapbox GL JS同様ベースマップのスタイルで作成したレイヤーがすべてそのまま扱えます。つまり、原理的にユーザーが作成したレイヤーと区別されないので、レイヤーの配置場所等が柔軟に設定できます。
Mapboxと違い道路が表示されていないのは、Mapboxでは道路のレイヤーが建物のレイヤーの上にあるのに対し、Mapbilerでは下にあるのが原因です。
Azure Maps
Azure MapsはDataSource
でソースを定義し、DataSource#add
でGeoJSONを追加します。そしてPolygonLayer
オブジェクト作成時にそのソースおよびスタイルを指定します。Map#layers.add
で作ったレイヤーを地図に追加します。
LayerManager#add
は第2引数にbefore?
を指定でき、この下にレイヤーを作成します。つまり、before?
を指定することでInterleavedとして動作します。指定しない場合は一番上のレイヤーにかぶせる状態になるのでOverlaidとなります。今回のレイヤーではbuildings
レイヤーを指定しているのでその下にレイヤーが配置されます。
また、日本の地図がラスタータイルで表示されるため、ここではベルリン中央駅周辺を囲むポリゴンを使用しました。
map.events.add("ready", () => {
const datasource = new atlas.source.DataSource();
datasource.add({
type: "FeatureCollection",
features: [
{
type: "Feature",
properties: {},
geometry: {
coordinates: [
[
[13.36734443824733, 52.52632030796306],
[13.36734443824733, 52.52199517379691],
[13.374232158425343, 52.52199517379691],
[13.374232158425343, 52.52632030796306],
[13.36734443824733, 52.52632030796306]
]
],
type: "Polygon"
}
}
]
});
map.sources.add(datasource);
const layer = new atlas.layer.PolygonLayer(
datasource,
'polygon_layer',
{
fillColor: '#000088',
fillOpacity: 1,
});
map.layers.add(layer, 'buildings');
});
結果は以下のとおりです。作成したレイヤーによって公園の地面(緑色)や川が隠れています。しかし建物、文字等は表示されています。buildings
レイヤーの下に配置したため、このように表示されています。
ただし、Azure MapsにおけるベースマップのレイヤーはMapboxにおけるレイヤーと異なります。下図の通り複数のレイヤーをまとめたものをレイヤー(例えばbuildings
)として使用しています。つまり、ざっくりとbuildings
という指定はできますが、より細かい位置レイヤー毎の指定はできません。ArcGISにおけるbaseLayers
を細かめに使用したイメージに近いです。
まとめ
どのサービスも自作のGeoJSONレイヤーを表示することが可能です。ただし、Overlaid/Interleavedという観点からはその自由度に差があることがわかりました。Mapbox GL JSを使用するサービスではベースマップのレイヤーが自作レイヤーと区別なく使用できるので自由度が高いです(そのため、Mapboxではベースマップと呼ばずにコアスタイルと読んでいます)。その他のサービスでは基本的にベースマップのレイヤーは自作レイヤーと区別されるため、自由度が低くなります。図にまとめると以下のとおりです。
ただし、レイヤーコントロールはたくさんある地図ライブラリの機能のうちの一部に過ぎません。各サービスの強みを知り、自身のアプリケーションにあったサービスを利用することが大切です。
Discussion