Elasticsearch 在地理信息空间索引的探索和演进

本文介绍了Elasticsearch在处理地理位置信息方面的演进,从2.0版本的简单实现到5.0版本的BKD树优化。文章详细阐述了从暴力算法到使用Geohash、Quadtree、BKD tree等数据结构的过程,以及如何通过这些技术实现毫秒级的地理距离查询。随着版本升级,Elasticsearch在减少资源消耗的同时提高了搜索和写入效率。
摘要由CSDN通过智能技术生成

🚀 优质资源分享 🚀

学习路线指引(点击解锁) 知识定位 人群定位
🧡 Python实战微信订餐小程序 🧡 进阶级 本课程是python flask+微信小程序的完美结合,从项目搭建到腾讯云部署上线,打造一个全栈订餐系统。
💛Python量化交易实战💛 入门级 手把手带你打造一个易扩展、更安全、效率更高的量化交易系统

vivo 互联网服务器团队- Shuai Guangying

本文梳理了Elasticsearch对于数值索引实现方案的升级和优化思考,从2015年至今数值索引的方案经历了多个版本的迭代,实现思路从最初的字符串模拟到KD-Tree,技术越来越复杂,能力越来越强大,应用场景也越来越丰富。从地理位置信息建模到多维坐标,数据检索到数据分析洞察都可以看到Elasticsearch的身影。

一、业务背景

LBS服务是当前互联网重要的一环,涉及餐饮、娱乐、打车、零售等场景。在这些场景中,有很重要的一项基础能力:搜索附近的POI。比如搜索附近的美食,搜索附近的电影院,搜索附近的专车,搜索附近的门店。例如:以某个坐标点为中心查询出1km半径范围的POI坐标,如下图所示:

图片

Elasticsearch在地理位置信息检索上具备了毫秒级响应的能力,而毫秒级响应对于用户体验至关重要。上面的问题使用Elasticsearch,只需用到geo_distance查询就可以解决业务问题。使用Elasticsearch的查询语法如下:

GET /my_locations/_search
{
  "query": {
    "bool": {
      "must": {
        "match\_all": {}
      },
      "filter": {
        "geo\_distance": {
          "distance": "1km",
          "pin.location": {
            "lat": 40,
            "lon": 116
          }
        }
      }
    }
  }
}

工具的使用是一个非常简单的事情,更有意思在于工具解决问题背后的思想。理解了处理问题的思想,就可以超然于工具本身,做到举一反三。本文基于在海量数据背景下,如何实现毫秒级搜索附近的POI这个问题,探讨了Elasticsearch的实现方案,以及实现地理位置索引技术的演进过程。

二、背景知识

在介绍Elasticsearch的处理方案前,我们首先需要介绍一些背景知识,主要是3个问题。

  1. 如何精确定位一个地址?

由经度、纬度和相对高度组成的地理坐标系,能够明确标示出地球上的任何一个位置。地球上的经度范围[-180, 180],纬度范围[-90,90]。通常以本初子午线(经度为0)、赤道(纬度为0)为分界线。对于大多数业务场景,由经纬度组成的二维坐标已经足以应对业务问题,可能重庆山城会有些例外。

  1. 如何计算两个地址距离?

对于平面坐标系,由勾股定理可以方便计算出两个点的距离。但是由于地球是一个不完美球体,且不同位置有不同海拔高度,所以精确计算两个距离位置是一个非常复杂的问题。在不考虑高度的情况下,二维坐标距离通常使用Haversine公式。

这个公式非常简单,只需用到arcsin和cos两个高中数学公式。其中φ和λ表示两个点纬度和经度的弧度制度量。其中d即为所求两个点的距离,对应的数学公式如下(参考维基百科):

图片

程序员更喜欢看代码,对照代码理解公式更简单。相应的代码如下:

// 代码摘自lucene-core-8.2.0, SloppyMath工具类
 
 /**
 * Returns the Haversine distance in meters between two points
 * given the previous result from {@link #haversinSortKey(double, double, double, double)}
 * @return distance in meters.
 */
 public static double haversinMeters(double sortKey) {
   return TO_METERS * 2 * asin(Math.min(1, Math.sqrt(sortKey * 0.5)));
 }
 
 /**
 * Returns a sort key for distance. This is less expensive to compute than
 * {@link #haversinMeters(double, double, double, double)}, but it always compares the same.
 * This can be converted into an actual distance with {@link #haversinMeters(double)}, which
 * effectively does the second half of the computation.
 */
 public static double haversinSortKey(double lat1, double lon1, double lat2, double lon2) {
   double x1 = lat1 * TO_RADIANS;
   double x2 = lat2 * TO_RADIANS;
   double h1 = 1 - cos(x1 - x2);
   double h2 = 1 - cos((lon1 - lon2) * TO_RADIANS);
   double h = h1 + cos(x1) * cos(x2) * h2;
   // clobber crazy precision so subsequent rounding does not create ties.
   return Double.longBitsToDouble(Double.doubleToRawLongBits(h) & 0xFFFFFFFFFFFFFFF8L);
 }
 // haversin
 // TODO: remove these for java 9, they fixed Math.toDegrees()/toRadians() to work just like this.
 public static final double TO\_RADIANS = Math.PI / 180D;
 public static final double TO\_DEGREES = 180D / Math.PI;
 
 // Earth's mean radius, in meters and kilometers; see http://earth-info.nga.mil/GandG/publications/tr8350.2/wgs84fin.pdf
 private static final double TO\_METERS = 6\_371\_008.7714D; // equatorial radius
 private static final double TO\_KILOMETERS = 6\_371.0087714D; // equatorial radius
 
/**
 * Returns the Haversine distance in meters between two points
 * specified in decimal degrees (latitude/longitude). This works correctly
 * even if the dateline is between the two points.
 * 
 * Error is at most 4E-1 (40cm) from the actual haversine distance, but is typically
 * much smaller for reasonable distances: around 1E-5 (0.01mm) for distances less than
 * 1000km.
 *
 * @param lat1 Latitude of the first point.
 * @param lon1 Longitude of the first point.
 * @param lat2 Latitude of the second point.
 * @param lon2 Longitude of the second point.
 * @return distance in meters.
 */


 public static double haversinMeters(double lat1, double lon1, double lat2, double lon2) {
   return
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

[虚幻私塾】

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值