レイヤの処理

L.map で作成するマップオブジェクトには、 レイヤ管理機能がある。レイヤは何枚でも重ねられるが以下の2つに分類される。

ベースレイヤ

表示地図の基盤となるレイヤで、複数のレイヤのうち 1枚だけ表示させることができる。

オーバーレイレイヤ

ベースレイヤの上に重ねて表示するレイヤで 同時に何枚でも表示させることができる。

ベースレイヤはタイル地図を保持するレイヤ、 オーバーレイレイヤには地点の註釈となるマーカや 経路を示すラインオブジェクト、エリアを表示するポリゴンなどを 格納するレイヤを登録するのが主な使い方となる。

Leaflet では2種類のレイヤをまとめて管理するコントロールレイヤを利用する。 L.control.layers() を以下のように使用する。

L.control.layers(BaseConf[, OvConf[, Options]])

例として、以下のような4つのレイヤを持つマップの作成を示す。

markerlayer.js

document.addEventListener("DOMContentLoaded", () => {
    // 例: コントロールレイヤ
    var mlCenter   = [38.891, 139.824];
    var otherPoint = [38.890, 139.822];
    var osmTile =	// OSMのタイル
	L.tileLayer('//{s}.tile.openstreetmap.org/{z}/{x}/{y}.png', {
	    attribution:
	    '&copy; <a href="http://osm.org/copyright">OpenStreetMap</a> \
contributors'
	});
    var gsiTile =	// 国土地理院タイル
	L.tileLayer('//cyberjapandata.gsi.go.jp/xyz/std/{z}/{x}/{y}.png', {
	    attribution:
	    '<a href="http://maps.gsi.go.jp/development/ichiran.html">国土地理院</a>'
	});
    var markerLayer1 = L.marker(mlCenter);
    var markerLayer2 = L.marker(otherPoint);
    var mlmap = L.map("markerlayer", {
	layers: [gsiTile, markerLayer1, markerLayer2],
	center: mlCenter, zoom: 16, scrollWheelZoom: false
    });
    /*     上記4行は以下のようにするのと同じ効果
	   var mlmap = L.map("markerlayer").setView(mlCenter, 16);
	   gsiTile.addTo(mlmap);
	   markerLayer1.addTo(mlmap);
	   markerLayer2.addTo(mlmap); */
    // 以下、コントロールレイヤの設定
    //   ベースレイヤはOSMと国土地理院2つをJSON形式で指定
    var baseLayers = {'OpenStreetMap': osmTile, '国土地理院': gsiTile};
    //   マーカーを2つオーバーレイレイヤに指定
    var ovlLayers  = {'マーク1': markerLayer1, 'マーク2': markerLayer2};
    //   ベースレイヤとオーバーレイレイヤをマップに追加
    L.control.layers(baseLayers, ovlLayers).addTo(mlmap);
}, false);	// HTML文書がロードされたら上のアロー関数を呼ばせる

markerlayer.html

<!DOCTYPE html>
<html lang="ja">
<head>
<title>MarkerLayer</title>
<link rel="stylesheet" type="text/css" href="../src/leaflet.css">
<script type="text/javascript" src="../src/leaflet.js"></script>
<script type="text/javascript" src="markerlayer.js"></script>
<style type="text/css">
<!--
div#markerlayer {width: 90vw; height: 90vw; margin: auto;}
-->
</style>
</head>

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

</body>
</html>

実例

レイヤグループ

マーカやポリラインなどのオブジェクトは1つ生成すると 1枚のレイヤとして返されマップに貼り付けることになる。 複数のオブジェクトをまとめて1枚のレイヤで管理したいときには レイヤグループ を使用する。

上記の例 markerlayer.js において、2つのマーカオブジェクト(レイヤ)をまとめて 1つにしてマップ上に配置するように変えるには、 L.layerGroup に複数のレイヤを含む配列を渡しグループ化し、 それをマップに貼り付けるとよい。具体的には以下のようにする。

var markerLayer1 = L.marker(mlCenter);		// マーカレイヤその1
var markerLayer2 = L.marker(otherPoint);	// マーカレイヤその2
// 2つのレイヤをまとめる
var markerLayerGroup = L.layerGroup([markerLayer1, markerLayer2]);
// まとめたレイヤをマップに貼り付ける
var mlmap = L.map("markerlayergroup", {
    layers: [gsiTile, markerLayerGroup],
    center: mlCenter, zoom: 16, scrollWheelZoom: false
});

GeoJSONレイヤ

マーカ、ライン、ポリゴンなどを地図上に配置したものを表現する形式に GeoJSON がある。たとえば以下のような値である。

triangle.geojson

var triangle = 	/* この行は便宜上追加。GeoJSON値は次の行から */
{
  "type": "FeatureCollection",
  "features": [
    {
      "type": "Feature",
      "properties": {
        "_storage_options": {
          "color": "Aqua",
          "opacity": "0.8"
        }
      },
      "geometry": {
        "type": "Polygon",
        "coordinates": [
          [
            [
              139.82367396354678,
              38.892920059048464
            ],
            [
              139.82132434844974,
              38.89035641677137
            ],
            [
              139.8262166976929,
              38.89023950579702
            ],
            [
              139.82367396354678,
              38.892920059048464
            ]
          ]
        ]
      }
    }
  ]
}

これは地図上に配置する Aqua 色のポリゴンを示す値である。 変数 triangle に代入したこの値を GeoJSON レイヤに変換してマップに貼り付けることができる。変換には L.geoJson() を用いる。

L.geoJson([GeoJSON[, Option]])

GeoJSON にはGeoJSONの値を、Option には GeoJSON 値の処理時に必要なオプション、 あるいはポリゴン等オブジェクトの描画に関るオプションを指定できる。 GeoJSON 値の処理に関るオプション一覧を以下に示す。

オプション働き
pointToLayer ポイントデータをマップ上に独自オブジェクトとして配置したい場合に用いる。 オブジェクトと座標を引数として受け取り、 その座標に配置すべきオブジェクトの値を返す関数を指定する。 ★★★ office.html
style オブジェクトを1つずつ受け取り、 そのスタイルオプションをJSON形式で返す関数を指定する。たとえば
style: function(feature) {
  return {color: "red"};
}
とすると、すべてのスタイルオプションに {color: "red"} を指定したのと同じことになる。
onEachFeature オブジェクト、レイヤを1つずつ受け取り、 繰り返し実行する関数を指定する。たとえば、GeoJSON 形式で打ち込んだポイントに "message" プロパティが設定してあるようなデータを読み取る場合には、
onEachFeature: function(feature, layer) {
  if (feature.properties && feature.properties.message) {
    layer.bindPopup(feature.properties.message);
  }
}
などとしておくと、ポップアップ文字列に message プロパティの値が利用される。
filter 引数に対象オブジェクトを受け取り、それを表示すべきか否かをブール値で 返す関数を指定する。false を返す場合はレイヤに載せない。

上記の GeoJSON 値代入式を読み込み、 マップ上のレイヤとして反映させる例を示す。 HTML文書ではあらかじめ triangle.geojson をロードしておく。

<script type="text/javascript" src="triangle.geojson" charset="utf-8">
</script>

その部分より後ろで、変数 triangle に代入された GeoJSON 値を GeoJSON レイヤに貼り付けるコードを記述する。 例示簡略化のため HTML 文書内に JavaScript コードも書く形式で示す。

<div id="geojson" style="width: 80vw; height: 80vh;"></div>
var jsonmap = L.map("jsonmap").setView([38.891, 139.824], 16);
L.tileLayer(
    '//{s}.tile.openstreetmap.org/{z}/{x}/{y}.png',
    {attribution:
     '&copy; <a href="http://osm.openstreetmap.org/copyright">OpenStreetMap</a> contributors'
    }).addTo(jsonmap);
L.geoJson(triangle, { // triangle変数は triangle.geojson からロードした
    style: function (feature) {return feature.properties;}
}).addTo(jsonmap);

このようにすることで、正しい GeoJSON 形式で保存された地理的データをマップ上に配置することができる。

なお、GeoJSON オブジェクトにポップアップを含む場合は、 ポップアップ時に出現させたい文字列をオブジェクトの bindPopup() メソッドで登録する。このためには、元となる GeoJSON 値にどのようにポップアップ文字列を埋め込んでおくかの取り決めが必要となる。

GeoJSONとポップアップ

uMapの例

uMapで作成した地図とオブジェクトの例

たとえば、マップ上の任意の位置に自由にマーカやラインなどを配置でき、 それを GeoJSON 形式で保存できるフリーなサービスである uMap では、 水色に色付けしたポリゴンとそれに設定したポップアップ文字列は以下のように GeoJSON 化される。例として、ポリゴンによる三角形とポップアップマーカを 1つずつ配置した地図を用意した。

この2つのオブジェクトいずれにもポップアップ表示を付加してある。 そのGeoJSONリストを以下に示す。

uMapで生成されたGeoJSONファイル

var triangleUmap =		// この1行だけ手で追加した。
{
  "type": "FeatureCollection",
  "features": [
    {
"type": "Feature", // (1)Feature その1 "properties": { "name": "三角地帯", "description": "飯森山の三角形" }, "geometry": { "type": "Polygon", "coordinates": [ [ [ 139.8236417770386, 38.89282820375738 ], [ 139.82629179954532, 38.89027290895218 ], [ 139.8214423656464, 38.89045662602478 ], [ 139.8236417770386, 38.89282820375738 ]]]}},
{ // (2)Feature その2 "type": "Feature", "properties": { "name": "南洲神社", "description": "西郷隆盛像あり" }, "geometry": { "type": "Point", "coordinates": [ 139.82450008392337, 38.88983866670988 ]}}
] }

このGeoJSON情報を Leaflet のマップ上にポップアップつきで貼り付けることを考える。 GeoJSONで表現されたオブジェクト(群)を上述の L.geoJson 関数で変換する際のオプション(Option)の onEachFeature を利用する。リスト「uMapで生成されたGeoJSONファイル」中、四角枠で囲った2つがそれぞれ Feature にあたり、これらが onEachFeature に指定した関数の第一引数に与えられる。 これを踏まえると次のような GeoJSON 生成でポップアップ登録ができる。

    onEachFeature: function(j, layer) {
	let p = j.properties;
	if (p) {
	    let name = p.name, desc = p.description;
	    let popup = "<h3>" + name + "</h3>" + "<p>" + desc + "</p>";
	    layer.bindPopup(popup);
	}
    }

各 Feature を仮引数 j で受けると、properties キーの持つJSONの、さらに内部のプロパティ namedescription が名前と説明文に当たるため、これを h2 要素と p 要素に仕立てたものをポップアップ文字列としている。

以上をまとめたものの、HTML ファイル(triangle-umap.html)と JavaScript プログラム(triangle-umap.js)を以下に示す。

triangle-umap.html

<!DOCTYPE html>
<html lang="ja">
<head>
<title>uMap GeoJSON</title>
<meta name="viewport" content="width=device-width, initial-scale=1" />
<link rel="stylesheet" href="../src/leaflet.css" />
<script src="../src/leaflet.js"></script>
<style type="text/css">
<!--
div#umap-json {width: 90vw; height: 80vh; margin: 0 auto;}
-->
</style>
</head>

<body>
<h1>Triangle GeoJSON by uMap</h1>
<div id="umap-json"></div>

<script type="text/javascript"
 src="../src/triangle-umap.geojson" charset="utf-8"></script>
<script type="text/javascript"
 src="../src/triangle-umap.js" charset="utf-8"></script>

</body>
</html>

triangle-umap.js

// 例: ポップアップつきのuMapオブジェクトを再現する
var jsonmap = L.map("umap-json").setView([38.891, 139.824], 16);
var layer = L.tileLayer(
    'https://cyberjapandata.gsi.go.jp/xyz/std/{z}/{x}/{y}.png',
    {attribution:
     '&copy; <a
 href="//www.gsi.go.jp/kikakuchousei/kikakuchousei40182.html">国土地理院</a>'
    }).addTo(jsonmap);
var gj = L.geoJson(triangleUmap, {
    style: function (feature) {
	return feature.properties;
    },
    onEachFeature: function(j, layer) {
	let p = j.properties;
	if (p) {
	    let name = p.name, desc = p.description;
	    let popup = "<h3>" + name + "</h3>" + "<p>" + desc + "</p>";
	    layer.bindPopup(popup);
	}
    }
});
// L.control.layers(null, {"Triangle": gj}).addTo(jsonmap);
gj.addTo(jsonmap)

GeoJSONデータファイルのロード

triangle-umap.html では、オブジェクトを含む GeoJSON データファイルに手を加え、 変数に GeoJSON 値を代入する JavaScript プログラムとしてから HTML ファイルで読み込む細工をした。この方式では、uMap 等でマップデータを編集・保存する度にファイルに手を加える必要があり効率的でない。 GeoJSON ファイルを純粋にデータとして、JavaScript プログラムからロードする方法に変えることにより余計な手間をなくせる。

JavaScript プログラムから外部ファイル中にある JavaScript オブジェクトを読み取る方法はいくつかある。ここでは leaflet.js と組み合わせる事を前提として作られた leaflet-omnivore ライブラリを利用する例を示す。

leaflet-omnivore の準備

https://github.com/mapbox/leaflet-omnivore/ を開く。 leaflet-omnivore.jsleaflet-omnivore.min.js が見える。もし、一覧になければ「Tag」プルダウンメニューを開き Tags 一覧から最新版のひとつ前(例としてv0.3.3)を選ぶと出てくる。 どちらを利用してもよいがここでは圧縮版の leaflet-omnivore.min.js を利用する。ファイル名をクリックすると 内容が見えるので [Raw] ボタンを押し、得られた内容をブラウザの機能で保存する。 これを、leaflet.js と同じディレクトリに配置する。

leaflet-omnivore の利用

先ほど作成した triangle-umap.htmltriangle-umap.js の組み合わせでは、HTML 側で

<script type="text/javascript"
 src="../src/triangle-umap.geojson" charset="utf-8"></script>

とし、プログラム化したファイルをロードして GeoJSON 値をグローバル変数 triangleUmapに代入させ、js ファイル側でその変数を直接利用していた。leaflet-omnivore を利用する場合は、HTML 側で leaflet-omnivore.min.js を読み込むだけにして、プログラムで GeoJSON 値のみを記したファイルをロードする形式に改める。

triangle-load.html

<!DOCTYPE html>
<html lang="ja">
<head>
<title>uMap GeoJSON</title>
<meta name="viewport" content="width=device-width, initial-scale=1" />
<link rel="stylesheet" href="../src/leaflet.css" />
<script src="../src/leaflet.js"></script>
<script src="../src/leaflet-omnivore.min.js"></script>
<style type="text/css">
<!--
div#load-json {width: 90vw; height: 80vh; margin: 0 auto;}
-->
</style>
</head>
<body>
<h1>Triangle GeoJSON by uMap</h1>
<div id="umap-json"></div>
<script type="text/javascript"
 src="triangle-load.js" charset="utf-8"></script>
</body>
</html>

triangle-load.js

(() => {
// 例: GeoJSONファイルを leaflet-omnivore でロードする
var jsonmap = L.map("load-json").setView([38.891, 139.824], 16);
var layer = L.tileLayer(
    'https://cyberjapandata.gsi.go.jp/xyz/std/{z}/{x}/{y}.png',
    {attribution:
     '&copy; <a href="//www.gsi.go.jp/kikakuchousei/kikakuchousei40182.html">国土地理院</a>'
    }).addTo(jsonmap);

var customLayer = L.geoJson(null, {	// omnivoreに引き渡すGeoJSONレイヤ
    onEachFeature: function(f, layer) {	// このブロックは
	let p = f.properties;		// triangle-umap.js と同じ
	if (p) {			// 
	    let name = p.name, desc = p.description;
	    let popup = "<h3>" + name + "</h3>" + "<p>" + (desc||"") + "</p>";
	    layer.bindPopup(popup);	// 
	}
    }
});

// geojson外部ファイルの読み込みは次の行
var gjl = omnivore.geojson("triangle-load.geojson", null, customLayer);
// ↑引数は順に: ファイル, 解析オプション, カスタムレイヤ
gjl.on("ready", function() {	// 'ready' イベントに読み終わったときの処理
    jsonmap.fitBounds(gjl.getBounds());	// 読み取り失敗時は 'error' イベント
});
gjl.addTo(jsonmap);		// マップに足す
L.control.layers(null, {"Triangle": gjl}).addTo(jsonmap);
})();

leaflet-omnivore では、ファイルからロードした GeoJSON レイヤのオブジェクトがファイルからの値読み込みを完了したときに ready イベントを発行する。これを捕捉する関数を登録しておくことで、 マップオブジェクトが表示の中心となるような処理が可能となる。 これを行なっているのが gjl.on('ready', ...) の部分である。

triangle-load.geojson, triangle-load.html

GPX/KMLデータファイルのロード

leaflet-omnivore を利用すると GeoJSON 以外の主要な地理データファイルも読み込むことができる。

たとえばGPSロガーでよく用いられる形式である GPX、 Google Mapで利用されている KML ファイルはそれぞれ以下のようにLeafletレイヤに取り込める。

omnivore.gpx(GPXファイル).addTo(map);
omnivore.kml(KMLファイル).addTo(map);

GeoJSON, KML, GPX 3種のファイルを同一マップ上にレイヤ表示する 例を示す。

mix-load.html (mix-load.js)

練習問題

以下の手順により、マップ上に存在するいくつかのオブジェクトを GeoJSON ファイルとして作成し、それらを表示する Web ページを作成せよ。大まかな手順は以下のとおりである。

  1. uMap を用いて「マップを作成」で新規の地図を開き、 ポイント、ライン、ポリゴンをそれぞれ任意の地点に配置する。
  2. 「共有」ボタンで「データダウンロード」画面を開き、 ダウンロード形式「GeoJSON」でローカルディレクトリに保存する。
  3. 表示地図の基盤となるHTMLを作成する。以下のテンプレート leaflet-omnivore-template.html を利用してよい。
  4. 上記 HTML ファイルから読み出され、同じディレクトリに置いた GeoJSON ファイルを読み込んでpopUpを結び付けるプログラムを作成する。

leaflet-omnivore 利用のテンプレート leaflet-omnivore-template.html

<!DOCTYPE html>
<html lang="ja">
<head><title>Loading GeoJSON with leaflet-omnivore</title>
<meta name="viewport" content="width=device-width, initial-scale=1" />
<link rel="stylesheet" href="../src/leaflet.css" />
<script src="../src/leaflet.js"></script>
<script src="../src/leaflet-omnivore.min.js"></script>
<style type="text/css">
<!--
div#map {width: 90vw; height: 80vh; margin: 0 auto;}
-->
</style>
</head>
<body>
<h1>例:GeoJSON ロード</h1>
<div id="map"></div>
<script type="text/javascript"
 src="[[ここをjsプログラムに変える]].js" charset="utf-8"></script>
</body>
</html>

anzen.html, anzen.geojson