91超碰碰碰碰久久久久久综合_超碰av人澡人澡人澡人澡人掠_国产黄大片在线观看画质优化_txt小说免费全本

溫馨提示×

溫馨提示×

您好,登錄后才能下訂單哦!

密碼登錄×
登錄注冊×
其他方式登錄
點擊 登錄注冊 即表示同意《億速云用戶服務條款》

Elasticsearch在地理信息空間索引的知識點有哪些

發布時間:2022-07-05 14:04:37 來源:億速云 閱讀:257 作者:iii 欄目:開發技術

本篇內容介紹了“Elasticsearch在地理信息空間索引的知識點有哪些”的有關知識,在實際案例的操作過程中,不少人都會遇到這樣的困境,接下來就讓小編帶領大家學習一下如何處理這些情況吧!希望大家仔細閱讀,能夠學有所成!

一、業務背景

LBS服務是當前互聯網重要的一環,涉及餐飲、娛樂、打車、零售等場景。在這些場景中,有很重要的一項基礎能力:搜索附近的POI。比如搜索附近的美食,搜索附近的電影院,搜索附近的專車,搜索附近的門店。例如:以某個坐標點為中心查詢出1km半徑范圍的POI坐標,如下圖所示:

Elasticsearch在地理信息空間索引的知識點有哪些

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即為所求兩個點的距離,對應的數學公式如下(參考維基百科):

Elasticsearch在地理信息空間索引的知識點有哪些

程序員更喜歡看代碼,對照代碼理解公式更簡單。相應的代碼如下:

// 代碼摘自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 h3 = 1 - cos(x1 - x2);
   double h4 = 1 - cos((lon1 - lon2) * TO_RADIANS);
   double h = h3 + cos(x1) * cos(x2) * h4;
   // 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,它更大的價值在于:

Geohash給地圖上每個坐標提供了獨一無二的ID,這個唯一ID就相當于給每個地理位置提供了一個身份證。唯一ID在數據庫中應用場景非常豐富。

在數據庫中給坐標點提供了另一種存儲方式,將二維的坐標點轉化成為一維的字符串,對于一維數據就可以借助B樹等索引來加速查詢。

Geohash是一種前綴編碼,位置相近的坐標點前綴相同。通過前綴提供了高性能的鄰近位置POI查詢,而鄰近位置POI查詢是LBS服務的核心能力。

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

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

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

  • 暴力算法

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

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

  • 二次篩選

基于坐標中心點計算出geohash, 基于半徑確定geohash前綴。

通過Geohash前綴初篩出大致符合要求的坐標點(需要將中心點所在Geohash塊周圍8個Geohash塊納入初篩范圍)。

對于初篩結果使用Haversine公式進行二次篩選。

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

三、方案演進

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

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

Elasticsearch在地理信息空間索引的知識點有哪些

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

Elasticsearch在地理信息空間索引的知識點有哪些

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

Elasticsearch在地理信息空間索引的知識點有哪些

Elasticsearch在地理信息空間索引的知識點有哪些

Elasticsearch在地理信息空間索引的知識點有哪些

Elasticsearch在地理信息空間索引的知識點有哪些

總的來說,資源消耗降低的前提下搜索和寫入數據效率都有大幅度提升。下面就詳細介紹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,也不限制查詢區間了。它的核心思路是將數值字節數組化,然后利用前綴分層管理區間。

如下圖所示:

Elasticsearch在地理信息空間索引的知識點有哪些

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

例如:如果precisionStep=8,則意味前綴樹葉子節點的上層控制著255個葉子。那么,當查詢范圍在1511時,由于跨了相鄰的2個非葉子節點,所以需要遍歷511個term。但是假如查詢范圍在0512,又只需遍歷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的搜索能力。

簡單總結一下要點:

利用中心點坐標和半徑確定矩形區域邊界。

利用Bool查詢綜合兩個NumericRangeQuery查詢,實現矩形區域初篩。

利用Haversine公式計算中心點和矩形區域內每個坐標點距離,進行第二階段過濾操作,篩選出最終符合條件的docId集合。

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

3.3 Elasticsearch 2.2 版本

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

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

Elasticsearch在地理信息空間索引的知識點有哪些

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

針對上面的問題,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的區間劃分過程。

Elasticsearch在地理信息空間索引的知識點有哪些

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)

Elasticsearch在地理信息空間索引的知識點有哪些

(取shift=36,45)

Elasticsearch在地理信息空間索引的知識點有哪些

**數據查詢:**在查詢時,首先將查詢中心點坐標轉換成一個矩形。這個處理思路我們延續了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生成對應的可視化圖形如下:

Elasticsearch在地理信息空間索引的知識點有哪些

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

  • Quadtree處理流程

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

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

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

  • 條件一:切分到正好跟前綴的precisionStep契合,并且quad-cell在矩形內部時。

  • 條件二:切分到最小層級(level=13)時且quad-cell跟矩形區域有交集時。

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

Elasticsearch在地理信息空間索引的知識點有哪些

另外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。例如:

Elasticsearch在地理信息空間索引的知識點有哪些

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

例如:

Elasticsearch在地理信息空間索引的知識點有哪些

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

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

其流程如下:

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

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

Elasticsearch在地理信息空間索引的知識點有哪些

對于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年的發展,已經走了很遠,然而依然有很多值得探索的領域和場景。

“Elasticsearch在地理信息空間索引的知識點有哪些”的內容就介紹到這里了,感謝大家的閱讀。如果想了解更多行業相關的知識可以關注億速云網站,小編將為大家輸出更多高質量的實用文章!

向AI問一下細節

免責聲明:本站發布的內容(圖片、視頻和文字)以原創、轉載和分享為主,文章觀點不代表本網站立場,如果涉及侵權請聯系站長郵箱:is@yisu.com進行舉報,并提供相關證據,一經查實,將立刻刪除涉嫌侵權內容。

AI

三台县| 丰原市| 吉安市| 宜州市| 二连浩特市| 静宁县| 资溪县| 仪征市| 五峰| 宣汉县| 福州市| 二连浩特市| 班玛县| 岑溪市| 密云县| 始兴县| 会宁县| 汨罗市| 策勒县| 木里| 孝义市| 嘉兴市| 泸州市| 绥江县| 岚皋县| 且末县| 永平县| 鄯善县| 石首市| 新安县| 青州市| 抚远县| 磴口县| 民乐县| 平湖市| 平定县| 天水市| 丹江口市| 太康县| 凌源市| 加查县|