A Google Maps mash-up

Integrate external data with the Google Maps API

Since Google published its Maps API, numerous applications, called mash-ups, have been developed that integrate customized data on top of Google's map interface. With the Maps API, it's straightforward to center a map at a given location, add markers, and display content in info windows—especially if the location is static. However, if the location is driven by user input or retrieved dynamically, the process is somewhat more complex because of the need to geo-code the address.

This article attempts to solve that problem by demonstrating a typical mash-up scenario that integrates an external datasource with Google maps. While the example application we demonstrate here is simplistic and can be completed with fewer steps, the suggested underlying framework can be leveraged to create more robust mash-ups, and should prove useful for those developers looking to integrate external datasources into their existing Java-based Web applications.

Before we get started, let's go over the technologies we will use to set up our mash-up. Google's Maps API is based on coordinates; one must specify the latitude and longitude of a location to show that location on the map. Google's API does not (yet) provide a geo-coding service by which you can show an address on the map directly or translate an address to coordinates. Fortunately, there is a service from geocoder.us that uses public domain data to translate US addresses to coordinates. The geocoder.us service is free for trial use and available for commercial usage for a reasonable fee. The geocoder API is available in various invocation mechanisms, from formal Web services SOAP calls to REST (representational state transfer). While the XML-based remote procedure calls (RPC) and REST-type calls may prove simpler if you are writing the glue-code by hand, the Apache Axis project makes it easy to call SOAP endpoints, so for this article, we use SOAP-based integration.

The Maps API is based on Ajax (asynchronous JavaScript and XML) calls, and cooking up simple applications using only JavaScript proves easy. The API even provides a way of loading data from the application server using Ajax. In our use case, however, the datasource is not local, and for security reasons, XMLHttp, which forms the basis of Ajax, does not allow cross-domain calls. Thus we need to proxy the SOAP call to geocoder.us from our own server.

Finally, an open source library, named DWR (for Direct Web Remoting) practically trivializes the process of connecting the server-side code to client-side JavaScript. We use this library to invoke our GeoCoder Web service proxy methods, get the results, and send that data to the Google Maps API. The overall sequence of events is as follows:

Map mash-up sequence diagram. Click on thumbnail to view full-sized image.

Running the sample application

The application (available as a zip file from Resources) is packaged as a standard war file. To run it, drop MapMash.war in the webapps directory of your Jakarta Tomcat installation (or equivalent directory if you use a different servlet container). After you start up the Tomcat server, you will find the application at: http://localhost:8080/MapMash/ (replace the hostname and port as appropriate for your development configuration).

You will also need to register for a Google Maps API key (available by following the link to the Google Maps API in Resources) and change the <script> tag to use your key when referencing the Google Maps API in index.htm.

The SOAP client

We start by creating the SOAP call to geocoder.us. Apache Axis's WSDL2Java tool generates our SOAP client using the geocoder.us Web Services Description Language. The basic command is:

 java org.apache.axis.wsdl.WSDL2Java -t -o src -Nhttp://rpc.geocoder.us/Geo/Coder/US/=samples.mapMashup http://geocoder.us/dist/eg/clients/GeoCoder.wsdl

As its name suggests, the WSDL2Java tool inspects geocoder.us's WSDL and generates the Java client binding that can invoke geocoder.us's services using SOAP. For additional information on Apache Axis, please refer to Resources.

GeoCoder proxy bean

Next, we create a GeoCoder proxy bean that uses our Axis-generated SOAP client to invoke the geocoder.us service. We will invoke this class using DWR from our HTML page. So, to satisfy DWR, our proxy bean must be a Java bean allowing introspection:

 public class GeoCoder {
    public GeoCoder() {
    }
   
    public GeocoderAddressResult[] geocode(String location) throws IOException, ServiceException {
        GeoCode_BindingStub binding = (GeoCode_BindingStub) new GeoCode_ServiceLocator().getGeoCode_Port();
   
        // Time out after a minute
        binding.setTimeout(60000);   
        GeocoderAddressResult[] values = binding.geocode_address(location);
        return values;
    }
  
}

DWR: Direct Web Remoting

Our sample application uses Ajax to process the user-entered address into latitude/longitude coordinates. While many Ajax libraries are available, this application uses DWR, which allows us to invoke published server-side bean methods from client-side JavaScript, thus freeing us from writing much of the client-to-server infrastructure code.

DWR's approach to Ajax is to dynamically generate JavaScript code based on JavaBeans components. The dwr.xml configuration file specifies which Java beans to expose for remote use via JavaScript:

 <dwr>
  <allow>
    <create creator="new" javascript="GeoCoder">
      <param name="class" value="samples.mapMash.GeoCoder"/>
    </create>
    <convert converter="bean" match="samples.mapMash.GeocoderAddressResult"/>
  </allow>
</dwr>

The DWR library dynamically creates a usage and test page, which you can use to test the bean methods being exposed. To see this, go to http://localhost:8080/MapMash/dwr/. Visit the page http://localhost:8080/MapMash/dwr/test/GeoCoder to see the DWR-generated JavaScript functions.

The DWR test page generates the script tags that we'll need to include in our HTML page:

 <script type='text/javascript' src='/MapMash/dwr/interface/GeoCoder.js'></script>
<script type='text/javascript' src='/MapMash/dwr/engine.js'></script>

The first library (GeoCoder.js) contains the DWR-generated code to access our GeoCoder.java proxy bean. The contents of this library appear below:

 function GeoCoder() { }
GeoCoder.geocode = function(p0, callback)
{
    DWREngine._execute('/MapMash2/dwr', 'GeoCoder', 'geocode', p0, callback);
}

The sample application's HTML page invokes the GeoCoder.geocode() function, which in turn relies on the DWR engine, referenced above as engine.js. The DWR engine handles the details of invoking our Java class via Ajax—we only need to supply a callback function (described below).

Your application console may warn you about missing items related to Spring, Hibernate, Bean Scripting Framework, JDOM, etc.:

 Missing classdef for creator 'spring'. Failed to load uk.ltd.getahead.dwr.create.SpringCreator.
Cause: org/springframework/beans/factory/BeanFactory Missing classdef for creator 'script'.
Failed to load uk.ltd.getahead.dwr.create.ScriptedCreator.
etc..

You can safely ignore these; we are not utilizing these features.

Tying it all together

Finally, in the browser, we display a map and an input field for the user to type an address. The Submit button's onclick event invokes the findAddress_onclick() event-handler function, which calls our GeoCoder proxy via DWR to translate the address into latitude/longitude coordinates and redraws the map accordingly:

 function findAddress_onclick() {
    var address = document.getElementById("address").value;
    // Create an anonymous callback function to manipulate the
    // map using the given latitude/longitude coordinates. 
    var moveMapCallback = function(points) {
        if (points.length > 0) {
            // In case there are multiple matches, we just use the first.
            var point = points[0];
            var newCenter = new GPoint(point._long, point.lat);
            map.recenterOrPanToLatLng(newCenter);
            map.addOverlay(new GMarker(newCenter));
            map.openInfoWindow(newCenter,
               document.createTextNode("Coordinates: " + newCenter)); 
            document.getElementById("debugGeoCoding").innerHTML= newCenter;
       } else {
            document.getElementById("debugGeoCoding").innerHTML = 'no match';
        }
    }
   // DWR will proxy this call to GeoCoder's geocode method on the server.
    // Once it gets a response, DWR will pass the result as an arg to the callback.
    GeoCoder.geocode(address, moveMapCallback);
}

Note that the findAddress_onclick() function passes an anonymous callback function to the DWR-generated GeoCoder.geocode() function. This satisfies the contract with DWR's generated JavaScript; since Ajax is asynchronous, the callback function is invoked once the call to our GeoCode proxy is completed. The geocode() method in GeoCoder.java returns an array of GeocoderAddressResults, which DWR passes to the callback function as an argument. The callback uses the first GeocoderAddressResult in the passed-in array to manipulate the map by centering to the new coordinates, adding a marker at the new center, and adding an info window displaying the coordinates.

Conclusion

While the mash-up demonstrated here may be a bit trivial, the framework used to build it can be applied to develop a much more robust application. We've integrated an outside datasource via SOAP, and we have "remotely" invoked our SOAP service using Ajax via the DWR library. This approach can be easily extended to integrate additional data to Google's map interface or to integrate third-party data into your own application.

Sumit Bando started his professional career working on compilers and debuggers at AT&T and Apple. He worked on Java virtual machines at Apple and Sun, and has been directing software development on the J2EE and Web services stack for the past six years, most recently at Selectica. Darius Kasad has more than 10 years of experience leading and delivering Web and client-server solutions in both start-up and enterprise environments, and is currently a software engineer at Selectica.

Learn more about this topic

Recommended
Join the discussion
Be the first to comment on this article. Our Commenting Policies
See more