如何检查经纬度是否在城市范围内

3
我怎样才能确定我的纬度/经度是否在城市限制范围内,例如大伦敦由以下边界包围:

[bbox=-0.489,51.28,0.236,51.686]

来源:

http://wiki.openstreetmap.org/wiki/Bounding_Box

我怎样才能确认一个地点(纬度/经度)是否在城市限制范围内?
51.55238,0.047032

这方面是否已经有宝石可用?或者最好的方法是什么?

Bounty更新:

我有一个可行的解决方案,但感觉不太对,我使用了geocoder宝石,我的用户有geocoded_by和lat/long属性。所以这就是我如何做到的:

def self.user_from_dublin(user_id)
  near([53.349937,-6.261917], 15).pluck(:id).include?(user_id)
end

这段代码加载了所有来自都柏林的用户,并将用户的ID与都柏林的用户进行比较。如果找到该ID,则认为该用户来自都柏林。

我认为当用户数量增加时,这种方法可能不太有效。

更新:

我正在使用Postgres数据库。


如果 box.lowx <= x <= box.highx 且 box.lowy <= y <= box.highy,则返回什么? - David
@bjhaid,我是否可以不使用MongoDB,真的需要MongoDB吗?我已经在我的PostgreSQL表中得到了纬度/经度,并且我所需要检查的只是用户的纬度/经度是否在边界框内。 - Gandalf StormCrow
@GandalfStormCrow 我提供了 geocoder gem 的主页链接,并建议使用mongodb作为替代方案。 - bjhaid
你对准确性有多在意?如果地理位置略微超出城市限制,这对你有多大影响?(我问这个问题是因为随着城市/用户数量的增加,你将面临性能问题) - Abdo
http://www.rubygeocoder.com/ - Aleksei Matiushkin
显示剩余2条评论
9个回答

3

你能使用原始的 SQL 吗?<@ 几何运算符将检查 是否包含在 中。

select '51.55238,0.047032'::point <@ '-0.489,51.28,0.236,51.686'::box;
 ?column? 
----------
 f

这将是特定于数据库的。 - seand
1
@seand 这个问题只被标记为 postgresql. - Clodoaldo Neto

3

您不需要任何特殊的东西。

边界框是一个矩形:

bbox = left,bottom,right,top

  +-----------(10,0)
  |              |
  |              |
  |              |
  |              |
  |              |
(0,10)-----------+

奇怪的(左下)到(右上)编号是因为,经度从左到右测量(简单来说,-180°在地球仪的左侧,+180°在地球仪的右侧),而纬度从底部向上测量(简单来说,-90°在地球仪的底部,+90°在地球仪的顶部)。

因此,根据示例:

(0,10)   = left, bottom = min longitude, min latitude
(10,10)  = right, top   = max longitude, max latitude

现在你想知道一个点(5,5)是否落在它内部。
  +-----------(10,10)
  |              |
  |              |
  |     (5,5)    |
  |              |
  |              |
(0,0)------------+

所以,您需要做的就是在Postgres(或任何其他数据库)中为城市创建一个表:
CREATE TABLE cities(
  city_name      VARCHAR(xxx),
  min_longitude  DECIMAL,
  min_latitude   DECIMAL,
  max_longitude  DECIMAL,
  max_latitude   DECIMAL
);

现在假设您想要找出一个经度为6,纬度为5的位置属于哪个城市,那么只需使用以下查询:

SELECT city_name 
FROM cities 
WHERE (min_latitude  >= 6 AND max_latitude  <= 6)
AND   (min_longitude >= 5 AND max_longitude <= 5)

将用户的纬度/经度替换为5和6,你就能得到用户所属城市。

编辑:小混乱,格式略作修改。


我认为你忽略了跨越赤道或本初子午线的使用情况。这是使用已建立的 gem 的好理由。 - eabraham
1
我不明白为什么在赤道等地会有问题,因为纬度/经度只是在连续函数上穿过0而已。但在极点会失败。只要你不像将北格陵兰岛的大片土地围起来那样做,这似乎是一个不错的简单解决方案。 - seand
1
实际上,你会在本初子午线的另一侧出现不连续性。穿过该线时,经度180度变为-180度。 - seand

3
也许这有点过度,但如果你使用 postgres ,你可以安装 postgis 扩展来管理空间数据。然后在 irb 中,你可以像这样做:
result = ActiveRecord::Base.connection.execute('SELECT
ST_Contains(ST_SetSRID(ST_MakeBox2D(ST_Point(-0.489, 51.28), ST_Point(0.236, 51.686)), 4326),
ST_SetSRID(ST_Point(-0.1265, 51.483), 4326))')

查询正在使用ST_contains函数检查点是否在给定的bbox内。
这将返回:
=> #<PG::Result:0x007fa517fcbe08 @connection=#<PG::Connection:0x007fa5167f8970 @socket_io=nil, @notice_receiver=nil, @notice_processor=nil>>

然后你可以这样做:
result.first

这将返回:
{"st_contains"=>"t"}

使用这个点-0.7265, 44.483(一个在bbox之外的点),结果将会是:

{"st_contains"=>"f"}

如果您不想使用原始SQL,可以使用gem管理空间数据。 一个非常好的是:rgeo。我建议阅读创作者的博客
使用rgeo,您可以为模型定义具有“geotypes”(如点、多边形等)的属性,然后使用诸如contains?之类的函数。
我给您留下了一份非常基本的说明文档,以安装ubuntu上的postgisgist

2
如果它是布尔值,为什么不使用where将结果限制为仅您的用户。这样只涉及一个SQL查询,其大小可能为空或为1(您的用户是唯一记录)。
def self.user_from_dublin(user_id)
  !(near([53.349937,-6.261917], 15).where(id: user_id).empty?)
end

empty? 如果用户不在您的范围内,将返回 true,因此在开头的 ! 会否定这一点,使得当用户在边界之外时,函数将返回false


2

1

我做了一些假设,请告诉我它们是否正确:

假设1: 这个经纬度表示用户居住的地方,并且当用户搬家时,这个位置会不经常更新。

假设2: 这个经纬度不代表用户当前的位置,而且会不断更新。

假设3: 你有所有想要包括在系统中的城市的边界框。

基于这些假设,我认为最好的处理方法是将每个人放入“城市”桶中。创建一个具有与用户多对多关系的城市模型:

class City < ActiveRecord::Base
  has_many :user_city
  has_many :users, :through => :user_city

  attr_accessible :name, :bounding_box #bounding box is an array with 4 values which represent the corners of the city limits.
end

class User < ActiveRecord::Base
  after_validation :geocode 
  after_create :assign_city

  has_many :user_city
  has_many :city, :through => :user_city

  attr_accessible :email, :created_at, :password, :updated_at, :latitude, :longitude

  def assign_city
    City.all.each do |city|
      inCity = self.within_bounding_box(city.bounding_box)
      if inCity
       UserCity.create(:user=>self,:city=>city)
       break
      end
    end
  end

end

class UserCity < ActiveRecord::Base
  belongs_to :user
  belongs_to :city

  attr_accessible :user, :city
end

现在您可以使用以下代码查询城市中的用户:

User.joins(:city).where({:cities=>{:name=>"London"}})

1

由于用户模型有 latlon 属性,因此使用一个 KISS 解决方案可能会有帮助。

如果您拥有伦敦的范围框,您可以形成一个 activerecord 范围,该范围将实现...

LONDON_BOX = [-0.489,51.28,0.236,51.686]
scope :inside_london -> {
  where(lat: LONDON_BOX[1]..LONDON_BOX[3]).
  where(lon: LONDON_BOX[0]..LONDON_BOX[2])
}

因此,您可以使用User.inside_london
请记住,这种类型的双范围查询性能不佳,MySQL无法使用适当的索引。对此,@david提供了一个有用的评论,使用mongodb及其地理空间功能将有所帮助。(但更改数据库是一个严肃的决定)

1
如果您想要查询最近的城市名称与Lat/Lon坐标相关,则可以使用谷歌地图的Geocoder服务。您需要设置一个包含LatLng信息的GeocoderRequest并发出请求。请注意,不要直接表述这一点。保留HTML标签。

1

BoundBox是一个矩形,而不是精确的城市边界。在每个查询中计算城市边界也不是很有效率。我认为你可以在使用地理编码器保存记录之前,在回调函数中保存城市关系。如果您有更好的解决方案,请分享。


网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接