この記事は公開されてから1年以上経過しています。情報が古い可能性がありますので、ご注意ください。
MongoDB には、地理空間のインデックスが用意されていて、簡単に位置情報を操作することが出来ます。
今回は、つぶやきを HTML5 の Geolocation API から取得した位置情報とともに MongoDB に保存しておき、
現在地近くのつぶやきだけを検索して表示するという簡単なサンプルを作ってみます。
ありがたいことに、うえじゅんさんの作った Node.js + MongoDB のサンプルがあるのでこれをベースに作っていきましょう。
https://dev.classmethod.jp/server-side/node-socket-io-mongodb/
位置情報を保存するフィールドを定義
つぶやきのモデルに位置情報の保存フィールドを定義します。
「message.coffee」
mongo = require 'mongoose'
mongo.connect 'mongodb://localhost/cmapp'
Schema = mongo.Schema
Message = mongo.model 'messages', new Schema
text: String
location: [Number, Number]
created_at: {type: Date, default: Date.now}
updated_at: {type: Date, default: Date.now}
module.exports = Message
location 部分を配列で定義します。
重要なのは順番で、保存や検索を行う際は、経度(longitude)、緯度(latitude)の順番で指定します。
地理空間インデックスの作成
二次元の地理空間のインデックス(geospatial index)を作成します。
MongoDBが起動している状態で以下を実行します。
$ mongo
$ use cmapp
$ db.messages.ensureIndex( { location : "2d" } )
つぶやき情報に位置情報を付加する
「chat.coffee」
$('#send').click (event) ->
navigator.geolocation.getCurrentPosition (position) =>
longitude = position.coords.longitude
latitude = position.coords.latitude
msg =
text: $("input#message").val()
longitude: longitude
latitude: latitude
$("input#message").val ""
socket.emit 'message:send', msg
HTML5 の Geolocation API から経度、緯度を取得して、その情報をつぶやきとともに emit します。
位置情報を保存する
保存時は、messageのlocationフィールドに 経度、緯度を渡して保存します。
「app.coffee(一部抜粋)」
socket.on 'message:send', (data) ->
msg = new message()
msg.text = data.text
msg.location = [data.longitude, data.latitude]
msg.save (err) ->
throw err if err
io.sockets.emit 'message:receive', { message: msg }
つぶやきを登録してみる
適当につぶやきます。
登録後、Collection を確認すると、位置情報が保存されていることが分かります。
$ db.messages.find()
{ "text" : "あきばだよ", "_id" : ObjectId("50cdf55fefc0380000000002"), "updated_at" : ISODate("2012-12-16T16:22:55.324Z"), "created_at" : ISODate("2012-12-16T16:22:55.324Z"), "location" : [ 139.6917375, 35.6162695 ], "__v" : 0 }
位置情報を指定して検索する
起動時に現在位置から半径1kmの範囲のつぶやきを取得するように修正します。
「chat.coffee(一部抜粋)」
navigator.geolocation.getCurrentPosition (position) ->
position =
latitude: position.coords.latitude
longitude: position.coords.longitude
socket.emit 'session:start', position
起動時に自身の位置情報をemitします。
「app.coffee(一部抜粋)」
io.sockets.on 'connection', (socket) ->
socket.on 'session:start', (position) ->
conditions = getConditions position
message.find conditions, (err, messages) ->
throw err if err
socket.emit 'messeges:show', { messages: messages}
socket.on 'message:send', (data) ->
msg = new message()
msg.text = data.text
msg.location = [data.longitude, data.latitude]
msg.save (err) ->
throw err if err
io.sockets.emit 'message:receive', { message: msg }
getConditions = (position) ->
conditions =
location :
$within :
$centerSphere :
[[position.longitude, position.latitude], 0.000157]
getConditions メソッド内で条件を取得しています。
centerSphere で 点(経度、緯度)と半径を指定し円を描き、within で その円内に存在するアイテムを選択することを指定します。
半径部分はラジアンを使って指定します。地球の半径で考えると1ラジアンは、6378.137km らしいので、1km ≒ 0.000157 としました。
画面を開くと、現在地近くのつぶやきのみ表示されているはずです。
まとめ
MongoDBの地理空間インデックスを使用することで、柔軟に位置情報を操作できることが分かりました。
位置情報と連動させて色々面白そうなものを作ってみたいです。
「app.coffee」
express = require 'express'
http = require 'http'
path = require 'path'
io = require 'socket.io'
message = require './models/message'
app = express()
server = http.createServer app
io = io.listen server
app.configure ->
app.set 'port', process.env.PORT || 3000
app.set 'views', __dirname + '/views'
app.set 'view engine', 'jade'
app.use express.favicon()
app.use express.logger 'dev'
app.use express.bodyParser()
app.use express.methodOverride()
app.use app.router
app.use express.static path.join __dirname, "public"
app.configure 'development', ->
app.use express.errorHandler()
server.listen app.get 'port'
io.sockets.on 'connection', (socket) ->
socket.on 'session:start', (position) ->
conditions = getConditions position
message.find conditions, (err, messages) ->
throw err if err
socket.emit 'messeges:show', { messages: messages}
socket.on 'message:send', (data) ->
msg = new message()
msg.text = data.text
msg.location = [data.longitude, data.latitude]
msg.save (err) ->
throw err if err
io.sockets.emit 'message:receive', { message: msg }
getConditions = (position) ->
conditions =
location :
$within :
$centerSphere :
[[position.longitude, position.latitude], 0.000157]
「chat.coffee」
socket = io.connect()
navigator.geolocation.getCurrentPosition (position) ->
position =
latitude: position.coords.latitude
longitude: position.coords.longitude
socket.emit 'session:start', position
socket.on 'messeges:show', (data) ->
for message in data.messages
$("div#chat-area").prepend "<div>" + message.text + "</div>"
socket.on 'message:receive', (data) ->
$("div#chat-area").prepend "<div>" + data.message.text + "</div>"
$('#send').click (event) ->
navigator.geolocation.getCurrentPosition (position) ->
latitude = position.coords.latitude
longitude = position.coords.longitude
msg =
text: $("input#message").val()
latitude: latitude
longitude: longitude
$("input#message").val ""
socket.emit 'message:send', msg