Rails 如何查询离我 1km 的用户

chinalegend · 2014年07月12日 · 最后由 sevk 回复于 2014年07月28日 · 9589 次阅读

每个用户都有经度、纬度两个字段,我想查询我附近 1km 的用户,然后按由近到远的方式排序,该怎么设置,怎么查询?

可以使用 mongodb 的二维地理位置查询。

#1 楼 @w7938940 额!我用的是 mysql 数据库,可以不?

#2 楼 @chinalegend 你可以自己写算法实现 mysql 的距离排序,但是速度绝对无法赶上 mongodb 二维地理位置索引的速度。

如果能换成 PostgresSQL 的话可以用 PostGIS 这个 extension.

5 楼 已删除

借道问下,大家是怎么用经纬度的,因为微信,百度,其它几个经纬度是有偏移,不兼容的。

大家一般什么场景,怎么处理这些数据的呢

用过一个 geocoder 的 gem,没高级需求,能用。

使用 gem: geokit-rails

https://github.com/geokit/geokit-rails

具体楼主的例子:

geokit_origin = Geokit::LatLng.new(latitude, longitude)
order_sql = User.distance_sql(geokit_origin)
users = User.within(1, origin: geokit_origin).order("#{order_sql} ASC")

#8 楼 @as181920 微信和百度的经纬度确实有偏移,如果开发微信应用,采取腾讯的经纬度,其他的,就要看需求了,视用户提交的经纬度是百度还是腾讯的

#10 楼 @sefier 微信端肯定是腾讯的,这个和腾讯地图是兼容的吧,后台一般是百度的,所以就不能直接计算了

我建议分两个步骤 以你为中心点,取你 1km 的 2 个点,左上角和右下角的经纬度,以 2 个点的经纬度去搜你的用户,只要在这个正方形访问内得用户都在你的周围,直接比较大小,查询还算是很快的

很久以前的代码,没做优化,大致代码如下:

class LatLngCalculation
  attr_accessor :lat,:lng
  def initialize(lat,lng)
    @lat,@lng = lat,lng
  end
  #返回最大最小范围
  def get_around(raidus)
    latitude = self.lat.to_f
    longitude = self.lng.to_f
    degree = (24901 * 1609) / 360.0
#    raidus_mile = (raidus.to_f / 1000)
    raidus_mile = (raidus.to_f)
    dpm_lat = 1 / degree
    radius_lat = dpm_lat * raidus_mile
    min_lat = latitude - radius_lat
    max_lat = latitude + radius_lat
    mpd_lng = degree * Math.cos(latitude * (Math::PI/180))
    dpm_lng = 1 / mpd_lng
    radius_lng = dpm_lng * raidus_mile
    min_lng = longitude - radius_lng
    max_lng = longitude + radius_lng
    hash = Hash.new
    hash['minLat'] = min_lat
    hash['minLng'] = min_lng
    hash['maxLat'] = max_lat
    hash['maxLng'] = max_lng
    hash
  end
end

如果你需要对这个范围内得用户进行距离排序,可以用这个,是定义的 mysql 的方法, 好处就是可以直接进行 order by 我以前用的,mysql 查询两点的距离,你当前的点,加上用户的点。

#下面的sql需要创建到mysql里
=begin
"
DELIMITER $$
USE db_development
goto
DROP FUNCTION IF EXISTS `MyDistance`$$
CREATE FUNCTION `MyDistance`(lat1 FLOAT, lng1 FLOAT, lat2 FLOAT, lng2 FLOAT)
 RETURNS  DOUBLE
BEGIN
 DECLARE distance DOUBLE;
 SET distance= 2 * 6378.137* ASIN(SQRT(POW(SIN(PI() * (lat1-lat2)/360),2)+COS(PI()*lat1/180)*COS(lat2*PI()/180)*POW(SIN(PI()*(lng1-lng2)/360),2)));
 RETURN distance;
END $$
DELIMITER;
"
=end

#5 楼 @yfractal 应该是勾股!原谅我强迫症😣

#11 楼 @as181920 前台是用户提交的,你当然没法指定了,我说的就是后台

比如我开发的是微信应用,我后台的经纬度,一律采用腾讯地图标准,这样用户提交的经纬度和我后台计算所用的经纬度就一致了。(之前采用百度的经纬度,用户提交的是腾讯的经纬度,结果两者有 500m 的误差,踩了一个坑,后面就改用腾讯的了)

@badboy 这个正方形方案好聪明。

@billy 谢谢,貌似也有封装好的 gem 算距离,楼上有人说过 @sefier 用算法做,就无关纠偏的事情,如果你用百度地图的 api 算,微信用的是 google 的地图维度,纠偏问题很麻烦滴。。。。

计算地球上两点的距离的参考资料: http://www.codecodex.com/wiki/Calculate_distance_between_two_points_on_a_globe

之前一个项目(火车 GPS 报站)中用到的相应的 Ruby 代码:

def get_distance( lat1, lon1, lat2, lon2 )
    rkm = 6371
    dlon = lon2 - lon1
    dlat = lat2 - lat1
    dlon_rad = dlon.to_rad
    dlat_rad = dlat.to_rad
    lat1_rad = lat1.to_rad
    lon1_rad = lon1.to_rad
    lat2_rad = lat2.to_rad
    lon2_rad = lon2.to_rad

    a = Math.sin(dlat_rad/2)**2 + Math.cos(lat1_rad) * Math.cos(lat2_rad) * Math.sin(dlon_rad/2)**2
    c = 2 * Math.asin( Math.sqrt(a))

    dKm = rkm * c * 1000 # delta in meters
  rescue
    0
  end

你这个问题,主要是解决了计算两点间的距离这个后就好解决了。

#16 楼 @badboy 不要乱插楼,我们讨论的是经纬度取点,不是说计算,有现成的 GEM 计算距离,我干嘛要自己写算法?

#1 楼 @w7938940 和楼上想法一致

一楼是正解,其他基本扯呼。

@hisea 嘿嘿,海信,你确定 1000 米距离有这么大落差???简单的当圆球没啥不好,这需求没到那么大距离的精度

不要搞得那么复杂 上一千米 下一千米 左一千米 右一千米 把用户找出来 大概一下就行了 不用介意有用户是 1414 米 如果真介意就把框里的用户穷举一次 也是可以搞定的

#25 楼 @badboy 1000 米球不球的误差可能不大,但是不同坐标系误差就大了。

#25 楼 @badboy 另外楼主的需求可能精度要求不高,稍微马虎点无所谓。

我现在的公司做的项目是房地产搜索引擎,需要吧房产放到地图上,如果放的位置差出去 50 米,就差出去好几栋房子了。其实我们这个按说也无所谓,用户到了周围自己找去吧,可是产品又要加 Google 街景地图,这个误差就麻烦了。照片跟街景看的房子就不一样了。

我们用geocoder完全可以达到效果

distance = 20
center_point = [40.71, 100.23]
box = Geocoder::Calculations.bounding_box(center_point, distance)
Venue.within_bounding_box(box)

其实正方形反而节约 CPU 和内存,没必要计较这几十米吧?

需要 登录 后方可回复, 如果你还没有账号请 注册新账号