精品欧美一区二区三区在线观看 _久久久久国色av免费观看性色_国产精品久久在线观看_亚洲第一综合网站_91精品又粗又猛又爽_小泽玛利亚一区二区免费_91亚洲精品国偷拍自产在线观看 _久久精品视频在线播放_美女精品久久久_欧美日韩国产成人在线

Elasticsearch 在地理信息空間索引的探索和演進

開發
本文梳理了Elasticsearch對于數值索引實現方案的升級和優化思考,從2015年至今數值索引的方案經歷了多個版本的迭代,實現思路從最初的字符串模擬到KD-Tree,技術越來越復雜,能力越來越強大,應用場景也越來越豐富。從地理位置信息建模到多維坐標,數據檢索到數據分析洞察都可以看到Elasticsearch的身影。

作者 | 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)為分界線。對于大多數業務場景,由經緯度組成的二維坐標已經足以應對業務問題,可能重慶山城會有些例外。

2. 如何計算兩個地址距離?

對于平面坐標系,由勾股定理可以方便計算出兩個點的距離。但是由于地球是一個不完美球體,且不同位置有不同海拔高度,所以精確計算兩個距離位置是一個非常復雜的問題。在不考慮高度的情況下,二維坐標距離通常使用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.
* <p>
* 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 haversinMeters(haversinSortKey(lat1, lon1, lat2, lon2));
}

3. 如何方便在互聯網分享經緯度坐標?

Geohash是2008-02-26由Gustavo Niemeyer在自己的個人博客上公布的算法服務。其初衷在于通過對經緯度的編碼對外提供簡短的URL標識地圖位置,方便在電子郵件、論壇和網站中使用。

實際上Geohash的價值不僅僅是提供簡短的URL,它更大的價值在于:

  1. Geohash給地圖上每個坐標提供了獨一無二的ID,這個唯一ID就相當于給每個地理位置提供了一個身份證。唯一ID在數據庫中應用場景非常豐富。
  2. 在數據庫中給坐標點提供了另一種存儲方式,將二維的坐標點轉化成為一維的字符串,對于一維數據就可以借助B樹等索引來加速查詢。
  3. Geohash是一種前綴編碼,位置相近的坐標點前綴相同。通過前綴提供了高性能的鄰近位置POI查詢,而鄰近位置POI查詢是LBS服務的核心能力。

關于Geohash的編碼規則,這里不展開。這里最關鍵的點在于:

  • Geohash是一種前綴編碼,位置相近的坐標點前綴相同。Geohash編碼長度不同,所覆蓋區域范圍不同。

在前面知識的鋪墊下,最簡單的求一個坐標點指定半徑范圍內的坐標集合的方案就出爐了。

暴力算法

中心坐標點依次跟集合中每個坐標點計算距離,篩選出符合半徑條件的坐標點。

這個算法大家太熟悉了,就是最常見的暴力(Brute Force)算法。這個算法在海量數據背景下是沒法滿足毫秒級響應時間要求的,所以多用于離線計算。對于毫秒級響應的業務訴求,這個算法可以基于geohash進行改造。

二次篩選

  1. 基于坐標中心點計算出geohash, 基于半徑確定geohash前綴。
  2. 通過Geohash前綴初篩出大致符合要求的坐標點(需要將中心點所在Geohash塊周圍8個Geohash塊納入初篩范圍)。
  3. 對于初篩結果使用Haversine公式進行二次篩選。

除了上述方案,Elasticsearch在地理信息處理上有哪些奇思妙想呢?

三、方案演進

Elasticsearch從2.0版本開始支持geo_distance查詢,到當前已更新到7.14版本。

從2015年至今已經經歷了6年的發展, 建設了如下的能力:

圖片

技術迭代大致可以分為3個階段:

圖片

發展的成效顯著,從性能測試的結果可以略窺一二:

圖片

圖片

圖片

總的來說,資源消耗降低的前提下搜索和寫入數據效率都有大幅度提升。下面就詳細介紹Elasticsearch對地理信息索引的思路。

3.1 史前時代

Elasticsearch是基于Lucene構建的搜索引擎。Lucene最開始的設想是一個全文檢索工具箱,即支持字符串檢索,并沒有考慮數值類型的處理。其核心思想非常簡單,將文檔分詞后,為每個詞構建一個term =>  array[docIds]的映射。

這樣用戶輸入關鍵詞只需要三步就可以獲得想要的結果:

第一步: 通過關鍵詞找到對應的倒排表。這一步簡單來說就是查詞典。例如:TermQuery.TermWeight 獲取該term的倒排表,讀取docId+freq信息。

第二步: 根據倒排表得到的docId和詞頻信息對文檔進行打分,返回給用戶分值最高的TopN結果。例如:TopScoreDocCollector -- collect()方法,基于小頂堆,保留分數最大的TopN文檔。

第三步: 基于docId查詢正排表獲取文檔字段明細信息。

這三步看起來簡單,但簡直是數據結構應用最佳戰場,它需要綜合考慮磁盤、內存、IO、數據結構時間復雜度,非常具有挑戰性。

例如:查詞典可以用很多數據結構實現,比如跳躍表,平衡樹、HashMap等,而Lucene的核心工程師Mike McCandless實現了一個只有他自己能懂的FST,  是綜合了有限自動機和前綴樹的一種數據結構,用來平衡查詢復雜度和存儲空間,比HashMap慢,但是空間消耗低。文檔打分通常用小頂堆來維護分值最高的N個結果,如果有新的文檔打分超過堆頂,則替換堆頂元素即可。  

問題:對于真實業務場景而言,只有字符串匹配查詢是不夠的,字符串和數值是應用最廣泛的兩種數據類型。如果需要進行區間查詢怎么辦呢?這是一個數據庫產品非常基礎的能力。

Lucene提供了一種適配方案RangeQuery。就是用枚舉來模擬數值查詢。簡單來說:

RangeQuery=BooleanQuery+TermQuery,所以限制查詢是整數且區間最大不能超過1024。這種實現是可以說是非常雞肋的,好在Lucene 2.9.0版本真正支持數值查詢。

LUCENE-1470,LUCENE-1582,LUCENE-1602,LUCENE-1673,LUCENE-1701, LUCENE-1712

Added NumericRangeQuery and NumericRangeFilter, a fast alternative to RangeQuery/RangeFilter for numeric searches. They depend on a specific structure of terms in the index that can be created by indexing using the new NumericField or NumericTokenStream classes. NumericField can only be used for indexing and optionally stores the values as string representation in the doc store. Documents returned from IndexReader/IndexSearcher will return only the String value using the standard Fieldable interface. NumericFields can be sorted on and loaded into the FieldCache. (Uwe Schindler, Yonik Seeley, Mike McCandless)

這個實現很強大,支持了int/long/float/double/

short/byte,也不限制查詢區間了。它的核心思路是將數值字節數組化,然后利用前綴分層管理區間。

 如下圖所示:

圖片

本質上還是RangeQuery=

BooleanQuery+TermQuery,只不過在前面做了一層轉換:通過前綴樹管理一個區間實現了匹配詞數量的縮減,而這個縮減是非常有效的。所以這里就有一個專家參數:precisionStep。就是用來控制每個數值字段在分詞是生成term的數量,生成term數量越多,區間控制粒度越細,占用磁盤空間越大,查詢效率通常越高。 

例如:如果precisionStep=8,則意味前綴樹葉子節點的上層控制著255個葉子。那么,當查詢范圍在1~511時,由于跨了相鄰的2個非葉子節點,所以需要遍歷511個term。但是假如查詢范圍在0~512,又只需遍歷2個term即可。這樣的實現用起來真的有過山車的感覺。

綜上,Elasticsearch核心的Lucene倒排索引是一種經典的以不變應萬變:字符串和數值索引核心都是查倒排表。理解這個核心,對于后面理解地理位置數據存儲和查詢非常關鍵。接下來我們以geo_distance的實現思路為探索主線條,探索一下ES各個版本的實現思路。

3.2  Elasticsearch 2.0 版本

這個版本實現geo_distance查詢的思路非常樸素,是建立在數值區間查詢(NumericRangeQuery)的基礎上。它的geo_point類型字段其實是一個復合字段,或者說是一個結構體。在底層實現時分別用兩個獨立字段索引來避免暴力掃描。即Elasticsearch的geo_point字段在實現上是lat,lon,加上編碼成的geohash綜合提供檢索聚合功能。

字段定義如下所示:

public static final class GeoPointFieldType extends MappedFieldType {
private MappedFieldType geohashFieldType;
private int geohashPrecision;
private boolean geohashPrefixEnabled;
private MappedFieldType latFieldType;
private MappedFieldType lonFieldType;
public GeoPointFieldType() {}
}

算法的執行分為三個階段:

第一步:根據中心點以及半徑計算出一個大致符合需求的矩形區域,然后利用矩形區域的最小最大經度得到一個數值區間查詢,利用矩形區域的最小最大緯度得到一個區間查詢。

核心代碼如下圖所示:

// 計算經緯度坐標+距離得到的矩形區域
// GeoDistance類
public static DistanceBoundingCheck distanceBoundingCheck(double sourceLatitude, double sourceLongitude, double distance, DistanceUnit unit) {
// angular distance in radians on a great circle
// assume worst-case: use the minor axis
double radDist = unit.toMeters(distance) / GeoUtils.EARTH_SEMI_MINOR_AXIS;
double radLat = Math.toRadians(sourceLatitude);
double radLon = Math.toRadians(sourceLongitude);
double minLat = radLat - radDist;
double maxLat = radLat + radDist;
double minLon, maxLon;
if (minLat > MIN_LAT && maxLat < MAX_LAT) {
double deltaLon = Math.asin(Math.sin(radDist) / Math.cos(radLat));
minLon = radLon - deltaLon;
if (minLon < MIN_LON) minLon += 2d * Math.PI;
maxLon = radLon + deltaLon;
if (maxLon > MAX_LON) maxLon -= 2d * Math.PI;
} else {
// a pole is within the distance
minLat = Math.max(minLat, MIN_LAT);
maxLat = Math.min(maxLat, MAX_LAT);
minLon = MIN_LON;
maxLon = MAX_LON;
}
GeoPoint topLeft = new GeoPoint(Math.toDegrees(maxLat), Math.toDegrees(minLon));
GeoPoint bottomRight = new GeoPoint(Math.toDegrees(minLat), Math.toDegrees(maxLon));
if (minLon > maxLon) {
return new Meridian180DistanceBoundingCheck(topLeft, bottomRight);
}
return new SimpleDistanceBoundingCheck(topLeft, bottomRight);
}

第二步:兩個查詢通過BooleanQuery組合成一個取交集的復合查詢,以實現初篩出在經緯度所示矩形區域內的docId集合。

核心代碼如下圖所示:

public class IndexedGeoBoundingBoxQuery {
public static Query create(GeoPoint topLeft, GeoPoint bottomRight, GeoPointFieldMapper.GeoPointFieldType fieldType) {
if (!fieldType.isLatLonEnabled()) {
throw new IllegalArgumentException("lat/lon is not enabled (indexed) for field [" + fieldType.names().fullName() + "], can't use indexed filter on it");
}
//checks to see if bounding box crosses 180 degrees
if (topLeft.lon() > bottomRight.lon()) {
return westGeoBoundingBoxFilter(topLeft, bottomRight, fieldType);
} else {
return eastGeoBoundingBoxFilter(topLeft, bottomRight, fieldType);
}
}
private static Query westGeoBoundingBoxFilter(GeoPoint topLeft, GeoPoint bottomRight, GeoPointFieldMapper.GeoPointFieldType fieldType) {
BooleanQuery.Builder filter = new BooleanQuery.Builder();
filter.setMinimumNumberShouldMatch(1);
filter.add(fieldType.lonFieldType().rangeQuery(null, bottomRight.lon(), true, true), Occur.SHOULD);
filter.add(fieldType.lonFieldType().rangeQuery(topLeft.lon(), null, true, true), Occur.SHOULD);
filter.add(fieldType.latFieldType().rangeQuery(bottomRight.lat(), topLeft.lat(), true, true), Occur.MUST);
return new ConstantScoreQuery(filter.build());
}
private static Query eastGeoBoundingBoxFilter(GeoPoint topLeft, GeoPoint bottomRight, GeoPointFieldMapper.GeoPointFieldType fieldType) {
BooleanQuery.Builder filter = new BooleanQuery.Builder();
filter.add(fieldType.lonFieldType().rangeQuery(topLeft.lon(), bottomRight.lon(), true, true), Occur.MUST);
filter.add(fieldType.latFieldType().rangeQuery(bottomRight.lat(), topLeft.lat(), true, true), Occur.MUST);
return new ConstantScoreQuery(filter.build());
}
}

第三步:利用FieldData緩存(正向信息)根據docId獲取矩形區域中每個坐標點的經緯度,然后利用前面的Haversine公式計算跟中心坐標點的距離,進行精確篩選,得到符合條件的文檔集合。

核心代碼如下所示:

// GeoDistanceRangeQuery類的實現
@Override
public Weight createWeight(IndexSearcher searcher, boolean needsScores) throws IOException {
final Weight boundingBoxWeight;
if (boundingBoxFilter != null) {
boundingBoxWeight = searcher.createNormalizedWeight(boundingBoxFilter, false);
} else {
boundingBoxWeight = null;
}
return new ConstantScoreWeight(this) {
@Override
public Scorer scorer(LeafReaderContext context) throws IOException {
final DocIdSetIterator approximation;
if (boundingBoxWeight != null) {
approximation = boundingBoxWeight.scorer(context);
} else {
approximation = DocIdSetIterator.all(context.reader().maxDoc());
}
if (approximation == null) {
// if the approximation does not match anything, we're done
return null;
}
final MultiGeoPointValues values = indexFieldData.load(context).getGeoPointValues();
final TwoPhaseIterator twoPhaseIterator = new TwoPhaseIterator(approximation) {
@Override
public boolean matches() throws IOException {
final int doc = approximation.docID();
values.setDocument(doc);
final int length = values.count();
for (int i = 0; i < length; i++) {
GeoPoint point = values.valueAt(i);
if (distanceBoundingCheck.isWithin(point.lat(), point.lon())) {
double d = fixedSourceDistance.calculate(point.lat(), point.lon());
if (d >= inclusiveLowerPoint && d <= inclusiveUpperPoint) {
return true;
}
}
}
return false;
}
};
return new ConstantScoreScorer(this, score(), twoPhaseIterator);
}
};
}

這是一種非常簡單且直觀的思路實現了中心點指定半徑范圍POI的搜索能力。

簡單總結一下要點:

  1. 利用中心點坐標和半徑確定矩形區域邊界。
  2. 利用Bool查詢綜合兩個NumericRangeQuery查詢,實現矩形區域初篩。
  3. 利用Haversine公式計算中心點和矩形區域內每個坐標點距離,進行第二階段過濾操作,篩選出最終符合條件的docId集合。

方案雖然簡單,但是畢竟實現了geo_distance的能力。又不是不能用,對吧?那么該方案有什么問題呢?

3.3  Elasticsearch 2.2 版本

ES2.0版本的實現有個問題, 就是沒有很好解決二維組合條件查詢的數據篩選。它是分別獲取符合緯度范圍條件的文檔集合和符合經度范圍條件的文檔集合然后進行交集,初篩了太多無效的文檔集合。

它的處理思路用一張圖表示如下:

即選擇了那么多的記錄,最終只有經緯度范圍交匯的紅色區域是初篩的范圍。

針對上面的問題,ES 2.2版本引入特性:基于四叉樹(Quadtree)的地理位置查詢(Lucene 5.3版本實現)。 

Quadtree并非什么復雜高深的數據結構,相比二叉樹,多了兩個子節點。

作為一種基礎的數據結構,Quadtree應用場景非常廣泛,在圖像處理、空間索引、碰撞檢測、人生游戲模擬、分形圖像分析等領域都可以看到它的身影。

在Elasticsearch地理位置空間索引問題上,Quadtree用來表示區間,可以視為前綴樹的一種。

Region quadtree

The region quadtree represents a partition of space in two dimensions by decomposing the region into four equal quadrants, subquadrants, and so on with each leaf node containing data corresponding to a specific subregion. Each node in the tree either has exactly four children, or has no children (a leaf node). The height of quadtrees that follow this decomposition strategy (i.e. subdividing subquadrants as long as there is interesting data in the subquadrant for which more refinement is desired) is sensitive to and dependent on the spatial distribution of interesting areas in the space being decomposed. The region quadtree is a type of trie.

在區間劃分上,Quadtree跟geohash的處理思路有些相似。在一維世界,二分可以無限迭代。同理,在二維世界,四分也可以無限迭代。下面這個圖可以非常形象展示Quadtree的區間劃分過程。

圖片

ES 2.2是如何使用Quadtree來實現geo_distance查詢呢?

通常我們使用一種數據結構,是先基于該數據結構存儲數據,然后查詢這個數據結構。ES這里使用Quadtree的做法非常巧妙:存儲的時候沒有感覺用到Quadtree,查詢時卻用其查詢方式。

morton編碼:在理解ES的處理思路前,需要科普一個知識點,那就是morton編碼。關于morton編碼,跟geohash類似,是一種將二維數據按二進制位交叉編碼成一維數據的一種網格編碼,其用法和特點跟geohash也是類似的。對于64位的morton碼,其經緯度定位精度范圍控制到了厘米級別,對于地理位置場景而言,是非常非常高的精度了。

數據存儲:ES2.2版本之前一個經緯度坐標需要三個字段存儲:lat,lon,geohash。有了Quadtree后,只需要一個字段存儲就可以了。具體的實現思路如下:將lat,lon坐標進行映射,使得經緯度的取值范圍從[-180,180]/[-90,90]映射到[0,2147483520](整數好處理), 然后處理成一維的mortonHash數值。對于數值字段的處理思路,就又回到了前綴(trie)的思路,就又回到了熟悉的專家參數precisionStep。在這里的前綴該如何理解?對于一維數據,每個前綴管理一段區間,對于二維數據每個前綴管理一個二維網格區域。例如一個坐標點利用precisionStep=9來劃分前綴,其可視化矩形區域如下:

(取shift=27,36)

圖片

(取shift=36,45)

圖片

數據查詢:在查詢時,首先將查詢中心點坐標轉換成一個矩形。這個處理思路我們延續了ES 2.0的做法,不陌生了。

例如:對于坐標為(116.433322,39.900255),半徑為1km的點,生成的矩形如下所示:

double centerLon = 116.433322;
double centerLat = 39.900255;
double radiusMeters = 1000.0;
GeoRect geoRect = GeoUtils.circleToBBox(centerLon, centerLat, radiusMeters);
System.out.println( geoRect );

用高德API生成對應的可視化圖形如下:

圖片

有了這個矩形后,后面的做法就跟ES 2.0有些不同了。ES 2.2版本的思路是利用Quadtree對整個世界地圖進行網格化。具體的流程如下:

Quadtree處理流程

第一步:  以經緯度(0,0)為起始中心點,將整個世界切分成4個區塊。并判斷參數生成的矩形在哪個區塊。

第二步:  對于矩形區域不在的區域,略過。對于矩形區域所在的區塊,繼續四分,切成4個區塊。

第三步: 當滿足如下任一條件時,將相關的文檔集合收集起來,作為第一批粗篩的結果。

  • 條件一:切分到正好跟前綴的precisionStep契合,并且quad-cell在矩形內部時。
  • 條件二:切分到最小層級(level=13)時且quad-cell跟矩形區域有交集時。

第四步:  利用lucene的doc_values緩存機制,獲取每個docId對應的經緯度,利用距離公式計算是否在半徑范圍內,得到最終的結果。(這個操作也是常規思路了)

圖片

另外ES在處理時進行了版本兼容。

例如:ES 2.2版本對于geo_distance的實現關鍵點,判斷索引版本是否是V_2_2_0版本以后創建,如果是則直接用Lucene的

GeoPointDistanceQuery查詢類,否則沿用ES 2.0版本的GeoDistanceRangeQuery。

IndexGeoPointFieldData indexFieldData = parseContext.getForField(fieldType);
final Query query;
if (parseContext.indexVersionCreated().before(Version.V_2_2_0)) {
query = new GeoDistanceRangeQuery(point, null, distance, true, false, geoDistance, geoFieldType, indexFieldData, optimizeBbox);
} else {
distance = GeoUtils.maxRadialDistance(point, distance);
query = new GeoPointDistanceQuery(indexFieldData.getFieldNames().indexName(), point.lon(), point.lat(), distance);
}
if (queryName != null) {
parseContext.addNamedQuery(queryName, query);
}

核心代碼參考:GeoPointDistanceQuery、GeoPointRadiusTermsEnum

3.4  Elasticsearch 5.0 版本

方案優化的探索是沒有沒有止境的,Lucene的核心工程師 Michael McCandless受到論文《Bkd-Tree: A Dynamic Scalable kd-Tree》啟發,基于BKD tree再次升級了地理位置數據索引建模和查詢功能。

這個數據結構不僅僅是用于解決地理位置查詢問題,更是數值類數據索引建模的通用方案。它可以處理一維的數值,從byte到BigDecimal, IPv6地址等等;它也可以處理二維乃至于N維的數據檢索問題。

LUCENE-6825

This can be used for very fast 1D range filtering for numerics, removing the 8 byte (long/double) limit we have today, so e.g. we could efficiently support BigInteger, BigDecimal, IPv6 addresses, etc.

It can also be used for > 1D use cases, like 2D (lat/lon) and 3D (x/y/z with geo3d) geo shape intersection searches.

...

It should give sizable performance gains (smaller index, faster searching) over what we have today, and even over what auto-prefix with efficient numeric terms would do.

在前面的版本中,對于數值區間查詢的處理思路本質上都是term匹配,通過前綴實現了一個term管理一個區間,從而降低了區間查詢需要遍歷的term數量。而從ES 5.0版本開始,徹底優化了數值查詢(從一維到N維),其底層是Lucene 6.0版本實現的BKD tree的獨立索引。其實現不僅降低了內存開銷,而且提升了檢索和索引速度。

關于bkd-tree的原理,其大體思路如下。在面對數值查詢區間查詢的問題上,大體分為兩個層次:

  • 【優化內存查詢】:BST(binary-search-tree) > Self-balanced BST  > kd-tree。
  • 【優化外存(硬盤)查詢】:B-tree > K-D-B-tree > BKD tree。

kd-tree其實就是多維的BST。例如:

圖片

【數據存儲】:BKD tree的核心思路是非常簡單的,將N維點集合形成的矩形空間(southWest,northEast)遞歸分割成更小的矩形空間。跟常見的kd-tree不同,當分割到網格區域里面坐標點的數量小于一定數量(比如1024)就停止了。

例如:

圖片

通過區域的分割,確保每個區域POI的數量大致相等。

【數據查詢】:搜索的時候,就不再是像Quadtree從整個世界開始定位,而是基于當前的點集合形成的空間來查找。例如以geo_distance查詢為例。

其流程如下:

第一步: 中心點坐標+半徑生成一個矩形(shape boundary)。這一步是常規操作了,前面的版本也都是這么做的。

第二步:該矩形跟BKD tree 葉子節點形成的矩形(cell)進行intersect運算,所謂intersect運算,就是計算兩個矩形的位置關系:相交、內嵌還是不相關。query和bkd-tree形成的區域有三種關系。

圖片

  • 對于CELL_CROSSES_QUERY,如果是葉子節點,則需要判斷cell中的每個POI是否符合query的查詢條件;否則查詢子區間;
  • 對于CELL_OUTSIDE_QUERY,直接略過;
  • 對于CELL_INSIDE_QUERY,整個cell中的POI都滿足查詢條件。

核心代碼:LatLonPoint/LatLonPointDistanceQuery

3.5  后續發展

Geo查詢能力的迭代和變遷,其實也是Elasticsearch作為一個數據庫對數值查詢能力的升級和優化,擴展產品的適用場景,讓使用者打破對Elasticsearch只能做全文檢索的偏見。從全文檢索數據庫擴展到分析型數據庫,Elasticsearch還有很長的路要走。

按照 Michael McCandless的設想,當前的多維數據只能是單個點,但是有些場景需要將形狀作為一個維度進行索引。在這種需求下,需要通過一種更普適化的k-d tree ,即R-Tree來實現。

路漫漫其修遠兮,ES從2.0版本支持geo-spatial開始經歷6年的發展,已經走了很遠,然而依然有很多值得探索的領域和場景。

參考: 

  1. ??https://www.elastic.co/cn/blog/lucene-points-6.0??
  2. ??https://www.cs.cmu.edu/~ckingsf/bioinfo-lectures/kdtrees.pdf??
  3. ??https://www.csee.usf.edu/~tuy/Literature/KDtree-CACM75.pdf??
責任編輯:未麗燕 來源: vivo互聯網技術
相關推薦

2016-10-16 14:01:38

2023-08-09 08:59:14

2021-01-22 05:40:54

保密安全信息

2014-07-14 14:03:59

跟蹤定位ios信息泄露

2013-05-16 10:57:29

2012-06-05 14:19:50

2012地理信息開發者

2011-08-05 08:58:32

信息系統

2015-06-15 10:30:37

3sNews

2023-09-28 09:03:56

開源搜索分析引擎

2011-04-27 10:20:46

2012-05-21 12:20:28

地理信息開發者大會

2010-03-09 16:18:25

ArcGIS Expl

2018-09-13 10:23:24

GPU

2015-05-04 10:02:26

WGDC

2020-09-07 10:30:22

NaliIPLinux

2018-09-03 16:34:23

2015-01-27 10:35:42

大數據

2009-11-06 11:10:21

點贊
收藏

51CTO技術棧公眾號

亚洲视频1区2区| 捆绑调教美女网站视频一区| 亚洲国产精品va在线看黑人| 国模无码视频一区二区三区| 国产区视频在线播放| 国内精品国产三级国产a久久| 欧美国产日韩在线| 在线 丝袜 欧美 日韩 制服| 香蕉久久一区| 黄色一区二区三区| 一区二区在线中文字幕电影视频| 乱精品一区字幕二区| 日韩精品三区四区| 欧美精品xxx| 极品尤物一区二区| 国产一区二区三区不卡av| 欧美日韩一卡二卡三卡| 国产av天堂无码一区二区三区| av色图一区| av电影在线观看完整版一区二区| 国产精品日韩av| 日韩xxxxxxxxx| 久久精品国内一区二区三区水蜜桃 | 欧美大片国产精品| youjizzxxxx18| 51漫画成人app入口| 综合在线观看色| 日韩av一区二区三区在线观看| 亚洲AV午夜精品| 久久国产精品色婷婷| 欧美综合一区第一页| 欧美久久久久久久久久久久| 日韩免费看片| 一区二区三区视频免费| 欧美成人午夜精品免费| 波多野结衣欧美| 欧美一区二区黄色| 中文字幕网av| 国产成人免费9x9x人网站视频| 婷婷中文字幕综合| 99热亚洲精品| 久草免费在线色站| 一区二区三区波多野结衣在线观看 | 97在线看免费观看视频在线观看| 日韩一区二区三区四区在线| 99久久婷婷| 最新的欧美黄色| 中文字幕伦理片| 精品一区免费| 亚洲色图五月天| 久久精品无码一区| 国产一区二区三区91| 亚洲欧美一区二区激情| 久久人人爽人人爽人人片| 欧美1区二区| 日韩高清人体午夜| 好吊一区二区三区视频| 牛牛影视一区二区三区免费看| 精品久久久久一区| 精品少妇人妻av一区二区三区| 午夜精品在线| 亚洲国产日韩欧美综合久久| 免费a在线观看播放| 亚洲永久精品唐人导航网址| 亚洲理论在线a中文字幕| 91精彩刺激对白露脸偷拍| 免费欧美视频| 伊人精品在线观看| 免费成年人视频在线观看| 欧美一区91| 久久久久久综合网天天| av大片免费观看| 日韩不卡在线观看日韩不卡视频| 国产欧亚日韩视频| 国产夫妻性生活视频| 成人动漫一区二区| 日本精品二区| 黄色在线免费网站| 亚洲二区视频在线| 欧美极品欧美精品欧美图片| 国产91亚洲精品久久久| 日韩你懂的在线观看| 久久久久久久人妻无码中文字幕爆| 日韩极品在线| 日韩中文字幕网| 久久久久久久久精| 性欧美暴力猛交另类hd| 国产日本欧美视频| 亚洲高清精品视频| 久久久高清一区二区三区| 手机成人av在线| 大桥未久在线视频| 欧美色涩在线第一页| aaaaa黄色片| 米奇777超碰欧美日韩亚洲| 日韩中文字幕视频| 日本韩国欧美中文字幕| 久久超碰97人人做人人爱| 国产乱码精品一区二区三区卡 | 欧美亚洲tv| www日韩欧美| 日本三级小视频| 国产在线观看免费一区| 欧美激情第一页在线观看| 久久久久久国产精品免费无遮挡| 欧美日韩国产丝袜另类| 黄色小视频免费网站| 网红女主播少妇精品视频| 久久夜精品香蕉| 丰满少妇xoxoxo视频| 国产成人av电影| 五月天综合网| 亚洲黄色免费av| 日韩欧美成人一区二区| 黄色片网站在线播放| 一本综合久久| 国产精品.com| 国产精品刘玥久久一区| 在线欧美一区二区| 搡老熟女老女人一区二区| 中文字幕一区二区三三| 国产精品视频白浆免费视频| 五月婷婷开心中文字幕| 一区二区三区精品| 天堂在线中文在线| 国产成人精品999在线观看| 国精产品一区一区三区有限在线| 国产精品主播一区二区| 欧美高清在线一区二区| 老熟妇仑乱视频一区二区| 久久1电影院| 欧美福利在线观看| 国产喷水吹潮视频www| 国产精品免费看片| 五月婷婷狠狠操| 国产精品午夜一区二区三区| 91精品国产91久久久久久吃药| 亚洲av永久纯肉无码精品动漫| 亚洲欧洲在线观看av| 国产精品区在线| 日韩理论电影| 国产区精品在线观看| 日本三级视频在线播放| 欧美私人免费视频| 久久婷婷五月综合| 爽好久久久欧美精品| 欧美极品一区| 欧美日韩美女| 一区二区亚洲精品国产| 欧美在线视频精品| 欧美国产一区视频在线观看| 日韩精品你懂的| 日本a级不卡| 成人有码在线视频| 黄色的网站在线观看| 欧美一区二区视频在线观看2022| 日韩欧美国产成人精品免费| 国产不卡高清在线观看视频| 男女激情免费视频| 日韩理论电影中文字幕| 日韩美女在线播放| 福利成人在线观看| 在线不卡中文字幕播放| 69xx绿帽三人行| 成人一道本在线| 少妇高潮喷水在线观看| 蜜桃视频欧美| 国产啪精品视频网站| 国产原厂视频在线观看| 精品久久久久久久久久久院品网| 豆国产97在线 | 亚洲| 91美女视频网站| 8x8x最新地址| 欧美福利影院| 久久久一本精品99久久精品66| 精品成人av| 久久亚洲精品小早川怜子66| 亚洲国产欧美另类| 日韩欧美在线视频观看| 国产精品综合激情| 国产白丝网站精品污在线入口| 鲁一鲁一鲁一鲁一澡| 日本一区二区三区视频| 99c视频在线| 成人美女大片| 美女国内精品自产拍在线播放| 欧美少妇bbw| 欧美日韩国产在线观看| 久久精品女人毛片国产| 亚洲国产精品高清| 亚洲AV成人精品| 日韩电影免费在线| 黄色片免费在线观看视频| 精品一区三区| yy111111少妇影院日韩夜片| av在线日韩| 欧美精品久久一区二区| 91社区在线| 亚洲精品电影在线观看| 国产精品一区二区黑人巨大| 色综合婷婷久久| 九九视频免费在线观看| 欧美国产视频在线| 亚洲欧美日韩色| 国产美女av一区二区三区| 免费日韩视频在线观看| 激情婷婷欧美| 伊人狠狠色丁香综合尤物| 亚洲成a人片77777在线播放 | 久久久亚洲欧洲日产国码aⅴ| 国产精品99999| 日韩成人在线视频观看| 99热这里是精品| 欧美性一级生活| 日韩色图在线观看| 亚洲国产成人av| 成人在线观看小视频| 国产三区在线成人av| 国模私拍在线观看| 国产 日韩 欧美大片| 在线一区二区不卡| 日本免费新一区视频| 亚洲熟妇无码另类久久久| 亚洲精品网址| 一区二区视频在线播放| 国内精品久久久久久99蜜桃| 久久大片网站| 激情亚洲另类图片区小说区| 91视频-88av| 亚洲青青久久| 国产视频观看一区| 福利一区二区| 国产欧美精品在线播放| 久久爱.com| 国产成人精品免高潮在线观看 | 在线观看免费版| 一区二区三区www| 国产区av在线| 亚洲小视频在线| 国产午夜精品一区理论片| 亚洲欧美视频在线| 黄上黄在线观看| 国产一区二区日韩| 高清毛片在线看| 色老头一区二区三区| 香蕉视频在线看| www国产精品com| 婷婷丁香在线| 久久久免费在线观看| 国产在线精彩视频| 欧美在线一区二区视频| av资源亚洲| 日本视频久久久| 成人精品三级| 成人免费福利在线| 日韩精品一区国产| 国产伦精品一区二区三区高清| 另类ts人妖一区二区三区| 国产伦精品一区二区三区视频免费 | 日韩高清不卡在线| 成人性生交免费看| 韩国精品一区二区| 无码人妻一区二区三区精品视频| 粉嫩av亚洲一区二区图片| 日本一级片在线播放| 久久久亚洲午夜电影| 国产精品久久免费观看| 亚洲日本一区二区| 日本免费一二三区| 色综合久久中文字幕| 日批视频免费观看| 欧美一区二区视频在线观看2022| 东京干手机福利视频| 日韩av网址在线观看| 国产98在线| 欧美日韩xxx| 国产精品伦理| 91精品中国老女人| 欧洲亚洲成人| 亚洲精品国产精品国自产| 欧美69wwwcom| 欧美视频第一区| 国模少妇一区二区三区| 800av在线播放| 国产精品乱子久久久久| 精品无码久久久久| 日本精品视频一区二区三区| 国产女无套免费视频| 亚洲美女激情视频| 黄色的网站在线观看| 欧美亚洲国产视频| 国产精品一级在线观看| 麻豆成人av| 欧美女激情福利| 国产高潮免费视频| 成人av免费观看| 多男操一女视频| 色欧美乱欧美15图片| 亚洲国产精品久久久久久久| 在线丨暗呦小u女国产精品| 爱情岛论坛亚洲品质自拍视频网站| 日韩女优在线播放| 成人中文字幕视频| 在线不卡视频一区二区| 亚洲一区国产| 日批视频在线看| 欧美国产在线观看| 久久久久女人精品毛片九一| 日韩一区二区在线观看| 成年人视频网站在线| 69av在线视频| 一区二区三区亚洲变态调教大结局 | 日本中文字幕二区| 91欧美激情一区二区三区成人| 免费在线黄色网| 欧美丝袜自拍制服另类| 婷婷开心激情网| 色综合男人天堂| www久久久| 亚洲高清在线观看一区| 久久最新视频| 丝袜美腿中文字幕| 亚洲国产wwwccc36天堂| www.五月激情| 久久亚洲精品毛片| www.久久草.com| 超碰成人在线免费观看| 日本伊人色综合网| 免费观看a级片| 一本大道久久精品懂色aⅴ| 日韩在线免费看| 91国内精品久久| 国产精品一线| 成年人看的毛片| 国产乱码字幕精品高清av | yiren22亚洲综合伊人22| 777精品视频| 亚州av日韩av| 青青草原av在线播放| 91美女精品福利| 欧美激情黑白配| 日韩毛片中文字幕| 两个人看的在线视频www| 国产日韩在线一区二区三区| 国产一区观看| 好吊色视频一区二区三区| 亚洲第一久久影院| 亚洲av成人精品毛片| 456亚洲影院| 奇米狠狠一区二区三区| 久久久精品麻豆| 中文字幕欧美一区| 国产绿帽刺激高潮对白| 久久国产精品久久久久久久久久 | 欧美大肚乱孕交hd孕妇| 欧美寡妇性猛交xxx免费| 国产日韩亚洲精品| 久久激情婷婷| 毛片久久久久久| 91精品欧美一区二区三区综合在| 国产黄a三级三级三级av在线看 | 一区二区三区在线观看欧美 | 亚洲女人天堂视频| 日韩av免费| 大地资源网在线观看免费官网| 国产宾馆实践打屁股91| 五月激情六月丁香| 亚洲天堂成人在线视频| 高清在线一区| 人妻激情另类乱人伦人妻| 97se亚洲国产综合在线| 69亚洲精品久久久蜜桃小说| 中文字幕亚洲一区| 成人av地址| 国产v亚洲v天堂无码久久久| 中文一区二区在线观看| www.久久久久久久久久| 日本一区二区三区四区视频| 99视频精品全部免费在线视频| 无码国产精品久久一区免费| 日韩欧美第一页| 麻豆视频在线观看免费网站| 久久99国产精品99久久| 免费在线观看视频一区| 精品肉丝脚一区二区三区| 一区二区三区动漫| jizz国产精品| 男人添女人下面免费视频| 亚洲一区二区视频在线观看| 国产精品四虎| 国产高清自拍一区| 日本vs亚洲vs韩国一区三区| 久久精品波多野结衣| 在线观看欧美成人| 成人av资源网址| 日本不卡一区在线| 欧美性猛交xxxx黑人| 国产在线观看免费麻豆| 欧美日韩综合另类| 国产传媒欧美日韩成人|