从基于Solr的地理位置搜索(2)文章中能够看到彻底基于GeoHash的查询过滤,将彻底遍历整个docment文档,从效率上来看并不太合适,因此结合笛卡尔层后,能有效缩减小过滤范围,从性能上能很大程度的提升。 缓存
docment.addField(“tier_” + tier , plotter.getTierBoxId(latitude, longitude));
看到这里你们确定明白了。越相近的经纬度在同层确定会在同一个网格中,因此他们存储的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
查询返回结果: