カスタムマップポータル:スタートからエンドまでのガイド
2022年4月12日発行 2022年6月20日更新

この記事では、カスタムマップ、独自データ、マップレジェンド、地名検索、選択した建物のサテライトビュー、ストリートビューを備えたジオポータルを作成する方法を説明します。読むことによって、データをMBTilesに変換し、MapTilerCloud のアカウントにアップロードし、MapLibre GL JSで開発されたアプリケーションで可視化する方法を学ぶことができます。
最終的なマップは次のようになります(建物をクリックするとマジックが表示されます)。
地図データのダウンロード
まず、スペインのCadastreから建物のデータをダウンロードします。この例では、Sant Feliu de Guixolsのデータを使用します。
データは以下のリンクからダウンロードできます。SANT FELIU DE GUIXOLS/A.ES.SDGC.BU.17170.zip
データをダウンロードしたら、ZIPファイルを解凍していきます。ZIPファイルの中には、建物の情報が入ったA.ES.SDGC.BU.17170.building.gmlというファイルがあり、このファイルはGML形式になっています。
GMLファイルからMBTilesへの変換
GMLファイルをMBTilesに変換するために、いくつかのオプションがあります。
- MapTilerDesktop: データを簡単にMBTilesやGeoPackageに変換することができます。また、変換されたデータを直接クラウドアカウントにアップロードすることもできます。もっと詳しく知りたい方は、Vector tiles generating (basic)の記事をご覧ください。
- GDAL/ogr2ogr: GDAL/ogr2ogrがコンピュータにインストールされている場合、以下のコマンドでGMLファイルをMBTilesに変換することも可能です。
ogr2ogr -f MVT guixols.mbtiles A.ES.SDGC.BU.17170.building.gml -dsco MAXZOOM=18 -dsco MINZOOM=9 -mapFieldType DateTime=String
- MapTilerEngine: このツールは、MapTilerDesktop のプロバージョンに含まれています。MapTilerDesktop のフルパワーで自動ワークフローを作成します。MapTilerEngine についてもっと知りたければ、MapTilerEngine Usage の記事をご覧ください。MapTilerEngine を使って gml ファイルを mbtiles に変換するには、次のコマンドを実行する必要がありま す。
maptiler -o guixols.mbtiles A.ES.SDGC.BU.17170.building.gml
見てわかるように、MapTilerEngine はデータに最適なズームレベルを計算し、それに対応するデータ タイプの変換を実行する役割を担っています。GDALの例では、ズームレベルを定義し、英数字のデータ型がMBTilesで有効なデータ型となるように変換を指示する必要があります。
MapTilerにジオデータをアップロードするCloud
MapTilerDesktop から直接 MapTilerCloud にデータをアップロードできることはすでに述べました ;MBTilesをアップロードする他の方法はMapTilerCloud Admin API を通してです。
There are numerous ways to send your requests to the API; whether you are a fan of API clients or go with the good old curl, don’t forget to set the Authorization header in the form of Token {YOUR_TOKEN} so we know it’s you making the requests. To make your life easier, we have also created a CLI utility to upload the tilesets.
curlを使って手動で全てのプロセスを行うには、複数のAPIコールを行う必要があります。例えば、インジェストを開始するためにAdmin APIを呼び出すとします。Admin APIは、ファイルをアップロードするためのGoogle DriveのURLを返します。次にGoogle Drive APIを呼び出してファイルをアップロードし、最後にAdmin APIを呼び出してファイルを処理します。
お客様の生活を楽にするために、私たちはタイルセットをアップロードするためのMapTilerCloud CLIユーティリティを開発しました。このオープンソースツールはPythonで開発されており、クラウドへのデータアップロードのプロセスを自動化することが可能です。MapTilerCloud CLI GitHubリポジトリでコードにアクセスすることができます。
MapTilerCloud CLI ツールを使って、データをクラウドにアップロードすることにします。
CLIツールのインストールが完了したら、ツールをインストールした仮想環境を起動する必要があります。次に、以下のコマンドを実行し、MBTiles ファイルを MapTilerCloud にアップロードします。
maptiler-cloud --token=YOUR_CREDENTIAL_TOKEN tiles ingest guixols.mbtiles
データを表示する地図付きアプリケーションを開発する
MapTilerCloud のデータを可視化するために、MapLibre GL JSライブラリを使用したアプリケーションを作成する予定です。
マップを作成する
まず最初に、データを表示するための地図を作成します。私たちの地図の参照用地図をロードするために、MapTilerCloud API を呼び出します。MapTilerCloud は幅広いユースケースに合うように、多くのすぐに使えるベースマップのスタイルを持っています。
MapLibreを使ったことがない場合は、GL JSチュートリアルのGet Started With MapLibreをチェックすることをお勧めします。
index.htmlというファイルを作成し、以下のコードをコピーしてください。
In the above example, we are calling the MapTiler Cloud API to load the base map https://api.maptiler.com/maps/basic/style.json?key=${apiKey}
ブラウザでアプリケーションを開くと、次の画像のような地図が表示されるはずです。
ジオコーディングコントロールの作成
場所を検索するためのコントロールを追加してみましょう。これには、Geocoding APIを使用します。
Geocoding APIの使用を容易にするために、私たちはあなたのアプリケーションに簡単に組み込むことができるgeocoderコンポーネントを開発しました。
ジオコーダー・コンポーネントを読み込むには、index.htmlファイルのヘッダーにハイライトされた行を追加します。
コンポーネントがロードされたら、マップに追加するためにカスタムのMapLibreコントロールを作成するつもりです。
マップナビゲーションコントロールを追加した直後です。ハイライトされた行を記述します。
//add navigation control
map.addControl(new maplibregl.NavigationControl(), 'top-right');
class searchControl {
onAdd(map) {
this._map = map;
this._container = document.createElement('div');
this._container.className = 'maplibregl-ctrl';
const _input = document.createElement('input');
this._container.appendChild(_input);
const geocoder = new maptiler.Geocoder({
input: _input,
key: apiKey
});
geocoder.on('select', function(item) {
map.fitBounds(item.bbox);
});
return this._container;
}
onRemove() {
this._container.parentNode.removeChild(this._container);
this._map = undefined;
}
}
map.addControl(new searchControl(), 'top-left');
マップの左上に検索コントロールが表示されているはずです。
地図データの可視化
地図にデータを追加するには、まず新しいデータソースを宣言し、このデータソースの情報を使って新しいレイヤーを追加する必要があります。
ページ上のマップを初期化することで、ブラウザにスタイルを要求するように指示します。これは、サーバーがそのスタイル要求に応答できる速度と、ブラウザがマップをレンダリングするのにかかる時間(通常はミリ秒)により、ある程度時間がかかる場合があります。これらのリソースはリモートなので非同期に実行され、これ以上のコードを実行する前にスタイルがロードされていることを確認することが重要です。
マップオブジェクトは、マップの状態が変化したときに発生する特定のイベントを、ブラウザに通知することができます。これらのイベントの 1 つは load イベントで、このイベントはスタイルがマップにロードされたときにトリガされます。
地図にデータソースを追加する
map.on('load', callback function) メソッドにより、このイベントが発生するまで、残りのコールバックコードが一切実行されないようにすることができます。
map.addControl(new searchControl(), 'top-left');
map.on('load', () => {
// the rest of the code will go in here
});
したがって、マップがレンダリングされる前に新しいソースがロードされないように、map.on('load')関数の内部でaddSource関数を呼び出す必要があります。
map.on('load', () => {
// the rest of the code will go in here
map.addSource("building_source", {
"type": "vector",
"url": `https://api.maptiler.com/tiles/YOUR_TILESET_ID/tiles.json?key=${apiKey}`
});
});
上記のコードでは、MapTilerCloud API を使って、MapTilerCloud にデータをアップロードしたときに生成されたTileJSONを参照しています。
地図にレイヤーを追加する
データソースを定義したら、レイヤーを地図に追加することができます。これには addLayer 関数を使います。addSourceのすぐ後に次の行を書きます。
map.on('load', () => {
// the rest of the code will go in here
map.addSource("building_source", {
"type": "vector",
"url": `https://api.maptiler.com/tiles/YOUR_TILESET_ID/tiles.json?key=${apiKey}`
});
map.addLayer({
"id": "building_pol",
"type": "fill",
"source": "building_source",
"source-layer": "Building",
"layout": {
"visibility": "visible"
},
"paint": {
"fill-color": "#3A1888",
"fill-opacity": [
"literal",
0.6
]
},
"filter": ["all",
["==", "$type", "Polygon"]
],
}, "label_airport");
});
上記のコードでは、空港ラベルレイヤーの直前に建物レイヤーを追加しています。これは、建物のポリゴンがラベルの下の地図に表示され、通り名や近隣地域などのテキストを隠さないようにするためです。
アプリケーションを再読み込みし、次のように入力します。 サン・フェリウ・デ・ギクソルスを入力し、検索結果の一覧から最初のエントリーを選択します。アプリケーションはSant Feliu de Guíxolsを拡大表示し、建物レイヤーが表示されます。
クリックで建物情報を表示
ここまで、地図上に独自のデータを追加して表示する方法について見てきました。次に行うのは、建物をクリックしたときに、その建物に関連する情報を表示することです。これには、地図のクリックイベントを使います。
地図にレイヤーを追加した後、以下のコードを記述します。
このコードでは、表示するコンテンツを作成するための関数(createPopupContent)を定義し、建物レイヤーのマップクリックイベントを登録しています。
ユーザーエクスペリエンスを向上させるために、建物の上にカーソルを置くとカーソルポインタが変化するようにし、ユーザーが建物とインタラクションできることを認識できるようにします。
クリックイベントを追加する場所の後に、次のように記述します。
map.on('click', 'building_pol', function (e) {
const content = createPopupContent(e.features[0]);
new maplibregl.Popup()
.setLngLat(e.lngLat)
.setHTML(content)
.addTo(map);
});
map.on('mouseenter', 'building_pol', () => {
map.getCanvas().style.cursor = 'pointer';
});
map.on('mouseleave', 'building_pol', () => {
map.getCanvas().style.cursor = '';
});
建物のコレオプス・マップを作成する
建物のコレポリスマップを作成するために、建物レイヤーのスタイルを変更し、建物に関連する英数字データを使用して、与えられたフィールドの値に従って分類し、ペイントすることになります。今回は、currentUseフィールドを使用して分類を行うことにします。
建物レイヤーの英数字データを見てみると、currentUseフィールドには6つの値があることがわかります。これらの値から配列を作成し、テキストと色を割り当てます。
APIキーの変数を宣言した直後に、以下のように記述します。
const apiKey = 'YOUR_API_KEY';
const categories_field = "currentUse";
const categories = [
{id: "1_residential", text: "Residential", color: "#FFD65F"},
{id: "2_agriculture", text: "Agriculture", color: "#35C186"},
{id: "3_industrial", text: "Industrial", color: "#805CC2"},
{id: "4_1_office", text: "Office", color: "#FF8F65"},
{id: "4_2_retail", text: "Retail", color: "#3388F1"},
{id: "4_3_publicServices", text: "Public Services", color: "#E25041"},
];
このカテゴリと色の配列を使って、建物を塗る色を決定する式を生成するcreateFillColor関数を作成します。
{id: "4_2_retail", text: "Retail", color: "#3388F1"},
{id: "4_3_publicServices", text: "Public Services", color: "#E25041"},
];
const createFillColor = (categories, categories_field) => {
const colors = categories.reduce((agg, item) => {
agg.push(item.id);
agg.push(item.color);
return agg;
}, []);
return [
"match",
[
"get",
categories_field
],
...colors,
"#ccc"
]
}
建物レイヤーのfill-colorプロパティを、定義された色("#3A1888")ではなく、createFillColor関数を使って値を決定するように変更します。
"paint": {
"fill-color": createFillColor(categories, categories_field),
"fill-opacity": [
"literal",
0.6
]
},
アプリケーションを再読み込みすると、用途に応じた色で建物が塗られていることがわかります。
インタラクティブな地図の凡例を作成する
地図上にインタラクティブな凡例を作り、分類の色を表示し、さらにどの分類がアクティブか非アクティブかを視覚的に変化させる予定です。
まず、レイヤーフィルターを作成するための関数を作成します。レイヤーのフィルタープロパティを使用して、地図に表示するカテゴリーを決定します。これにより、アクティブなカテゴリと非アクティブなカテゴリの表示を変更することができます。
...colors,
"#ccc"
]
}
const createFilter = (categories, categories_field) => {
const filters = categories.reduce((agg, item) => {
agg.push(["in", categories_field, item.id]);
return agg;
}, []);
return [
"all",
["==", "$type", "Polygon"],
["any",
...filters
]
]
}
建物レイヤーのフィルタープロパティを変更する
"paint": {
"fill-color": createFillColor(categories, categories_field),
"fill-opacity": [
"literal",
0.6
]
},
"filter": createFilter(categories, categories_field),
}, "label_airport");
凡例コントロールの作成
凡例コントロールを作成する前に、カテゴリの可視性を切り替えるための関数をいくつか作成します。関数createFilterの作成の隣に、以下の行を記述します。
["any",
...filters
]
]
}
const removeAtIndex = (arr, index) => {
const copy = [...arr];
copy.splice(index, 1);
return copy;
};
const toggle = (arr, item, getValue = item => item) => {
const index = arr.findIndex(i => getValue(i) === getValue(item));
if (index === -1) return [...arr, item];
return removeAtIndex(arr, index);
}
検索コントロールを追加した場所のすぐ下に、次のようにコピーしてください。
map.addControl(new searchControl(), 'top-left');
class legendControl {
constructor (categories, field) {
this.categories = categories;
this.field = field;
}
onAdd(map) {
this._map = map;
this._container = document.createElement('div');
this._container.className = 'maplibregl-ctrl';
const _fragment = document.createDocumentFragment();
const _nav = document.createElement('nav');
_nav.className = 'maplibregl-ctrl filter-group';
_nav.id = 'filter-group';
_fragment.appendChild(_nav);
this.categories.forEach(element => {
const _input = document.createElement('input');
_input.type = "checkbox";
_input.id = element.id;
_input.className = "input-layers";
_input.checked = true;
const this_ = this;
_input.addEventListener('change', function (e) {
this_.updateLegend(e.target.id);
});
const _label = document.createElement('label');
_label.htmlFor = element.id;
const _text = document.createTextNode(element.text);
const _legend = document.createElement('i');
_legend.style.backgroundColor = element.color;
_label.appendChild(_text);
_label.appendChild(_legend);
_nav.appendChild(_input);
_nav.appendChild(_label);
});
this._container.appendChild(_fragment);
return this._container;
}
onRemove() {
this._container.parentNode.removeChild(this._container);
this._map = undefined;
}
updateLegend(id) {
let filter = this._map.getFilter('building_pol');
if (filter){
const [any, ...filters] = filter[2];
filter[2] = [any, ...toggle(filters, ["in", this.field, id], (item) => item[2])];
this._map.setFilter('building_pol', filter);
}
}
}
map.addControl(new legendControl(categories, categories_field), 'bottom-left');
次に、凡例コントロールのスタイルを設定します。ページのスタイルセクションに、次のように記述します。
#map {position: absolute; top: 0; right: 0; bottom: 0; left: 0;}
.filter-group {
font: 12px/20px 'Ubuntu', sans-serif;
font-weight: 400;
position: absolute;
bottom: 25px;
z-index: 1;
border-radius: 4px;
width: 150px;
color: rgba(51, 51, 89, 1);
box-shadow: 0px 15px 68px rgba(51, 51, 89, 0.15);
background: rgba(255, 255, 255, 0.9);
}
.filter-group input[type='checkbox']:first-child + label {
border-radius: 6px 6px 0 0;
}
.filter-group label:last-child {
border-radius: 0 0 6px 6px;
border: none;
}
.filter-group input[type='checkbox'] {
display: none;
}
.filter-group input[type='checkbox'] + label {
display: block;
cursor: pointer;
padding: 10px;
border-bottom: 1px solid rgba(0, 0, 0, 0.25);
text-transform: capitalize;
}
.filter-group input[type='checkbox'] + label i {
width: 18px;
height: 18px;
float: right;
margin-right: 0 8px;
opacity: 0.7;
}
.filter-group input[type='checkbox'] + label:hover {
background-color: rgba(49, 112, 254, 0.05);
}
.filter-group input[type='checkbox'] + label:before {
content: '';
margin-right: 15px;
}
.filter-group input[type='checkbox']:checked + label:before {
content: '✔';
margin-right: 5px;
}
ページを再読み込みすると、凡例コントロールが地図の左下隅に表示されているはずです。凡例のいくつかのカテゴリーをクリックすると、そのカテゴリーの建物の可視性が変化することがわかります。
選択した建物のサテライトビュー画像を表示する
建物の航空写真画像を作成するために、MapTilerCloud Static Maps APIを使用します。
建物選択時に表示される情報を修正し、その建物の衛星写真のイメージを表示するようにします。
建物の衛星写真画像を作成するためには、建物のバウンディングボックスを知る必要があります。ここでは、ジオメトリのバウンディングボックスを取得できるTurf.jsライブラリーを使用する。
ページの先頭で次の行をコピーして、Turfライブラリを追加します。
createPopupContent関数の先頭に、以下の行を追加します。
createPopupContent関数で、documentLinkプロパティを表示する画像の直後のreturnの末尾に、以下の行を追加します。
さらに、「使用」フィールドに表示される情報を、識別子コードではなく、カテゴリーテキストを表示するように変更する予定です。そのために、選択されたカテゴリのテキストを返す関数を作成する予定です。
toggle関数の直後に、以下の行をコピーしてください。
if (index === -1) return [...arr, item];
return removeAtIndex(arr, index);
}
const getUse = (use, categories) => {
return categories.reduce((agg, item) => {
return item.id === use ? item.text : agg;
}, "Unknown");
}
この関数を呼び出して、使用欄の情報を表示することにします。
アプリケーションを更新して、建物をクリックします。建物の情報とともに、その建物の航空写真画像が表示されます。
概要
この記事では、MapTilerCloud API と MapLibre ライブラリを用いて、我々のデータでインタラクティブな地図を作成する方法を見ました。
してきました。
- Admin APIを使用してデータをアップロードする
- Maps APIによるベースマップのアップロード
- Geocoding APIコンポーネントを使用して場所を検索する
- Tiles API を使ってデータをベクタータイルとして可視化する。
- チョロプシスマップの作成
- インタラクティブな凡例の作成
- Static Maps APIを利用した建物の衛星写真画像の作成
MapTilerの詳細Cloud API
API を使用して MBTiles または GeoPackage を MapTilerCloud にアップロードする方法。
MapTilerCloud APIを使った地図の作り方 - 使用例・事例紹介