Web Mapping Services (WMS) On Android

Implemented Using a Google Maps Android API v2, TileLayer

This brief article documents some early exploration into using WMS with the new Google Maps V2 API. It is intended as a reference to help someone trying to get WMS tiles (IE from GeoServer) onto an Android map.


WMS is used to serve map tiles over HTTP by back end frameworks like GeoServer. Some set of geo-referenced data, typically shape files or data stored in a PostGIS database, are returned as raster map tiles. In the past, this data has been consumed by web applications using a client library such as Leaflet or OpenLayers. With Google's v2 mapping API for android, it is now relatively straightforward to build Android apps that combine WMS tiles with Google?s base maps and other data such as vector shapes and map markers.

PhillyTreeMap Android App (Released Spring 2013) showing WMS technique described in the article.

PhillyTreeMap Android App (Released Spring 2013) showing WMS technique described in the article.

For basic getting started info for v2 Maps, see the Google Developer's site for the v2 API. This article assumes a working v2 setup with the sample code running without error. After downloading the google play SDK and setting up the library make sure you can view the TileOverlayDemo. ($ANDROID_SDK_ROOT/extras/google/google_play_services/samples/maps)

Extending the UrlTileProvider class.

Please refer to the sample code at the end of this post.

The v2 API provides the UrlTileProvider class, a partial implementation of the TileProvider class which allows developers to pull in map tiles by composing a URL string.  The API to UrlTileProvider is its getTileUrl method. To request a WMS tile, we override this method to compose the right URL, and the Android mapping SDK does the rest for us. It seems simple enough, but the problem is that the signature of getTileUrl which is getTileUrl(int x, int y, int zoom)  provides tile indexes (x and y) and a zoom level, but WMS requires that we provide a bounding box (xmin, ymin, xmax, ymax) in the request URL. The x, y, zoom parameters provide us enough information to figure this out, but we have to do a little bit of math.

Calculating the map bounds

We know the bounds of the entire map which is square. (roughly -20037508m to 20037508m in both directions using Web Mercator. See the graphic below or the <a href="http://www.maptiler.org/google-maps-coordinates-tile-bounds-projection/">map tiler site</a> for exact values.) We don't use Latitude/Longitude though, because it is unprojected, and this will cause map distortions.

From the google api docs for TileOverlay:

Note that the world is projected using the Mercator projection (see <a href="http://en.wikipedia.org/wiki/Mercator_projection">Wikipedia</a>) with the left (west) side of the map corresponding to -180 degrees of longitude and the right (east) side of the map corresponding to 180 degrees of longitude. To make the map square, the top (north) side of the map corresponds to 85.0511 degrees of latitude and the bottom (south) side of the map corresponds to -85.0511 degrees of latitude. Areas outside this latitude range are not rendered.

Dividing by the number of tiles for a given zoom level.

The number of tiles in either x or y at any zoom level is n = 2^z. With this, and the bounds of the map, we can figure out the size of the tile. Using this information combined with the maps origin (see graphic) and the x, y, zoom data for a given tile, we can find out its bounding box.

Again from the TileOverlay docs:

At each zoom level, the map is divided into tiles and only the tiles that overlap the screen are downloaded and rendered. Each tile is square and the map is divided into tiles as follows:

  • At zoom level 0, one tile represents the entire world. The coordinates of that tile are (x, y) = (0, 0).
  • At zoom level 1, the world is divided into 4 tiles arranged in a 2 x 2 grid.
  • ...
  • At zoom level N, the world is divided into 4N tiles arranged in a 2^N x 2^N grid.


  • zoom level: z = [0..21]  (See GoogleMap.get[Min|Max]ZoomLevel)

  • map size: S = 20037508.34789244 * 2 This constant comes from converting the lat/long values above to EPSG:900913, Web Mercator. Again, see this page on maptiler for a fantastic visual explanation.

  • tile size = S / Math.pow(2, z) So @ zoom level 0, S is the full map, at zoom level 1 there are 2x2 tiles, and at zoom 3 there are 8x8 tiles and so forth.
  • tile origin = (-20037508.34789244, 20037508.34789244)
  • minX of the tiles bbox (for tile index x,y) = origin.x + x * S  Where x is the tile index in the east-west direction passed to the getTileUrl function discussed above.
  • maxX of the tiles bbox (for tile index x,y) = origin.x + (x+1) * S  x+1 because we are looking for the right edge of the tile
  • minY of the tiles bbox (for tile index x,y) = origin.y + y * S
  • maxY of the tiles bbox (for tile index x,y)= origin.y + (y+1) * S

Demo Code

In addition to the following code snippets, I?ve put a sample project on github.

Here is a WMSTileProvider class, which inherits from UrlTileProvider. It supports the above bounding box calculation.</p>


You might use this class in a factory class as follows:


So your Activity code (again see the sample Google maps code referenced above) would have the following calls to add the overlay:


This article presented a demonstration of a simple WMS client using Google's Android v2 Maps API. It covered the math involved with converting from tile index/zoom level to Web Mercator bounding box, and showed how to compose a URL using these values and an instance of Google's UrlTileProvider class.