Googleマップをメッシュ単位で地図色塗りする


## はじめに

[Location Tech Advent Calendar](https://qiita.com/advent-calendar/2021/location-tech)の８日目の記事です。

自分は現在、技研商事インターナショナルという会社で開発を担当しています。
弊社はGISやそれに関わる製品・サービスを提供していて、今回「位置情報に関することならなんでも！」ということで参加させていただくことにしました。
扱っているサービスでは、位置情報というよりは地図と情報を結びつけるといった側面が強いので、そういったアプローチのごくライトな内容を紹介します。


## 【前提】メッシュについて
### メッシュとは

緯度・経度を元に地図を均等に分割した区域のことです。（超ざっくり説明）
メッシュは細かさで「〇次メッシュ」と種類が分けられており、下記のようになっています。

|  種類  |  説明  |  一辺のサイズ  |
| ---- | ---- | --- |
|  一次メッシュ  |  縦：経度1<br/>横：緯度2/3  | 約80km |
|  二次メッシュ |  一次メッシュの1/8  | 約10km |
|  三次メッシュ  | 二次メッシュの1/10  | 約1km |
|  四次メッシュ |  三次メッシュの1/2  | 約500m |
|  五次メッシュ |  四次メッシュの1/2  | 約250m |

※「四次メッシュ」「五次メッシュ」はそれぞれ一般的に「1/2地域メッシュ」「1/4地域メッシュ」と呼ばれたりします。

### メッシュコード
各メッシュにはそれぞれコードが振られていて、そのコードで場所を特定することができます。

|  メッシュ  |  コード桁数  |  説明  |
| ---- | ---- | --- |
|  一次メッシュ  | 4桁  | 南端の緯度×1.5の整数2桁 + 西端の経度-100の整数2桁 |
|  二次メッシュ |  6桁  | 属する一次メッシュのコード + 一次メッシュを8×8に分割したうちどこに属するか |
|  三次メッシュ  | 8桁 | 属する二次メッシュのコード + 二次メッシュを10×10に分割したうちどこに属するか|
|  四次メッシュ  | 9桁 | 属する三次メッシュのコード + 三次メッシュを2×2に4分割したうちどこに属するか |
|  五次メッシュ  | 10桁 | 以下省略 |


#### 例：東京駅の場合
緯度：35.6809591 経度：139.7673068 なので、
一次メッシュコードは
上2桁：35.6809591 × 1.5　= 53
下2桁：(139 -100) = 39
=> **5339** になります。

二次メッシュコードは下記の画像を参考に、**533946**になります。
（画像黒枠が一次メッシュなので、それを8×8に分割したうちの所在）
![地図3.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2277407/f33b53a5-dbfa-128c-4f76-28d76e5dfd4a.png)


同じ要領で、三次メッシュコードは**53394611**です。


## 完成イメージ

![完成イメージ２.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2277407/32844d34-9994-aecb-70bf-89ad669bc35d.png)

こんな感じで、Googleマップ上でメッシュごとに人口が多いところと少ないところを可視化できるようにしたいと思います。
例に挙げた東京駅のある二次メッシュ533946でやってみます。

## メッシュデータ

メッシュコードを利用し、それぞれのメッシュに対しての統計データを用意します。
今回は、2015年の国勢調査を基にした人口総数のデータを key:メッシュコード val:データ の形にしておきます。

```js
var mesh_data = {
  "53394600":873,
  "53394601":7696,
  "53394602":22845,
  "53394603":17602,
  "53394604":20031,
  "53394605":22865,
  "53394606":20525,
  "53394607":11043,
  "53394608":22406,
  "53394609":23112,
  "53394610":8,
  "53394611":1104,
  // 以下省略
};
```

このデータをGoogleマップ上で地図色塗りします。

## 実装

### 分類ごとの色の決定

データを何段階に分類するか決定し、表示する色コードを決めておきます。
今回は赤系の色で12段階に分類して表示します。
色が淡い順に定義します。

```js
var color = [
  "#fff4f4","#ffeaea","#ffd5d5","#ffaaaa",
  "#ff8080","#ff5555","#ff2b2b","#ff0000",
  "#d50000","#aa0000","#800000","#550000"
];
```

### 描画領域の作成

canvasタグを用意し、大きさをデータの北西端のメッシュと南東端のメッシュの位置から決定します。
※`getLatLngFromCode(code)` と `fromLatLngToPoint(LatLng)`は本題から逸れるため割愛します。

```js
var canvas_ = document.createElement( "canvas" );
var context = canvas_.getContext('2d');

// 南西端と北東端のメッシュの緯度経度を取得
var sw = getLatLngFromCode("53394600");
var ne = getLatLngFromCode("53394699");

// 緯度経度を座標に変換し、高さと幅を決定
// map = google.maps.Map
var top_px = fromLatLngToPoint(new google.maps.LatLng(sw.lat, sw.lng), map);
var bottom_px = fromLatLngToPoint(new google.maps.LatLng(ne.lat, ne.lng), map);

var width = Math.abs(top_px.x - bottom_px.x);
var height = Math.abs(top_px.y - bottom_px.y);

canvas_.width = width;
canvas_.height = height;
```

### データの色塗り

用意したcanvasをメッシュごとに分類した色で塗りつぶします。

```js
// 背景を白にしておく
context.beginPath();
context.fillStyle = "#ffffff";
context.fillRect(0, 0, canvas_.width, canvas_.height);

// 分類する段階の幅を算出
var max = Math.max(...Object.values(mesh_data));
var step = max / color.length;

// 北西からメッシュごとにcanvasを塗りつぶす
for(var i=9; i>=0; i--){
  for(var j=0; j<=9; j++){
    var data = mesh_data["533946"+ i + j];
    // データのないメッシュは無視
    if(data){
      var dur = Math.floor(data / step);
      // 最大値をもつメッシュは一番濃い色を振る
      if(dur == color.length){
        dur -= 1;
      }
      context.fillStyle = color[dur];
      context.fillRect(canvas_.width / 10 * i, canvas_.height / 10 * j, canvas_.width / 10, canvas_.height / 10);
    }
  }
}
```

### マップ上に配置

Googleマップ上で移動・拡大・縮小しても表示がずれないよう、canvasを画像化してマップ上に表示します。

```js
var img_src = canvas_.toDataURL();

// 表示位置
var bounds = {
  north: ne.lat,
  south: sw.lat,
  east: ne.lng,
  west: sw.lng
};

var overlay = new google.maps.GroundOverlay(
  img_src, bounds, {
    map: map,  // google.maps.Map
    opacity: 0.5
  }
);
overlay.setMap(map)
```

これで実装はおわりです。

## おわりに

メッシュに対応したデータを用意しちゃえば、メッシュは単なる四角なので表示はそんなに複雑ではありません。
ライブラリと組み合わせれば分類数ごとの色の準備も容易になると思います。

データを用意して画像化するなんてめんどくさい！データの用意なんてどうやってやるの！という方向けに、技研商事インターナショナルでは取得するデータや位置情報をリクエストすると色塗りした画像を返してくれるAPIを提供しています。（有料）
今回紹介した内容では実装の難しい行政界ごとの色塗りにも対応しています。

詳細は [お問い合わせ](https://www.giken.co.jp/contact/) ください。

なお、今回は弊社のサービスで上記APIを利用し画像を色塗りに利用しているためこのような手法の紹介となりましたが、Polygonクラスを利用しての色塗りの方が容易なケースもあります。

https://developers.google.com/maps/documentation/javascript/examples/polygon-simple

## 参考
 - メッシュコードから緯度経度への変換
 `getLatLngFromCode(code)`
https://www.gis-py.com/entry/py-mesh
※上記コードを参考にjsに書き直したものを本記事では使用しました。  

 - 緯度経度から座標への変換
 `fromLatLngToPoint(LatLng)`
https://krasimirtsonev.com/blog/article/google-maps-api-v3-convert-latlng-object-to-actual-pixels-point-object