2

我正在使用 DataMapper 管理具有按纬度和经度定位的兴趣点 (POI) 的数据库。我想做一个查询并找到给定纬度和经度 x 距离内的所有 POI。例如,纬度 45、经度 90 1000m 范围内的所有 POI。

我设置了这个:

class POI
  include DataMapper::Resource
  property :id,                  String,  :key => true
  property :title,               String,  :required => true
  property :lat,                 Float,   :required => true
  property :lon,                 Float,   :required => true

  def distance(latitude, longitude)
    # Taken from https://github.com/almartin/Ruby-Haversine
    earthRadius = 6371 # Earth's radius in km

    # convert degrees to radians
    def convDegRad(value)
      unless value.nil? or value == 0
        value = (value/180) * Math::PI
      end
      return value
    end

    deltaLat = (self.lat - latitude)
    deltaLon = (self.lon - longitude)
    deltaLat = convDegRad(deltaLat)
    deltaLon = convDegRad(deltaLon)

    # Calculate square of half the chord length between latitude and longitude
    a = Math.sin(deltaLat/2) * Math.sin(deltaLat/2) +
      Math.cos((self.lat/180 * Math::PI)) * Math.cos((latitude/180 * Math::PI)) *
      Math.sin(deltaLon/2) * Math.sin(deltaLon/2);
    # Calculate the angular distance in radians
    c = 2 * Math.atan2(Math.sqrt(a), Math.sqrt(1-a))

    distance = earthRadius * c
    return distance
  end
end

我希望能够通过类似于以下的调用找到记录:

pois = POI.all(distance(45,90).lte => 1000)

但这给出了一个错误:

./poi-provider.rb:44:in `<main>': undefined method `distance' for main:Object (NoMethodError)

我阅读了 dkubb 的关于在方法中定义复杂查询的答案,但这是不同的,因为我需要传入参数并且我试图将该方法用作条件。

我该怎么做——或者,有没有更好的方法来使用 DataMapper 来查找给定纬度和经度附近的点,而不会崩溃并且只使用原始 SQL?

4

2 回答 2

2

每当您处理纬度和经度时,我强烈建议您使用空间数据库,而不仅仅是两个 Float 列。否则,一旦您拥有大量数据,您的查询就会变得异常缓慢。

MySQL 确实有空间扩展,但开源空间数据库的事实上的标准是 PostGIS 对 Postgres 的扩展。幸运的是,您可以很容易地将它与 DataMapper 一起使用。

安装

你需要这个dm-postgis插件。主要代码似乎是废弃软件,但有人对其进行了更新以与当前的 DataMapper 一起使用。从 github 克隆 dm-postgis,应用此拉取请求,然后gem install从本地目录。

如果您安装dm-ar-finders,您还会发现生活更轻松,它允许您使用 SQL 查询并获取 DataMapper 对象。

数据模型

然后,您可以将坐标属性定义为 PostGIS 几何类型,如下所示:

property :location, PostGISGeometry

并这样设置:

location=GeoRuby::SimpleFeatures::Point.from_x_y(longitude,latitude)

您可能希望在您的位置列上创建一个索引。您需要地理数据的 GIST 索引,而不是 Postgres 的默认 B 树。不幸的是,dm-postgis 不够聪明,无法创建它,所以从 psql 命令行执行它:

CREATE INDEX index_poi_location ON poi USING GIST (location);

邻近查询

好的。所以现在你有一个位置字段。您如何运行邻近查询?

关键是 PostGIS 的ST_DWithin函数。正如文档所说,这“如果几何形状在彼此之间的指定距离内,则返回 true”。所以在你的情况下,你会做这样的事情:

POI.find_by_sql "SELECT id,name FROM pois WHERE ST_DWithin(location, ST_GeomFromText('POINT(45 90)') , 0.05 )"

0.05 是所需的距离。请注意,这是度数,而不是米。(谈论投影和 SRID 超出了这个答案的范围,但是,例如,如果你所有的数据都在英国,你可以使用 OSGB 投影并很容易地获得米。或者,有人可以改进 dm-postgis 到处理 PostGIS 地理以及几何,它们本身就可以处理米距离。它在我的清单上......有一天。)

这一切的真正伟大之处在于 PostGIS 将更复杂的地理查询置于您的掌握之中。例如,在我目前正在进行的一个项目中,我是这样使用它的:

    shops=Shop.find_by_sql [<<-EOS,route.id]
        SELECT listings.id, listings.name, listings.provides
          FROM listings
          JOIN routes ON ST_DWithin(listings.location, routes.linestring, 0.05)
         WHERE routes.id=? AND type='Shop'
      ORDER BY ST_Line_Locate_Point(routes.linestring, listings.location)
    EOS

这会找到沿特定路线(折线)的所有商店并命令它们从头到尾……而且做得非常快。

于 2013-01-27T18:21:04.960 回答
1
  1. 您正在调用实例方法而不实例化。这就是导致您的错误的原因。
  2. 您不能使用 DataMapper 方法作为条件。这意味着您将不得不编写一些 SQL。

就像是:

class POI
  ...
  def self.all_within_distance(distance, longitude, latitude)
    repository.adapter.query "SELECT *
    FROM pois 
    WHERE " + distance.to_s + " > ** Complicated math coded in SQL that gets uses poi.lon and poi.lat to get the distance from the given longitude and latitude ** " 
  end

然后打电话

POI.all_within_distance(1000, 45, 90)
于 2013-01-07T23:19:20.720 回答