TILE38紹介（5） ：Geofencing

|TILE38紹介|
|:---|
|[TILE38紹介（1） ：基本コマンド](https://qiita.com/tesujiro/items/69f8970bd69d501cb0c8)|
|[TILE38紹介（2） ：空間系コマンド](https://qiita.com/tesujiro/items/0a998f2c6e3dd49d7429)|
|[TILE38紹介（3） ：Goプログラムからのアクセス1](https://qiita.com/tesujiro/items/fa7dfc079a94824cc77e)|
|[TILE38紹介（4） ：Goプログラムからのアクセス2](https://qiita.com/tesujiro/items/d9bf26233ce96bc3596f)|
|[TILE38紹介（5） ：Geofencing](https://qiita.com/tesujiro/items/e4b5a3df174e69516553)|

# TL;DR
- Tile38のGeofencing機能について説明する。
- Geofencingは簡単に言うと「指定された特定の領域について、動的に変化するオブジェクト（点、線、多角形）が入ったり、出たりしたイベントを検知する」ものである。
- Tile38では事前に登録されたWebhookのエントリポイントに対して、イベント発生時にHttpリクエストを送る機能を持つ。
- データベースがGeofencing用のWebhook機能を持つことのメリットは、利用側が判定ロジックを書かずに済む実装の容易さだけでなく、必要なタイミングだけでイベントが発生することで不要な判定負荷を避けることができ最小限のリソースで実装できることにもある。（イベント監視のために一定間隔でチェックした場合にはDB側、利用側共にリソース負荷が高くなる。）

# サンプルモデル
- ここでは最も簡単な例として、円形の領域を監視するWebhookを設定し、領域内にオブジェクトが侵入している間にWebサーバのAPIを呼び出す。
![tile38_webhook.mov.gif](https://qiita-image-store.s3.amazonaws.com/0/41075/90305283-8975-846f-8bf8-ae7429c5793a.gif)

# Webhookの設定
- Webhook設定はSETHOOKコマンドを使用する。
- ここでは説明を簡略にするためにCLIからの実行例を示すが、redigo等のredisクライアントを使用すればコマンドを実行することは可能である。

```bash:
> tile38-cli
127.0.0.1:9851> SETHOOK alert http://localhost:8001/webhook NEARBY intruder FENCE DETECT inside POINT 35.6581 139.6975 100
{"ok":true,"elapsed":"119.974µs"}
127.0.0.1:9851> quit
> 
```
- Webhook設定にはSETHOOKコマンドを使用する。
 - 設定可能な値：SETHOOK(設定)、DELHOOK(削除)、PDELHOOK(パターン削除)、HOOKS(表示)
- 特定の点から半径100メートルを監視範囲とする。
- 監視イベントの設定はinsideとする。侵入時点のみを監視すること等も可能。
 - 設定可能な値：inside, outside, enter, exit, cross
- イベント発生時のhttpリクエスト送り先のURLを設定する。

# Webhookの作成
- 設定時に指定したURLを実装する。
- httpリクエストはGETメソッドを使用し、ボディ部分にはJSON形式で監視イベントが発生したオブジェクトの情報が入る。
- 以下はGoでの実装例を示すが、Webhookやtile38特有のAPI使用はない。

```go:server.go
package main

import (
        "encoding/json"
        "io"
        "log"
        "net/http"
        "strconv"
)

type server struct {
        router *http.ServeMux
}

func main() {
        s := newServer()
        s.routes()
        http.ListenAndServe("localhost:8001", s.router)
}

func newServer() *server {
        return &server{
                router: http.NewServeMux(),
        }
}

func (s *server) routes() {
        s.router.HandleFunc("/webhook", s.handleWebhook())
}

func (s *server) handleWebhook() http.HandlerFunc {
        return func(w http.ResponseWriter, r *http.Request) {
                log.Printf("Received Webhook request!\n")

                length, err := strconv.Atoi(r.Header.Get("Content-Length"))
                if err != nil {
                        w.WriteHeader(http.StatusInternalServerError)
                        return
                }

                body := make([]byte, length)
                length, err = r.Body.Read(body)
                if err != nil && err != io.EOF {
                        w.WriteHeader(http.StatusInternalServerError)
                        return
                }

                var jsonBody map[string]interface{}
                err = json.Unmarshal(body[:length], &jsonBody)
                if err != nil {
                        w.WriteHeader(http.StatusInternalServerError)
                        return
                }
                log.Printf("Request.Body: %v", jsonBody)
        }
}
```
- Webサーバを実装する。
- Webhookで登録したエントリポイント(/webhook)に対する挙動を記述する。
- ここでは受け取ったリクエスト情報をjsonオブジェクトとして解釈して、ログにその内容を出力する。

# 実行
## クライアントの実行
- あらかじめWebhookを登録し、Webサーバを起動した状態でオブジェクトの移動をtile38-cliから登録する。（ここではコマンドのみ記載し、結果のメッセージは省略する。）

```
127.0.0.1:9851> SET intruder point:X POINT 35.65949 139.69963
127.0.0.1:9851> SET intruder point:X POINT 35.65946 139.69947
127.0.0.1:9851> SET intruder point:X POINT 35.65944 139.69932
127.0.0.1:9851> SET intruder point:X POINT 35.65936 139.69909
127.0.0.1:9851> SET intruder point:X POINT 35.65932 139.69893
127.0.0.1:9851> SET intruder point:X POINT 35.65929 139.69879
127.0.0.1:9851> SET intruder point:X POINT 35.65923 139.69860
127.0.0.1:9851> SET intruder point:X POINT 35.65917 139.69836
127.0.0.1:9851> SET intruder point:X POINT 35.65914 139.69821
127.0.0.1:9851> SET intruder point:X POINT 35.65908 139.69801
127.0.0.1:9851> SET intruder point:X POINT 35.65899 139.69769
127.0.0.1:9851> SET intruder point:X POINT 35.65891 139.69755
127.0.0.1:9851> SET intruder point:X POINT 35.65889 139.69750
127.0.0.1:9851> SET intruder point:X POINT 35.65882 139.69740
127.0.0.1:9851> SET intruder point:X POINT 35.65873 139.69731
127.0.0.1:9851> SET intruder point:X POINT 35.65868 139.69723
127.0.0.1:9851> SET intruder point:X POINT 35.65860 139.69718
127.0.0.1:9851> SET intruder point:X POINT 35.65849 139.69705
127.0.0.1:9851> SET intruder point:X POINT 35.65841 139.69689
127.0.0.1:9851> SET intruder point:X POINT 35.65828 139.69679
127.0.0.1:9851> SET intruder point:X POINT 35.65817 139.69670
127.0.0.1:9851> SET intruder point:X POINT 35.65809 139.69664
127.0.0.1:9851> SET intruder point:X POINT 35.65794 139.69653
127.0.0.1:9851> SET intruder point:X POINT 35.65778 139.69642
127.0.0.1:9851> SET intruder point:X POINT 35.65764 139.69629
127.0.0.1:9851> SET intruder point:X POINT 35.65753 139.69619
127.0.0.1:9851> SET intruder point:X POINT 35.65745 139.69615
127.0.0.1:9851> SET intruder point:X POINT 35.65733 139.69605
127.0.0.1:9851> SET intruder point:X POINT 35.65725 139.69600
127.0.0.1:9851> SET intruder point:X POINT 35.65716 139.69596
127.0.0.1:9851> SET intruder point:X POINT 35.65703 139.69588
127.0.0.1:9851> SET intruder point:X POINT 35.65691 139.69576
```

## Webサーバの実行
- 登録されたWebhookの条件に基づいてWebサーバにリクエストが発行される。

```bash:
> go run ./server.go 
2019/02/10 09:32:29 Received Webhook request!
2019/02/10 09:32:29 Request.Body: map[command:set group:5c5f41e9ad8031bbfeee805e detect:inside hook:alert key:intruder time:2019-02-10T07:32:29.234948+07:00 id:point:X object:map[type:Point coordinates:[139.69769 35.65899]]]
2019/02/10 09:32:29 Received Webhook request!
2019/02/10 09:32:29 Request.Body: map[detect:inside hook:alert key:intruder time:2019-02-10T07:32:29.755473+07:00 id:point:X object:map[coordinates:[139.69755 35.65891] type:Point] command:set group:5c5f41e9ad8031bbfeee805e]
2019/02/10 09:32:30 Received Webhook request!
2019/02/10 09:32:30 Request.Body: map[object:map[type:Point coordinates:[139.6975 35.65889]] command:set group:5c5f41e9ad8031bbfeee805e detect:inside hook:alert key:intruder time:2019-02-10T07:32:30.282235+07:00 id:point:X]
## 中略 ##
2019/02/10 09:32:34 Received Webhook request!
2019/02/10 09:32:34 Request.Body: map[object:map[coordinates:[139.69664 35.65809] type:Point] command:set group:5c5f41e9ad8031bbfeee805e detect:inside hook:alert key:intruder time:2019-02-10T07:32:34.977593+07:00 id:point:X]
```
- 監視イベントとしてはinsideを指定したので、オブジェクトが円形の範囲内にある間はSETコマンドが実行される毎にWebhookにリクエストが発行される。
- httpリクエストのボディは以下のような構造となっている。

```javascript:
{
	command: "set",
	group:   <不明>,
	detect:  "inside",
	hook:    <webhook名>,
	key:     <オブジェクトのキー>,
	id:      <オブジェクトのid>,
	time:    <時間>,
	object:  <geojson形式のオブジェクト情報>
}
```

以上でGeofencingの説明を終了するが、TILE38の機能を使用することで地図データの判定ロジックを書くことなく実装できることがわかった。