IT技术博客大学习 共学习 共进步
全部 移动开发 后端 数据库 AI 算法 安全 DevOps 前端 设计 开发者

基于Solr的空间搜索(3)

淘宝网综合业务平台团队博客 2013-08-15 13:40:09 累计浏览 4,182 次
本机暂存

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

   CartesianTiers+GeoHash

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

   构建索引阶段:

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,如下代码所示:

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():

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内的数据:

   q=*:*&fq={!geofilt pt=30.15,-79.85 sfield=tier d=500}

   查询返回结果:

   原图已失效

同分类推荐文章

  1. 第七章 事务 (2026-04-07 08:00:00)
  2. 第六章:分区 (2026-03-29 08:00:00)
  3. Neko Master: 从 0 到 1K+ Star 的 Vibe Coding 实践 (2026-03-01 08:00:00)

查看更多 数据库 文章 →

建议继续学习

  1. 几种常见的基于Lucene的开源搜索解决方案对比 (累计阅读 6,086)
  2. geohash:用字符串实现附近地点搜索 (累计阅读 5,082)
  3. Solr\Lucene优劣势分析 (累计阅读 3,602)
  4. 基于Lucene/XML的站内全文检索解决方案:WebLucene (累计阅读 3,381)
  5. SolrQuery挖掘–单维度聚合分析 (累计阅读 3,322)
  6. Solr调优参考 (累计阅读 3,182)
  7. 基于Solr的空间搜索(2) (累计阅读 3,183)
  8. Solr的TrieField范围查询分析 (累计阅读 2,983)
  9. 基于Solr的空间搜索(1) (累计阅读 2,963)
  10. 分布式全文检索系统SolrCloud简介 (累计阅读 2,880)