At the Forge - Creating Mashups

It's a crime not to mashup two or more Web services to deliver more than they can deliver separately.
Working with Addresses and Cities

This map is a nice start, but far from what we want to accomplish. And, one of the biggest impediments is the fact that Google Maps expects to get longitude/latitude pairs. Amazon's Web service does return information about third-party vendors, but it provides us with city and state information. So, we need a way to translate city and state names into latitude and longitude.

The easiest way to do this is to rely on someone else, who can translate an address into a longitude/latitude pair. Such geocoder services exist as Web services on the Internet; some of them are freely available, and others charge money. One of the best-known free geocoder services is at geocoder.us. To use this geocoder, we simply use a REST-style URL, as follows: http://geocoder.us/service/rest?address=ADDRESS, replacing ADDRESS with the place we want to go. For example, to find my house, we would say, http://geocoder.us/service/rest?address=9120+Niles+Center+Road+Skokie+IL.

The geocoder service returns an XML document that looks like this:


<rdf:RDF>
<geo:Point rdf:nodeID="aid77952462">
    <dc:description>9120 Niles Center Rd, Skokie IL 60076</dc:description>
    <geo:long>-87.743874</geo:long>
    <geo:lat>42.046517</geo:lat>
</geo:Point>
</rdf:RDF>

Because the longitude and latitude are nicely compartmentalized inside of the XML, it's easy to extract it in our program and then insert it into the JavaScript that we generate. However, from looking through the geocoder.us documentation, it doesn't seem as though it is able to handle city names (that is, without street addresses).

Luckily, at least one free geocoder service handles city names, returning a similarly styled XML document. We submit the name of a city as follows, once again using a REST-style request: http://brainoff.com/geocoder/rest?city=Skokie,IL,US.

We get the following result:


<rdf:RDF>
<geo:Point>
    <geo:long>-87.762660</geo:long>
    <geo:lat>42.034680</geo:lat>
</geo:Point>
</rdf:RDF>

As you can see, the longitude and latitude points we got back from this query are slightly different. If we were looking to create a map for driving directions, this would be of greater importance. But, we already know that we'll be looking at the entire map of the United States for this application, and that being blocks away, or even two miles away, won't make any difference.

We can now update our ERB file, such that it has an array of cities, rather than longitude/latitude pairs, as you can see in Listing 2. We begin the file by importing two Ruby classes that will be needed to handle this additional functionality:


<% require 'net/http' %>
<% require 'rexml/document' %>

Although our starting (and centering) point begins at the same longitude/latitude location, we begin at zoom level 13, which will be large enough to show all of the cities.

We then define four cities, putting them in an array called cities, showing four of the US cities in which I have lived. Notice that each element of this array is a string containing a city name, state abbreviation and US (for United States). Also note that when the city name has a space, we must replace it with a + sign (or %20), so the Web service request works appropriately:


<% cities = ["Skokie,IL,US", "Longmeadow,MA,US",
     "Somerville,MA,US", "Old+Westbury,NY,US"] %>

We then iterate through these cities, using each as the argument to our Web service geocoder:


<% geocoder_response =
    Net::HTTP.get_response('brainoff.com', "/geocoder/rest/?city=#{city}") %>

The results of the geocoder Web service are in XML, as we saw earlier. To extract the results of this query from the XML, we use the REXML library that comes with Ruby. This allows us to retrieve the geo:long and geo:lat elements, and then grab the textual contents of the elements:


<% longitude = xml.root.elements["/rdf:RDF/geo:Point/geo:long"].text %>
<% latitude = xml.root.elements["/rdf:RDF/geo:Point/geo:lat"].text %>

Having done the hard work, we now insert the appropriate JavaScript:


    var myMarker<%= index %> = new GMarker(new GPoint(<%= longitude %>,
<%= latitude %>));
    map.addOverlay(myMarker<%= index %>);

Along the way, we collect city names and locations into an array named final_list. We can then use this to produce a list at the end of the document:


<% final_list.each do |city| %>
<tr>
    <td><%= city['city'] %></td>
    <td><%= city['longitude'] %></td>
    <td><%= city['latitude'] %></td>
</tr>
<% end %>

Sure enough, this produces a page with a Google map showing all of those locations, and with a list at the bottom.

______________________

Comments

Comment viewing options

Select your preferred way to display the comments and click "Save settings" to activate your changes.

crime mashup

GFL's picture

Another crime mashup for you:
http://www.newhavencrimelog.org/

Error

cbier's picture

I get this error when I try to run the code from listing 3:

undefined method `text' for nil:NilClass
(
NoMethodError
)

The error is from this line:

next if geocoder_xml.root.nil?

Thanks for writing great articles. At the Forge is one of my favorite articles.

Error

Reuven Lerner's picture

Hi, cbier. Sorry that it took a while to respond, but I only realized recently that people were asking questions of me on this forum!

The error message tells us that we're trying to invoke a method -- the "text" method -- on nil. Now, we obviously don't have a literal nil in the code, so that means our variable must have a nil value, and we're trying to invoke an unknown method on it.

My guess, and I can't prove it, is that Amazon gave you back a city for which there wasn't any geocoder information. (Maybe the city name was spelled incorrectly?) Thus you got nil back. But then you tried to invoke a method on it. Where? I'm not sure, but I'm guessing that we needed to do a bit more testing -- checking that geocoder_response wasn't nil, and that geocoder_xml wasn't nil, either.

Reuven
Senior Columnist, Linux Journal

Reuven M. Lerner, Linux Journal Senior Columnist, a longtime Web developer, consultant and trainer,
is completing his PhD in learning sciences at Northwestern University.

Webinar
One Click, Universal Protection: Implementing Centralized Security Policies on Linux Systems

As Linux continues to play an ever increasing role in corporate data centers and institutions, ensuring the integrity and protection of these systems must be a priority. With 60% of the world's websites and an increasing share of organization's mission-critical workloads running on Linux, failing to stop malware and other advanced threats on Linux can increasingly impact an organization's reputation and bottom line.

Learn More

Sponsored by Bit9

Webinar
Linux Backup and Recovery Webinar

Most companies incorporate backup procedures for critical data, which can be restored quickly if a loss occurs. However, fewer companies are prepared for catastrophic system failures, in which they lose all data, the entire operating system, applications, settings, patches and more, reducing their system(s) to “bare metal.” After all, before data can be restored to a system, there must be a system to restore it to.

In this one hour webinar, learn how to enhance your existing backup strategies for better disaster recovery preparedness using Storix System Backup Administrator (SBAdmin), a highly flexible bare-metal recovery solution for UNIX and Linux systems.

Learn More

Sponsored by Storix