OpenLayers中的球面墨卡托投影
最近看OpenLayers,研究到地图投影时找到官方的文档,就翻译了一下,由于英文能力差,翻译不好的地方,请看原文
原文地址:http://docs.openlayers.org/library/spherical_mercator.html
球面墨卡托投影
该文档说明了什么是球面墨卡托投影以及何时使用该投影。文档中包含一些必要的背景知识、商用图层的代码演示、添加WMS图层以及使用OpenLayers进行投影变换的内容。要求读者对投影变换和OpenLayers有一个基本的了解。
-
什么是球面墨卡托投影?
球面墨卡托投影在OpenLayers community版本和其他OSG community版本中都有使用。Google Maps,微软Virtual Earth,Yahoo Maps和其他商业地图API的提供者都使用该投影。
该投影是将地球当作一个球体而不是椭球体,然后应用墨卡托投影的方法,将地图投影到一个地图平面上。
为了正确的在商业地图API上叠加地图数据,就必须使用该投影。最基本的是在商业地图API上显示栅格瓦片地图——例如TMS,WMS以及其他类似的瓦片。
为了更好的使用商业地图API,基于Google Maps的数据生成人员也需要使用该投影。最基本的例如OpenStreetMap,栅格地图瓦片都是使用的“球面墨卡托投影”。
GIS中,通常用“EPSG”的代码来表示一种地图投影。最常用的“EPSG:4326”,在地图上将经纬度直接当作X /Y对待。球面墨卡托投影在官方指定的代码为EPSG:3785。但是在官方发布之前,很多软件已经使用了EPSG:900931代码来表示该投影,OpenLayers仍然使用这个非官方的代码。看到“EPSG:4326”字符,就是经纬度坐标的描述,看到“EPSG:900931”则是用“米”做单位的x/y坐标的描述。
-
创建地图
首先我们创建一个使用球面墨卡托投影的地图。在这里我们使用基于微软Virtual Earth API的地图。以下的HTML代码将在地图中用到。
2 <head>
3 <title>OpenLayers Example</title>
4 <script src='http://dev.virtualearth.net/mapcontrol/mapcontrol.ashx?v=6.1'></script>
5 <script src="http://openlayers.org/api/OpenLayers.js%22%3E%3C/script>
6 </head>
7 <body>
8 <div style="width:100%; height:100%" id="map"></div>
9 <script defer='defer' type='text/javascript'>
10 // Code goes here
11 </script>
12 </body>
13 </html>
就下来添加Virtual Earth图层作为地图的基础图层
2 var layer = new OpenLayers.Layer.VirtualEarth("Virtual Earth",
3 {
4 sphericalMercator: true,
5 maxExtent: new OpenLayers.Bounds(-20037508.34,-20037508.34,20037508.34,20037508.34)
6 });
7 map.addLayer(layer);
8 map.zoomToMaxExtent();
这样就创建了一副地图。像这样的地图,应该着重注意:setCenter不能再使用经纬度坐标,而应该是投影以后以“米”为单位的坐标。你可以拖动该地图,但是如果你不理解球面墨卡托投影,接下来做任何功能都将非常困难。
该地图的maxResolution根据一些默认值进行计算,通常球面墨卡托投影的地图范围是经度-180~180,纬度-85.0511~85.0511,这是因为墨卡托投影两极将变形到无穷远处,必须排除掉北极和南极区域,剩下的区域投影后正好是一个正方形,投影后的范围是从 -20037508.34到20037508.34。
地图的maxResolution默认值的计算方法是:将该范围匹配在边长为256像素的图片上,结果maxResolution的值就是156543.0339。在图层中默认的就是该值,不需要通过图层options来设置了。
如果将球面墨卡托投影的WMS或者TMS图层作为一个单独的图层,需要指定图层的maxResolution属性,另外还需要定义该图层的maxExtent。
-
使用投影坐标
OpenLayers提供了在客户端进行投影变换的工具,可以将经纬度坐标转换为球面墨卡托坐标。文档中首先在setCenter或者其他函数中需要使用坐标转换,接着演示如何通过使用map的displayProjection选项来将地图的坐标系显示为其他的投影坐标。
-
Points,Bounds投影变换
首先创建一个投影对象作为默认的投影,标准的经纬度投影的字符串是“EPSG:4326”——基于WGS84的参考椭球面(如果你的数据和Google Maps匹配很准,就是这种投影)。
接着创建一个对象保存你的坐标,然后转换
var point = new OpenLayers.LonLat(-71, 42);
point.transform(proj, map.getProjectionObject());
该点已经转换为球面墨卡托投影坐标,你可以传递给map的setCenter方法:
也可以直接在setCenter中调用:
var point = new OpenLayers.LonLat(-71, 42);
map.setCenter(point.transform(proj, map.getProjectionObject()));
通过这种方法,可以使用经纬度坐标来设置地图中心。
还可以使用相同的方法来投影变换OpenLayers.Bounds对象:同样用Bounds对象的transfrom方法:
bounds.transform(proj, map.getProjectionObject());
坐标变换后替换掉原来的对象,因此不需要重新定义一个变量。
-
Geometries的投影变换
Geometry对象和LonLat与Bounds对象一样拥有坐标转换方法。在你的应用程序代码中创建的geometry对象添加到图层上之前,应该先进行坐标转换,同样从图层获取得到的geometry对象在别的地方使用,也需要坐标转换。
由于所有的坐标转换都是更新对象自身,所以想添加一个geometry到图层上,不应该直接调用转换方法,而是克隆一个geometry对象后再调用:
var geometry = feature.geometry.clone();
geometry.transform(layerProj, targetProj);
-
矢量数据的投影变换
创建一副带投影的地图后,很可能需要将矢量数据进行投影变换然后添加到基础地图上,完成这个工作,只需要简单的设置好矢量数据的投影,并确定地图的投影就可以了。
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);
可以使用该方法加载任何OpenLayers支持格式的矢量数据,包括WKT,GeoJSON,KML和其他一些格式,在GML图层上指定format选项。
projection: new OpenLayers.Projection("EPSG:4326"),
format: OpenLayers.Format.GeoJSON
});
map.addLayer(geojson);
设置了图层的投影属性后,如果手动添加features到图层上(比如调用layer.addFeatures),在添加到图层上之前必须进行坐标转换。
-
投影变换后的数据的序列化
OpenLayers中矢量数据序列化的方法是将矢量图层上获取数据集合传递给格式化类写数据。在一个具有投影的地图中,我们获取到的数据是已经投影变换过的,为了进行数据转换,需要使用内部投影和外部投影两个参数给格式化类,然后再用格式化类序列化数据。
'internalProjection': new OpenLayers.Projection("EPSG:900913"),
'externalProjection': new OpenLayers.Projection("EPSG:4326")
});
var jsonstring = format.write(vector_layer.features);
-
在控件中显示投影坐标
有些控件可以将地图坐标显示给用户,有的直接显示,有的包含在超链接中。MousePosition和Permalink控件(包括ArgParser控件)都是用地图所使用地图投影坐标——也就是球面墨卡托坐标。为了避免用户混淆不清,OpenLayers可以通过displayProjection设置控件的坐标系,将地图坐标系转换为显示坐标系。
要使用这一功能,在创建地图时需要指定projection和displayProjection选项,控件将自动选择这些选项的设置。
projection: new OpenLayers.Projection("EPSG:900913"),
displayProjection: new OpenLayers.Projection("EPSG:4326")
});
map.addControl(new OpenLayers.Control.Permalink());
map.addControl(new OpenLayers.Control.MousePosition());
这样你就可以正常加载地图了。
-
创建球面墨卡托投影栅格图片
球面墨卡托投影如此重要的一个原因是只有这种投影能让你将图片地图正确的叠加到类似于Google Maps这样的商业图层上。在浏览器是使用栅格地图,在一个瘦GIS客户端是不可能对图片进行重新投影,只能是所有的图片使用同样的投影。
如何生成球面墨卡托投影的瓦片地图取决于你使用什么软件来创建地图图片。MapServer的使用包含在此文档中。
-
MapServer
MapServer采用proj.4来支持地图投影。为了转换为球面墨卡托投影,需要在proj.4的data目录下添加投影定义。
Linux环境下,打开/usr/share/proj/epsg文件,在文件尾部添加一行:
<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
然后在地图文件中添加投影在wms_srs元数据中:
web
metadata
wms_srs "EPSG:4326 EPSG:900913"
end
end
# Layers go here
end
这样就可以通过MapServer的WMS服务请求使用球面墨卡托投影的瓦片地图,通过OpenLayers很好的和商业数据匹配。
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图层自动继承地图基础图层的投影,所以不需要在图层中设置投影选项。
-
GeoServer
最新版的GeoServer已经支持EPSG:900913,因此不需要额外添加投影。把GeoServer的图层作为WMS添加到地图上即可。
-
自定义瓦片地图
另一个使用球面墨卡托投影的场合是加载自定义的瓦片地图。很多瓦片使用和Google Maps一样的投影,而且是使用同样的z/x/y语法来访问瓦片。
如果你的瓦片是依照Google的瓦片编码规则(从世界的左上角开始,按照x,y,z编码),通过简单的修改get_url函数,就可以用TMS图层来加载这些瓦片。
首先定义getURL函数:允许接受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;
}
然后,创建TMS图层,传入一个选项告诉图层自定义瓦片的加载函数是什么:
"http://example.com/",
{ 'type':'png', 'getURL':get_my_url });
自己编写的函数将覆盖getURL函数,请求自己的瓦片来代替标准的TMS瓦片。
这样做,你的地图选项还需要包含和Google Maps一样的maxExtent和maxResolution。
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")
});
-
球面墨卡托投影和EPSG的其他命名
球面墨卡托投影在OpenLayers中使用代码EPSG:900913,很多其他的服务比如OpenStreetMap, Bing和Yahoo也用同样的投影,但并不一定要用EPSG:900913代码,一些其他的代码比如EPSG:3857 和EPSG:102113也在使用。现在官方统一规定了代码EPSG:3857来代替EPSG:900913(http://www.epsg-registry.org/export.htm?gml=urn:ogc:def:crs:EPSG::3857)。因此,如果你想合并OpenLayers球面墨卡托投影的叠加图层,不管该图层使用的是其他代码还是官方的代码,你必须确保OpenLayers请求的是EPSG:3857或者别的代码而不是EPSG:900913。在加载一个图层到地图上之前,重写图层的投影来完成这些工作。代码如下:
OpenLayers.Projection.addTransform("EPSG:4326", "EPSG:3857", OpenLayers.Layer.SphericalMercator.projectForward);
OpenLayers.Projection.addTransform("EPSG:3857", "EPSG:4326", OpenLayers.Layer.SphericalMercator.projectInverse);
// 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]);
这样,叠加图层(例如WMS图层)将使用3857代码,并在4326和3857之间完成坐标变换。