Posted at 2012-01-22 18:04:31 under テクノロジ (by key)

MyBikeに特定の位置情報をマスクする機能を追加しました。

どんなところで使っているかというと、GarminのGPSログをアップロードして地図や自転車のデータを見られる機能があるんですが、 例えば自宅から会社まで移動する際のログを取り続けると、始点終点を含めたポリラインが地図に描画されてしまい、自宅も会社もモロバレになってしまいます。

内部的にはこんな構造でデータを格納しています。クエリ一発で1行帰ってくるからすごく高速だし、 単純なループでグラフライブラリやGoogle Maps APIに渡すことが出来るので、システム全体としての負荷は激減しました。

activity_document = {
    'records' [
        {
            'timestamp': '2012-01-03T13:32:57',
            'speed': 35.0,
            'location': [135.0, 45.0],
        },
        {
            'timestamp': '2012-01-03T13:32:58',
            'speed': 36.0,
            'location': [135.0, 45.0],
        },
        {
            'timestamp': '2012-01-03T13:32:59',
            'speed': 38.0,
            'location': [135.0, 45.0],
        },
    ]
}
db.activity_record.insert(activity_documents)

MongoDBには位置情報を取り扱うための2Dインデックスというものがあって、 長方形や円に含まれる ドキュメントを 取得することができます。 次のように計算することで、自宅やオフィスを隠蔽できるなと考えたわけです。

位置情報全体 - 隠したい位置情報 = 表示する位置情報

ドキュメント内に情報を直列で持っている(recordsは配列になっている)ので records.location にインデックスを張ればいいや〜と簡単に考えていたのですが、 MongoDBの理解が浅くて罠にハマりました…。

罠。

  • nested arrayにはインデックスが張れない
  • embedded objectのみを取り出す方法が無い

こんなことが出来ません:

#records内のobjectにインデックスを指定…は出来ない
# 本来は records.0.location などとすべきだから。
db.activity_record.ensureIndex({'records.location':'2d'})

※MongoDB 2.0からインデックス作れるらしい。が、後述の問題は残る。

こんなことも出来ません:

# 経度、緯度から近い情報を取得…と言ってもドキュメントが丸っと帰ってくる
# 意味なし
db.activity_record.find({'location': {'$near': [130.0, 45.0]}})

仕方が無いので、データが増えることを覚悟の上で、時間と位置情報だけを持つコレクションを追加しました。 以下のドキュメントをデータ数分、activity_timestamp_locationというコレクションに保存します。

timestamp_location_doc = {
    'timestamp': '2012-01-03T13:32:59',
    'location': [135.0, 45.0]
}
db.activity_timestamp_location.insert(timestamp_location_doc)

最終的にGoogle Maps APIに渡す際は、次のように処理をしています。

# 隠すべき位置情報と時刻を取得する

# 隠したい場所の緯度経度、半径(km)
longitude, latitude = 135.0, 45.0
distance = 1

# 地球の半径
earthRadius = 6371

# 隠したい場所と周囲を指定して、位置と時刻を検索
results = db.activity_timestamp_location.find({
    'location': {
        "$within": {
            "$centerSphere": [[longitude, latitude], distance / earthRadius]
        }
    }
})
hidden_timestamp = [result['timestamp'] for result in results]

# 全てのデータから、隠したい場所の時刻を除外する
locations = [record['location'] for record in activity_record_document['records'] if record['timestamp'] not in hidden_timestamp]

GPS機器を使用した場合には(少なくともGarminは)時刻をキーとして利用できるので、 それを利用してマッチングをかけています。

ループ回して隠してる辺りがイマイチですが、速度的にはストレスを感じるほどでは無いのでしばらくこのままで行こうと思います。


環境。

  • MongoDB 2.0.2
  • pymongo 2.1.1

Tags: mongodb
Posted at 2012-01-04 20:48:39 under テクノロジ (by key)

衝撃!Squeeze標準のredisは1.2.6だった。

mybike.jpのサーバ移行に伴いデータの引越しを検討していたところ、 memcachedに入っているセッションデータが移せなくて涙した。 redis なら永続化してくれるので、こんなことは無い…と考え、 新サーバではredisをキャッシュストレージに利用しようと誓いました。

で、 django-redis-cache を利用しようとしたところ、 SETEXなんてコマンドはねーよ!とエラーを吐かれて発覚。SETEXコマンドはredis 1.3から追加されており、 コマンド一つでデータ登録とageの設定が行えるものらしい。

とにかく、redis 1.2.6を使うわけには行かなくなった。あれこれググッたところ、 squeeze backportsというapt-line(sidからのbackportかな?)にredis 2が含まれており、 これを呼び出すことでsqueeze環境でredisが使えるようだった。

利用方法は

  1. apt-lineにエントリを足す
  2. aptをアップデートする
  3. redis-serverパッケージをインストール

とする。

具体的には次のようにします。

/etc/apt/sources.listを編集:

deb http://backports.debian.org/debian-backports squeeze-backports main

aptのアップデート:

apt-get update

redis-serverパッケージをインストール:

apt-get -t squeeze-backports install redis-server

これであなたも快適redis 2生活!

元ネタはこちら

Posted at 2012-01-04 01:26:36 under テクノロジ (by key)

Garminデバイスは一部のファイルフォーマットでsemicircleというやつを返してくる(例: Garmin Edge 800)。 最初、何のことだかわからず、この妙な数値はいったいどうやって使えば良いのだろう?と頭をひねったもんですが、 軽くググッてみたところ簡単に度に変換出来るようだったのでメモ。

# semicircle to degrees
latitude * (180.0 / pow(2, 31))
longitude * (180.0 / pow(2, 31))

ということでした。

Tags: gis, python
Posted at 2011-12-03 10:46:29 under テクノロジ (by key)

MyBike.jpで使ってるRabbitMQがいつの間にかクラッシュしてたので対策。 環境はDebian 5.0, RabbitMQは 公式のapt lineから インストール。

Continue reading
Posted at 2011-11-25 23:44:23 under テクノロジ (by key)

本日もsudsを利用してpythonでaffiliate windowをいじくるテスト。 昨日の例 に習ってザクッと初期化。

import suds
url = 'http://v3.core.com.productserve.com/ProductServeService.wsdl'

client = suds.client.Client(url)

userauth = client.factory.create('UserAuthentication')
userauth.sApiKey = 'あーぶらかたぶらー'

client.set_options(soapheaders=userauth)

商品リストはgetProductLst()を呼び出す。デュラエースを探したいなら次のようにする。

client.service.getProductList(sQuery='SHIMANO DURA-ACE')

結果。

(reply){
   oProduct[] =
      (Product){
         iId = 128559266
         iCategoryId = 252
         iMerchantId = 3395
         iAdult = 0
         sName = "Shimano Dura-Ace 7900 SPD SL Road Pedals - 2011"
         sAwDeepLink = "http://www.awin1.com/pclick.php?p=128559266&a=101730&m=3395&platform=cs"
         sAwThumbUrl = "http://images.productserve.com/thumb/3395/128559266.jpg"
         fPrice = 164.99
      },
      (Product){
         iId = 128559456
         iCategoryId = 252
         iMerchantId = 3395
         iAdult = 0
         sName = "Shimano Dura-Ace 7900 35mm Carbon Front Road Wheel - 2011 (Clincher)"
         sAwDeepLink = "http://www.awin1.com/pclick.php?p=128559456&a=101730&m=3395&platform=cs"
         sAwThumbUrl = "http://images.productserve.com/thumb/3395/128559456.jpg"
         fPrice = 551.99
      }
(略)

解ってしまうと意外と簡単。サービスに組み込むにはいくつか気を付けないといけないなーと思ったのは以下。

  • そもそも処理が遅い。秒単位で時間がかかる。
  • iMerchantIdのような外部参照があるので別途取得する必要がある。

Tags: python