👻

GeoJSONレイヤー表示における各地図サービスの比較

2023/04/23に公開

はじめに

この記事はスタイルで比較する地図サービスのスピンオフ企画です。各地図サービス上でGeoJSONレイヤーを表示し、その違いを比較します。

Overlaid v.s. Interleaved

作成したレイヤーをどのようにベースマップにかぶせるか、という観点で一般的にOverlaidとInterleavedの二種類が存在します。deck.glの解説を見るとわかりやすいですが、Overlaidはレイヤーをベースマップの上にかぶせるのでベースマップが隠れます。それに対しInterleavedはベースマップの特定のレイヤーに作成したレイヤーを挟み込むため、例えばビルの下に表示するといったことが可能になります。各サービスでこれらにどのように対応しているかは注目ポイントの一つです。

GeoJSONの準備

GeoJSONの作成にはgeojson.ioというサービスが便利です。ブラウザに表示された地図上で図形を描くと、即座にGeoJSONに変換されます。今回は以下のように日比谷公園周辺で長方形のポリゴンを描画します。

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を細かめに使用したイメージに近いです。

Azure Maps Basemap Layer

まとめ

どのサービスも自作のGeoJSONレイヤーを表示することが可能です。ただし、Overlaid/Interleavedという観点からはその自由度に差があることがわかりました。Mapbox GL JSを使用するサービスではベースマップのレイヤーが自作レイヤーと区別なく使用できるので自由度が高いです(そのため、Mapboxではベースマップと呼ばずにコアスタイルと読んでいます)。その他のサービスでは基本的にベースマップのレイヤーは自作レイヤーと区別されるため、自由度が低くなります。図にまとめると以下のとおりです。

ベースマップのレイヤー自由度

ただし、レイヤーコントロールはたくさんある地図ライブラリの機能のうちの一部に過ぎません。各サービスの強みを知り、自身のアプリケーションにあったサービスを利用することが大切です。

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

Discussion