.. _spherical-mercator: ================== Spherical Mercator ================== .. include:: spherical_mercator_intro.inc |spherical-mercator-intro| What is Spherical Mercator? --------------------------- Spherical Mercator is a de facto term used inside the OpenLayers community -- and also the other existing Open Source GIS community -- to describe the projection used by Google Maps, Microsoft Virtual Earth, Yahoo Maps, and other commercial API providers. This term is used to refer to the fact that these providers use a Mercator projection which treats the earth as a sphere, rather than a projection which treats the earth as an ellipsoid. This affects calculations done based on treating the map as a flat plane, and is therefore important to be aware of when working with these map providers. In order to properly overlay data on top of the maps provided by the commerical API providers, it is neccesary to use this projection. This applies primarily to displaying raster tiles over the commercial API layers -- such as TMS, WMS, or other similar tiles. In order to work well with the existing commercial APIs, many users who create data designed for use within Google Maps will also use this projection. One prime example is OpenStreetMap, whose raster map tiles are all projected into the 'spherical mercator' projection. Projections in GIS are commonly referred to by their "EPSG" codes, identifiers managed by the European Petroleum Survey Group. One common identifier is "EPSG:4326", which describes maps where latitude and longitude are treated as X/Y values. Spherical Mercator has an official designation of EPSG:3857. However, before this was established, a large amount of software used the identifier EPSG:900913. This is an unofficial code, but is still the commonly used code in OpenLayers. Any time you see the string "EPSG:4326", you can assume it describes latitude/longitude coordinates. Any time you see the string "EPSG:900913", it will be describing coordinates in meters in x/y. First Map --------- The first thing to do with the Spherical Mercator projection is to create a map using the projection. This map will be based on the Microsoft Virtual Earth API. The following HTML template will be used for the map. .. code-block:: html OpenLayers Example
**Ex. 1**: HTML Template The next step is to add the default Microsoft Virtual Earth layer as a base layer to the map. .. code-block:: javascript var map = new OpenLayers.Map('map'); var layer = new OpenLayers.Layer.VirtualEarth("Virtual Earth", { sphericalMercator: true, maxExtent: new OpenLayers.Bounds(-20037508.34,-20037508.34,20037508.34,20037508.34) }); map.addLayer(layer); map.zoomToMaxExtent(); This creates a map. However, once you have this map, there is something very important to be aware of: the coordinates that you use in setCenter are not longitude and latitude! Instead, they are in projected units -- meters, in this case. This map will let you drag around, but without understanding a bit more about spherical mercator, it will be difficult to do anything more with it. This map has a set of assumptions about the maxResolution of the map. Specifically, most spherical mercator maps use an extent of the world from -180 to 180 longitude, and from -85.0511 to 85.0511 latitude. Because the mercator projection stretches to infinity as you approach the poles, a cutoff in the north-south direction is required, and this particular cutoff results in a perfect square of projected meters. As you can see from the maxExtent parameter sent in the constructor of the layer, the coordinates stretch from -20037508.34 to 20037508.34 in each direction. The maxResolution of the map defaults to fitting this extent into 256 pixels, resulting in a maxResolution of 156543.0339. This is handled internally by the layer, and does not need to be set in the layer options. If you are using a standalone WMS or TMS layer with spherical mercator, you will need to specify the maxResolution property of the layer, in addition to defining the maxExtent as demonstrated here. Working with Projected Coordinates ---------------------------------- Thankfully, OpenLayers now provides tools to help you reproject your data on the client side. This makes it possible to transform coordinates from Longitude/Latitude to Spherical Mercator as part of your normal operation. First, we will transform coordinates for use within the setCenter and other calls. Then we will show how to use the displayProjection option on the map to modify the display of coordinate data to take into account the projection of the base map. Reprojecting Points, Bounds +++++++++++++++++++++++++++ To do this, first create a projection object for your default projection. The standard latitude/longitude projection string is "EPSG:4326" -- this is latitude/longitude based on the WGS84 datum. (If your data lines up correctly on Google Maps, this is what you have.) You will then be creating an object to hold your coordinates, and transforming it. .. code-block:: javascript var proj = new OpenLayers.Projection("EPSG:4326"); var point = new OpenLayers.LonLat(-71, 42); point.transform(proj, map.getProjectionObject()); The point is now projected into the spherical mercator projection, and you can pass it to the setCenter method on the map: .. code-block:: javascript map.setCenter(point); This can also be done directly in the setCenter call: .. code-block:: javascript var proj = new OpenLayers.Projection("EPSG:4326"); var point = new OpenLayers.LonLat(-71, 42); map.setCenter(point.transform(proj, map.getProjectionObject())); In this way, you can use latitude/longitude coordinates to choosing a center for your map. You can use the same technique for reprojecting OpenLayers.Bounds objects: simply call the transfrom method on your Bounds object. .. code-block:: javascript var bounds = new OpenLayers.Bounds(-74.047185, 40.679648, -73.907005, 40.882078) bounds.transform(proj, map.getProjectionObject()); Transformations take place on the existing object, so there is no need to assign a new variable. Reprojecting Geometries +++++++++++++++++++++++ Geometry objects have the same transform method as LonLat and Bounds objects. This means that any geometry object you create in your application code must be transformed by calling the transform method on it before you add it to a layer, and any geometry objects that you take from a layer and wish to use will need to be transformed before further use. Because all transforms are in place, once you have added a geometry to a layer, you should not call transform on the geometry directly: instead, you should transform a *clone* of the geometry: .. code-block:: javascript var feature = vector_layer.features[0]; var geometry = feature.geometry.clone(); geometry.transform(layerProj, targetProj); Reprojecting Vector Data ------------------------ When creating projected maps, it is possible to reproject vector data onto a basemap. To do so, you must simply set the projection of your vector data correctly, and ensure that your map projection is correct. .. code-block:: javascript var map = new OpenLayers.Map("map", { projection: new OpenLayers.Projection("EPSG:900913") }); var myBaseLayer = new OpenLayers.Layer.Google("Google", {'sphericalMercator': true, 'maxExtent': new OpenLayers.Bounds(-20037508.34,-20037508.34,20037508.34,20037508.34) }); map.addLayer(myBaseLayer); var myGML = new OpenLayers.Layer.GML("GML", "mygml.gml", { projection: new OpenLayers.Projection("EPSG:4326") }); map.addLayer(myGML); Note that you can also use this setup to load any format of vector data which OpenLayers supports, including WKT, GeoJSON, KML and others. Simply specify the format option of the GML layer. .. code-block:: javascript var geojson = new OpenLayers.Layer.GML("GeoJSON", "geo.json", { projection: new OpenLayers.Projection("EPSG:4326"), format: OpenLayers.Format.GeoJSON }); map.addLayer(geojson); Note that even if you set the projection object on a layer, if you are adding features to the layer manually (via layer.addFeatures), they *must* be transformed before adding to the layer. OpenLayers will only transform the projection of geometries that are created internally to the library, to prevent duplicating projection work. Serializing Projected Data -------------------------- The way to serialize vector data in OpenLayers is to take a collection of data from a vector layer and pass it to a Format class to write out data. However, in the case of a projected map, the data that you get from this will be projected. To reproject the data when converting, you should pass the internal and external projection to the format class, then use that format to write out your data. .. code-block:: javascript var format = new OpenLayers.Format.GeoJSON({ 'internalProjection': new OpenLayers.Projection("EPSG:900913"), 'externalProjection': new OpenLayers.Projection("EPSG:4326") }); var jsonstring = format.write(vector_layer.features); Display Projection on Controls ++++++++++++++++++++++++++++++ Several controls display map coordinates to the user, either directly or built into their links. The MousePosition and Permalink control (and its companion control, ArgParser) both use coordinates which match the internal projection of the map -- which in the case of Spherical Mercator layers is projected. To prevent user confusion, OpenLayers allows one to set a 'display' projection. When these controls are used, transformation is made from the map projection to the display projection. To use this option, when creating your map, you should specify the projection and displayProjection options. Once this is done, the controls will automatically pick up this option from the map. .. code-block:: javascript var map = new OpenLayers.Map("map", { projection: new OpenLayers.Projection("EPSG:900913"), displayProjection: new OpenLayers.Projection("EPSG:4326") }); map.addControl(new OpenLayers.Control.Permalink()); map.addControl(new OpenLayers.Control.MousePosition()); You can then add your layer as normal. Creating Spherical Mercator Raster Images ----------------------------------------- One of the reasons that the Spherical Mercator projection is so important is that it is the only projection which will allow for overlaying image data on top of commercial layers like Google Maps correctly. When using raster images, in the browser, it is not possible to reproject the images in the same way it might be in a 'thick' GIS client. Instead, all images must be in the same projection. How to create Spherical Mercator projected tiles depends on the software you are using to generate your images. MapServer is covered in this document. MapServer +++++++++ MapServer uses proj.4 for its reprojection support. In order to enable reprojection to Spherical Mercator in MapServer, you must add the definition for the projection to your proj.4 data directories. On Linux systems, edit the /usr/share/proj/epsg file. At the bottom of that file, add the line:: <900913> +proj=merc +a=6378137 +b=6378137 +lat_ts=0.0 +lon_0=0.0 +x_0=0.0 +y_0=0 +k=1.0 +units=m +nadgrids=@null +no_defs After you do this, you must add the projection to your wms_srs metdadata in your map file:: map web metadata wms_srs "EPSG:4326 EPSG:900913" end end # Layers go here end This will allow you to request tiles from your MapServer WMS server in the Spherical Mercator projection, which will align with commercial provider data in OpenLayers. .. code-block:: javascript var options = { projection: new OpenLayers.Projection("EPSG:900913"), units: "m", maxResolution: 156543.0339, maxExtent: new OpenLayers.Bounds(-20037508.34, -20037508.34, 20037508.34, 20037508.34) }; map = new OpenLayers.Map('map', options); // create Google Mercator layers var gmap = new OpenLayers.Layer.Google( "Google Streets", {'sphericalMercator': true, 'maxExtent': new OpenLayers.Bounds(-20037508.34,-20037508.34,20037508.34,20037508.34) } ); // create WMS layer var wms = new OpenLayers.Layer.WMS( "World Map", "http://vmap0.tiles.osgeo.org/wms/vmap0", {'layers': 'basic', 'transparent': true} ); map.addLayers(gmap, wms); WMS layers automatically inherit the projection from the base layer of a map, so there is no need to set the projection option on the layer. GeoServer +++++++++ Current versions of GeoServer have support for EPSG:900913 built in, so there is no need to add additional projection data. Simply add your GeoServer layer as a WMS and add it to the map. Custom Tiles ++++++++++++ Another common use case for spherical mercator maps is to load custom tiles. Many custom tile sets are created using the same projection as Google Maps, usually with the same z/x/y scheme for accessing tiles. If you have tiles which are set up according to the 'Google' tile schema -- that is, based on x,y,z and starting in the upper left corner of the world -- you can load these tiles with the TMS layer with a slightly modified get_url function. (Note that in the past there was a 'LikeGoogle' layer in SVN -- this is the appropriate replacement for that code/functionality.) First, define a getURL function that you want to use: it should accept a bounds as an argument, and will look something like this: .. code-block:: javascript function get_my_url (bounds) { var res = this.map.getResolution(); var x = Math.round ((bounds.left - this.maxExtent.left) / (res * this.tileSize.w)); var y = Math.round ((this.maxExtent.top - bounds.top) / (res * this.tileSize.h)); var z = this.map.getZoom(); var path = z + "/" + x + "/" + y + "." + this.type; var url = this.url; if (url instanceof Array) { url = this.selectUrl(path, url); } return url + path; } Then, when creating your TMS layer, you pass in an option to tell the layer what your custom tile loading function is: .. code-block:: javascript new OpenLayers.Layer.TMS("Name", "http://example.com/", { 'type':'png', 'getURL':get_my_url }); This will cause the getURL function to be overridden by your function, thus requesting your inverted google-like tiles instead of standard TMS tiles. When doing this, your map options should contain the maxExtent and maxResolution that are used with Google Maps: .. code-block:: javascript new OpenLayers.Map("map", { maxExtent: new OpenLayers.Bounds(-20037508.34,-20037508.34,20037508.34,20037508.34), numZoomLevels:18, maxResolution:156543.0339, units:'m', projection: "EPSG:900913", displayProjection: new OpenLayers.Projection("EPSG:4326") }); As describe above, when using this layer, you will interact with the map in projected coordinates. SphericalMercator and EPSG aliases ++++++++++++++++++++++++++++++++++ The SphericalMercator projection in OpenLayers uses code EPSG:900913. Many other services, such as OpenStreetMap, Bing and Yahoo are now also using the same projection, but are not necessarily supporting the use of code EPSG:900913. Other codes, such as EPSG:3857 and EPSG:102113 were invented. Today, there is an officially registered EPSG code 3857 whose projection is identical to EPSG:900913. (http://www.epsg-registry.org/export.htm?gml=urn:ogc:def:crs:EPSG::3857). So, if you need to combine overlay layers that are using either an alias or the official EPSG code with an OpenLayers SphericalMercator layer, you have to make sure that OpenLayers requests EPSG:3857 or other alias in stead of EPSG:900913. You can accomplish this by overriding the layer projection before adding the layer to the map. For example: .. code-block:: javascript // create sphericalmercator layers var googleLayer = new OpenLayers.Layer.Google("Google", {"sphericalMercator": true}); var osmLayer = new OpenLayers.Layer.OSM("OpenStreetMap"); // override default epsg code aliasproj = new OpenLayers.Projection("EPSG:3857"); googleLayer.projection = osmLayer.projection = aliasproj; //add baselayers to map map.addLayers([googleLayer, osmLayer]); At this point, overlays (such as WMS layers) will be requested using the 3857 code; transformations will work between 4326 and 3857 as expected.