基於Solr的空間搜索(3)

接上文,本文將繼續介紹基於Solr的地理位置搜索的第二種實現方案 git

CartesianTiers+GeoHash 數組

從基於Solr的地理位置搜索(2)文章中能夠看到徹底基於GeoHash的查詢過濾,將徹底遍歷整個docment文檔,從效率上來看並不太合適,因此結合笛卡爾層後,能有效縮減小過濾範圍,從性能上能很大程度的提升。 緩存

構建索引階段: ide

String geoHash = GeoHashUtils.encode(latitude, longitude);
      docment.addField(「geohash」, geoHash);
      //Cartesian Tiers
      int tier = START_TIER;//開始構建索引的層數
      //Create a bunch of tiers, each deeper level has more precision
//將一條記錄的經緯度對應所有笛卡爾層的tierBoxId做爲域值構建索引
      for (CartesianTierPlotter plotter : plotters) {
        docment.addField(「tier_」 + tier , plotter.getTierBoxId(latitude, longitude));
        tier++;
      }

看到這裏你們確定明白了。越相近的經緯度在同層確定會在同一個網格中,因此他們存儲的tierBoxId就會是同樣。那麼查詢的時候經過經緯度對應層的tierBoxId,也就能找到相同層域的docId,可是若是給定的的查詢範圍大,可能須要將若干層的所屬網格的docId都查到。 性能

   整個查詢過程是先經過笛卡爾層將若干個網格涉及的DocList存入bitSet,以下代碼所示: this

public DocIdSet getDocIdSet(final IndexReader reader) throws IOException {
    final FixedBitSet bits = new FixedBitSet(reader.maxDoc());
final TermDocs termDocs = reader.termDocs();
//須要查詢的若干層網格的boxIdList,固然至此已通過濾掉不須要查詢層的boxIdList
    final List<Double> area = shape.getArea();
    int sz = area.size();
    final Term term = new Term(fieldName);//
    // iterate through each boxid
    for (int i =0; i< sz; i++) {
      double boxId = area.get(i).doubleValue();
termDocs.seek(term.createTerm(NumericUtils.doubleToPrefixCoded(boxId)));
      // iterate through all documents
      // which have this boxId
//遍歷全部包含給定boxId的docList,並將其放入bitset
      while (termDocs.next()) {
        bits.set(termDocs.doc());
      }
    }
    return bits;
  }

介紹完笛卡爾層的計算後,接下來介紹笛卡爾層過濾後返還的bitset如何和geoHash結合,從實現上講其實很簡單,就是將經過笛卡爾層過濾的數據結果集合 依次遍歷計算其與查詢給定的經緯度座標的球面距離,同時將該計算距離和查詢指定範圍距離進行比較,若是大於給定距離,則將當前記錄繼續過濾掉,那麼最終剩下的數據結果集合,將是知足查詢條件的地理位置結果集合。具體實現流程見以下代碼: 編碼

//將笛卡爾層的Filter做爲Geohash的Filter參數傳遞進去,造成一個過濾鏈
 filter = distanceFilter = new GeoHashDistanceFilter(cartesianFilter, lat, lng, miles, geoHashFieldPrefix);

再看GeoHashDistanceFilter中最核心的方法getDocIdSet(): code

 public DocIdSet getDocIdSet(IndexReader reader) throws IOException {
      //在這裏使用到了Lucene的FieldCache來做爲緩存,實際上緩存了一個以docId爲下標,base32編碼爲值的數組
    final String[] geoHashValues = FieldCache.DEFAULT.getStrings(reader, geoHashField);
    final int docBase = nextDocBase;
    nextDocBase += reader.maxDoc();
    return new FilteredDocIdSet(startingFilter.getDocIdSet(reader)) {
      @Override
      public boolean match(int doc) {
        //經過笛卡爾層的過濾後的doc直接找到對應的base32編碼
        String geoHash = geoHashValues[doc];
        //經過解碼將base32還原成經緯度座標
        double[] coords = GeoHashUtils.decode(geoHash);
        double x = coords[0];
        double y = coords[1];
        Double cachedDistance = distanceLookupCache.get(geoHash);
        double d;
        if (cachedDistance != null) {
          d = cachedDistance.doubleValue();
        } else {
           //計算2個經緯度座標的距離
          d = DistanceUtils.getDistanceMi(lat, lng, x, y);
          distanceLookupCache.put(geoHash, d);
        }
       //小於給定查詢距離的的docid放入緩存,以供下次使用,同時返回True表明當前docId是知足條件的記錄
        if (d < distance){
          distances.put(doc+docBase, d);
          return true;
        } else {
          return false;
        }
      }
    };

  從上述分析中你們應該能夠想到 採用笛卡爾層 Filter結合GoHash Filter的實現方案,在計算規模上會比單獨使用GeoHash少了不少,而在查詢性能也會有更優異的表現。 索引

最後附上一個本地Demo的查詢實例,用geofilter查找給定經緯度500km內的數據: ci

查詢返回結果:

相關文章
相關標籤/搜索