2012年10月13日土曜日

Solr の Spatial Search 機能試してみました

以前に勤めてた会社のエンジニアブログで公開してたシロモノなんですが、都合によりそちらのブログが消えちゃったのでこちらで公開します。もう一年半以上前に書いたものなのでだいぶ古いですが。(もう Solr もバージョン4が出てますし…)

全文検索システム Solr のバージョン 3.2 を使って Solr の Spatial Search 機能に触れてみました、という内容です。Solr Wiki の Spatial Search をなぞってみたのですが、こちらで使われているサンプルデータに含まれるのはもちろん海外の位置情報です。島国に生まれた人間として、どうもイメージしづらかったので大雑把な日本のランドマーク位置情報データを作り、これに対して Spatial Search してみました。

環境

今回の作業は以下の環境で行いましたが、たぶんこの環境でなくともだいたいは実施可能と思います。

Solr 3.2 のセットアップ

Solr のサイト から apache-solr-3.2.0.tgz をダウンロードします。ダウンロードしたら適当なディレクトリで展開します。どこでもいいのですが以下では /usr/local/ に展開しています。
# mv apache-solr-3.2.0.tgz /usr/local
# tar xvfz apache-solr-3.2.0.tgz

これで /usr/local/apache-solr-3.2.0 以下に Solr 3.2 が展開されます。以降ではこのパスを $SOLRと表記します。

以降では基本的に Solr 付属のサンプルを使いますので、スキーマ定義ファイルや設定ファイルなどは編集しません。さっそく Solr を起動します。
# cd $SOLR/example
# java  -jar start.jar
起動が完了したら、http://localhost:8983/solr/admin/ にアクセスしてみます。Solr Admin と表示されたページが開くはずです。以降では Solr をローカルホストで立ち上げることを前提に話を進めます。もしも別のホストで Solr を立ち上げている場合には URL 中のlocalhost をそのホストの IP アドレスに読み替えて下さい。


Solr 付属のサンプルデータで Spatial Search

準備ができたところで、Solr 付属のサンプルデータのどこに Spatial Search で使う情報が書かれているかをちょっと見てみます。
$SOLR/example/exampledocs/mem.xml を見てみると、以下のような記載があり
<add>
  <doc>
    <field name="id">TWINX2048-3200PRO</field>
    <field name="name">CORSAIR  XMS 2GB (2 x 1GB) ... </field>
                          :
    <!-- San Francisco store -->
    <field name="store">37.7752,-122.4232</field>
                          :
    <!-- Dodge City store -->
    <field name="store">37.7752,-100.0232</field>
                          :
    <!-- Buffalo store -->
    <field name="store">45.17614,-93.87341</field>


フィールド名 storeに含まれているのが緯度と経度の情報です。次にスキーマ定義ファイル $SOLR/example/solr/conf/schema.xml を見てみます。422 行目、474 行目付近に以下のような記述があります。
<!-- A specialized field for geospatial search. If indexed, this fieldType must not be multivalued. -->
   <fieldType name="location" class="solr.LatLonType" subFieldSuffix="_coordinate"/>
                                      :
                                      :
   <!--
   The following store examples are used to demonstrate the various ways one might _CHOOSE_ to
    implement spatial.  It is highly unlikely that you would ever have ALL of these fields defined.
    -->
   <field name="store" type="location" indexed="true" stored="true"/>


422 行目の方で、location というフィールドタイプのものに solr.LatLonType 型が定義されています。コメント部分にも記載されてますが、この型のものに対して Spatial Search が可能になっています。474 行目の方で store (お店) フィールドにこの location 型が定義されてます。

ではこのサンプルデータを登録します。
# cd $SOLR/example/exampledocs
# java -jar post.jar *.xml
登録したら、http://localhost:8983/solr/select?indent=true&fl=name,store&q=*:*&fq={!geofilt}&sfield=store&pt=45.15,-93.85&d=5 にアクセスしてみます。これは緯度 45.15, 経度 -93.85 の地点から 5 km 以内に位置するものを呼び出すクエリです。結果は以下のようになります。
<?xml version="1.0" encoding="UTF-8"?>
<response>

<lst name="responseHeader">
  <int name="status">0</int>
  <int name="QTime">1</int>
  <lst name="params">
    <str name="d">5</str>
    <str name="fl">name,store</str>
    <str name="indent">true</str>
    <str name="q">*:*</str>
    <str name="sfield">store</str>
    <str name="pt">45.15,-93.85</str>
    <str name="fq">{!geofilt}</str>
  </lst>
</lst>
<result name="response" numFound="6" start="0">
  <doc>
    <str name="name">Maxtor DiamondMax 11 - hard drive - 500 GB - SATA-300</str>
    <str name="store">45.17614,-93.87341</str>
  </doc>
  <doc>
    <str name="name">Belkin Mobile Power Cord for iPod w/ Dock</str>
    <str name="store">45.17614,-93.87341</str>
  </doc>
  <doc>
    <str name="name">A-DATA V-Series 1GB 184-Pin DDR SDRAM Unbuffered DDR 400 (PC 3200) System Memory - OEM</str>
    <str name="store">45.17614,-93.87341</str>
  </doc>
  <doc>
    <str name="name">ViewSonic VA902B - flat panel display - TFT - 19"</str>
    <str name="store">45.17614,-93.87341</str>
  </doc>
  <doc>
    <str name="name">Canon PIXMA MP500 All-In-One Photo Printer</str>
    <str name="store">45.17614,-93.87341</str>
  </doc>
  <doc>
    <str name="name">Canon PowerShot SD500</str>
    <str name="store">45.17614,-93.87341</str>
  </doc>
</result>
</response>
6件のデータがヒットしていますね。ですが場所が同じであまり面白くありませんねw(え?そんなことないすか?)。上記の URL の末尾の 5 を 1000 にすると 9 件にヒット数が増えるんですが、やっぱりイマイチイメージできません。そんなわけで国内ランドマークの位置情報を含むデータで Spatial Search を試してみましょう。

国内ランドマークの位置情報を使って Spatial Search

というわけで少々強引ですが国内のランドマークの位置情報を含むデータで Spatial Search してみます。まずはデータを作ります。位置情報の取得には Google API を用います。方針としては①ランドマークをリストアップ、②それらをURIエンコードして Google API に投げる、③レスポンスデータから緯度と経度を抜き出す、④Solrのデフォルト設定に適した XML 形式として保存、なのですが、これを一々やってると面倒なので、一発で XML を作成するための準備をします。

まず、以下のパッケージをインストールして下さい。perl と GET コマンドさえ使えればよいので、CentOS以外の場合はこれらのコマンドを含むパッケージなりをインストールしてみて下さい。
# yum install perl perl-libwww-perl
次に場所名を列挙したテキストファイルを作ってみて下さい。$SOLR 以下にディレクトリを作ってそこで作業することにしてみます。
# mkdir $SOLR/example/exampledocs_geosearch
# cd $SOLR/example/exampledocs_geosearch
# vi locations.txt
locations.txt には以下のような内容を記載します。独断と偏見と思いつきで書いてるので、適当に書き換えて下さって OK です。(それ「ランドマーク」じゃないだろ、というものも多数。。。 ^^;)
神宮球場
国立競技場
新宿歴史博物館
新宿御苑
六本木ヒルズ
明治神宮
代々木公園
東京ディズニーランド
東京ビッグサイト
鶴岡八幡宮
高徳院
東京タワー
通天閣
神戸ポートタワー
福岡タワー
ミッドタウンタワー
京都駅
東京大学
北海道大学
東北大学
京都大学
九州大学
大阪大学
名古屋大学
神戸大学
青山学院大学
はりまや橋
時計台
オランダ坂
守礼門
リオグランデ・ド・スル
舞鶴港
鳥取砂丘
次に、このファイルにリストされた場所を Google API に問い合わせて緯度経度を取得し、結果を XML に出力するためのスクリプト geo_xml_gen.sh を用意します。
# cd $SOLR/example/exampledocs_geosearch
# touch geo_xml_gen.sh
# chmod 755 geo_xml_gen.sh
# vi geo_xml_gen.sh
geo_xml_gen.sh には以下を記述します。
#!/bin/sh

###########################
INPUT=locations.txt
OUTPUT=geo.xml
###########################
id_num=0
echo '<add>' > $OUTPUT
echo -n "Creating $OUTPUT  " >&2
for place in `cat $INPUT`; do
  echo -n "." >&2
  id_num=`expr $id_num + 1`
  id=`printf %08d $id_num`
  uri=`perl -e "use URI::Escape; print uri_escape(\"$place\");"`
  rawxml=`GET "http://maps.google.co.jp/maps/geo?q=${place}&output=xml"`
  res_code=`echo $rawxml | sed 's|.*<code>\(.*\)</code>.*|\1|g'`
  if [ ! "200" = "$res_code" ]; then
    echo >&2
    echo "\"$place\" is not found." >&2
    continue
  fi
  latlon=`echo $rawxml | sed 's|.*<coordinates>\(.*\)</coordinates>.*|\1|g'`
  lat=`echo $latlon | cut -d, -f1`
  lon=`echo $latlon | cut -d, -f2`
  cat >> $OUTPUT <<EOF
  <doc>
    <field name="id">$id</field>
    <field name="name">$place</field>
    <field name="manu">unknown</field>
    <field name="cat">unknown</field>
    <field name="features">unknown</field>
    <field name="weight">0</field>
    <field name="price">0.0</field>
    <field name="popularity">1</field>
    <field name="inStock">false</field>
    <field name="store">$lon,$lat</field>
    <field name="manufacturedate_dt">2005-08-01T16:30:25Z</field>
  </doc>
EOF
done
echo '</add>' >> $OUTPUT
echo " done. " >&2
ではこのスクリプトを動かして XML ファイルを作ってみます。高速化とか全く考えず一々問い合わせてるのでちょっとばかし時間がかかりますがご容赦を。
# cd $SOLR/example/exampledocs_geosearch
# ./geo_xml_gen.sh
Creating geo.xml  .............................
"オランダ坂" is not found.
.
"守礼門" is not found.
... done.
これで geo.xml ファイルができました。がっかり名所で有名な場所
もGoogle API で位置情報が返ってくるものとこないものがありますね。検索した場所が見つからない場合にはその情報は XML に反映されないようにしてあります。上記の locations.txt に列挙したものは(上の 2 つの not found なものを除いて)一応想定した位置情報が返ってくることを確認してますが、他の場所を追加した場合に期待した情報が返ってくるかは Google API さん次第ですのでこれまたご容赦を。

さて、geo.xml の中身を抜粋すると以下のような感じです (geo.xml 全文)。
<add>
  <doc>
                       :
    <field name="name">神宮球場</field>
                       :
    <field name="store">35.6745095,139.7170489</field>
                       :
  </doc>
  <doc>
                       :
    <field name="name">国立競技場</field>
                       :
    <field name="store">35.6800590,139.7141270</field>
場所の名前と位置情報が含まれてるのが分かりますね。ちなみに他にもいろいろと「これおかしくね?」というフィールドと値も含まれてますが、これはスキーマ定義ファイルをデフォルトから書き換えずに済ませたかった(楽がしたかった(汗))からです。美しくないですがお赦しを m(_ _;)m

ではやっとデータもできたところで登録してみましょう。
# cd $SOLR/example/exampledocs_geosearch
# cp ../exampledocs/post.jar  .
# java -jar post.jar geo.xml
これで登録完了です。ではいろいろ検索してみます。

  • 神宮球場から近い順にソートして表示する。(ついでに距離も表示する)。

    http://localhost:8983/solr/select?indent=true&rows=100&fl=name,store,score&q={!func}geodist%28%29&sfield=store&pt=35.6745095,139.7170489&sort=score+asc


    今回は geodist を使って距離でソートしています。 geofilt は使ってないので登録されているすべての情報がリストされます (レスポンスの全文)。まず初めの方だけ抜粋してみます。
    <?xml version="1.0" encoding="UTF-8"?>
    <response>
    
    <lst name="responseHeader">
                   :
    </lst>
    <result name="response" numFound="48" start="0" maxScore="18612.07">
      <doc>
        <float name="score">0.0</float>
        <str name="name">神宮球場</str>
        <str name="store">35.6745095,139.7170489</str>
      </doc>
      <doc>
        <float name="score">0.67114747</float>
        <str name="name">国立競技場</str>
        <str name="store">35.6800590,139.7141270</str>
      </doc>
      <doc>
        <float name="score">1.594742</float>
        <str name="name">ミッドタウンタワー</str>
        <str name="store">35.6657249,139.7310036</str>
      </doc>
      <doc>
        <float name="score">1.6029218</float>
        <str name="name">青山学院大学</str>
        <str name="store">35.6614775,139.7094640</str>
      </doc>
    上記のクエリの場合 score に返ってきているのが起点からの距離(単位 km)になります。 geodist によって複数の地点を近い順にソートできてますね。返ってきた結果の最後の部分もちょっと見てみます。
    <doc>
        <float name="score">10849.213</float>
        <str name="name">ASUS Extreme N7800GTX/2DHTV (256 MB)</str>
        <str name="store">40.7143,-74.006</str>
      </doc>
      <doc>
        <float name="score">10849.213</float>
        <str name="name">ATI Radeon X1900 XTX 512 MB PCIE Video Card</str>
        <str name="store">40.7143,-74.006</str>
      </doc>
                           :
      <doc>
        <float name="score">18612.07</float>
        <str name="name">リオグランデ・ド・スル</str>
        <str name="store">-29.5345050,-53.3906074</str>
      </doc>
    なんか凄いことになってますねw。日本の真裏ぐらいにあるブラジルのリオグランデ・ド・スル州まで18612 km だそうです。また、最初に登録したサンプルデータのものもリストアップされていることが分かりますね。

おわりに

今回は Solr Wiki - Spatial Search をなぞりつつ、比較的馴染みのある国内のランドマークな場所を使って Spatial Search 機能に触れてみました。位置情報を入れてあげれば、比較的簡単に距離による検索を行えるんですね。実用する際にはまっとうにスキーマを定義するのはもちろんのこと、用件に応じて距離の精度や距離計算のコストも見積もる必要がありますね。こちらの記事を書いて以降 Solr や全文検索系のツールを触ってないのですが、久しぶりに見なおしてみるとこれはこれで面白かったなと。また触れることがあるのやらないのやら。

まあそのうちですかね。


0 件のコメント:

コメントを投稿