Image
Top
Navigation

OrientDB Luceneの空間インデックス

それでは、OrientDBでluceneのSpatial indexを試してみましょう。

OrientDB では lucene を使ってポイント型の Spatial Index を生成することが可能です。

現時点では Point型のみですが、今後他の幾何型のデータにも対応していく予定とのことです。

準備

Schemaの設定

まず、今回は以下のようなClass(テーブル)を作成します。

名称 プロパティ名
名前 name OType.STRING
緯度 latitude OType.DOUBLE
経度 longitude OType.DOUBLE
住所 address OType.STRING
価格 value OType.INTEGER

そちらに、少量のデータではつまらないので日本全国の郵便局データを入れて試します。価格フィールドに関しては、適当な値をいれますね。サンプルとしてわかりづらくてすいません。

まず、SQLで作成する場合ですが、

CREATE CLASS Point
CREATE PROPERTY Point.name STRING
CREATE PROPERTY Point.latitude DOUBLE
CREATE PROPERTY Point.longitude DOUBLE
CREATE PROPERTY Point.address STRING
CREATE PROPERTY Point.value INTEGER

Spatil Index の設定

次に緯度、経度に関してSpatil indexを貼ります。

CREATE INDEX Point.latitude_longitude ON Point(latitude,longitude) SPATIAL ENGINE LUCENE 

OrientDB-Luecenが正しくインストールされていない場合は、エラーがでます。

それでは、OrientStudio の Scheme 画面から確認してみましょう。
SnapCrab_Desktop_2014-10-23_21-19-54_No-00

データの登録

次にデータを登録します。先程も書きましたが、少量のデータではつまらないので日本全国の郵便局データ24526件を入れて試します。
早速ですが、コードを見てみましょう。

登録プログラムのサンプル

package hogehoge;

import java.io.File;
import java.io.IOException;
import java.util.List;
import java.util.Random;

import jp.sf.orangesignal.csv.Csv;
import jp.sf.orangesignal.csv.CsvConfig;
import jp.sf.orangesignal.csv.handlers.StringArrayListHandler;

import com.orientechnologies.orient.core.db.document.ODatabaseDocumentTx;
import com.orientechnologies.orient.core.record.impl.ODocument;

public class Spatial {

    public static void main(String[] args) throws IOException {

        ODatabaseDocumentTx database = new ODatabaseDocumentTx("remote:localhost/indextest");

        String className = "Point";
        try {
            database.open("admin", "admin");

            // 日本全国の郵便局の名前、住所、緯度、経度、管理番号が入ったCSV
            // こちらOrientDBとは関係ありません、CSVをパスしたものが一行毎にStringの配列に入っているものとしています
            List<String[]> postOffices = Csv.load(new File("P30-13_UTF8.csv"), new CsvConfig(), new StringArrayListHandler());

            final ODocument point = new ODocument();

            for (String[] postOffice : postOffices) {

                point.reset();
                point.setClassName(className);
                point.field("name", postOffice[0]);

                point.field("address", postOffice[1]);
                point.field("longitude", postOffice[2]);
                point.field("latitude", postOffice[3]);

                // java.util.Random を使って適当に大きな値を作成
                Random rnd = new Random();
                int ran = (rnd.nextInt(10) + 10) * 1000000;

                point.field("value", ran);

                point.save();

            }
            System.out.println("終了");

        } finally {
            database.close();
        }
    }

}

特にluceneによる幾何型のindexを張ってあるということを意識する必要はなく、普通にdouble型のフィールドに緯度と経度を登録することができます。
Point型だけに関して言えば、この手軽さががPostgresSQL等を使用している場合との違いかも知れません。

データを検索する

それでは、上記のプログラムにて正しく登録されているかカウントしてみましょう。

SELECT count(*) from Point

SnapCrab_Desktop_2014-10-23_22-34-59_No-00
24526正しく登録されていますね

それでは実際にLucene Indexを利用して検索しみましょう。

次に私の所属するビーグッド・テクノロジーの事務所近辺の1キロ以内のポイントを検索します。

SELECT * from Point where [latitude,longitude,$spatial] NEAR [35.708029,139.769074,{"maxDistance": 1}]

結果は Query executed in 0.292 sec. Returned 12 record(s)

実際に、OrientStidoでの実行した結果です。

SnapCrab_Desktop_2014-10-23_22-31-38_No-00

次に、Luecenのindexを使っていようともSQLの関数等はもちろん利用可能です。

例として上記のSQLで検索されたポイントのvalueの平均値を求めてみます。

SELECT AVG(value) from Point where [latitude,longitude,$spatial] NEAR [35.708029,139.769074,{"maxDistance": 1}]

Query executed in 0.094 sec. Returned 1 record(s)

SnapCrab_Desktop_2014-10-23_22-32-23_No-00

REST-APIからの利用

OrietnDBはRestでの操作が可能でありDBへの検索結果を容易にJSON形式にて取得することが可能です。

それでは、先ほどの中心からの指定範囲内のポイントを返すSQLを使った簡単なFunctionを登録してみます。

function の作成

getPointsDistanceという名で、緯度、経度と中心からの半径距離を引数にもつ、実質1行の簡単なJavascriptのFunctionを作成します。

var result = db.query('select name,address,longitude,latitude, price from Point where [latitude,longitude,$spatial] NEAR [' + latitude + ',' + longitude + ',{"maxDistance": ' + distance + '}]');

return result;

登録したFunctionは以下のように呼び出すことが可能です。

http://%あなたのサーバー%:2480/function/indextest/getPointsDistance/35.708029/139.769074/1

下記は上記のRESTの結果のJSONです。

{
    "result": [
        {
            "@fieldTypes": "longitude=d,latitude=d", 
            "@rid": "#-2:1", 
            "@type": "d", 
            "@version": 0, 
            "address": "上野5-16-12", 
            "latitude": 35.705596, 
            "longitude": 139.775348, 
            "name": "仲御徒町郵便局"
        }, 
//// 省略します //// 
        {
            "@fieldTypes": "longitude=d,latitude=d", 
            "@rid": "#-2:11", 
            "@type": "d", 
            "@version": 0, 
            "address": "湯島2-21-1", 
            "latitude": 35.705207, 
            "longitude": 139.766349, 
            "name": "湯島二郵便局"
        }, 
        {
            "@fieldTypes": "longitude=d,latitude=d", 
            "@rid": "#-2:12", 
            "@type": "d", 
            "@version": 0, 
            "address": "湯島4-6-11", 
            "latitude": 35.708623, 
            "longitude": 139.768876, 
            "name": "湯島四郵便局"
        }
    ]
}

Javascript から OrientDB を操作する

さて、それでは、これをJavascriptから直接呼び出し、GoogleMap上へポインティングしてみます。

GoogleMapからdragendのタイミングでgetPointsDistanceに対して直接APIをコールし取得したJSONに含まれるポイントデータを地図上にマッピングしてみます。


    google.maps.event.addListener(gmap, "dragend", function() {
        markerArray = new Array();
        //ボタンが押されたら、マーカーの配列に対してsetMap(null)を実行し、地図上から削除
            GmapMarkerList.forEach(function(marker, idx) {
              marker.setMap(null);
           });
    //中心の座標を取得
    var pos = gmap.getCenter();
    var lat = pos.lat();
    var lng = pos.lng();

       //OrientDBからjsonファイルの取得 URLは適宜変えてください
       $.ajax({
           url: 'http://localhost:2480/function/indextest/getPointsDistance/'+lat+'/'+lng+'/10',
           type: 'GET',
           dataType: 'json',
           timeout: 10000,
           error: function(){
             alert("point");
           },
           success: function(json){

             //帰ってきた地点の数だけループ
             $.each(json.result,function(){
              markerArray.push({
                position: new google.maps.LatLng(this.latitude,this.longitude),
                 title: this.name,
                 content:this.price
               });
             });

             // マーカーデータをセット
             if(markerArray){
               setMarkerData(markerArray);
             }
           }

         });

        });

実行した結果は以下のようになります。

SnapCrab_Desktop_2014-10-23_22-50-39_No-00

非常に簡単なサンプルですが、イメージが掴めましたでしょうか?

次に、OrientDB-Luece での日本語の全文検索をご紹介します。