1
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

国土数値情報の将来推計人口データを地図で可視化してみる

1
Posted at

はじめに

国土数値情報では、将来推計人口データが公開されています。

そこで、将来推計人口データを地図上で可視化し、年度ごとの変化を確認できるビューアを作成してみます。

今回は首都圏の変化を確認しやすくするため、埼玉県、千葉県、東京都、神奈川県のデータを使用します。

使用するデータ

今回使用するのは、国土数値情報が公開している「1kmメッシュ別将来推計人口データ(R6国政局推計)」です。

このデータは令和2年国勢調査をもとに、2070年までの将来人口を推計したものです。

データには総人口だけでなく、男女別や年齢階級別の推計値も含まれています。

今回は総人口を使用します。

データのダウンロードはこちらです。

今回は以下の4ファイルを使用しました。

  • 埼玉県(1km_mesh_2024_11.geojson)
  • 千葉県(1km_mesh_2024_12.geojson)
  • 東京都(1km_mesh_2024_13.geojson)
  • 神奈川県(1km_mesh_2024_14.geojson)

完成イメージ

完成すると以下のようになります。

49d26c94-e657-4358-87ce-e71e949e8ee9.png

人口モードでは指定した年度の人口を表示できます。

また、2025年比モードへ切り替えることで、人口の増減傾向も確認できます。

d2100662-b9b6-4805-9891-d9085e050f8c.png

プロジェクト構成

今回の構成は以下の通りです。

mesh-population-viewer/
├─ index.html
└─ data/
   ├─ 1km_mesh_2024_11.geojson
   ├─ 1km_mesh_2024_12.geojson
   ├─ 1km_mesh_2024_13.geojson
   └─ 1km_mesh_2024_14.geojson

HTMLファイル1つで実装しています。

実装

今回はHTMLファイル1つで実装しました。

以下が全体コードです。

<!doctype html>
<html lang="ja">
  <head>
    <meta charset="utf-8" />
    <meta name="viewport" content="width=device-width, initial-scale=1" />
    <title>将来推計人口ビューア</title>
    <link rel="stylesheet" href="https://unpkg.com/leaflet@1.9.4/dist/leaflet.css" />
    <style>
      html,
      body {
        margin: 0;
        padding: 0;
        height: 100%;
        font-family: sans-serif;
      }
      #container {
        display: flex;
        flex-direction: column;
        height: 100%;
      }
      #toolbar {
        padding: 12px;
        background: #ffffff;
        border-bottom: 1px solid #cccccc;
        display: flex;
        flex-wrap: wrap;
        gap: 16px;
        align-items: center;
      }
      #map {
        flex: 1;
      }
      #yearLabel {
        font-size: 1.2rem;
        font-weight: bold;
      }
      input[type="range"] {
        width: 300px;
      }
      .legend {
        background: white;
        padding: 10px;
        line-height: 1.5;
        border-radius: 4px;
        box-shadow: 0 0 6px rgba(0, 0, 0, 0.3);
      }
      .legend i {
        display: inline-block;
        width: 18px;
        height: 18px;
        margin-right: 8px;
        opacity: 0.8;
        vertical-align: middle;
      }
    </style>
  </head>
  <body>
    <div id="container">
      <div id="toolbar">
        <div>
          表示年:
          <span id="yearLabel">2025</span>
        </div>
        <input type="range" id="yearSlider" min="2025" max="2070" step="5" value="2025" />
        <label>
          <input type="radio" name="mode" value="population" checked />
          人口
        </label>
        <label>
          <input type="radio" name="mode" value="ratio" />
          2025年比
        </label>
      </div>
      <div id="map"></div>
    </div>
    <script src="https://unpkg.com/leaflet@1.9.4/dist/leaflet.js"></script>
    <script>
      // 地図を初期化し、首都圏が収まるよう表示位置を設定する
      const map = L.map("map");
      map.setView([35.681236, 139.767125], 9);
      L.tileLayer("https://tile.openstreetmap.org/{z}/{x}/{y}.png", {
        attribution: "&copy; OpenStreetMap contributors",
      }).addTo(map);

      const yearLabel = document.getElementById("yearLabel");
      const yearSlider = document.getElementById("yearSlider");

      // 現在の表示モード("population": 人口 / "ratio": 2025年比)
      let displayMode = "population";

      // 読み込んだGeoJSONデータとLeafletレイヤーを保持する変数
      let geojsonData = null;
      let geojsonLayer = null;

      // 都道府県ごとに分かれたGeoJSONファイルのパス一覧
      const files = ["./data/1km_mesh_2024_11.geojson", "./data/1km_mesh_2024_12.geojson", "./data/1km_mesh_2024_13.geojson", "./data/1km_mesh_2024_14.geojson"];

      // GeoJSONの属性名は「PTN_西暦年」の形式(例: PTN_2025)になっている
      function getPopulation(feature, year) {
        return Number(feature.properties[`PTN_${year}`]) || 0;
      }

      // 2025年の人口を基準として、指定年の人口比率(%)を算出する
      function getRatio(feature, year) {
        const base = Number(feature.properties["PTN_2025"]) || 0;
        const current = Number(feature.properties[`PTN_${year}`]) || 0;
        if (base === 0) return 0;
        return (current / base) * 100;
      }

      // 人口の多さを赤系の色で表現する(人口が多いほど濃い赤)
      function getPopulationColor(value) {
        if (value >= 5000) return "#800026";
        if (value >= 3000) return "#BD0026";
        if (value >= 1000) return "#E31A1C";
        if (value >= 500) return "#FC4E2A";
        if (value >= 100) return "#FD8D3C";
        return "#FFEDA0";
      }

      // 人口増加を赤、ほぼ横ばいを白、人口減少を青で表現する
      function getRatioColor(value) {
        if (value >= 130) return "#b30000";
        if (value >= 120) return "#cb181d";
        if (value >= 105) return "#fc9272";
        if (value >= 95) return "#f7f7f7";
        if (value >= 80) return "#c6dbef";
        if (value >= 70) return "#6baed6";
        if (value >= 60) return "#3182bd";
        return "#08306b";
      }

      // 年度を受け取り、GeoJSONレイヤーを再描画する
      // 年度切り替えのたびに既存レイヤーを削除して新たに追加する
      function renderLayer(year) {
        if (geojsonLayer) {
          map.removeLayer(geojsonLayer);
        }
        geojsonLayer = L.geoJSON(geojsonData, {
          style: (feature) => {
            let fillColor;
            if (displayMode === "population") {
              const population = getPopulation(feature, year);
              fillColor = getPopulationColor(population);
            } else {
              const ratio = getRatio(feature, year);
              fillColor = getRatioColor(ratio);
            }
            return {
              color: "#666",
              weight: 0.3,
              fillColor,
              fillOpacity: 0.7,
            };
          },
          // メッシュクリック時に人口・増減率を表示するポップアップを設定する
          onEachFeature: (feature, layer) => {
            const population = getPopulation(feature, year);
            const ratio = getRatio(feature, year);
            const changeRate = ratio - 100;
            layer.bindPopup(`
                    <table>
                        <tr>
                            <th align="left">メッシュID</th>
                            <td>${feature.properties.MESH_ID}</td>
                        </tr>
                        <tr>
                            <th align="left">年</th>
                            <td>${year}</td>
                        </tr>
                        <tr>
                            <th align="left">人口</th>
                            <td>${population.toLocaleString()} 人</td>
                        </tr>
                        <tr>
                            <th align="left">2025年比</th>
                            <td>${ratio.toFixed(1)} %</td>
                        </tr>
                        <tr>
                            <th align="left">増減率</th>
                            <td>${changeRate > 0 ? "+" : ""}${changeRate.toFixed(1)} %</td>
                        </tr>
                    </table>
                `);
          },
        }).addTo(map);
      }

      // 凡例コントロールを地図右下に追加する
      const legend = L.control({ position: "bottomright" });
      legend.onAdd = function () {
        const div = L.DomUtil.create("div", "legend");
        updateLegend(div);
        return div;
      };
      legend.addTo(map);

      // 表示モードに応じて凡例の内容を切り替える
      function updateLegend(element) {
        if (displayMode === "population") {
          element.innerHTML = `
            <strong>人口</strong><br>
            <i style="background:#800026;border:1px solid #ccc"></i>5000以上<br>
            <i style="background:#BD0026;border:1px solid #ccc"></i>3000以上<br>
            <i style="background:#E31A1C;border:1px solid #ccc"></i>1000以上<br>
            <i style="background:#FC4E2A;border:1px solid #ccc"></i>500以上<br>
            <i style="background:#FD8D3C;border:1px solid #ccc"></i>100以上<br>
            <i style="background:#FFEDA0;border:1px solid #ccc"></i>100未満
        `;
        } else {
          element.innerHTML = `
            <strong>2025年比</strong><br>
            <i style="background:#b30000;border:1px solid #ccc"></i>130%以上<br>
            <i style="background:#cb181d;border:1px solid #ccc"></i>120~130%<br>
            <i style="background:#fc9272;border:1px solid #ccc"></i>105~120%<br>
            <i style="background:#f7f7f7;border:1px solid #ccc"></i>95~105%<br>
            <i style="background:#c6dbef;border:1px solid #ccc"></i>80~95%<br>
            <i style="background:#6baed6;border:1px solid #ccc"></i>70~80%<br>
            <i style="background:#3182bd;border:1px solid #ccc"></i>60~70%<br>
            <i style="background:#08306b;border:1px solid #ccc"></i>60%未満
        `;
        }
      }

      // 複数のGeoJSONを並列で取得し、全件完了後に1つのFeatureCollectionとしてまとめて描画する
      Promise.all(files.map((file) => fetch(file).then((response) => response.json())))
        .then((results) => {
          geojsonData = {
            type: "FeatureCollection",
            features: results.flatMap((item) => item.features),
          };
          renderLayer(Number(yearSlider.value));
        })
        .catch((error) => {
          console.error(error);
          alert("GeoJSONの読み込みに失敗しました");
        });

      // スライダー操作で表示年度を切り替える
      yearSlider.addEventListener("input", () => {
        const year = Number(yearSlider.value);
        yearLabel.textContent = year;
        if (geojsonData) {
          renderLayer(year);
        }
      });

      // ラジオボタン切り替えで表示モードを変更し、凡例とレイヤーを更新する
      document.querySelectorAll('input[name="mode"]').forEach((radio) => {
        radio.addEventListener("change", (event) => {
          displayMode = event.target.value;
          const legendElement = document.querySelector(".legend");
          updateLegend(legendElement);
          renderLayer(Number(yearSlider.value));
        });
      });
    </script>
  </body>
</html>

実装上のポイントをいくつか見ていきます。

複数のGeoJSONをまとめて読み込む

将来推計人口データは都道府県ごとにファイルが分かれています。

今回は首都圏をまとめて表示したかったため、4つのGeoJSONを読み込み、1つの FeatureCollection として扱っています。

Promise.all を利用することで、複数ファイルの読み込み完了後にまとめて処理できます。

人口モードと2025年比モードを切り替える

人口データをそのまま表示すると、東京都心部や横浜市周辺は常に人口が多いため、年度を切り替えても変化が分かりにくいことがあります。

そこで2025年を基準として人口比率を計算し、人口増加地域を赤、人口減少地域を青で表示するモードを追加しました。

2025年比モードに切り替えると、人口の増減傾向が把握しやすくなります。

スライダーで年度を切り替える

GeoJSONには以下のような属性が格納されています。

  • PTN_2025
  • PTN_2030
  • PTN_2035
  • ...
  • PTN_2070

スライダーで年度を選択し、その年度に対応する属性を参照することで、人口推移を確認できるようにしています。

おわりに

今回は国土数値情報の将来推計人口データを利用して、将来人口の推移を確認できるビューアを作成してみました。

人口そのものを表示するだけでなく、2025年比で表示することで、地域ごとの変化も把握しやすくなります。

また、GeoJSON形式で公開されているため、Leafletと組み合わせることで比較的手軽に可視化できます。

今回は首都圏の4都県を対象としましたが、他の都道府県のデータでも同様に利用できます。

将来人口データがどのように変化していくのか、地図上で眺めてみるだけでも新しい発見があるかもしれません。

1
0
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
1
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?