Geocoding as easy as 1-2...
3?, nope there is no 3. Geocoding as easy as 1-2!
- Create your models
- Find them!
event = Event.create :street => "777 NE Martin Luther King, Jr. Blvd.", :city => "Portland", :region => "Oregon", :postal_code => 97232 # how far am I from RailsConf 2007? event.distance_to "49423" #=> 1807.66560483205 # Find our new event, and any other ones in the area Event.find(:all, :within => 50, :origin => "97232") # Find the nearest restaurant with beer Restaurant.find(:nearest, :origin => event, :conditions => 'beer = true')
I know you’re excited, so instead of blabbing on-and-on, FAQ-style:
How are you performing this voodoo?
A slick new plugin called acts_as_geocodable.
How do I get it?
script/plugin install -x git://github.com/collectiveidea/acts_as_geocodable.git
How do I set it up?
Its all in the README.
Why did you write this when there’s already several geocoding plugins?
The Ruby geocoding space is pretty fragmented right now. There’s all kinds of geocoders available, and none of them provide everything. We’re determined to fix that with Graticule and acts_as_geocodable.
We believe that all the heavy lifting of geocoding, distance calculations, etc., should be left to a gem, and only code that is directly related to Rails should be a Rails plugin. Even more, all the different geocoders should be available in the same package and have the same interface. A plugin should then be built on top of the gem that makes your apps geo-capabilities seem like voodoo.
We started our own projects because we didn’t think anyone else got it right. Recently, Bill Eisenhaur and Andre Lewis did a pretty good job, and we borrowed some of their ideas, but I still have issues with their approach and code, mainly that it’s all tied up into a Rails plugin.
Did you get it right?
I think we have a great foundation. It’s not perfect, nor does it have all the features of the other packages, but I think it is well architected.
Why don’t you work together?
Excellent idea! We would love to.
How does this fit in with JWZ’s use case?
(Why are you asking this?) A third of the problem is knowing where, a third is when, and a third is who. You’re on your own for the who and when.
Happy geocoding!
16 Comments
Great work. This is what I wanted to do but was short on time when I wrote acts_as_geocode. You guys did a much more thorough job. I was just doing something quick to make my life easier on some real estate projects.
Now I will switch soon.
Hi,
Thanks for the plugin! :)
I’ve installed Graticule and acts_as_geocodable but I can’t seem to get it to run reliably.
I’m on Windows XP, using a Yahoo Application id.
It seems setting Geocode.geocoder in the environment.rb isn’t “sticking”. It set when the app is loaded but is nil when Geocode.rb is hit as part of the save process.
I’m working around this by using:
def self.geocode(location)
if geocoder.nil?
self.geocoder = Graticule.service(:yahoo).new ‘Dd0Ib1zV34G4ypZoQ_DAedMH5uU7IejCvf5Mzw8llr2YkKFZzkymiyumE_OJ’
end
…
end
Thanks!
Shawn
Shawn,
That’s really odd. Looking through the code, I have no clue what would be causing that.
I don’t have immediate access to a Windows environment, so could you (or anyone else) help me debug it?
Hi Brandon,
Sure – I’m kinda, sorta new to ROR but will try some things to see if cattr’s disappear on some other classes.
Here’s another item relating to Yahoo geocode requests…
In acts_as_geocoable.rb, the full_address method has a “\n” after the street. This was causing Yahoo to return ZIP level precision vs STREET level. Google didn’t seem to mind.
Orignal:
def full_address
returning("") { |address|
address << “#{geo_attribute(:street)}\n” unless geo_attribute(:street).blank?
address << "#{geo_attribute(:city)}, " unless geo_attribute(:city).blank?
address << "#{geo_attribute(:region)} " unless geo_attribute(:region).blank?
address << “#{geo_attribute(:postal_code)}” unless geo_attribute(:postal_code).blank?
address << " #{geo_attribute(:country)}" unless geo_attribute(:country).blank?
}.strip
end
Changed to:
def full_address
returning("") { |address|
address << “#{geo_attribute(:street)},” unless geo_attribute(:street).blank?
address << "#{geo_attribute(:city)}, " unless geo_attribute(:city).blank?
address << "#{geo_attribute(:region)} " unless geo_attribute(:region).blank?
address << “#{geo_attribute(:postal_code)}” unless geo_attribute(:postal_code).blank?
address << " #{geo_attribute(:country)}" unless geo_attribute(:country).blank?
}.strip
end
Basically I replaced the “\n” with a “,”. Yahoo now returns a STREET level precision on valid address. Google seems okay to.
Hope this helps!
Shawn
Hi Brandon,
Tested out the geocoder.nil? issue on Win XP. It seems to only occur when the server is started in development mode. Production mode, the object seems to stay created.
I tested using Mongrel and WEBrick with same results.
Hope this helps!
Shawn
Any thoughts of adding :through support for acts_as_geocodable? For example, say I have events which belong_to locations, it would be good to be able to search for forthcoming events based on their location.
I like the way acts_as_locateable implements that, but this plugin overall looks more appropriate for my needs.
Your approach of separating concerns is great.
It seems to me that being able to order results by distance as well as selecting by :within is important. Do you have plans to do this? Suggestions on how I might do it?
Thanks again
Peter,
Sorry for the late response, I fell off the face of the earth for a week. You should be able to order the results by passing
:order => 'distance'to the find. Let me know if that doesn’t work.Something weird is going on… I can’t start the mongrel after installing the acts_as_geocodable plugin, it says that Graticules 0.2.0 is required and I have the 0.2.6 installed.. what am I doing wrong??
I’ve installed the latest acts_as_geocodable and the latest Graticule, run the migrations, added the line into environment.rb (in development mode) and It Doesn’t Work™.
To start checking it works with my models I’ve uncommented the task in acts_as_geocodeable_tasks.rake, purely as a simple way to load my environment and test a model can become geocodeable. But when the environment loads I get “Unititialized constant Geocode”. Now I’m a new Rails user so there’s obviously some Rails idiom which is assumed in the instruction “just add this to your environment.rb”, but I don’t know what it is. Please clue me in, there must be something more I need to add to the environment.rb to get it to work.
John,
Did you restart the server? Any time you install a plugin, you need to restart script/server.
I’m running into the same problem that John Small is. Whenever I try to restart the server I get “Unititialized constant Geocode"
I’ve also tried adding “require ‘graticule’” to the environment.rb file as well with no luck.
Any help will be greatly appreciated. Thanks.
I have the same problem reported about geocoder.nil:
NoMethodError: You have a nil object when you didn’t expect it!
The error occurred while evaluating nil.latitude
from (irb):6
However, this is on OSX with the latest Rails, Gratitude and ActsAsGeocoder.
However, I don’t think the issue is that Geocode.geocoder is not sticking because when on the same console session I type
Geocode.geocoder
I does return:
=> #<Graticule::Geocoder::Google:0×22998dc @url=#<URI::HTTP:0×114cb10 URL:http://maps.google.com/maps/geo>, @key=“my-key”
So, it seems the problem is not related to the “stickiness” in environment.rb (neither development nor production). The problem seems to be somewhere in the AR methods added to the model not being able to access Geocode.geocoder.
Any clues anyone?
I think I may know where the problem is.
I was looking at acts_as_geocodable.rb and found:
def to_location
returning Graticule::Location.new do |location|
[:street, :locality, :region, :postal_code, :country].each do |attr|
location.send “#{attr}=”, geo_attribute(attr)
end
end
end
Which has the attributes hard coded in the array. Therefore, if your Model doesn’t have these exact attributes, then attach_geocode will fail and return nil. As a test, I just created instance methods in my model to return the values of my attributes based on the names in the array and now it works just fine.
It seems to be that this was somewhat documented in the README file and I must have missed it.
The problem I have is that my models looks something like this:
class Address < ActiveRecord::Base
belongs_to :postal_code
end
class PostalCode < ActiveRecord::Base
belongs_to :state
end
class State < ActiveRecord::Base
belongs_to :country
end
So, if I want to get the zip, city, and state of an address, I need:
address.postal_code.zip_code
address.postal_code.state.iso2
address.postal_code.state.country.iso2
respectively.
If I were to use the recommendations in the README, how can I do this:
class Address < ActiveRecord::Base
belongs_to :postal_code
acts_as_geocodable :address => {:street => :street1, :locality => :postal_code.city, :region => :postal_code.state.iso2, :postal_code => :postal_code.zip_code }
end
That doesn’t work for me unless instead I just define corresponding instance methods.
Any ideas?
Thanks
The folks with the issue of Geocode.geocoder in the environment file:
You are likely placing the call inside of the Rails::Initializer.run loop
Place it AFTER.
In addition to Chad’s comments (WRT the issue of Geocode.geocoder in the environment file) I’d like to add that if you have installed acts_as_geocodable as a gem, that you need to put the following inside your Rails::Initializer block (inside environment.rb):
config.gem ‘graticule’
config.gem ‘acts_as_geocodable’
Post a Comment