This post describes how to display WMS data from Geoserver as an overlay on top of a Google base map using the Google Maps API v3. For added interest, the source data for the overlay is stored in the British Ordnance Survey's OSGB projection (EPSG:27700) within a SQL Server 2008 database.
I found two ways to overlay data from my Geoserver on top of a Google basemap using the Google Maps javascript API:
- google.maps.ImageMapType for displaying OGC WMS data as tiled images. This method may suit relatively static data sets that cover large areas. WMS layers may be tiled and cached for improved display performance using GeoWebCache.
- google.maps.KmlLayer for displaying more dynamic (query driven) data covering smaller areas.
Google maps is happy receiving data in WGS84 projection (EPSG:4326) and displaying it in the Google Mercator projection (EPSG:3857, formerly 900913). I found that life became much easier when I accepted that I needed to provide the Geoserver maps/kml in WGS84. I also did a lot of investigation using the Proj4js library to formulate requests but produced some pretty odd looking maps (messy tile edges). Serving the data in WGS84 greatly simplified things, bypassing the need to re-project coordinates within javascript and relying instead upon Geoserver's built in coordinate re-projection facilities as illustrated.
The javascript below is called on the page load event to populate a map div within a web page. It creates a map centered on Lincolnshire and displays Google's roadmap as the base layer.
To add a custom overlay of census output areas from Geoserver on top of that we define a new Google ImageMapType which includes the getTileUrl function which will make one WMS request to our Geoserver for each map tile to display. This involves taking the coordinate of the top left hand corner of each map tile (as a screen coordinate) and calculating its bounding box coordinates as Latitude/Longitude. We can then build the individual WMS requests for adding the custom overlay to the map.
The code also demonstrates the use of a kmlLayer as an overlay.
//myMap.js //javascript to create web map displaying Google base map //with custom overlays from geoserver wms and kml (function () { window.onload = function () { // Create a MapOptions object with the required properties for base map // Centered on Lincolnshire, UK var mapOptions = { zoom: 9, center: new google.maps.LatLng(53.3567, -0.244), mapTypeId: google.maps.MapTypeId.ROADMAP }; // Create the base map map = new google.maps.Map(document.getElementById('map'), mapOptions); //Define custom WMS layer for census output areas in WGS84 var censusLayer = new google.maps.ImageMapType( { getTileUrl: function (coord, zoom) { // Compose URL for overlay tile var s = Math.pow(2, zoom); var twidth = 256; var theight = 256; //latlng bounds of the 4 corners of the google tile //Note the coord passed in represents the top left hand (NW) corner of the tile. var gBl = map.getProjection().fromPointToLatLng( new google.maps.Point(coord.x * twidth / s, (coord.y + 1) * theight / s)); // bottom left / SW var gTr = map.getProjection().fromPointToLatLng( new google.maps.Point((coord.x + 1) * twidth / s, coord.y * theight / s)); // top right / NE // Bounding box coords for tile in WMS pre-1.3 format (x,y) var bbox = gBl.lng() + "," + gBl.lat() + "," + gTr.lng() + "," + gTr.lat(); //base WMS URL var url = "http://mywebserver/geoserver/lincs/wms?"; url += "&service=WMS"; //WMS service url += "&version=1.1.0"; //WMS version url += "&request=GetMap"; //WMS operation url += "&layers=census_oa_2001"; //WMS layers to draw url += "&styles="; //use default style url += "&format=image/png"; //image format url += "&TRANSPARENT=TRUE"; //only draw areas where we have data url += "&srs=EPSG:4326"; //projection WGS84 url += "&bbox=" + bbox; //set bounding box for tile url += "&width=256"; //tile size used by google url += "&height=256"; //url += "&tiled=true"; return url; //return WMS URL for the tile }, //getTileURL tileSize: new google.maps.Size(256, 256), opacity: 0.85, isPng: true }); // add WMS layer to map // google maps will end up calling the getTileURL for each tile in the map view map.overlayMapTypes.push(censusLayer); // define kml layer // set base WMS URL for kml request var kmlUrl = "http://mywebserver/geoserver/lincs/wms/reflect?"; kmlUrl += "&version=1.1.0"; //WMS version kmlUrl += "&layers=study_area_poly"; //WMS layers kmlUrl += "&viewparams=analysis_id:177"; //analysis_id parameter for query filter kmlUrl += "&styles="; kmlUrl += "&format=kml"; var kmlOptions = { map: map, // the map that the kml layer will be added to url: kmlUrl, // the url for the kml data preserveViewport: true // do not adjust map location and zoom }; // add the kml layer to the map studyLayer = new google.maps.KmlLayer(kmlOptions); }; })();
The above WMS request includes parameters to return the overlay tiles in WGS84 projection and to make areas without data to be completely transparent. The viewparams parameter used by the KML example allows for dynamic layer creation using parameterised sql views from Geoserver.
The image illustrates the Google roads basemap with census areas (grey polygons) overlain as WMS tiles and kml data (green grid) overlain as feature data.
One of the nice features of using kml layers is that the Google Maps API automatically provides support for feature identification via info windows.
The actual web page html is provided below:
<!DOCTYPE html /> <html xmlns="http://www.w3.org/1999/xhtml" lang="en"> <head> <meta http-equiv="Content-Type" content="text/html; charset=utf-8" /> <title>Google Map with Geoserver WMS Overlay</title> <link type="text/css" href="Style/maps.css" rel="Stylesheet" media="all" /> <script type="text/javascript" src="http://maps.google.com/maps/api/js?sensor=false"></script> <script src="Scripts/myMap.js" type="text/javascript"></script> </head> <body> <h1>Google Map with Geoserver WMS and KML Overlay</h1> <div id="map"></div> </body> </html>
You might question the sanity of producing web maps that require reprojection on-the-fly and I would have to agree; anything that introduces processing and delay into web mapping is to be avoided, but sometimes you just have to go with what is available.
It would be very nice to see the Google Maps API introduce support for other OGC standards such as WFS; this would enable the creation of more interactive maps and improved support for dynamic mapping of the data, especially where we are dealing with feature data that covers larger areas.
Thanks to Opinionated Geek for the html encoder, needed to make the html display correctly with Alex Gorbatchev's syntax highlighter.