根据两个点经纬度计算距离 地理空间距离计算及优化( 三 )


E、F 2个点是赤道上的2个点 , 它们的纬度是0 。EF的距离是EF=2*sin(dlon/2)
A、D2个点所在的纬度是lat1 。AD所在纬度的圆平面的半径是cos(lat1) 。从A作一条垂线()到OE为AG , AO是球半径 , 则OG=cos(lat1) , 即A、D所在纬度圆圈的半径(AO`) 。
这时候 , AD的弦长AD= 2*sin(dlon/2)*cos(lat1) , 类似的可以推出CB的长度= CB=2*sin(dlon/2)*cos(lat2)
下面看一下如何求AB的长度 , 回到平面等腰梯形 , 如下图:
(图3)
AH是到CB的垂线() , CH= (CB-AD)/2 。
根据勾股定理( ): 【^2表示2的平方】
AH^2 = AC^2 - CH^2
= AC^2 - (CB-AD)^2/4
HB 的长度是HB=AD+CH = AD+(CB-AD)/2 = (CB+AD)/2 , 根据勾股定理得到:
AB^2 = AH^2 + HB^2
【根据两个点经纬度计算距离地理空间距离计算及优化】= AC^2 - (CB-AD)^2/4 + (CB+AD)^2/4
= AC^2 + CB*AD
根据前面球面上的求经纬距离的方式 , 我们已经得到 AC、AD和CB的长度 , 代入公式得到:
AB^2 = 4*(sin^2(dlat/2) + 4*cos(lat1)*cos(lat2)*sin^2(dlon/2))
假设中间值h 是AB长度一半的平方 , 如下
h = (AB/2)^2
= (sin^2(dlat/2)) + cos(lat1) * cos(lat2) * sin^2(dlon/2)
(请参看代码里的h)
最后一步 , 是求得代表AB长度的角度AOB 。参照图1的方式 , 我们可以知道
(图4)
设AC=
 , 根据勾股定理( )得到:
OC=
= sqrt(OA^2 - AC^2)
=
= sqrt(1-a) // sqrt表示开根号
如果设c是角AOB的度数值 。
tan(
则:
c = 2 * (sqrt(a)/sqrt(1-a)) , 
最后的AB真实距离 , 把地球半径带上就可以了 。
= 2 ** c 。
2)另外一种方法:
SQL 本身是支持空间数据索引的( ) , 具有空间数据计算能力 。
他是通过一个扩展DLL ..Types.dll 来实现这些功能的 。这是一个托管DLL , 那意味着.NET C#也可以使用些功能 。
例如通过引用: ..Types.dll 这个dll 。
var a = SqlGeography.Point(22.54587746 , 114.12873077, 4326); //上海的某个点var b = SqlGeography.Point(23, 115, 4326); //上海的某个点,4236代表WGS84这种坐标参照系统 。Console.WriteLine(a.STDistance(b)); //距离
这个算出来的距离 , 与上面使用公式算出的距离 , 误差在几米之内 。
参考资料:
目前北京地区在线服务有5w个POI , 计算一遍距离需要7ms 。现在数据增长特别快 , 未来北京地区POI数目增大到100w时 , 我们筛选服务仅计算距离这一项就需要消耗144多ms , 性能十分堪忧 。(注:本文测试环境的处理器为2.9GHz Intel Core i7 , 内存为8GB 1600 MHz DDR3 , 操作系统为OS X10.8.3 , 实验在单线程环境下运行)
4.优化方案
通过抓栈我们发现消耗cpu较多的线程很多都在执行距离公式中的三角函数例如atan2等 。因此我们的优化方向很直接---消减甚至消除三角函数 。基于此方向我们尝试了多种方法 , 下文将一一介绍 。
4.1 坐标转换方法
1)基本思路
之前POI保存的是经纬度(lon,lat) , 我们的计算场景是计算用户位置与所有筛选出来的poi的距离 , 这里会涉及到大量三角函数计算 。一种优化思路是POI数据不保存经纬度而保存球面模型下的三维坐标(x,y,z) , 映射方法如下:
x = Math.cos(lat) Math.cos(lon);
y = Math.cos(lat) Math.sin(lon);
z = Math.sin(lat);
那么当我们求夹角AOB时 , 只需要做一次点乘操作 。比如求(lon1,lat1)和 (lon2,lat2)的夹角 , 只需要计算x1x2 + y1y2 + z1*z2 ,  这样避免了大量三角函数的计算 。