简体   繁体   English

如何使用 bookshelf.js 或 knex 在 MySQL 中查询按距离与纬度、经度坐标排序的地点?

[英]How to query places sorted by distance with lat, lng coordinates in MySQL with bookshelf.js or knex?

Is it possible to search nearest places in mysql by using Bookshelf.js, or Knex?是否可以使用 Bookshelf.js 或 Knex 在 mysql 中搜索最近的位置? And, if yes, how to do it correct?而且,如果是,如何正确地做到这一点?

UPD: I'm not strong with raw mysql queries, and can't find how to search with lat, lng in Bookshelf and knex. UPD:我不擅长原始 mysql 查询,也找不到如何在 Bookshelf 和 knex 中使用 lat、lng 进行搜索。

For example, i have places table with:例如,我有一个地方表:

  • title标题
  • address地址
  • lat纬度
  • lng.升。

What query i should use to get all places in 10 miles radius from Point(lat, lng)?我应该使用什么查询来从 Point(lat, lng) 获取半径 10 英里内的所有地方?

thanks.谢谢。

After some research i found working solution:经过一番研究,我找到了可行的解决方案:

const {lat, lng} = req.params;

const X1 = +lat - 10,
      Y1 = +lng - 10,
      X2 = +lat + 10,
      Y2 = +lng + 10;


const distanceInMilesSql = `( 3959 * acos( cos( radians(${+lat}) ) 
          * cos( radians( place.lat ) ) 
          * cos( radians( place.lng ) - radians(${+lng}) ) 
          + sin( radians(${+lat}) ) 
          * sin( radians( place.lat ) ) ) ) AS distance 
        `;

const places = await new Place().query(qb => {
    qb.whereBetween('lat', [X1, X2]);
    qb.whereBetween('lng', [Y1, Y2]);
    qb.column(['*', Bookshelf.knex.raw(distanceInMilesSql)]);
    qb.having('distance', '<', 10);
}).fetchAll({withRelated: [
    'place_specific_fields',
    'state',
    'media'
]});

Demo演示

http://rextester.com/NDOR4479 http://rextester.com/NDOR4479

SQL SQL

SELECT *,
       ACOS(SIN(RADIANS(:lat)) * SIN(RADIANS(lat)) + COS(RADIANS(:lat)) * COS(RADIANS(lat))
       * COS(RADIANS(lng - :lng))) * 3959 AS distance
FROM places
WHERE  ACOS(SIN(RADIANS(:lat)) * SIN(RADIANS(lat)) + COS(RADIANS(:lat)) * COS(RADIANS(lat))
       * COS(RADIANS(lng - :lng))) * 3959 <= 10
ORDER BY distance;

Notes笔记

The above uses the Spherical Law of Cosines where :lat and :lng are the latitude and longitude values from Point and 3959 is the Earth radius in miles.上述用途,余弦的球面法,其中:lat:lng从是纬度和经度值Point3959在英里地球半径。

If it helps anyone, here's some code I use (Strapi + MySQL):如果它对任何人有帮助,这是我使用的一些代码(Strapi + MySQL):

  1. Query for events events查询
  2. Join locations into events , where id matcheslocations加入events ,其中id匹配
  3. Gets anything within 5km (5000 meters) of the user获取用户 5 公里(5000 米)范围内的任何东西

On a side note: As a Strapi user, a raw query like this negates the high-level functionality that Strapi auto-builds for the events model.附带说明:作为 Strapi 用户,像这样的原始查询否定了 Strapi 为events模型自动构建的高级功能。 ie Strapi url filtering like &Cost_gte=20 won't work.即像&Cost_gte=20这样的 Strapi url 过滤将不起作用。 I'm still working on how to get around this and avoid having to build an entire custom controller from scratch (all just to have distance filtering?).我仍在研究如何解决这个问题并避免从头开始构建整个自定义控制器(只是为了进行距离过滤?)。

Code:代码:

const knex = strapi.connections.default
const result = await knex('events')
    .join('locations', 'events.Location', 'locations.id')
    .where(knex.raw(                
        `round(st_distance_sphere(
            st_geomfromtext(CONCAT('POINT(',locations.Longitude, ' ',locations.Latitude,')')),
            st_geomfromtext(CONCAT('POINT(` + ctx.query.Longitude + ` ` + ctx.query.Latitude + `)'))
        )) <= 5000`
    ))
    return result

You don't make it clear in your question, but I believe that the Bookshelf.js library works as a layer above PostgreSQL, which means that you have access to geometric data types .你在你的问题中没有说清楚,但我相信 Bookshelf.js 库作为 PostgreSQL 之上的一层,这意味着你可以访问几何数据类型 These can make use of these special operations .这些可以利用这些特殊操作 The search could be entirely SQL based, so without further context I can simply offer the following:搜索可以完全基于 SQL,所以没有进一步的上下文,我可以简单地提供以下内容:

Combine the latitude and longitude into a single data piece of type point (using the decimal form of the latitude and longitude).将纬度和经度组合成一个类型为 point 的数据块(使用纬度和经度的十进制形式)。 Here I will refer to that as coord .在这里,我将其称为coord

With the proper data in order, you can do a query of:使用顺序正确的数据,您可以执行以下查询:

SELECT p.*, (point'(@origin_long,$origin_lat)' <-> `coord`) distance
FROM `places` p
WHERE circle '((@origin_long, @origin_lat),@radius)' @> `coord`
ORDER BY distance;

If the data structures are set right, this should return a list, ordered by distance, of all pairs of coordinates within a given radius.如果数据结构设置正确,这应该返回一个列表,按距离排序,包含给定半径内的所有坐标对。 You can set the coordinates at the top of this function, or maybe even wrap it in a stored procedure (if you don't mind the overhead).您可以在此函数的顶部设置坐标,或者甚至可以将其包装在存储过程中(如果您不介意开销)。

For further reading, I know that PostgreSQL has an " Earth Distance " function as well, so if you are looking to include the calculation of true distance traveled between points, it might be useful!为了进一步阅读,我知道 PostgreSQL 也有一个“ 地球距离”功能,所以如果你想计算点之间的真实距离,它可能会有用!

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM