lucene 空间检索
在上一篇文章[1]中,我们讨论了如何将Lucene与HBase集成以提高可伸缩性和可用性。 在本文中,我将展示如何在空间支持下扩展此实现。
Lucene空间贡献包[2、3、4、5]为空间搜索提供了有力的支持,但仅限于找到最接近的点。 实际上,空间搜索通常具有更多的要求,例如,哪些点属于给定的形状(圆形,边界框,多边形),哪些形状与给定的形状相交等等。 本文介绍的解决方案可以解决所有上述问题。
空间搜索法
所提出的实现是基于两级搜索的:第一级搜索基于笛卡尔网格[3]搜索,第二级实现特定于形状的空间计算。
对于笛卡尔网格实现,我以某个缩放级别[2]铺平了[1]整个世界。 在这种情况下,几何形状由包含完整形状的图块ID集合定义。 这些图块ID的计算对于任何形状类型都是微不足道的,并且可以用几行代码[3]来实现 。 在这种情况下,参考某种形状的文档和搜索形状都可以由图块ID列表来表示。 这意味着第一级搜索实现只是在搜索形状的图块ID与文档形状之间找到交集。 [4]这可以实现为Lucene非常擅长的“标准”文本搜索操作。 一旦完成初始选择,就可以通过过滤第一级搜索的结果来使用所需的几何算法来获得更精确的结果。 从技术上讲,这些计算可能与提高搜索精度所需的计算一样复杂。 因为这些计算从技术上讲不是基本实现的一部分,所以每个相关方都可以开发最适合其需求的自己的实现。
图1-两级搜索
让我们考虑一个具体的示例(图1)。 在这里,我仅显示有限数量的图块-1到12和4个文档“ a”到“ e”。 根据其位置和形状,每个文档都可以描述为一组图块ID,例如,图块11和12中包含“ b”。现在假设我们要搜索与边界框(宽)相交的每个文档。线)。 如图1所示,搜索边界框可以表示为ID为1到12的一组图块。作为第一级搜索的结果,将找到所有形状“ a”至“ e”。 第二级搜索将丢弃不与结果边界框相交的形状“ a”和“ e”,并且整体搜索将返回形状“ b”,“ c”和“ d”。
在Lucene中纳入对空间搜索的支持
将上述算法整合到Lucene中非常简单。 在我们的实现中,Lucene引擎的工作是进行一级搜索-切片匹配。 第二级搜索可以通过外部过滤完成,而不必将其合并到Lucene中。
为了同时支持这两种方法,需要使用其他字段来扩展Lucene文档:
- 形状字段描述与文档关联的形状[5] ,过滤器可以使用它来获取与文档关联的形状,以执行显式过滤。
- 瓦片ID字段包含描述不同缩放级别包含该形状的瓦片的字段。
尽管理论上这些附加字段可以由客户端直接生成,但我还是决定引入一个特殊的类-空间文档类,通过添加形状信息来扩展Lucene Document类。 这使我们可以向用户隐藏这些其他字段,并为任何Spatial Document实例自动生成它们。
我决定也对搜索者使用类似的方法。 我没有尝试扩展Lucene的Query类以支持我们的地理空间构造,而是实现了SpatialReaderFilter类,可以在搜索之前在Reader [6]类上对其进行设置。
这种方法允许为Lucene构建通用的一级地理空间搜索扩展,它可以用作解决任何复杂的地理空间问题的基础。
优化地理空间信息支持
将地理空间信息直接放入文档字段是在Lucene索引中存储地理空间信息的最简单方法。 它的缺点是要么导致索引大小“爆炸” [7],并且需要进行大量数据“联接”(Lucene和查询),要么需要“蛮力”方法-遍历现有项/值索引并仅使用通过过滤条件的项目(属于给定缩放级别的一组图块)。 当Lucene索引的平均大小相对较小(大约成千上万个项目)时,这两种方法都适用于简单情况。 在更复杂的情况下,当索引大小增加时,两种方法都将导致搜索时间显着增加。 在后一种情况下,一种更好的方法是将给定字段/术语值的文档信息存储为多级哈希表(图2)。
图2-给定字段/术语的地理空间索引存储
哈希的第一个级别是按缩放级别。 级别1-整个世界都是为没有空间信息的请求而保留的,并且包含具有给定期限的给定值的所有文档的文档信息。 所有其他级别都包含一个哈希图,该哈希图包含每个文档的文档信息,每个文档包含给定术语的给定值并位于给定图块ID中。 由于给定图块中包含的文档数量总是比整个世界中的文档数量小得多,因此基于这种索引组织的搜索将大大加快包含地理空间成分的搜索,同时保留大致相同的搜索时间没有它们的搜索。
尽管这样的索引组织可以增加所需的内存占用量,但是由于总体实现是基于即将到期的惰性缓存,因此这种增加将相对较小。
在内存缓存[1]中进行修改以利用多级哈希表(图2)进行索引实现相当简单。
优化HBase表
尽管通过引入其他键结构可以在原始HBase表设计[1]中支持多级哈希表的实现(图2),但对于我们提供地理空间支持的实现,我还是决定对其进行修改,以便更好地应对数量显着增加的情况。行并进一步提高搜索的可扩展性。 在这种情况下,我不是使用单个HBase索引表,而是使用N + 1索引表(图3)-一个用于全局数据,一个用于每个缩放级别。
图3-HBase索引表
(图3)中的整个世界表与[1]中描述的索引表完全相同,而每个缩放级别的表具有相同的内容,但键结构不同。 对于他们来说,关键是不是两个值而是三个值的串联-瓦片ID,字段名和术语值。
除了按缩放级别拆分表可以使每个表变小(创建更多可管理的表)外,它还可以增加搜索的并行化-对不同缩放级别的请求将从不同的表中读取。 以上键定义提供的自然支持在Lucene实现所需的给定缩放级别/平铺ID的字段/术语级别进行扫描。
实施二级搜索
一旦完成第一级搜索并返回包含搜索结果的Lucene的TopDocs类,就可以执行结果的第二级过滤。 一个简单的类(清单1)显示了如何执行此执行。
package com.navteq.lucene.spatial.geometry.filters; import java.io.IOException;
import java.util.ArrayList;
import java.util.LinkedList;
import java.util.List;
import org.apache.lucene.document.Document;
import org.apache.lucene.index.IndexReader;
import org.apache.lucene.search.ScoreDoc;
import org.apache.lucene.search.TopDocs;
import com.navteq.lucene.hbase.indexing.HBaseIndexReader;
import com.navteq.lucene.spatial.GeometricShapeHelper;
import com.navteq.lucene.spatial.geometry.GeometricShape;
public class SpatialFilterProcessor {
private AbstractSpatialFilter _filter = null ;
public SpatialFilterProcessor(AbstractSpatialFilter filter){
_filter = filter;
}
public TopDocs FilterDocuments(IndexReader reader, TopDocs set)
throws IOException {
ScoreDoc[] original = set. scoreDocs ;
List<Integer> ids = new ArrayList<Integer>(original. length );
List<ScoreDoc> filtered = new LinkedList<ScoreDoc>();
float maxScore = -1;
for (ScoreDoc d : original)
ids.add(d. doc );
List<Document> documents =
((HBaseIndexReader)reader).documents(ids);
int i = 0;
for (Document doc : documents) {
if (doc != null ) {
GeometricShape shape =
GeometricShapeHelper. getGeometry (doc);
if (shape != null ){
if ( _filter .accept(shape)) {
filtered.add(original[i]);
if (original[i]. score > maxScore)
maxScore = original[i]. score ;
}
}
}
i++;
}
ScoreDoc[] resSet = (ScoreDoc[])filtered.toArray(
new ScoreDoc[filtered.size()]);
int hits = set. totalHits - (original. length - resSet. length );
return new TopDocs(hits, resSet, maxScore);
}
}
清单1-调用二级过滤
此类利用我们在[1]中描述的Lucene实现中的优化,允许基于所设置的文档ID读取多个文档。 它依赖于以下事实:HBase中的多获取操作比单个获取的顺序要快。
它还利用抽象空间过滤器接口(清单2)基于与之关联的形状来检查是否接受给定的文档。
package com.navteq.lucene.spatial.geometry.filters; import com.navteq.lucene.spatial.geometry.GeometricShape;
public interface AbstractSpatialFilter {
public abstract boolean accept(GeometricShape shape);
}
清单2-抽象空间过滤器类
尽管我提供了抽象空间过滤器接口的一些基本实现,例如,定界框内的点,圆内的点,定界框内的定界框等,但我们的实现是面向用户驱动的可扩展性的。 每个用户都可以自由实施最适合其需求的空间滤波器。 这种方法还允许从简单的框(例如边界框内的点或边界框等)构建复合空间过滤器[8] 。
进一步改进
此处描述的二级过滤集成(清单1)允许删除一些搜索结果,但不能修改找到的文档的权重。 实际上,在地理空间搜索的情况下,结果权重通常由与中心点的接近度/距离来驱动。 此处介绍的实现(清单1,清单2)可以很容易地修改以支持此要求。
结论
本文介绍的地理空间搜索的两级方法允许创建一个非常强大且可扩展的基于Lucene的地理空间搜索引擎,该引擎可用于解决许多现实世界中的问题。
lucene 空间检索