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. div 纯数字很长时无法折行解决

    <div style="width:100%;word-wrap:break-word;word-spacing:normal;"> </div> 加上红色 ...

  2. thinkphp缓存

    最简答的缓存 // 缓存设置 public function ff(){ S('); } // 缓存读取 public function aa(){ $value = S('name'); echo ...

  3. DEDEcms 在php5.4以上 后台登录空白解决办法

    本次环境php5.5 首先确定 dede data文件夹已经有写入权限 775 777都可以 然后再远程桌面或者FTP修改 include/userlogin.class.php 文件 注释掉下面六句 ...

  4. zabbix3 设置邮件报警(五)

    Zabbix邮件报警配置 一.安装sendmail或者postfix(安装一种即可) yum install sendmail #安装 service sendmail start #启动 chkco ...

  5. KALI Linux problems & Study Red Hat | Ubuntu

    Problem When you ask some website with https head.you may met the problem  secure connection failed ...

  6. T1加权像(T1 weighted image,T1WI)

    T1加权成像(T1-weighted imaging,T1WI)是指这种成像方法重点突出组织纵向弛豫差别,而尽量减少组织其他特性如横向弛豫等对图像的影响. 弛豫:物理用语,从某一个状态恢复到平衡态的过 ...

  7. dos命令行 指令

    盘符: 例如想进入D盘 d: ../ 上一层目录 md 文件夹名 新建文件夹cd 文件夹名 进入到该目录cd.. 返回上一层目录cd\ 返回根目录 cd \windows 进入到当前盘Windows目 ...

  8. protobuf编译出错的解决方案(iOS,OSX)

    protobuf 最近使用protobuf,变编译工具时遇上一点问题.现在附上解决方案 编译过程 完全参照 https://github.com/alexeyxo/protobuf-objc 编译出错 ...

  9. Debian8升级4.5内核

    本文讲述如何升级Debian8的内核到4.5版本 0x01:去linux kernel官网https://www.kernel.org/下载4.5的内核,选择tar.xz格式 0x02:想办法把下载好 ...

  10. (转)CString截取字符串全攻略

    附:去掉空格方法 CString str("e w r we  中华ADF 华AG中"); newstr=str.Replace(" ", "&quo ...