rch850 の上澄み

技術的な話題とか、雑談とか。タイトルを上澄みに変えました @ 2020/09/02

Leaflet で福井県の年度別市町村人口を出してみた

前回の記事

rch850.hatenablog.com

白地図を出すだけでは芸がないので、福井県の年度別市町村人口を表示してみました。表示するときは、少しデータの取得に時間がかかります。10秒も待てば終わると思います。

だいたいこんな感じです。

f:id:rch850:20190530005738p:plain

市区町村の人口を出すにあたってやったことは、大きく分けて2つです。

  • 人口に応じて市区町村の色を変える
  • 年度を切り替えられるようにする

人口データは福井県のオープンデータ「市町別・年齢別・男女別人口 」を使っています。

人口に応じて市区町村の色を変える

L.getJSON の第2引数に style 関数を渡すことで、何かの数字に応じて色を変えることができます。この関数が人口に応じた色の設定を返します。ポイントは fillColorpopulation に応じて変えているところです。

引数などの細かい条件はドキュメントに詳しく書いてあります。

let populations = []
function style(feature) {
  const mname = mcodeToNameMap[feature.properties.municipality_code];
  const population = populations.reduce((p, row) => {
    if (row[2] === mname) p += row[4]
      return p
  }, 0)
  console.log(`${mname} => ${population}`)
  return {
    weight: 2,
    dashArray: '3',
    fillColor: population > 100000 ? '#800026' :
               population > 80000  ? '#BD0026' :
               population > 60000  ? '#E31A1C' :
               population > 40000  ? '#FC4E2A' :
               population > 30000  ? '#FD8D3C' :
               population > 20000   ? '#FEB24C' :
               population > 10000   ? '#FED976' :
               '#FFEDA0'
  }
}
const g = L.geoJSON(geojson, { style }).addTo(mymap);

年度を切り替えられるようにする

人口データは2010年から2015年にかけて6年分のデータが入っているので、せっかくだから年度を切り替えて表示できるようにしました。

const yearEl = document.querySelector('#year')
rxjs.fromEvent(yearEl, 'change')
  .subscribe(() => { updateYear(Number(yearEl.value)) })

function updateYear(year) {
  google.script.run.withSuccessHandler((p) => {
    populations = p
    console.log(populations);
    g.eachLayer(l => g.resetStyle(l))
  }).getPopulations(year);
}

range input を動かしたら updateYear を呼び出して populations を更新し、地図の見た目を更新しています。地図の見た目は g.eachLayer(l => g.resetStyle(l)) で更新することができます。これで style 関数が再評価されるようです。イベント処理に RxJS を使っていますが深い意味はないです。

ここの実装については公式サンプルの Interactive Choropleth Map が参考になりました。

まとめ

これらをまとめた一連の実装です。全市区町村の geojson から、ある都道府県の市区町村だけを抽出するコードとか、市区町村の名前とコード (MstMunicipality.code) を対応付けるコードとか、本質でないコードもあるので、少し長めです。

(function () {
  let mymap = L.map('mapid').setView([35.8, 136.5], 8);
  // これらの cache-control ヘッダーでキャッシュが禁止されている。
  Promise.all([
    // 市区町村の形状データ
    fetch('https://www.itdashboard.go.jp/js/data/municipality.geojson').then(resp => resp.json()),
    // 都道府県と市区町村の対応付データ
    fetch('https://www.itdashboard.go.jp/Api/getData.json?dataset=MstMunicipality').then(resp =>resp.json())
  ]).then(([geojson, municipalities]) => {
    return extractPrefecture(geojson, municipalities, '18')
  }).then(([geojson, municipalities]) => {
    const mcodeToNameMap = municipalities.reduce((map, value) => {
      map[value.MstMunicipality.code] = value.MstMunicipality.name;
      return map;
    }, {})
    let populations = []
    function style(feature) {
      const mname = mcodeToNameMap[feature.properties.municipality_code];
      const population = populations.reduce((p, row) => {
        if (row[2] === mname) p += row[4]
          return p
      }, 0)
      console.log(`${mname} => ${population}`)
      return {
        weight: 2,
        dashArray: '3',
        fillColor: population > 100000 ? '#800026' :
            population > 80000  ? '#BD0026' :
            population > 60000  ? '#E31A1C' :
            population > 40000  ? '#FC4E2A' :
            population > 30000  ? '#FD8D3C' :
            population > 20000   ? '#FEB24C' :
            population > 10000   ? '#FED976' :
                        '#FFEDA0'
      }
    }
    const g = L.geoJSON(geojson, { style }).addTo(mymap);
    updateYear(2015);
    const yearEl = document.querySelector('#year')
    rxjs.fromEvent(yearEl, 'change')
      .subscribe(() => { updateYear(Number(yearEl.value)) })
    function updateYear(year) {
      google.script.run.withSuccessHandler((p) => {
        populations = p
        console.log(populations);
        g.eachLayer(l => g.resetStyle(l))
      }).getPopulations(year);
    }
  })
  
  function extractPrefecture(geojson, municipalities, prefectureCode) {
    // MstMunicipality: {
    //   code: string,
    //   name: string,
    //   re_code: string,
    //   re_name: string
    // }
    const extractedMunicipalities = municipalities.raw_data.filter(d => d.MstMunicipality.re_code ===prefectureCode)
    const mcodes = extractedMunicipalities.map(d => Number(d.MstMunicipality.code))
    const extractedGeojson = {
      ...geojson,
      features: geojson.features.filter(feature => mcodes.indexOf(feature.properties.municipality_code)!== -1)
    }
    return [extractedGeojson, extractedMunicipalities]
  }
})()

(6/1 追記)

これは Google Apps Script (GAS) でホスティングしている HTML で実装しました。途中で唐突に google.script.run が出てくるのはそのためです。福井県の年度別市町村人口をシートに転記して、それを GAS で作った getPopulations 関数経由で読み込んでいます。

GAS 側のコードはこれだけです。

function doGet() {
  return HtmlService.createHtmlOutputFromFile('map')
}

function getPopulations(year) {
  var sheet = SpreadsheetApp.getActiveSpreadsheet().getActiveSheet()
  var range = sheet.getRange(2, 1, sheet.getMaxRows() - 1, 7);
  var rows = range.getValues();
  var results = [];
  for (var i = 0; i < rows.length; i++) {
    if (rows[i][0] != String(year)) continue;
    results.push(rows[i]);
  }
  return results;
}