プロジェクト2018「Google Mapを使わない完全フリーの地図情報プラットフォーム(Leaflet.js)を用いた地理情報処理と地理情報の共有」
アップデートしたものがプロジェクト2019ページにありますのでそちらも読んでください。
目標:地図情報を利用して、人々の安全・健康・便利の増進に関わる問題を空間的に理解し、それを共有することをめざす。
達成目標:
参考資料(書籍は各班に配布します(貸与))
予定表(under construction)
回 | テーマ | 行事 | 学習内容 | 課題 |
---|---|---|---|---|
1 | 導入とGeoJSONデータの表示 | 班分け、班長選出、背景・概要説明、注意、テキスト配布 | 地図の表示、オブジェクトの表示、GeoJSONデータの表示 | 課題1~7 |
2 | GeoJSONレイヤーへの操作 | 理解度チェック | ポップアップ、複数のGeoJSONレイヤー、レイヤーのコントロール、集計とスタイル再設定 | 課題8~16 |
3 | 作成するWebページのテーマを考える | 各班での相談 | 緯度経度と1kmメッシュ(基準地域メッシュ(第3次地域区画))との関係、集計とスタイル再設定 | 課題17~20 |
4 | Webページの制作(1) | |||
5 | Webページの制作(2) | |||
6 | Webページの制作(3) | |||
7 | まとめ | 発表会、書籍返却 |
注意:
各班最終提出物:
個人最終提出物:
Leaflet Essentialsの8~10ページにあるように、Leafletで地図を表示するには次の4つの要素が必要です。
<div>
要素をHTMLファイルに書くこと
L.map()
コンストラクタによって、map
オブジェクトを作ること
L.tyleLayer()
コンストラクタとaddTo()
メソッドによって、タイルレイヤー(tyle layer)---これはベースレイヤー(base layer)ともいいます---を生成し、map
オブジェクトに付け加えること。
Leaflet(https://leafletjs.com/)のTutorialsのLeaflet Quick Start Guideを参考にして、次のような地図を表示しましょう。
課題1:東邦大学習志野キャンパス(緯度:北緯35.6920°,経度: 東経140.0486°)を中心とする地図
L.map()
コンストラクタの2つめの引数は、オプション群を表すひとつのオブジェクトです。そのオブジェクトの中のどのプロパティを変えれば、地図の表示範囲を変えられるでしょうか?実際に試してみてください。課題2:好きな場所の緯度と経度を調べて、そこを中心とする地図
ただし、タイルレイヤーとして、そのページの例ではMapboxを使っていますが、サインアップしてアクセストークンを取得することが必要なので、この実習では不便だと感じました。そこで、代わりに、Leaflet Essentialsの最初の例が使っている、Open Street Mapを使うことにします。
Leaflet Essentialsの11頁のように、タイルレイヤーを指定してください。
L.tileLayer('http://{s}.tile.osm.org/{z}/{x}/{y}.png').addTo(map);
プログラミングのヒント(1):
これから書く、いろいろなWebページの骨格として、次のようなファイルを作り、leaflet_page_template.html
とでも名付けてUTF-8形式で(注意!)保存しておきましょう
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="utf-8"/>
<link type="text/css" rel="stylesheet" href="http://code.jquery.com/ui/1.11.2/themes/smoothness/jquery-ui.min.css">
<link rel="stylesheet" href="https://unpkg.com/leaflet@1.3.4/dist/leaflet.css"
integrity="sha512-puBpdR0798OZvTTbP4A8Ix/l+A4dHDD0DGqYW6RQ+9jxkRFclaxxQb/SJAWZfWAkuyeQUytO7+7N4QKrDh+drA=="
crossorigin=""/>
<style>
#map { width: 800px; height: 600px;}
</style>
<!-- Make sure you put this AFTER Leaflet's CSS -->
<script src="https://unpkg.com/leaflet@1.3.4/dist/leaflet.js"
integrity="sha512-nMMmRyTVoLYqjP9hrbed9S+FzjZHW5gY1TWCHA5ckwXZBadntCNs8kEqAWdrb9O7rxbCaA4lKTIWjDXZxflOcA=="
crossorigin=""></script>
<script type='text/javascript'src="https://code.jquery.com/jquery-2.1.1.min.js"></script>
<!--My program and data files -->
<!-- Program and Data
<script type='text/javascript' src="my_program.js"></script>
<script type='text/javascript' src="my_data.js"></script>
-->
</head>
<body>
<!--Map is drawn here-->
<div id="map"></div>
</body>
</html>
プログラミングのヒント(2):
例えば、課題1では、上のテンプレートの、<!-- Program and Data -->
でコメントアウトした最初の行を次のように書き換え、また、その行だけコメントアウトを外しますます。
<script type='text/javascript' src="app_day1_001.js"></script>
プログラミングのヒント(3)
課題1で、外部のJavascriptファイルapp_day1_001.js
は例えば次のように書きます。
jQuery(document.body).ready(function($){ ... });
の意味は、DOMの読み込みが終わったら実行せよ、という指示をあらわす方法の一つです。
//======================================================================
// Main function
//======================================================================
jQuery(document.body).ready(function($){
var map = L.map('map',
{
center: [35.6920, 140.0486],
zoom: 15
});
L.tileLayer('http://{s}.tile.osm.org/{z}/{x}/{y}.png').addTo(map);
// Do not forget to close the brackets
});
プログラミングのヒント(4)
開発ツール(とくにWebコンソール)を積極的に使いましょう。
同様にQuick Start Guideを参考にして、地図に次のようなオブジェクトを描きましょう。
L.marker()
コンストラクタの引数に緯度(lattitude)と経度(longitude)を配列[lat, long]
で与え、.addTo(map)
関数で地図オブジェクトmap
に加えます。GeoJSONデータを表示しましょう
GeoJSONとは、Javascript Object Notation (JSON)で地理データを記述するための形式です。
"キー":"値"
のペアを1つ以上含む、Javascriptのオブジェクト(つまり、{"キー1":"値1", "キー2":"値2", ...}
)として表現されます。Leaflet Essentialsの70頁のように、GeoJSONデータでは、地図上のオブジェクト(つまり、点、線、平面)は、features
というプロパティの値である配列に、要素として格納されます。
それぞれのオブジェクトのプロパティの中で特に重要なのは、geometry
というプロパティです。この値もまたオブジェクトですが、そのプロパティのうち、
type
はオブジェクトの種類(例えばPolygon
)を表し、coordinates
は座標群つまり緯度と経度からなる配列を一つ以上格納する配列です。GeoJSONデータを読み込むには、同じくLeaflet Essentialsの70頁にあるように、別ファイルにJavascriptの変数として宣言して、HTMLファイルの<script>
タグのsrc
属性として宣言するのが一番簡単です。
GeoJSONデータの中のオブジェクトを一括して地図上に表示するには、Leaflet Essentialsの43頁にあるように、L.geoJson()
コンストラクタとaddTo()
関数を使います。
課題6の前に(1):千葉県の医療機関のGeoJSONファイルを読んで、個々のfeatureにどのような情報が入っているか、調べてください。具体的な病院(例えば済生会習志野病院)に注目して、どのような情報が得られるか試してください。国土数値情報ダウンロードページのデータ詳細で個々のfeatureのpropertiesに含まれるプロパティの定義を確認してください。
課題6:千葉県の病院リストをダウンロードして、表示してみましょう。
<script src="chiba_med_providers.js"></script>
で十分です。これをHTMLファイルの<head>要素に書き足しましょう。
med_providers
)を使いますから、名前を間違えないようにしましょう。
var GeoJSONLayer_1 = L.geoJson(med_providers).addTo(map);
課題7:病院だけを表示しましょう。
features
プロパティの、properties
プロパティの、何というプロパティでしょうか?
L.geoJson()
コンストラクタのfilter
オプションを記述しましょう。
var GeoJSONLayer_1 = L.geoJson(med_providers,{
filter:function(feature){
switch (feature.properties.P04_001) {
//Write scripts here.
}
}
}).addTo(map);
まず、前回の課題7の解答例です。
HTMLファイル(ファイル名:leaflet_day1_007.htmlに関しては、
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="utf-8"/>
<link type="text/css" rel="stylesheet" href="http://code.jquery.com/ui/1.11.2/themes/smoothness/jquery-ui.min.css">
<link rel="stylesheet" href="https://unpkg.com/leaflet@1.3.4/dist/leaflet.css"
integrity="sha512-puBpdR0798OZvTTbP4A8Ix/l+A4dHDD0DGqYW6RQ+9jxkRFclaxxQb/SJAWZfWAkuyeQUytO7+7N4QKrDh+drA=="
crossorigin=""/>
<style>
#map { width: 800px; height: 600px;}
</style>
<!-- Make sure you put this AFTER Leaflet's CSS -->
<script src="https://unpkg.com/leaflet@1.3.4/dist/leaflet.js"
integrity="sha512-nMMmRyTVoLYqjP9hrbed9S+FzjZHW5gY1TWCHA5ckwXZBadntCNs8kEqAWdrb9O7rxbCaA4lKTIWjDXZxflOcA=="
crossorigin=""></script>
<script src="https://code.jquery.com/jquery-2.1.1.min.js"></script>
<!--My program and data files -->
<script type='text/javascript' src="app_day1_007.js"></script>
<script src="chiba_med_providers.js"></script>
</head>
<body>
<!--Map is drawn here-->
<div id="map"></div>
</body>
</html>
また、Javascriptファイル(ファイル名:app_day1_007.js)に関しては、
//======================================================================
// Main function
//======================================================================
jQuery(document.body).ready(function($){
var map = L.map('map',
{
center: [35.6920, 140.0486],
zoom: 15
});
L.tileLayer('http://{s}.tile.osm.org/{z}/{x}/{y}.png').addTo(map);
var GeoJSONLayer_1 = L.geoJson(med_providers,{
filter:function(feature){
//Write scripts here.
switch (feature.properties.P04_001) {
case "1": return true;
default: return false;
}
}
}).addTo(map);
// Do not forget to close the brackets
});
今回は、GeoJSONオブジェクトのレイヤーに対して次のような操作を行います。
第1回の課題6で、GeoJSON形式の、Pointデータ群を表示しました。
これらに、病院名、医院名と診療科のポップアップ(pop up)を付けるにはどうしたらいいでしょうか?
すでに作られたオブジェクトやレイヤーにポップアップをつけるには、bindPopup()
メソッドを使います。この関数の引数として、最低、Latlng形式(緯度経度の組の配列)が必要です。
例えば、第1回の課題3について、さらにマーカーオブジェクトにポップアップを付けるには、次のようにします。
var coordinate = [35.6920, 140.0486];
...(中略)...
var marker = L.marker(coordinate).addTo(map);
marker.bindPopup("Toho University Narashino Campus");
同様にして、bindPopup()
メソッドを用いて、GeoJSONレイヤーにもポップアップを付ける(同じレイヤーに含まれるすべてのオブジェクトに同じ方法でポップアップを付ける)ことができます。
Point型のオブジェクトは、その他の型とは付け方が違います。L.geoJson()
コンストラクタがPoint型のGeoJSONオブジェクトを生成するとき、これの2つめの引数となるオプション群のうち、pointToLayer
プロパティのデフォルト値はが次の動作を行う関数です。
function(feature, latlng) {
return L.marker(latlng);
}
ここで、feature
はGeoJSONデータを構成する個々のfeature(すなわち点のデータ)を、latlng
はLatlng形式の配列を、それぞれ表しています。
ですから、このデフォルトの関数を上書きすることで、マーカーにポップアップを付けることができます。
課題8: 課題6を改造して、すべての医療機関に名称と診療科からなるポップアップをつけてください。下にHTMLから呼び出すプログラムのヒントを示します。
var latlng
と23行目の、関数の引数のlatlng
がかぶっていますが、違うものであるのが明らかであることはわかりますか?オプションのプロパティの値となる関数の引数については、何が入るか決まっているので、名前は何でもいいのです。hoge1とhoge2でもいいのです。
//======================================================================
// Program name: app_day2_001_008.js
// Description: Add a pop up to every medical provider
//======================================================================
//======================================================================
// Main function
//======================================================================
jQuery(document.body).ready(function($){
var latlng = [35.6920, 140.0486];
var map = L.map('map',
{
center: latlng,
zoom: 15
});
L.tileLayer('http://{s}.tile.osm.org/{z}/{x}/{y}.png').addTo(map);
var GeoJSONLayer_1 = L.geoJson(med_providers,{
pointToLayer: function(feature,latlng){
var name = //Name of the hospital of clinic
var spec = //Specialities
var content = //Pop up content
var x = L.marker(latlng).bindPopup(content); return x;
}
});
GeoJSONLayer_1.addTo(map);
// Do not forget to close the brackets
});
次に、行政区画の地図を重ねてみましょう。
行政区画のGeoJSONデータを用意しました(ファイル名:chiba_areas.js)。これも、国土数値情報ダウンロードサービスから行政区域データをシェープファイルとして取得し、QGISでGeoJSON形式に変換して、さらに、手作業でJavascriptの変数として宣言したものです。
HTMLファイルのヘッダー(例えば、課題7の21と22行目の間)に、この変数を呼び出す行を1行加えます。
<script type='text/javascript' src="chiba_areas.js"></script>
また、呼び出されるプログラムは、例えば次のようになります。ついでに、地図(実際は、行政区画をあらわすポリゴンオブジェクト)をクリックすると市町村名が出るようにしましよう。
ポップアップをGeoJSONレイヤーにつけるには、一般には、L.geoJson()コンストラクタを呼び出したときに、次のようにします(Leaflet Essentialsの50頁「Attaching pop ups with onEachFeature」も読んでください。)。
myPopup
と名付けましょう。
bindPopup()
メソッドを使って、レイヤーを構成するフィーチャーに対して一括してポップアップの内容を設定します。
課題9 次のプログラムで、XXXで伏せられているところを正しいコードに置き換えてプログラムを完成させてください。また、このプログラムを呼び出すHTMLファイルを作ってください。それを開くと、課題8の地図に行政区画が重ねて表示され、また、地図をクリックすると市町村名が表示されるようにしましょう。
//======================================================================
// Program name: app_day2_002_009.js
// Description: Two layers (administrative districts and medical providers)
//======================================================================
// Functions
//======================================================================
function myPopup(feature,layer){
var pref = feature.properties.XXX;//都道府県名
var county = feature.properties.XXX;//郡または政令指定都市名
var city = feature.properties.XXX;//市町村名または政令指定都市の区名
var name = "";
if (county){
//郡または政令指定都市名が定義されているとき
name = XXXXXXXXX;
}
else {
name = XXXXXXXX;
}
layer.bindPopup(name);
}
//======================================================================
// Main function
//======================================================================
jQuery(document.body).ready(function($){
var latlng = [35.6920, 140.0486];
var map = L.map('map',
{
center: latlng,
zoom: 15
});
L.tileLayer('http://{s}.tile.osm.org/{z}/{x}/{y}.png').addTo(map);
var GeoJSONLayer_1 = L.geoJson(med_providers,{
pointToLayer: function(feature,latlng){
var name = feature.properties.P04_002;
var dep = feature.properties.P04_004;
var content = name + ":<br>" + dep;
var x = L.marker(latlng).bindPopup(content); return x;
}
});
GeoJSONLayer_1.addTo(map);
var GeoJSONLayer_2 = L.geoJson(chiba_areas,{
onEachFeature: myPopup
});
GeoJSONLayer_2.addTo(map);
// Do not forget to close the brackets
});
次は、Leaflet.jsが提供するLayers Control機能を利用して、レイヤーの切り替えボタンを地図の中に作りましょう。
病院と行政区画(すなわち、課題9のコードのGeoJSONLayer_1とGeoJSONLayer_2)を切り替えるコントロールを作りましょう。
そのために、まず、これらのレイヤーを別々にaddTo(map)するのはやめます。次に、切り替えるべきレイヤーの集合をJavascriptオブジェクトとして宣言して、L.control.layers()コンストラクタでコントロールを生成します。
コードの要点は次のようになるでしょう。
var GeoJSONLayer_1 = L.geoJson(med_providers,{
pointToLayer: function(feature,latlng){
...
}
});
var GeoJSONLayer_2 = L.geoJson(chiba_areas,{
onEachFeature: myPopup
});
var overlays = {
"Administrative districts": GeoJSONLayer_2,
"Hospitals and clinics": GeoJSONLayer_1
}
L.control.layers(overlays).addTo(map);
課題10 上記を参考にして、病院と行政区画を切り替えるコントロールを地図の中に作ってください。
次に、これと同じことを、mapオブジェクトの外で、Javascriptのボタンを作って実現してみましょう。
病医院の数が非常に多くて表示に時間がかかるので、行政区画を常時表示して、必要に応じて病医院を表示することにします。
jQueryを使えば簡単にできます。
まず、病医院を表示するボタン、消すボタンを作りましょう。それぞれ、addTo(map), map.removeLayer(layer)を実行します。
HTMLファイルでは、次のbutton要素を加えましょう。
<button id="on_button">Show markers</button>
<button id="off_button">Clear markers</button><br>
プログラムファイルの要点は例えば次のようになります。
$("#on_button").on('click', function(){GeoJSONLayer_1.addTo(map);});
$("#off_button").on('click', function(){map.removeLayer(GeoJSONLayer_1);});
課題11 病医院を表示するボタン、消すボタンを地図の外に作って、動作させてください。
病院だけ表示する機能がやはりほしいと思いませんか?そこで、ラジオボタンで「病院だけ、病医院、どちらも表示しない」を選択できるようにします。
HTMLによるラジオボタンの表現は、例えば次のようになるでしょう。
<div id="selector-hosp">
<input type="radio" name="show_hosp" id="select_hosp_1" value="1"><label for="select_hosp_1">Only hospitals</label>
<input type="radio" name="show_hosp" id="select_hosp_2" value="2"><label for="select_hosp_2">Hospitals and clinics</label>
<input type="radio" name="show_hosp" id="select_hosp_3" value="3" checked><label for="select_hosp_3">Show no markers</label>
</div>
これらがクリックされたときの反応は例えば次のように表現できるでしょう。
$("#select_hosp_1").on('click', function(){
map.removeLayer(GeoJSONLayer_1);
GeoJSONLayer_3.addTo(map);
});
$("#select_hosp_2").on('click', function(){
map.removeLayer(GeoJSONLayer_3);
GeoJSONLayer_1.addTo(map);
});
$("#select_hosp_3").on('click', function(){
map.removeLayer(GeoJSONLayer_1);
map.removeLayer(GeoJSONLayer_3);
});
なお、GeoJSONLayer_3の作り方の例は次のとおりです。
var GeoJSONLayer_3 = L.geoJson(med_providers,{
pointToLayer: function(feature,latlng){
var name = feature.properties.P04_002;
var dep = feature.properties.P04_004;
var content = name + ":<br>" + dep;
var x = L.marker(latlng).bindPopup(content); return x;
},
filter:function(feature){
switch (feature.properties.P04_001) {
case "1": return true;
default: return false;
}
}
});
課題12 地図の外に、病院だけ、病医院、どちらも表示しない、を選択できるラジオボタンを作って、動作させてください。
次は、地図の色を、病院または病医院の数に応じて塗り分けることを考えます。
ここで問題なのは、病医院のGeoJSONファイルと行政区域ファイルとを結びつけるキーが存在しないことです。これらのもととなった、国土数値情報ダウンロードサイトのデータ定義を確認してください。
そこで、課題12のプログラムで行政区域ファイルを読み込んだときのonEachFeatureプロパティに対する関数myPopupを改造して、自治体コードと、市町村名との対応表を作ります。
myPopupは例えば次のようになるでしょう。16~18行目で、いわゆる連想配列を使っていることに注意してください。実際には配列ではなくて、オブジェクト(つまり、キーと値との対応関係)データとなっています。(このページのAssociative Arraysの項を読んでください)
var code2city = [];
function myPopup(feature,layer){
var pref = feature.properties.N03_001;
var county = feature.properties.N03_003;
var city = feature.properties.N03_004;
var div_code = feature.properties.N03_007;
var name = "";
if (county){
name = pref+county+city;
}
else {
name = pref+city;
}
// Make a code to city table
if (!code2city[div_code]){
code2city[div_code] = name;
}
layer.bindPopup(name+":"+div_code);
}
課題13 課題12で、地図で行政区域をクリックしたときのポップアップに、名称と自治体コードを表示させてください。16~18行目の意味を説明してください。また、FirefoxのWebコンソールで、連想配列が動作していることを確認してください。
次に、病医院を読み込みマーカーのレイヤーを形成するとき、自治体ごとに数を数えます。
前に説明したように、病医院データには自治体のコードがついていないことが問題です。そのため、住所からコードを取得します。
病医院のGeoJSONレイヤーを作るとき、住所を読み込み、課題13で作った連想配列で、値が住所の先頭から完全にマッチするものを探します。
そのための関数は例えば次のようになります。
console.log()
関数が残っていますが、デバッグのために積極的に使ってほしいことを伝えたかったので、あえて残しました。
function search_code(name){
for (var code in code2city){
var city = code2city[code];
var match_result = name.indexOf(city);
//console.log(match_result + ":" + city + ":" + name);
if (match_result != -1){
return code;
}
}
}
これを使うときは、次のようにします。
var address = feature.properties.P04_003;//病医院の住所
var code = search_code(address);
マッチしたら、そのコードに対してカウンターを1つ加算します。そのために連想配列を使います。
if (!code2count_1[code]){
code2count_1[code] = 1;
}
else {
code2count_1[code]++;
}
病院だけのレイヤーを作るときのコードの例を示します。
var GeoJSONLayer_3 = L.geoJson(med_providers,{
pointToLayer: function(feature,latlng){
var name = feature.properties.P04_002;
var dep = feature.properties.P04_004;
var content = name + ":<br>" + dep;
var x = L.marker(latlng).bindPopup(content);
//医療機関データから住所を取得。住所からから自治体コードを取得
var address = feature.properties.P04_003;
var code = search_code(address);
//自治体コードごとにマーカーを集計
if (!code2count_2[code]){
code2count_2[code] = 1;
}
else {
code2count_2[code]++;
}
return x;
},
filter:function(feature){
switch (feature.properties.P04_001) {
case "1": return true;
default: return false;
}
}
});
次の問題は、行政区域のポップアップにカウンター情報を反映させる方法です。
ラジオボタンがクリックされるごとに、行政区域レイヤーのポップアップをbindPopup()メソッドでアップデートします。
$("#select_hosp_1").on('click', function(){
code2count = code2count_2;//病院だけの集計
map.removeLayer(GeoJSONLayer_1);//病医院レイヤーを削除
GeoJSONLayer_3.addTo(map);
GeoJSONLayer_2.bindPopup(newPopup);//行政区域レイヤーのポップアップを更新
});
そのために、bindPopup(newPopup)
として呼び出される関数newPoup(layer)は例えば次のようになります。
function newPopup(layer){
var pref = layer.feature.properties.N03_001;
var county = layer.feature.properties.N03_003;
var city = layer.feature.properties.N03_004;
var div_code = layer.feature.properties.N03_007;
var name = "";
if (county){
name = pref+county+city;
}
else {
name = pref+city;
}
// Get the number of hospitals or clinics
var num_hosp = code2count[div_code];
if (!num_hosp){
num_hosp = 0;
}
var content = name+":"+div_code+":"+num_hosp;
return content;
}
課題14
クリックイベントと起動される関数等との関係は、例えば次のようになります。
//病院だけのレイヤーを表示
$("#select_hosp_1").on('click', function(){
code2count = code2count_2;
map.removeLayer(GeoJSONLayer_1);
GeoJSONLayer_3.addTo(map);
GeoJSONLayer_2.bindPopup(newPopup);
});
//病医院のレイヤーを表示
$("#select_hosp_2").on('click', function(){
code2count = code2count_1;
map.removeLayer(GeoJSONLayer_3);
GeoJSONLayer_1.addTo(map);
GeoJSONLayer_2.bindPopup(newPopup);
});
//すべてのレイヤーを消去、行政区域レイヤーを初期化
$("#select_hosp_3").on('click', function(){
code2count = [];
map.removeLayer(GeoJSONLayer_1);
map.removeLayer(GeoJSONLayer_3);
GeoJSONLayer_2.bindPopup(newPopup);
});
//マーカーだけ消去、行政区域レイヤーはそのまま
$("#off_button").on('click', function(){
map.removeLayer(GeoJSONLayer_1);
map.removeLayer(GeoJSONLayer_3);
});
最後に、地図の色分けをします。
ラジオボタンがクリックされたときに、GeoJSONレイヤーに対するsetStyle()
メソッドがレイヤーのスタイルをアップデートします。
このメソッドは、bindPopup()メソッドと同様に、レイヤーに対してチェイン(連結)させて使うことができます。
例えば、次のように使います。
$("#select_hosp_1").on('click', function(){
code2count = code2count_2;
map.removeLayer(GeoJSONLayer_1);
GeoJSONLayer_3.addTo(map);
GeoJSONLayer_2.bindPopup(newPopup).setStyle(newStyle_1);
});
このメソッドは、レイヤーのオプションオブジェクトのうちstyleプロパティを操作します。styleプロパティの値は、GeoJSONフィーチャーを引数としてスタイルオプションオブジェクトを返す関数で、デフォルトでは、空白のオプションオブジェクトを返すだけです(デフォルトのスタイルが適用されます)。
このメソッドの引数となる、スタイルオプションオブジェクトを返す関数は例えば次のようになります。
function newStyle_1 (feature){
var area_code = feature.properties.N03_007;
var count = code2count[area_code];
return {
fillColor: color(count),
weight: 1,
opacity: 1,
color: 'white',
fillOpacity: 0.5
};
}
下に示すcolor()関数のダイナミックレンジに合わせるために、このようなスタイル設定関数を、病院だけのときと、病医院のときと、別々に用意したほうがいいでしょう。どうしたらいいですか?
同様に、病院も病医院のレイヤーも選ばないラジオボタンをクリックしたときに、もとの色に戻す必要があります。
color()関数の1例を示します。値と色の対応表については、Leaflet Essenntialsの71頁「Setting the color with a function」を読んでください。
function color(x){
return x > 10 ? '#990000' :
x > 8 ? '#d7301f' :
x > 6 ? '#ef6548' :
x > 4 ? '#fc8d59' :
x > 2 ? '#fdbb84' :
x > 1 ? '#fdd49e' :
x > 0 ? '#fee8c8' : '#fff7ec';
}
課題15 表示するマーカーの数に応じて、行政区域を塗り分けてください。
多数のマーカーを表示するときに高速化するためには、
map
オブジェクトを生成するときのオプションに、preferCanvas: true
を追加する。
pointToLayer
プロパティの値である関数の中で、マーカーではなく、サークルマーカー(Circle marker)を生成する。すなわち、
var x = L.circleMarker(latlng,{radius: 5, color: '#3388ff'}).bindPopup(content);
のようにする。
L.circleMarker
の2番めの引数のオブジェクトの中で、radiusプロパティを適切な多きさ(デフォルトは半径10ピクセル)に設定する。課題16 課題15に対して、この方法で表示の高速化を試みてください。
千葉県の2010年人口と予測人口メッシュデータを表示するのが目的です。
このデータの説明は、国土数値情報ダウンロードサービスを読んでください。
課題17 上のメッシュデータがどのように配置されているか、表示してみてください。
そのためには、Leaflet.js 地図表示HTMLテンプレートとJavascriptテンプレートをダウンロード(右クリックでダウンロード)して、それぞれからHTMLファイルとJavascriptファイルを作ります。
HTMLファイルでは、上のメッシュデータと、javascriptプログラムファイルを<script>
タグで指示してください。
Javascriptについては、最低限次のように書き足します。
var meshLayer = L.geoJson(chiba_base_mesh, {});
meshLayer.addTo(map);
HTMLファイルを開くと、メッシュの境界が表示され、メッシュの中は、デフォルトの色(fill color)と不透明度(fill opacity)で塗られているのがわかるでしょう。
課題18 病院だけ、または、病医院のマーカーを表示し、さらに、メッシュごとにマーカーの数を数え、数に応じてメッシュの色を変えてください。
まず、HTMLのdiv
要素は次のようになるでしょう。
プログラムは、次のような部分に分けて作成できるでしょう。
preferCanvas
プロパティの値をtrueにしておきます。
L.geoJson(GeoJSONオブジェクト, オプションオブジェクト)
でレイヤーを作るところは、これまでの課題で何回も書いてきたので慣れたと思いますが、念のために例を示します。
//人口レイヤー(メッシュ)
var popLayer = L.geoJson(chiba_base_mesh, {
onEachFeature: meshPopup
});
popLayer.addTo(map);
//すべての医療機関のレイヤー
//myPoint
はポップアップをつくる、2つのマーカーレイヤーに共通の関数、count_1
は数を数える、このレイヤーだけの関数です。
var medLayer = L.geoJson(med_providers,{
pointToLayer: myPoint,
onEachFeature: count_1
});
//病院だけのレイヤー
//count_2
は数を数える、このレイヤーだけの関数です。
var hospLayer = L.geoJson(med_providers,{
pointToLayer: myPoint,
onEachFeature: count_2,
filter: onlyHosp
});
これらのオプションのプロパティの値が、関数になっていることに注意してください。それらの関数の作成例を示します。
まず、集計用連想配列の初期化と、サークルマーカーのオプションの設定です。
//var code2city = []; //今回、市町村名を表示しないので使いません
var code2count_all = []; //メッシュごとの病医院の数
var code2count_hosp = [];//メッシュごとの病院の数
var code2count = [];//メッシュに表示するマーカー数。上のどちらかです。
var circleMarkerOptions = {
radius: 5,
color: '#ff0000'
};
次に、レイヤー表示時の動作です。
//最初にメッシュを表示するときのポップアップの設定
//メッシュのポップアップでは、メッシュIDと自治体IDを表示します。
function meshPopup(feature,layer){
var mesh_id = feature.properties.MESH_ID;
var city_code = feature.properties.CITY_CODE;
var content = mesh_id+":"+city_code;
layer.bindPopup(content);
};
//ラジオボタンでマーカーレイヤーを選択したとき、メッシュレイヤーへのbindPopup
メソッドの引数となる、ポップアップ内容の設定。マーカー数が加わります。
function newPopup(layer){
var mesh_id = layer.feature.properties.MESH_ID;
var city_code = layer.feature.properties.CITY_CODE;
var count = code2count[mesh_id];
var content = mesh_id+":"+city_code + ":" + count;
return content;
};
//マーカーレイヤーを作成した時のポップアップの設定。オプションオブジェクトの、pointToLayerプロパティの値となります。
//マーカーのポップアップでは名称、診療科、メッシュIDを表示します
function myPoint(feature,latlng){
var name = feature.properties.P04_002;
var dep = feature.properties.P04_004;
var type = feature.properties.P04_001;
var mesh_id = getMeshId(latlng);
//console.log(latlng);
var content = name + ":<br>" + dep + ":<br>" + mesh_id;
x = L.circleMarker(latlng,circleMarkerOptions).bindPopup(content);
return x;
};
//病院だけのマーカーレイヤーを作るとき、オプションオブジェクトの、filterプロパティの値となる、病院だけを選ぶ関数。
function onlyHosp(feature){
switch (feature.properties.P04_001) {
case "1": return true;
default: return false;
}
};
メッシュのスタイルを設定する関数は次のとおりです。いずれも、レイヤーのsetStyle
メソッドの引数となる、スタイルオブジェクトを返します。
//病院だけのレイヤーを選んだときの、メッシュのスタイルの設定。
function newStyle_1 (feature){
var mesh_id = feature.properties.MESH_ID;
var count = code2count_hosp[mesh_id];
return {
fillColor: color(count),
weight: 1,
opacity: 1,
color: 'white',
fillOpacity: 0.5
};
}
//病医院のレイヤーを選んだときの、メッシュのスタイルの設定。
//あとのcolor
関数のダイナミックレンジに対応するために、数を10で割っています。
function newStyle_2 (feature){
var mesh_id = feature.properties.MESH_ID;
var count = code2count_all[mesh_id];
count = count/10;
return {
fillColor: color(count),
weight: 1,
opacity: 1,
color: 'white',
fillOpacity: 0.5
};
}
//レイヤーを選ばないときに、デフォルトの設定に戻します。
function orgStyle(feature){
return {fillColor:'#3388ff'
};
}
なお、color()
関数は次のとおりです
function color(x){
return x > 10 ? '#990000' :
x > 8 ? '#d7301f' :
x > 6 ? '#ef6548' :
x > 4 ? '#fc8d59' :
x > 2 ? '#fdbb84' :
x > 1 ? '#fdd49e' :
x > 0 ? '#fee8c8' : '#fff7ec';
}
色のセットを決めるために、Color Brewer (http://http://colorbrewer2.org)などを参考にするといいでしょう(Leaflet.js Essentials 71頁等参照)。
メッシュごとにマーカーの数を集計する関数は次のとおりです。
これらは、onEachFeatureプロパティから呼び出されることに気をつけてください。この場合、latlng
オブジェクトをもらうことができません。ですから、各フィーチャーのデータ構造を読み取って、座標を得て、L.latLng
コンストラクタでlatlng
オブジェクトを作る必要があります。
GeoJSONデータでは、座標(coordinates)は[経度, 緯度]の順になっていることに気づきましたか?
//病医院の集計
function count_1(feature,layer){
var lat_lng = feature.geometry.coordinates;
var latlng = L.latLng(lat_lng[1], lat_lng[0]);
var mesh_id = getMeshId(latlng);
if (!code2count_all[mesh_id]){
code2count_all[mesh_id] = 1;
}
else {
code2count_all[mesh_id]++;
}
};
//病院の集計
function count_2(feature,layer){
var lat_lng = feature.geometry.coordinates;
var latlng = L.latLng(lat_lng[1], lat_lng[0]);
var mesh_id = getMeshId(latlng);
if (!code2count_hosp[mesh_id]){
code2count_hosp[mesh_id] = 1;
}
else {
code2count_hosp[mesh_id]++;
}
};
さて、緯度と経度からメッシュIDを作るgetMeshId(latlng)
関数はどうなるでしょうか。
これは、上の国土数値情報ダウンロードページの中にある「基準地域メッシュ(第3次地域区画」の説明の「緯度・経度から地域メッシュ・コードを算出する方法 」に基づいています。
そこでは次のような説明がなされています。
地域メッシュ・コードの算出式 ※緯度・経度は10進数に変換して算出します。 ・緯度×60分÷40分=p 余りa ・ a÷5分=q 余りb ・ b×60秒÷30秒=r 余りc ・ c÷15秒=s 余りd ・ d÷7.5秒=t 余りe ・経度-100度=u 余りf ・ f×60分÷7分30秒=v 余りg ・ g×60秒÷45秒=w 余りh ・ h÷22.5秒=x 余りi ・ i÷11.25秒=y 余りj ・ (s×2)+(x+1)=m ・ (t×2)+(y+1)=n 基準地域メッシュ・コードは pu qv rw の順に組み合わせたもの 2分の1地域メッシュ・コードは pu qv rw m の順に組み合わせたもの 4分の1地域メッシュ・コードは pu qv rw m n の順に組み合わせたもの
そこで、例えば次のように書けます。
function getMeshId(latlng){
var lat = latlng.lat;
var lng = latlng.lng;
var first = Math.floor(lat*60/40);
var first_2 = Math.floor(lng - 100);
var first_2_rest = lng - 100 - first_2;
var first_rest = lat*60%40;
var second = Math.floor(first_rest/5);
var second_2 = Math.floor(first_2_rest*60/7.5);
var second_rest = first_rest%5;
var second_2_rest = (first_2_rest*60)%7.5;
var third = Math.floor(second_rest*2);
var third_2 = Math.floor(second_2_rest*60/45);
var mesh_id = first.toString()+first_2.toString()+second.toString()+second_2.toString()+third.toString()+third_2.toString();
return mesh_id;
};
これらの関数の呼び出し方ですが、例えば次のように書けるでしょう。
//病院だけラジオボタンをクリックしたとき
$("#only_hosp").on('click',function(){
code2count = code2count_hosp;
map.removeLayer(medLayer);
hospLayer.addTo(map);
popLayer.bindPopup(newPopup).setStyle(newStyle_1);
});
//病医院ラジオボタンをクリックしたとき
$("#med_all").on('click',function(){
code2count = code2count_all;
map.removeLayer(hospLayer);
medLayer.addTo(map);
popLayer.bindPopup(newPopup).setStyle(newStyle_2);
});
//マーカーレイヤーを除去してメッシュの表示を初期状態に戻すラジオボタンをクリックしたとき
$("#no_med").on('click',function(){
code2count = [];
map.removeLayer(medLayer);
map.removeLayer(hospLayer);
popLayer.bindPopup(newPopup).setStyle(orgStyle);
});
//マーカーを非表示にするボタンをクリックしたとき
$("#off_button").on('click', function(){
map.removeLayer(medLayer);
map.removeLayer(hospLayer);
});
コーディング例(ファイル名:leaflet_day3_002_018.html)
国土情報ウェブマッピングシステム(http://nrb-www.mlit.go.jp/WebGIS/)にアクセスして、説明を読んでください。
そして「使い方」をクリックして、使い方を読んで理解してください。
戻って、「国土情報ウェブマッピングシステムへ」をクリックして、ウェブマッピングシステムを表示します。
画面の左にはレイヤーツリーが表示され、右の画面に表示するレイヤーを選択することができます。
画面の右を、左上に表示されたバーを使って、または画面のスワイプで拡大すると、都道府県境の他に、高速道路、鉄道、道路が表示されているように見えます。
表示されているデータを、右の画面上方の「データ一覧」で確認できます。
左の画面のレイヤーツリーで、これらのデータのレイヤーを減らしたり、足したりすることができます。
どのデータを表示するとどのような意思決定に有効かを考えながら、表示するデータの組み合わせを考えてください。
課題19 国土情報ウェブマッピングシステムを使って、どのようなデータを表示したらいいか、考えてください。各自が考えた後、グループ内で、どのようなデータでどのような判断・計画ができそうか、話し合ってください。
課題19に基づき、班として取り組むテーマのために適していると考えたデータを表示するWebサイトを作ってください。
どのような準備ができたか、ホワイトボードなどを使って全体で共有しましょう。
GeoJSON変換済データの例
千葉県内の国・県の施設(national_prefectural_places_chiba.geojson)
千葉県内の郵便局(chiba_post_office.geojson)
東京都の集客施設(tokyo_gathering_facilities.geojson)
千葉県の集客施設(places_people_gather_chiba.geojson)
東京都の学校(tokyo_schools.geojson)
千葉県の福祉施設>(welfare_chiba_2015.gegojson)
全国の駅(N02-17_Station.geojson)
千葉県の河川(線データ)(chiba_river_stream.geojson)
千葉県の河川(分岐点のデータ)(chiba_river_node.geojson)
千葉県の洪水想定域(chiba_flood.geojson)
千葉県の学校(chiba_schools.geojson)
千葉県のガソリンスタンド(chiba_gas_station.geojson)
千葉県のバス停(chiba_bus_stop.geojson)
千葉県の土地利用(北東)(平成26年)(1次メッシュID:5340)(chiba_land_usage_5340.geojson)
千葉県の土地利用(北東)(平成18年)(1次メッシュID:5340)(chiba_land_usage_5340_2006.geojson)
千葉県の土地利用(中~南)(平成26年)(1次メッシュID:5240)(chiba_land_usage_5240_2014.geojson)
千葉県の土地利用(中~南)(平成18年)(1次メッシュID:5240)(chiba_land_usage_5240_2006.geojson)