背景
最近在学习MEAN,看的是Simon Helmes的Getting MEAN with Mongo, Express, ANgular, and Node这本书。其中Chapter 8 Adding Angular components to an Express application中涉及到了Mongoose
的geoNear
方法的使用。不过自己在按照作者的方法进行测试的时候,发现并不能输出想要的结果。通过相关研究找到了解决的方法,因此分享。
问题
作者在书中演示了通过浏览器的navigator.geolocation
发送经纬度坐标到API接口,接着后台使用Mongoose
的geoNear
方法,从数据库中将离目标坐标较近的数据推送出来。后台从Mongo
中取数的大致代码如下:
/* GET list of locations */
module.exports.locationsListByDistance = function(req, res) {
var lng = parseFloat(req.query.lng);
var lat = parseFloat(req.query.lat);
var maxDistance = parseFloat(req.query.maxDistance);
var point = {
type: "Point",
coordinates: [lng, lat]
};
var geoOptions = {
spherical: true,
maxDistance: theEarth.getRadsFromDistance(maxDistance),
num: 10
};
if ((!lng && lng!==0) || (!lat && lat!==0) || ! maxDistance) {
// ...
}
Loc.geoNear(point, geoOptions, function(err, results, stats) {
// ...
});
};
其中,作者的意思是maxDistance
数据是按照公里进行输入,然后转换为弧度,并把弧度作为参数传入geoNear
中。但是得到的结果,确实没有任何数据输出。
解决
经过查找后发现,Mongo中对此是如下的定义:
2dsphere Index
If using a 2dsphere index, you can specify either a GeoJSON point or a legacy coordinate pair for the near value.
You must include spherical: true in the syntax.
With spherical: true, if you specify a GeoJSON point, MongoDB uses meters as the unit of measurement:db.runCommand( { geoNear: <collection> , near: { type: "Point" , coordinates: [ <coordinates> ] } , spherical: true, ... } )
With spherical: true, if you specify a legacy coordinate pair, MongoDB
uses radians as the unit of measurement:db.runCommand( { geoNear: <collection> , near: [ <coordinates> ], spherical: true, ... } )
书中的源代码确实是GeoJSON
的格式,那为何作者使用了弧度,而没有使用米呢?原来Mongoose
在3.9.5
版本才支持了Mongo
的这个设置。原文如下:
3.9.5 / 2014-11-10
fixed; geoNear() no longer enforces legacy coordinate pairs – supports GeoJSON #1987 alabid
用可能是作者在写书的时候,还用的OK,后来版本更新后,设置就失效了。
解决
因此,按照作者原来的思路,代码应该改为:
/* GET list of locations */
module.exports.locationsListByDistance = function(req, res) {
// ...
var geoOptions = {
spherical: true,
maxDistance: maxDistance * 1000, // <--
num: 10
};
// ...
};
var buildLocationList = function(req, res, results, stats) {
var locations = [];
results.forEach(function(doc) {
locations.push({
distance: doc.dis / 1000, // <--
// ...
});
});
return locations;
};