基于Solr的地理位置搜索(3) – – ITeye技术网站

【说明】 本文转载自:http://hadoopcn.iteye.com/blog/1766762

于Solr的地理位置搜索(3) – – ITeye 技术网站

接上文,本文将继续介绍基于Solr的地理位置搜索的第二种实现方案Cartesian Tiers+GeoHash

从基于Solr的地理位置搜索(2)中可以看到完全基于GeoHash的查询过滤,将完全遍历整个docment文档,从效率上来看并不太合适,所以结合笛卡尔层后,能有效缩减少过滤范围,从性能上能很大程度的提高

构建索引阶段:

Java代码  

  1. String geoHash = GeoHashUtils.encode(latitude, longitude);  
  2.       docment.addField("geohash", geoHash);  
  3.       //Cartesian Tiers  
  4.       int tier = START_TIER;//开始构建索引的层数  
  5.       //Create a bunch of tiers, each deeper level has more precision  
  6. //将一条记录的经纬度对应全部笛卡尔层的tierBoxId作为域值构建索引  
  7.       for (CartesianTierPlotter plotter : plotters) {  
  8.         docment.addField("tier_" + tier , plotter.getTierBoxId(latitude, longitude));  
  9.         tier++;  
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,如下代码所示:

Java代码  

  1. public DocIdSet getDocIdSet(final IndexReader reader) throws IOException {  
  2.     final FixedBitSet bits = new FixedBitSet(reader.maxDoc());  
  3. final TermDocs termDocs = reader.termDocs();  
  4. //需要查询的若干层网格的boxIdList,当然至此已经过滤掉不需要查询层的boxIdList  
  5.     final List<Double> area = shape.getArea();  
  6.     int sz = area.size();  
  7.     final Term term = new Term(fieldName);//  
  8.     // iterate through each boxid  
  9.     for (int i =0; i< sz; i++) {  
  10.       double boxId = area.get(i).doubleValue();  
  11. termDocs.seek(term.createTerm(NumericUtils.doubleToPrefixCoded(boxId)));  
  12.       // iterate through all documents  
  13.       // which have this boxId  
  14. //遍历所有包含给定boxId的docList,并将其放入bitset  
  15.       while (termDocs.next()) {  
  16.         bits.set(termDocs.doc());  
  17.     return bits;  
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结合,从实现上讲其实很简单,就是将通过笛卡尔层过滤的数据结果集合 依次遍历计算其与查询给定的经纬度坐标的球面距离,同时将该计算距离和查询指定范围距离进行比较,如果大于给定距离,则将当前记录继续过滤掉,那么最终剩下的数据结果集合,将是满足查询条件的地理位置结果集合具体实现流程见如下代码:

Java代码  

  1. //将笛卡尔层的Filter作为Geohash的Filter参数传递进去,形成一个过滤链  
  2.  filter = distanceFilter = new GeoHashDistanceFilter(cartesianFilter, lat, lng, miles, geoHashFieldPrefix);  
//将笛卡尔层的Filter作为Geohash的Filter参数传递进去,形成一个过滤链
 filter = distanceFilter = new GeoHashDistanceFilter(cartesianFilter, lat, lng, miles, geoHashFieldPrefix);

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

Java代码  

  1. public DocIdSet getDocIdSet(IndexReader reader) throws IOException {  
  2.      //在这里使用到了Lucene的FieldCache来作为缓存,实际上缓存了一个以docId为下标,base32编码为值的数组  
  3.    final String[] geoHashValues = FieldCache.DEFAULT.getStrings(reader, geoHashField);  
  4.    final int docBase = nextDocBase;  
  5.    nextDocBase += reader.maxDoc();  
  6.    return new FilteredDocIdSet(startingFilter.getDocIdSet(reader)) {  
  7.      @Override  
  8.      public boolean match(int doc) {  
  9.        //通过笛卡尔层的过滤后的doc直接找到对应的base32编码  
  10.        String geoHash = geoHashValues[doc];  
  11.        //通过解码将base32还原成经纬度坐标  
  12.        double[] coords = GeoHashUtils.decode(geoHash);  
  13.        double x = coords[0];  
  14.        double y = coords[1];  
  15.        Double cachedDistance = distanceLookupCache.get(geoHash);  
  16.        double d;  
  17.        if (cachedDistance != null) {  
  18.          d = cachedDistance.doubleValue();  
  19.        } else {  
  20.           //计算2个经纬度坐标的距离  
  21.          d = DistanceUtils.getDistanceMi(lat, lng, x, y);  
  22.          distanceLookupCache.put(geoHash, d);  
  23.       //小于给定查询距离的的docid放入缓存,以供下次使用,同时返回True代表当前docId是满足条件的记录  
  24.        if (d < distance){  
  25.          distances.put(doc+docBase, d);  
  26.          return true;  
  27.        } else {  
  28.          return false;  
 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内的的数据

 http://localhost:8983/solr/select/?q=*:*&fq={!geofilt pt=30.15,-79.85 sfield=tier d=500}

 

此条目发表在未分类分类目录,贴了标签。将固定链接加入收藏夹。