web-dev-qa-db-ja.com

地理空間クエリを続編する:場所に最も近い「n」個のポイントを見つける

Sequelize および地理空間クエリを使用して、特定の場所に最も近い「n」個のポイントを見つけたい場合、Sequelizeクエリはどのようにすべきですか?

次のようなモデルがあるとします。

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

ここで、次のような方法でデータベースに100個のランダムなポイントを入力するとします。

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

場所を定義するための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

問題は何ですか/それを修正する方法はありますか?

どうも!

11
Pensierinmusica

sequelize.fnを角かっこで囲む場合は、エイリアスとして文字列も含める必要があります。

[sequelize.fn('ST_Distance_Sphere', sequelize.literal('geolocation'), location), 'ALIASNAME']

また、ST_DistanceST_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);
    })

これは実際に私のために働いています。 obs:ジオメトリデータ型のモデルを「User」に置き換えてください。

更新:それでも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
    }

距離精度の更新:

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))))");
5
Edudjr

MySQLは、関数ST_Distance_Sphereが存在しないというエラーを出す可能性があります。その場合、次の代替ソリューションを使用できます。

ポイント情報は、緯度と経度の小数として別々に保持しています。次のようなモデルが必要であると想定します。

sequelize.define('Point', {latitude: DataTypes.DECIMAL(11,2)},
                          {longitude: DataTypes.DECIMAL(11,2)});

場所を定義するための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
});

更新:

Haversine Formula を使用すると、距離がより正確になります。

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
});
5
sarikaya

@ 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
})

ここで、位置は次のように定義されます。

sequelize.define('Position', {
    location: DataTypes.GEOMETRY('POINT')
})

ポイントには(経度緯度)の形式の座標が必要であることに注意してください

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

4
Eric Yang