フリーダムの日記

GIS(地理情報システム)を中心に技術的なことを書いています。

オープンソースの Koop からベクトルタイルに変換して MapLibre GL JS で表示してみる。

はじめに

本エントリーは Smart Maps Advent Calendar 2022 の 24日目の記事です。

qiita.com

OpenAI API と MapLibre GL JS、ArcGIS ロケーションサービスを使用して、ある場所に関してのランチ情報を表示するアプリを作成しましたので、紹介していきたいと思います。

このアプリは、OpenAI API、MapLibre GL JS マップ、および ArcGIS 位置サービスを使用して、ある地域に関する恐ろしい事実を表示します。ピンをドラッグして新たな恐ろしい事実を見つけてください!

koop, esri, arcgis

Koop は、ほんとんど知られてないと思いますが、Esri が開発しているオープンソースで、地理空間データをオンザフライで変換するための Web サーバーです。Koop では、Providers(リモート ソースからデータを要求し、GeoJSON に変換するプラグイン)と出力(GeoJSON をクライアントが消費できるように他のフォーマットに変換するプラグイン)で構成されるプラグイン アーキテクチャによって実現されています。ですので、esri のプラットフォームに縛られず、色々なプラットフォームからデータにアクセスすることができるようになっており、WMSWFS、GeoJSON などの出力形式にも対応しています。

Koop, esri, arcgis

今回は、ローカル上に GeoJSON を配置して、それを Koop によってベクトルタイルに変換して、MapLibre GL JS で表示しました。MapLibre GL JS でリクエスト(http://localhost:8080/file-geojson/rest/services/japan/VectorTileServer/12/3637/1612.pbf)があると Koop がベクトルタイルに変換して結果を返してくれます。日本全国の市区町村データの GeoJSON で確認しましたが、地図への描画も早く、変換も早いイメージでした。

MapLibre GL JS
MapLibre GL JS でのベクトルタイルの表示

今回作成した Koop のデモと Koop を参照してベクトルタイルを表示した MapLibre GL JS については以下の GitHub にアップしました。 github.com

Koop とは

Koop - オープンソースの地理空間データ サーバー
地理空間データをオンザフライで変換し、GeoJSON、Vector Tiles、Feature Services などとして提供することが可能

koop esri

Koop は、空間 API を接続するための JavaScript のツールキットです。GeoJSON を ArcGIS 製品でサポートされている GeoServices 仕様に変換するサーバー(Node.js)で、すぐに利用することができます。そのプラグインアーキテクチャは、ベクトルタイル、WMS、 GeoJSON を含む他のフォーマットでの出力をサポートしています。

Koop は、様々なソースから API 仕様に基づいてデータを変換するために拡張することができます。API の非互換性に邪魔されることなく、Koop のデータ プロバイダーの 1 つを使用するか、独自のものを作成することができます。

koopjs.github.io

Koop は providers を使用して、さまざまなソースからのデータを GeoJSON に変換します。データが GeoJSON に変換されると、キャッシュやクエリの実行など、様々な出力への変換が可能になります。 また、Providers や Outputs に関してはすでに多くの環境に対応しているため、プラグインとしてインストールするだけですぐに使用することが可能となっています。

Koop

koopjs.github.io

Koop

koopjs.github.io

Koop のインストールから利用するまで

KoopNode.js を利用するため、インストールを事前に行う必要があります。WindowsMacUbuntu などの OS で動かすことが可能です。Koop を利用するための各システム要件の詳細は、System requirements をご参照ください。

インストールなど Koop の動作は、こちらのクイックスタートを参考に行いました。

CLI 経由で Koop をインストールします。

$ npm install -g @koopjs/cli

インストールされると、コンソールで Koop コマンドが利用できるようになります。demo-app という名前で新しい Koop アプリケーションを作成します。

$ koop new app demo-app
✓ created project
✓ initialized Git
✓ added app configuration
✓ installed dependencies
✓ done

作成した demo-app フォルダに移動します。

cd demo-app

NPM で公開されている Koop provider を最低 1 つ追加します。ここでは、Koop Githubプロバイダ Koop Github provider を追加します。

$ koop add provider @koopjs/provider-github
✓ added provider-github
✓ registered provider-github
✓ done

今回はさらに koop-provider-file-geojson と出力形式として、ベクトルタイル koop-output-vector-tiles も追加します。

$ koop add provider @koopjs/provider-file-geojson 
✓ added provider-file-geojson
✓ registered provider-file-geojson
✓ done

$ koop add output @koopjs/output-vector-tiles
✓ added output-vector-tiles
✓ registered output-vector-tiles
✓ done

Koop は、provider を使用して、さまざまなソースからのデータを GeoJSON に変換します。データが GeoJSON にフォーマットされると、キャッシュ、クエリ、およびさまざまな出力への変換が可能になります。その他の provider については以下をご参照ください。

koopjs.github.io

これで準備は完了です。次のコマンドで Koop を実行します。

$ koop serve

次のようなコンソールログが表示されるはずです。

WARNING: "/MapServer" routes will be registered, but only for specialized 404 handling in FeatureServer.
[github provider] No github access token configured. Github API requests may be rate limited.
{"level":"info","message":"registered output:"}
No root directory was specified, defaulting to:  /Users/kamiya/JavaScript/koop/demo-app
{"level":"info","message":"registered filesystem:"}

"datasets" provider routes  Methods
--------------------------  -------
/datasets/:id               GET    
/datasets/:id               PUT    
/datasets/:id               DELETE 
/datasets/:id/metadata      GET    
/datasets/:id/metadata      PUT    
/datasets/:id/metadata      DELETE 


"Geoservices" output routes for the "datasets" provider   Methods  
--------------------------------------------------------  ---------
/datasets/rest/info                                       GET, POST
/datasets/tokens/:method                                  GET, POST
/datasets/tokens                                          GET, POST
/datasets/rest/services/:id/FeatureServer/:layer/:method  GET, POST
/datasets/rest/services/:id/FeatureServer/layers          GET, POST
/datasets/rest/services/:id/FeatureServer/:layer          GET, POST
/datasets/rest/services/:id/FeatureServer                 GET, POST
/datasets/:id/FeatureServer/:layer/:method                GET, POST
/datasets/:id/FeatureServer/layers                        GET, POST
/datasets/:id/FeatureServer/:layer                        GET, POST
/datasets/:id/FeatureServer                               GET, POST
/datasets/rest/services/:id/FeatureServer*                GET, POST
/datasets/:id/FeatureServer*                              GET, POST
/datasets/rest/services/:id/MapServer*                    GET, POST
/datasets/:id/MapServer*                                  GET, POST

{"level":"info","message":"registered provider:"}
{"level":"info","message":"registered output:"}





"Geoservices" output routes for the "github" provider   Methods  
------------------------------------------------------  ---------
/github/rest/info                                       GET, POST
/github/tokens/:method                                  GET, POST
/github/tokens                                          GET, POST
/github/rest/services/:id/FeatureServer/:layer/:method  GET, POST
/github/rest/services/:id/FeatureServer/layers          GET, POST
/github/rest/services/:id/FeatureServer/:layer          GET, POST
/github/rest/services/:id/FeatureServer                 GET, POST
/github/:id/FeatureServer/:layer/:method                GET, POST
/github/:id/FeatureServer/layers                        GET, POST
/github/:id/FeatureServer/:layer                        GET, POST
/github/:id/FeatureServer                               GET, POST
/github/rest/services/:id/FeatureServer*                GET, POST
/github/:id/FeatureServer*                              GET, POST
/github/rest/services/:id/MapServer*                    GET, POST
/github/:id/MapServer*                                  GET, POST


"VectorTileServer" output routes for the "github" provider                       Methods  
-------------------------------------------------------------------------------  ---------
/github/:id/VectorTileServer/:z([0-9]+)/:x([0-9]+)/:y([0-9]+).pbf                GET, POST
/github/:id/VectorTileServer/tiles.json                                          GET      
/github/rest/services/:id/VectorTileServer/:z([0-9]+)/:x([0-9]+)/:y([0-9]+).pbf  GET, POST
/github/rest/services/:id/VectorTileServer                                       GET, POST
/github/rest/services/:id/VectorTileServer                                       GET, POST
/github/rest/services/:id/VectorTileServer/resources/styles/root.json            GET      

{"level":"info","message":"registered provider:"}





"Geoservices" output routes for the "file-geojson" provider   Methods  
------------------------------------------------------------  ---------
/file-geojson/rest/info                                       GET, POST
/file-geojson/tokens/:method                                  GET, POST
/file-geojson/tokens                                          GET, POST
/file-geojson/rest/services/:id/FeatureServer/:layer/:method  GET, POST
/file-geojson/rest/services/:id/FeatureServer/layers          GET, POST
/file-geojson/rest/services/:id/FeatureServer/:layer          GET, POST
/file-geojson/rest/services/:id/FeatureServer                 GET, POST
/file-geojson/:id/FeatureServer/:layer/:method                GET, POST
/file-geojson/:id/FeatureServer/layers                        GET, POST
/file-geojson/:id/FeatureServer/:layer                        GET, POST
/file-geojson/:id/FeatureServer                               GET, POST
/file-geojson/rest/services/:id/FeatureServer*                GET, POST
/file-geojson/:id/FeatureServer*                              GET, POST
/file-geojson/rest/services/:id/MapServer*                    GET, POST
/file-geojson/:id/MapServer*                                  GET, POST


"VectorTileServer" output routes for the "file-geojson" provider                       Methods  
-------------------------------------------------------------------------------------  ---------
/file-geojson/:id/VectorTileServer/:z([0-9]+)/:x([0-9]+)/:y([0-9]+).pbf                GET, POST
/file-geojson/:id/VectorTileServer/tiles.json                                          GET      
/file-geojson/rest/services/:id/VectorTileServer/:z([0-9]+)/:x([0-9]+)/:y([0-9]+).pbf  GET, POST
/file-geojson/rest/services/:id/VectorTileServer                                       GET, POST
/file-geojson/rest/services/:id/VectorTileServer                                       GET, POST
/file-geojson/rest/services/:id/VectorTileServer/resources/styles/root.json            GET      

{"level":"info","message":"registered provider:"}
{"level":"info","message":"Koop server listening at 8080"}
{"level":"warn","message":"GeoJSON files will be read from: /Users/******/******/koop/demo-app/data"}

ここで、ローカル上に配置する GeoJSON ですが、/demo-app/data に data ディレクトリを作成して、そこに置きます。今回は、japan.geojson ファイルというファイルを置きました。

実際に Koop にアクセスして確認してみます。 アクセス先の URL は、コンソールログにも出力されているかと思いますが、その仕様に従ってアクセスしていきます。

例えば、GitHub 上のデータにアクセスする場合の方法を見ていきます。 github.com

コンソールログで以下のように出力されているので、

  • /github/rest/services/:id/FeatureServer*

のような感じでクエリでアクセスします。

REST の結果として表示されます。 koop, esri, arcgis

同様にベクトルタイルに対してもコンソールログで以下のように出力されているので、

  • /file-geojson/rest/services/:id/VectorTileServer

のような感じでアクセスします。ここの :id は data ディレクトリに配置しているファイル名を指定します。japan.geojson というファイルなので、japan を指定します。

同じように REST の結果として表示されます。こちらは、Vector Tile Service の REST と仕様と同じようです。

koop, esri, arcgis

developers.arcgis.com

MapLibre GL JS で表示

最後に MapLibre GL JS でベクトルタイルを表示します。MapLibre GL JS の使用方法の詳細について、以下のガイドをを参考としました。

developers.arcgis.com

maplibre.org

ベクトルタイルの表示は、こちらの Add a vector tile layer を参考にしました。ベクトルタイルの参照先として以下の URL を指定します。

http://localhost:8080/file-geojson/rest/services/japan/VectorTileServer/{z}/{x}/{y}.pbf"

全体のソースは以下になります。

<!DOCTYPE html>
<html>
  <head>
    <meta charset="utf-8" />
    <meta name="viewport" content="initial-scale=1,maximum-scale=1,user-scalable=no" />
    <script src="https://unpkg.com/maplibre-gl@2.4.0/dist/maplibre-gl.js"></script>
    <link href="https://unpkg.com/maplibre-gl@2.4.0/dist/maplibre-gl.css" rel="stylesheet" />
    <style>
        body { margin: 0; padding: 0; }
        #map { position: absolute; top: 0; bottom: 0; width: 100%; }
    </style>
  </head>

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

  <script>

    const apiKey = "AAPK07c4048cb3bb48d2a98ee544297631b918Z5mh7gBlBL6owpfjx6YzXq2gnRQzTZhhFiahyfSYojOmvFgnbBV-aaZT9S3nCb";
    const basemapEnum = "OSM:Streets";
    const map = new maplibregl.Map({
      container: "map", // the id of the div element
      style: `https://basemaps-api.arcgis.com/arcgis/rest/services/styles/${basemapEnum}?type=style&token=${apiKey}`,
      zoom: 12, // starting zoom
      center: [139.69167, 35.68944] // starting location [longitude, latitude]
    });
    
    map.addControl(new maplibregl.NavigationControl());
    
    map.once("load", () => {
      // This code runs once the base style has finished loading.
      map.addSource("japan", {
        type: "vector",
        tiles: [
          "http://localhost:8080/file-geojson/rest/services/japan/VectorTileServer/{z}/{x}/{y}.pbf"
        ]
      });

      map.addLayer({
        id: "japan_id",
        type: "fill",
        source: "japan",
        "source-layer": "japan",
        paint: {
          "fill-color": "hsl(200, 80%, 50%)",
          "fill-opacity": 0.5,
          "fill-outline-color": "white"
        }
      });

    });

  </script>

</html>

MapLibre GL JS
MapLibre GL JS でのベクトルタイルの表示

最後に

今回 Koop を使用してベクトルタイルを表示をしました。データの参照先として、今回は GitHub、ローカルの GeoJSON を使用しましたが、それ以外にも AWS S3 や ArcGIS Online、Google Analytics、CKAN など色々と対応しています。また、ベクトルタイル以外にもWMSWFSOGC API - Features などにも対応していたりと、色々と興味深いです。さらにこれらはプラグインとしても拡張ができますので、コンセプト自体も素晴らしいなと思いました。データのキャッシュなどもあったりと、まだ多くの機能を提供しているので、今後機会があればまた触ってみたいと思います。

関連リンク