分类: Java开发

Lucene7.3.1 依赖 spatial3d 建立 GEO 索引

关于 Lucene

Lucene是一款高性能的、可扩展的信息检索(IR)工具库。信息检索是指文档搜索、文档内信息搜索或者文档相关的元数据搜索等操作。

更多信息参见官网

可能 Solr 或者 ElasticSearch 等成品的应用越来越多导致直接基于 Lucene 的应用太少了,文档也几乎没有,既然做完了,就在此处把过程简单记一下

先建索引

测试数据量不大,就直接放内存了:

Directory idx = new RAMDirectory();
Analyzer analyzer = new StandardAnalyzer();
IndexWriterConfig iwc = new IndexWriterConfig(analyzer);
iwc.setOpenMode(IndexWriterConfig.OpenMode.CREATE);
IndexWriter writer = new IndexWriter(idx, iwc);

造点假数据 :

for(int i = 0; i < 100000; i ++) {
    Document doc = new Document();
    double lat = RandomUtils.nextDouble(0, 180) - 90;
    double lon = RandomUtils.nextDouble(0, 360) - 180;
    Geo3DPoint point = new Geo3DPoint("location", lat, lon);
    doc.add(point);
    doc.add(new StringField("lat", String.valueOf(lat), Store.YES));
    doc.add(new StringField("lon", String.valueOf(lon), Store.YES));
    doc.add(new Geo3DDocValuesField("location", new GeoPoint(PlanetModel.WGS84, lat * Math.PI / 180.0, lon * Math.PI / 180.0)));
    writer.addDocument(doc);
}
writer.close();

在全地图范围内随机十万个点并建立索引,这段代码有两个注意点,一个是搜索的数据不代码索引存储的数据,另一个就是 Geo3D 有个坐标转换 lat * Math.PI / 180.0

按中心点搜索

double centerLat = RandomUtils.nextDouble(0, 180) - 90;
double centerLon = RandomUtils.nextDouble(0, 360) - 180;
TopDocs top = searcher.search(Geo3DPoint.newDistanceQuery("location", centerLat, centerLon, 5000), 100);
System.out.println(String.format("基于(%s,%s)找到%s条记录", centerLat, centerLon, top.totalHits));
for(ScoreDoc score : top.scoreDocs) {
    Document doc = searcher.doc(score.doc);
    double lat = Double.parseDouble(doc.get("lat"));
    double lon = Double.parseDouble(doc.get("lon"));
    System.out.println(String.format("坐标 (%s,%s) 距离 %s m", lat, lon, getDistance(lon, lat, centerLon, centerLat)));
}

随机了两个坐标,然后查询周围数据,查询完成后计算距离输出,可以观察到日志:

基于(-89.96952956643,-11.260478505556932)找到16条记录
坐标 (-89.99264542696646,-70.76693088287959) 距离 3058.0 m
坐标 (-89.97647597846868,58.716452188881476) 距离 3504.0 m
坐标 (-89.97365195452447,-22.01130538613026) 距离 748.0 m
坐标 (-89.95052374989857,-71.74465074455922) 距离 4840.0 m
坐标 (-89.93040495551264,14.247534646989976) 距离 4908.0 m
坐标 (-89.97361220682883,-32.565946847449084) 距离 1252.0 m
坐标 (-89.94207593524472,-59.4539002073804) 距离 4891.0 m
.....

可以看到查询出来的坐标已经全是附近的了,我们可以更进一步按距离排序。

按距离排序

double centerLat = RandomUtils.nextDouble(0, 180) - 90;
double centerLon = RandomUtils.nextDouble(0, 360) - 180;
TopDocs top = searcher.search(Geo3DPoint.newDistanceQuery("location", centerLat, centerLon, 5000), 100,
        new Sort(Geo3DDocValuesField.newDistanceSort("location", centerLat, centerLon, 6378137)));
System.out.println(String.format("基于(%s,%s)找到%s条记录", centerLat, centerLon, top.totalHits));
for(ScoreDoc score : top.scoreDocs) {
    Document doc = searcher.doc(score.doc);
    double lat = Double.parseDouble(doc.get("lat"));
    double lon = Double.parseDouble(doc.get("lon"));
    System.out.println(String.format("坐标 (%s,%s) 距离 %s m", lat, lon, getDistance(lon, lat, centerLon, centerLat)));
}

仍然取随机坐标然后按距离排序,代码 Geo3DPoint.newDistanceQuery("location", centerLat, centerLon, 5000) 创建了距离查询对象,更复杂的搜索可以通过 BooleanQuery.Builder 添加查询条件,具体可以查看对应 API。

附上日志:

基于(-89.94187801375949,-141.630961986715)找到14条记录
坐标 (-89.92912471546683,-138.06161574744138) 距离 1487.0 m
坐标 (-89.93344854208907,-131.706273535846) 距离 1521.0 m
坐标 (-89.95792761673655,-130.90964750288336) 距离 2061.0 m
坐标 (-89.93276085756439,-161.3964521546222) 距离 2595.0 m
坐标 (-89.96597938765674,-131.0529298950386) 距离 2833.0 m
坐标 (-89.92140038655656,-158.78326898789038) 距离 3198.0 m
坐标 (-89.96591086351229,-110.18049893419807) 距离 3790.0 m
坐标 (-89.97102483776298,-116.27884741223299) 距离 3814.0 m
...

看日志已经返回从近到远,排序好的附近商家了。

All Done!

完美,附距离计算代码如下:

public static double getDistance(double lng1, double lat1, double lng2, double lat2) {

    double radLat1 = Math.toRadians(lat1);
    double radLat2 = Math.toRadians(lat2);
    double a = radLat1 - radLat2;
    double b = Math.toRadians(lng1) - Math.toRadians(lng2);
    double s = 2 * Math.asin(Math.sqrt(Math.pow(Math.sin(a/2),2) + 
    Math.cos(radLat1)*Math.cos(radLat2)*Math.pow(Math.sin(b/2),2)));
    s = s * 6378137;  
    return Math.round(s * 10000) / 10000;  
}

关于魔数 6378137 这是地球的赤道半径,也是 3D 模型到 地球实体的映射倍数。

Recent Posts

Docker 容器非 root 用户监听 80 端口

起因是基于 CentOS 的 …

2 年 之前

基于 Docker 定时打印文件

先说背景,喷墨打印机有个很大的…

3 年 之前

Java 运行时反射获取来自继承的泛型

背景 正常情况下 Java 的…

3 年 之前

Java 基于 ByteBuddy 重写系统当前时间

背景 一般单元测试时总会有些代…

3 年 之前

华硕 B450F-Gaming 主板 I211-AT 网卡驱动安装

事情起因是买了块华硕的 ROG…

3 年 之前

PHP 安装 Memcached 扩展

登录服务器挨步执行: # su…

4 年 之前