Mapboxの地図上に複数地点の到達圏を表示してみる（都内介護医療院の診療圏表示）

## はじめに
Mapboxの地図上に複数地点の到達圏（自動車30分圏内）を表示してみます。GeoJSONファイルの複数地点情報を元にして、Isochrone APIから各到達圏を取得します。

地点情報については、高齢化社会における医療施設が気になっているので、都内の介護医療院にしてみました。

## 到達圏とは
徒歩・自転車・自動車・電車等の移動手段で、ある地点から一定の時間や距離で到達可能な範囲を指します。

既に弊社の記事で扱っていますが、MapboxのIsochrone APIを使えば、徒歩・自転車・自動車のいずれかでの到達圏（最大60分または最大100km）を取得することができます。

https://qiita.com/yan_x/items/4e0a71c54764ac19cdf9

## 複数地点データ（都内介護医療院）
元データは2024年11月時の [東京都内 介護医療院 一覧](https://www.fukushi.metro.tokyo.lg.jp/documents/d/fukushi/20241101iryouin_1) で、全34件の地点情報になります。GeoJSONファイルまでにする手順は概要になりますが、以下になります。

1. 元データから施設名や住所を抽出
1. 各住所に対してジオコーディング[^1]を実施し、緯度・経度を付与したCSVを用意

    <img width="400" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2893989/e70d8329-da38-461f-b12e-4b5d350a337c.png">

1. [QGIS](https://qgis.org/) でCSVテキストレイヤを追加

    <img width="400" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2893989/de860880-35d6-4997-ae74-d7b4dcca5454.png"><p></p><img width="400" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2893989/aa320de1-7ab5-43ad-93aa-ef5a79642423.png">

1. CSVテキストレイヤをGeoJSONファイルとしてエクスポート
    <img width="400" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2893989/31fe578e-f831-440a-b13c-2f7490f1323d.png">

## 地図アプリ作成
Mapbox地図の表示、ポリゴンやマーカーの表示、Isochrone APIの利用を行うには、Mapboxのアカウントとアクセストークンが必要になります。弊社でMapboxを初めて扱った以下記事を参照していただけると、入門しやすいかと思います。

https://qiita.com/Book-Ma/items/2cfc5bc68606bd6e2366

### HTML
```index.html
<!DOCTYPE html>
<html lang="ja">
<head>
    <meta charset="UTF-8" />
    <title>都内介護医療院の診療圏表示・自動車30分圏内</title>
    <meta name="viewport" content="initial-scale=1,maximum-scale=1,user-scalable=no">
    <link href="https://api.mapbox.com/mapbox-gl-js/v3.10.0/mapbox-gl.css" rel="stylesheet">
    <script src="https://api.mapbox.com/mapbox-gl-js/v3.10.0/mapbox-gl.js"></script>
    <style>
        body { margin: 0; padding: 0; }
        #map { position: absolute; top: 0; bottom: 0; width: 100%; }
    </style>
</head>
<body>
    <div id="map"></div>
    <script src="map.js"></script>
</body>
</html>
```
### JavaScript
```map.js
mapboxgl.accessToken = 'YOUR_MAPBOX_ACCESS_TOKEN';

const map = new mapboxgl.Map({
    container: 'map',
    style: 'mapbox://styles/mapbox/streets-v11',
    center: [139.55434049, 35.67520028],    // 初期表示の中心座標（東京）
    zoom: 10
});

map.on('load', async function () {
    // GeoJSONデータの取得、マーカーの表示
    const pointGeojson = await fetchPointGeojson();
    // 30分圏内の表示
    await fetchIsochrones(pointGeojson);
});

async function fetchPointGeojson() {
    try {
        const response = await fetch('kaigo-iryoin.geojson');
        const geojson = await response.json();
        
        // マーカーの表示
        geojson.features.forEach(feature => {
            const coordinates = feature.geometry.coordinates;
            new mapboxgl.Marker({
                color: '#314ccd'
            })
            .setLngLat(coordinates)
            .setPopup(new mapboxgl.Popup().setHTML('<p>' + feature.properties.name + '</p>'))
            .addTo(map);
        });

        return geojson;
    } catch (error) {
        console.error('Error fetching GeoJSON:', error);
    }
}

async function fetchIsochrones(pointGeojson) {
    try {
        const features = pointGeojson.features;
        for (let i = 0; i < features.length; i++) {
            const coordinates = features[i].geometry.coordinates;
            const url = `https://api.mapbox.com/isochrone/v1/mapbox/driving/${coordinates[0]},${coordinates[1]}?contours_minutes=30&polygons=true&access_token=${mapboxgl.accessToken}`;

            const response = await fetch(url);
            const data = await response.json();
            
            map.addSource(`isochrone-${i}`, {
                'type': 'geojson',
                'data': data
            });

            map.addLayer({
                'id': `isochrone-${i}`,
                'type': 'fill',
                'source': `isochrone-${i}`,
                'layout': {},
                'paint': {
                    'fill-color': '#5a3fc0',
                    'fill-opacity': 0.3
                }
            });
        }
    } catch (error) {
        console.error('Error fetching isochrones:', error);
    }
}
```
:::note warn
**注意点**
今回の34地点ぐらいならば問題無いですが、地点数が膨大になればメモリ使用量も比例しますし、Isochrone APIのレートリミットや利用料金にもご注意下さい。

レートリミット：　300リクエスト / 分
無料枠：　　0~100,000リクエスト / 月

[Isochrone API | API Docs | Mapbox](https://docs.mapbox.com/api/navigation/isochrone/#isochrone-api-restrictions-and-limits)
[利用料金｜Mapbox Japan](https://www.mapbox.jp/pricing)
:::
## 表示結果
都内介護医療院の診療圏（自動車30分圏内）の表示は以下のようになりました。マーカーをクリックすると施設名がポップアップ表示されるようにはなっています。

![kaigo-iryoin-driving-area.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2893989/51542267-0ac5-47f0-8615-034b66980b33.png)

各病床数までは考慮していませんが、青の濃いエリアほど条件に恵まれていると言えそうです。高齢者人口のレイヤーも追加して、このようなポリゴンをオーバーレイする分析も良いかもしれません。

記事は以上になります。ありがとうございました。


[^1]: 弊社のジオコーダーによる街区レベルのマッチング結果を利用しました。