1kmメッシュ滞在人口（全国人流オープンデータ）をMapbox GL JSで表示してみました

# はじめに
- [1kmメッシュ滞在人口（全国人流オープンデータ）](https://www.geospatial.jp/ckan/dataset/mlit-1km-fromto)をMapbox GL JSで表示してみました。
- Mapbox GL JSの表示部分は、[こちらのブログ記事](https://tech-blog.optim.co.jp/entry/2019/05/14/173000)を参考にさせていただきました。
- 国土交通省が2019年・2020年を対象に、[全国の「1kmメッシュ別の滞在人口データ」を全国人流オープンデータ](https://www.geospatial.jp/ckan/dataset/mlit-1km-fromto)として公開していますので、今回はこちらのデータを用いています。
- Mapboxのアクセストークンが必要になります。

# アウトプットイメージ
<blockquote class="twitter-tweet"><p lang="ja" dir="ltr">こちらのブログ記事（<a href="https://t.co/eR52JTnmaO">https://t.co/eR52JTnmaO</a>）を参考にさせていただきながら、1kmメッシュ滞在人口（全国人流オープンデータ）をMapboxGLJSで表示してみた😀<br><br>出典：「全国の人流オープンデータ」（国土交通省）<br>※2020年8月平日昼<a href="https://t.co/xrimNC92Gs">https://t.co/xrimNC92Gs</a><a href="https://twitter.com/hashtag/Mapbox?src=hash&amp;ref_src=twsrc%5Etfw">#Mapbox</a> <a href="https://t.co/WmfL7Sq87L">pic.twitter.com/WmfL7Sq87L</a></p>&mdash; shi_works⛅ (@syanseto) <a href="https://twitter.com/syanseto/status/1421738860891889666?ref_src=twsrc%5Etfw">August 1, 2021</a></blockquote> <script async src="https://platform.twitter.com/widgets.js" charset="utf-8"></script>
※出典：「全国の人流オープンデータ」（国土交通省）
https://www.geospatial.jp/ckan/dataset/mlit-1km-fromto
※上記出典とは別に、編集・加工等を行った

# 前提条件
- 1kmメッシュ滞在人口（全国人流オープンデータ）は、csv形式のデータで提供されていますが、Mapbox GL JSで表示するために、予めQGIS等でgeojson形式のデータ（メッシュポリゴンデータ）に変換しています。
- 1kmメッシュ滞在人口（全国人流オープンデータ）のcsv形式のデータは以下のとおりです。
- オープンデータ（人流データ）の定義書は[こちら](https://www.geospatial.jp/ckan/dataset/mlit-1km-fromto/resource/e2083495-8095-4119-a865-e5c22e6a7eee)を参照してください。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/205045/f18d46ff-f150-044f-113a-290bf7cb380d.png)
- 変換後のgeojson形式のデータは以下のとおりです。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/205045/8f0dac79-942d-9b02-4fd9-9c5547dc1f11.png)

# html、css
```html:mesh1km_pop.html
<!DOCTYPE html>
<html>

<head>
    <meta charset='utf-8' />
    <title>1kmメッシュ滞在人口</title>
    <meta name='viewport' content='initial-scale=1,maximum-scale=1,user-scalable=no' />
    <script src='https://api.tiles.mapbox.com/mapbox-gl-js/v1.12.0/mapbox-gl.js'></script>
    <script src='https://npmcdn.com/@turf/turf/turf.min.js'></script>

    <script src="https://d3js.org/d3.v5.min.js"></script>
    <script src="https://d3js.org/d3-queue.v3.min.js"></script>
    <script src="https://d3js.org/d3-request.v1.min.js"></script>

    <link href='https://api.tiles.mapbox.com/mapbox-gl-js/v1.12.0/mapbox-gl.css' rel='stylesheet' />

    <style>
        body {
            margin: 0;
            padding: 0;
        }

        #map {
            position: absolute;
            top: 0;
            bottom: 0;
            width: 100%;
        }

        .legend {
            background-color: #fff;
            border-radius: 3px;
            bottom: 30px;
            box-shadow: 0 1px 2px rgba(0, 0, 0, 0.10);
            font: 15px/20px 'Helvetica Neue', Arial, Helvetica, sans-serif;
            padding: 10px;
            position: absolute;
            right: 10px;
            z-index: 1;
            line-height: 25px;
            height: 280px;
        }

        .legend h4 {
            margin: 0 0 10px;
        }

        .legend div span {
            border-radius: 50%;
            display: inline-block;
            height: 15px;
            margin-right: 10px;
            width: 15px;
            color: orangered
        }
    </style>
</head>

<body>
    <!-- Load the `mapbox-gl-geocoder` plugin. -->
    <script
        src="https://api.mapbox.com/mapbox-gl-js/plugins/mapbox-gl-geocoder/v4.7.2/mapbox-gl-geocoder.min.js"></script>
    <link rel="stylesheet"
        href="https://api.mapbox.com/mapbox-gl-js/plugins/mapbox-gl-geocoder/v4.7.2/mapbox-gl-geocoder.css"
        type="text/css">
    <div id="tooltip"></div>
    <div id='map'></div>
    <div id='county-legend' class='legend'>
        <h4>1kmメッシュ滞在人口</h4>
        <div><span style='background-color: rgb(44, 123, 182)'></span>0 - 4,000</div>
        <div><span style='background-color: rgb(100, 165, 205)'></span>4,000 - 8,000</div>
        <div><span style='background-color: rgb(157, 207, 228)'></span>8,000 - 12,000</div>
        <div><span style='background-color: rgb(199, 230, 219)'></span>12,000 - 16,000</div>
        <div><span style='background-color: rgb(237, 247, 201)'></span>16,000 - 20,000</div>
        <div><span style='background-color: rgb(255, 237, 170)'></span>20,000 - 24,000</div>
        <div><span style='background-color: rgb(254, 201, 128)'></span>24,000 - 28,000</div>
        <div><span style='background-color: rgb(249, 158, 89)'></span>28,000 - 32,000</div>
        <div><span style='background-color: rgb(232, 91, 58)'></span>32,000 - 36,000</div>
        <div><span style='background-color: rgb(215, 25, 28)'></span>36,000 - 40,000</div>
    </div>

</body>

<script type="text/javascript" src=script.js></script>

</html>
```
# JavaScript
```javascript:script.js
mapboxgl.accessToken = "Mapboxのアクセストークンを入力してください";

// Mapbox Streets読み込み
const map = new mapboxgl.Map({
  container: "map",
  style: 'mapbox://styles/mapbox/streets-v9',
  center: [139.7527995, 35.685175],
  zoom: 12
});

// Create a popup, but don't add it to the map yet.
let popup = new mapboxgl.Popup({
  closeButton: false,
  closeOnClick: false
});

let hoveredStateId = null;

map.on("mousemove", "2DmeshLayer", function (e) {

  map.getCanvas().style.cursor = 'pointer';

  if (e.features.length > 0) {
    if (hoveredStateId) {
      map.setFeatureState({ source: 'meshdata', id: hoveredStateId }, { hover: false });
    }
    hoveredStateId = e.features[0].layer.id;
    map.setFeatureState({ source: 'meshdata', id: hoveredStateId }, { hover: true });
  }

  popup.setLngLat(e.lngLat)
    .setHTML(
      "<div><b>3次メッシュコード &nbsp;</b>" + e.features[0].properties.meshid + "</div>" +
       "<div><b>1kmメッシュ滞在人口</b></div>" +
      "<div>" + Math.round(e.features[0].properties.population) + " 人</div>")
    .addTo(map);

});

// When the mouse leaves the state-fill layer, update the feature state of the
// previously hovered feature.
map.on("mouseleave", "2DmeshLayer", function () {
  map.getCanvas().style.cursor = '';
  popup.remove();
  if (hoveredStateId) {
    map.setFeatureState({ source: 'meshdata', id: hoveredStateId }, { hover: false });
  }
  hoveredStateId = null;
});

let POP_1 = ["<", ["get", "population"], 4000];
let POP_2 = ["all", [">=", ["get", "population"], 4000], ["<", ["get", "population"], 8000]];
let POP_3 = ["all", [">=", ["get", "population"], 8000], ["<", ["get", "population"], 12000]];
let POP_4 = ["all", [">=", ["get", "population"], 12000], ["<", ["get", "population"], 16000]];
let POP_5 = ["all", [">=", ["get", "population"], 16000], ["<", ["get", "population"], 20000]];
let POP_6 = ["all", [">=", ["get", "population"], 20000], ["<", ["get", "population"], 24000]];
let POP_7 = ["all", [">=", ["get", "population"], 24000], ["<", ["get", "population"], 28000]];
let POP_8 = ["all", [">=", ["get", "population"], 28000], ["<", ["get", "population"], 32000]];
let POP_9 = ["all", [">=", ["get", "population"], 32000], ["<", ["get", "population"], 36000]];
let POP_10 = ["all", [">=", ["get", "population"], 36000], ["<", ["get", "population"], 40000]];

let colors = [
  'rgb(44, 123, 182)',
  'rgb(100, 165, 205)',
  'rgb(157, 207, 228)',
  'rgb(199, 230, 219)',
  'rgb(237, 247, 201)',
  'rgb(255, 237, 170)',
  'rgb(254, 201, 128)',
  'rgb(249, 158, 89)',
  'rgb(232, 91, 58)',
  'rgb(215, 25, 28)'
]

const meshToMap = (meshdata) => {

  map.addSource('meshdata', {
    type: 'geojson',
    data: meshdata,
  });

  map.addLayer({
    'id': '2DmeshLayer',
    'type': 'fill',
    'source': 'meshdata',
    'layout': {},
    'paint': {
      "fill-color":
        ["case",
          POP_1, colors[0],
          POP_2, colors[1],
          POP_3, colors[2],
          POP_4, colors[3],
          POP_5, colors[4],
          POP_6, colors[5],
          POP_7, colors[6],
          POP_8, colors[7],
          POP_9, colors[8],
          POP_10, colors[9],
          colors[9]
        ],
      "fill-outline-color": "white",
      "fill-opacity": ["case",
        ["boolean", ["feature-state", "hover"], false],
        1,
        0.5
      ]
    }
  });

};

let meshGeoJsonURL = 'data/monthly_mdp_mesh1km_202008_1_0.geojson';

const handleGetData = (err, meshdata) => {
  meshToMap(meshdata);
}

d3.queue()
  .defer(d3.json, meshGeoJsonURL)
  .await(handleGetData);

// コントロール関係表示
var nav = new mapboxgl.NavigationControl();
map.addControl(nav, 'top-left');

// スケール表示
map.addControl(new mapboxgl.ScaleControl({
  maxWidth: 200,
  unit: 'metric'
}));

// Add the control to the map.
map.addControl(
  new MapboxGeocoder({
    accessToken: mapboxgl.accessToken,
    mapboxgl: mapboxgl
  })
);
```
# 参考文献
https://tech-blog.optim.co.jp/entry/2019/05/14/173000