プロジェクト2018「Google Mapを使わない完全フリーの地図情報プラットフォーム(Leaflet.js)を用いた地理情報処理と地理情報の共有」

アップデートしたものがプロジェクト2019ページにありますのでそちらも読んでください。

目標:地図情報を利用して、人々の安全・健康・便利の増進に関わる問題を空間的に理解し、それを共有することをめざす。

達成目標:

参考資料(書籍は各班に配布します(貸与))

  1. Leaflet.js Essentials:基本的なテキストです。2014年の出版ですが既に古くなっているところ、細かなタイプミスがあるので、必ずLeafletのWebサイトとAPIレファレンスで確認します。
  2. Leaflet.jsのWebサイト:Tutorialがたいへん参考になります。
  3. Leaflet API reference (ver. 1.3.4):Leafletによるプログラミングをする際に参考にします。
  4. これからWebをはじめる人のHTML&CSS、JavaScriptのきほんのきほん
  5. JavaScript逆引きレシピ jQuery対応
  6. オープンデータ-QGIS-統計・防災・環境情報がひと目でわかる地図の作り方:地図データがどのように使われているかの事例学習のためです。
  7. Interface(インターフェース) 2017年10月号(特集:IoTのための地図・地形・地球大集合)

予定表(under construction)

テーマ行事学習内容課題
1導入とGeoJSONデータの表示班分け、班長選出、背景・概要説明、注意、テキスト配布地図の表示、オブジェクトの表示、GeoJSONデータの表示課題1~7
2GeoJSONレイヤーへの操作理解度チェックポップアップ、複数のGeoJSONレイヤー、レイヤーのコントロール、集計とスタイル再設定課題8~16
3作成するWebページのテーマを考える各班での相談緯度経度と1kmメッシュ(基準地域メッシュ(第3次地域区画))との関係、集計とスタイル再設定課題17~20
4Webページの制作(1)
5Webページの制作(2)
6Webページの制作(3)
7まとめ発表会、書籍返却

注意:

各班最終提出物:

  1. プレゼンテーション
  2. Firefoxで動作するWebサイト

個人最終提出物:

  1. A4レポート用紙1枚以上(表紙不要)⇒電子メールに添付して送付

第1回: 地図の表示からGeoJSONデータの表示まで

地図の表示

Leaflet Essentialsの8~10ページにあるように、Leafletで地図を表示するには次の4つの要素が必要です。

  1. LeafletのJavascriptライブラリの場所への参照を示すこと。
  2. 地図を載せるための<div>要素をHTMLファイルに書くこと
  3. L.map()コンストラクタによって、mapオブジェクトを作ること
  4. L.tyleLayer()コンストラクタとaddTo()メソッドによって、タイルレイヤー(tyle layer)---これはベースレイヤー(base layer)ともいいます---を生成し、mapオブジェクトに付け加えること。

Leaflet(https://leafletjs.com/)のTutorialsのLeaflet Quick Start Guideを参考にして、次のような地図を表示しましょう。

課題1:東邦大学習志野キャンパス(緯度:北緯35.6920°,経度: 東経140.0486°)を中心とする地図

課題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は例えば次のように書きます。


//======================================================================
// 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コンソール)を積極的に使いましょう。

課題1の解答例

オブジェクトの表示

同様にQuick Start Guideを参考にして、地図に次のようなオブジェクトを描きましょう。

  1. 課題3:東邦大学習志野キャンパスに立てたマーカー: Leaflet Essentialsの19頁のように、
    1. L.marker()コンストラクタの引数に緯度(lattitude)と経度(longitude)を配列[lat, long]で与え、
    2. .addTo(map)関数で地図オブジェクトmapに加えます。
  2. 課題4:東邦大学習志野キャンパスを中心とする半径500mの円: Leaflet Essentialsの24頁のように、で円を描けます
  3. 課題5:東邦大学習志野キャンパス、JR津田沼駅、JR新習志野駅を頂点とするポリゴン:Leaflet Essentialsの22頁のように、でポリゴンを描けます。

GeoJSONデータの表示

GeoJSONデータを表示しましょう

GeoJSONとは、Javascript Object Notation (JSON)で地理データを記述するための形式です。

Leaflet Essentialsの70頁のように、GeoJSONデータでは、地図上のオブジェクト(つまり、点、線、平面)は、featuresというプロパティの値である配列に、要素として格納されます。

それぞれのオブジェクトのプロパティの中で特に重要なのは、geometryというプロパティです。この値もまたオブジェクトですが、そのプロパティのうち、

GeoJSONデータを読み込むには、同じくLeaflet Essentialsの70頁にあるように、別ファイルにJavascriptの変数として宣言して、HTMLファイルの<script>タグのsrc属性として宣言するのが一番簡単です。

GeoJSONデータの中のオブジェクトを一括して地図上に表示するには、Leaflet Essentialsの43頁にあるように、L.geoJson()コンストラクタとaddTo()関数を使います。

課題6の前に(1):千葉県の医療機関のGeoJSONファイルを読んで、個々のfeatureにどのような情報が入っているか、調べてください。具体的な病院(例えば済生会習志野病院)に注目して、どのような情報が得られるか試してください。国土数値情報ダウンロードページのデータ詳細で個々のfeatureのpropertiesに含まれるプロパティの定義を確認してください。

課題6:千葉県の病院リストをダウンロードして、表示してみましょう。

課題6解答例

課題7:病院だけを表示しましょう。

  1. データオブジェクトが病院を表しているかどうかは、どのプロパティでわかるでしょうか?国土数値情報 医療機関データの詳細を参考にしてください。
  2. Leaflet Essentialsの52頁"Displaying a subset of data with filter"を参考にして、L.geoJson()コンストラクタのfilterオプションを記述しましょう。

第2回: GeoJSONオブジェクトレイヤーへの操作

前回のまとめ

まず、前回の課題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オブジェクトのレイヤーに対して次のような操作を行います。

病院・医院名と診療科のポップアップを付ける(課題8)

第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から呼び出すプログラムのヒントを示します。


//======================================================================
// 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レイヤーを重ねる(課題9)

次に、行政区画の地図を重ねてみましょう。

行政区画の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」も読んでください。)。

課題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
});

レイヤーのコントロール(1):Leaflet.jsに組み込まれた機能(課題10)

次は、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 上記を参考にして、病院と行政区画を切り替えるコントロールを地図の中に作ってください。

レイヤーのコントロール(2):Javascriptのイベントによって(課題11,12)

次に、これと同じことを、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 地図の外に、病院だけ、病医院、どちらも表示しない、を選択できるラジオボタンを作って、動作させてください。

連想配列で対応表を作る(課題13)

次は、地図の色を、病院または病医院の数に応じて塗り分けることを考えます。

ここで問題なのは、病医院の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コンソールで、連想配列が動作していることを確認してください。

連想配列で集計する(課題14)

次に、病医院を読み込みマーカーのレイヤーを形成するとき、自治体ごとに数を数えます。

前に説明したように、病医院データには自治体のコードがついていないことが問題です。そのため、住所からコードを取得します。

病医院のGeoJSONレイヤーを作るとき、住所を読み込み、課題13で作った連想配列で、値が住所の先頭から完全にマッチするものを探します。

そのための関数は例えば次のようになります。


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()メソッドでアップデートします。

そのために、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 

  1. 課題13を修正して、次の要件を実現してください。
  2. さらに、課題11の、マーカーをすべて消すボタンを復活させてください(ただし、マーカーをすべて消しても、行政区域のポップアップに表示される数は変わらないようにしてください)。マーカーを消して、地図の広範囲を表示して、各自治体での病医院の数を比較してみてください。

クリックイベントと起動される関数等との関係は、例えば次のようになります。


//病院だけのレイヤーを表示
$("#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);

});

レイヤー間の関係をポリゴンレイヤーの塗り分けに反映させる(課題15)

最後に、地図の色分けをします。

ラジオボタンがクリックされたときに、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 表示するマーカーの数に応じて、行政区域を塗り分けてください。

表示を高速化する(課題16)

多数のマーカーを表示するときに高速化するためには、

  1. mapオブジェクトを生成するときのオプションに、preferCanvas: trueを追加する。
  2. L.geoJson()コンストラクタの2番めの引数であるオプションオブジェクトにおいて、pointToLayerプロパティの値である関数の中で、マーカーではなく、サークルマーカー(Circle marker)を生成する。すなわち、 var x = L.circleMarker(latlng,{radius: 5, color: '#3388ff'}).bindPopup(content);のようにする。

課題16 課題15に対して、この方法で表示の高速化を試みてください。

第3回 題材を探す

予測人口メッシュデータを表示する(課題 17)

千葉県の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)で塗られているのがわかるでしょう。

マーカーとメッシュを重ね、マーカーのメッシュIDを得る。メッシュごとに集計する。メッシュの中を塗る(課題18)

課題18 病院だけ、または、病医院のマーカーを表示し、さらに、メッシュごとにマーカーの数を数え、数に応じてメッシュの色を変えてください。

まず、HTMLのdiv要素は次のようになるでしょう。

  1. 地図を描画するところ
  2. ラジオボタン(病院だけ、病医院、なし)
  3. マーカーを非表示にするボタン(背景のメッシュの色は変わらない)

プログラムは、次のような部分に分けて作成できるでしょう。

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

国土情報ウェブマッピングシステムを使って、取り組むテーマを考える(課題19)

国土情報ウェブマッピングシステム(http://nrb-www.mlit.go.jp/WebGIS/)にアクセスして、説明を読んでください。

そして「使い方」をクリックして、使い方を読んで理解してください。

戻って、「国土情報ウェブマッピングシステムへ」をクリックして、ウェブマッピングシステムを表示します。

画面の左にはレイヤーツリーが表示され、右の画面に表示するレイヤーを選択することができます。

画面の右を、左上に表示されたバーを使って、または画面のスワイプで拡大すると、都道府県境の他に、高速道路、鉄道、道路が表示されているように見えます。

表示されているデータを、右の画面上方の「データ一覧」で確認できます。

左の画面のレイヤーツリーで、これらのデータのレイヤーを減らしたり、足したりすることができます。

どのデータを表示するとどのような意思決定に有効かを考えながら、表示するデータの組み合わせを考えてください。

課題19 国土情報ウェブマッピングシステムを使って、どのようなデータを表示したらいいか、考えてください。各自が考えた後、グループ内で、どのようなデータでどのような判断・計画ができそうか、話し合ってください。

国土数値情報のデータを表示するWebページを作成する(課題20)

課題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)