Google Maps and JSF components

Many acclaim Google MapsTM as one of the best online mapping tools as it is intuitive and well designed. It provides aerial photos in impressive detail for many areas and street level information for all major metropolitan areas.

The license terms are probably too restrictive for most commercial applications, but many people that have a compatible business model would like to make use of its advanced online capabilities to display geo-referenced content.

Now, how can I use this nice tool with my J2EE server which serves dynamic content ? JSF Created Google Maps Overlay

Google map overlays

Google Maps provides an extensive and performant AJAX based java script library. Most interestingly for us, this library provides a few techniques to overlay custom content on top of the regular street maps or satellite imagery .

  • Markers, Lines and Areas: You can create - on the fly - points on the map using markers (sometimes with a custom icon). You can also add lines and areas that will be displayed using polylines or polygons.
  • Tile Overlays: The API contains a mechanism to use a grid of images which reside (or are generated dynamically) on a server, based on a simple URL syntax. That mechanism can be overridden to produce specific requests that a custom server can understand.
  • Custom Overlays: They can be used if you need even more control, at the DOM level on what HTML elements should be created.
  • Ground Overlays: These are rectangular areas that can be used to geo-reference a single image, at a specific location.
  • KML Overlays: You can pass the URL of a PUBLICLY accessible KML file to display additional georeferenced images, polygons, polylines or markers.

How can I use that

Because of the restriction to the use of the KML overlay (which need to be publicly accessible), this type of overlay is hard to use when your server would dynamically generate the file. It is additionnally a pain to develop, since in most cases, developers have their own private web server that Google Maps cannot see, and will refuse to use as a source of KML files.

I would only use Custom overlays for the more advanced cases, when everything else fails, since it requires deep knowledge of how dynamic html works.

Ground overlays are rather easy to use, if you are aware that Google Maps coordinate system is based on a Mercator projection. If your image is in another projection, you would have to deal with image re-projection, and that is not a simple affair. If you accept some level of deformation, or your image is “small” compared to the earth surface, you can use images that are in lat/lon coordinates, since the main effect of the Mercator projection would be to stretch the image vertically.

Markers and polylines can be created on the fly, via a simple API, according to some information retrieved from the server. Using them is pretty straightforward, but will require some java-script development. If you have plenty of them, you might want to use Ajax techniques to detect the ones that are visible on the map, and create them asynchronously.

If your server is able to render images in a mercator projection, using Tile Overlays is probably the more versatile design. They allow you to “pave” the map aynchronously when the user pans or zooms. Google Maps caches the images received, so, if your data changes rapidly, you might find yourself displaying inconsistent tiles for your overlay. This type of overlay is best for displaying information that does not change (or very slowly). Since a user action usually triggers the tile request this makes harder the control of the rendering flow from the server side. Since the tile overlay is typically on top of the default Google maps display, you need to generate PNG (or GIF) images with some transparent parts if you want to see the regular map through your custom tiles.

A simple example: The javascript integration

Using a Tile overlay to display server side images

Lets create a Java script prototype based class that retrieves server side tiles to display them on our browser.

That class needs to be passed an unique “DIV” identifier to know where to put the map, as the Google Map API requires.
Here is some sample code to add a custom tile layer on this map view that connects to the server to retrieve the images.

function GoogleMapView (parentDiv) {
 this.parentNode = document.getElementById(parentDiv);
 this.map = new GMap2(this.parentNode);
 // setup copyrights, if needed
 var copyCollection = new GCopyrightCollection('My Company');
 copyCollection.addCopyright(new GCopyright(1,
  new GLatLngBounds(
  new GLatLng(-90, -180),
  new GLatLng(90, 180)), 0, "©2007"));
 // create the tiled layer
 this.tiledLayer = new GTileLayer(copyCollection, 1, 17);
 // function that returns a specific server URL.
 var client=this;
 this.tiledLayer.getTileUrl = function(p0,zl) {
  return client.getTileUrl(p0,zl);
 }
 // add the overlay
 this.map.addOverlay(new GTileLayerOverlay(this.tiledLayer));
}

We will need a method that sets the base URL to reach the servlet (to be called when loading the page) such as:

GoogleMapView.prototype.setServletURL = function (servletURL) {
  this.servletURL = servletURL;
};

We will also need an utility method that computes a string from two georeferenced points. For exemple:

GoogleMapView.prototype.getBox= function (ne,sw) {
 return ne.getLat()+","+ne.getLon()+","+sw.getLat()+","+sw.getLon();
};

This done, we can write the getTileUrl method that will create the request URL

GoogleMapView.prototype.getTileUrl = function(p0,zl) {
 // compute some parameters from the center point and zoom level
 var proj=this.map.getCurrentMapType().getProjection();
// top left coordinates
 var p1=new GPoint((p0.x+0)*256,(p0.y+0)*256);
 var latlon1=proj.fromPixelToLatLng(p1, zl);
 // bottom right coordinates
 var p2=new GPoint((p0.x+1)*256,(p0.y+1)*256);
 var latlon2=proj.fromPixelToLatLng(p2, zl);
 // create a request URL for the server to return the image
 // between  latlon1,latlon2
 var reqBuf = this.servletURL + '?request=image&bbox=' ;
 reqBuf = reqBuf + this.getBox(latlon1,latlon2);
 return uu;
}

If you have a servlet that is able to return (in Mercator projection) the PNG image when it receives a http://my.servlet/someUrl?request=image&bbox=n,s,e,w" request, you will see your own tiled layer appear on top of the Google map component.

Adding markers on demand

With the example above, Google map will go through the getTileUrl method call whenever a tile is not in the cache and has to be displayed. Why not make use of that to ask the server what markers should be added to the area ?

GoogleMapView.prototype.getTileUrl = function(p0,zl) {
 ...
 this.updateMarkers(latlon1,latlon2);
}

The updateMarkers method we introduced above will just send an AJAX request to the server to return the list of markers that belong to that tile.

GoogleMapView.prototype.updateMarkers =
 function updateMarkers(layers,ne,sw) {
 /* update the markers for each symbol in the lat/lon bbox */
 var requrl=this.servletURL + '?request=markers&bbox='
 requrl=requrl + this.getBox(latlon1,latlon2);
 // Build an AJAX request for that
 var request = GXmlHttp.create();
 request.open("GET", requrl, true);
 var client=this;
 request.onreadystatechange = function() {
  client.updateMarkers(request);
 }
 request.send(null);
}

We’re now ready to decode the server information, asynchronously. Lets suppose that the server returns a JSON array containing information on the markers.

GoogleMapView.prototype.updateMarkers = function (request) {
 if (request.readyState == 4) {
  var markers=eval("(" +request.responseText+")" );
  for (i=0;i<markers.length;i++) {
   this.updateMarker(markers[i]);
  }
 }
}

If we suppose that the JSON information for each marker is uniquely identified by an “sid” attribute, we can update our marker list.

GoogleMapView.prototype.updateSymbol=function (info) {
 // this.markers is the array of known markers
 var id=info.sid;
 if(this.markers[id]){
  // remove the potential old marker
  this.map.removeOverlay(this.markers[id]);
 }
 // create the marker
 this.markers[id]=this.createMarker(info);
 if(this.symbols[id]){
  // add this new marker on the map
  this.map.addOverlay(this.markers[id]);
 }
}

We will finally create the marker with some Google Map API, making sure we use information from the server. For exemple “yLat” and “xLon” and “title” attributes.

GoogleMapView.prototype.createMarker = function (info){
 var sicon = new GIcon(G_DEFAULT_ICON);
 var loc=new GLatLng(info.yLat,info.xLon);
 var marker=new GMarker(loc, {
  icon:sicon,
  title:unescape(info.title),
 });
 return marker;
};

That method can be improved to change the marker image (possibly through a round trip to the server, though if you have many markers you have to take care it does not bring the server down). You can also install mouse listeners that will trigger something when the user clicks on the marker.

Java Server Faces

The simple exemple above shows a java script component that connects a server side servlet with a client side Google Map view. This is typically what a Java Server Faces Component does. It is a straightforward task to wrap this all in a single JSF component which exposes only the necessary tags (such as the map initial zoom and centers), and makes sure the server-side and client side context are in phase, for an easy use in any application.
Traffic monitoring based with Google Maps and ILOG JViews
The next version of ILOG JViewsTM will provide that JSF component.

Some references

The Java BluePrints Map viewer component:
https://blueprints.dev.java.net/complib/v2/map-viewer.html
The Google Maps API:
http://www.google.com/apis/maps/documentation/ 

Share and Enjoy: These icons link to social bookmarking sites where readers can share and discover new web pages.
  • Digg
  • del.icio.us
  • Netvouz
  • DZone
  • ThisNext
  • MisterWong
  • Wists

Tags: , , , ,

Leave a Reply