地图坐标转换
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 版本(待完成)
来源: https://segmentfault.com/a/1190000013558653