以下内容转载自木的树的文章《Web地图呈现原理》
作者:木的树
来源:博客园
著作权归作者所有。商业转载请联系作者获得授权,非商业转载请注明出处。
腾讯位置服务致力于为各行各业提供全方位的位置服务产品。与微信、手机QQ、王者荣耀、
京东、滴滴出行等多个在各自行业具有领先地位的产品开展深度合作。欢迎大家了解并体验腾讯位置服务。本篇内容为大家揭开地图其呈现原理!
地图投影
对于接触互联网地图的同学来说,最开始接触的恐怕就是坐标转换的过程了。由于地球是个近似椭球的形状,有各种各样的椭球模型来模拟地球,最著名的也就是GPS系统使用的WGS84椭球了。但是这些椭球体的坐标使用的是经纬度,单位是角度。目前我们的地图大多是二维平面上展示,使用角度为基础来计算多有不便,所以有众多数学家提出各种不同的转换方式来将经纬度表示的位置转换成平面坐标,这个转换过程地图学上成为投影。投影的方式多种多样,对我们做互联网地图的来说,最重要的就是墨卡托投影的变体——Web墨卡托投影。我们先来看一下墨卡托投影的转换过程
(以赤道本初子午线为原点)
投影完毕后的结果就是:
先不要头疼数学公式,已经有很多类库做好了代码实现,比如leaflet:
L.Projection.Mercator = {
R: 6378137,
R_MINOR: 6356752.314245179,
bounds: L.bounds([-20037508.34279, -15496570.73972], [20037508.34279, 18764656.23138]),
project: function (latlng) {
var d = Math.PI / 180,
r = this.R,
y = latlng.lat * d,
tmp = this.R_MINOR / r,
e = Math.sqrt(1 - tmp * tmp),
con = e * Math.sin(y);
var ts = Math.tan(Math.PI / 4 - y / 2) / Math.pow((1 - con) / (1 + con), e / 2);
y = -r * Math.log(Math.max(ts, 1E-10));
return new L.Point(latlng.lng * d * r, y);
},
unproject: function (point) {
var d = 180 / Math.PI,
r = this.R,
tmp = this.R_MINOR / r,
e = Math.sqrt(1 - tmp * tmp),
ts = Math.exp(-point.y / r),
phi = Math.PI / 2 - 2 * Math.atan(ts);
for (var i = 0, dphi = 0.1, con; i < 15 && Math.abs(dphi) > 1e-7; i++) {
con = e * Math.sin(phi);
con = Math.pow((1 - con) / (1 + con), e / 2);
dphi = Math.PI / 2 - 2 * Math.atan(ts * con) - phi;
phi += dphi;
}
return new L.LatLng(phi * d, point.x * d / r);
}
};
接下来我们说一下互联网地图真正使用的投影——Web墨卡托或者也叫球形墨卡托。一般来说按照传统地图学的要求,一个投影坐标系都要有一个对应的椭球体,比如从WGS84的坐标转换成国内腾讯地图或者百度地图的坐标,都是要经过一步椭球体转换成gcj02椭球下的经纬度然后才能打点。所以有没有小伙伴在开发中使用Geolocation接口获取的经纬度直接传入上面地图api中打点发现误差很大?就是因为没有转成gcj02椭球下的经纬度。但是web墨卡托这个投影其实并不符合地图学的要求,它没有对应的椭球体,它是谷歌自己造出来的(因为简单),也可以说对任何椭球体都适用,但这种时候我们在表达一个位置信息时严格来说应当这样表达:椭球下的Web墨卡托投影坐标是。
好了现在来说一下web墨卡托的转换方式:
/*
* @namespace Projection
* @projection L.Projection.SphericalMercator
*
* Spherical Mercator projection — the most common projection for online maps,
* used by almost all free and commercial tile providers. Assumes that Earth is
* a sphere. Used by the `EPSG:3857` CRS.
*/
L.Projection.SphericalMercator = {
R: 6378137,
MAX_LATITUDE: 85.0511287798,
project: function (latlng) {
var d = Math.PI / 180,
max = this.MAX_LATITUDE,
lat = Math.max(Math.min(max, latlng.lat), -max),
sin = Math.sin(lat * d);
return new L.Point(
this.R * latlng.lng * d,
this.R * Math.log((1 + sin) / (1 - sin)) / 2);
},
unproject: function (point) {
var d = 180 / Math.PI;
return new L.LatLng(
(2 * Math.atan(Math.exp(point.y / this.R)) - (Math.PI / 2)) * d,
point.x * d / this.R);
},
bounds: (function () {
var d = 6378137 * Math.PI;
return L.bounds([-d, -d], [d, d]);