Integrating Maps into Your Java Web Application with Google Maps and Ajax Blog

Version 2


    Interactive maps are becoming more and more widespread in modern web applications. Potential uses abound: re al estate and tourism are two obvious domains where interactive maps can provide real added value, but there are dozens of others as well. However, although many of today's web applications could make good use of integrated mapping software, development teams often shy away from the complexity of in tegrating a full-fledged geographical database system. Indeed, while clickable maps have been around for years, truly interactive maps, which interact in real time with a server application, have always been considerably more complex. Until now.

    Two relatively recent technologies, Google Maps and Ajax, can make life considerably easier in this area. In this article, we will discuss how you can easily implement dynamic interactive maps on your Java web application, using the Google Maps API for the web interface, and Ajax to provide real-time interaction with the server. This article is designed to give you a rapid introduction to both Google Maps and Ajax, and to let you get your own interactive maps up and running quickly and easily.

    A Brief Introduction to Google Maps

    In the first part of this article, we will discuss how to integrate a feature-rich map into your application in record time, by using the Google Maps API. The Google Maps API is an easy-to-use JavaScript API that enables you to embed interactive maps directly in your application's web pages. And as we will see, it is easy to extend it to integrate real-time server requests using Ajax.

    Getting started with the Google Maps API is easy. There is nothing to download; you just need to sign up to obtain a key to use the API. There is no charge for publicly accessible sites (for more details, see the Sign up for the Google Maps API page). You need to provide the URL of your website, and, when your application is deployed on a website, your key will only work from this URL. One annoying thing about this constraint is that you need to set up a special key to use for your development or test machines: for the sample code, I had to create a special key for http://localhost:8080/maps, for example.

    Once you have a valid key, you can see the Google Maps API in action. Let's start off with something simple: displaying a map on our web page. Suppose you have been commissioned by the Ministry of Tourism of Elbonia to build a promotional website about the many tourist attractions of Elbonia, and in particular its renowned hot mud baths. Note: In our example, since Elbonia is difficult to find on current maps, we will display a map of New Zealand, a small island nation in the middle of the South Pacific ocean.

    Although the API is not particularly complicated, working with Google Maps requires a minimal knowledge of JavaScript. You also need to know the latitude and longitude of the area you want to display. If you're not sure, you can find this sort of information on the internet, or even by looking in an atlas!

    The full code listing of our first Google Map is shown here:

    <!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN"> <html> <head> <meta http-equiv="Content-Type" content="text/html; charset=UTF-8"> <title>Our first Google Map</title> <script src="" type="text/javascript"></script> <script type="text/javascript"> //<![CDATA[ function load() { if (GBrowserIsCompatible()) { var map = new GMap2(document.getElementById("map")); map.setCenter(new GLatLng(-41.5, -185), 5); } } //]]> </script> </head> <body onload="load()" onunload="GUnload()"> <div id="map" style="width: 420px; height: 420px"></div> </body> </html> 

    The first thing to notice here is the code that fetches the actual JavaScript code from the Google Maps server. You need to supply your key here for the code the work.

    <script src="" type="text/javascript"> </script> 

    Next comes the code that actually downloads the map from the server.

    <script type="text/javascript"> //<![CDATA[ function load() { if (GBrowserIsCompatible()) { var map = new GMap2(document.getElementById("map")); map.setCenter(new GLatLng(-41.5, -187.5), 5); } } //]]> </script> 

    You create a GMap2 object, providing a reference to some element in your page. In this case, we refer to the<div> element in the page body. You need to provide the map with a valid latitude and longitude, and the zoom level. The higher the zoom, the more detailed the map.

    Google Maps will work correctly with recent versions of most modern browsers. Nevertheless, it is good practice to make sure all the same, by using the GBrowserIsCompatible()method.

    Finally, in the body, we display the map. The size and shape of the map are taken from the corresponding HTML element. The map is initialized when the page is loaded (via the onloadevent). In addition, when the user leaves the page, theGUnload() method is called (via theonunload event). This cleans up the map data structure in order to avoid memory leak problems that occur in Internet Explorer.

    <body onload="load()" onunload="GUnload()"> <div id="map" style="width: 420px; height: 420px"></div> </body> 

    The resulting map is illustrated in Figure 1.

    A simple map
    Figure 1. A simple map

    Panning and Zooming

    Now that we can successfully display a map, let's try to add some zoom functionality. The Google Maps API lets you add a number of different controls to your map, including panning and zooming tools, a map scale, and a set of buttons letting you change between Map and Satellite views. In our example, we'll add a small pan/zoom control and an "Overview map" control, which places a small, collapsible overview map. You add controls using theaddControl() method, as shown here:

    function load() { if (GBrowserIsCompatible()) { var map = new GMap2(document.getElementById("map")); map.setCenter(new GLatLng(-41.5, -187.5), 5); map.addControl(new GSmallMapControl()); map.addControl(new GOverviewMapControl()); } } 

    Our new map is illustrated in Figure 2.

    A map with some controls
    Figure 2. A map with some controls

    Adding Markers to Your Maps

    The Google Maps API is not just about displaying a map; you can also configure more dynamic maps. To do this, you add "overlays" to your map. Overlays are objects that are displayed as certain locations on the map, and that the user can interact with. A typical use of an overlay is to place a marker at a given location to indicate some special place or address.

    Let's add a simple marker to our map. We want a marker to be displayed over a particular tourist attraction in Elbonia. After obtaining the latitude and longitude of the site, we create a newGMarker object and add it to the map using theaddOverlay() method:

    function load() { if (GBrowserIsCompatible()) { var map = new GMap2(document.getElementById("map")); map.setCenter(new GLatLng(-41.5, -187.5), 5); map.addControl(new GSmallMapControl()); map.addControl(new GOverviewMapControl()); var point = new GLatLng(-41.28, -185.22); marker = new GMarker(point); map.addOverlay(marker) } } 

    In our example, we want to display some details when a user clicks on our marker. We just add a listener to the marker, and invoke the openInfoWindowHtml() method, which displays an HTML message in a text bubble.

    marker = new GMarker(point); map.addOverlay(marker) GEvent.addListener(marker, "click", function() { marker.openInfoWindowHtml("<p>More details</p>"); }); 

    Now that we have gone through the basics of the Google Maps API, let's see how we can integrate a Google Map with a server-side application.

    An Introduction to Ajax

    Ajax is the technology behind Google Maps. It is also the technology we will use to extend and integrate our Google Map into a dynamic web application. Ajax stands for "Asynchronous JavaScript and XML." Behind all the hype, Ajax is basically a technology that enables JavaScript code to sent a request to a server, retrieve some data in response (generally in XML format, hence the X in Ajax), process this data, and update the web page accordingly. The first A stands for "Asynchronous;" Ajax requests can sent off to a server in the background, without interrupting the user. When the results arrive in the client browser, a callback function is called to process the data and dynamically update the screen. The screen does not have to be reloaded at each update, which provides for a much smoother, faster user experience.

    At the heart of Ajax is the XMLHttpRequestclass.

    <script language="javascript" type="text/javascript"> var req = new XMLHttpRequest(); </script> 

    In fact, creating an XMLHttpRequest object is not quite that simple: the time-old problems of browser compatibility raise their ugly heads again here. Put simply, Microsoft Internet Explorer uses one of two possible ActiveXObjectobjects, whereas virtually all other browsers use theXMLHttpRequest class. There are many documented workarounds to this problem, most of which generally involve something along the following lines:

    <script language="javascript" type="text/javascript"> <script type="text/javascript"> //<![CDATA[ var req = false; if (window.XMLHttpRequest) { // Create XMLHttpRequest object in non-Microsoft browsers req = new XMLHttpRequest(); } else if (window.ActiveXObject) { // Create XMLHttpRequest via MS ActiveX try { // Try to create XMLHttpRequest in later versions // of Internet Explorer req = new ActiveXObject("Msxml2.XMLHTTP"); } catch (e1) { // Failed to create required ActiveXObject try { // Try version supported by older versions // of Internet Explorer req = new ActiveXObject("Microsoft.XMLHTTP"); } catch (e2) { // Unable to create an XMLHttpRequest with ActiveX } } } </script> 

    However, the Google Maps API provides a convenient browser-safe shortcut, by using the GXmlHttp class:

    <script language="javascript" type="text/javascript"> var req = GXmlHttp.create(); </script> 

    You use your XMLHttpRequest object to fetch data from the server. Since Ajax is asynchronous, you don't know when you will get a response, and your application will not hang around waiting for the server to answer. So when you send the request, you also provide a special callback function. The main purpose of this callback function is to process the returned data. However, because of the way Ajax does things, it needs to verify the status of the request beforehand to make sure it has really received the data correctly.

    Here is an example of some code calling an Ajax request. It is designed to fetch the list of all the cities on the map, in XML form, in order to display them on the map.

    <script language="javascript" type="text/javascript"> // Create the XMLHttpRequest object var request = GXmlHttp.create(); // Prepare an asynchronous HTTP request to the server"GET", "/maps/SiteDirectory?method=findAllCities", true); // Returned data will be processed by this function request.onreadystatechange = getCallbackFunction(request, processCityData); // Send the query request.send(null); </script> 

    Most of the code is pretty self-explanatory. The key function here is the getCallbackFunction() function. This function does some checks on the request's status, and when a correct response has been received, it calls theprocessCityData() function, which contains all the interesting business logic:

    <script language="javascript" type="text/javascript"> function getCallbackFunction(req, processData) { // Return an anonymous function that listens to the // XMLHttpRequest instance return function () { // If the request's status is "complete" if (req.readyState == 4) { if (req.status == 200) { // Pass the XML payload of the response to the // handler function processData(req.responseXML); } else { // An HTTP problem has occurred alert("HTTP error: "+req.status); } } } } function processCityData(xmlDoc) { // Process the returned XML data ... } </script> 

    And, for our purposes, that's pretty much all we need to know for now. In the next section, we will see how you can use Ajax to add visual markers, updated dynamically from a database, to your map.

    Using Ajax to Enhance our Google Maps Application: Dynamic Markers

    Now that we have a working knowledge of the Google Maps API, and an idea of what Ajax can do for us, let's see how we can integrate a Google Map with a Java web application using Ajax. In our Elbonian website, we want to display all the tourist attractions of Elbonia. You want an interactive map where users can localize a particular tourist attraction using markers on the map. When they click on a site, a window pops up with details about that particular attraction.

    The first step is to integrate a set of markers provided by the server. One way to do this would be to dynamically generate the JavaScript code on the server. However this is cumbersome, and can generate excessively large HTML pages. Luckily, there is a better way. The Google Maps API provides a convenient function calledGDownloadUrl() that lets you download data in XML form and process it in your JavaScript code. From there, it is fairly easy to convert your XML data into Google Map Markers.

    This Ajax application will be powered on the server by a single servlet. This servlet will provide data about tourist attractions and cities containing tourist attractions, in XML form. The details of this servlet are not particularly important for the purposes of this discussion; the key thing to remember is that the servlet returns XML (and not HTML) data based on the query parameters it receives:

    public class TouristSiteDirectoryServlet extends javax.servlet.http.HttpServlet implements javax.servlet.Servlet { ... /** * Data Access Object providing search methods on the tourist attraction site database */ private SiteDAO getSiteDAO() { ... } /** * Data Access Object providing search methods on the site database */ protected void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException { String method=request.getParameter("method"); XStream xstream = new XStream(); xstream.alias("marker", Marker.class); response.setContentType("application/xml"); if (method == null) { String id = request.getParameter("id"); Site site = getSiteDAO().findSiteById(id); xstream.alias("site", Site.class); String xml = xstream.toXML(site); response.getWriter().write(xml); } else if (method.equals("findAll")) { List<Marker> sites = getSiteDAO().findAllAsMarkers(); String xml = xstream.toXML(sites); response.getWriter().write(xml); } else if (method.equals("findAllCities")) { List<Marker> cities = getSiteDAO().findAllCityAsMarkers(); String xml = xstream.toXML(cities); response.getWriter().write(xml); } else if (method.equals("findByCity")) { String city = request.getParameter("city"); List<Marker> sites = getSiteDAO().findByCityAsMarkers(city); String xml = xstream.toXML(sites); response.getWriter().write(xml); } } } 

    Again, the code is fairly self-explanatory. The servlet basically queries the database via the DAO object, and the uses the XStream site to convert the data into XML form. This data can also be stored in a plain old XML file. Of course, the advantage of storing geographical site coordinates in a database is that they can be updated as necessary; for example, in the case of a major earthquake or land slide, or continental drift, or to keep track of the famous migrating mud-transporting camel caravans of the central Elbonian steppes. This XML is then written directly to the servlet response stream. The resulting lists look something like this:

    <list> <marker> <id>3</id> <name>The Famous Mud Baths of central Elbonia</name> <latitude>-36.87</latitude> <longitude>185.22</longitude> </marker> <marker> <id>5</id> <name>The North Elbonian Mud Baths</name> <latitude>-36.77</latitude> <longitude>185.92</longitude> </marker> ... </list> 

    On the client side, we fetch the XML marker list, and generate a set of corresponding markers on the map. To do this, we use Ajax via a Google utility class: GXmlHttp. This class sends a query to the server to fetch an XML document, which you can then parse using the JavaScript DOM functions.

    Let's look at how this is done in a real example. The XML data shown above is fetched by the loadSites() function, which invokes the processSiteData() function to, well, process the site data:

    <script language="javascript" type="text/javascript"> function loadSites() { var request = GXmlHttp.create();"GET", "/maps/SiteDirectory?method=findAll", true); request.onreadystatechange = getCallbackFunction(request, processSiteData); request.send(null); } </script> 

    The processSiteData() function, and the associateddisplaySiteMarkers() function, use JavaScript DOM processing techniques to extract useful data about tourist attraction sites, and to create the corresponding markers on the map:

    <script language="javascript" type="text/javascript"> function processSiteData(xmlDoc){ // obtain the array of markers and loop through it siteMarkers = xmlDoc.documentElement.getElementsByTagName("marker"); displaySitesMarkers(); } function displaySitesMarkers() { map.clearOverlays(); for (var i = 0; i < siteMarkers.length; i++) { // obtain the attributes of each marker var lat = parseFloat(siteMarkers[i].getElementsByTagName("latitude")[0].firstChild.nodeValue); var lng = parseFloat(siteMarkers[i].getElementsByTagName("longitude")[0].firstChild.nodeValue); var id = siteMarkers[i].getElementsByTagName("id")[0].firstChild.nodeValue; var label = siteMarkers[i].getElementsByTagName("name")[0].firstChild.nodeValue; createSiteMarker(new GLatLng(lat,lng),label,id); } } </script> 

    Using Ajax to Enhance our Google Maps Application: On-Demand Details

    When a user clicks on a site, we want an information box to pop up with details about the site (see Figure 3). This is a frequently needed requirement in map-based applications.

    Displaying details
    Figure 3. Displaying details

    However, in real-world applications, there is often too much data to load all at once. You need to fetch and display in on an "on-demand" basis. To do this, we will use Ajax once again. First, we add a listener to the markers, calling theopenInfoWindow() function:

    <script language="javascript" type="text/javascript"> GEvent.addListener(marker, "click", function() { openInfoWindow(marker,"" + id); }); </script> 

    The openInfoWindow() function displays a temporary message ("Loading details...") and then proceeds to fetch the detailed information from the server. The temporary message is important as a visual cue for the user, in order to let the user know the request is being processed.

    <script language="javascript" type="text/javascript"> function openInfoWindow(marker, id) { marker.openInfoWindowHtml("Loading details..."); fetchDetails(id,marker); } </script> 

    The fetchDetails() function is very similar to the previous Ajax code we have seen, as is thedisplayDetails() function, which uses JavaScript DOM to extract some useful fields and then prepares HTML details text to be displayed in the information window:

    <script language="javascript" type="text/javascript"> function fetchDetails(id) { var req = GXmlHttp.create();"GET", "/maps/SiteDirectory?id="+id, true); req.onreadystatechange = getCallbackFunction(req, displayDetails); req.send(null); } function displayDetails(siteDetailsXML) { // Get the root "site" element from the document var site = siteDetailsXML.getElementsByTagName("site")[0]; var name = getNodeValue(site.getElementsByTagName("name")[0]); var id = getNodeValue(site.getElementsByTagName("id")[0]); var symbol = getNodeValue(siteDetailsXML.getElementsByTagName("symbol")[0]); var website = getNodeValue(siteDetailsXML.getElementsByTagName("website")[0]); var address = site.getElementsByTagName("address")[0] var address1 = getNodeValue(siteDetailsXML.getElementsByTagName("line1")[0]); var address2 = getNodeValue(siteDetailsXML.getElementsByTagName("line2")[0]); var city = getNodeValue(siteDetailsXML.getElementsByTagName("city")[0]); var postcode = getNodeValue(siteDetailsXML.getElementsByTagName("postcode")[0]); marker = getMarker(id); marker.showMapBlowup(); var html = '<span class="site-title-line">' + name + ' (' + symbol + ')' +'</span>' + '<span class="site-details-line">' + address1 +'</span>' + '<span class="site-details-line">' + address2 +'</span>' + '<span class="site-details-line">' + city + ' ' + postcode +'</span>' + '<span class="site-details-line">' + '<a href="' + website + '">' + website + '</a>' +'</span>' marker.openInfoWindowHtml(html); } </script> 


    There are obviously many more advanced topics that could not be discussed in an introductory article like this one. For example, in a real-world application of this type, there will probably be too many sites (or tourist attractions, or houses for sale, or whatever) to comfortably show on a small-scale map. So, at the start, when the whole country is being displayed, we only want markers for the cities we are interested in. When a user clicks on a city, the map will zoom in and display the sites in that city. This sort of functionality is fairly easy to implement using other Google Map API features not discussed here, but examples can be found in the sample code.

    Hopefully, this article has given you a good picture of how to use Google Maps and Ajax to integrate non-trivial map-based functionalities into your web application. As an introductory article, the server-side Ajax techniques presented here are fairly basic. For more sophisticated applications, Ajax frameworks such asDWR are well worth looking into.