MarkLogicでRaspberryPi3のセンサー情報を取り込んでみよう（9）センサー情報をリアルタイム監視してみよう

この記事は[MarkLogic Advent Calendar 2017](https://qiita.com/advent-calendar/2017/marklogic)の25日目です。

# はじめに
アドベントカレンダーもいよいよ最終日となりました。この連載もまとめになります。

MarkLogicには、DB内のドキュメントに対するリアルタイムアラート機能が搭載されています。この機能を使って、Raspberry Pi3から送信されるデータを監視して、監視用のWebブラウザにリアルタイムに情報を表示させてみようと思います。

なお、リアルタイムアラートの詳細は[MarkLogicのリアルタイムアラート機能を使ってみる](https://qiita.com/t2hk/items/17076c1b924d350c9f70)をご参照下さい。

# 環境
サーバサイドは以下の環境を使用します。

| 環境 | バージョン |
| --- | --- |
| CentOS7 | 7.4.1708 |
| Node.js | v8.9.1 |
| npm | 5.5.1 |
| express | 5.6.0 |
| Mosca | 2.7.0 |
| MQTT.js | 2.14.0 |
| MarkLogic9 | 9.0-3 |
| MarkLogic Node.js Client API | 2.0.3 |

Raspberry Pi3側は以下の環境を使用します。今回はカメラを追加しました。

| 環境 | バージョン |
| --- | --- |
| Raspberry Pi3 Model B |  |
| RASPBIAN STRETCH WITH DESKTOP | November 2017 |
| Python3 | 3.6.3 |
| paho-mqtt | 1.3.1 |
| [GLOBALSAT BU-353S4 USB GPSレシーバー](https://www.tokyo2show.co.jp/gps/globalsat_bu353s4) | - |
| [Sense HAT](https://www.raspberrypi.org/products/sense-hat/) | 2.2.0 |
| Raspberry Pi Camera | V2 |

今回もWebブラウザを使います。以下のJavascriptライブラリを使用します。

| 環境 | バージョン |
| --- | --- |
| Chart.js | 2.7.1 |
| paho-mqtt | 1.0.1 |
| jQuery | 3.2.1 |

# やってみること
Raspberry Pi3からセンサーデータを送信させ、MarkLogicのリアルタイムアラート機能で検出してアラート処理を行って、その結果を監視用のWebブラウザに動的に表示させます。
Raspberry Pi3からはカメラで撮影した写真も送信し、ブラウザに表示させます。

![pic01_image_01.png](https://qiita-image-store.s3.amazonaws.com/0/187299/8f31625c-d9b6-11bc-b0ec-9731720ebf18.png)

# こんな感じです
Raspberry Pi3でジョイスティックを操作したらカメラで写真を撮影し、GPSの座標と写真をMQTTで送信します。このデータはMQTTブローカー等を経由してMarkLogicに登録します。
MarkLogicはジオ検索機能を使ってRaspberry Pi3の座標から最寄駅を検索し、MQTTでリアルタイムにGoogleマップ上に表示します。Googleマップには撮影した写真も表示します。

![pic02_image01.jpg](https://qiita-image-store.s3.amazonaws.com/0/187299/fb157353-b8ed-eea5-1bf6-55c412760768.jpeg)

# 作業内容
具体的な作業は以下の通りです。アドベントカレンダーで連載してきた内容の集大成+αとなります。

* MarkLogicのリアルタイムアラートの設定とアラート処理の開発
    * アラートデータの検出ルールの設定
    * 検出時に実行するXQueryの作成
        * アラートデータに含まれる座標を使ってジオ検索し、最寄りの地物情報を取得する
        * アラートデータに含まれる座標と、検索した地物情報の座標をフロントのHTTPサーバにPOSTする
* MarkLogic用のリアルタイム通信フロントの構築
    * MQTTでRaspberry Pi3のセンサーデータをサブスクライブしてMarkLogicに登録する
    * MarkLogicからのアラート処理の結果をHTTP POSTで受信する
    * POSTされたアラート処理結果をMQTTパブリッシュする
* Raspberry Pi3からアラートデータをMQTTパブリッシュする
    * 通常時はGPSの座標、気温、湿度、気圧のデータを送信する
    * Sense HATのジョイスティックを操作したらアラートデータを送信する
        * アラートデータはGPSの座標等のテキストデータ、RaspberryPi3で撮影する写真(バイナリ)の2種類とする
        * ジョイスティックを操作したら写真を撮影する(JPEG)
* 監視用のWebアプリの開発
    * ブラウザ上でアラートの処理結果をMQTTサブスクライブする
    * Google Maps APIを使ってアラート発生源やその最寄駅の位置を図示する
    * アラート発生源にはRaspberry Pi3から送られた写真も掲載する

# MarkLogicのリアルタイムアラートの設定
MarkLogicのアラート機能を使用した全体的な流れは以下の通りです。
トピックが「raspi/Exception」というデータが登録されるとアラート用のクエリ「alert-receive-iot-exception.xqy」が実行されるようにアラートを定義します。このクエリ内部ではブラウザに送信するデータを組み立て、HTTP POSTでリアルタイム通信フロントに渡します。

![pic01_alertflow_01.png](https://qiita-image-store.s3.amazonaws.com/0/187299/be144b15-74c8-eab4-e0c2-f2ec563cfd88.png)


具体的な設定内容は以下の通りです。

```xquery:アラート定義
xquery version "1.0-ml";
import module namespace alert = "http://marklogic.com/xdmp/alert" at "/MarkLogic/alert.xqy";

let $alert-config := alert:make-config(
    "/alert/mqtt/exception",
    "IoT-Exception-Alert",
    "Alerting config for IoT exception",
    <alert:options/> )
return
alert:config-insert($alert-config)
```

アラートデータを受信したときのアクション定義は以下の通り作成しました。

```xquery:アラートアクション定義
xquery version "1.0-ml";

import module namespace alert = "http://marklogic.com/xdmp/alert" at "/MarkLogic/alert.xqy";
let $action := alert:make-action(
    "mqtt-exception-receive-action",
    "alerting for MQTT IoT exception received.",
    xdmp:modules-database(),
    xdmp:modules-root(),
    "/alert-receive-iot-exception.xqy",
    <alert:options></alert:options> )
return
alert:action-insert("/alert/mqtt/exception", $action)
```

また、アクションとして"alert-receive-iot-exception.xqy"を作成します。
受信したアラートデータから緯度・経度を取得し、MarkLogicのジオ検索を使って最寄りの拠点を検索します。
アラートデータの緯度・経度と、最寄り拠点の緯度・経度をアラート処理結果としてフロントのHTTPサーバにPOSTします。HTTP POSTは[xdmp:http-post](https://docs.marklogic.com/xdmp:http-post)関数を使用します。

```xquery:alert-receive-iot-exception.xqy
xquery version "1.0-ml";

declare namespace alert = "http://marklogic.com/xdmp/alert";

import module "http://marklogic.com/xdmp/alert" at "/MarkLogic/alert.xqy";

(:アラートアクションの外部変数定義:)
declare variable $alert:config-uri as xs:string external;
declare variable $alert:doc as node() external;
declare variable $alert:rule as element(alert:rule) external;
declare variable $alert:action as element(alert:action) external;

(: RaspberryPi3から送られたアラートデータを取得する :)
let $date := $alert:doc//message/date/text()
let $errorMessage := $alert:doc//message/errorMessage/text()
let $picture := $alert:doc//message/picture/text()
let $lat := $alert:doc//message/gps/lat/text()
let $long := $alert:doc//message/gps/long/text()

let $iotPoint := cts:point(xs:double($lat), xs:double($long))

(: 東京メトロの地物情報との距離を算出し近い順にソートする。 :)
let $geoResult :=
for $i in cts:uris("", (), cts:directory-query("/metro/station/","infinity"))
  let $stationDoc := fn:doc($i)
  let $stationName := fn:string($stationDoc/root/dc_title)
  let $stationLat := fn:string($stationDoc/root/geo_lat)
  let $stationLong := fn:string($stationDoc/root/geo_long)
  let $stationPoint := cts:point(xs:double($stationLat), xs:double($stationLong))
  let $distance := geo:distance($iotPoint, $stationPoint)
  let $stationInfo := <result><distance>{$distance}</distance><stationName>{$stationName}</stationName><stationLat>{$stationLat}</stationLat><stationLong>{$stationLong}</stationLong></result>
  order by $distance
  return $stationInfo

(:ブラウザに渡すJSONのアラートデータを組み立てる:)
let $object := json:object()
let $_ := map:put($object, "date", $date)
let $_ := map:put($object, "lat",$lat)
let $_ := map:put($object, "long",$long)
let $_ := map:put($object, "stationLat",$geoResult[1]//stationLat/text())
let $_ := map:put($object, "stationLong",$geoResult[1]//stationLong/text())
let $_ := map:put($object, "stationName",$geoResult[1]//stationName/text())
let $_ := map:put($object, "errorMessage",$errorMessage)
let $_ := map:put($object, "picture",$picture)
let $payload := xdmp:to-json-string($object)

(:アラートデータをリアルタイム通信フロントにPostする:)
let $result := xdmp:http-post(
  "http://[MarkLogic用リアルタイム通信フロントのアドレス:ポート番号]",
  <options xmlns="xdmp:http">
    <data>{$payload}</data>
    <headers>
       <content-type>application/json</content-type>
    </headers>
  </options>)
return $result
```

アラートのルールは以下のように設定しました。
topicが"raspi/Exception"のデータを受信した場合、上記で設定したアラートアクションを実行します。

```xquery:アラートルール定義
xquery version "1.0-ml";
import module namespace alert = "http://marklogic.com/xdmp/alert" 
		  at "/MarkLogic/alert.xqy";

let $rule := alert:make-rule(
    "IoT-exception-rule", 
    "IoT exception rule",
    0, 
    cts:element-word-query(xs:QName("topic"), "raspi/Exception"),
    "mqtt-exception-receive-action",
    <alert:options/> )
return
alert:rule-insert("/alert/mqtt/exception", $rule)
```

以上でMarkLogicのアラート設定が完了になります。topicエレメントが"raspi/Exception"のデータが/mqtt/ディレクトリ配下に登録されると、アクションのXQueryが実行されます。

# MarkLogic用のリアルタイム通信フロントの構築
本連載の[MarkLogicでRaspberryPi3のセンサー情報を取り込んでみよう（6）MQTT＋TLS＋簡易ユーザ認証](https://qiita.com/t2hk/items/5980be3e84f61ea37200)にて、MarkLogic用のMQTTサブスクライバーを作成し、パブリッシュされたデータをMarkLogicに登録しました。

今回はアラートデータを受信した場合、画像データとテキストデータを分けてMarkLogicに登録します。これは、ブラウザにMQTTパブリッシュする際にテキストデータのみをリアルタイムに配信し、データサイズの大きい画像データは非同期で表示させるためです。
まず、受信したトピックから画像データを取得しMarkLogicに登録します。MarkLogicから登録したURIを受け取り、テキストデータのpicture要素にURIを設定してMarkLogicに登録します。

![pic03_alertflow_02.png](https://qiita-image-store.s3.amazonaws.com/0/187299/5320669f-7db7-279b-303b-5d155e31de01.png)

また、MarkLogicがアラートで検出したデータをブラウザにリアルタイムに送信する機能を追加します。
MarkLogicは、RapberryPi3から受信したアラートデータを検知し、アラートアクションを実行します。アラートアクションはHTTP POSTでデータを送信します。これはMQTTブローカー等を経由してブラウザにリアルタイムに送られて描画されます。

![pic05_alertflow_04.png](https://qiita-image-store.s3.amazonaws.com/0/187299/fc28cf57-6dab-d977-6a15-6eec1c8aea70.png)

実際のコードは以下のような感じです。

```js:mqtt_ml_front.js
const express = require('express');
const bodyParser = require('body-parser');
const mqtt = require('mqtt');
const marklogic = require('marklogic');
const my = require('./my-connection.js');
const fs = require('fs');
const to_xml = require('xmljson').to_xml;
const async = require('async');

const TRUSTED_CA = 'CA証明書';

const options = {
  username: 'subscriber',
  password: 'subscriber',
  ca: [fs.readFileSync(TRUSTED_CA)],
  rejectedUnauthorized: false
};

const client = mqtt.connect('mqtts://my-broker:8443', options);
const db = marklogic.createDatabaseClient(my.connInfo);

client.on('connect', () => {
    console.log('subscriber.connected.');
});

client.subscribe('#', (err, granted) => {
    console.log('subscriber.subscribed.');
});

// アラート対象のトピックの場合、データに含まれている写真をMarkLogicに登録し、登録後のURIを取得する。
// 取得したURIをアラートデータのpictureプロパティに設定して戻す。
// 通常のトピックの場合、そのデータをそのまま戻す。
function writePicture(topic, message, dir){
  return new Promise((resolve, reject) => {
  if(topic === "raspi/Exception"){
    var jsonValue = JSON.parse(message);
    var picture = jsonValue.picture;

    var bitmap = new Buffer(picture, 'base64');

     db.documents.write({
        extension: 'jpg',
        contentType: 'image/jpeg',
        directory: dir + "pic/",
        content: bitmap 
     }).result( (response) => {
        var picUri =  response["documents"][0]["uri"];
        jsonValue.picture = picUri;
        console.log("pic uri : " + picUri);
        resolve(JSON.stringify(jsonValue));
     });
  }else{
    resolve(message);
  }});
}

// サブスクライブしているメッセージを受信した場合の処理
client.on('message', (topic, message, packet) => {
  (async function() { 
    let dir = "/mqtt/";
    
    // アラートデータの場合は写真をMarkLogicに登録する。
    let resultMessage = await writePicture(topic, message, dir); 
    let date = Date.now().toString();
    let jsonStr = '{"receiveDate": ' + date + ',"topic":"' + topic + '", "message":' + resultMessage + '}';

    // アラートデータをMarkLogicに登録する。
    // JSONからXMLに変換しているのはMarkLogicでその方が扱いやすいため。
    to_xml(jsonStr, (err, xml) => {
      db.documents.write(
      { extension: 'xml',
        contentType: 'application/xml',
        directory: dir,
        content: xml
      }).result( (response) => {
         console.log(JSON.stringify(response));
      }, (error) => {
         console.log(error);
      });
    }); 
  })(); 
});

// アラートデータ受信用のHTTPサーバ
var app = express();
app.use(bodyParser.urlencoded({
  limit:'1mb',
  extended: true
}));
app.use(bodyParser.json());

app.listen(3000);
console.log('Alert HTTP Server is online.');

// MarkLogicのアラートからHTTP POSTを受信した場合、アラート用のトピックでメッセージをパブリッシュする。
app.post('/', (req, res) => {
  res.setHeader('Content-Type', 'text/plain');
  var sendJson = JSON.stringify(req.body);

  let topic = "ml/Exception";
  client.publish(topic, sendJson);
});
```

# Raspberry Pi3からアラートデータをMQTTパブリッシュする
[前々回](https://qiita.com/t2hk/private/9724158b7db252bbc98d)に、ラズパイからGPSとセンサーデータをMQTTパブリッシュしました。今回はこれを改造して、カメラで写真撮影してアラートデータもパブリッシュしてみます。
センサーデータのトピックは「raspi/topic1」としましたが、今回のアラートデータのトピックは「raspi/Exception」とします。MarkLogicのリアルタイムアラートは、この「raspi/Exception」トピックに反応します。

なお、プログラム中で本当にエラーを発生させるのではなく、折角Sense HATを搭載しているのでジョイスティックを操作したら写真撮影とアラートデータを送信するようにしています。

```py3:RaspberryPi3パブリッシュ用
import io
from time import sleep
import paho.mqtt.client as mqtt
from datetime import datetime
from gps3 import gps3
import json
from sense_hat import SenseHat
import picamera
import base64

sense = SenseHat()

gps_socket = gps3.GPSDSocket()
data_stream = gps3.DataStream()
gps_socket.connect()
gps_socket.watch()

# MQTTブローカーの情報
host = 'my-broker'
port = 8443
topic = 'raspi/topic1'
username = 'publisher'
password = 'publisher'

# MQTTブローカーへの接続処理
def on_connect(client, userdata, flags, result):
  print("Connected result : " + str(result))

# GPSの情報を取得してディクショナリで返却する。
def get_gps_data():
  for new_data in gps_socket:
    if new_data:
      data_stream.unpack(new_data)
      time = data_stream.TPV['time']
      lat = data_stream.TPV['lat']
      long = data_stream.TPV['lon']
      alt = data_stream.TPV['alt']
      speed = data_stream.TPV['speed']
      gpsDict = {
        'time': time,
        'lat' : lat,
        'long' : long,
        'alt' : alt,
        'speed': speed
      }
      return gpsDict

# Sense HATの温度、気圧、湿度を取得してディクショナリで返却する。
def get_sense_hat():
  t = sense.get_temperature()
  p = sense.get_pressure()
  h = sense.get_humidity()
  senseHatDict = {
    'temperature': t,
    'pressure': p,
    'humidity': h
  }
  return senseHatDict

# カメラで写真を撮影する。MQTTで送信するためBase64で文字列化しておく。
def get_picture():
  try:
    camera = picamera.PiCamera()
    camera.resolution = (150,150)
    stream = io.BytesIO()
    camera.capture(stream, format='jpeg')
    pic = base64.b64encode(stream.getvalue()).decode('utf-8')
    return pic
  finally:
    camera.close()

if __name__ == '__main__':
  # MQTTブローカーに接続する。TLSでユーザ認証あり。
  client = mqtt.Client(protocol=mqtt.MQTTv311)
  client.username_pw_set(username, password)
  client.on_connect = on_connect
  client.tls_set("my_ca.crt")
  client.connect(host, port=port, keepalive=60)
  
  # 送信するメッセージ
  dictMessage = {
    "username": username
  }

  # 3秒間隔でMQTTにGPS情報等を送信する。
  while True:
    messageStr = ""
    pic = ""

    dateTime = datetime.now()

    # ジョイスティックが操作されたら、写真を撮影してアラート用のTopicを送信する。
    for event in sense.stick.get_events(): 
      if(event.action is "pressed"):
        print(event.direction, event.action)
        topic = 'raspi/Exception'
        messageStr = "何かエラーが発生した模様です。"
        pic = get_picture()

    # GPSのデータを取得する。
    gpsDict = get_gps_data()

    # Sense HATのデータを取得する。
    senseHatDict = get_sense_hat()

    # 送信するメッセージのJSONを組み立てる。
    dictMessage.update({"date":str(dateTime)})
    dictMessage.update({"errorMessage":messageStr})
    dictMessage.update({"picture":pic})
    dictMessage.update({"gps":gpsDict})
    dictMessage.update({"senseHat":senseHatDict})

    # メッセージをパブリッシュする。次の処理に備えてトピックを元に戻しておく。
    client.publish(topic, json.dumps(dictMessage))
    topic = 'raspi/topic1'
    sleep(3)
```

以上で、ラズパイに搭載したSense HATのジョイスティックを操作すると写真を撮影してアラートデータをパブリッシュするようになりました。このデータはMQTTブローカーやMarkLogic用リアルタイムフロントを経由してMarkLogicに登録します。MarkLogicはアラートデータが登録されるとリアルタイムアラートで検知します。

# 監視用のWebアプリの開発
最後にアラートの監視用のWebアプリケーションを作成します。これは[前回](https://qiita.com/t2hk/private/ef98f76432733c937b54)ご紹介したMarkLogicのHTTPサーバを用いてXQueryでHTMLを返す方式とします。
ブラウザから監視用のWebアプリにアクセスするとXQueryがHTMLを返します。このHTMLにはMQTTサブスクライバーが搭載されており、MarkLogicからのアラートデータをリアルタイムに描画します。
MarkLogicはアラートデータが登録されるとアラート機能で検知し、アラートアクションを実行します。アラートアクションはアラートデータを作成してHTTP POSTでリアルタイムフロントに送信します。リアルタイムフロントはこれをMQTTパブリッシュしてブラウザに送信します。
アラートデータを受信したブラウザはテキストデータを即時に描画します。続いて、画像データやGoogleマップの描画を行います。

今回ブラウザに表示する内容は以下の通りです。

* アラートの発生源や最寄駅の情報をテキスト表示する
* アラート発生源の緯度・経度を元に、Google Map上にマーカーを表示する
* アラート発生源のマーカーには、発生時刻とメッセージ、写真を表示する
* アラート発生源の最寄駅にもマーカーを表示する
* 最寄駅は[以前の投稿](https://qiita.com/t2hk/items/7bf80ae1c6f0a126b661)で取り込んだ東京メトロの地物情報からMarkLogicのジオ検索で求めたものを使用する

![pic04_alertflow_03.png](https://qiita-image-store.s3.amazonaws.com/0/187299/e058e85e-343e-bc83-9c32-c4bcfc19ee03.png)

XQueryは以下の通りです。
リアルタイムに画面表示する処理はJavascriptファイル"raspi-alert-draw.js"に定義しています。
また、MQTTの通信処理は"raspi-alert-mqtt.js"に定義しています。
Google Maps APIを利用するため、そのAPIキーを定義します。Google Maps APIを使用するにはAPIキーの取得が必要です。

```xquery:raspi-alert-visualize.xqy
xquery version "1.0-ml";

import module namespace r="get-raspi-data" at "/iot/get-raspi-xml-data.xqy";

xdmp:set-response-content-type("text/html"),
'<!DOCTYPE html>',
<html xmlns="http://www.w3.org/1999/xhtml" lang="ja">
<head>
    <link rel="stylesheet" type="text/css" href="myApps.css" />
    <title>Raspberry Pi3 Alerting</title>
    <script src="https://ajax.googleapis.com/ajax/libs/jquery/3.2.1/jquery.min.js"></script>
    <script src="https://cdnjs.cloudflare.com/ajax/libs/paho-mqtt/1.0.1/mqttws31.js" type="text/javascript"></script>
    <script src="https://cdnjs.cloudflare.com/ajax/libs/Chart.js/2.7.1/Chart.bundle.min.js"></script>
    <script src="https://maps.googleapis.com/maps/api/js?key=[APIキー]"></script>
    <script src="raspi-alert-draw.js"></script>
    <script src="raspi-alert-mqtt.js"></script>
</head>

<body>
    <div id="map"></div>

    <div>
      <div id="error_message"></div>
      <p><label>発生時刻 : </label><strong id="alert_time"></strong></p>
    </div>
    <div>
      <div class="box"><legend>発生源</legend></div>
      <div class="box">
        <p><label>緯度 : </label><strong id="source_lat"></strong></p>
        <p><label>経度 : </label><strong id="source_long"></strong></p>
      </div>
    </div>
    <div>
      <div class="box"><legend>最寄地点</legend></div>
      <div class="box">
      <p><label>駅名 : </label><strong id="near_place_name"></strong></p>
      <p><label>緯度 : </label><strong id="near_place_lat"></strong></p>
      <p><label>経度 : </label><strong id="near_place_long"></strong></p>
     </div>
    </div>
</body>
</html>
```

次に、リアルタイムに画面表示する処理を定義したJavascriptファイル"raspi-alert-draw.js"を記します。主に以下のような処理を行っています。

* アラートデータを画面にテキスト表示する
* Google Map上にアラート発生源と最寄駅にマーカーを表示する
* 各マーカーにはアラート情報のラベルを表示する
* 発生源のマーカーにはRaspberryPi3が撮影した写真を表示する
* 地図の表示位置は、アラート発生源と最寄駅が表示されるように自動調整する

```javascript:raspi-alert-draw.js
var myAlertApps = new Object();

// 画面表示処理
myAlertApps.draw = function(jsonData){
  console.log(jsonData);

  var json = JSON.parse(jsonData);
  var alertTime = json.date;
  var sourceLat = json.lat;
  var sourceLong = json.long;
  var stationName = json.stationName;
  var stationLat = json.stationLat;
  var stationLong = json.stationLong;
  var errorMessage = json.errorMessage;
  var picUri = json.picture;

  // 画面にテキスト情報を設定する。
  $("#alert_time").text(alertTime);
  $("#source_lat").text(sourceLat);
  $("#source_long").text(sourceLong);
  
  $("#near_place_name").text(stationName);
  $("#near_place_lat").text(stationLat);
  $("#near_place_long").text(stationLong);

  $("#error_message").text(errorMessage);

  // Google Maps APIに関する処理
  var map = new google.maps.Map(document.getElementById('map'));
  
  // アラートの発生した場所と最寄り駅の座標
  var sourcePosition = new google.maps.LatLng(sourceLat, sourceLong);
  var stationPosition = new google.maps.LatLng(stationLat, stationLong);
  
  // アラート発生場所のマーカーに表示する内容を組み立てる。
  // 写真は"get-pic.xqy"というクエリを使ってMarkLogicから取得する。
  var contentHtml = '<div class="box"><p>' + errorMessage + '</p>' 
         + '<p>発生時刻 : ' + alertTime + '</p></div>'
         + '<div class="box"><img src="get-pic.xqy?uri=' + picUri + '"></img></div>';

  // アラートの発生した場所のマーカー
  var sourceInfo = new google.maps.InfoWindow({
    content: contentHtml 
  });

  // 最寄駅のマーカー
  var stationInfo = new google.maps.InfoWindow({
    content: '<div>最寄駅は ' + stationName + 'です。 </div>'
  });

  // 発生源と最寄駅のマーカーの設定
  var sourceMarker = new google.maps.Marker({
    position: sourcePosition,
    map: map
  });
  var stationMarker = new google.maps.Marker({
    position: stationPosition,
    map: map
  });

  sourceInfo.open(map,sourceMarker);
  stationInfo.open(map,stationMarker);

  // 地図の表値位置をマーカーの位置から自動設定する
  var bounds = new google.maps.LatLngBounds();
  bounds.extend(sourcePosition);
  bounds.extend(stationPosition);
  map.fitBounds(bounds);

  map.addListener('click', (e) => {
     console.log("lat:" + e.latLng.lat() + " lng:" + e.latLng.lng());
  });
}
```

また、上記で使用したMarkLogicから画像データを取得するクエリは以下になります。
取得する画像のURIはURLパラメータで指定します。これはimgタグで使用します。

```xquery:get-pic.xqy
xquery version "1.0-ml";

let $uri := xdmp:get-request-field("uri","")

return
fn:doc($uri)
```

HTMLで画像描画する際には以下のように使用します。

```HTML:XQueryで画像取得してHTMLに描画する例
<img src="get-pic.xqy?uri=' + picUri + '"></img>
```

最後にMQTTサブスクライバーです。アラート用のトピック「topic/Exception」をサブスクライブし、受信したら画面の描画処理を実行します。

```javascript:raspi-alert-mqtt.js
var clientAlert = new Paho.MQTT.Client("MQTTブローカーのIPアドレス等", [ポート番号], "client2");

clientAlert.onConnectionLost = onConnectionLost;
clientAlert.onMessageArrived = onMessageArrived;

// ブローカーに接続する。
clientAlert.connect({
  userName:"subscriber",password:"subscriber",
  onSuccess:onConnect,
  onFailure:failConnect
});

// ブローカーに接続したときの処理。アラート用のトピックをサブスクライブする。
function onConnect() {
  console.log("[Alert] onConnect");
  clientAlert.subscribe("ml/Exception");
}

// 接続に失敗した場合の処理。
function failConnect(e){
  console.log("[Alert] connect fail");
  console.log(e);
}

// 接続が途切れたときの処理。
function onConnectionLost(responseObject) {
  if (responseObject.errorCode !== 0) {
    console.log("[Alert] onConnectionLost:"+responseObject.errorMessage);
  }
}

// メッセージを受信したときの処理。
function onMessageArrived(message) {
  console.log("alert receive :"+message.payloadString);
  myAlertApps.draw(message.payloadString);
}
```

# おしまい
今回はこれまでのアドベントカレンダーで連載してきた内容を使い、さらに+αとしてカメラも扱ってみました。
エラー発生させる契機としてジョイスティックの操作を代用しましたが、人感センサーなどを繋げば不法侵入のリアルタイム検知などに応用できます。

MarkLogicはスキーマレスのDBですので、RDBのように最初にデータ構造を決める必要が無くさっとシステムを構築し始めることができます。しかもデータ構造の変更やXML/JSONなどフォーマットの変更にも強い点がメリットです。

今回も当初はJSONのみで実装しようと考えていましたが、途中からXMLも使っています。例えばIoTデバイスのようにリソースに制約があるような環境ではデータサイズが小さいJSONで処理し、リソースに余裕のあるサーバサイドでは可読性が高く高機能なXMLで処理する、など、状況に応じてデータ形式を使い分けることも可能です。
また、GPSや温度・湿度・気圧、画像など多くのデータを徐々に追加しデータ構造も変化しましたが、その変更も簡単に取り込むことが出来ました。

MarkLogicは、漠然としたアイデアを元にとにかく動くものを作ってみよう、というアジャイル的な開発には最適なデータベースだと思います。

本日でこのアドベントカレンダーも終了です。これまでお付き合い下さった皆様、ありがとうございました。