利用地图SDK生成火星坐标纠偏数据库

众所周知,我们国内的电子地图如谷歌高德都是采用加密偏移过的火星坐标(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万条记录大概十分钟可以生成完成,大大超出我的意料。

利用地图SDK生成火星坐标纠偏数据库_第1张图片

考虑到实际需要,我生成的是0.025精度的数据,大概500万条数据。直接在模拟器运行,运行完成后,在Finder找到生成的文件,将它拷贝出来导入数据库。

利用地图SDK生成火星坐标纠偏数据库_第2张图片

数据库我用的是ORACLE,用SQL LOADER导入,导入前先删除所有索引约束,扩大表空间限制,每隔5000条COMMIT一次,本机到服务器千兆连接,导入速度大概每秒1500条,也导了大半小时才导完。

导完后建索引。考虑到浮点数索引效率可能不高,我把经纬度转成了整数,如113.05转成113050000,23.05转成23050000,然后建立联合索引。索引建完后一测试,查询一个坐标需要6、7秒,还是太慢。于是把经度和纬度各取6位合并成一个12位唯一主键索引字段,如113.05和23.05合并成113050023050,主键索引建立好后再测试,查询SQL瞬间返回了。

至此纠偏数据库建立完成,可以直接SQL调用查询了。

 

你可能感兴趣的:(利用地图SDK生成火星坐标纠偏数据库)