最近的工程涉及到了地理信息,我和大多数程序员一样也遇到了中国特色的火星坐标系的问题。这次研究相应坐标系的问题时发现对于地理坐标的很可能是无意义的。
首先我为什么地理坐标是不可加密的。
以下讨论暂时先脱离地理信息系统,提出以下几个事实或者推论:
1、地理坐标是连续的(准确的说是地理坐标在其取值范围内是连续的)。
2、加密后的地理坐标也是连续的。
3、作为地理坐标的加密函数必须是连续的。(在这里我们假设地理坐标的取值范围和加密后的地理坐标的取值范围相同,如果不同,将加密后的取值范围线性的映射到原地理坐标取值范围,加密函数与映射函数的复合被认为是这里的加密函数)
4、D(加密函数(原地理坐标)-原地理坐标)必须是震荡函数,且范围小。
我们先暂停一下,分析一下上述事实和推论。
第一条是由空间的连续性决定的,是显而易见。
第二条是由地图的特性决定的,也就是说加密后还是一份传统意义上的地图,而不是打乱的拼图。
第三条需要解释一下:
为什么加密函数必须是连续的呢?
学过地理信息系统的都知道,地理信息模型简单的说就是点、线、多边形。
如果函数不连续,对于点是没有影响的,
对于线我们以简单的道路和河流为例,如果函数不连续,那么在函数不连续的地方这类的地理要素就会出现断裂或者错位。
对于多边形以简单的行政区域为例(没有飞地的),如果函数不连续,函数不连续的地方这类的地理要素就会出现飞地或重叠。
第四条是说加密函数的本质是为地理坐标引入误差,这个误差虽然不是常量或者规律性很强,但一定不会太大,且在广域上不会积累。
就上面的四条而言加密函数(x',y')=f(x,y)被破解的意思是什么呢?
是找到f'满足(x,y)=f'(f(x,y));
而只要能够找到f使得f’不存在,则f就可以作为加密函数。这样的函数存在么?举个简单的例子:
f(x,y)=((x+sin(x)+cos(y)),(y+sin(y)+cos(x)));
这个函数就满足上述特征。
从上面的论述看,似乎地理坐标的加密应该是可行的,可是上面的东西过度理想。
以下的讨论我们回到地理信息系统中
5、地理信息系统中的地理坐标是离散的。
6、但凡测量必有误差,还有精度。
7、我们用的是电子计算机而不是生物脑。
8、我们有一门学科叫数值计算。
关于第五条,诸位看官,如果你不明白第五条是什么意思还是不要看下去了,这一条是由数字计算机的基本原理决定的,这一条决定了我们记录的地理坐标精度是有限的。
关于第六条,不知道诸位看官是否和笔者一样初中物理第一课学的是测量,我对这个原理记忆深刻,后半句是我加的,这里的精度是测量工具允许的读数精度。在我的记忆里没有什么工具允许无限精度的读取测量值。这一条决定了我们测量得到的地理坐标精度是有限的。而且和第五条相比,第六条限制的精度更低。一般gps的可信精度到0.000001°就不错了(换算称距离大约是1m),据说有能到0.0000003°的。百度地图sdk给出的精度是前者。
第七条是说我们实际的计算速度很快,有多快,忽略 1+1和sin(1)在计算时间上的差异。
结合前面七条,再加上第8条,就导致加密函数破解的定义发生了改变
对加密函数a'=f(a(x,y))的破解是指找到算法k对于任意已知点A都可以在可以忍受的时间内计算得到 B'=k(A),B=f(B'),使得B与A各维度的差的绝对值小于δ,其中δ一般取第六条所属述之精度。
而数值计算恰好非常擅长解决这类问题:
设:已知加密后的坐标 A,加密函数的实现 x=f(x’),寻找 A' 使 A=f(A').(这个编辑器没提供下标功能,就用[i]代替)。
假设 B’[0]是我们猜测的第一个点,则可以得到
令B[0]=f(B’[0]);
令B'[1]=B’[0]+A-B[0];
令B[1]=f(B’[1]);
令B'[2]=B’[1]+A-B[1];
.
.
.
令B'[i]=B’[i-1]+A-B[i-1];
当i趋向于∞时,B'[i]趋向于A'
那么B’[0]应该如何猜测呢?
在这里我们猜测B’[0]=A。
根据我的试验,对于gcj02->wgs84,大约i=3时,精度就到了0.000001°。
具体的代码
public class EvilTransform { final static double pi = 3.14159265358979324; // // // a = 6378245.0, 1/f = 298.3 // b = a * (1 - f) // ee = (a^2 - b^2) / a^2; final static double a = 6378245.0; final static double ee = 0.00669342162296594323; // // World Geodetic System ==> Mars Geodetic System public static double[] transform(double wgLat, double wgLon) { double mgLat=0; double mgLon=0; if (outOfChina(wgLat, wgLon)) { mgLat = wgLat; mgLon = wgLon; }else{ double dLat = transformLat(wgLon - 105.0, wgLat - 35.0); double dLon = transformLon(wgLon - 105.0, wgLat - 35.0); double radLat = wgLat / 180.0 * pi; double magic = Math.sin(radLat); magic = 1 - ee * magic * magic; double sqrtMagic = Math.sqrt(magic); dLat = (dLat * 180.0) / ((a * (1 - ee)) / (magic * sqrtMagic) * pi); dLon = (dLon * 180.0) / (a / sqrtMagic * Math.cos(radLat) * pi); mgLat = wgLat + dLat; mgLon = wgLon + dLon; } double[] point={mgLat,mgLon}; return point; } private static boolean outOfChina(double lat, double lon) { if (lon < 72.004 || lon > 137.8347) return true; if (lat < 0.8293 || lat > 55.8271) return true; return false; } private static double transformLat(double x, double y) { double ret = -100.0 + 2.0 * x + 3.0 * y + 0.2 * y * y + 0.1 * x * y + 0.2 * Math.sqrt(Math.abs(x)); ret += (20.0 * Math.sin(6.0 * x * pi) + 20.0 * Math.sin(2.0 * x * pi)) * 2.0 / 3.0; ret += (20.0 * Math.sin(y * pi) + 40.0 * Math.sin(y / 3.0 * pi)) * 2.0 / 3.0; ret += (160.0 * Math.sin(y / 12.0 * pi) + 320 * Math.sin(y * pi / 30.0)) * 2.0 / 3.0; return ret; } private static double transformLon(double x, double y) { double ret = 300.0 + x + 2.0 * y + 0.1 * x * x + 0.1 * x * y + 0.1 * Math.sqrt(Math.abs(x)); ret += (20.0 * Math.sin(6.0 * x * pi) + 20.0 * Math.sin(2.0 * x * pi)) * 2.0 / 3.0; ret += (20.0 * Math.sin(x * pi) + 40.0 * Math.sin(x / 3.0 * pi)) * 2.0 / 3.0; ret += (150.0 * Math.sin(x / 12.0 * pi) + 300.0 * Math.sin(x / 30.0 * pi)) * 2.0 / 3.0; return ret; } }
这段代码据说是gcj02的java实现,但不知道是否可信,就这段代码而言,转wgs84的方法如下
public static double[] toWGS84(double gcjLat, double gcjLon){ double[] wgs_point=toWGS84(gcjLat,gcjLon,gcjLat,gcjLon); for(int i = 0;i<10;++i){ wgs_point=toWGS84(wgs_point[0],wgs_point[1],gcjLat,gcjLon); } return wgs_point; } public static double[] toWGS84(double wgsLat, double wgsLon, double gcjLat, double gcjLon) { double[] ng_point=transform(wgsLat, wgsLon); double[] real_point={gcjLat-ng_point[0]+wgsLat,gcjLon-ng_point[1]+wgsLon}; return real_point; }
在这里,我固定转换11次,没检测精度,因为这个过程实在是太快了,没有太大的必要优化。
以下是百度wgs84转dbII09和gcj02转dbII09的代码
// 将google地图、soso地图、aliyun地图、mapabc地图和amap地图 // 所用坐标转换成百度坐标 CoordinateConverter converter = new CoordinateConverter(); converter.from(CoordType.COMMON); // sourceLatLng待转换坐标 converter.coord(sourceLatLng); LatLng desLatLng = converter.convert(); // 将GPS设备采集的原始GPS坐标转换成百度坐标 CoordinateConverter converter = new CoordinateConverter(); converter.from(CoordType.GPS); // sourceLatLng待转换坐标 converter.coord(sourceLatLng); LatLng desLatLng = converter.convert();
所以dbII09转wgs84的代码应该是这样的
public static LatLng toWGS84(LatLng dbLatLng){ LatLng wgsLatLng=toWGS84(dbLatLng,dbLatLng) for(int i=0;i<10:++i){ LatLng wgsLatLng=toWGS84(wgsLatLng,dbLatLng) } return wgsLatLng; } public static LatLng toWGS84(LatLng wgsLatLng, LatLng dbLatLng) { LatLng mDbLatLng=toDBII02(wgsLatLng); LatLng wgsLatLng=new LatLng(); wgsLatLng2.latitude=wgsLatLng.latitude+dbLatLng.latitude-mDbLatLng.latitude; wgsLatLng2.longitude=wgsLatLng.longitude+dbLatLng.longitude-mDbLatLng.longitude; return wgsLatLng2; } public static LatLng toDBII02(LatLng sourceLatLng){ CoordinateConverter converter = new CoordinateConverter(); converter.from(CoordType.GPS); converter.coord(sourceLatLng); LatLng desLatLng = converter.convert(); return desLatLng; }
考虑百度sdk实现的gcj02转dbII09的可信度比较高,可以考虑先将gcj02转成dbII09再将dbII09转成wgs84,这里就不单独写代码了。