はじめに
本エントリーは Smart Maps Advent Calendar 2022 の 24日目の記事です。
OpenAI API と MapLibre GL JS、ArcGIS ロケーションサービスを使用して、ある場所に関してのランチ情報を表示するアプリを作成しましたので、紹介していきたいと思います。
このアプリは、OpenAI API、MapLibre GL JS マップ、および ArcGIS 位置サービスを使用して、ある地域に関する恐ろしい事実を表示します。ピンをドラッグして新たな恐ろしい事実を見つけてください!
Koop は、ほんとんど知られてないと思いますが、Esri が開発しているオープンソースで、地理空間データをオンザフライで変換するための Web サーバーです。Koop では、Providers(リモート ソースからデータを要求し、GeoJSON に変換するプラグイン)と出力(GeoJSON をクライアントが消費できるように他のフォーマットに変換するプラグイン)で構成されるプラグイン アーキテクチャによって実現されています。ですので、esri のプラットフォームに縛られず、色々なプラットフォームからデータにアクセスすることができるようになっており、WMS や WFS、GeoJSON などの出力形式にも対応しています。
今回は、ローカル上に GeoJSON を配置して、それを Koop によってベクトルタイルに変換して、MapLibre GL JS で表示しました。MapLibre GL JS でリクエスト(http://localhost:8080/file-geojson/rest/services/japan/VectorTileServer/12/3637/1612.pbf)があると Koop がベクトルタイルに変換して結果を返してくれます。日本全国の市区町村データの GeoJSON で確認しましたが、地図への描画も早く、変換も早いイメージでした。
今回作成した Koop のデモと Koop を参照してベクトルタイルを表示した MapLibre GL JS については以下の GitHub にアップしました。 github.com
Koop とは
Koop - オープンソースの地理空間データ サーバー
地理空間データをオンザフライで変換し、GeoJSON、Vector Tiles、Feature Services などとして提供することが可能
Koop は、空間 API を接続するための JavaScript のツールキットです。GeoJSON を ArcGIS 製品でサポートされている GeoServices 仕様に変換するサーバー(Node.js)で、すぐに利用することができます。そのプラグインのアーキテクチャは、ベクトルタイル、WMS、 GeoJSON を含む他のフォーマットでの出力をサポートしています。
Koop は、様々なソースから API 仕様に基づいてデータを変換するために拡張することができます。API の非互換性に邪魔されることなく、Koop のデータ プロバイダーの 1 つを使用するか、独自のものを作成することができます。
Koop は providers を使用して、さまざまなソースからのデータを GeoJSON に変換します。データが GeoJSON に変換されると、キャッシュやクエリの実行など、様々な出力への変換が可能になります。 また、Providers や Outputs に関してはすでに多くの環境に対応しているため、プラグインとしてインストールするだけですぐに使用することが可能となっています。
Koop のインストールから利用するまで
Koop は Node.js を利用するため、インストールを事前に行う必要があります。Windows や Mac、Ubuntu などの OS で動かすことが可能です。Koop を利用するための各システム要件の詳細は、System requirements をご参照ください。
インストールなど 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 については以下をご参照ください。
これで準備は完了です。次のコマンドで 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 の結果として表示されます。
同様にベクトルタイルに対してもコンソールログで以下のように出力されているので、
- /file-geojson/rest/services/:id/VectorTileServer
のような感じでアクセスします。ここの :id は data ディレクトリに配置しているファイル名を指定します。japan.geojson というファイルなので、japan を指定します。
同じように REST の結果として表示されます。こちらは、Vector Tile Service の REST と仕様と同じようです。
MapLibre GL JS で表示
最後に MapLibre GL JS でベクトルタイルを表示します。MapLibre GL JS の使用方法の詳細について、以下のガイドをを参考としました。
ベクトルタイルの表示は、こちらの 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>
最後に
今回 Koop を使用してベクトルタイルを表示をしました。データの参照先として、今回は GitHub、ローカルの GeoJSON を使用しましたが、それ以外にも AWS S3 や ArcGIS Online、Google Analytics、CKAN など色々と対応しています。また、ベクトルタイル以外にもWMS、WFS、OGC API - Features などにも対応していたりと、色々と興味深いです。さらにこれらはプラグインとしても拡張ができますので、コンセプト自体も素晴らしいなと思いました。データのキャッシュなどもあったりと、まだ多くの機能を提供しているので、今後機会があればまた触ってみたいと思います。