`
usenrong
  • 浏览: 515018 次
  • 性别: Icon_minigender_1
  • 来自: 南京
社区版块
存档分类
最新评论

spatial4j中Law of Cosines距离函数的使用优化

 
阅读更多
最近用了下https://github.com/spatial4j/spatial4j spatial4j 做地理位置的搜索。

其中给定两个点的经纬度计算距离的公式有3种:

Haversine  http://en.wikipedia.org/wiki/Haversine_formula

Law of Cosines(余弦定理) http://en.wikipedia.org/wiki/Spherical_law_of_cosines

Vincenty http://en.wikipedia.org/wiki/Vincenty's_formulae 

spatial4j 默认使用的是Haversine

其中根据http://stackoverflow.com/questions/2096385/formulas-to-calculate-geo-proximity/  的讨论

从速度上讲: Law of Cosines > Haversine > Vincenty

然后我就用了 Law of Cosines,结果发现比另两个都慢。。。

这不科学啊,然后我就开始看code: https://github.com/spatial4j/spatial4j/blob/master/src/main/java/com/spatial4j/core/distance/DistanceUtils.java

里面的3个函数distHaversineRAD, distLawOfCosinesRAD 和 distVincentyRAD

给定的地球表面的两点,这三个函数都是计算球心到这两点的向量的夹角,有了这个夹角的话乘以地球半径就是距离。

把里面的3个函数拿出来跑测试,用同一组100万中国经纬度范围longitude (73, 135) latitude (3, 53)

的随机数据在我的MacBookPro测试一下:

distHaversineRAD cost: 193.561ms

distLawOfCosinesRAD cost: 407.285ms

distVincentyRAD cost: 264.404ms



distLawOfCosinesRAD 果然最慢的啊,仔细看了下这个函数。

  public static double distLawOfCosinesRAD(double lat1, double lon1, double lat2, double lon2) {

    // Check for same position

    if (lat1 == lat2 && lon1 == lon2)

      return 0.0;

    // Get the m_dLongitude difference. Don't need to worry about

    // crossing 180 since cos(x) = cos(-x)

    double dLon = lon2 - lon1;

    double a = DEG_90_AS_RADS - lat1;

    double c = DEG_90_AS_RADS - lat2;

    double cosB = (Math.cos(a) * Math.cos(c))

        + (Math.sin(a) * Math.sin(c) * Math.cos(dLon));

    // Find angle subtended (with some bounds checking) in radians

    if (cosB < -1.0)

      return Math.PI;

    else if (cosB >= 1.0)

      return 0;

    else

      return Math.acos(cosB);

  }



优化1:

直觉告诉我acos这个计算反余弦的肯定最耗时。其实在搜索中使用距离函数最多的场景是做Filter用,比如给定中心点的经纬度搜索10公里范围内的人。

假设中心点经纬度是(lon1, lat1),距离d,地球半径为r,要判断的点是(lon2, lat2)

spatial4j中判断是否在范围内的做法:distLawOfCosinesRAD(lat1, lon1, lat2,  lon2)  < d/r

注意到Cosine在[0, PI]之间是递减的,任意两个向量的夹角也是在[0, PI]之间,所以如果判断距离是不是在范围内,可以不计算出来角度, 只计算出Cosine即可。

Math.cos(distLawOfCosinesRAD(lat1, lon1, lat2,  lon2))  > Math.cos(d/r)

其中Math.cos(d/r)只需要计算一次,存储下来。

这样做距离Filter用的时候,distLawOfCosinesRAD 变成了这个样子

public static double distLawOfCosinesRAD(double lat1, double lon1, double lat2, double lon2) {

    if (lat1 == lat2 && lon1 == lon2) return 0.0;

    // Get the m_dLongitude difference. Don't need to worry about

    // crossing 180 since cos(x) = cos(-x)

    double dLon = lon2 - lon1;

    double a = DEG_90_AS_RADS - lat1;

    double c = DEG_90_AS_RADS - lat2;

    double cosB = (Math.cos(a) * Math.cos(c)) + (Math.sin(a) * Math.sin(c) * Math.cos(dLon));

    return cosB;

  }

distLawOfCosinesRAD cost: 79.048ms

耗时变成了原来的1/5。



优化2:

又看了一下这个函数,作为处女座的人完全无法接收这两行啊:

    double a = DEG_90_AS_RADS - lat1;

    double c = DEG_90_AS_RADS - lat2;

其中 DEG_90_AS_RADS = Math.PI / 2;

显然Math.cos(a) = Math.sin(Math.PI / 2 - a) 所以干掉这两行后,distLawOfCosinesRAD 变成了这个样子

  public static double distLawOfCosinesRAD(double lat1, double lon1, double lat2, double lon2) {

    // Check for same position

    if (lat1 == lat2 && lon1 == lon2) return 0.0;

    // Get the m_dLongitude difference. Don't need to worry about

    // crossing 180 since cos(x) = cos(-x)

    double dLon = lon2 - lon1;

    double cosB = (Math.sin(lat1) * Math.sin(lat2)) + (Math.cos(lat1) * Math.cos(lat2) * Math.cos(dLon));

    return cosB;

  }

distLawOfCosinesRAD cost: 36.258ms

又减少了一半。这个下降的幅度显然不合理,因为只是少做了两次double减法。感觉这个提高是和数据分布有关系,在中国的经纬度范围内效果很好,其实在美国的经纬度范围longitude (-75,-120) latitude (30, 50) 效果也很好。



优化3:

一次搜索中,中心点的经纬度(lon1, lat1)是不变的,但是每次计算距离我们都要计算Math.sin(lat1)  和 Math.cos(lat1), 如果把Math.sin(lat1)  和 Math.cos(lat1) 只计算一次每次计算距离时作为参数直接传入,那么会各减少一次Math.sin 和  Math.cos 计算。

distLawOfCosinesRAD cost: 24.356ms

减少了10多ms,这个优化效果不明显了。



如果还要继续优化:

假设每次搜索过程中需要和中心点(lon1, lat1)比较的点(lon2, lat2)的集合只取决于索引(比如每个用户的经纬度),那么我们在建索引时可以将经纬度(lon, lat)

转为空间坐标系(x, y, z)比如可以这样映射:

x = Math.cos(lat) * Math.cos(lon);

y = Math.cos(lat) * Math.sin(lon);

z = Math.sin(lat);

然后把(x, y, z)建入索引。

那么当我们求向量夹角时,只需要做一次点乘操作。比如求(lon1, lat1)和 (lon2, lat2)的夹角,只需要计算

x1*x2 + y1*y2 + z1*z2 这样避免了做任何三角函数换算这个效率是最高的,做100万次这样的操作基本在10ms以内。

Bobo的GeoFacetFilter就是这样做的。https://github.com/senseidb/bobo/blob/master/bobo-browse/src/main/java/com/browseengine/bobo/facets/filter/GeoFacetFilter.java

其实

x1*x2 + y1*y2 + z1*z2

= Math.cos(lat1) *Math.cos(lon1)*Math.cos(lat2) * Math.cos(lon2)  +
Math.cos(lat1) *Math.sin(lon1) * Math.cos(lat2)*Math.sin(lon2) +
Math.sin(lat1)*Math.sin(lat2)

= Math.cos(lat1)*Math.cos(lat2)*(Math.cos(lon1) * Math.cos(lon2) + Math.sin(lon1)*Math.sin(lon2)) + Math.sin(lat1)*Math.sin(lat2)

= Math.cos(lat1)*Math.cos(lat2)*Math.cos(lon1-lon2) + Math.sin(lat1)*Math.sin(lat2)



这个结果和distLawOfCosinesRAD完全等价。



由于计算距离的耗时只是整个搜索耗时中的一部分,所以第一个优化在线上体现最明显,第二个不太明显,第三个基本属于过度优化了。
分享到:
评论

相关推荐

    spatial4j-0.6-API文档-中文版.zip

    赠送jar包:spatial4j-0.6.jar; 赠送原API文档:spatial4j-0.6-javadoc.jar; 赠送源代码:spatial4j-0.6-sources.jar; 赠送Maven依赖信息文件:spatial4j-0.6.pom; 包含翻译后的API文档:spatial4j-0.6-javadoc-...

    spatial4j-0.6-API文档-中英对照版.zip

    赠送jar包:spatial4j-0.6.jar; 赠送原API文档:spatial4j-0.6-javadoc.jar; 赠送源代码:spatial4j-0.6-sources.jar; 赠送Maven依赖信息文件:spatial4j-0.6.pom; 包含翻译后的API文档:spatial4j-0.6-javadoc-...

    spatial4j.jar spatial4j-0.8.jar

    spatial4j官方发布以及maven发布的版本都是基于jdk1.7编译的,碰到jdk1.6的项目会报unsupported major.minor version 51.0错误。这个资源是我基于jdk1.6编译的,执行测试案例都通过了。

    spatial4j-0.4.1.jar

    spatial4j-0.4.1.jar

    spatial4j:LocationTech Spatial4j

    如果您正在使用空间网格平方索引方案(无论是还是自定义的东西),那么您很可能会从Spatial4j中找到特别有用的工具。 Spatial4j经过了良好的测试; 通过持续集成(以及另一个Hudson构建)对其进行监视,我们使用...

    spatial4j, Java的地理空间库.zip

    spatial4j, Java的地理空间库 Spatial4j ( 注:spatial4j主页的官方版本位于 LocationTech: 但是,这个自述文件包含了更丰富的信息。Spatial4j是一个通用的空间/地理空间 ASL 许可的开放源代码Java库。 它的核

    使用Oracle Spatial对ArcSDE中的SDO_GEOMETRY类型数据进行空间操作

    "使用Oracle Spatial对ArcSDE中的SDO_GEOMETRY类型数据进行空间操作" Oracle Spatial 是 Oracle 数据库中的一个空间数据处理组件,用于存储、管理和操作空间数据。ArcSDE 是一个空间数据引擎,用于存储和管理大规模...

    Oracle_Spatial_的空间查询处理机制分析及优化

    原理部分会深入探讨Oracle Spatial如何使用空间索引、空间函数和空间操作符来提升查询效率,而技术支持部分则会涉及具体的优化策略,如如何选择合适的索引类型、如何构建和维护空间索引、以及如何利用Oracle提供的...

    handbook of spatial logics

    - **标题**:《空间逻辑手册》(Handbook of Spatial Logics) - **编辑**:Marco Aiello(格罗宁根大学)、Ian Pratt-Hartmann(曼彻斯特大学)与Johan van Benthem(阿姆斯特丹大学) - **出版社**:Springer - **...

    Hibernate Spatial 4 教程

    为了使用 Hibernate Spatial 4,需要在classpath中添加以下库:hibernate、JDBC 驱动程序和 hibernate-spatial.jar,以及所有 transitive 依赖项。 二、 配置 Hibernate Spatial 要使用 Hibernate Spatial,需要在...

    mysql函数-根据经纬度坐标计算距离

    为了优化性能,可以考虑使用空间索引,如MySQL的`SPATIAL`索引,或者将距离计算移到应用程序层。 总的来说,通过理解和应用哈弗辛公式,以及创建相应的MySQL函数,我们可以有效地在数据库中处理基于经纬度的地理...

    Handbook of Applied Spatial Analysis

    part 2 共两部分 Handbook of Applied Spatial Analysis Software Tools, Methods and Applications

    oracle.spatial的jar包

    开发者可以使用 SDO API(Spatial Data Object API)来执行复杂的空间查询,例如找出距离某个点最近的设施,或者找出跨越特定区域的对象。 `sdotype.jar`:这个库包含了 Oracle Spatial 的数据类型定义,比如几何...

    Oracle Spatial OCI源码

    Oracle Spatial OCI源码是Oracle数据库中用于处理空间数据的一个组件,它是Oracle Call Interface(OCI)的扩展,专门设计用于在Oracle Spatial中执行高效的空间查询和操作。OCI是Oracle数据库提供的一种C语言编程接口...

    SqlServer spatial示例

    在本示例中,我们将探讨如何使用特定工具将 ShapeFile 数据导入到 SqlServer spatial 中。 ShapeFile 是一种常见的矢量地理数据格式,通常由 .shp(形状文件)、.dbf(属性数据)和 .shx(索引文件)等几个文件组成...

    Oracle Spatial与ArcGIS连接

    许多组织正在转向使用Oracle Spatial作为其核心数据库系统,这主要是因为Oracle Spatial能够提供强大的空间数据存储与处理能力。在这一背景下,如何实现ArcGIS与Oracle Spatial的有效集成成为了一个重要的课题。本文...

    Oracle Spatial9i介绍

    除了基本的数据存储之外,Oracle Spatial还提供了丰富的函数和操作符,可以用于执行空间数据之间的各种运算,如距离计算、空间关系判断等。这些操作使得开发者可以在SQL语句中直接处理空间数据,而无需编写额外的...

    Beyond Bags of Features: Spatial Pyramid Matching for Recognizing Natural Scene Categories

    ### Beyond Bags of Features: Spatial Pyramid Matching for Recognizing Natural Scene Categories #### 概述 本文介绍了一种基于近似全局几何对应的方法来识别自然场景类别。该技术通过将图像分割成越来越精细的...

    Oracle Spatial 官方文档 pdf

    Oracle Spatial 是 Oracle 数据库的一个重要组件,专门用于处理地理空间数据和执行空间分析。这个官方文档是英文版,对于非英语母语者来说可能阅读起来有些困难,但它是理解 Oracle Spatial 功能和操作的关键资源。...

Global site tag (gtag.js) - Google Analytics