众所周知,我们国内的电子地图如谷歌高德都是采用加密偏移过的火星坐标(GCJ-02),但手机GPS获取的却是地球坐标(WGS-84),如何从地球纠偏转到火星,这个问题是中国程序员必须要面对的。一般来说,有以下几种方式:纠偏算法、纠偏接口和纠偏数据库。有些东西其实已经是公开的秘密,因此这里我把生成纠偏数据库的过程写一下,应该也不是什么大问题吧。请注意此文的前提是假设没有纠偏算法,用SDK的纠偏接口生成纠偏库。
纠偏数据可以在网上找,但免费的并不好找,网上能找到的多数不全,或者精度较低;X宝上有人卖纠偏数据库或纠偏接口的,价格也不便宜,这么嚣张的数据一般人也不愿意共享。于是我萌生了自己调纠偏接口生成纠偏数据库的念头。
高德和百度提供了在线纠偏的接口,但毕竟是在线网络请求,坐标多的话纠偏速度较慢。全中国0.01精度的纠偏数据大概有3千万条,以一条40字节算有1.1G大小,压缩后也有一两百M。假设每秒纠10条,3千万条可能需要纠上一个月。一般地图的移动SDK(如高德)也提供了坐标纠偏功能,但多数是转而调用在线纠偏接口,速度慢。但我在使用百度地图SDK时,发现百度地图也有纠偏接口,这个接口在文档上并未公开,但确是存在的,而且它的纠偏是瞬间直接完成的,不需要连网,因此可以确定它内置了纠偏算法,可以利用它来生成纠偏数据库。
百度地图SDK有安卓和iOS版,我选择的是iOS版,原因很简单:iOS支持x86的原生指令模式运行,速度自然比安卓ARM的JAVA虚拟机快得多。代码并不复杂,基本上就是循环遍历中国的所有经纬度范围,进行转换坐标,计算偏移量。百度自己在火星坐标基础上做了个二次加密形成自己的百度坐标系(BD-09),支持从地球坐标(WGS-84)转到百度坐标(BD-09),但它也支持从火星坐标(GCJ-02)转到百度坐标(BD-09),因此我们可以通过两者相减计算出火星坐标(GCJ-02)。
于是乎我写了纠偏库生成程序,支持不同精度类型的纠偏数据生成,主体代码如下:
- (void) doGen{ if(isRunning)//防止重复进入 return; isRunning=YES; //获取文件保存路径,生成文件名 NSArray *documentsPaths=NSSearchPathForDirectoriesInDomains(NSDocumentDirectory , NSUserDomainMask , YES); NSString *fn=[NSString stringWithFormat:@"coordoffset_db_%d_%d.txt",genType,stepDis]; fn=[[documentsPaths objectAtIndex:0] stringByAppendingPathComponent:fn]; NSLog(@"gen filename: %@",fn); //用C函数打开文件,以便写大文件 FILE * hFile = fopen([fn UTF8String],"w+"); if (hFile == NULL) { isRunning=NO; return; } //使用自定义的内存池,定时释放内存,防止大循环中内存不足 NSAutoreleasePool *pool=[[NSAutoreleasePool alloc] init]; curCount=0; int cchCount=5000; CLLocationCoordinate2D coor1,coor2,coor3,coor4; char * buf=(char*)malloc(101*1024); //申请101KB的内存作为写入缓存 int idx=0; for(int ix=startLon;ix<=endLon;ix+=stepDis){ for(int iy=startLat;iy<=endLat;iy+=stepDis){ coor1.longitude=ix/1000000.0; coor1.latitude=iy/1000000.0; if(genType==0){//判断转换类型 //百度,直接转 NSDictionary * dist=BMKBaiduCoorForWgs84(coor1); coor2=BMKCoorDictionaryDecode(dist); } else { //谷歌高德,先WGS转百度,再谷歌高德转百度,然后计算大概偏移,最后重新计算 NSDictionary * dist=BMKBaiduCoorForWgs84(coor1); coor2=BMKCoorDictionaryDecode(dist); dist=BMKBaiduCoorForGcj(coor1); coor3=BMKCoorDictionaryDecode(dist); double dx1=coor3.longitude-coor1.longitude; double dy1=coor3.latitude-coor1.latitude; double dx2=coor2.longitude-coor1.longitude; double dy2=coor2.latitude-coor1.latitude; double dx3=dx2-dx1; double dy3=dy2-dy1; //计算可能最接近原坐标纠偏结果的高德坐标,重新计算偏移 coor3.longitude=coor1.longitude+dx3; coor3.latitude=coor1.latitude+dy3; dist=BMKBaiduCoorForGcj(coor3); coor4=BMKCoorDictionaryDecode(dist); dx1=coor4.longitude-coor3.longitude; dy1=coor4.latitude-coor3.latitude; dx3=dx2-dx1; dy3=dy2-dy1; coor2.longitude=coor1.longitude+dx3; coor2.latitude=coor1.latitude+dy3; } //写入缓冲区 int len=sprintf(&buf[idx],"%0.3f,%0.3f,%0.6f,%0.6f\n",coor1.longitude,coor1.latitude,coor2.longitude-coor1.longitude,coor2.latitude-coor1.latitude); idx+=len; cchCount++; curCount++; if(cchCount>5000||idx>=100*1024||curCount>=recCount){ //每隔5000坐标,或缓冲使用达到100K时,或者达到最后一条记录时,写一次文件 fwrite(buf, idx, 1, hFile); idx=0; cchCount=0; //释放内存池,并重新创建内存池 [pool release]; pool=[[NSAutoreleasePool alloc] init]; //记录当前坐标供刷新进度使用 curLon=ix/1000000.0; curLat=iy/1000000.0; } if(!isRunning) break; } if(!isRunning) break; } free(buf); [pool release]; fclose(hFile); isRunning=NO; }
以上代码在线程中执行,需要注意的是,为了防止大循环中内存池不释放导致内存不足,需要自己建立内存池并定时释放内存;为了支持大文件的写入,需要采用C的文件操作函数代替NSDATA;为了保证写入速度,我在其中使用了写入缓冲,每5000行写入一次。我原以为生成过程需要执行半天,还专门写了定时器刷新进度估计完成时间,结果经过这么优化后,生成速度比较快,3000万条记录大概十分钟可以生成完成,大大超出我的意料。
考虑到实际需要,我生成的是0.025精度的数据,大概500万条数据。直接在模拟器运行,运行完成后,在Finder找到生成的文件,将它拷贝出来导入数据库。
数据库我用的是ORACLE,用SQL LOADER导入,导入前先删除所有索引约束,扩大表空间限制,每隔5000条COMMIT一次,本机到服务器千兆连接,导入速度大概每秒1500条,也导了大半小时才导完。
导完后建索引。考虑到浮点数索引效率可能不高,我把经纬度转成了整数,如113.05转成113050000,23.05转成23050000,然后建立联合索引。索引建完后一测试,查询一个坐标需要6、7秒,还是太慢。于是把经度和纬度各取6位合并成一个12位唯一主键索引字段,如113.05和23.05合并成113050023050,主键索引建立好后再测试,查询SQL瞬间返回了。
至此纠偏数据库建立完成,可以直接SQL调用查询了。