Using Google’s v3 Geocoder with Groovy

For quite some time now (at least the last couple of years), I’ve been using the Google geocoder to translate addresses into latitudes and longitudes. It’s a good system, and unlike some of the other free systems, works all over the world.

The reason I use the geocoder is to do what the true mapping people call “red dot fever,” i.e., to add markers to a Google Map. The Google Maps API is a very clean, easy to use JavaScript library, and Scott Davis has written a really good ebook on how to use it. I used to have an app that displayed everywhere I’d been, with polylines (geodesic arcs connecting my office to other points) and everything, but I’ve taken it down because I need to revise it. Besides, my skills at user interface design (other than the Google map) leave something to be desired.

I liked using the geocoder as an example of an interesting Grails service. When I do a demo of Grails, I sometimes add Knights, based in Castles, undertaking Tasks to accomplish great Quests (like finding the holy Grails), and I use the geocoder to put the castles on the map.

The problem with the geocoder, though, is that it requires you to generate a key based on the deployed URL. For example, to deploy a Google Map on my site, I’d need to generate a key based on . That would work for all subdomains of Kousen IT, Inc, which is pretty cool. But I don’t want to give out my own key to the world whenever I do a demo.

Recently, though, I tried putting http://localhost into the key-generating form, and to my surprise it worked. I certainly don’t mind giving out that key. Then, to demonstrate how the geocoder worked, I’d write a Groovy script similar to:
[sourcecode language=”groovy”]
city = ‘New York’
state = ‘NY’
params = [sensor:false,
q:[city,state].collect { URLEncoder.encode(it,’UTF-8′) }.join(‘,+’),

url = "" + params.collect { k,v -> "$k=$v" }.join(‘&’)
(status,accuracy,lat,lng) = url.toURL().text.split(‘,’)
println "($status,$accuracy,$lat,$lng)"
assert Math.abs(40.7142691 – lat.toDouble()) < 0.0001
assert Math.abs(-74.0059729 – lng.toDouble()) < 0.0001

That script is some seriously groovy Groovy. It uses the collect function to apply a closure to every element of a list, then uses the join method to convert the resulting list into a parameter string. It also reminds people that you can use classes and methods directly from the Java standard library (like URLEncoder.encode(...)) right in the middle of a Groovy script with no problems. It even uses the new cool capability of Groovy 1.6+ to return multiple results from a single method. Of course, I also don’t mind embedding the generated key in the source code here, because it’s for http://localhost anyway.

The reason the parsing via the split method worked is that Google Maps supports (at least until today) a “csv” output type. If the value of the output parameter is csv, then the results come back as a comma-separated string, of the form
// status, accuracy, latitude, longitude

What could be easier to parse, especially with Groovy? I can either just split the string at the commas, as shown above, and return all the values, or I can access the last two fields from the right end using something like
[sourcecode language=”groovy”]
result = url.toURL().text.split(‘,’)
lat = result[-2]
lng = result[-1]
which is how I did it prior to Groovy 1.6.

Today, however, I received an email from Google saying that their new version 3 geocoder has (at least) two major changes:

  • the key is no longer necessary (cool)
  • the csv output type is no longer supported (bummer)

The only available output types on the V3 geocoder are xml and json, and they prefer json since it’s less verbose. Well sure, of course they prefer it, for that reason and because their own API is based on JavaScript, which naturally loves json.

For Groovy, though, it’s an opportunity to make some lemonade out of those lemons. XML is nothing to Groovy. If you take a look at the response tree for the new geocoder in XML form, you’ll find that the latitude and longitude are buried a few levels down, but not too deeply. So I wrote a new script that looks like this:
[sourcecode language=”groovy”]
city = ‘New York’
state = ‘NY’
base = ‘;
params = [sensor:false,
address:[city,state].collect { URLEncoder.encode(it,’UTF-8′) }.join(‘,+’)]
url = base + params.collect { k,v -> "$k=$v" }.join(‘&’)

response = new XmlSlurper().parse(url)

// Walk the tree
lat =

// Finders work too
lng = response.’**’.find { =~ /lng/ }

println "($lat,$lng)"
assert Math.abs(40.7142691 – lat.toDouble()) < 0.0001
assert Math.abs(-74.0059729 – lng.toDouble()) < 0.0001

This time the key is gone, and now I use an XmlSlurper to parse the results. Then I extract the latitude and longitude two different ways, just to show what can be done. The latitude comes from walking the tree, as a GPath expression, which won’t work if you miss an intermediate node (like I did the first couple of times). The longitude I extracted using a depth-first search (that’s what the ‘**’ does) with a find method using the name of the tag I’m looking for. If you want to see the raw XML, just print url.toURL().text.

Everything still works, and the new script is almost exactly the same length as the old. Google has a few examples of using XPath parsing in Java to extract the results. Trying showing those to any Java developer and see how quickly they come to like Groovy. 🙂

The old, V2 version still works, but now I can convert everything to V3 without a problem.

5 responses to “Using Google’s v3 Geocoder with Groovy”

  1. […] Using Google’s v3 Geocoder with Groovy « Stuff I’ve learned recently… (tags: groovy grails google geocoder geocode map) […]

  2. […] Using Google’s v3 Geocoder with Groovy « Stuff I’ve learned recently… […]

  3. Sweet, sweet groovy!

  4. thanks for a nice tutorial

  5. Thanks for the lectures at nfjs in STL today! I think the download and install took longer than recreating the Geocoder example you used. I think I’m really going to enjoy this. 🙂

Leave a Reply

This site uses Akismet to reduce spam. Learn how your comment data is processed.