有次在一个项目中有人提出了一种基于LBS业务的搜索的技术,lucene spatial搜索。随后在网上进行了大搜索。只搜索出一些lucene3代的小例子。现在lucene已经发到4.6了,3代明显太落后了。所以,进行lucene4 spatial的例子搜索,很不幸:使用的人太少了,没有一个例子。就连官网上也没有太多的说明。
由于没有仔细观看官网关于spatial模块的说明,未发现有用东西。一次不经意找到一行。位于API_Javadocs中spatial search的index.html上“
For some sample code showing how to use the API, see SpatialExample.java in the tests.”
终于找到了入门的法宝。
SpatialExample
下面对其中使用到的核心类作下说明:
SpatialContext:空间模块上下文,们于spatial4j包中,一个全局单例对象。它是spatial4j的入口。它提供读写Shape对象的通道。
Shape:形状接口。
SpatialStrategy:lucene spatial模块提供创建查询Shape对象索途径。
package com.spatial.client;
import java.io.File;
import java.io.IOException;
import org.apache.lucene.document.Document;
import org.apache.lucene.document.Field;
import org.apache.lucene.document.IntField;
import org.apache.lucene.document.StoredField;
import org.apache.lucene.document.TextField;
import org.apache.lucene.index.DirectoryReader;
import org.apache.lucene.index.IndexReader;
import org.apache.lucene.index.IndexWriter;
import org.apache.lucene.index.IndexWriterConfig;
import org.apache.lucene.index.IndexableField;
import org.apache.lucene.index.Term;
import org.apache.lucene.search.Filter;
import org.apache.lucene.search.IndexSearcher;
import org.apache.lucene.search.Query;
import org.apache.lucene.search.ScoreDoc;
import org.apache.lucene.search.TermQuery;
import org.apache.lucene.search.TopDocs;
import org.apache.lucene.spatial.SpatialStrategy;
import org.apache.lucene.spatial.prefix.RecursivePrefixTreeStrategy;
import org.apache.lucene.spatial.prefix.tree.GeohashPrefixTree;
import org.apache.lucene.spatial.prefix.tree.SpatialPrefixTree;
import org.apache.lucene.spatial.query.SpatialArgs;
import org.apache.lucene.spatial.query.SpatialOperation;
import org.apache.lucene.store.Directory;
import org.apache.lucene.store.FSDirectory;
import org.apache.lucene.util.Version;
import com.chenlb.mmseg4j.analysis.SimpleAnalyzer;
import com.spatial4j.core.context.SpatialContext;
import com.spatial4j.core.distance.DistanceUtils;
import com.spatial4j.core.shape.Shape;
public class LuceneSpatialExample {
public static void main(String[] args) throws IOException {
new LuceneSpatialExample().test();
}
public void test() throws IOException {
init();
createIndex();
search();
}
private SpatialContext ctx;//"ctx" is the conventional variable name
private SpatialStrategy strategy;
private Directory directory;
private String indexPath = "E:\\index\\spatial\\";
protected void init() {
//Typical geospatial context
// These can also be constructed from SpatialContextFactory
this.ctx = SpatialContext.GEO;
int maxLevels = 11;//results in sub-meter precision for geohash
//TODO demo lookup by detail distance
// This can also be constructed from SpatialPrefixTreeFactory
SpatialPrefixTree grid = new GeohashPrefixTree(ctx, maxLevels);
this.strategy = new RecursivePrefixTreeStrategy(grid, "myGeoField");
try {
this.directory = FSDirectory.open(new File(indexPath));
} catch (IOException e) {
e.printStackTrace();
}
}
private void createIndex() throws IOException {
IndexWriterConfig iwConfig = new IndexWriterConfig(Version.LUCENE_40,new SimpleAnalyzer());
IndexWriter indexWriter = new IndexWriter(directory, iwConfig);
//Spatial4j is x-y order for arguments|对于参数的顺序是Longitude,Latitude
indexWriter.addDocument(newSampleDocument(0, "一星大饭店",ctx.makePoint(116.430360,39.939686)));
indexWriter.addDocument(newSampleDocument(1, "二星大饭店",ctx.makePoint( 116.430319,39.939702)));
indexWriter.addDocument(newSampleDocument(2, "三星大饭店",ctx.makePoint(116.430459,39.939802)));
indexWriter.addDocument(newSampleDocument(3, "四星大饭店", ctx.makePoint(116.430449,39.939902)));
indexWriter.addDocument(newSampleDocument(4, "六星大饭店",ctx.makePoint(116.430439,39.93402)));
indexWriter.addDocument(newSampleDocument(5, "七星大饭店",ctx.makePoint(116.430419,39.934102)));
indexWriter.addDocument(newSampleDocument(6, "五星大饭店",ctx.makePoint(116.430429,39.934202)));
indexWriter.addDocument(newSampleDocument(6, "五星大酒店",ctx.makePoint(115.430429,39.934202)));
indexWriter.commit();
indexWriter.close();
}
private Document newSampleDocument(int id,String keyword, Shape... shapes) {
Document doc = new Document();
doc.add(new IntField("id", id, Field.Store.YES));
//Potentially more than one shape in this field is supported by some
// strategies; see the javadocs of the SpatialStrategy impl to see.
for (Shape shape : shapes) {
for (IndexableField f : strategy.createIndexableFields(shape)) {
doc.add(f);
}
//store it too; the format is up to you
doc.add(new StoredField(strategy.getFieldName(), ctx.toString(shape)));
}
doc.add(new TextField("keyword",keyword, Field.Store.YES));
return doc;
}
private void search() throws IOException {
IndexReader indexReader = DirectoryReader.open(directory);
IndexSearcher indexSearcher = new IndexSearcher(indexReader);
{
//16米范围内
SpatialArgs args = new SpatialArgs(SpatialOperation.Intersects, ctx.makeCircle(116.430459,39.939802, DistanceUtils.dist2Degrees(0.016,DistanceUtils.EARTH_MEAN_RADIUS_KM)));
Filter filter = strategy.makeFilter(args);
Term term = new Term("keyword","饭店");
Query query001 = new TermQuery(term);
TopDocs docs = indexSearcher.search(query001,filter,1000);
System.out.println(docs.totalHits);
ScoreDoc[] scoreDoc = docs.scoreDocs;
if(docs.totalHits>0)
for (int i = 0; i < scoreDoc.length; i++) {
int doc = scoreDoc[i].doc;
Document mydoc = indexReader.document(doc);
System.out.println(mydoc.get("myGeoField"));
String value = mydoc.get("myGeoField");
String []lonlat = value.split(" ");
double eLat = Double.valueOf(lonlat[1]);
double eLon = Double.valueOf(lonlat[0]);
System.out.println(SpatialUtils.getDistance(39.939802, 116.430459, eLat, eLon, false)+"|keyword: "+mydoc.get("keyword"));
}
}
indexReader.close();
}
}
lucene spatial 模块本身就是一个Filter的实现。核心还是lucene的全文检索。真正理解学习新框架或新开源的东西就得从TestCase看起。