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数据的更多相关文章

  1. Lucene 索引与检索架构图

  2. InnoDB这种行锁实现特点意味者:只有通过索引条件检索数据,InnoDB才会使用行级锁,否则,InnoDB将使用表锁!

    InnoDB行锁是通过索引上的索引项来实现的,这一点MySQL与Oracle不同,后者是通过在数据中对相应数据行加锁来实现的. InnoDB这种行锁实现特点意味者:只有通过索引条件检索数据,InnoD ...

  3. lucene: 索引建立完后无法查看索引文件中的数据

    索引建立时      1.对原有索引文件进行建立,是可以访问索引文件中的数据的      2.建立新索引文件,必须等建立完毕后,才可以访问,新建立的文件如果没有建立完是不可以被访问的     如果想建 ...

  4. lucene索引文件大小优化小结

    http://www.cnblogs.com/LBSer/p/4068864.html 随着业务快速发展,基于lucene的索引文件zip压缩后也接近了GB量级,而保持索引文件大小为一个可以接受的范围 ...

  5. MySQL和Lucene索引对比分析

    MySQL和Lucene都可以对数据构建索引并通过索引查询数据,一个是关系型数据库,一个是构建搜索引擎(Solr.ElasticSearch)的核心类库.两者的索引(index)有什么区别呢?以前写过 ...

  6. lucene索引

    一.lucene索引 1.文档层次结构 索引(Index):一个索引放在一个文件夹中: 段(Segment):一个索引中可以有很多段,段与段之间是独立的,添加新的文档可能产生新段,不同的段可以合并成一 ...

  7. 基于python的Elasticsearch索引的建立和数据的上传

    这是我的第一篇博客,还请大家多多指点 Thanks ♪(・ω・)ノ         今天我想讲一讲关于Elasticsearch的索引建立,当然提前是你已经安装部署好Elasticsearch. ok ...

  8. 基于 Golang 完整获取百度地图POI数据的方案

    百度地图为web开发者提供了基于HTTP/HTTPS协议的丰富接口,其中包括地点检索服务,web开发者通过此接口可以检索区域内的POI数据.百度地图处于数据保护对接口做了限制,每次访问服务,最多只能检 ...

  9. Lucene索引文件学习

     最近在做搜索,抽空看一下lucene,资料挺多的,不过大部分都是3.x了--在对着官方文档大概看一下. 优化后的lucene索引文件(4.9.0) 一.段文件 1.段文件:segments_5p和s ...

随机推荐

  1. 让IE6 IE7 IE8 IE9 IE10 IE11支持Bootstrap的解决方法

    首先需要确保你的HTML页面开始部分要有DOCTYPE声明.DOCTYPE告诉浏览器使用什么样的HTML或XHTML规范来解析HTML文档,具体会影响:对标记attributes .propertie ...

  2. ubuntu安装/卸载mysql

    1.安装mysql root@openstack001:/tmp# apt-get install mysql-server Reading package lists... Done Buildin ...

  3. highcharts去掉版权|去掉水印链接(右下角)_

    credits: {                      enabled: false                  }

  4. jQuery导入Eclipse后报错解决方法

         使用Eclipse 3.7及以上版本时,工程中加入jquery.min.js文件,发现该文件出现错误提示(红×),但使用Eclipse 3.7以前的版本就不会出现这种提示.是因为Eclips ...

  5. 第一次做socket的一些心得

    理论什么的bilibala的就是自己百度吧 推荐一篇不错的关于socket的文章 http://www.cnblogs.com/sunway/archive/2010/01/29/1659074.ht ...

  6. [Machine Learning & Algorithm]CAML机器学习系列2:深入浅出ML之Entropy-Based家族

    声明:本博客整理自博友@zhouyong计算广告与机器学习-技术共享平台,尊重原创,欢迎感兴趣的博友查看原文. 写在前面 记得在<Pattern Recognition And Machine ...

  7. Visifire Chart相关属性详解

    <vc:Chart x:Name="HourlyChart" Theme="Theme1" Grid.Row="1" xmlns:vc ...

  8. SSAS 通过 ETL 自动建立分区

    一.动态分区的好处就不说了,随着时间的推移,不可能一个度量值组都放在一个分区中,处理速度非常慢,如何动态添加分区,如何动态处理分区,成为了很多新手BI工程师一个头痛的问题,废话不多说,分享一下我的经验 ...

  9. __run_timers() -- 处理全部超时定时器

    __run_timers() -- 处理全部超时定时器 run_timer_softirq() --> __run_timers() /usr/src/linux-/kernel/timer.c ...

  10. linux glances的基本使用

    一.Glances介绍 glances 是一款用于 Linux.BSD 的开源命令行系统监视工具,它使用 Python 语言开发,能够监视 CPU.负载.内存.磁盘 I/O.网络流量.文件系统.系统温 ...