Pen*_*ica 10 mysql postgresql postgis geospatial sequelize.js
使用Sequelize和地理空间查询,如果我想找到某个位置的"n"最近点,那么Sequelize查询应该如何?
假设我有一个看起来像这样的模型:
sequelize.define('Point', {geo: DataTypes.GEOMETRY('POINT')});
Run Code Online (Sandbox Code Playgroud)
现在让我们说我们通过以下方式在数据库中输入100个随机点:
db.Point.create({geo: {type: 'Point', coordinates: [randomLng,randomLat]}});
Run Code Online (Sandbox Code Playgroud)
想象一下,我们有一个lat
和lng
变量来定义一个位置,我们希望找到最近的10个点.当我运行此查询时,我收到一个错误:
const location = sequelize.literal(`ST_GeomFromText('POINT(${lat} ${lng})', 4326)`);
db.Point.findAll({
attributes: [['distance', sequelize.fn('ST_Distance', sequelize.col('Point'), location)]],
order: 'distance',
limit: 10
});
// -> TypeError: s.replace is not a function
Run Code Online (Sandbox Code Playgroud)
知道问题是什么/如何解决?
谢谢!
MySQL 可以给出函数ST_Distance_Sphere
不存在的错误。在这种情况下,您可以使用此替代解决方案:
我将点信息分别保存为纬度和经度小数点。假设你应该有一个看起来像这样的模型:
sequelize.define('Point', {latitude: DataTypes.DECIMAL(11,2)},
{longitude: DataTypes.DECIMAL(11,2)});
Run Code Online (Sandbox Code Playgroud)
想象一下,我们有一个lat
和lng
变量来定义一个位置,我们想要找到离它最近的 10 个点:
db.Point.findAll({
attributes: [[sequelize.fn('POW',sequelize.fn('ABS',sequelize.literal("latitude-"+lat)),2),'x1'],
[sequelize.fn('POW',sequelize.fn('ABS',sequelize.literal("longitude-"+lng)),2),'x2']],
order: sequelize.fn('SQRT', sequelize.literal('x1+x2')),
limit: 10
});
Run Code Online (Sandbox Code Playgroud)
更新:
使用Haversine 公式,距离更准确:
db.Point.findAll({
attributes: [[sequelize.literal("6371 * acos(cos(radians("+lat+")) * cos(radians(latitude)) * cos(radians("+lng+") - radians(longitude)) + sin(radians("+lat+")) * sin(radians(latitude)))"),'distance']],
order: sequelize.col('distance'),
limit: 10
});
Run Code Online (Sandbox Code Playgroud)
小智 6
建立关@ Edudjr的答案,这是我做了什么得到它在我的项目的工作:
const location = sequelize.literal(`ST_GeomFromText('POINT(${ startLongitude } ${ startLatitude })')`)
const distance = sequelize.fn('ST_Distance_Sphere', sequelize.col('location'), location)
const inRadius = await Position.findAll({
order: distance,
where: sequelize.where(distance, { $lte: radius }),
logging: console.log
})
Run Code Online (Sandbox Code Playgroud)
其中位置定义为:
sequelize.define('Position', {
location: DataTypes.GEOMETRY('POINT')
})
Run Code Online (Sandbox Code Playgroud)
注意 Point 需要格式为 (longitude latitude) 的坐标
当用括号将sequelize.fn括起来时,还必须包含一个字符串作为别名:
[sequelize.fn('ST_Distance_Sphere', sequelize.literal('geolocation'), location), 'ALIASNAME']
Run Code Online (Sandbox Code Playgroud)
另外,尝试更改ST_Distance
为ST_Distance_Sphere
。所以:
const location = sequelize.literal(`ST_GeomFromText('POINT(${lng} ${lat})', 4326)`);
User.findAll({
attributes: [[sequelize.fn('ST_Distance_Sphere', sequelize.literal('geolocation'), location),'distance']],
order: 'distance',
limit: 10,
logging: console.log
})
.then(function(instance){
console.log(instance);
})
Run Code Online (Sandbox Code Playgroud)
这实际上为我工作。obs:确保用几何数据类型所在的模型替换“用户”。
更新:如果您仍然不能使用命令订购order: 'distance'
,也许您应该在var中声明它,并且order: distance
不带引号使用,例如:
var lat = parseFloat(json.lat);
var lng = parseFloat(json.lng);
var attributes = Object.keys(User.attributes);
var location = sequelize.literal(`ST_GeomFromText('POINT(${lng} ${lat})')`);
var distance = sequelize.fn('ST_Distance_Sphere', sequelize.literal('geolocation'), location);
attributes.push([distance,'distance']);
var query = {
attributes: attributes,
order: distance,
include: {model: Address, as: 'address'},
where: sequelize.where(distance, {$lte: maxDistance}),
logging: console.log
}
Run Code Online (Sandbox Code Playgroud)
距离精度的更新:
sarikaya提到的解决方案似乎更准确。这是使用postgres的方法:
var distance = sequelize.literal("6371 * acos(cos(radians("+lat+")) * cos(radians(ST_X(location))) * cos(radians("+lng+") - radians(ST_Y(location))) + sin(radians("+lat+")) * sin(radians(ST_X(location))))");
Run Code Online (Sandbox Code Playgroud)