MarkLogicでRaspberryPi3のセンサー情報を取り込んでみよう（5）MarkLogicを使ったジオコーディング

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

# はじめに
[MarkLogicでRaspberryPi3のセンサー情報を取り込んでみよう（3）位置情報を含むオープンデータを取り込んでみる](https://qiita.com/t2hk/items/7bf80ae1c6f0a126b661)で、東京メトロさんがオープンデータとして公開している駅等の地物情報をMarkLogicに取り込みました。
この地物情報のJSONデータには、駅の名前や緯度・経度は含まれていますが住所は含まれていません。

今回こそはMQTTを導入しようと思いましたが、地物情報に住所が無いことが気になったので、MarkLogicの位置情報検索を使用して、緯度・経度から住所を求めてみようと思います。

国土交通省が公開している[位置参照情報](http://nlftp.mlit.go.jp/isj/)をMarkLogicに取り込み、東京メトロのオープンデータの地物情報の緯度・経度から、各駅の住所を紐付けてみます。

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

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

# 位置参照情報のロード
国土交通省の位置参照情報はCSV形式のデータです。カラムは以下の通りです。

"都道府県名","市区町村名","大字・町丁目名","街区符号・地番","座標系番号","Ｘ座標","Ｙ座標","緯度","経度","住居表示フラグ","代表フラグ","更新前履歴フラグ","更新後履歴フラグ"

MarkLogicでこのデータを扱うにはXMLかJSONに変換する必要があります。
MarkLogicのデータローダーである[MLCP](https://developer.marklogic.com/products/mlcp)を使えば、CSV形式のデータをXMLに変換した上でロードしてくれます。CSVのカラムがXMLのエレメントになります。そして、CSVの1行が1XMLファイルに変換されます。つまり、CSVの行数と同じ数のXMLファイルが作成されることになります。

MLCPの実行例は以下になります。Windowsのコマンドラインでの実行方法になります。
以下を実行すると、MarkLogicの"/GeoData/"ディレクトリ配下にXMLファイルとしてロードされます。

```
@echo off

set MLCP_BIN=[MLCPの実行バッチファイルの置き場所]
set CURRENT_DIR=[位置参照情報を格納したディレクトリのフルパス]

set ML_HOST=[ロード先のMarkLogicサーバのホスト名・IPアドレス]
set ML_PORT=[ロード先のXDBCサーバのポート番号]
set ML_USER=[ロードするユーザのID]
set ML_PASSWORD=[ロードするユーザのパスワード]

call %MLCP_BIN%mlcp.bat import ^
-host %ML_HOST% ^
-port %ML_PORT% ^
-username %ML_USER% ^
-password %ML_PASSWORD% ^
-input_file_path %CURRENT_DIR%位置参照情報.csv ^
-input_file_type delimited_text ^
-delimited_root_name root ^
-output_uri_replace "%CURRENT_DIR%,''" ^
-output_uri_prefix /GeoData ^
-generate_uri ^
-output_uri_suffix .xml
```

ロード結果の一部を確認してみます。

```
for $i in xdmp:directory("/GeoData/")[1]
 return $i

<root>
  <都道府県名>東京都</都道府県名>
  <市区町村名>千代田区</市区町村名>
  <大字・町丁目名>飯田橋二丁目</大字・町丁目名>
  <街区符号・地番>18</街区符号・地番>
  <座標系番号>9</座標系番号>
  <Ｘ座標>-33359.3</Ｘ座標>
  <Ｙ座標>-7436.2</Ｙ座標>
  <緯度>35.699289</緯度>
  <経度>139.751162</経度>
  <住居表示フラグ>1</住居表示フラグ>
  <代表フラグ>1</代表フラグ>
  <更新前履歴フラグ>0</更新前履歴フラグ>
  <更新後履歴フラグ>1</更新後履歴フラグ>
</root>
```

# インデックスを設定する
国土交通省の位置参照情報にインデックスを設定します。[MarkLogicでRaspberryPi3のセンサー情報を取り込んでみよう（4）位置情報を検索してみる](https://qiita.com/t2hk/items/26c84ce208cbf1988f10)と同様の手順になります。

WebブラウザでMarkLogicの管理画面（8001番ポート）にアクセスし、「geospatial element pair indexes」を作成します。
設定値は以下の通りです。下記以外はデフォルト値としました。

| 項目名 | 設定値 |
| --- | --- |
| parent localname | root |
| latitude localname | 緯度 |
| longitude localname | 経度 |
| range value positions | true |

# 地物情報に位置参照情報を紐付ける
XQueryで、東京メトロの地物情報の緯度・経度を元に、最も近い住所を国土交通省の位置参照情報から取得して紐付けてみます。

まず、地物情報の座標を中心に半径0.1マイルに含まれる位置参照情報を検索します。
次に、その位置参照情報と地物情報の距離を算出し、最も近い場所を特定します（距離の昇順にソートした最初の要素）。

```
xquery version "1.0-ml";

(: 東京メトロの地物情報1件ずつ処理する。 :)
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)
  
  (: 地物情報の座標を中心に半径0.1マイルの位置参照情報を検索する。 :)
  let $stationPoint := cts:point(xs:double($stationLat), xs:double($stationLong))
  let $neighborArea :=
    cts:search(xdmp:directory("/GeoData/"), 
     cts:element-pair-geospatial-query(
           xs:QName("root"), xs:QName("緯度"), xs:QName("経度"),
           cts:circle(0.1, $stationPoint )))

  (: 地物情報と位置参照情報の距離を算出し、その昇順でソートする。
     ソートした1件目が最も近い住所となる。 :)
  let $neighborAreaOrdered :=
    for $i in $neighborArea
      let $foreignPoint := cts:point($i/root/緯度, $i/root/経度)
      let $distance := geo:distance($stationPoint, $foreignPoint)
      order by $distance
      return $i

  (: 地物情報と位置参照情報の1件目（最短距離の住所）をXMLとして返却する。 :)
  return
    <station>
      <name>{$stationName}</name>
      <lat>{$stationLat}</lat>
      <long>{$stationLong}</long>
      <nearest>
        <prefecture>{$neighborAreaOrdered[1]/root/都道府県名/text()}</prefecture>
        <city>{$neighborAreaOrdered[1]/root/市区町村名/text()}</city>
        <town>{$neighborAreaOrdered[1]/root/大字・町丁目名/text()}</town>
        <parcel-number>{$neighborAreaOrdered[1]/root/街区符号・地番/text()}</parcel-number>
        <lat>{$neighborAreaOrdered[1]/root/緯度/text()}</lat>
        <long>{$neighborAreaOrdered[1]/root/経度/text()}</long>
      </nearest>
   </station>
```

結果は以下のようになります。

```
<station>
  <name>水天宮前出入口1b</name>
  <lat>35.681652908668</lat>
  <long>139.78662537626</long>
  <nearest>
    <prefecture>東京都</prefecture>
    <city>中央区</city>
    <town>日本橋箱崎町</town>
    <parcel-number>22</parcel-number>
    <lat>35.681371</lat>
    <long>139.786314</long>
  </nearest>
</station>
```

これらのデータをxdmp:document-insertでMarkLogicに登録しておけば、現在地から近い駅の情報などを検索できるようになります。

# おわりに
今回は異なるオープンデータの座標情報を使って、座標間の距離が最短の住所を取得してみました。
IoTデバイス等のGPS情報をと連携したアプリケーションの開発も容易に実現できます。

# 参考情報
本記事では以下のデータを活用させて頂きました。

東京メトロオープンデータ(https://developer.tokyometroapp.jp/info)
国土交通省 位置参照情報(http://nlftp.mlit.go.jp/isj/)