Using Sequelize and geospatial queries, if I want to find the \"n\" closest points to a certain location, how should the Sequelize query be?
Assume I have a model th
When you surround sequelize.fn with brackets, you must also include a string as an alias:
[sequelize.fn('ST_Distance_Sphere', sequelize.literal('geolocation'), location), 'ALIASNAME']
Also, try changing ST_Distance
to ST_Distance_Sphere
. So:
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);
})
This is actually working for me. obs: be sure you substitute 'User' with the model in which you have the geometry data type.
Update: If you still can't order using order: 'distance'
, maybe you should declare it in a var and use order: distance
without quotes, like this:
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
}
Update on distance accuracy:
The solution mentioned by sarikaya does seem to be more accurate. Here is how to do it using 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))))");
MySQL can give an error that function ST_Distance_Sphere
does not exist. In that case you can use this alternative solution:
I hold point information separately as latitude and longitude decimals. Assume you should have a model that looks something like this:
sequelize.define('Point', {latitude: DataTypes.DECIMAL(11,2)},
{longitude: DataTypes.DECIMAL(11,2)});
Imagine we have a lat
and lng
variables to define a location, and we want to find the 10 closest points to it:
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
});
Update:
With Haversine Formula, distance is more accurate:
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
});
Building off @Edudjr's answer, this is what I did to get it to work in my project:
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
})
where Position is defined as:
sequelize.define('Position', {
location: DataTypes.GEOMETRY('POINT')
})
Note that Point requires the coordinates in the format of (longitude latitude)
https://gis.stackexchange.com/questions/209008/incorrect-arguments-to-st-distance-sphere-in-special-cases
https://dba.stackexchange.com/questions/33410/whats-the-difference-between-pointx-y-and-geomfromtextpointx-y