GoogleMapsAPI × データ分析 × プロットマッピング

## 始めに
GoogleMap上に円を描き、その円の大きさ、色でデータを分析する画面を作成します。
これを行う場合Excelでは限界を感じ、GoogleMapsAPIでプロットマッピングを行います。

実際に作成した画面はこんな感じです。
<img width="953" alt="plotMappingSample.PNG" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/138786/f5779769-b47d-eb18-00af-241ef38965e5.png">

## 準備

・[APIキー取得](https://qiita.com/Haruka-Ogawa/items/997401a2edcd20e61037)
GoogleMapsAPIを使うには、APIキーを取得する必要があります。
APIキーの取得に関して上記リンクが非常に参考になります。

・ソースコード [gitHub](https://github.com/yunomichawan/PlotMappingToWeb)
最初に載せている画面をこのソースで確認できます。
※ ソースコード内のAPIキーを自身で取得したAPIキーにする必要があります。

``` html
<!-- 「YOUR_API_KEY」を自身で取得したAPIキーにしてください。 -->
<script async defer src="https://maps.googleapis.com/maps/api/js?key=YOUR_API_KEY&callback=initMap">
```

## プロットマッピング

### マップの初期化
プロットマッピングを行う処理の全体としてはこんな感じになっています。
データ部分に関しては一部省略しています。
詳細については次セクションで説明します。

``` Javascript

var map;
var infoWindow;
// 表示用データ
var testData = {
    '202001': {
        'type': 'FeatureCollection',
        'features': [{ 'type': 'Feature', 'geometry': { 'type': 'Point', 'coordinates': [139.45, 35.41], 'properties': { 'name': '東京', 'dataCount': 10, 'meter': 50, }, } },]
    }
};

// マップ初期化
function initMap() {
    // google map 初期化
    map = new google.maps.Map(document.getElementById('map'), {
        zoom: 4, // 初期表示時の縮尺
        center: { lat: 36, lng: -101 }, // 初期表示位置
        mapTypeControl: false, // 地図、航空写真機能を非表示
        zoomControl: false, // ズーム機能(+-)を非表示
        streetViewControl: false, // ストリートビュー機能を非表示
    });
    infoWindow = new google.maps.InfoWindow();

    // 円のスタイル設定をmapに保持
    var styling = new circleStyling();
    map.styling = styling;
    // 円描画時のスタイルを設定
    map.data.setStyle(function (feature) {
        var meter = feature.getProperty('meter');
        var cnt = feature.getProperty('dataCount');
        return {
            icon: map.styling.getCircle(meter, cnt)
        };
    });

    // プロットマッピング初期化
    var plotMap = new plotMapping();
    plotMap.init();
    plotMap.setMapData('202001');
};

// プロットマッピング
function plotMapping() {
    // データセット
    this.setMapData = function (key) {
        var mappingData = testData[key];

        // 0:min,1:max
        var rangeAvg = [,];
        var rangeCount = [,];

        // 値域の決定
        for (var i = 0; i < mappingData.features.length; i++) {
            var cnt = mappingData.features[i].properties.dataCount;
            var avg = mappingData.features[i].properties.meter / cnt;
            if (i == 0) {
                rangeAvg[0] = avg;
                rangeAvg[1] = avg;
                rangeCount[0] = cnt;
                rangeCount[1] = cnt;
            } else {
                rangeAvg[0] = rangeAvg[0] > avg ? avg : rangeAvg[0];
                rangeAvg[1] = rangeAvg[1] < avg ? avg : rangeAvg[1];
                rangeCount[0] = rangeCount[0] > cnt ? cnt : rangeCount[0];
                rangeCount[1] = rangeCount[1] < cnt ? cnt : rangeCount[1];
            }
        }

        map.styling.rangeCount = rangeCount;
        map.styling.rangeAvg = rangeAvg;
        // データを追加し、円を描画する。
        map.data.addGeoJson(mappingData);
    };
};

// 円スタイル設定
function circleStyling() {
    // 緑色最大値
    const gmax = 255;
    // 円の最大、最小サイズ
    const scaleMin = 5;
    const scaleMax = 60;

    // データ範囲(0:min,1:max)
    this.rangeCount = [,];
    this.rangeAvg = [,];
    
    // 円描画
    this.getCircle = function (meter, cnt) {
        var avg = meter / cnt;
        var avgRatio = avg / this.rangeAvg[1];
        avgRatio = avgRatio > 1 ? 1 : avgRatio;
        var cntRatio = cnt / this.rangeCount[1];

        // 緑値決定
        var g = (1 - avgRatio) * gmax;
        g = g > gmax ? gmax : g;
        // 円サイズ決定
        var s = cntRatio * scaleMax;
        if (s < scaleMin) {
            s = scaleMin;
        } else if (s > scaleMax) {
            s = scaleMax;
        }

        return {
            path: google.maps.SymbolPath.CIRCLE, // 円を指定
            fillColor: 'rgb(255,' + g + ',0)', // 円の色
            fillOpacity: 0.4, // 円の透明度
            scale: s, // 円のサイズ
            strokeColor: 'white', // 円の枠線の色
            strokeWeight: 0.5 // 円の枠線の太さ
        };
    };
};
```

### データの準備
GoogleMap上に円を表示するには決められたデータフォーマットに従う必要があります。
最低限、自身で設定する必要がある項目は「経度、緯度」のみになります。
今回、作成したサンプルではデータによって、円の大きさ、色見を変える必要があるため、「properties」に値を持たせています。

``` Javascript
{
	'type': 'FeatureCollection',
    'features': [{
    		'type': 'Feature', 
    		'geometry': { 
    			'type': 'Point', 
    			// 表示したい位置を経度,緯度で指定。
    			'coordinates': [139.45, 35.41] ,
    			'properties': {
    				// ここは自由に定義できます。
    				'name': '東京',
    				'dataCount': 10,
    				'meter': 50,
    			},
    		}
    	},
    ]
}

```

### 円を表示する
ここではまとめて円を表示する方法について書きます。
マップ上にまとめて円を表示するには、「map.data.addGeoJson(data)」で行うことができます。

``` Javascript
// データセット
this.setMapData = function (key) {
	// mappingDataにはデータの準備と同じフォーマットのデータが入ります。
    var mappingData = testData[key];

    // 0:min,1:max
    var rangeAvg = [,];
    var rangeCount = [,];

    // 値域の決定
    for (var i = 0; i < mappingData.features.length; i++) {
        var cnt = mappingData.features[i].properties.dataCount;
        var avg = mappingData.features[i].properties.meter / cnt;
        if (i == 0) {
            rangeAvg[0] = avg;
            rangeAvg[1] = avg;
            rangeCount[0] = cnt;
            rangeCount[1] = cnt;
        } else {
            rangeAvg[0] = rangeAvg[0] > avg ? avg : rangeAvg[0];
            rangeAvg[1] = rangeAvg[1] < avg ? avg : rangeAvg[1];
            rangeCount[0] = rangeCount[0] > cnt ? cnt : rangeCount[0];
            rangeCount[1] = rangeCount[1] < cnt ? cnt : rangeCount[1];
        }
    }
    
	// 表示データの値域を設定
    map.styling.rangeCount = rangeCount;
    map.styling.rangeAvg = rangeAvg;
    
    // データを追加し、円を描画する。
    map.data.addGeoJson(mappingData);
};
```

表示する際、データの最小・最大を調べていますが、これは円の大きさ、色見を決定するのに使用します。

### 円のスタイルを設定する
1つの円を描画する際に、「getCicrle」を呼び出すように設定しています。
「getCircle」では表示するデータ群の値域と比べて円の大きさ、色見を決定しています。

``` Javascript

// 円のスタイル設定をmapに保持
var styling = new circleStyling();
map.styling = styling;
// 円描画時のスタイルを設定
map.data.setStyle(function (feature) {
	// propertiesからデータを取得
    var meter = feature.getProperty('meter');
    var cnt = feature.getProperty('dataCount');
    return {
        icon: map.styling.getCircle(meter, cnt)
    };
});

// 円スタイル設定
function circleStyling() {
    // 緑色最大値
    const gmax = 255;
    // 円の最大、最小サイズ
    const scaleMin = 5;
    const scaleMax = 60;

    // データ範囲(0:min,1:max)
    this.rangeCount = [,];
    this.rangeAvg = [,];
    
    // propertiesの値をもとに円描画
    this.getCircle = function (meter, cnt) {
        var avg = meter / cnt;
        var avgRatio = avg / this.rangeAvg[1];
        avgRatio = avgRatio > 1 ? 1 : avgRatio;
        var cntRatio = cnt / this.rangeCount[1];

        // 緑値決定
        var g = (1 - avgRatio) * gmax;
        g = g > gmax ? gmax : g;
        // 円サイズ決定
        var s = cntRatio * scaleMax;
        if (s < scaleMin) {
            s = scaleMin;
        } else if (s > scaleMax) {
            s = scaleMax;
        }

        return {
            path: google.maps.SymbolPath.CIRCLE, // 円を指定
            fillColor: 'rgb(255,' + g + ',0)', // 円の色
            fillOpacity: 0.4, // 円の透明度
            scale: s, // 円のサイズ
            strokeColor: 'white', // 円の枠線の色
            strokeWeight: 0.5 // 円の枠線の太さ
        };
    };
};
```

円に設定できるオプションの詳細については下記リンクを確認してください。
・[Polygons CircleOptions Maps JavaScript API Google Developers](https://developers.google.com/maps/documentation/javascript/symbols#properties)

### 円を全て削除する
円の再表示、データの切替を行いたい場合などに。
addGeoJsonのようにまとめて操作できるものはないかなと思いましたが[ドキュメント](https://developers.google.com/maps/documentation/javascript/reference/3.39/data?hl=en#Data)を見る限りなさそうでした。

``` Javascript
// 円を全て削除
this.removeAll = function () {
    map.data.forEach(function (value) {
        map.data.remove(value);
    });
};
```

### Tipsについて

Tipsにある「円の大きさ」、「円の色見」についてcssで表現しています。

``` css
/*円の色見をグラデーションで作成*/
.color-range {
    width: 150px;
    height: 12px;
    background: linear-gradient(to right,rgba(255,0,0,0.4),rgba(255,255,0,0.4));
    display: inline-block;
}

/*CSSで円を作成*/
.circle-range {
    border: 0.5px solid white;
    border-radius: 50%;
    background-color: rgba(255,100,0,0.4);
    display: inline-block;
    margin-left: 10px;
    margin-right: 10px;
}
```


## 参考にしたサイト

・[APIキー取得](https://qiita.com/Haruka-Ogawa/items/997401a2edcd20e61037)

・[Maps JavaScript API Google Developers](https://developers.google.com/maps/documentation/javascript/tutorial?hl=ja)