在数据库中使用纬度/经度从邮政编码进行地理搜索

时间:2013-11-08 20:57:40

标签: mysql ruby-on-rails thinking-sphinx zipcode

我很难理解如何实现这一目标,并且似乎有很多人在没有答案的情况下提出这个问题。我有一个带有邮政编码的用户表。我创建了一个拉链表,其中包含美国纬度/经度的每个邮政编码。

我想要做的是连接两者,以便用户可以搜索其他用户。我有思考狮身人面像,我宁愿继续使用它。我想为用户提供搜索距离的复选框(5,10,25,50,100,500英里)。结果应始终返回最近的用户。

我认为不需要来自控制器或型号的代码,但如果需要,请询问,我会提供。

搜索表单:

<%= form_tag searches_path, method: :get do %>
<p>
        <%= text_field_tag :search, params[:search] %>
        <%= button_tag "Search", name: nil %>
        </p>
<% end %>

<P><%= link_to "Advanced Search", new_search_path %><p>

<%= form_tag users_path, method: :get do %>
<%= label :zip_code, "Enter zip code: " %>
<%= text_field_tag :zip_code, params[:zip_code] %>
<% end %>

/indices/user_index.rb:

     ThinkingSphinx::Index.define :user, :with => :active_record do
  # fields
  indexes name, :as => :user, :sortable => true
  indexes religion, zip_code, about_me, career, sexuality, children, user_smoke, user_drink, gender, ethnicity, education


  # attributes
  has id, created_at, updated_at
  has zips.city, :as => :zip_city

  has "RADIANS(zips.lat)",  :as => :latitude,  :type => :float
  has "RADIANS(zips.lon)", :as => :longitude, :type => :float


end

用户模型:

  has_and_belongs_to_many :zips

Zip模型:

class Zip < ActiveRecord::Base
  attr_accessible :city, :lat, :lon, :code, :zipcode
  has_and_belongs_to_many :users

  validates :code, uniqueness: true

    self.primary_key = 'code'      

  def self.code(code)
    find_by(:code => code)
  end


end

用户表格包含以下列:zip_code

邮政编码表格包含以下列:codecitystatelatlon

4 个答案:

答案 0 :(得分:1)

第一步是在用户及其位置之间创建关联,以便可以从用户的ActiveRecord引用该位置的ActiveRecord。

class User < ActiveRecord::Base
  belongs_to :location
  attr_accessible :name
end

class Location < ActiveRecord::Base
  attr_accessible :city, :latitude, :longitude, :zipcode
end

接下来,在索引中使用关联。

您必须为位置模型上的字段创建别名,以确保位置表已加入。并且您必须为位置的纬度和经度添加属性:

ThinkingSphinx::Index.define :user, :with => :active_record do
  # fields
  indexes name

  # attributes
  has created_at, updated_at
  has location.city, :as => :location_city
  has "RADIANS(locations.latitude)",  :as => :latitude,  :type => :float
  has "RADIANS(locations.longitude)", :as => :longitude, :type => :float

end

正如其他人已经提到的那样,由于地球不平坦,当你计算位置之间的距离时,你需要考虑到这一点。 Haversine功能对此有好处。思考Sphinx内置了它,您可以使用:geo对其进行过滤和排序。

然后例如,以度为单位查找200公里范围内lat / lng参数的所有用户,按最近的顺序排序:

class DistanceController < ApplicationController
  def search
    @lat = params[:lat].to_f * Math::PI / 180
    @lng = params[:lng].to_f * Math::PI / 180
    @users = User.search :geo => [@lat, @lng], :with => {:geodist => 0.0..200_000.0}, :order => "geodist ASC"
  end
end

对于调试,很高兴知道在视图中你也可以参考计算的距离:

  <% @users.each do |user| %>
    <tr>
      <td><%= user.name %></td>
      <td><%= user.location.zipcode %></td>
      <td><%= user.location.city %></td>
      <td><%= user.distance %></td>
    </tr>

编辑:添加了有关我的工作版本的更多详细信息,以及完整性&#39;还要添加表定义。 (MySQL,使用db:migrate生成,这些是MySQL Workbench生成它们的创建脚本):

CREATE TABLE `users` (
  `id` int(11) NOT NULL AUTO_INCREMENT,
  `name` varchar(255) DEFAULT NULL,
  `location_id` int(11) DEFAULT NULL,
  `created_at` datetime NOT NULL,
  `updated_at` datetime NOT NULL,
  PRIMARY KEY (`id`),
  KEY `index_users_on_location_id` (`location_id`)
) ENGINE=InnoDB AUTO_INCREMENT=4 DEFAULT CHARSET=utf8;


CREATE TABLE `locations` (
  `id` int(11) NOT NULL AUTO_INCREMENT,
  `zipcode` varchar(255) DEFAULT NULL,
  `latitude` float DEFAULT NULL,
  `longitude` float DEFAULT NULL,
  `city` varchar(255) DEFAULT NULL,
  `created_at` datetime NOT NULL,
  `updated_at` datetime NOT NULL,
  PRIMARY KEY (`id`)
) ENGINE=InnoDB AUTO_INCREMENT=4 DEFAULT CHARSET=utf8;

答案 1 :(得分:1)

听起来您将问题映射到位置时,原始问题似乎具有误导性。

不是将位置映射到用户,而是位置表应该是基于zip的所有位置的参考表。 然后,您应该向位置添加范围,以便您可以按邮政编码搜索位置。

这样的事情:

# Add scope to /models/location.rb
scope :by_zip_code, ->(code) { where('code = ?', code) }

然后在用户类中,添加方法,而不是说用户has_many位置,以根据用户邮政编码检索所有位置。

示例:

# in /models/user.rb

 def location

    if Location.by_zip_code(self.zip_code.to_s).any?
        # you can return all here if you want more than one
        # for testing just returning the first one
        return Location.by_zip_code(self.zip_code.to_s).first
    else
        return nil
    end
end

然后,当您拥有用户对象时,可以使用新的实例方法:

user = User.first
user.location

答案 2 :(得分:0)

你可以使用geocoder gem完成所有这些(以及更多)。 gem是可自定义的,您可以指定所需的任何地图API(默认为谷歌地图)。这是地理编码器上的railscast episode,其中详细说明了使用情况。

答案 3 :(得分:-1)

您可以使用Haversine公式计算两点之间的距离。

Landon Cox已经遇到了在这个位置为你创建Haversine课程的麻烦: http://www.esawdust.com/blog/gps/files/HaversineFormulaInRuby.html

我在之前的项目中使用过这个类,它的工作非常好

您需要做的就是将逻辑与每个用户与当前位置或所选用户的距离进行比较。 既然你已经拥有了lat和long,那么这应该非常简单。

希望这有帮助。