MongoDBでは位置情報を検索するためのインデックス - Geospatianl Index - というのがあることを知り、使ってみることにしました。
データを適当に収集してからインデックスを貼ろうとすると、いろいろ大変だったので、備忘録として記載します。
そもそもMongoDBの基本操作
前提
latitude, longitudeをStringでplacesというコレクションに保存してた。
latitude, longitudeが空文字のものや、存在しないものがあった。
[{
name: "aaa",
latitude: "33.333",
longitude: "134.4444"
},
{
name: "bbb",
latitude: "",
longitude: ""
,
{
name: "ccc"
}]
1. Geospatianl IndexはFloatのArray
ドキュメントにて、Geospatianl IndexはArrayである必要があるらしいということで、とりあえず、以下のようにしてArrayを作った
db.places.find({latitude: {$ne: ""}}).forEach(function(r){
db.places.update({_id: r._id},{$set: {coordinates: [r.latitude, r.longitude]}})
})
2dのインデックスを貼る(他に2dsphere、geoHeystackなどがある。2dsphereは後述する)
db.places.createIndex( { coodinates: "2d" })
#=> Point must only contain numeric elements
どうやら、Array内は数字しかダメらしい。latitude, longitudeを文字列として保存してたことが原因。リセットしてparseFloatをかけた
db.places.update({}, {$unset: {coordinates: 1}}, {multi: true})
db.places.find({latitude: {$ne: ""}}).forEach(function(r){
db.places.update({_id: r._id},{$set: {coordinates: [parseFloat(r.latitude), parseFloat(r.longitude)]}})
})
db.places.createIndex( { coodinates: "2d" })
#=> exception: point not in interval of [ -180, 180 ]:: caused by :: {..., coordinates[non.0, non.0]}
どうやらlatitude, longitudeが存在しないもののcoordinatesも作っちゃってたらしい
2. latitude, longitudeの存在確認
先ほどまでは、{$ne: ""}
で空じゃないことだけ確認していたが、そもそもfieldが存在するかの確認もしなくてはならない。ということで、上記リセットコマンドを打ったのち、以下のようにしてみた。
db.places.find({latitude: {$ne: ""}, latitude: {$exists: true}}).forEach(function(r){
db.places.update({_id: r._id},{$set: {coordinates: [parseFloat(r.latitude), parseFloat(r.longitude)]}})
})
で、index作成
db.places.createIndex( { coodinates: "2d" })
#=> exception: point not in interval of [ -180, 180 ]:: caused by :: {..., coordinates[non.0, non.0]}
先ほどと同じエラーがでた。
どうやら、絞り込み条件が間違っていて、latitude: {$ne: ""}
が効いてないらしい。
ということで、修正して再度挑戦
db.places.find({latitude: {$ne: "", $exists: true}}).forEach(function(r){
db.places.update({_id: r._id},{$set: {coordinates: [parseFloat(r.latitude), parseFloat(r.longitude)]}})
})
db.places.createIndex( { coodinates: "2d" })
できた
Geospatianl Index を2dから2dsphereにしてみる
While basic queries using spherical distance are supported by the 2d index, consider moving to a 2dsphere index if your data is primarily longitude and latitude.
「latitude, longitudeのデータしかないなら2dsphereの方がいいよ」と言ってるので、2dsphere用に再度データを作り変える
- 上記リセットコマンドを打つ
db.places.update({}, {$unset: {coordinates: 1}}, {multi: true})
- 以下のコマンドを打って、coordinateとtypeを指定。latitudeとlongitudeの順番が逆なことに注意
db.places.find({latitude: {$ne: "", $exists: true}}).forEach(function(r){
db.places.update({_id: r._id},{$set: {loc: {coordinates: [parseFloat(r.longitude), parseFloat(r.latitude)], type: "Point"}}})
})
- 2dsphereのコマンドを打つ
db.places.createIndex( { loc: "2dsphere" })
できた
geo_nearを使ってみる
db.runCommand({
geoNear: "places" ,
near: [ 0, 0 ],
spherical: true
})