MarkLogicでRaspberryPi3のセンサー情報を取り込んでみよう（3）位置情報を含むオープンデータを取り込んでみる

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

#はじめに
[前回](https://qiita.com/t2hk/items/a5f8b2cef781d7d16251)の次回予告でMQTTを導入する旨を記しましたが、ちょっと気が変わりました。
せっかくMarkLogic+Node.js環境を構築したのですから、もう少しこの環境で遊ぼうと思います。

#東京メトロのオープンデータを取り込んでみる
東京メトロさんがオープンデータとして、東京メトロの鉄道運行情報や駅などの地物情報をJSON形式で公開しています。(https://developer.tokyometroapp.jp/info)

利用する際にはユーザ登録を行い、アクセストークンを入手する必要があります。
オープンデータにアクセスするためのAPIは2種類あります。

* データ取得・検索API (datapoints)
    *  鉄道の運行情報など、緯度経度の地理情報を持たないもの
    *  クエリパラメータにマッチするデータを取得するAPI
* 地物情報取得・検索API (places) 
    * 駅や施設の位置情報
    * 緯度経度など地理情報で絞り込む

今回は地物情報取得・検索APIを使用してみようと思います。

本APIを利用する際には利用規約をご確認下さい。
東京メトロオープンデータAPI利用許諾規約(https://developer.tokyometroapp.jp/terms.html)

また、以下の記事を参考にさせて頂きました。ありがとうございます。
[東京メトロAPIを使ったアプリケーションを作ってみよう](https://qiita.com/mima_ita/items/ac8efe441b6c7f2165e2)

# 環境
以下の環境を使用します。

| 環境 | バージョン |
| --- | --- |
| CentOS7 | 7.4.1708 |
| Node.js | v8.9.1 |
| MarkLogic9 | 9.0-3 |
| MarkLogic Node.js Client API | 2.0.3 |

#MarkLogicの位置検索機能
MarkLogicは緯度経度などの位置情報を扱うことができ、位置や近接度、距離による検索が可能です。
扱える座標系も緯度経度の地球座標だけでなく、X軸・Y軸のユークリッド空間を扱えます。
デフォルトの座標系はWGS84（World Geodetic System）です。

搭載している位置検索は、ポイント検索、ボックス検索、半径検索、多角形検索になります。

* ポイント検索
    * 緯度、経度などで定められる1点にマッチする座標を検索する
* ボックス検索
    * 長方形内部の任意のポイントにマッチする座標を検索する
* 半径検索
    * あるポイントからの指定した距離範囲の任意のポイントにマッチする座標を検索する
* 多角形検索
    * 指定した多角形内部の任意のポイントにマッチする座標を検索する

今回使用する東京メトロの地物情報には、緯度・経度の情報が含まれています。このデータに対してMarkLogicの位置検索機能を使用してみようと思います。

なお、MarkLogicの位置検索機能を使用するにはオプションのライセンスが必要になります。
開発者ライセンスはお試しとして使用できますが、商用で利用する場合は本体のラインセスとは別に、オプションライセンスが必要になるそうです。ご注意下さい。

# 地物情報取得・検索APIからJSONデータをMarkLogicに取り込む
やることは単純で、API仕様に従ってHTTPSのGETでアクセスしてデータを取得し、MarkLogicに書き込むだけです。
データを取得する際にはアクセストークンを指定する必要があり、これは秘密にしておく必要があります。

なお、本データをMarkLogicに登録する際に、多少データを加工します。

* JSONのプロパティ名に含まれる"@"や":"を置換する
    * JSONのプロパティにこれらの文字が含まれていると正常に検索できない模様なので置換・除去します
    * 何か解決方法をご存じでしたらお教え下さい・・・
* JSONデータにルート要素を加える
    * 本データはルート要素直下に各プロパティが設定されています
    * 位置情報を検索する際に、座標用のプロパティに親プロパティが存在した方が扱いが楽になるため、ルート要素を追加します

で、作成したNode.jsアプリケーションは以下になります。
MarkLogicへの接続方法は[2回目](https://qiita.com/t2hk/items/a5f8b2cef781d7d16251)と同じです。

```js
const request = require('request');
const marklogic = require('marklogic');
const my = require('./my-connection.js');
const db = marklogic.createDatabaseClient(my.connInfo);

// アクセストークンは秘密です。
const token = 'ACL_CONSUMERKEY';

// 取得する地物情報の範囲（緯度、経度、半径）
const lat = 緯度;
const lon = 経度;
const radius = 半径（マイル）;

// 地物情報取得・検索API
const placeApi = 'https://api.tokyometroapp.jp/api/v2/places?rdf:type=ug:Poi';

// APIにアクセスし、DBに書き込む。
let placeJson = request.get({
  url: placeApi,
  qs:{
    "lat": lat,
    "lon": lon,
    "radius": radius,
    "acl:consumerKey": token
  }
}, (error, response, body) => {
  //@や:は使用できない模様のため、プロパティ名を置換する。
  let placeJsonString = body 
    .replace(/@id/g,"id")
    .replace(/@type/g,"type")
    .replace(/dc:/g,"dc_")
    .replace(/ug:/g,"ug_")
    .replace(/geo:/g,"geo_")
    .replace(/ugsrv:/g,"ugsrv_")
    .replace(/@context/g,"context")
    .replace(/ug:/g,"ug_");

  let placeJsonObj = JSON.parse(placeJsonString);

  // DBに書き込む。URIは自動的に付与される（/metro/station/配下）。
  placeJsonObj.forEach( (val, key) => {
    // 取得したJSONデータの親要素としてrootを設定する
    let jsonString = '{"root":' + JSON.stringify(placeJsonObj[key]) +"}";

    db.documents.write(
      { extension: 'json',
        directory: '/metro/station/',
        content: JSON.parse(jsonString)
      }
    ).result( (response) => {
         console.log(JSON.stringify(response));
      }, (error) => {
         console.log(error);
      });
  });
}); 
```

実行すると、/metro/station/配下に各施設のJSONデータが格納されます。

# 位置検索の概要
ここで簡単ですが、MarkLogicの位置検索機能について概要を記します。

MarkLogicの位置検索は、ポイント検索と領域検索の2種類の検索方法があります。

ポイント検索は、ドキュメント内に記された座標を検索対象とし、検索条件で指定した領域と比較します。
領域検索は、ドキュメント内に記された領域を検索対象とし、検索条件で指定した領域と比較します。
ポイント検索の場合は、「この領域内に含まれる座標を持つドキュメントを検索する」などになります。
領域検索の場合は、「この領域と交差する領域を持つドキュメントを検索する」などになります。

今回は領域を持つデータが無いため、ポイント検索について記します。

検索には[cts:search](https://docs.marklogic.com/cts:search)([cts.search](https://docs.marklogic.com/cts.search))を使います。括弧内はJavascriptの場合の型です。
この第1引数には検索対象のドキュメント範囲を指定します。
第2引数には検索条件を表す[cts:query](https://docs.marklogic.com/cts:query)([cts.query](https://docs.marklogic.com/cts.query))を指定します。

第1引数は、「特定のディレクトリ配下のドキュメントに対して検索する」、「特定の文字列を保持するドキュメントに対して検索する」、のように検索範囲を指定します。
例えば、特定のディレクトリ配下の全ドキュメントを対象にする場合は以下のようになります。

```
xdmp:directory("検索対象のディレクトリのURI", "infinity")
```

第2引数はcts:query型の位置検索用の関数で、検索条件を指定します。主に以下になります。
これらは、検索対象のドキュメントの種類（XML、JSON）やドキュメント内で位置情報をどのように表現しているか（座標情報を表すエレメント、属性、プロパティや、緯度経度の表現方法）によって使い分けます。

* [cts:element-attribute-pair-geospatial-query](https://docs.marklogic.com/cts:element-attribute-pair-geospatial-query)([cts.elementAttributePairGeospatialQuery](https://docs.marklogic.com/cts.elementAttributePairGeospatialQuery))
* [cts:element-pair-geospatial-query](https://docs.marklogic.com/cts:element-pair-geospatial-query)([cts.elementPairGeospatialQuery](https://docs.marklogic.com/cts.elementPairGeospatialQuery))
* [cts:element-geospatial-query](https://docs.marklogic.com/cts:element-geospatial-query)([cts.elementGeospatialQuery](https://docs.marklogic.com/cts.elementGeospatialQuery))
* [cts:element-child-geospatial-query](https://docs.marklogic.com/cts:element-child-geospatial-query)([cts.elementChildGeospatialQuery](https://docs.marklogic.com/cts.elementChildGeospatialQuery))
* [cts:json-property-child-geospatial-query](https://docs.marklogic.com/cts:json-property-child-geospatial-query)([cts.jsonPropertyChildGeospatialQuery](https://docs.marklogic.com/cts.jsonPropertyChildGeospatialQuery))
* [cts:json-property-geospatial-query](https://docs.marklogic.com/cts:json-property-geospatial-query)([cts.jsonPropertyGeospatialQuery](https://docs.marklogic.com/cts.jsonPropertyGeospatialQuery))
* [cts:json-property-pair-geospatial-query](https://docs.marklogic.com/cts:json-property-pair-geospatial-query)([cts.jsonPropertyPairGeospatialQuery](https://docs.marklogic.com/cts.jsonPropertyPairGeospatialQuery))
* [cts:path-geospatial-query](https://docs.marklogic.com/cts:path-geospatial-query)([cts.pathGeospatialQuery](https://docs.marklogic.com/cts.pathGeospatialQuery))

cts:queryには検索条件とする領域も指定します。領域用の型として以下を使用します。
例えばcts:boxならば指定した座標で囲われた四角形の範囲に含まれるドキュメントを検索します。
cts:circleならば、指定した中心座標と半径に囲われた円形の範囲に含まれるドキュメントを検索します。

* [cts:box](https://docs.marklogic.com/cts:box)([cts.box](https://docs.marklogic.com/cts.box))
* [cts:circle](https://docs.marklogic.com/cts:circle)([cts.circle](https://docs.marklogic.com/cts.circle))
* [cts:complex-polygon](https://docs.marklogic.com/cts:complex-polygon)([cts.complexPolygon](https://docs.marklogic.com/cts.complexPolygon))
* [cts:linestring](https://docs.marklogic.com/cts:linestring)([cts.linestring](https://docs.marklogic.com/cts.linestring))
* [cts:point](https://docs.marklogic.com/cts:point)([cts.point](https://docs.marklogic.com/cts.point))
* [cts:polygon](https://docs.marklogic.com/cts:polygon)([cts.polygon](https://docs.marklogic.com/cts.polygon))

例えば、先ほどロードした東京メトロのデータを検索対象とする場合、以下のようになります。
rootプロパティの子要素であるgeo_latとgeo_longに緯度と経度の情報を保持しています。
それらを第1から第3引数で指定します。
第4引数には検索範囲を指定します。下記の例ではcts:pointで指定した緯度経度に一致するドキュメントを検索します。

```xquery
cts:json-property-pair-geospatial-query("root", "geo_lat", "geo_long", cts:point(緯度, 経度))
```

# 位置情報を検索してみる
取り込んだ位置情報を検索してみます。WebブラウザでQueryConsole（8000番ポート）にアクセスしてクエリを実行してみます。

## XQueryでの検索
緯度経度と半径を指定してその円内に含まれる場所を検索してみます。半径の単位はマイルのみです。

```xquery
xquery version "1.0-ml";

import module namespace geo = "http://marklogic.com/geospatial"
   at "/MarkLogic/geospatial/geospatial.xqy";

(: 検索する半径と緯度・経度 :)
let $circle := cts:circle(半径（マイル）, 緯度, 経度)
return 
cts:search(xdmp:directory("/metro/station/"), 
   cts:json-property-pair-geospatial-query("root", "geo_lat", "geo_long", $circle))
```

#今回はこの辺にて
少し長くなりましたので、今回はここまでにして明日に続けます。
今回は位置情報を含むオープンデータをMarkLogicに登録し、XQueryで位置検索するまで実施しました。
位置検索ができるようになるとGPSを搭載したIoT機器から位置情報を受信し、その近辺の情報を検索して返却したり、IoTのセンサー情報と位置情報を組み合わせてDBに保存し活用できるようになります。

#次回予告
次はNode.jsアプリケーションから、MarkLogicのクライアントAPIを使用して位置検索する方法を記します。