Sequelize geospatial query: find “n” closest point

2019-02-17 00:32发布

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 that looks something like this:

sequelize.define('Point', {geo: DataTypes.GEOMETRY('POINT')});

Now let's say we input 100 random points in the db through something like:

db.Point.create({geo: {type: 'Point', coordinates: [randomLat, randomLng]}});

Imagine we have a lat and lng variables to define a location, and we want to find the 10 closest points to it. when I run this query I get an error:

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

Any idea what is the issue / how to fix it?

Thx!

3条回答
Viruses.
2楼-- · 2019-02-17 00:57

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
});
查看更多
forever°为你锁心
3楼-- · 2019-02-17 01:13

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(${lat} ${lng})', 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(${lat} ${lng})')`);
    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))))");
查看更多
手持菜刀,她持情操
4楼-- · 2019-02-17 01:15

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

查看更多
登录 后发表回答