Create Your Own Geodatabase using Ruby on Rails and Google Maps
Posted by Lars | Posted in Geocoding, Rails, Ruby, Tutorials | Posted on 04-05-2009
6
Geocoding with Ruby on Rails isn’t a big deal. But sometimes you have do more than simple geocoding. For real estate webpages you need information about state, county or suburb for a given address.
This tutorial will show you how to build your own geodatabase without overusing google maps requests(limited on 15000 Requests per Ip/month).
Before you can start you must have a fresh rails application and a google maps api key. You can signup for one here: http://code.google.com/apis/maps/
This tutorial has following parts:
- Geocode an address
- Get extented information to this address
- An Pratical example
We touch only the model layer(thin controllers fat models). I will use an address ressource like follows:
county:string city:string suburb:string zipcode:string street:string
streetno:string longitude:float latitude:float error_code:string
and
You can use this address ressources for many purposes. But more on this later. You need some ruby-gems too.
1 2 3 4 5 6 7 | class Address < ActiveRecord::Base require 'open-uri' require 'cgi' require 'hpricot' require 'iconv' ... end |
Geocode an address
For geocoding an address you can use a simple callback function.
1 2 3 | def before_create geocode end |
And the gecode function itself:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 | def geocode url = ["http://maps.google.com/maps/geo?q="] url << address_for_geocoding url << "&output=xml&key=" url << "your_google_maps_api_key" open(url.to_s) do |file| @body = file.read doc = Hpricot(@body) (doc/:status/:code).each do |code| self.error_code = code.inner_html end (doc/:point/:coordinates).each do |link| self.longitude, self.latitude = link.inner_html.split(',') end end end |
On line 3 i need an encoded address for proper geocoding. This function catches incomplete address information too.
1 2 3 4 5 6 7 8 | def address_for_geocoding addressline = [] addressline << streetno.to_i unless (streetno || "").blank? addressline << street unless (street || "").blank? addressline << zipcode unless (zipcode || "").blank? addressline << city unless (city || "").blank? CGI::escape(addressline.join(' ')) end |
On line 8 we start the parsing of the returned xml-data from google maps. For more information about the xml structure you can read: Google Maps XML Structure
On line 15 we assign the longitude and latitude to our address attributes. So you get the coordinates into address model before rails creates it.
Get extented information
You can read at Google Maps XML Structure that far more information can delivered from google maps. So we update our geocode function:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 | def geocode url = ["http://maps.google.com/maps/geo?q="] url << address_for_geocoding url << "&output=xml&key=" url << "your_google_maps_api_key" open(url.to_s) do |file| @body = file.read doc = Hpricot(@body) (doc/:status/:code).each do |code| self.error_code = code.inner_html end (doc/:country/:countrynamecode).each do |country_code| self.country = country_code.inner_html end (doc/:country/:administrativearea/:administrativeareaname).each do |area| self.state = Iconv.conv('utf-8', 'ISO-8859-1', area.inner_html) end (doc/:country/:administrativearea/:subadministrativearea/:subadministrativeareaname).each do |sub_area| self.county = Iconv.conv('utf-8', 'ISO-8859-1', sub_area.inner_html) end (doc/:country/:administrativearea/:subadministrativearea/:locality/:dependentlocality/:dependentlocalityname).each do |t_area| self.suburb = Iconv.conv('utf-8', 'ISO-8859-1', t_area.inner_html) end (doc/:point/:coordinates).each do |link| self.longitude, self.latitude = link.inner_html.split(',') end end end |
So we get extented information about country, state, county and suburb related to the address information you entered. You have now the complete set of address, administrative-level information and geo coordinates.
A practical example
It’s time for some practical stuff. I will use real estate example. In this example i will geocode a house location and add administrative information to it.
So let’s start with a property ressource scaffold:
street:string streetno:string zip:string city:string address_id:integer
and
I know there are some redundant address information, but it’s necessary to trace typing errors from users.
Lets go to the model layer an make some associations. One house can only have one address but one address can have many houses. So we write:
1 2 3 4 | class Address < ActiveRecord::Base has_many :properties ... end |
and
1 2 3 4 | class Property < ActiveRecord::Base belongs_to :address ... end |
Now lets start coding:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 | class Property < ActiveRecord::Base belongs_to :address after_save :get_location protected def get_location address = address_exist? address =Address.create(:streetno =>self.streetno, :street => self.street,:zip => self.zip, :city => self.city) unless address self.address = address end def address_exist? address = Address.find(:first, :conditions => "streetno = ? and street= ? and zip = ? and city = ?", self.streetno,self.street,self.zip,self.city) address ? address : false end end |
Done. What happened? If you enter property data the property will saved. After save we call the get_location function to assign a address to our new property.
On line 7 we call the address_exist? function which try to find an address in our database. If this address doesn’t exist we create a new one on line 8.
Everytime a user adds unknown property your address database will grow. Once an address is saved it will resused every time a property with same address is entered.
Now you can get property listings like:
1 2 3 4 5 6 7 8 9 10 | class Property < ActiveRecord::Base belongs_to :address ... def get_all_properties_with_same_streetname addresses = Address.find(:all, :conditions => ["street = ?",self.street]) addresses.collect { |address| address.properties } end ... end |
I hope i could help you with this tutorial. Please write a comment if you have any questions or critics.











Nice tutorial.
I am looking for a tutorial for generating tracks from GPX files and showing them using google maps in ruby on Rails.
Any1 who know how to do this in a simple way?
/MartOn
Hi marton,
thanks for your comment. In one of my next tutorials i will explain how to import data from xml files into rails applications.
It isn’t exactly the same but i am sure you can solve your problem with it.
See you later,
Lars
Thanks for the tuturial! It works here!
Only you must change this
addressline << zip unless (zip || “”).blank?
into
addressline << zipcode unless (zipcode || “”).blank?
If i do that it works otherwise it gives an error
Thank you bart, i fixed the error in tutorial.
Kind regards,
Lars
da best. Keep it going! Thank you
Hey,
I really dig the article, very helpful.
Maybe you can help with the next part, I’m looking to parse an address from a block of text (think Craigslist). Any idea how one would go about that? Seems like some serious regex.