0
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?

地図とグラフで2025~2050年全国都道府県の将来推計人口変化を見てみた

Last updated at Posted at 2025-03-18

完成画面

初期画面では、右下に全国の都道府県の人口指数を示すバーチャートが表示されます。ここでは、2020年の人口総数を基準に、2025年から2050年までの人口増減が表現されています。
人口指数.gif

また、左側のマップ上の都道府県ポリゴンをクリックすると、該当する都道府県の2025年~2050年の人口総数を示す折れ線グラフが表示されます。
人口総数.gif

将来推計人口データについて

今回使用したデータは、国立社会保障・人口問題研究所が発表している「将来推計人口」をもとに作成した「未来人口データ」です。2020年を基準年とし、2025年から2050年までを5年ごとに、5歳階級別の推計人口データとなっています。

今回使用した全国都道府県ポリゴンおよび未来人口推計データは、TerraMap APIからレスポンスされたものです。

コードについて

地図表示

地図はOSMを使用し、地図ライブラリはLeafletを用いて、TerraMap APIから取得したGeoJSONファイルを読み込み、マップ上にポリゴンを表示しています。TerraMap APIにリクエストする部分は、area.jsonにリクエストすることで疑似的に表現しています。

let map;
let geojsonData;

async function initMap() {
    map = L.map('map').setView([35.68437, 139.75247], 7);

    L.tileLayer('https://tile.openstreetmap.org/{z}/{x}/{y}.png', {
        maxZoom: 13,
        attribution: '&copy; <a href="http://www.openstreetmap.org/copyright">OpenStreetMap</a>'
    }).addTo(map);

    try {
        const response = await fetch("area.json");
        geojsonData = await response.json();     

        L.geoJSON(geojsonData, {
            style: {
                fillColor: "#2196F3",
                fillOpacity: 0.4,
                color: "#1565C0",
                weight: 2
            },
            onEachFeature: (feature, layer) => {
                layer.on('click', () => {
                    const districtName = feature.properties.points[0][0];
                    createLineChart(districtName, feature.properties.data);
                });
            }
        }).addTo(map);    

    } catch (error) {
        console.error("GeoJSONの読み込みに失敗しました:", error);
    }
}

initMap();

JSONファイルの抜粋は以下になります。

{"type":"FeatureCollection","features":[{"type":"Feature","properties":{"area":{"area":78124.56292457819,"ratio_area":[78124.56292457819],"ratio":[1.0]},"data":[{"is_authorized":true,"ratio_value":["5224614"],"stat_item_id":21142,"stat_id":"029002300","value":"5224614"},{"is_authorized":true,"ratio_value":["5007066"],"stat_item_id":21143,"stat_id":"029002300","value":"5007066"},{"is_authorized":true,"ratio_value":["4791556"],"stat_item_id":21144,"stat_id":"029002300","value":"4791556"},{"is_authorized":true,"ratio_value":["4562362"],"stat_item_id":21145,"stat_id":"029002300","value":"4562362"},{"is_authorized":true,"ratio_value":["4319217"],"stat_item_id":21146,"stat_id":"029002300","value":"4319217"},{"is_authorized":true,"ratio_value":["4067642"],"stat_item_id":21147,"stat_id":"029002300","value":"4067642"},{"is_authorized":true,"ratio_value":["3820016"],"stat_item_id":21148,"stat_id":"029002300","value":"3820016"}],"point_coordinates":[143.33075,43.46036],"geocode":"01","points":[["北海道"]]},"geometry":{"type":"MultiPolygon","coordinates":[[[[140.218914647,41.419258644],

チャートの表示にはChart.jsを使用します

バーチャートの生成と表示設定

未来人口の人口指数は、2020年国勢調査の人口を基準(100)として、未来人口の増減率を示す値です。

例:2050年総人口指数 = (2050年人口総数 / 2020年人口総数) * 100

また、クリックしたボタンに応じて、その年の人口指数が表示されます。

/**
 * 年度に応じた stat_item_id を返す関数
 */
function getStatId(year) {
    switch(year) {
        case 2025: return 21143;
        case 2030: return 21144;
        case 2035: return 21145;
        case 2040: return 21146;
        case 2045: return 21147;
        case 2050: return 21148;
        default: return 21143;
    }
}

/**
 * 各地区の人口指数のバーチャートを作成する関数
 * @param {number} year 対象の年(2025, 2030, ...)
 */
function createBarChart(year) {
    // 対象の stat_item_id を取得
    const statId = getStatId(year);

    const districtNames = [];
    const populationIndices = [];

    // 全地区のデータをループして、x軸のラベルと対象年の人口指数を計算
    geojsonData.features.forEach(feature => {
        const districtName = feature.properties.points[0][0];
        const data = feature.properties.data;
        const pop2020 = data.find(entry => entry.stat_item_id === 21142)?.value || 0;
        const popYear = data.find(entry => entry.stat_item_id === statId)?.value || 0;
        let index = 0;
        if (pop2020 > 0) {
            index = (popYear / pop2020) * 100;
        }
        districtNames.push(districtName);
        populationIndices.push(index);
    });

    const ctx = document.getElementById('barChart').getContext('2d');

    // 既にチャートがある場合は破棄
    if (window.barChart instanceof Chart) {
        window.barChart.destroy();
    }

    window.barChart = new Chart(ctx, {
        type: 'bar',
        data: {
            labels: districtNames,
            datasets: [
                {
                    label: year + "年の人口指数(増加)",
                    data: populationIndices.map(value => value >= 100 ? value - 100 : 0), 
                    backgroundColor: 'rgba(54, 162, 235, 0.5)',
                    borderColor: 'rgba(54, 162, 235, 1)',
                    borderWidth: 1
                },
                {
                    label: year + "年の人口指数(減少)",
                    data: populationIndices.map(value => value < 100 ? value - 100 : 0),
                    backgroundColor: 'rgba(255, 99, 132, 0.5)',
                    borderColor: 'rgba(255, 99, 132, 1)',
                    borderWidth: 1
                }
            ]
        },
        options: {
            responsive: true,
            scales: {
                x: {
                    stacked: true,
                    title: {
                        display: true,
                        text: '地区'
                    },
                    ticks: {
                        autoSkip: false,
                        maxRotation: 90, // 最大回転角度
                        minRotation: 90, // 最小回転角度(90なら縦書きになる)
                        font: {
                          size: 10
                        }
                    }
                },
                y: {
                    stacked: true,
                    beginAtZero: false,
                    suggestedMin: 0, // 適宜調整
                    suggestedMax: 10, // 適宜調整
                    title: {
                        display: true,
                        text: '人口指数'
                    }
                }
            }
        }
    });        
}

/**
 * ボタンから呼ばれる、チャート更新用の関数
 * @param {number} year
 */
function updateChart(year) {
    createBarChart(year);
}

折り線グラフの生成と表示設定

グラフのY軸は、全データ中の最大の人口総数に合わせて設定しています。

function createLineChart(districtName, data) {
    const ctx = document.getElementById('lineChart').getContext('2d');
    const population2020 = data.find(entry => entry.stat_item_id === 21142)?.value || 0; // (基準)2020年人口総数
    const population2025 = data.find(entry => entry.stat_item_id === 21143)?.value || 0; // 2025年人口総数
    const population2030 = data.find(entry => entry.stat_item_id === 21144)?.value || 0; // 2030年人口総数
    const population2035 = data.find(entry => entry.stat_item_id === 21145)?.value || 0; // 2035年人口総数
    const population2040 = data.find(entry => entry.stat_item_id === 21146)?.value || 0; // 2040年人口総数
    const population2045 = data.find(entry => entry.stat_item_id === 21147)?.value || 0; // 2045年人口総数
    const population2050 = data.find(entry => entry.stat_item_id === 21148)?.value || 0; // 2050年人口総数
   
    const chartData = {
        labels: ['2020年', '2025年', '2030年', '2035年', '2040年', '2045年', '2050年'],
        datasets: [{
            label: districtName,
            data: [population2020, population2025, population2030, population2035, population2040, population2045, population2050],
            backgroundColor: 'rgba(255, 99, 132, 0.2)',
            borderColor: 'rgba(255, 99, 132, 1)',
            borderWidth: 1,
            fill: false
        }]
    };

    if (window.lineChart instanceof Chart) {
        window.lineChart.destroy();
    }

    window.lineChart = new Chart(ctx, {
        type: 'line',
        data: chartData,
        options: {
            scales: {
                x: {
                    display: true,
                    title: {
                        display: true,
                        text: '年次'
                    }
                },
                y: {
                    beginAtZero: true,
                    max: unifiedMax,
                    display: true,
                    title: {
                        display: true,
                        text: '人口総数'
                    }
                }
            }
        }
    });

    // チャートを表示
    document.getElementById('lineChart').style.display = 'block';
}

全体のコードは以下のファイルに記載しています。

index.html
<!DOCTYPE html>
<html lang="ja">
<head>
    <title>Leaflet Map</title>
    <meta charset="utf-8" />
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <link rel="stylesheet" href="https://unpkg.com/leaflet@1.9.4/dist/leaflet.css" integrity="sha256-p4NxAoJBhIIN+hmNHrzRCf9tD/miZyoHS5obTRR9BMY=" crossorigin=""/>
    <script src="https://unpkg.com/leaflet@1.9.4/dist/leaflet.js" integrity="sha256-20nQCchB9co0qIjJZRGuk2/Z9VM+kNiyxNV1lvTlZBo=" crossorigin=""></script>
    <script src="https://cdn.jsdelivr.net/npm/chart.js"></script>
    <link rel="stylesheet" href="styles.css">
</head>
<body>
  <div class="container">
    <div id="map"></div>
    <div id="charts">
      <canvas id="lineChart"></canvas>
      <hr style="border: 1px solid #ccc; margin: 0;">
      <canvas id="barChart"></canvas>
      <div id="buttons">
        <button onclick="updateChart(2025)">2025年</button>
        <button onclick="updateChart(2030)">2030年</button>
        <button onclick="updateChart(2035)">2035年</button>
        <button onclick="updateChart(2040)">2040年</button>
        <button onclick="updateChart(2045)">2045年</button>
        <button onclick="updateChart(2050)">2050年</button>
      </div>
    </div>    
  </div>  
  <script src="index.js"></script>
</body>
</html>

style.css
body, html {
    margin: 0;
    padding: 0;
    height: 100%;
    overflow: hidden;
  }
  
  .container {
    display: flex;
    height: 100vh;
  }
  
  #map {
    width: 50%;
    height: 100%;
  }
  
  #charts {
    width: 50%;
    height: 45%;
    display: flex;
    flex-direction: column;
  }
  
  #lineChart {
    flex: 1;
    display: block;
  }

  #buttons {
    text-align: center;
    padding: 10px;
    background-color: #f5f5f5;
  }

  #buttons button {
    margin: 0 5px;
    padding: 5px 10px;
  }
index.js
let map;
let geojsonData;
let unifiedMax = 0;

async function initMap() {
    map = L.map('map').setView([35.68437, 139.75247], 7);

    L.tileLayer('https://tile.openstreetmap.org/{z}/{x}/{y}.png', {
        maxZoom: 13,
        attribution: '&copy; <a href="http://www.openstreetmap.org/copyright">OpenStreetMap</a>'
    }).addTo(map);

    try {
        const response = await fetch("area.json");
        geojsonData = await response.json();

        geojsonData.features.forEach(feature => {
            const data = feature.properties.data;
            const population2020 = data.find(entry => entry.stat_item_id === 21142)?.value || 0;
            const population2025 = data.find(entry => entry.stat_item_id === 21143)?.value || 0;
            const population2030 = data.find(entry => entry.stat_item_id === 21144)?.value || 0;
            const population2035 = data.find(entry => entry.stat_item_id === 21145)?.value || 0;
            const population2040 = data.find(entry => entry.stat_item_id === 21146)?.value || 0;
            const population2045 = data.find(entry => entry.stat_item_id === 21147)?.value || 0;
            const population2050 = data.find(entry => entry.stat_item_id === 21148)?.value || 0;
            const maxForFeature = Math.max(population2020, population2025, population2030, population2035, population2040, population2045, population2050);
            if (maxForFeature > unifiedMax) {
                unifiedMax = maxForFeature;
            }
        });        

        L.geoJSON(geojsonData, {
            style: {
                fillColor: "#2196F3",
                fillOpacity: 0.4,
                color: "#1565C0",
                weight: 2
            },
            onEachFeature: (feature, layer) => {
                layer.on('click', () => {
                    const districtName = feature.properties.points[0][0];
                    createLineChart(districtName, feature.properties.data);
                });
            }
        }).addTo(map);

        // ページ読み込み時にデフォルト(2025年)のバーチャートを描画
        createBarChart(2025);        

    } catch (error) {
        console.error("GeoJSONの読み込みに失敗しました:", error);
    }
}

/**
 * 年度に応じた stat_item_id を返す関数
 */
function getStatId(year) {
    switch(year) {
        case 2025: return 21143;
        case 2030: return 21144;
        case 2035: return 21145;
        case 2040: return 21146;
        case 2045: return 21147;
        case 2050: return 21148;
        default: return 21143;
    }
}

/**
 * 各地区の人口指数のバーチャートを作成する関数
 * @param {number} year 対象の年(2025, 2030, ...)
 */
function createBarChart(year) {
    // 対象の stat_item_id を取得
    const statId = getStatId(year);

    const districtNames = [];
    const populationIndices = [];

    // 全地区のデータをループして、x軸のラベルと対象年の人口指数を計算
    geojsonData.features.forEach(feature => {
        const districtName = feature.properties.points[0][0];
        const data = feature.properties.data;
        const pop2020 = data.find(entry => entry.stat_item_id === 21142)?.value || 0;
        const popYear = data.find(entry => entry.stat_item_id === statId)?.value || 0;
        let index = 0;
        if (pop2020 > 0) {
            index = (popYear / pop2020) * 100;
        }
        districtNames.push(districtName);
        populationIndices.push(index);
    });

    const ctx = document.getElementById('barChart').getContext('2d');

    // 既にチャートがある場合は破棄
    if (window.barChart instanceof Chart) {
        window.barChart.destroy();
    }

    window.barChart = new Chart(ctx, {
        type: 'bar',
        data: {
            labels: districtNames,
            datasets: [
                {
                    label: year + "年の人口指数(増加)",
                    data: populationIndices.map(value => value >= 100 ? value - 100 : 0), 
                    backgroundColor: 'rgba(54, 162, 235, 0.5)',
                    borderColor: 'rgba(54, 162, 235, 1)',
                    borderWidth: 1
                },
                {
                    label: year + "年の人口指数(減少)",
                    data: populationIndices.map(value => value < 100 ? value - 100 : 0),
                    backgroundColor: 'rgba(255, 99, 132, 0.5)',
                    borderColor: 'rgba(255, 99, 132, 1)',
                    borderWidth: 1
                }
            ]
        },
        options: {
            responsive: true,
            scales: {
                x: {
                    stacked: true,
                    title: {
                        display: true,
                        text: '地区'
                    },
                    ticks: {
                        autoSkip: false,
                        maxRotation: 90, // 最大回転角度
                        minRotation: 90, // 最小回転角度(90なら縦書きになる)
                        font: {
                          size: 10
                        }
                    }
                },
                y: {
                    stacked: true,
                    beginAtZero: false,
                    suggestedMin: 0, // 適宜調整
                    suggestedMax: 10, // 適宜調整
                    title: {
                        display: true,
                        text: '人口指数'
                    }
                }
            }
        }
    });        
}

/**
 * ボタンから呼ばれる、チャート更新用の関数
 * @param {number} year
 */
function updateChart(year) {
    createBarChart(year);
}

function createLineChart(districtName, data) {
    const ctx = document.getElementById('lineChart').getContext('2d');
    const population2020 = data.find(entry => entry.stat_item_id === 21142)?.value || 0; // (基準)2020年人口総数
    const population2025 = data.find(entry => entry.stat_item_id === 21143)?.value || 0; // 2025年人口総数
    const population2030 = data.find(entry => entry.stat_item_id === 21144)?.value || 0; // 2030年人口総数
    const population2035 = data.find(entry => entry.stat_item_id === 21145)?.value || 0; // 2035年人口総数
    const population2040 = data.find(entry => entry.stat_item_id === 21146)?.value || 0; // 2040年人口総数
    const population2045 = data.find(entry => entry.stat_item_id === 21147)?.value || 0; // 2045年人口総数
    const population2050 = data.find(entry => entry.stat_item_id === 21148)?.value || 0; // 2050年人口総数
   
    const chartData = {
        labels: ['2020年', '2025年', '2030年', '2035年', '2040年', '2045年', '2050年'],
        datasets: [{
            label: districtName,
            data: [population2020, population2025, population2030, population2035, population2040, population2045, population2050],
            backgroundColor: 'rgba(255, 99, 132, 0.2)',
            borderColor: 'rgba(255, 99, 132, 1)',
            borderWidth: 1,
            fill: false
        }]
    };

    if (window.lineChart instanceof Chart) {
        window.lineChart.destroy();
    }

    window.lineChart = new Chart(ctx, {
        type: 'line',
        data: chartData,
        options: {
            scales: {
                x: {
                    display: true,
                    title: {
                        display: true,
                        text: '年次'
                    }
                },
                y: {
                    beginAtZero: true,
                    max: unifiedMax,
                    display: true,
                    title: {
                        display: true,
                        text: '人口総数'
                    }
                }
            }
        }
    });

    // チャートを表示
    document.getElementById('lineChart').style.display = 'block';
}

initMap();

0
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
0
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?