地图坐标转换
LBS,基于位置的服务(Location Based Service),近年来已经无处不在,尤其是我们前端,相信或多或少都有接触一些地图API服务,比如高德、百度啊、谷歌啊~
但是用的时候可能看到下面这些字眼:比如BD09、火星坐标、WGS84……不由得还是蒙圈了啊?
那么接下来,我们就来了解一下,关于当前用到的一些互联网地图的基础坐标转换知识~
0、首先给大家出一个题
地图上的经纬度转换到平面坐标时,和平面坐标的XY的对应关系是什么,就是经度(longitude)和维度(latitude)分别给对应X,Y中的谁?
这是在实际中经常会用到的一个知识点,我之前没有想太多,反正就把数值往里尝试,因为位置差异很大,正确还是错误一眼就看出来了的,但是这样其实很不好,被师兄说了,我一个GISer的连这个都弄不明白不应该,哈哈哈。不求甚解是可以的,但是专业性还是要强化的。
来看看上面的图:
经纬度大家都知道,地球上横线是纬度,纵线是经度。
这也导致了我们下意识就会觉得,横线是X,纵线是Y。这样的认知显然是错误的。
但其实,横线是刻画了Y轴上的刻度,纵线是刻画了X轴上的刻度,这里要用到投影的角度来看问题。
所以大家要记住经纬XY
经度 (longitude) —— 对应 X
维度 ( latitude ) —— 对应 Y
一、当前互联网地图的坐标系现状
1.地球坐标 (WGS84)
国际标准,从专业GPS 设备中取出的数据的坐标系
国际地图提供商使用的坐标系
2.火星坐标 (GCJ-02)也叫国测局坐标系
中国标准,从国行移动设备中定位获取的坐标数据使用这个坐标系
国家规定: 国内出版的各种地图系统(包括电子形式),必须至少采用GCJ-02对地理位置进行首次加密。
3.百度坐标 (BD-09)
百度标准,百度 SDK,百度地图,Geocoding 使用
(百度在火星坐标上的二次加密)
二、互联网在线地图使用的坐标系
火星坐标系:
iOS 地图(其实是高德)
Gogole地图
搜搜、阿里云、高德地图
百度坐标系:
当然只有百度地图
WGS84坐标系:
国际标准,谷歌国外地图、osm地图等国外的地图一般都是这个
三、从设备获取经纬度(GPS)坐标
如果使用的是百度sdk那么可以获得百度坐标(bd09)或者火星坐标(GCJ02),默认是bd09
如果使用的是ios的原生定位库,那么获得的坐标是WGS84
如果使用的是高德sdk,那么获取的坐标是GCJ02
四、坐标转换方法--JS版本
我在之前的一篇文章里,基于Ionic框架的使用讲到了地图定位:ionic2入门教程(六)地图服务(谷歌、高德、百度定位),现在重新写一个小demo来实现我们的坐标转换。
关于方法,我找到了应该是最通用的一种,源码地址——作者wandergis,大部分的转换方式应该都是基于他的这个版本,相关说明也是最清楚的。
实际中我们可能会用到不同的地图,那么就对应到不同坐标系的转换,比如说,你有一份wgs84的数据服务,你要展现在百度或者高德地图上,这时候你就需要转换了。
我这里的例子是,我用到百度搜索地名,得到经纬度,但是我要将它绘制在以84为坐标系的地图leaflet之上,这时候我就需要将返回的经纬度进行转换。
我们先用百度搜索广州塔,定位中心
基于我们选择的OpenStreetMap,未转换之前,我们用百度搜索广州塔返回的值画点,可以看出很明显是偏移了的:
<h1>百度地名搜索</h1>
<input type="text" id="searchVal">
<button id="searchBtn">广州市内搜索</button>
<div id="map1">
</div>
<script>
var searchBtn = document.getElementById('searchBtn');
var mymap = L.map('map1').setView([39.897445, 116.331398], 13);
L.tileLayer(
'https://api.tiles.mapbox.com/v4/{id}/{z}/{x}/{y}.png?access_token=pk.eyJ1IjoibWFwYm94IiwiYSI6ImNpejY4NXVycTA2emYycXBndHRqcmZ3N3gifQ.rJcFIG214AriISLbB6B5aw', {
maxZoom: 18,
attribution: 'Map data © <a href="http://openstreetmap.org">OpenStreetMap</a> contributors, ' +
'<a href="http://creativecommons.org/licenses/by-sa/2.0/">CC-BY-SA</a>, ' +
'Imagery © <a href="http://mapbox.com">Mapbox</a>',
id: 'mapbox.streets'
}).addTo(mymap);
// 创建地址解析器实例
searchBtn.onclick = function () {
var searchVal = document.getElementById('searchVal').value;
var myGeo = new BMap.Geocoder();
// 将地址解析结果显示在地图上,并调整地图视野
myGeo.getPoint(searchVal, function (point) {
if (point) {
console.log(point);
L.marker([point.lat,point.lng]).addTo(mymap);
mymap.setView([point.lat,point.lng],15);
} else {
alert("您选择地址没有解析到结果!");
}
}, "广州市");
}
转换代码如下:
/**
* copy from https://github.com/wandergis/coordtransform
* Created by Wandergis on 2015/7/8.
* 提供了百度坐标(BD09)、国测局坐标(火星坐标,GCJ02)、和WGS84坐标系之间的转换
*/
//UMD魔法代码
// if the module has no dependencies, the above pattern can be simplified to
(function (root, factory) {
if (typeof define === 'function' && define.amd) {
// AMD. Register as an anonymous module.
define([], factory);
} else if (typeof module === 'object' && module.exports) {
// Node. Does not work with strict CommonJS, but
// only CommonJS-like environments that support module.exports,
// like Node.
module.exports = factory();
} else {
// Browser globals (root is window)
root.coordtransform = factory();
}
}(this, function () {
//定义一些常量
var x_PI = 3.14159265358979324 * 3000.0 / 180.0;
var PI = 3.1415926535897932384626;
var a = 6378245.0;
var ee = 0.00669342162296594323;
/**
* 百度坐标系 (BD-09) 与 火星坐标系 (GCJ-02)的转换
* 即 百度 转 谷歌、高德
* @param bd_lon
* @param bd_lat
* @returns {*[]}
*/
var bd09togcj02 = function bd09togcj02(bd_lon, bd_lat) {
var bd_lon = +bd_lon;
var bd_lat = +bd_lat;
var x = bd_lon - 0.0065;
var y = bd_lat - 0.006;
var z = Math.sqrt(x * x + y * y) - 0.00002 * Math.sin(y * x_PI);
var theta = Math.atan2(y, x) - 0.000003 * Math.cos(x * x_PI);
var gg_lng = z * Math.cos(theta);
var gg_lat = z * Math.sin(theta);
return [gg_lng, gg_lat]
};
/**
* 火星坐标系 (GCJ-02) 与百度坐标系 (BD-09) 的转换
* 即谷歌、高德 转 百度
* @param lng
* @param lat
* @returns {*[]}
*/
var gcj02tobd09 = function gcj02tobd09(lng, lat) {
var lat = +lat;
var lng = +lng;
var z = Math.sqrt(lng * lng + lat * lat) + 0.00002 * Math.sin(lat * x_PI);
var theta = Math.atan2(lat, lng) + 0.000003 * Math.cos(lng * x_PI);
var bd_lng = z * Math.cos(theta) + 0.0065;
var bd_lat = z * Math.sin(theta) + 0.006;
return [bd_lng, bd_lat]
};
/**
* WGS84转GCj02
* @param lng
* @param lat
* @returns {*[]}
*/
var wgs84togcj02 = function wgs84togcj02(lng, lat) {
var lat = +lat;
var lng = +lng;
if (out_of_china(lng, lat)) {
return [lng, lat]
} else {
var dlat = transformlat(lng - 105.0, lat - 35.0);
var dlng = transformlng(lng - 105.0, lat - 35.0);
var radlat = lat / 180.0 * PI;
var magic = Math.sin(radlat);
magic = 1 - ee * magic * magic;
var sqrtmagic = Math.sqrt(magic);
dlat = (dlat * 180.0) / ((a * (1 - ee)) / (magic * sqrtmagic) * PI);
dlng = (dlng * 180.0) / (a / sqrtmagic * Math.cos(radlat) * PI);
var mglat = lat + dlat;
var mglng = lng + dlng;
return [mglng, mglat]
}
};
/**
* GCJ02 转换为 WGS84
* @param lng
* @param lat
* @returns {*[]}
*/
var gcj02towgs84 = function gcj02towgs84(lng, lat) {
var lat = +lat;
var lng = +lng;
if (out_of_china(lng, lat)) {
return [lng, lat]
} else {
var dlat = transformlat(lng - 105.0, lat - 35.0);
var dlng = transformlng(lng - 105.0, lat - 35.0);
var radlat = lat / 180.0 * PI;
var magic = Math.sin(radlat);
magic = 1 - ee * magic * magic;
var sqrtmagic = Math.sqrt(magic);
dlat = (dlat * 180.0) / ((a * (1 - ee)) / (magic * sqrtmagic) * PI);
dlng = (dlng * 180.0) / (a / sqrtmagic * Math.cos(radlat) * PI);
var mglat = lat + dlat;
var mglng = lng + dlng;
return [lng * 2 - mglng, lat * 2 - mglat]
}
};
var transformlat = function transformlat(lng, lat) {
var lat = +lat;
var lng = +lng;
var ret = -100.0 + 2.0 * lng + 3.0 * lat + 0.2 * lat * lat + 0.1 * lng * lat + 0.2 * Math.sqrt(Math.abs(lng));
ret += (20.0 * Math.sin(6.0 * lng * PI) + 20.0 * Math.sin(2.0 * lng * PI)) * 2.0 / 3.0;
ret += (20.0 * Math.sin(lat * PI) + 40.0 * Math.sin(lat / 3.0 * PI)) * 2.0 / 3.0;
ret += (160.0 * Math.sin(lat / 12.0 * PI) + 320 * Math.sin(lat * PI / 30.0)) * 2.0 / 3.0;
return ret
};
var transformlng = function transformlng(lng, lat) {
var lat = +lat;
var lng = +lng;
var ret = 300.0 + lng + 2.0 * lat + 0.1 * lng * lng + 0.1 * lng * lat + 0.1 * Math.sqrt(Math.abs(lng));
ret += (20.0 * Math.sin(6.0 * lng * PI) + 20.0 * Math.sin(2.0 * lng * PI)) * 2.0 / 3.0;
ret += (20.0 * Math.sin(lng * PI) + 40.0 * Math.sin(lng / 3.0 * PI)) * 2.0 / 3.0;
ret += (150.0 * Math.sin(lng / 12.0 * PI) + 300.0 * Math.sin(lng / 30.0 * PI)) * 2.0 / 3.0;
return ret
};
/**
* 判断是否在国内,不在国内则不做偏移
* @param lng
* @param lat
* @returns {boolean}
*/
var out_of_china = function out_of_china(lng, lat) {
var lat = +lat;
var lng = +lng;
// 纬度3.86~53.55,经度73.66~135.05
return !(lng > 73.66 && lng < 135.05 && lat > 3.86 && lat < 53.55);
};
return {
bd09togcj02: bd09togcj02,
gcj02tobd09: gcj02tobd09,
wgs84togcj02: wgs84togcj02,
gcj02towgs84: gcj02towgs84
}
}));
坐标转换后显示如下,我们需要将百度坐标转成火星坐标再转成wgs84,因为我们用的地图是openstreet,是wgs84坐标系
可以看到,下面中三个点中,有一个已经是正确了的
myGeo.getPoint(searchVal, function (point) {
if (point) {
console.log(point);
// bd09->gcj02
var myPoint = coordtransform.bd09togcj02(point.lng, point.lat);
console.log(myPoint);
// gcj02->wgs84
var myPoint2 = coordtransform.gcj02towgs84(myPoint[0], myPoint[1]);
console.log(myPoint2);
var latlng = L.latLng([myPoint[1], myPoint[0]]);
var latlng2 = L.latLng([myPoint2[1], myPoint2[0]]);
// 画点
L.marker(point).addTo(mymap);
L.marker(latlng).addTo(mymap);
L.marker(latlng2).addTo(mymap);
// 设置中心
mymap.setView([point.lat, point.lng], 13);
} else {
alert("您选择地址没有解析到结果!");
}
}, "广州市");
源码:https://github.com/JiaXinYi/i...
五、EPSG:3857
如果你用到了leaflet/openlayers/arcgis jsAPI的话,应该还有一个点需要了解
这个算是题外话,因为一般都是学gis的才会用到这些,一般情况下百度高德这些大概都能够满足需求了。
像用到这些地图的情况,经常会涉及到EPSG:3857 或者OpenLayers:900913,acrgis: 102100(3857)。
EPSG:3857 其实是EPSG协会(European Petroleum Survey Group)为 Web Wercator 最终设立的WKID,也就是现在我们常用的Web 地图的坐标系,并且给定官方命名 “WGS 84 / Pseudo-Mercator“。
Web Mercator 是一个投影坐标系统,其基准面是 WGS 1984 。
WGS 1984 是一个长半轴(a)为6378137,短半轴(b)为6356752.314245179 的椭球体,扁率(f)为298.257223563,f=(a-b)/a 。
但是,Web Mercator 坐标系使用的投影方法不是严格意义的墨卡托投影而是一个被 EPSG称为伪墨卡托的投影方法,这个伪墨卡托投影方法的大名是 Popular Visualization Pseudo Mercator,PVPM。
Google 最先发明了这套系统,在投影过程中,将表示地球的参考椭球体近似的作为正球体处理(正球体半径 R = 椭球体半长轴 a)。
后来,Web Mercator 在 Web 地图领域被广泛使用,这个坐标系就名声大噪。尽管这个坐标系由于精度问题一度不被GIS专业人士接受,但最终 EPSG 还是给了 WKID:3857。
所以其实看到EPSG:3857,就知道,当前的坐标系是wgs84,而这个属性,通常在地图的默认设置中。就是说,如果你不改,这些地图就应该是wgs84坐标系。
六、坐标转换方法--TS版本(待完成)
请输入代码
如果对你有帮助的话,能否考虑打赏a cup of coffee,比心
参考:
http://wandergis.com/coordtra...
http://blog.csdn.net/kikitamo...
https://www.sogou.com/link?ur...
http://developer.baidu.com/ma...