您的位置:首页 > 其它

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

2017-09-18 17:53 351 查看

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

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

相关坐标系

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

WGS84:为一种大地坐标系,也是目前广泛使用的GPS全球卫星定位系统使用的坐标系。

GCJ02:又称火星坐标系,是由中国国家测绘局制订的地理信息系统的坐标系统。由WGS84坐标系经加密后的坐标系。

BD09:为百度坐标系,在GCJ02坐标系基础上再次加密。其中bd09ll表示百度经纬度坐标,bd09mc表示百度墨卡托米制坐标。

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

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

总而言之,百度地图对应的是BD09坐标系,我们采集来的WGS84坐标下的数据必须经过加偏才能准确的展示在地图上。

坐标转换

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

分类未认证个人认证企业认证
日配额(次)100,000300,0003000,000
分钟并发量(次/分钟)6,00012,00030,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采集端,定位精度也就是几米,这样完全可以满足需求。同时可以扩大采样间隔来满足不同需求,用户可以自己测试。
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息