最近对Lucene进行了一些安静的改进,这些改进加起来使将地理空间距离刻面添加到任何Lucene搜索应用程序中变得异常简单,例如:
< 1 km (147)
< 2 km (579)
< 5 km (2775)
由于用户现在大多数搜索都来自移动智能手机,因此这种允许用户快速将其搜索结果过滤到靠近其位置的距离刻面变得尤为重要。
在过去,要实现它具有挑战性,因为它是如此动态且如此昂贵:构面计数取决于每个用户的位置,因此无法在用户之间进行缓存和共享,并且空间距离的基础数学很复杂。
但是,Lucene最近进行了几项改进,使这一过程变得异常简单!
首先,Lucene的动态范围 ValueSource
面已被通用化,可以接受任何ValueSource
,而不仅仅是索引中的数字doc values字段。 多亏了最近添加的expressions模块 ,这意味着您可以提供从任意JavaScript表达式计算出的动态范围构面,因为该表达式使用ASM定制生成的Java字节码即时编译为ValueSource
。 Lucene的范围刻面现在也更快,它使用段树快速将每个值分配给匹配的range 。
第二,将Haversine距离函数 添加到表达式模块。 该实现对通常昂贵的三角函数使用了令人印象深刻的快速近似,而三角函数的一部分是从Java Optimized Development Kit项目中偷偷偷取的,而不会牺牲太多的准确性。 在实践中,近似值不太可能会变得很重要,并且存在进一步改善近似值的未解决问题 。
突然有了这些改进,如果您在每个文档DoubleDocValuesField
经度和纬度索引为DoubleDocValuesField
,并且您知道每个请求的用户的经度/纬度位置,则可以轻松计算构面计数并按任意一组选定的距离进行下钻。
首先,使用纬度/经度字段为文档建立索引:
Document doc = new Document();
doc.add(new DoubleField("latitude", 40.759011, Field.Store.NO));
doc.add(new DoubleField("longitude", -73.9844722, Field.Store.NO));
writer.addDocument(doc);
在搜索时,通过构建调用Haversine函数的动态表达式来获取ValueSource
:
private ValueSource getDistanceValueSource() {
Expression distance;
try {
distance = JavascriptCompiler.compile(
"haversin(40.7143528,-74.0059731,latitude,longitude)");
} catch (ParseException pe) {
// Should not happen
throw new RuntimeException(pe);
}
SimpleBindings bindings = new SimpleBindings();
bindings.add(new SortField("latitude", SortField.Type.DOUBLE));
bindings.add(new SortField("longitude", SortField.Type.DOUBLE));
return distance.getValueSource(bindings);
}
您应该填写用户的位置,而不是上面的有线纬度/经度。
使用该ValueSource
,像这样计算动态构面计数:
FacetsCollector fc = new FacetsCollector();
searcher.search(new MatchAllDocsQuery(), fc);
Facets facets = new DoubleRangeFacetCounts(
"field",
getDistanceValueSource(), fc,
ONE_KM,
TWO_KM,
FIVE_KM,
TEN_KM);
return facets.getTopChildren(10, "field");
通常,您将使用“真实”查询而不是顶层浏览的MatchAllDocsQuery
。 最后,一旦用户选择了要向下钻取的距离,请使用Range.getFilter
方法并将其添加到使用ConstantScoreQuery
的DrillDownQuery
中:
public TopDocs drillDown(DoubleRange range) throws IOException {
// Passing no baseQuery means we drill down on all
// documents ("browse only"):
DrillDownQuery q = new DrillDownQuery(null);
q.add("field", new ConstantScoreQuery(
range.getFilter(getDistanceValueSource())));
return searcher.search(q, 10);
}
请参阅lucene/demo
模块中的完整源代码 。
当我初次测试该示例时,有一个有趣的bug ,随后对Facet API进行了全面修订 ,因此您需要等待Lucene 4.7版本,或者仅使用最新的4.x源来获得此示例。工作。
尽管此示例很简单且可以正常运行,但仍有可能实现一些明显的性能改进,例如使用边界框作为快速匹配项,以避免计算明显超出可能的向下钻取范围(补丁)的命中的Haversine欢迎!)。 即便如此,这对于Lucene的刻面而言还是一个不错的进步,而Lucene的地理空间距离刻面可以如此简单令人惊讶。