zoukankan      html  css  js  c++  java
  • 地图坐标转换

    地图坐标转换

    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 &copy; <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坐标系。

  • 相关阅读:
    Eclipse安装SVN插件(转载)
    推荐eclipse插件Properties Editor(转)
    Eclipse快捷键(转载)
    添加路由命令
    oracle 随机数(转载)
    《图解CSS3——第1章 揭开CSS3的面纱》
    css高度自适应
    JS生成随机数
    判断IE浏览器版本
    IE下SCRIPT5009: “JSON”未定义
  • 原文地址:https://www.cnblogs.com/pengfei25/p/9707466.html
Copyright © 2011-2022 走看看