【IT168 技术文档】
实现思路是根据文斌提供的。而本文实现的这个周边搜索的概念也只不过是作为文斌实现的引擎中的微不足道的一部分。在此对文斌的指导表示感谢!
对于基于空间的索引,这个思路在lucene in action 的第六章的第一个例子就是说明怎么样使用lucene查找空间信息。提出的需要解决问题是“what mexican food restaurant is nearest to me?”;解决的思路是使用lucene的过滤器功能。而我的思路也是这样,就是提供一个filter来对Lucene的索引文件进行过滤。
过滤作为一种缩小lucene搜索范围的机制,把可能的结果限制在所有文档的一个子集中。可以用来实现基于权限的搜索和在搜索结果中再次搜索的特性。
当谈及到具体的实现的时候,关键是filter本身是基于标志位设定。
过滤器部分实现的代码如下:
/** *//**
*
* @author lang
* @date 2007-12-19
* @email lanfanss@126.com
* @desc 按着一定的空间位置来提供一个过滤器
* @since
*
*/
public class SpatialFilter extends Filter {
int lo;
int la;
int scope;
Quadtree quadtree;
public SpatialFilter(int lo, int la, int scope, Quadtree quadtree) {
super();
this.lo = lo;
this.la = la;
this.scope = scope;
this.quadtree = quadtree;
}
@Override
public BitSet bits(IndexReader reader) throws IOException {
final BitSet bits = new BitSet(reader.maxDoc());
// 设置所有的文档都是不能够检索到的
bits.set(0, bits.size() - 1, false);
// 判断scope的范围,如果不合理,就使用默认的500米
if (scope <= 0) {
scope = 500;
}
// 目前先使用一个简单的矩形框作为范围搜索
List<String> ids = quadtree.query(new Envelope(new Coordinate(lo
- scope, la - scope), new Coordinate(lo + scope, la + scope)));
for (String id : ids) {
// 矩形范围内的点是可以被Lucene搜索到的
bits.set(Integer.valueOf(id).intValue(), true);
}
return bits;
}
实现一个lucene的Filter接口,必须重写lits方法。在我的这个实现中,利用构造函数中注入的quadtree来对某个经纬度周边一定范围内的点来搜索。在细节上,我首先把索引中的所有的文档的都设置成不可见的(也就是都不符合过滤得标准)。然后使用空间索引来搜索周围一定范围内部的点,然后再次将这些点设置成为可见的。这样就实现了将搜索范围框定在一个空间内部的目的。
从更深次的需求考虑,这个搜索只是利用了jts的空间搜索的方法,而Jts的这个方法是基于矩形搜索的,所以,需要在实际的项目中改进成基于不规则图形搜索。不过这一点,我目前不会。如果各位看官有建设性意见,请不吝赐教才好!
有了过滤器,这个问题的难点部分就过去了,接下来就是怎么组织一个基于Lucene的搜索类了。
对于这个问题,我是如下的想法。这个类是一个单态类,并且在构造时候将jts的空间索引建立好。这个类应该提供基于多态的search方法。接下来分成三部分来阐述。
第一部分是构造函数。这一部分中最关键的要构造出来一个空间索引。
public class SearchWithSpatial {
private static org.apache.commons.logging.Log log = LogFactory
.getLog(SearchWithSpatial.class);
/** *//**
* 索引的路径
*/
final String INDEX_PATH = "lucene_index";
Directory directory = null;
private IndexSearcher search = null;
/** *//**
* 空间索引
*/
Quadtree quadtree = null;;
/** *//**
* 索引实例
*/
private static SearchWithSpatial instance = null;
/** *//**
* 读取索引
*/
IndexReader reader = null;
/** *//**
* 私有构造函数
*/
private SearchWithSpatial() {
// 将文件中的索引加载到内存中来
try {
directory = new RAMDirectory(FSDirectory.getDirectory(INDEX_PATH));
search = new IndexSearcher(directory);
// 将所有的文档建立在内存中的空间引擎中,空间引擎使用的jts的quatree
quadtree = new Quadtree();
reader = IndexReader.open(INDEX_PATH);
Document document = null;
for (int i = 0, p = reader.numDocs(); i < p; i++) {
// 将文本中的所有的点都建在空间索引中
document = reader.document(i);
quadtree.insert(new Envelope(new Coordinate(new Integer(
document.get("lo")), new Integer(document.get("la")))),
i + "");
}
// FIXME 考虑使用软引用,使这个类牵涉到的内存尽量的少
} catch (IOException e) {
log.error("请求的索引文件不存在!");
e.printStackTrace();
}
}
public static SearchWithSpatial getInstance() {
if (instance == null) {
synchronized (SearchWithSpatial.class) {
if (instance == null) {
instance = new SearchWithSpatial();
}
}
}
return instance;
}
在实现细节上,使用IndexReader循环读出lucene的索引,然后填充进空间索引,此时需要注意的是空间索引中对于位置的附加信息是lucene中索引的排序号。这个序号在过滤器中会被使用到。其他的细节就是实现了这个类的单态模式。
第二步是提供两个函数供查询用。两个函数的区别就是,一个是只查找name,另外一个可以查找更多的字段。没什么需要说明的,代码很简单。
/** *//**
* 提供基于关键词的查询功能
*
* @param value
*/
// FIXME 返回结果目前没有想好呢
public Hits search(String value) {
// 构建一个基于多字段的搜索
Query query;
try {
query = new MultiFieldQueryParser(new String[] { "name", "cla",
"dis", "add" }, new MMAnalyzer()).parse(value);
return search.search(query);
} catch (ParseException e) {
e.printStackTrace();
} catch (IOException e) {
e.printStackTrace();
}
return null;
}
/** *//**
* 提供基于关键词的查询功能
*
* @param value
*/
// FIXME 返回结果目前没有想好呢
public Hits searchByName(String value) {
Query query;
try {
query = new QueryParser("name", new MMAnalyzer()).parse(value);
return search.search(query);
} catch (ParseException e) {
e.printStackTrace();
} catch (IOException e) {
e.printStackTrace();
}
return null;
}第三步是提供基于空间的关键词搜索。先看一下代码。
/** *//**
*
* @param lo
* 中心点的精度
* @param la
* 中心点的维度
* @param value
* 所要查找的单词
* @param scope
* 范围
*/
public Hits search(int lo, int la, String value, int scope) {
// 首先需要实现出来一个filter
// 感觉需要将quatree传入似乎是不合理的
Filter filter = new SpatialFilter(lo, la, scope, quadtree);
// 构建一个基于多字段的搜索
Query query;
try {
query = new MultiFieldQueryParser(new String[] { "name", "cla",
"dis", "add" }, new MMAnalyzer()).parse(value);
return search.search(query, filter);
} catch (ParseException e) {
e.printStackTrace();
} catch (IOException e) {
e.printStackTrace();
}
return null;
当中就是先实现了一个过滤器,然后将这个过滤器传入到search方法中。
到此,关于周边搜索的思路全部论述完成。个人感觉也就不过尔尔,拾人牙慧。希望对你有用。
下边给出一个测试代码。作为结束。
另外,测试结果如下:北京,23万数据,500米周边搜索,pentium(R)4 2.93Ghz cpu 1G内存,测试结果是50毫秒。
1public static void main(String[] args) {
2 SearchWithSpatial search = SearchWithSpatial.getInstance();
3 Hits hits = null;
4 String[] names = new String[] { "上地", "天安门","中关村","王府井" };
5 for (int j = 0; j < 4; j++) {
6
7 long start = System.currentTimeMillis();
8 hits = search.search(names[j]);
9 if (hits.length() > 0) {
10 try {
11 Document document = hits.doc(0);
12 String lo = document.get("lo").toString();
13 String la = document.get("la").toString();
14 System.out.println("在 " + document.get("name") + " 周围搜索 :");
15 hits = search.search(Integer.valueOf(lo).intValue(),
16 Integer.valueOf(la).intValue(), "公司", 500);
17 for (int i = 0, p = hits.length(); i < p; i++) {
18 document = hits.doc(i);
19 System.out.println(document.get("name"));
20 }
21 System.out.println(System.currentTimeMillis() - start);
22 } catch (CorruptIndexException e) {
23 e.printStackTrace();
24 } catch (IOException e) {
25 e.printStackTrace();
26 }
27 }
28 }