使用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 ...
随机推荐
- Swift_提醒框
p.p1 { margin: 0.0px 0.0px 0.0px 0.0px; font: 24.0px Menlo; color: #ffffff } p.p2 { margin: 0.0px 0. ...
- 使用sublime一键格式化XML文件
1 sublime简介 sublime是一款代码编辑和阅读软件,体积小,运行快,界面非常简洁漂亮.官方地址:https://www.sublimetext.com/ 2 在sublime上安装插件 使 ...
- 美团HD(3)-加载分类导航数据
DJHomeViewController.m /** 设置导航栏左侧内容 */ - (void)setupLeftNavItem { // Logo UIImageView *logoView = [ ...
- ls目录内容
ls:列出目录内容 1.常用参数: -a, –all 列出目录下的所有文件,包括以 . 开头的隐含文件(常用) -A 同-a,但不列出“.”(表示当前目录)和“..”(表示当前目录的父目录). -c ...
- Greenplum-概念篇
Greenplum主要组件包括:Master.Segments.Interconnect:其他组件包括ETL Server.Greenplum command center等.0. 组件之-Maste ...
- [NOIP2016]愤怒的小鸟
题目描述 Kiana最近沉迷于一款神奇的游戏无法自拔. 简单来说,这款游戏是在一个平面上进行的. 有一架弹弓位于(0,0)处,每次Kiana可以用它向第一象限发射一只红色的小鸟,小鸟们的飞行轨迹均为形 ...
- 关于C#联接数据库是出现'未在本地计算机上注册'错误的解决办法
今天在用c#连接access数据库处理数据的时候遇到了一个诡异的问题, 未在本地计算机上注册"Microsoft.ACE.OLEDB.12.0"提供程序 我们的部分代码如下: st ...
- 【转】linux查看及修改文件权限以及相关
linux查看及修改文件权限以及相关 查看文件权限的语句: 在终端输入: ls -l xxx.xxx (xxx.xxx是文件名) 那么就会出现相类似的信息,主要都是这些: -rw-rw-r-- 一共有 ...
- collections 模块(namedtuple, deque, Counter )
基本介绍 我们都知道,Python拥有一些内置的数据类型,比如str, int, list, tuple, dict等, collections模块在这些内置数据类型的基础上,提供了几个额外的数据类型 ...
- CapsLock与ctrl的键位修改
windows下修改方式: linux下修改方式: 在用户目录下新建文档命名为keychange.sh 编辑以下内容: remove Lock = Caps_Lock remove Control = ...