2

DataMapperを使用して、緯度と経度で特定されたPOI(Points)を持つデータベースを管理しています。クエリを実行して、指定された緯度と経度の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

緯度と経度を扱う場合は常に、2つのFloat列ではなく、空間データベースを使用することを強くお勧めします。そうしないと、データの量がごくわずかになるとすぐに、クエリが非常に遅くなります。

MySQLには空間拡張機能がありますが、オープンソースの空間データベースのデファクトスタンダードはPostgresのPostGIS拡張機能です。幸い、これは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)

おそらく、場所の列にインデックスを作成することをお勧めします。PostgresのデフォルトのBツリーではなく、地理データのGISTインデックスが必要です。残念ながら、dm-postgisはこれを作成するのに十分賢くないので、psqlコマンドラインから作成します。

CREATE INDEX index_poi_location ON poi USING GIST (location);

近接クエリ

Ok。これで、ロケーションフィールドができました。近接クエリをどのように実行しますか?

重要なのは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 に答える