LoginSignup
9
9

More than 5 years have passed since last update.

MongoDBにGeospatianl Indexを貼る - MongoDBはまりポイント

Posted at

MongoDBでは位置情報を検索するためのインデックス - Geospatianl Index - というのがあることを知り、使ってみることにしました。
データを適当に収集してからインデックスを貼ろうとすると、いろいろ大変だったので、備忘録として記載します。
そもそもMongoDBの基本操作

前提

latitude, longitudeをStringでplacesというコレクションに保存してた。
latitude, longitudeが空文字のものや、存在しないものがあった。

places
[{
  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用に再度データを作り変える

  1. 上記リセットコマンドを打つ
リセットコマンド
db.places.update({}, {$unset: {coordinates: 1}}, {multi: true})
  1. 以下のコマンドを打って、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"}}})
})
  1. 2dsphereのコマンドを打つ
db.places.createIndex( { loc: "2dsphere" }) 

できた

geo_nearを使ってみる

db.runCommand({
   geoNear: "places" ,
   near: [ 0, 0 ],
   spherical: true
})
9
9
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
9
9