Skip to content

Instantly share code, notes, and snippets.

@pjb3
Created June 27, 2009 06:01
Show Gist options
  • Star 3 You must be signed in to star a gist
  • Fork 4 You must be signed in to fork a gist
  • Save pjb3/136919 to your computer and use it in GitHub Desktop.
Save pjb3/136919 to your computer and use it in GitHub Desktop.
namespace :load do
ZIP_CODE_DATA_URL = 'http://www.census.gov/tiger/tms/gazetteer/zips.txt'
# Swiped from ActiveRecord migrations.rb
def announce(message)
length = [0, 75 - message.length].max
puts "== %s %s" % [message, "=" * length]
end
desc "Loads zip codes from #{ZIP_CODE_DATA_URL}"
task :zip_codes => :environment do
begin
time = Benchmark.measure do
require 'fastercsv'
require 'open-uri'
FasterCSV.parse(open(ZIP_CODE_DATA_URL).read) do |row|
# Connascence of Position, Jim Weirich forgive me!
zip = Zip.create!(
:code => row[1],
:state => row[2],
:city => row[3],
:lat => row[4],
:lon => row[5])
puts "%20s, %2s, %5s" % [zip.city, zip.state, zip.code]
end
end
announce "Loaded %5d zip codes in (%2dm %2.0fs)" % [Zip.count, *time.real.divmod(60)]
rescue LoadError
puts "This rake task requires fastercsv. To install, run this command:\n\n sudo gem install fastercsv\n\n"
end
end
desc "Creates n number random users using the Random Data gem, defaults to 1000"
task :random_users, :n, :needs => :environment do |t, args|
begin
n = args.n.to_i < 1 ? 1000 : args.n.to_i
time = Benchmark.measure do
require 'random_data'
domains = %w[yahoo.com gmail.com privacy.net webmail.com msn.com hotmail.com example.com privacy.net]
zips = Zip.all #Can we fit 29k zip codes into memory?
n.times do |i|
user = User.new(:first_name => Random.firstname, :last_name => Random.lastname)
user.username = "#{user.first_name[0,1]}#{user.last_name}#{i}".downcase
user.password = user.password_confirmation = "secret"
user.email = "#{user.username}\@#{domains.rand}"
user.address = Random.address_line_1
zip = zips.rand
user.city = zip.city.titleize
user.state = zip.state
user.zip = zip
user.save!
puts "%6d: %15s %15s, %30s, %20s, %2s, %5s" % [(i+1), user.first_name, user.last_name, user.email, user.city, user.state, user.zip.code]
end
end
announce "Loaded %6d users in (%2dm %2.0fs)" % [n, *time.real.divmod(60)]
rescue LoadError
puts "This rake task requires random_data. To install, run this command:\n\n sudo gem install random_data\n\n"
end
end
end
class User < ActiveRecord::Base
belongs_to :zip
named_scope :within_miles_of_zip, lambda{|radius, zip|
# Get the parameters for the search
area = zip.area_for(radius)
# now find all zip codes that are within
# these min/max lat/lon bounds and return them
# weed out any zip codes that fall outside of the search radius
{ :select => "#{User.columns.map{|c| "users.#{c.name}"}.join(', ')}, sqrt(
pow(#{area[:lat_miles]} * (zips.lat - #{zip.lat}),2) +
pow(#{area[:lon_miles]} * (zips.lon - #{zip.lon}),2)) as distance",
:joins => :zip,
:conditions => "(zips.lat BETWEEN #{area[:min_lat]} AND #{area[:max_lat]})
AND (zips.lon BETWEEN #{area[:min_lon]} AND #{area[:max_lon]})
AND sqrt(pow(#{area[:lat_miles]} * (zips.lat - #{zip.lat}),2) +
pow(#{area[:lon_miles]} * (zips.lon - #{zip.lon}),2)) <= #{area[:radius]}",
:order => "distance"}
}
def within_miles(radius)
self.class.within_miles_of_zip(radius, zip)
end
end
class Zip < ActiveRecord::Base
has_many :users
def self.code(code)
first(:conditions => {:code => code})
end
# Returns a hash with values that can be used
# to perform a proximity search query
def area_for(radius)
area = {}
area[:radius] = radius.to_f
area[:lat_miles] = 69.172 #this is constant
#longitude miles varies based on latitude, that is calculated here
area[:lon_miles] = (area[:lat_miles] * Math.cos(lat * (Math::PI/180))).abs
area[:lat_degrees] = radius/area[:lat_miles] #radius in degrees latitude
area[:lon_degrees] = radius/area[:lon_miles] #radius in degrees longitude
#now set min and max lat and long accordingly
area[:min_lat] = lat - area[:lat_degrees]
area[:max_lat] = lat + area[:lat_degrees]
area[:min_lon] = lon - area[:lon_degrees]
area[:max_lon] = lon + area[:lon_degrees]
area
end
end
@masterkrang
Copy link

This is a great post. Just curious if you're still using this or if there have been any updates? Found your article here: http://paulbarry.com/articles/2009/06/27/zip-code-proximity-search-with-rails thanks!

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment