商业地图纠偏接口次数限制的处理策略

商业地图纠偏接口次数限制的处理策略

现在的地图服务提供商,都会有自己的坐标系统,这样就存在坐标转换相关的处理,然而有些坐标转换会在服务端有一定的次数限制,不能满足一些应用场景。本文从纠偏出现的缘由出发,以百度地图为例,给出服务端纠偏的一些通用的处理策略。

相关坐标系

目前国内主要有以下三种坐标系:

WGS84:为一种大地坐标系,也是目前广泛使用的GPS全球卫星定位系统使用的坐标系。
GCJ02:又称火星坐标系,是由中国国家测绘局制订的地理信息系统的坐标系统。由WGS84坐标系经加密后的坐标系。
BD09:为百度坐标系,在GCJ02坐标系基础上再次加密。其中bd09ll表示百度经纬度坐标,bd09mc表示百度墨卡托米制坐标。

非中国地区地图,服务坐标统一使用WGS84坐标。

那么为什么会有火星坐标这一说,是因为国家测绘局为了信息安全原因,对WGS84坐标进行加偏的结果,其实我们口口声声所说的纠偏,其实是加偏,国家认为地理信息数据是保密数据,不应该这么容易就被获取。真正显示在地图服务上的也是经过加偏处理,显示的内容也是经过过滤的。同样的原因,只有一定的测绘资质的公司,才可以有权采集处理地理信息数据。
总而言之,百度地图对应的是BD09坐标系,我们采集来的WGS84坐标下的数据必须经过加偏才能准确的展示在地图上。

坐标转换

百度对于JavaScript和移动端的坐标转换没有次数限制,然而有些场景我们需要在服务端进行坐标转换,然而服务端的接口调用会有次数限制:

分类 未认证 个人认证 企业认证
日配额(次) 100,000 300,000 3000,000
分钟并发量(次/分钟) 6,000 12,000 30,000

这样它并不能满足所有的需求。这样我们可以通过升级为企业认证来解决这个问题,但是对于量的上限还是解决不了问题,或者申请多个用户同样存在瓶颈。难道我们我们要申请个测绘资质,自己搞个地图服务,替换掉百度,成本太高哦。我们可以基于样本采集来解决这个问题。

原理

首先按照一定的间隔调用百度坐标转换接口进行采样,获取采样点的偏移量;然后根据需要纠偏点的坐标,获取该点的周围的采样点,根据采样点计算该点的偏移值,进而获得该点的百度坐标。

采样

前端代码如下

<#import "common/spring.ftl" as spring />
<#assign base=request.contextPath />
<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN" "http://www.w3.org/TR/html4/loose.dtd">
<html xmlns="http://www.w3.org/1999/xhtml">
<head>
    <meta name="viewport" content="initial-scale=1.0, user-scalable=no"/>
    <meta http-equiv="Content-Type" content="text/html; charset=utf-8"/>
    <link rel="stylesheet" href="${base}/css/midlay/v6/midlay_mw_transport.css" type="text/css"/>

    <title>纠偏采集</title>
    <style type="text/css">
        html {
            height: 100%
        }

        body {
            height: 100%;
            margin: 0px;
            padding: 0px
        }
        #map_canvas {
            height: 80%
        }
    </style>
    <script type="text/javascript"
            src="http://api.map.baidu.com/api?v=2.0&ak=U0VvRG8vCFLW3VG49WlNA7GZ"></script>
    <script type="text/javascript" src="${base}/js/jquery/jquery-1.9.1.min.js"></script>
    <script type="text/javascript" src="${base}/js/jquery/jquery.min.js"></script

<#include "script/locale/messages.ftl">
<#include "script/custom/transportGrid.ftl">
<#include "script/layer/layer.ftl">
<#include "midlay/v6/mwtransport/function/tempModel.ftl">

</head>
<body>
<div id="map_canvas">


</div>
选择城市<input value="南京市" id="city"/>
采样间隔<input value="0.001" id="interval"/>度
采集范围:<input value="0" id="startCol"/>列-》<input value="10000" id="endCol"/>
断点处行:<input value="0" id="interruptRow"/>行

<button id="btnPreCollect">计算配置信息</button>
<button id="btnCollect">开始采集</button>
<button id="btnTest">测试纠偏</button>
<div>
   采集信息 <span id="collectInfo"></span>
</div>
<div>
    本次采集信息 <span id="collectInfoSingle"></span>
</div>
<div>
    百分比 <span id="percent"></span>
    个数 <span id="exeNum"></span>
    耗时<span id="consumeTime"></span>
    当前执行<span id="nowExe"></span>
</div>
<div>
    经度范围<input value="" id="startLng"/>-》<input value="" id="endLng"/>
    纬度范围<input value="" id="startLat"/>-》<input value="" id="endLat"/>
    测试个数<input value="1000" id="testNum"/>
</div>

<script type="text/javascript">
    var base='${base}';
    // 百度地图API功能
    var map = new BMap.Map("map_canvas");    // 创建Map实例
    map.addControl(new BMap.MapTypeControl());   //添加地图类型控件
    map.centerAndZoom("南京",15);         // 设置地图显示的城市 此项是必须设置的
    map.enableScrollWheelZoom(true);     //开启鼠标滚轮缩放
    var convertor = new BMap.Convertor();
    var geoc = new BMap.Geocoder();
    var timer;
    var marker;

    var maxLng=-1000;
    var minLng=1000;
    var maxLat=-1000;
    var minLat=1000;
    var interval;
    var sumNum;
    var sumCol;
    var sumRow;
    var prodNum=0;
    var curLng;
    var curLat;
    var startExeTime;


    $("#btnPreCollect").on("click",function () {
        var city=$("#city").val();
        interval=parseFloat($("#interval").val());
        var bdary = new BMap.Boundary();
        bdary.get(city, function(rs){       //获取行政区域
            map.clearOverlays();        //清除地图覆盖物
            var count = rs.boundaries.length; //行政区域的点有多少个
            if (count === 0) {
                alert('未能获取当前输入行政区域');
                return ;
            }
            var pointArray = [];
            for (var i = 0; i < count; i++) {
                var ply = new BMap.Polygon(rs.boundaries[i], {strokeWeight: 2, strokeColor: "#ff0000"}); //建立多边形覆盖物
                map.addOverlay(ply);  //添加覆盖物
                pointArray = pointArray.concat(ply.getPath());
            }
            map.setViewport(pointArray);    //调整视野
            for(var i=0;i<pointArray.length;i++){
                if(pointArray[i].lng>maxLng){
                    maxLng=pointArray[i].lng;
                }
                if(pointArray[i].lng<minLng){
                    minLng=pointArray[i].lng;
                }
                if(pointArray[i].lat>maxLat){
                    maxLat=pointArray[i].lat;
                }
                if(pointArray[i].lat<minLat){
                    minLat=pointArray[i].lat;
                }
            }
            var collectInfo="";
            $("#startLng").val(minLng);
            $("#endLng").val(maxLng);
            $("#startLat").val(minLat);
            $("#endLat").val(maxLat);
            collectInfo+=("经度"+minLng+"->"+maxLng+";")
            collectInfo+=("纬度"+minLat+"->"+maxLat+";")
            sumNum=parseInt((maxLng-minLng)*(maxLat-minLat)/(interval*interval));
            sumCol=parseInt((maxLng-minLng)/interval);
            sumRow=parseInt((maxLat-minLat)/interval);
            collectInfo+=("采集总数目->"+sumNum+";");
            collectInfo+=("采集总列数->"+sumCol+";");
            collectInfo+=("采集总行数->"+sumRow+";");
            $("#collectInfo").text(collectInfo);
            var startCol=parseInt($("#startCol").val());
            var endCol=parseInt($("#endCol").val());
            var interruptRow= parseInt($("#interruptRow").val());
            if(startCol<0){
                startCol=0;
                $("#startCol").val(startCol);
            }
            if(endCol>sumCol){
                endCol=sumCol-1;
                $("#endCol").val(endCol);
            }
            if(interruptRow<0){
                interruptRow=0;
            }
            if(interruptRow>sumRow){
                interruptRow=sumRow-1;
            }
             $("#interruptRow").val(interruptRow);
            maxLng=minLng+endCol*interval+0.00001;
            minLng=minLng+startCol*interval;
            curLat=minLat+interruptRow*interval;
            curLng=minLng;
            collectInfo="本次采集:";
            collectInfo+=("经度"+minLng+"->"+maxLng+";")
            collectInfo+=("纬度"+minLat+"->"+maxLat+";")
            sumNum=(endCol-startCol+1)*sumRow-interruptRow;
            collectInfo+=("总数目->"+sumNum+";");
            $("#collectInfoSingle").text(collectInfo);
            var point = new BMap.Point(curLng, curLat);
            marker=new BMap.Marker(point);  // 创建标注
            map.addOverlay(marker);               // 将标注添加到地图中
            startExeTime=new Date();
        });
    });
    $("#btnCollect").on("click",function () {
        timer=setInterval(fetch,5*1000);
    });

    function fetch() {
        var pts=[];
        for(var i=0;i<10;i++){
            curLat+=interval;
            if(curLat>maxLat){
                curLng+=interval;
                curLat=minLat;
                pts.push(new BMap.Point(curLng, curLat));
            }
            else{
                pts.push(new BMap.Point(curLng, curLat));
            }
        }
        if(curLng>maxLng){
            clearInterval(timer);
        }

        convertor.translate(pts, 1,5 , function (data) {
            if(data.status === 0) {
                var realPts="";
                var baiduPts="";
                for(var i=0;i<pts.length;i++){
                    var curLngBaidu=data.points[i].lng;
                    var curLatBaidu=data.points[i].lat;
                    var realLng=pts[i].lng;
                    var realLat=pts[i].lat;
                    if(realPts==""){
                        realPts=realLng+","+realLat;
                        baiduPts=curLngBaidu+","+curLatBaidu;
                    }else{
                        realPts+=(";"+realLng+","+realLat);
                        baiduPts+=(";"+curLngBaidu+","+curLatBaidu);
                    }

                }
                marker.setPosition(data.points[0]);
                $.ajax({
                    url: base + "/gpsDataDeal/positiondiffbatch",
                    data: {
                        realPts: realPts,
                        baiduPts: baiduPts
                    },
                    type: "post",
                    dataType: "json",
                    async: true,
                    success: function (result) {
                        if(result.succeed){
                            prodNum+=pts.length;
                            $("#percent").text(prodNum/sumNum*100+"%");
                            $("#exeNum").text(prodNum);
                            $("#consumeTime").text(new Date().getTime()-startExeTime.getTime());
                            $("#nowExe").text(curLng+","+curLat);
                        }else{
                        }
                    },
                    error: function (xhr, statusText, errStatus) {
                    }
                });
            }else {

            }
        });
    };


    $("#btnTest").on("click",function (){
        var startLng=$("#startLng").val();
        var endLng=$("#endLng").val();
        var startLat=$("#startLat").val();
        var endLat=$("#endLat").val();
        var testNum=$("#testNum").val();
        $.ajax({
            url: base + "/gpsDataDeal/test",
            data: {
                startLng: startLng,
                endLng:endLng,
                startLat: startLat,
                endLat:endLat,
                testNum:testNum
            },
            type: "post",
            dataType: "json",
            async: true,
            success: function (result) {
            },
            error: function (xhr, statusText, errStatus) {
            }
        });
    })

</script>
</body>
</html>

后端代码如下

 public void upsert(PosDiffGrid posDiffGrid) {
        String id = this.formGridId(posDiffGrid);
        tlCriteria.set(Criteria.where("id").is(id));
        PosDiffGrid oldPosDiffGrid = (PosDiffGrid)this.get();
        if(oldPosDiffGrid == null) {
            posDiffGrid.setId(id);
            posDiffGrid.setTsCreate(new Date());
            posDiffGrid.setTsUpDate(new Date());
            posDiffGrid.setDiffLon(posDiffGrid.getBaiduPoint().getLongitude() - posDiffGrid.getRealPoint().getLongitude());
            posDiffGrid.setDiffLat(posDiffGrid.getBaiduPoint().getLatitude() - posDiffGrid.getRealPoint().getLatitude());
            List<PosDiffGrid> diffs = new ArrayList();
            diffs.add(posDiffGrid);
            this.insertList(diffs);
        } else {
            Update update = new Update();
            update.set("diffLon", Double.valueOf(posDiffGrid.getBaiduPoint().getLongitude() - posDiffGrid.getRealPoint().getLongitude()));
            update.set("diffLat", Double.valueOf(posDiffGrid.getBaiduPoint().getLatitude() - posDiffGrid.getRealPoint().getLatitude()));
            update.set("tsUpdate", new Date());
            update.set("province", posDiffGrid.getProvince());
            update.set("city", posDiffGrid.getCity());
            update.set("district", posDiffGrid.getDistrict());
            update.set("address", posDiffGrid.getAddress());
            tlUpdate.set(update);
            this.update();
        }

    }

纠偏

纠偏代码如下

public PosDiffGrid near(PosDiffGrid posDiffGrid) {
        PosDiffGrid result = new PosDiffGrid();
        Point point = new Point(posDiffGrid.getRealPoint().getLongitude(), posDiffGrid.getRealPoint().getLatitude());
        Criteria criteria = Criteria.where("realPoint").near(point).maxDistance(0.0013498920086393088D);
        tlCriteria.set(criteria);
        List<PosDiffGrid> grids = this.list();
        double minDis = 2000.0D;
        if(CollectionUtils.isEmpty(grids)) {
            return null;
        } else {
            PosDiffGrid st = null;
            Iterator var9 = grids.iterator();

            while(var9.hasNext()) {
                PosDiffGrid pdg = (PosDiffGrid)var9.next();
                double dis = GeoUtil.calcDistance(posDiffGrid.getRealPoint().getLongitude(), posDiffGrid.getRealPoint().getLatitude(), pdg.getRealPoint().getLongitude(), pdg.getRealPoint().getLatitude());
                if(minDis > dis) {
                    minDis = dis;
                    st = pdg;
                }
            }

            if(st == null) {
                return null;
            } else {
                GiPoint baiduPt = new GiPoint();
                baiduPt.setLongitude(posDiffGrid.getRealPoint().getLongitude() + st.getDiffLon());
                baiduPt.setLatitude(posDiffGrid.getRealPoint().getLatitude() + st.getDiffLat());
                result.setRealPoint(posDiffGrid.getRealPoint());
                result.setBaiduPoint(baiduPt);
                result.setProvince(st.getProvince());
                result.setCity(st.getCity());
                result.setDistrict(st.getDistrict());
                result.setAddress(st.getAddress());
                return result;
            }
        }
    }

    public PosDiffGrid distanceRight(PosDiffGrid posDiffGrid, double n) {
        PosDiffGrid result = new PosDiffGrid();
        Point point = new Point(posDiffGrid.getRealPoint().getLongitude(), posDiffGrid.getRealPoint().getLatitude());
        Criteria criteria = Criteria.where("realPoint").near(point).maxDistance(0.0010799136069114472D);
        tlCriteria.set(criteria);
        List<PosDiffGrid> grids = this.list();
        if(CollectionUtils.isEmpty(grids)) {
            return null;
        } else if(grids.size() == 1) {
            result = this.near(posDiffGrid);
            return result;
        } else {
            double minDis = 2000.0D;
            double sumDis = 0.0D;
            PosDiffGrid st = null;
            Iterator var13 = grids.iterator();

            double latDiff;
            while(var13.hasNext()) {
                PosDiffGrid pdg = (PosDiffGrid)var13.next();
                latDiff = GeoUtil.calcDistance(posDiffGrid.getRealPoint().getLongitude(), posDiffGrid.getRealPoint().getLatitude(), pdg.getRealPoint().getLongitude(), pdg.getRealPoint().getLatitude());
                sumDis += Math.pow(latDiff, n);
                if(minDis > latDiff) {
                    minDis = latDiff;
                    st = pdg;
                }
            }

            double lngDiif = 0.0D;
            latDiff = 0.0D;

            PosDiffGrid pdg;
            double dis;
            for(Iterator var17 = grids.iterator(); var17.hasNext(); latDiff += Math.pow(dis, n) / sumDis * pdg.getDiffLat()) {
                pdg = (PosDiffGrid)var17.next();
                dis = GeoUtil.calcDistance(posDiffGrid.getRealPoint().getLongitude(), posDiffGrid.getRealPoint().getLatitude(), pdg.getRealPoint().getLongitude(), pdg.getRealPoint().getLatitude());
                lngDiif += Math.pow(dis, n) / sumDis * pdg.getDiffLon();
            }

            GiPoint baiduPt = new GiPoint();
            baiduPt.setLongitude(posDiffGrid.getRealPoint().getLongitude() + lngDiif);
            baiduPt.setLatitude(posDiffGrid.getRealPoint().getLatitude() + latDiff);
            result.setRealPoint(posDiffGrid.getRealPoint());
            result.setBaiduPoint(baiduPt);
            result.setProvince(st.getProvince());
            result.setCity(st.getCity());
            result.setDistrict(st.getDistrict());
            result.setAddress(st.getAddress());
            return result;
        }
    }

    private String formGridId(PosDiffGrid posDiffGrid) {
        return posDiffGrid.getRealPoint().getLongitude() * 1000.0D + "_" + posDiffGrid.getRealPoint().getLatitude() * 1000.0D;
    }

前端页面
采集前端页面

采集量级及纠偏结果

对于南京这样的城市,采用100m*100m的网格,大概采集了120W条数据。采用最近距离算法误差会在1m以内,然而对于民用的GPS采集端,定位精度也就是几米,这样完全可以满足需求。同时可以扩大采样间隔来满足不同需求,用户可以自己测试。

猜你喜欢

转载自blog.csdn.net/sn_gis/article/details/78021932
今日推荐