每个用户都有经度、纬度两个字段,我想查询我附近 1km 的用户,然后按由近到远的方式排序,该怎么设置,怎么查询?
借道问下,大家是怎么用经纬度的,因为微信,百度,其它几个经纬度是有偏移,不兼容的。
大家一般什么场景,怎么处理这些数据的呢
用过一个 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")
我建议分两个步骤 以你为中心点,取你 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
计算地球上两点的距离的参考资料: 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
你这个问题,主要是解决了计算两点间的距离这个后就好解决了。
TL;DR: 这个东西还是不要自己计算。 如果是玩玩的话就算了。 如果认真的话还是换 Postgres 上 PostGIS, 这个东西是开源 GIS 数据处理最好的东东,没有之一。
如果先计算用户跟我的距离,再找出小于 1km 的,如果总用户有 100 万,你需要 100 万用户都算一遍,即便是算完了,还需要排序。 如果用坐标的盒子查询,第一不精密,角上的用户已经超过 1km, 第二排序还是要计算距离,第三不够灵活,如果明天需求改了,需要找条距离一条曲线马路一公里以内的用户的时候,又该怎么办呢。 而且,上面的计算极有很大的可能是错误的。
原因如下:
简单来说,如果要画地图,首先要找准一个平面,地球根本不是圆的,也不是椭圆的,是个橡皮泥砸的有点像圆球的这么一坨。最早地图用的水平面,后来发现海拔因为潮汐关系也靠不住,后来用重力加速度,重力加速度为同一常量的作为一个平面。得出的结果是这个
搞清楚形状了,找到地球表面的高度了,但是做成地图还需要一个投影模式。 我们平常看的世界地图是椭圆投影。这个对于形状表示比较好,距离计算跟面积误差很大。赤道周围跟极低周围误差极大。 当然还有别的投影模式,比如把世界地图剥成橘子皮那样的。
除了投影模式,还需要一个 Datum,就是地理坐标系统。说白了就是经纬度。 这个标准就更多了,北美有 NAD 83, WGS 84, 每个系统定义了地球的椭圆投影的椭圆的具体规格。长半径短半径有多长。这个差一点,两点的距离就会差很多。
上面所有的因素加在一起,组成了 Spatial Reference System(SRS 空间参照系统).
你可能会问我说这么多跟计算距离有毛关系。
关系就是,在不同的 SRS 参考坐标系下,相同的经纬度根本不在一个地方。更不要说两个点的距离了。 算出来的距离可能差很远。
所以当你拿到一个坐标,首先要了解是什么 SRS, 楼上说的各种不同地图提供商和用户位置提供商提供的经纬度误差,就是因为用了不同的参考坐标系。国内肯定有自己的 SRS 这些我就不太了解了。 知道了是什么坐标系,才能计算距离或者做其他查询,然后也需要考虑到地球的弧面因素。
PostGIS 提供了以下功能:
不要搞得那么复杂 上一千米 下一千米 左一千米 右一千米 把用户找出来 大概一下就行了 不用介意有用户是 1414 米 如果真介意就把框里的用户穷举一次 也是可以搞定的
我们用geocoder完全可以达到效果
distance = 20
center_point = [40.71, 100.23]
box = Geocoder::Calculations.bounding_box(center_point, distance)
Venue.within_bounding_box(box)