GoogleMapsAPIだけで作るDrawSearch

# 概要
GoogleMap上に好きなように線を引き、その中に含まれるマーカーのみを残すというのを作ってみました。

###### ※ちなみにこういう検索方法をなんというのか分からなかったので、DrawSearchというのは自分で勝手に名付けました。正式名称をご存じの方いましたら是非教えてください。

<img width="400" alt="スクリーンショット 2017-04-19 21.18.28.png" src="https://qiita-image-store.s3.amazonaws.com/0/14172/a48f082c-a9d1-3877-a75d-6e93de1a38fc.png">

↓

<img width="400" alt="スクリーンショット 2017-04-19 21.23.04.png" src="https://qiita-image-store.s3.amazonaws.com/0/14172/ef97fb70-1b1c-80f4-ae3f-e9b4825cc06f.png">

# サンプルコード
https://github.com/s-ichikawa/drawsearch/blob/master/draw_search.html

#解説

## なにはともあれマップを表示
GoogleMapやマーカーの表示のし方なんて見飽きたぜというかたはサラッと飛ばして「GoogleMap上に好きなように線を引く」から見てください。

`<script src="https://maps.googleapis.com/maps/api/js?key={{ your_api_key }}&callback=initMap" async defer></script>`
まず、GoogleのCDNからGoogleMapsAPIのライブラリを呼び出しています。callbackパラメータにinitMapを指定することにより、ライブラリが読み込まれた時ソース上のinitMapファンクションが呼び出されます。
{{ your_api_key }}の部分はGoogleAPIコンソールから生成したAPIキーをいれてください。
APIキーの作成方法は他で丁寧に解説している記事があると思うので省きます。

```
var map,
    startLatLng,
    isDrawing = false,
    polyline,
    markers = [];

function initMap() {
    map = new google.maps.Map(document.getElementById('map'), {
        center: {lat: 35.628956, lng: 139.738907},
        zoom: 14,
        draggable: false,
        clickableIcons: false
    });

    for (var i = 0; i < 100; i++) {
        randomMarker();
    }

    map.addListener('mousedown', startDrawing);
    map.addListener('mousemove', addLatLng);
    map.addListener('mouseup', endDrawing);
}
```
initMapファンクションでは以下の処理を行っています。

- Googleマップの初期化
- マーカーの作成
- ラインを引くための処理をイベントに紐付かせる

### Googleマップの初期化

`new google.maps.Map()`の第１引数はキャンバスとなる要素、第２引数は各種パラメータを渡します。
今回は品川駅を中心にズームレベルは14で表示しています。
draggableはドラッグして地図を移動する機能のON/OFF
clickableIconsは地図上のアイコンをクリックした時に情報を表示する機能のON/OFF
です。DrawSearchする際はじゃまになるのでfalseを設定してOFFにします。

### マーカーの作成
今回はデモなので以下のファンクションで100個のマーカーをランダムで作成しています。
本来はデータセットの緯度経度を渡して作成する感じになるでしょう。
ここでのポイントは、線を引いたあとに範囲内かどうか判定する必要があるため`markers`配列に格納しているところです。

```
function randomMarker() {
    var lat = 35.56 + (Math.random() / 10),
        lng = 139.69 + (Math.random() / 10),
        marker = new google.maps.Marker({
            position: new google.maps.LatLng(lat, lng),
            map: map
        });
    markers.push(marker);
}
```

### ラインを引くための処理をイベントに紐付かせる
[GoogleMapsAPIのライブラリでしようできるイベント](https://developers.google.com/maps/documentation/javascript/reference#Map)は色々定義されていますが、
今回はmousedownで描き始め、mousemoveで描画中、mouseupで描き終わり用のファンクションをそれぞれ呼び出すようにします。

```
map.addListener('mousedown', startDrawing);
map.addListener('mousemove', addLatLng);
map.addListener('mouseup', endDrawing);
```

## GoogleMap上に好きなように線を引く
ここからが本題です。
クリックしながらマウスポインタを動かすと好きに線が引けて離すと書き終わるようにします。
また最終的にやりたいことは範囲内のマーカーのみ表示させることなので、描き終わった時の座標と書き始めた時の座標を結んで必ず線が閉じるようにしてあげます。

### 書き始めの処理
```
function startDrawing(event) {
    polyline = new google.maps.Polyline();
    polyline.setMap(map);
    polyline.addListener('mouseup', endDrawing);
    startLatLng = event.latLng;
    polyline.getPath().push(startLatLng);
    isDrawing = true;
}
```

`polyline = new google.maps.Polyline();`
GoogleMap上に線を引くためにはPolylineというクラスを使います。
Mapと同様に引数に設定を渡すことで線の色や太さなど様々な設定が行えます。

`polyline.setMap(map);`はPolylineをマップに紐付けています。
これを呼び出すことによってPolylineで定義した線をマップ上に描画させます。

`polyline.addListener('mouseup', endDrawing);`
mapの方でもmouseupイベントで描き終わり処理を呼び出すようにしていましたが、mapの方だけだと時々正しくイベントを拾えなかったのでPolylineの方でもmouseup時には描き終わり処理を呼び出します。

`startLatLng = event.latLng;`
最終的に線を閉じるようにするために書き始めの座標を記録しています。

`polyline.getPath().push(startLatLng);`
polylineのpathは連続した座標配列で、pushした順に座標を線で結んでいきます。

`isDrawing = true;`
mousemoveイベント中に描画中なのかどうか判定するためにフラグをONにしています。

### 描画中の処理
```
function addLatLng(event) {
    if (isDrawing) {
        polyline.getPath().push(event.latLng);
    }
}
```

描画中の処理は単純に描画中であればpolylineのpathにポインタがある場所の座標を追加していくだけです。

### 描き終わりの処理
```
function endDrawing(event) {
    isDrawing = false;
    polyline.getPath().push(startLatLng);

    var polygon = new google.maps.Polygon({paths: polyline.getPath()});
    for (var i = 0; i < markers.length; i++) {
        if (!google.maps.geometry.poly.containsLocation(markers[i].getPosition(), polygon)) {
            markers[i].setMap(null);
        }

    }
}
```

`isDrawing = false;`でひとまずフラグをOFFにし、
`polyline.getPath().push(startLatLng);`で線を閉じるために最後に書き始めた時の座標をpushします。

ここまでで好きなように線を引き、最後に線を閉じるところまで出来ました。

次に範囲外にあるマーカーを非表示にする処理が続きます。
ある緯度経度が図の範囲内かどうか判定するためには[google.maps.geometry.poly.containsLocation(point:LatLng, polygon:Polygon)](https://developers.google.com/maps/documentation/javascript/reference#poly)を使用します。

`var polygon = new google.maps.Polygon({paths: polyline.getPath()});`
まずは`containsLocation()`の第２引数に渡すPolygonクラスを生成します。
polyline.getPath()で座標配列を取得出来るのでそれをpathsに渡すことで自分が書いた線のPolygonクラスを生成することができます。

```
for (var i = 0; i < markers.length; i++) {
    if (!google.maps.geometry.poly.containsLocation(markers[i].getPosition(), polygon)) {
        markers[i].setMap(null);
    }
}
```
最後に全てのマーカーを走査します。
`markers[i].getPosition()`で座標が取得できるのでこれを`containsLocation()`の第１引数に、先程生成したPolygonクラスを第２引数に渡すことで範囲内か範囲外か判定できます。

`markers[i].setMap(null);`
範囲外だった場合にマーカーの`setMap`に`null`を渡すことで非表示にできます。
ちなみにこれはマップに表示されなくなるだけで消えたわけではないので、`setMap`に最初に作った`map`を再設定してあげれば再度表示できると思います。