• openlayers6 解决调用百度地图之各种问题


    说在前面

      最近接了一个恶心的活,客户需求是国内、国外都要进行地图展示货运车辆位置及路线图,国外没啥好说的,本身客户国内用的百度API使用百度地图,国外使用谷歌API调用谷歌地图,这不能忍啊,同一套业务整两套代码这怎么维护呢,完全不符合设计原则,于是果断建议国内国外均使用百度地图API(主要是没花钱想最小代价解决问题。。。),国外仍然用百度API调用谷歌地图,于是乎,花了一天时间实在解决不了谷歌地图在百度地图里的偏移问题。
      没办法只能建议用户使用ol重写,保证一套代码同时满足国内国外需求,本来想着ol有以前的积累很快搞定,国内果断使用高德地图,国外使用谷歌地图。结果一提交,客户提出新的问题,国内用户有国外业务,仍然需要在国内查看国外地图,而高德地图在国外没有道路数据。。。这就很扯了,看来是避不过去百度地图这个大坑了,只能一边问候当初设计bd-09的人一边开搞。。。
      果不其然,遇到各种问题,期间也网上找了不少资料、咨询了同行,总算解决了,特记录下来,备无奈使用百度地图的同行参考~

    开发环境

      简单说下开发环境:vue+openlayers6

    第一个问题:调用百度地图缩放时瓦片产生偏移

      废话不多说,直接上正确调用百度地图栅格瓦片代码:

    let resolutions = [];
            for (let i = 0; i < 19; i++) {
              resolutions[i] = Math.pow(2, 18 - i);
            }
            let tilegrid = new TileGrid({
              origin: [0, 0],
              resolutions: resolutions,
            });
    
            let baidu_source = new TileImage({
              projection: "EPSG:3857",
              tileGrid: tilegrid,
              tileUrlFunction: function (tileCoord, pixelRatio, proj) {
                if (!tileCoord) {
                  return "";
                }
                let z = tileCoord[0];
                let x = tileCoord[1];
                let y = -tileCoord[2] - 1;//ol6需要此处减一,否则缩放有偏移
    
                if (x < 0) {
                  x = "M" + -x;
                }
                if (y < 0) {
                  y = "M" + -y;
                }
                return "https://online3.map.bdimg.com/onlinelabel/?qt=tile&x=" + x + "&y=" + y + "&z=" + z + "&styles=pl&udt=20151021&scaler=1&p=1";
              }
            });
            mapLayer = new TileLayer({
              source: baidu_source
            })
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32

    引用:

    import TileLayer from "ol/layer/Tile"
    import TileImage from 'ol/source/TileImage';
    import TileGrid from 'ol/tilegrid/TileGrid';
    
    • 1
    • 2
    • 3

      问题主要出在这一句:let y = -tileCoord[2] - 1;
      openlayers6之前不减一可以正常显示,但是ol6这个版本机制改变了,至于改成啥了,咱也没看源码,按网上大神解决方式解决了就ok,以后有时间再研究吧(大概率很久很久了。。。)。

    第二个问题:判断坐标是否在国内,常用方式不准确

      由于客户的货车全国各地均有,在使用网上常用方式:

    return (lng < 72.004 || lng > 137.8347) || ((lat < 18.033 || lat > 55.8271) || false);//粗略判断
    
    • 1

      进行国内国外判断是并不准确,翻了下网上博客,有好几种精确判定的方式,各有优劣,选了一个相对准确且运算速度快的,代码如下:

    //计算四至范围
        Rectangle(latitude1, longitude1, latitude2, longitude2) {
            let reJ = {
                West: null,
                North: null,
                East: null,
                South: null,
            }
            reJ.West = Math.min(longitude1, longitude2);
            reJ.North = Math.max(latitude1, latitude2);
            reJ.East = Math.max(longitude1, longitude2);
            reJ.South = Math.min(latitude1, latitude2);
            return reJ
        },
    
        /**
         * 精确判断是否在中国
         * @param longitude
         * @param latitude
         * @returns {boolean}
         * @constructor
         */
        IsInsideChina(longitude, latitude) {
            //大陆
            let region = [
                this.Rectangle(49.220400, 79.446200, 42.889900, 96.330000),
                this.Rectangle(54.141500, 109.687200, 39.374200, 135.000200),
                this.Rectangle(42.889900, 73.124600, 29.529700, 124.143255),
                this.Rectangle(29.529700, 82.968400, 26.718600, 97.035200),
                this.Rectangle(29.529700, 97.025300, 20.414096, 124.367395),
                this.Rectangle(20.414096, 107.975793, 17.871542, 111.744104),
            ]
            //台湾省
            let exclude = [
                this.Rectangle(25.398623, 119.921265, 21.785006, 122.497559),
                this.Rectangle(22.284000, 101.865200, 20.098800, 106.665000),
                this.Rectangle(21.542200, 106.452500, 20.487800, 108.051000),
                this.Rectangle(55.817500, 109.032300, 50.325700, 119.127000),
                this.Rectangle(55.817500, 127.456800, 49.557400, 137.022700),
                this.Rectangle(44.892200, 131.266200, 42.569200, 137.022700),
            ]
            for (let i = 0; i < region.length; i++) {
                if (this.InRectangle(region[i], longitude, latitude)) {
                    for (let j = 0; j < exclude.length; j++) {
                        if (this.InRectangle(exclude[j], longitude, latitude)) {
                            return false;
                        }
                    }
                    return true;
                }
            }
            return false;
        },
        /**
         * 判断是否在范围内
         * @param rect
         * @param longitude
         * @param latitude
         * @returns {boolean}
         * @constructor
         */
        InRectangle(rect, longitude, latitude) {
            return rect.West <= longitude && rect.East >= longitude && rect.North >= latitude && rect.South <= latitude;
        }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40
    • 41
    • 42
    • 43
    • 44
    • 45
    • 46
    • 47
    • 48
    • 49
    • 50
    • 51
    • 52
    • 53
    • 54
    • 55
    • 56
    • 57
    • 58
    • 59
    • 60
    • 61
    • 62
    • 63
    • 64

      整体原理比较简单,就是将原来的一个四至范围剖分出很多小的四至范围,增加判定准确度,这里面有个坑,把台湾省排除出去了,具体还没测试台湾省的范围内是否用gcj02偏移了,后面有数据可以验证下~

    第三个问题:百度地图国内部分,存在坐标偏移问题

      问题解决到这,再次问候当初设计bd-09的人。。。根据以前的经验(5、6年前)百度地图是原始经纬度先gcj02偏移,然后bd-09偏移,然后再转web墨卡托,但是一通操作下来,坐标飞了,这特么奇了怪了,当年设计bd-09的人又换人了?此处再问候新的设计此机制的人。。。。
      网上搜了下,发现现在百度地图长能耐了,人家叫百度经纬度和百度墨卡托。。。。这是看谷歌搞了个web墨卡托,表示不服吗,你特么不符你用同一套标准椭球体行吗,非要自己玩自己的是吧,怪不得市场份额跟百度的市值一样缩的惨不忍睹。
      转换思路:wgs84(原始经纬度)转gcj02,然后转bd-09经纬度,最后百度经纬度坐标再转百度墨卡托。
      解决核心代码如下:

    var coordinateTransform = {
        //定义一些常量
        PI: 3.1415926535897932384626,
        x_PI: 3.14159265358979324 * 3000.0 / 180.0,
        a: 6378245.0,
        ee: 0.00669342162296594323,
        EARTHRADIUS: 6370996.81,
        MCBAND: [12890594.86, 8362377.87, 5591021, 3481989.83, 1678043.12, 0],
        LLBAND: [75, 60, 45, 30, 15, 0],
        MC2LL: [[1.410526172116255e-8, 0.00000898305509648872, -1.9939833816331, 200.9824383106796, -187.2403703815547, 91.6087516669843, -23.38765649603339, 2.57121317296198, -0.03801003308653, 17337981.2], [-7.435856389565537e-9, 0.000008983055097726239, -0.78625201886289, 96.32687599759846, -1.85204757529826, -59.36935905485877, 47.40033549296737, -16.50741931063887, 2.28786674699375, 10260144.86], [-3.030883460898826e-8, 0.00000898305509983578, 0.30071316287616, 59.74293618442277, 7.357984074871, -25.38371002664745, 13.45380521110908, -3.29883767235584, 0.32710905363475, 6856817.37], [-1.981981304930552e-8, 0.000008983055099779535, 0.03278182852591, 40.31678527705744, 0.65659298677277, -4.44255534477492, 0.85341911805263, 0.12923347998204, -0.04625736007561, 4482777.06], [3.09191371068437e-9, 0.000008983055096812155, 0.00006995724062, 23.10934304144901, -0.00023663490511, -0.6321817810242, -0.00663494467273, 0.03430082397953, -0.00466043876332, 2555164.4], [2.890871144776878e-9, 0.000008983055095805407, -3.068298e-8, 7.47137025468032, -0.00000353937994, -0.02145144861037, -0.00001234426596, 0.00010322952773, -0.00000323890364, 826088.5]],
        LL2MC: [[-0.0015702102444, 111320.7020616939, 1704480524535203, -10338987376042340, 26112667856603880, -35149669176653700, 26595700718403920, -10725012454188240, 1800819912950474, 82.5], [0.0008277824516172526, 111320.7020463578, 647795574.6671607, -4082003173.641316, 10774905663.51142, -15171875531.51559, 12053065338.62167, -5124939663.577472, 913311935.9512032, 67.5], [0.00337398766765, 111320.7020202162, 4481351.045890365, -23393751.19931662, 79682215.47186455, -115964993.2797253, 97236711.15602145, -43661946.33752821, 8477230.501135234, 52.5], [0.00220636496208, 111320.7020209128, 51751.86112841131, 3796837.749470245, 992013.7397791013, -1221952.21711287, 1340652.697009075, -620943.6990984312, 144416.9293806241, 37.5], [-0.0003441963504368392, 111320.7020576856, 278.2353980772752, 2485758.690035394, 6070.750963243378, 54821.18345352118, 9540.606633304236, -2710.55326746645, 1405.483844121726, 22.5], [-0.0003218135878613132, 111320.7020701615, 0.00369383431289, 823725.6402795718, 0.46104986909093, 2351.343141331292, 1.58060784298199, 8.77738589078284, 0.37238884252424, 7.45]],
        
        _transformlat(lng, lat) {
            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 * this.PI) + 20.0 * Math.sin(2.0 * lng * this.PI)) * 2.0 / 3.0;
            ret += (20.0 * Math.sin(lat * this.PI) + 40.0 * Math.sin(lat / 3.0 * this.PI)) * 2.0 / 3.0;
            ret += (160.0 * Math.sin(lat / 12.0 * this.PI) + 320 * Math.sin(lat * this.PI / 30.0)) * 2.0 / 3.0;
            return ret
        },
    
        _transformlng(lng, lat) {
            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 * this.PI) + 20.0 * Math.sin(2.0 * lng * this.PI)) * 2.0 / 3.0;
            ret += (20.0 * Math.sin(lng * this.PI) + 40.0 * Math.sin(lng / 3.0 * this.PI)) * 2.0 / 3.0;
            ret += (150.0 * Math.sin(lng / 12.0 * this.PI) + 300.0 * Math.sin(lng / 30.0 * this.PI)) * 2.0 / 3.0;
            return ret
        },
            _getLoop(lng, min, max) {
            while (lng > max) {
                lng -= max - min;
            }
            while (lng < min) {
                lng += max - min;
            }
            return lng;
        },
    
        _getRange(lat, min, max) {
            if (min != null) {
                lat = Math.max(lat, min);
            }
            if (max != null) {
                lat = Math.min(lat, max);
            }
            return lat;
        }
    }
    /**
     * WGS转百度经纬
     * @param lon
     * @param lat
     * @returns {*[]}
     */
    coordinateTransform.WGS2BD = function (lon, lat) {
        //先由经纬转火星
        var coor = this.WGS2GCJ(lon, lat);
        //再将火星转百度
        coor = this.GCJ2BD(coor[0], coor[1]);
        return coor;
    }
    /**
     * WGS84转GCj02
     * @param lon
     * @param lat
     * @returns {*[]}
     */
    coordinateTransform.WGS2GCJ = function (lon, lat) {
        if (this._out_of_china(lon, lat)) {
            return [lon, lat]
        } else {
            var dlat = this._transformlat(lon - 105.0, lat - 35.0);
            var dlon = this._transformlng(lon - 105.0, lat - 35.0);
            var radlat = lat / 180.0 * this.PI;
            var magic = Math.sin(radlat);
            magic = 1 - this.ee * magic * magic;
            var sqrtmagic = Math.sqrt(magic);
            dlat = (dlat * 180.0) / ((this.a * (1 - this.ee)) / (magic * sqrtmagic) * this.PI);
            dlon = (dlon * 180.0) / (this.a / sqrtmagic * Math.cos(radlat) * this.PI);
            var mglat = lat + dlat;
            var mglon = lon + dlon;
            return [mglon, mglat];
        }
    }
    /**
     * 火星坐标系 (GCJ-02) 与百度坐标系 (BD-09) 的转换
     * 即谷歌、高德 转 百度
     * @param lon
     * @param lat
     * @returns {*[]}
     */
    coordinateTransform.GCJ2BD = function (lon, lat) {
        var z = Math.sqrt(lon * lon + lat * lat) + 0.00002 * Math.sin(lat * this.x_PI);
        var theta = Math.atan2(lat, lon) + 0.000003 * Math.cos(lon * this.x_PI);
        var bd_lon = z * Math.cos(theta) + 0.0065;
        var bd_lat = z * Math.sin(theta) + 0.006;
        return [bd_lon, bd_lat];
    }
    /**
     * 百度经纬度转百度墨卡托
     * @param lng
     * @param lat
     * @returns {*[]}
     */
    coordinateTransform.BD_WGS2MKT = function (lng, lat) {
        var cF = null;
        lng = this._getLoop(lng, -180, 180);
        lat = this._getRange(lat, -74, 74);
        for (var i = 0; i < this.LLBAND.length; i++) {
            if (lat >= this.LLBAND[i]) {
                cF = this.LL2MC[i];
                break;
            }
        }
        if (cF != null) {
            for (var i = this.LLBAND.length - 1; i >= 0; i--) {
                if (lat <= -this.LLBAND[i]) {
                    cF = this.LL2MC[i];
                    break;
                }
            }
        } 
        lng = cF[0] + cF[1] * Math.abs(lng);
        var cC = Math.abs(lat) / cF[9];
        lat = cF[2] + cF[3] * cC + cF[4] * cC * cC + cF[5] * cC * cC * cC + cF[6] * cC * cC * cC * cC + cF[7] * cC * cC * cC * cC * cC + cF[8] * cC * cC * cC * cC * cC * cC;
        lng *= (lng < 0 ? -1 : 1);
        lat *= (lat < 0 ? -1 : 1);
        return [lng, lat];
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40
    • 41
    • 42
    • 43
    • 44
    • 45
    • 46
    • 47
    • 48
    • 49
    • 50
    • 51
    • 52
    • 53
    • 54
    • 55
    • 56
    • 57
    • 58
    • 59
    • 60
    • 61
    • 62
    • 63
    • 64
    • 65
    • 66
    • 67
    • 68
    • 69
    • 70
    • 71
    • 72
    • 73
    • 74
    • 75
    • 76
    • 77
    • 78
    • 79
    • 80
    • 81
    • 82
    • 83
    • 84
    • 85
    • 86
    • 87
    • 88
    • 89
    • 90
    • 91
    • 92
    • 93
    • 94
    • 95
    • 96
    • 97
    • 98
    • 99
    • 100
    • 101
    • 102
    • 103
    • 104
    • 105
    • 106
    • 107
    • 108
    • 109
    • 110
    • 111
    • 112
    • 113
    • 114
    • 115
    • 116
    • 117
    • 118
    • 119
    • 120
    • 121
    • 122
    • 123
    • 124
    • 125
    • 126
    • 127
    • 128

      BD_WGS2MKT这个方法里的代码参考网上大神,git地址:https://github.com/FreeGIS/CoordinateTransform
      写到这里想着终于完事了,于是选了全球的数据进行展示测试,对比了百度地图和谷歌地图上的数据,国内都基本在同一位置,国外澳大利亚和美国的都飞到海里了,我特么的,不是说国外部分是wgs84的未加偏移坐标吗?又是哪个百度的高人整的活,此处问候+1。。。

    第四个问题:百度地图国外部分,存在坐标偏移问题

      接第三个问题,明明东南亚地区坐标按上面的转换方式没有便宜,为何澳大利亚和美国都飞了呢,试了好几次发现北半球、东半球均可以正常转换且无偏移,南半球、西半球均会产生偏移,仔细看了下大神的代码,发现转换所用的参数LLBAND只有0-75,那坐标要是负值的就直接报错了。。。
      想了半天解决方式无非两种,要么调用百度转换API,要么自己写转换算法,第一种调用百度API会有次数限制,花钱是不可能花钱的,第二种自己写算法也没那个精力 (本事)。。。
      是咱建议客户换了百度地图API的,不甘心就这样打脸,于是想想有没有什么好办法可以解决的,最开始想照着大神的参数把负值加上,但是没那个精力 (本事)去计算转换参数,想破了脑袋,试试能不能取个巧,遇到负值先转成正值,转换之后再变成负值,写完试验了下,结果和谷歌的叠加看没什么问题。。。
      最后总结下转换路径:百度地图叠加国外的数据,不进行gcj02转换,需要用原始经纬度(应该在国外与百度经纬度一致)直接转换为百度墨卡托才行。
      核心代码(其实就是做了个负值转换)如下:

    /**
     * 百度经纬度转百度墨卡托(南半球、西半球均适用)
     * @param lng
     * @param lat
     * @returns {*[]}
     */
    coordinateTransform.BD_WGS2MKT_NORTH = function (lng, lat) {
        let isNorth = false
        let isWest = false
        if(lat<0){
            lat=Math.abs(lat)
            isNorth = true
        }
        if(lng<0){
            lng=Math.abs(lng)
            isWest = true
        }
        var cF = null;
        lng = this._getLoop(lng, -180, 180);
        lat = this._getRange(lat, -74, 74);
        for (var i = 0; i < this.LLBAND.length; i++) {
            if (lat >= this.LLBAND[i]) {
                cF = this.LL2MC[i];
                break;
            }
        }
        if (cF != null) {
            for (var i = this.LLBAND.length - 1; i >= 0; i--) {
                if (lat <= -this.LLBAND[i]) {
                    cF = this.LL2MC[i];
                    break;
                }
            }
        } else {
            console.dir('未转成功',[lng, lat])
            return [lng, lat];
        }
        lng = cF[0] + cF[1] * Math.abs(lng);
        var cC = Math.abs(lat) / cF[9];
        lat = cF[2] + cF[3] * cC + cF[4] * cC * cC + cF[5] * cC * cC * cC + cF[6] * cC * cC * cC * cC + cF[7] * cC * cC * cC * cC * cC + cF[8] * cC * cC * cC * cC * cC * cC;
        lng *= (lng < 0 ? -1 : 1);
        lat *= (lat < 0 ? -1 : 1);
        if(isNorth){
            lat = -lat;
        }
        if(isWest){
            lng = -lng;
        }
        return [lng, lat];
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40
    • 41
    • 42
    • 43
    • 44
    • 45
    • 46
    • 47
    • 48
    • 49
    • 50

      写到这里,所有问题才算全部解决,只能感叹,百度地图没落从一开始就注定了,虽说高德有阿里这个金主爸爸不担心营收压力可以全力做技术的优势,但是你百度也不差啊,广告的钱还是很好赚的呀,搜索不干人事,其他地方干点人事也行啊,没看见各种路子富起来的都做点慈善吗,**平衡知道不,不然ALL IN AI就能翻身了???

    写在后面

      好久不写博客了,也好久没有做GIS开发了,没想到几年时间变化那么多,一直以为大家都按OGC标准来玩,没想到只有基础是可以复制的,代码时间长了就不见得能复用了,还是得常学常用,共勉。

  • 相关阅读:
    Python模拟登录豆瓣:轻松探索海量文化资源!
    电信行业信创发展前景广阔 如何选择可靠的信创传输软件?
    OpenCV入门(C++/Python)- 使用OpenCV标注图像(六)
    HBase 2.x ---- HBase简介
    【100个 Unity实用技能】☀️ | UGUI Text中加入超链接文本,可直接点击跳转
    在CENTOS 8上安装FFMPEG(视频处理)
    进程调度例题解析
    localForage封装代码
    网络安全(黑客)自学
    jenkies构建springboot
  • 原文地址:https://blog.csdn.net/weixin_42356271/article/details/126125853