Search code examples
node.jspostgresqlsequelize.jspostgis

Sequelize, postgres and posgis: find activities within n° kilometers


Please help! I have a project using Sequelize, postgres and postgis (for gps). The schema (simplified) is:

export default class Activity extends Model {
  static init(sequelize) {
    return super.init({
      sequelize,
      tableName: 'activities',
    })
  }

  static get modelAttributes() {
    return {
      id: {
        type: DataTypes.UUID,
        defaultValue: DataTypes.UUIDV4,
        primaryKey: true,
      },

      activity_name: {
        type: 'citext',
        allowNull: false,
        unique: {
          args: true,
          msg: 'Activity name already in use.',
        },

      point: {
        type: DataTypes.GEOMETRY,
        allowNull: true,
      },
    }
  }
}

I don't put the migration file, because this part works well: whene I save the data, it's correctly registered.

await Activity.bulkCreate({
  activity_name: 'whatever,
  point: { type: 'Point', coordinates: [lat, lng] },
})

The problem comes when I try to get activities within a certain distance:

const result = await User.findAll({
      include: [
        {
          model: Activity,
          required: true,
          where: {
            point: Sequelize.fn(
              'ST_DWithin',
              Sequelize.fn(
                'ST_SetSRID',
                Sequelize.fn('ST_MakePoint', 40.119536, 8.495837),
                4326
              ),
              parseFloat(distance) // number 10000
            ),
          },
        },
      ],
    })

This code throws an error: "function st_dwithin(geometry, integer) does not exist". I struggling with this problem since a while. Any solution? thanks in advance!!!


Solution

  • The problem is two-fold:

    1. ST_DWithin() requires two geometries and a distance limit. You only supplied a single geometry and the limit:

      boolean ST_DWithin(geometry g1, geometry g2, double precision distance_of_srid);

    2. ST_SetSRID() adds the context of a reference system to the supplied geometry. You selected EPSG:4326, which uses geographical degrees as the base unit, not meters, not miles. There's a warning in ST_DWithin documentation:

      The distance is specified in units defined by the spatial reference system of the geometries.

      Which means a distance limit of 180 already returns everything, filters nothing, because on a sphere you're always at most 180 degrees away: 181 degrees East is just 179 degrees West. Change the SRID or convert your units: assuming WGS-84's spherical Earth radius of 6371008.8m, you can arrive at a single degree of 111195.08m by dividing the circumference over 360 degrees: 6371008.8*2*pi/360. A single meter is then 1/111195.08 of a degree which is why you might want to trade this:

      parseFloat(distance) * 5000
      

      for this

      parseFloat(distance) /111195.08