现在手机APP满天飞,我想大家都用过这个功能:【搜索我附近的饭店或宾馆】之类的功能,类似这样的地理位置搜索功能非常适用,因为它需要利用到用户当前的地理位置数据,是以用户角度出发,找到符合用户自身需求的信息,应用返回的信息对于用户来说满意度会比较高,可见,地理位置空间搜索在提高用户体验方面有至关重要的作用。在Lucene中,地理位置空间搜索是借助Spatial模块来实现的。
要实现地理位置空间搜索,我们首先需要对地理位置数据创建索引,比较容易想到的就是把经度和纬度存入索引,可是这样做,有个弊端,因为地理位置数据(经纬度)是非常精细的,一般两个地点相差就0.0几,这样我们需要构建的索引体积会很大,这会显著减慢你的搜索速度。在精确度上采取折衷的方法通常是将纬度和经度封装到层中。您可以将每个层看作是地图的特定部分的缩放级别,比如位于美国中央上方的第 2 层几乎包含了整个北美,而第 19 层可能只是某户人家的后院。尤其是,每个层都将地图分成 2层 # 的箱子或网格。然后给每个箱子分配一个号码并添加到文档索引中。如果希望使用一个字段,那么可以使用 Geohash编码方式将纬度/经度编码到一个 String 中。Geohash 的好处是能够通过切去散列码末尾的字符来实现任意的精度。在许多情况下,相邻的位置通常有相同的前缀。
同样比较重要的,就是距离计算,给定两个坐标点需要你计算这两个点之间的距离,至于怎么计算,这取决于你对地球怎么进行建模,一般对于距离计算精度要求不是很精确的(误差在10-20米范围内能接受的话)
采用平面模型就够了。当然你也可以计算球面模型,这样计算精度更精确,但更耗CPU,意味着计算时间更长,需要自己去优化。
下面给出一个Spatial使用示例代码:
package com.yida.framework.lucene5.spatial; import org.apache.lucene.document.Document; import org.apache.lucene.document.Field; import org.apache.lucene.document.NumericDocValuesField; import org.apache.lucene.document.StoredField; import org.apache.lucene.index.DirectoryReader; import org.apache.lucene.index.IndexReader; import org.apache.lucene.index.IndexWriter; import org.apache.lucene.index.IndexWriterConfig; import org.apache.lucene.queries.function.ValueSource; import org.apache.lucene.search.Filter; import org.apache.lucene.search.IndexSearcher; import org.apache.lucene.search.MatchAllDocsQuery; import org.apache.lucene.search.ScoreDoc; import org.apache.lucene.search.Sort; import org.apache.lucene.search.SortField; import org.apache.lucene.search.TopDocs; import org.apache.lucene.spatial.SpatialStrategy; import org.apache.lucene.spatial.prefix.RecursivePrefixTreeStrategy; import org.apache.lucene.spatial.prefix.tree.GeohashPrefixTree; import org.apache.lucene.spatial.prefix.tree.SpatialPrefixTree; import org.apache.lucene.spatial.query.SpatialArgs; import org.apache.lucene.spatial.query.SpatialOperation; import org.apache.lucene.store.Directory; import org.apache.lucene.store.RAMDirectory; import org.wltea.analyzer.lucene.IKAnalyzer; import com.spatial4j.core.context.SpatialContext; import com.spatial4j.core.distance.DistanceUtils; import com.spatial4j.core.shape.Point; import com.spatial4j.core.shape.Shape; /** * Lucene地理位置查询测试 * * @author Lanxiaowei * */ public class LuceneSpatialTest { /** Spatial上下文 */ private SpatialContext ctx; /** 提供索引和查询模型的策略接口 */ private SpatialStrategy strategy; /** 索引目录 */ private Directory directory; /** * Spatial初始化 */ protected void init() { // SpatialContext也可以通过SpatialContextFactory工厂类来构建 this.ctx = SpatialContext.GEO; //网格最大11层 int maxLevels = 11; // SpatialPrefixTree也可以通过SpatialPrefixTreeFactory工厂类构建 SpatialPrefixTree grid = new GeohashPrefixTree(ctx, maxLevels); this.strategy = new RecursivePrefixTreeStrategy(grid, "myGeoField"); // 初始化索引目录 this.directory = new RAMDirectory(); } private void indexPoints() throws Exception { IndexWriterConfig iwConfig = new IndexWriterConfig(new IKAnalyzer()); IndexWriter indexWriter = new IndexWriter(directory, iwConfig); //这里的x,y即经纬度,x为Longitude(经度),y为Latitude(纬度) indexWriter.addDocument(newSampleDocument(2, ctx.makePoint(-80.93, 33.77))); /** WKT表示法:POINT(Longitude,Latitude)*/ indexWriter.addDocument(newSampleDocument(4, ctx.readShapeFromWkt("POINT(60.9289094 -50.7693246)"))); indexWriter.addDocument(newSampleDocument(20, ctx.makePoint(0.1, 0.1), ctx.makePoint(0, 0))); indexWriter.close(); } /** * 创建Document索引对象 * * @param id * @param shapes * @return */ private Document newSampleDocument(int id, Shape... shapes) { Document doc = new Document(); doc.add(new StoredField("id", id)); doc.add(new NumericDocValuesField("id", id)); for (Shape shape : shapes) { for (Field f : strategy.createIndexableFields(shape)) { doc.add(f); } Point pt = (Point) shape; doc.add(new StoredField(strategy.getFieldName(), pt.getX() + " " + pt.getY())); } return doc; } /** * 地理位置搜索 * @throws Exception */ private void search() throws Exception { IndexReader indexReader = DirectoryReader.open(directory); IndexSearcher indexSearcher = new IndexSearcher(indexReader); // 按照id升序排序 Sort idSort = new Sort(new SortField("id", SortField.Type.INT)); //搜索方圆200千米范围以内,这里-80.0, 33.0分别是当前位置的经纬度,以当前位置为圆心,200千米为半径画圆 //注意后面的EARTH_MEAN_RADIUS_KM表示200的单位是千米,看到KM了么。 SpatialArgs args = new SpatialArgs(SpatialOperation.Intersects, ctx.makeCircle(-80.0, 33.0, DistanceUtils.dist2Degrees(200, DistanceUtils.EARTH_MEAN_RADIUS_KM))); //根据SpatialArgs参数创建过滤器 Filter filter = strategy.makeFilter(args); //开始搜索 TopDocs docs = indexSearcher.search(new MatchAllDocsQuery(), filter, 10, idSort); Document doc1 = indexSearcher.doc(docs.scoreDocs[0].doc); String doc1Str = doc1.getField(strategy.getFieldName()).stringValue(); int spaceIdx = doc1Str.indexOf(' '); double x = Double.parseDouble(doc1Str.substring(0, spaceIdx)); double y = Double.parseDouble(doc1Str.substring(spaceIdx + 1)); double doc1DistDEG = ctx .calcDistance(args.getShape().getCenter(), x, y); System.out.println("(Longitude,latitude):" + "(" + x + "," + y + ")"); System.out.println("doc1DistDEG:" + doc1DistDEG * DistanceUtils.DEG_TO_KM); System.out.println(DistanceUtils.degrees2Dist(doc1DistDEG,DistanceUtils.EARTH_MEAN_RADIUS_KM)); //定义一个坐标点(x,y)即(经度,纬度)即当前用户所在地点 Point pt = ctx.makePoint(60, -50); //计算当前用户所在坐标点与索引坐标点中心之间的距离即当前用户地点与每个待匹配地点之间的距离,DEG_TO_KM表示以KM为单位 ValueSource valueSource = strategy.makeDistanceValueSource(pt, DistanceUtils.DEG_TO_KM); //根据命中点与当前位置坐标点的距离远近降序排,距离数字大的排在前面,false表示降序,true表示升序 Sort distSort = new Sort(valueSource.getSortField(false)) .rewrite(indexSearcher); TopDocs topdocs = indexSearcher.search(new MatchAllDocsQuery(), 10, distSort); ScoreDoc[] scoreDocs = topdocs.scoreDocs; for (ScoreDoc scoreDoc : scoreDocs) { int docId = scoreDoc.doc; Document document = indexSearcher.doc(docId); int gotid = document.getField("id").numericValue().intValue(); String geoField = document.getField(strategy.getFieldName()).stringValue(); int xy = geoField.indexOf(' '); double xPoint = Double.parseDouble(geoField.substring(0, xy)); double yPoint = Double.parseDouble(geoField.substring(xy + 1)); double distDEG = ctx .calcDistance(args.getShape().getCenter(), xPoint, yPoint); double juli = DistanceUtils.degrees2Dist(distDEG,DistanceUtils.EARTH_MEAN_RADIUS_KM); System.out.println("docId:" + docId + ",id:" + gotid + ",distance:" + juli + "KM"); } /*args = new SpatialArgs(SpatialOperation.Intersects, ctx.makeCircle( -80.0, 33.0, 1)); SpatialArgs args2 = new SpatialArgsParser().parse( "Intersects(BUFFER(POINT(-80 33),1))", ctx); System.out.println("args2:" + args2.toString());*/ indexReader.close(); } public static void main(String[] args) throws Exception { LuceneSpatialTest luceneSpatialTest = new LuceneSpatialTest(); luceneSpatialTest.init(); luceneSpatialTest.indexPoints(); luceneSpatialTest.search(); } }
最后列出一些补充学习资料,关于Spatial具体更深入的学习,需要自己去研究,我只是说了个大概,其实地理位置搜索实现起来并不难,难在数据量巨大时索引数据体积庞大导致的查询速度损耗问题如何解决,动态计算两点之间的距离算法如何优化至在1-10ms内返回等等:
有关WTK 空间数据表示法参考资料:
WKT - 概念
WKT(Well-known text)是一种文本标记语言,用于表示矢量几何对象、空间参照系统及空间参照系统之间的转换。它的二进制表示方式,亦即WKB(well-known binary)则胜于在传输和在数据库中存储相同的信息。该格式由开放地理空间联盟(OGC)制定。
WKT - 几何对象
WKT可以表示的几何对象包括:点,线,多边形,TIN(不规则三角网)及多面体。可以通过几何集合的方式来表示不同维度的几何对象。
几何物体的坐标可以是2D(x,y),3D(x,y,z),4D(x,y,z,m),加上一个属于线性参照系统的m值。
以下为几何WKT字串样例:
POINT(6 10)
LINESTRING(3 4,10 50,20 25)
POLYGON((1 1,5 1,5 5,1 5,1 1),(2 2,2 3,3 3,3 2,2 2))
MULTIPOINT(3.5 5.6, 4.8 10.5)
MULTILINESTRING((3 4,10 50,20 25),(-5 -8,-10 -8,-15 -4))
MULTIPOLYGON(((1 1,5 1,5 5,1 5,1 1),(2 2,2 3,3 3,3 2,2 2)),((6 3,9 2,9 4,6 3)))
GEOMETRYCOLLECTION(POINT(4 6),LINESTRING(4 6,7 10))
POINT ZM (1 1 5 60)
POINT M (1 1 80)
POINT EMPTY
MULTIPOLYGON EMPTY
WKT - 空间参照系统
一个表示空间参照系统的WKT字串描述了空间物体的测地基准、大地水准面、坐标系统及地图投影。
WKT在许多GIS程序中被广泛采用。ESRI亦在其shape文件格式(*.prj)中使用WKT。
以下是空间参照系统的WKT表示样例:
COMPD_CS["OSGB36 / British National Grid + ODN",
PROJCS["OSGB 1936 / British National Grid",
GEOGCS["OSGB 1936",
DATUM["OSGB_1936",
spheroid["Airy 1830",6377563.396,299.3249646,AUTHORITY["EPSG","7001"]],
TOWGS84[375,-111,431,0,0,0,0],
AUTHORITY["EPSG","6277"]],
PRIMEM["Greenwich",0,AUTHORITY["EPSG","8901"]],
UNIT["DMSH",0.0174532925199433,AUTHORITY["EPSG","9108"]],
AXIS["Lat",NORTH],
AXIS["Long",EAST],
AUTHORITY["EPSG","4277"]],
PROJECTION["Transverse_Mercator"],
PARAMETER["latitude_of_origin",49],
PARAMETER["central_meridian",-2],
PARAMETER["scale_factor",0.999601272],
PARAMETER["false_easting",400000],
PARAMETER["false_northing",-100000],
UNIT["metre",1,AUTHORITY["EPSG","9001"]],
AXIS["E",EAST],
AXIS["N",NORTH],
AUTHORITY["EPSG","27700"]],
VERT_CS["Newlyn",
VERT_DATUM["Ordnance Datum Newlyn",2005,AUTHORITY["EPSG","5101"]],
UNIT["metre",1,AUTHORITY["EPSG","9001"]],
AXIS["Up",UP],
AUTHORITY["EPSG","5701"]],
AUTHORITY["EPSG","7405"]]
关于如何对地球进行建模方面的知识,请自己Google学习,比如平面建模,球面建模,曼哈顿距离等等,平面建模一般采用勾股定理或其变体就能解决,球面建模一般是采用求大圆弧长来解决,在Lucene Spatial中有Haversine 和 Geohash Haversine两个公式实现。至于Haversine公式的算法以及GeoaHash编码的算法啊自己Google学习去吧。Demo源码请看底下的附件!!!
如果你还有什么问题请加我Q-Q:7-3-6-0-3-1-3-0-5,
或者加裙
一起交流学习!
相关推荐
标题中的“lucene地理位置搜索所用jar包”指的是Apache Lucene库在实现地理位置搜索功能时所需的特定Java档案(jar)文件。Lucene是开源的全文搜索引擎库,它为开发者提供了强大的文本检索和分析功能。在扩展到处理...
5. **Sorting**:在Lucene中,我们可以自定义排序规则,包括基于地理位置的距离排序。这可以通过实现`SortComparatorSource`接口来自定义比较器,或者使用`FieldComparatorSource`来创建一个基于特定字段(如地理...
在本场景中,我们探讨的是基于Lucene 4.8版本的空间检索功能,这是一种允许用户根据地理位置信息进行搜索的技术。下面我们将深入理解这个主题,包括Lucene的空间检索原理、实现步骤以及如何处理给定的CSV文件。 一...
空间搜索则允许对地理位置信息进行检索,这对于地理位置相关的应用非常有用。 总的来说,Lucene 4.3.1是一个功能强大且高度可定制的全文搜索引擎库。通过正确导入和使用提供的资源文件,开发者可以轻松地在自己的...
`lucene-misc-3.6.1.jar`包含了其他杂项功能,如地理位置搜索的支持。`lucene-grouping-3.6.1.jar`实现了搜索结果的分组,方便用户按类别查看。最后,`lucene-spatial-3.6.1.jar`则提供了空间索引和查询的功能,对于...
`lucene-spatial-4.10.3.CHM`专注于地理空间搜索,允许开发者根据地理位置信息进行查询。这个模块提供了对地理坐标的索引和查询支持,对于需要地理信息检索的应用至关重要。 `lucene-highlighter-4.10.3.CHM`涉及...
4. **Lucene-Spatial**: 支持地理位置的搜索,允许根据距离或地理区域进行检索。 5. **Lucene-Store**: 包含用于存储和读取索引的不同策略,如RAMDirectory、FSDirectory等。 6. **Lucene-Util**: 提供了一系列...
`Spatial`模块支持地理空间搜索,`Facet`模块则实现了分类和 faceted search。 通过深入学习和分析Lucene 4.10.4的源码,开发者不仅可以掌握全文检索的基本原理,还能理解其在实际应用中的优化策略,为构建高效、...
`lucene-spatial.jar`则处理地理空间搜索,允许基于地理位置的查询。 6. **索引和优化**: `lucene-backward-codecs.jar`支持向后兼容旧的编码格式,`lucene-expressions.jar`允许在查询时动态计算表达式。`lucene-...
8. **空间搜索**:如`lucene-spatial.jar`,支持地理空间索引和查询,适用于地图应用或其他需要地理位置搜索的场景。 9. **多线程支持**:如`lucene-backward-codecs.jar`,包含旧版本的编码器,用于向后兼容和并行...
6. **lucene-spatial-6.6.0.jar**:Lucene的空间搜索模块,支持地理空间搜索和索引,使得Elasticsearch可以处理地理位置相关的数据。 7. **jopt-simple-5.0.2.jar**:一个命令行选项解析库,用于处理Elasticsearch...
10. **Spatial Search(空间搜索)**:Solr 还支持地理位置数据的搜索,允许根据地理坐标进行范围查询和距离排序。 压缩包 "solr-jars.zip" 中的 JAR 文件很可能包含了 Solr 和 Lucene 的核心库,这些库可能包括...
【LBS】(Location-Based Services,位置服务)是利用移动设备获取用户地理位置信息,为用户提供相关服务的技术。在本文中,LBS与GIS(Geographic Information System,地理信息系统)结合,构建了一个名为SSE4J的Java...
例如,geo-spatial searches(地理空间搜索)允许用户根据地理位置进行搜索,这在需要地图搜索和位置定位的应用中非常重要。此外,Elasticsearch还可以处理文档之间的关系,这对于构建复杂的数据结构和实现复杂查询...
在深入探讨ElasticMaps Main的源码之前,我们先要理解Elasticsearch的基础知识,它是基于Lucene的分布式搜索引擎,广泛应用于日志分析、实时监控、大数据分析等领域。 Elasticsearch的核心概念包括索引、文档、类型...
它提供了高度灵活的分布式全文检索能力,并且支持多种高级特性,如高亮显示、分面搜索、地理位置查询等。Solr广泛应用于企业级搜索解决方案中,适用于对性能和可扩展性有较高要求的应用场景。 **本书《Solr in ...