Mapbox Isochrone APIを使用して、地図上に到達圏を表示してみた

## はじめに
手順や完成したコードは公式チュートリアルに沿って、場所を東京駅周辺に変えて試してみました。

https://docs.mapbox.com/help/tutorials/get-started-isochrone-api/

### Isochrone APIとは
Isochrone APIは指定地点から自動車、自転車、徒歩で指定した移動時間で到達できる範囲を得ることができるAPIです。例えば、東京駅から自動車30分で到達できる場所の範囲を知ることができます。

https://docs.mapbox.com/api/navigation/isochrone/

こういった範囲は到達圏や時間圏などと言われます。

https://www.mapmarketing.co.jp/glossary/2/44.html?grid=qiita&utm_source=qiita.com&utm_medium=referral&utm_campaign=qiita_mbox-Isochrone_4e0a71c54764ac19cdf9_01

### 完成イメージ
作成したサンプルは以下になります。
![qiita11.gif](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2916958/3d7e5460-6ac5-6a60-accd-b1bcfabe9ed8.gif)

コードはトークンを入れれば使えます。
```html
<!doctype html>
<html lang="en">
  <head>
    <meta charset="utf-8" />
    <title>Demo: Get started with the Isochrone API</title>
    <meta name="viewport" content="width=device-width, initial-scale=1" />
    <!-- Mapbox GL JS -->
    <script src="https://api.tiles.mapbox.com/mapbox-gl-js/v3.3.0/mapbox-gl.js"></script>
    <link
      href="https://api.tiles.mapbox.com/mapbox-gl-js/v3.3.0/mapbox-gl.css"
      rel="stylesheet"
    />
    <!-- Mapbox Assembly -->
    <link
      href="https://api.mapbox.com/mapbox-assembly/v1.3.0/assembly.min.css"
      rel="stylesheet"
    />
    <script src="https://api.mapbox.com/mapbox-assembly/v1.3.0/assembly.js"></script>
    <style>
      body {
        margin: 0;
        padding: 0;
      }

      #map {
        position: absolute;
        top: 0;
        bottom: 0;
        width: 100%;
      }
    </style>
  </head>

  <body>
    <div id="map"></div>

    <div class="absolute fl my24 mx24 py24 px24 bg-gray-faint round">
      <form id="params">
        <h4 class="txt-m txt-bold mb6">Choose a travel mode:</h4>
        <div class="mb12 mr12 toggle-group align-center">
          <label class="toggle-container">
            <input name="profile" type="radio" value="walking" />
            <div class="toggle toggle--active-null toggle--null">Walking</div>
          </label>
          <label class="toggle-container">
            <input name="profile" type="radio" value="cycling" checked />
            <div class="toggle toggle--active-null toggle--null">Cycling</div>
          </label>
          <label class="toggle-container">
            <input name="profile" type="radio" value="driving" />
            <div class="toggle toggle--active-null toggle--null">Driving</div>
          </label>
        </div>
        <h4 class="txt-m txt-bold mb6">Choose a maximum duration:</h4>
        <div class="mb12 mr12 toggle-group align-center">
          <label class="toggle-container">
            <input name="duration" type="radio" value="10" checked />
            <div class="toggle toggle--active-null toggle--null">10 min</div>
          </label>
          <label class="toggle-container">
            <input name="duration" type="radio" value="20" />
            <div class="toggle toggle--active-null toggle--null">20 min</div>
          </label>
          <label class="toggle-container">
            <input name="duration" type="radio" value="30" />
            <div class="toggle toggle--active-null toggle--null">30 min</div>
          </label>
        </div>
      </form>
    </div>

    <script>
      mapboxgl.accessToken = 'UserAccessToken';

      const map = new mapboxgl.Map({
        container: 'map', // container id
        style: 'mapbox://styles/mapbox/streets-v12', // stylesheet
        center: [139.767124, 35.681236], // starting position [lng, lat]
        zoom: 11.5 // starting zoom
      });

      // Target the params form in the HTML
      const params = document.getElementById('params');

      // Create variables to use in getIso()
      const urlBase = 'https://api.mapbox.com/isochrone/v1/mapbox/';
      const lon = 139.767124;
      const lat = 35.681236;
      let profile = 'cycling';
      let minutes = 10;

      // Set up a marker that you can use to show the query's coordinates
      const marker = new mapboxgl.Marker({
        'color': '#314ccd'
      });

      // Create a LngLat object to use in the marker initialization
      // https://docs.mapbox.com/mapbox-gl-js/api/#lnglat
      const lngLat = {
        lon: lon,
        lat: lat
      };

      // Create a function that sets up the Isochrone API query then makes a fetch call
      async function getIso() {
        const query = await fetch(
          `${urlBase}${profile}/${lon},${lat}?contours_minutes=${minutes}&polygons=true&access_token=${mapboxgl.accessToken}`,
          { method: 'GET' }
        );
        const data = await query.json();
        // Set the 'iso' source's data to what's returned by the API query
        map.getSource('iso').setData(data);
      }

      // When a user changes the value of profile or duration by clicking a button, change the parameter's value and make the API query again
      params.addEventListener('change', (event) => {
        if (event.target.name === 'profile') {
          profile = event.target.value;
        } else if (event.target.name === 'duration') {
          minutes = event.target.value;
        }
        getIso();
      });

      map.on('load', () => {
        // When the map loads, add the source and layer
        map.addSource('iso', {
          type: 'geojson',
          data: {
            'type': 'FeatureCollection',
            'features': []
          }
        });

        map.addLayer(
          {
            'id': 'isoLayer',
            'type': 'fill',
            'source': 'iso',
            'layout': {},
            'paint': {
              'fill-color': '#5a3fc0',
              'fill-opacity': 0.3
            }
          },
          'poi-label'
        );

        // Initialize the marker at the query coordinates
        marker.setLngLat(lngLat).addTo(map);

        // Make the API call
        getIso();
      });
    </script>
  </body>
</html>
```

## 作成手順

### マップ表示
表示する初期座標を東京駅の座標にしました。
```
center: [139.767124, 35.681236]
```
### Isochrone APIへのリクエスト
Isochrone APIへリクエストするには、次の3つのパラメータが必要になります。
- profile:移動する交通手段。walking、cycling、drivingと交通量を考慮したdriving-trafficがオプションとしてあります
- coordinates:到達圏の中心座標
- contours_minutes:到達圏を特定するための時間、最大60分まで

```
https://api.mapbox.com/isochrone/v1/mapbox/{profile}/{coordinates}.json?{contours_minutes}&access_token= <UserAccessToken />
```

### 到達圏を描画する
addSourceとaddLayerでisoという名前の空のFeatureCollectionのレイヤーを作成します。APIへのリクエストから返されたGeoJSONの結果をそのままisoにセットします。
```js
map.getSource('iso').setData(data);

map.addSource('iso', {
  type: 'geojson',
  data: {
    'type': 'FeatureCollection',
    'features': []
  }
});

map.addLayer(
  {
    'id': 'isoLayer',
    'type': 'fill',
    'source': 'iso',
    'layout': {},
    'paint': {
      'fill-color': '#5a3fc0',
      'fill-opacity': 0.3
    }
  },
  'poi-label'
);
```

### 動的変化を適用する
マップ上に表示する到達圏を左上のサイドバーで指定した移動手段と時間によって変更します。
```js
// Target the "params" form in the HTML portion of your code
const params = document.getElementById('params');

// When a user changes the value of profile or duration by clicking a button, change the parameter's value and make the API query again
params.addEventListener('change', (event) => {
  if (event.target.name === 'profile') {
    profile = event.target.value;
  } else if (event.target.name === 'duration') {
    minutes = event.target.value;
  }
  getIso();
});
```