使用Lucene索引和检索POI数据
1、简介
关于空间数据搜索,以前写过《使用Solr进行空间搜索》这篇文章,是基于Solr的GIS数据的索引和检索。
Solr和ElasticSearch这两者都是基于Lucene实现的,两者都可以进行空间搜索(Spatial Search),在有些场景,我们需要把Lucene嵌入到已有的系统提供数据索引和检索的功能,这篇文章介绍下用Lucene如何索引带有经纬度的POI信息并进行检索。
2、环境数据
Lucene版本:5.3.1
POI数据库:Base_Station测试数据,每条数据主要是ID,经纬度和地址。
3、实现
基本变量定义,这里对“地址”信息进行了分词,分词使用了Lucene自带的smartcnSmartChineseAnalyzer。
private String indexPath = "D:/IndexPoiData";
private IndexWriter indexWriter = null;
private SmartChineseAnalyzer analyzer = new SmartChineseAnalyzer(true); private IndexSearcher indexSearcher = null; // Field Name
private static final String IDFieldName = "id";
private static final String AddressFieldName = "address";
private static final String LatFieldName = "lat";
private static final String LngFieldName = "lng";
private static final String GeoFieldName = "geoField"; // Spatial index and search
private SpatialContext ctx;
private SpatialStrategy strategy; public PoiIndexService() throws IOException {
init();
} public PoiIndexService(String indexPath) throws IOException {
this.indexPath = indexPath;
init();
} protected void init() throws IOException {
Directory directory = new SimpleFSDirectory(Paths.get(indexPath));
IndexWriterConfig config = new IndexWriterConfig(analyzer);
indexWriter = new IndexWriter(directory, config); DirectoryReader ireader = DirectoryReader.open(directory);
indexSearcher = new IndexSearcher(ireader); // Typical geospatial context
// These can also be constructed from SpatialContextFactory
ctx = SpatialContext.GEO; int maxLevels = 11; // results in sub-meter precision for geohash
// This can also be constructed from SpatialPrefixTreeFactory
SpatialPrefixTree grid = new GeohashPrefixTree(ctx, maxLevels); strategy = new RecursivePrefixTreeStrategy(grid, GeoFieldName);
}
索引数据
public boolean indexPoiDataList(List<PoiData> dataList) {
try {
if (dataList != null && dataList.size() > 0) {
List<Document> docs = new ArrayList<>();
for (PoiData data : dataList) {
Document doc = new Document();
doc.add(new LongField(IDFieldName, data.getId(), Field.Store.YES));
doc.add(new DoubleField(LatFieldName, data.getLat(), Field.Store.YES));
doc.add(new DoubleField(LngFieldName, data.getLng(), Field.Store.YES));
doc.add(new TextField(AddressFieldName, data.getAddress(), Field.Store.YES));
Point point = ctx.makePoint(data.getLng(),data.getLat());
for (Field f : strategy.createIndexableFields(point)) {
doc.add(f);
}
docs.add(doc);
}
indexWriter.addDocuments(docs);
indexWriter.commit();
return true;
}
return false;
} catch (Exception e) {
log.error(e.toString());
return false;
}
}
这里的PoiData是个普通的POJO。
检索圆形范围内的数据,按距离从近到远排序:
public List<PoiData> searchPoiInCircle(double lng, double lat, double radius){
List<PoiData> results= new ArrayList<>();
Shape circle = ctx.makeCircle(lng, lat, DistanceUtils.dist2Degrees(radius, DistanceUtils.EARTH_MEAN_RADIUS_KM));
SpatialArgs args = new SpatialArgs(SpatialOperation.Intersects, circle);
Query query = strategy.makeQuery(args);
Point pt = ctx.makePoint(lng, lat);
ValueSource valueSource = strategy.makeDistanceValueSource(pt, DistanceUtils.DEG_TO_KM);//the distance (in km)
Sort distSort = null;
TopDocs docs = null;
try {
//false = asc dist
distSort = new Sort(valueSource.getSortField(false)).rewrite(indexSearcher);
docs = indexSearcher.search(query, 10, distSort);
} catch (IOException e) {
log.error(e.toString());
} if(docs!=null){
ScoreDoc[] scoreDocs = docs.scoreDocs;
printDocs(scoreDocs);
results = getPoiDatasFromDoc(scoreDocs);
} return results;
} private List<PoiData> getPoiDatasFromDoc(ScoreDoc[] scoreDocs){
List<PoiData> datas = new ArrayList<>();
if (scoreDocs != null) {
//System.out.println("总数:" + scoreDocs.length);
for (int i = 0; i < scoreDocs.length; i++) {
try {
Document hitDoc = indexSearcher.doc(scoreDocs[i].doc);
PoiData data = new PoiData();
data.setId(Long.parseLong((hitDoc.get(IDFieldName))));
data.setLng(Double.parseDouble(hitDoc.get(LngFieldName)));
data.setLat(Double.parseDouble(hitDoc.get(LatFieldName)));
data.setAddress(hitDoc.get(AddressFieldName));
datas.add(data);
} catch (IOException e) {
log.error(e.toString());
}
}
} return datas;
}
搜索矩形范围内的数据:
public List<PoiData> searchPoiInRectangle(double minLng, double minLat, double maxLng, double maxLat) {
List<PoiData> results= new ArrayList<>();
Point lowerLeftPoint = ctx.makePoint(minLng, minLat);
Point upperRightPoint = ctx.makePoint(maxLng, maxLat);
Shape rect = ctx.makeRectangle(lowerLeftPoint, upperRightPoint);
SpatialArgs args = new SpatialArgs(SpatialOperation.Intersects, rect);
Query query = strategy.makeQuery(args);
TopDocs docs = null;
try {
docs = indexSearcher.search(query, 10);
} catch (IOException e) {
log.error(e.toString());
} if(docs!=null){
ScoreDoc[] scoreDocs = docs.scoreDocs;
printDocs(scoreDocs);
results = getPoiDatasFromDoc(scoreDocs);
} return results;
}
搜索某个范围内并根据地址关键字信息来检索POI:
public List<PoiData>searchPoByRangeAndAddress(doublelng, doublelat, double range, String address){
List<PoiData> results= newArrayList<>();
SpatialArgsargs = newSpatialArgs(SpatialOperation.Intersects,
ctx.makeCircle(lng, lat, DistanceUtils.dist2Degrees(range, DistanceUtils.EARTH_MEAN_RADIUS_KM)));
Query geoQuery = strategy.makeQuery(args); QueryBuilder builder = newQueryBuilder(analyzer);
Query addQuery = builder.createPhraseQuery(AddressFieldName, address); BooleanQuery.BuilderboolBuilder = newBooleanQuery.Builder();
boolBuilder.add(addQuery, Occur.SHOULD);
boolBuilder.add(geoQuery,Occur.MUST); Query query = boolBuilder.build(); TopDocs docs = null;
try {
docs = indexSearcher.search(query, 10);
} catch (IOException e) {
log.error(e.toString());
} if(docs!=null){
ScoreDoc[] scoreDocs = docs.scoreDocs;
printDocs(scoreDocs);
results = getPoiDatasFromDoc(scoreDocs);
} return results;
}
4、关于分词
POI的地址属性和描述属性都需要做分词才能更好的进行检索和搜索。
简单对比了几种分词效果:
原文:
这是一个lucene中文分词的例子,你可以直接运行它!Chinese Analyer can analysis english text too.中国农业银行(农行)和建设银行(建行),江苏南京江宁上元大街12号。东南大学是一所985高校。
分词结果:
smartcn SmartChineseAnalyzer 这\是\一个\lucen\中文\分\词\的\例子\你\可以\直接\运行\它\chines\analy\can\analysi\english\text\too\中国\农业\银行\农行\和\建设\银行\建行\江苏\南京\江\宁\上\元\大街\12\号\东南\大学\是\一\所\985\高校\ MMSegAnalyzer ComplexAnalyzer 这是\一个\lucene\中文\分词\的\例子\你\可以\直接\运行\它\chinese\analyer\can\analysis\english\text\too\中国农业\银行\农行\和\建设银行\建\行\江苏南京\江\宁\上\元\大街\12\号\东南大学\是一\所\985\高校\ IKAnalyzer 这是\一个\lucene\中文\分词\的\例子\你\可以\直接\运行\它\chinese\analyer\can\analysis\english\text\too.\中国农业银行\农行\和\建设银行\建行\江苏\南京\江宁\上元\大街\12号\东南大学\是\一所\985\高校\
分词效果对比:
1)Smartcn不能正确的分出有些英文单词,有些中文单词也被分成单个字。
2)MMSegAnalyzer能正确的分出英文和中文,但对于类似“江宁”这样的地名和“建行”等信息不是很准确。MMSegAnalyzer支持自定义词库,词库可以大大提高分词的准确性。
3)IKAnalyzer能正确的分出英文和中文,中文分词比较不错,但也有些小问题,比如单词too和最后的点号分在了一起。IKAnalyzer也支持自定义词库,但是要扩展一些源码。
总结:使用Lucene强大的数据索引和检索能力可以为一些带有经纬度和需要分词检索的数据提供搜索功能。
代码托管在GitHub上:https://github.com/luxiaoxun/Code4Java
使用Lucene索引和检索POI数据的更多相关文章
- Lucene 索引与检索架构图
- InnoDB这种行锁实现特点意味者:只有通过索引条件检索数据,InnoDB才会使用行级锁,否则,InnoDB将使用表锁!
InnoDB行锁是通过索引上的索引项来实现的,这一点MySQL与Oracle不同,后者是通过在数据中对相应数据行加锁来实现的. InnoDB这种行锁实现特点意味者:只有通过索引条件检索数据,InnoD ...
- lucene: 索引建立完后无法查看索引文件中的数据
索引建立时 1.对原有索引文件进行建立,是可以访问索引文件中的数据的 2.建立新索引文件,必须等建立完毕后,才可以访问,新建立的文件如果没有建立完是不可以被访问的 如果想建 ...
- lucene索引文件大小优化小结
http://www.cnblogs.com/LBSer/p/4068864.html 随着业务快速发展,基于lucene的索引文件zip压缩后也接近了GB量级,而保持索引文件大小为一个可以接受的范围 ...
- MySQL和Lucene索引对比分析
MySQL和Lucene都可以对数据构建索引并通过索引查询数据,一个是关系型数据库,一个是构建搜索引擎(Solr.ElasticSearch)的核心类库.两者的索引(index)有什么区别呢?以前写过 ...
- lucene索引
一.lucene索引 1.文档层次结构 索引(Index):一个索引放在一个文件夹中: 段(Segment):一个索引中可以有很多段,段与段之间是独立的,添加新的文档可能产生新段,不同的段可以合并成一 ...
- 基于python的Elasticsearch索引的建立和数据的上传
这是我的第一篇博客,还请大家多多指点 Thanks ♪(・ω・)ノ 今天我想讲一讲关于Elasticsearch的索引建立,当然提前是你已经安装部署好Elasticsearch. ok ...
- 基于 Golang 完整获取百度地图POI数据的方案
百度地图为web开发者提供了基于HTTP/HTTPS协议的丰富接口,其中包括地点检索服务,web开发者通过此接口可以检索区域内的POI数据.百度地图处于数据保护对接口做了限制,每次访问服务,最多只能检 ...
- Lucene索引文件学习
最近在做搜索,抽空看一下lucene,资料挺多的,不过大部分都是3.x了--在对着官方文档大概看一下. 优化后的lucene索引文件(4.9.0) 一.段文件 1.段文件:segments_5p和s ...
随机推荐
- (转载)详解网络传输中的三张表,MAC地址表、ARP缓存表以及路由表
郑重声明:原文转载于http://dengqi.blog.51cto.com/5685776/1223132 向好文章致敬!!! 一:MAC地址表详解 说到MAC地址表,就不得不说一下交换机的工作原理 ...
- 使用powershell批量添加Keil和IAR的头文件路径
在Keil和IAR的工程中,为了使文件结构清晰,通常会设置很多的子文件夹,然后将头文件和源文件放在不同的子文件夹中,这样就需要手动添加这些头文件夹的路径.当工程结构非常复杂时,文件夹的数量就非常多,特 ...
- Eclipse中的搜索快捷键
Ctrl+H 全文搜索Ctrl+F 当前文件Ctrl+Shift+T 类文件Ctrl+Shift+R 资源文件
- 第五章 --- 关于Javascript 设计模式 之 发布-订阅模式
先来个最简单的 发布订阅模式 document.body.addEventListener('click',function(){ alert(123); }); document.body.clic ...
- 《锋利的jQuery(第2版)》笔记-第1章-认识jQuery
jQuery是随着Web2.0兴起的JavaScript库之一,因为其独特的优点,受到越来越多人的追捧! 1.1 JavaScript和JavaScript库 1.1.1 JavaScript简介 J ...
- [Algorithm & NLP] 文本深度表示模型——word2vec&doc2vec词向量模型
深度学习掀开了机器学习的新篇章,目前深度学习应用于图像和语音已经产生了突破性的研究进展.深度学习一直被人们推崇为一种类似于人脑结构的人工智能算法,那为什么深度学习在语义分析领域仍然没有实质性的进展呢? ...
- JavaScript - 原型
一切皆为对象 殊不知,JavaScript的世界中的对象,追根溯源来自于一个 null 「一切皆为对象」,这句着实是一手好营销,易记,易上口,印象深刻. 万物初生时,一个null对象,凭空而生,接着O ...
- Datalogic组网模式下通讯
1.首先要在visiset工具下,设置好地址端口号,组网模式master slave参数: 2.打开工具hercules,选择TCP Client选项,设置参数好连接并通讯,发送打开.关闭 按钮指令, ...
- linux 比较两个文件是否一致
diff source target 如果一致不弹出任何信息
- Python: open和codecs.open
python的编解码: input文件(gbk, utf-8...) ----decode-----> unicode -------encode------> output文件 ...