Solr查询:地理位置搜索功能详解
概述
Solr支持位置数据用于空间/地理空间搜索。
使用空间搜索,您可以:
- 索引点或其他形状
- 通过边界框、圆形或其他形状过滤搜索结果
- 按点之间的距离或矩形之间的相对面积进行排序或提升评分
- 生成2D网格的分面计数用于热力图生成或点绘制
有四种主要的空间搜索字段类型:
LatLonPointSpatialField
(默认启用docValues
)PointType
SpatialRecursivePrefixTreeFieldType
(简称RPT),包括其衍生版本RptWithGeometrySpatialField
BBoxField
LatLonPointSpatialField
是最常见的经纬度点数据用例的理想字段类型。
RPT为更高级/自定义用例提供更多功能和选项,如多边形和热力图。
RptWithGeometrySpatialField
用于索引和搜索非点数据,尽管它也可以处理点。它不能进行排序/提升。
BBoxField
用于索引边界框,通过框查询,指定搜索谓词(相交、包含、包含于、分离、相等),以及相关性排序/提升,如重叠比率或简单的面积。
本指南未涵盖的一些详细信息可以在Solr Wiki的空间搜索部分找到。
LatLonPointSpatialField
以下是LatLonPointSpatialField
(LLPSF)在模式中的通常配置方式:
1 | <fieldType name="location" class="solr.LatLonPointSpatialField" docValues="true"/> |
LLPSF支持切换indexed
、stored
、docValues
和multiValued
。
LLPSF在启用”indexed”时(默认)内部使用二维Lucene”Points”(BDK树)索引。
当启用”docValues”时(默认),纬度和经度对被位交错到64位并放入Lucene DocValues。
docValues数据的精度大约是一厘米。
索引点
对于索引大地测量点(纬度和经度),以”lat,lon”顺序提供(逗号分隔)。
对于索引非大地测量点,这取决于情况。
如果是RPT,使用x y
(空格)。
但对于PointType,使用x,y
(逗号)。
如果您希望使用标准行业格式,Solr支持WKT和GeoJSON。
但是,对于这样简单的数据,它比原始坐标要臃肿得多。
(PointType不支持)
索引GeoJSON和WKT
使用bin/solr post
工具:
1 | bin/solr post -t "application/json" --solr-url http://localhost:8983 --name mycollection --params "format=geojson" /path/to/geojson.file |
请求中需要传递的关键参数是:
format
- 可选,默认:
true
- 要传入的文件格式
- 接受的值:
WKT
或geojson
使用查询解析器搜索
有两个地理空间搜索的Solr”查询解析器”:geofilt
和bbox
。
它们采用以下参数:
d
- 必需,默认:无
- 径向距离,通常以公里为单位
- RPT和BBoxField可以通过设置
distanceUnits
设置其他单位
pt
- 必需,默认:无
- 中心点,如果是纬度和经度,使用格式
lat,lon
- 否则,对于PointType使用”x,y”,对于RPT字段类型使用”x y”
sfield
- 必需,默认:无
- 空间索引字段
score
- 可选,默认:
none
- 如果查询在评分上下文中使用(例如,作为
q
中的主查询),此本地参数决定产生什么分数 - 高级选项;PointType不支持
有效值为:
none
:固定分数为1.0
kilometers
:字段值和指定中心点之间的公里距离miles
:字段值和指定中心点之间的英里距离degrees
:字段值和指定中心点之间的度数距离distance
:字段值和指定中心点之间在该字段配置的distanceUnits
中的距离recipDistance
:距离的倒数
警告:不要将此用于索引的非点形状(例如多边形)。结果将是错误的。对于RPT,仅建议用于多值点数据,因为实现扩展性不好,对于单值字段,您应该使用单独的非RPT字段纯粹进行距离排序。
与BBoxField
一起使用时,支持其他选项:
overlapRatio
:索引形状和查询形状之间的相对重叠area
:基于haversine的重叠形状面积,以为此字段配置的distanceUnits
表示area2D
:基于笛卡尔坐标的重叠形状面积,以为此字段配置的distanceUnits
表示
filter
- 可选,默认:
true
- 如果您只想让查询评分(使用上面的
score
本地参数),而不过滤,则将此本地参数设置为false
- 高级选项;PointType不支持
geofilt
geofilt
过滤器允许您基于给定点的地理空间距离(也称为”大圆距离”)检索结果。
另一种看待它的方式是它创建一个圆形形状过滤器。
例如,要查找给定经纬度点5公里内的所有文档,您可以输入:
1 | &q=*:*&fq={!geofilt sfield=store}&pt=45.15,-93.85&d=5 |
此过滤器返回给定半径初始点周围圆形内的所有结果:
bbox
bbox
过滤器与geofilt
非常相似,除了它使用计算圆的边界框。
请参阅下图中的蓝色框。
它采用与geofilt相同的参数。
这是一个示例查询:
1 | &q=*:*&fq={!bbox sfield=store}&pt=45.15,-93.85&d=5 |
矩形形状计算更快,因此有时在可以接受返回半径外的点时用作geofilt
的替代方案。
但是,如果理想目标是圆形但希望运行更快,那么可以考虑使用RPT字段并尝试大的distErrPct
值,如0.1
(10%半径)。
这将返回半径外的结果,但会在形状周围相对均匀地这样做。
重要:当边界框包含极点时,边界框最终成为”边界碗”(球面帽),如果它接触北极,则包括圆的最低纬度以北的所有值(如果它接触南极,则包括最高纬度以南的值)。
通过任意矩形过滤
有时空间搜索要求需要查找矩形区域内的所有内容,例如用户正在查看的地图覆盖的区域。
对于这种情况,geofilt和bbox无法胜任。
这有点技巧,但您可以通过提供左下角作为范围开始和右上角作为范围结束来使用Solr的范围查询语法:
1 | &q=*:*&fq=store:[45,-94 TO 46,-93] |
对于RPT和BBoxField,如果您不使用经纬度坐标(geo="false"
),则必须因为空格而引用点,例如"x y"
。
优化:缓存还是不缓存
最常见的是将空间查询放入”fq”参数——过滤查询中。
默认情况下,Solr将在过滤缓存中缓存查询。
如果您知道过滤查询(无论是空间的还是其他的)相当独特且不太可能获得缓存命中,则指定cache="false"
作为本地参数,如下例所示。
只有具有docValues的空间类型(如LatLonPointSpatialField或BBoxField)才能从这种技术中受益。
1 | &q=...mykeywords...&fq=...someotherfilters...&fq={!geofilt cache=false}&sfield=store&pt=45.15,-93.85&d=5 |
距离排序或提升(函数查询)
有四个距离函数查询:
geodist
,见下文,通常最合适dist
,计算多维向量之间的p-范数距离hsin
,计算球面上两点之间的距离sqedist
,计算两点之间的平方欧几里得距离
有关这些函数查询的更多信息,请参阅函数查询部分。
geodist
geodist
是一个距离函数,它接受三个可选参数:(sfield,latitude,longitude)
。
您可以使用geodist
函数按距离排序结果或对返回结果评分。
例如,要按升序距离排序结果,使用如下请求:
1 | &q=*:*&fq={!geofilt}&sfield=store&pt=45.15,-93.85&d=50&sort=geodist() asc |
要返回距离作为文档分数,使用如下请求:
1 | &q={!func}geodist()&sfield=store&pt=45.15,-93.85&sort=score+asc&fl=*,score |
更多空间搜索示例
以下是您可以在Solr中使用空间搜索做的一些更有用的示例。
用作子查询以扩展搜索结果
这里我们将查询佛罗里达州杰克逊维尔的结果,或45.15,-93.85(明尼苏达州布法罗附近)50公里范围内的结果:
1 | &q=*:*&fq=(state:"FL" AND city:"Jacksonville") OR {!geofilt}&sfield=store&pt=45.15,-93.85&d=50&sort=geodist()+asc |
按距离分面
要按距离分面,您可以使用frange
查询解析器:
1 | &q=*:*&sfield=store&pt=45.15,-93.85&facet.query={!frange l=0 u=5}geodist()&facet.query={!frange l=5.001 u=3000}geodist() |
还有其他方法,比如在每个facet.query中使用{!geofilt}
。
提升最近的结果
使用DisMax或eDisMax查询解析器,您可以将空间搜索与提升函数结合以提升最近的结果:
1 | &q.alt=*:*&fq={!geofilt}&sfield=store&pt=45.15,-93.85&d=50&bf=recip(geodist(),2,200,20)&sort=score desc |
将距离作为字段返回
要将距离作为伪字段返回,您可以在字段列表中使用geodist()
函数:
1 | &fl=distance:geodist() |
RPT
RPT指的是SpatialRecursivePrefixTreeFieldType
(也称为简单RPT)和扩展版本:RptWithGeometrySpatialField
(也称为带几何的RPT)。
RPT相对于LatLonPointSpatialField提供了几个功能改进:
- 非大地测量——geo=false通用x和y(不是纬度和经度)——如果需要
- 通过多边形和其他复杂形状查询,以及圆形和矩形
- 索引非点形状(例如多边形)以及点的能力——参见RptWithGeometrySpatialField
- 热力图网格分面
RPT与LatLonPointSpatialField
共享各种功能。这里列出了一些:
- 纬度/经度索引点数据;可能是多值
- 使用
geofilt
、bbox
过滤器和范围查询语法快速过滤(支持日期线交叉) - 众所周知的文本(WKT)形状语法(指定多边形和其他复杂形状所需),以及GeoJSON
除了索引和搜索外,这还适用于wt=geojson
(GeoJSON Solr响应写入器)和[geo f=myfield]
(geo Solr文档转换器) - 通过
geodist
排序/提升——尽管不推荐
重要:尽管RPT支持距离排序/提升,但它在执行此操作时效率很低,以至于将来可能会被移除。幸运的是,您可以同时使用LatLonPointSpatialField和RPT。使用LLPSF进行距离排序/提升;它只需要具有docValues;可以禁用索引属性,因为不会使用它。
RPT的模式配置
要使用RPT,字段类型必须在集合的模式中注册和配置。此字段类型有许多选项。
name
- 必需,默认:无
- 字段类型的名称
class
- 必需,默认:无
- 这应该是
solr.SpatialRecursivePrefixTreeFieldType
- 但要注意Lucene空间模块包括RPT之外的其他所谓”空间策略”,特别是TermQueryPT*、BBox、PointVector*和SerializedDV
- Solr需要相应的字段类型才能使用这些策略。带星号的有它们
spatialContextFactory
- 可选,默认:无
- 这是一个Java类名,用于控制形状定义和解析支持的内部扩展点
- 有两个已知实现的内置别名:
Geo3D
和JTS
- 默认空白值不支持多边形
geo
- 可选,默认:
true
- 如果为
true
,将使用纬度和经度坐标,数学模型通常是球体 - 如果为
false
,坐标将是2D平面上使用欧几里得/笛卡尔几何的通用X和Y
format
- 可选,默认:
WKT
- 定义要使用的形状语法/格式
- 默认为
WKT
,但GeoJSON
是另一种流行格式 - Spatial4j控制此功能并支持其他格式
- 如果给定形状可解析为”lat,lon”或”x y”,则始终支持
distanceUnits
- 可选,默认:无
- 用于指定在使用此字段的整个过程中用于距离测量的单位
- 可以是
degrees
、kilometers
或miles
- 它适用于涉及字段的几乎所有距离测量:
maxDistErr
、distErr
、d
、geodist
,以及当分数为distance
、area
或area2d
时的score
- 但是,它不影响WKT字符串中嵌入的距离,例如
BUFFER(POINT(200 10),0.2)
,它们仍然以度为单位 - 如果
geo
为true
,distanceUnits
默认为kilometers
,如果geo
为false
,则默认为degrees
distanceUnits
取代units
属性;后者现在已弃用并与此属性互斥
distErrPct
- 可选,默认:见描述
- 定义非点形状(索引和查询)的默认精度,作为
0.0
(完全精确)到0.5
之间的分数 - 此数字越接近零,形状越准确
- 但是,更精确的索引形状使用更多磁盘空间并需要更长时间索引
- 更大的
distErrPct
值将使查询更快但不太准确 - 在查询时,这可以在查询语法中覆盖,例如覆盖为
0.0
以便不近似搜索形状 - RPT字段的默认值为
0.025
- 注意:对于RPTWithGeometrySpatialField(见下文),序列化几何体总是完全准确,因此这不会控制准确性,而是控制索引应该有多大的权衡。该字段的
distErrPct
默认为0.15
maxDistErr
- 可选,默认:见描述
- 定义索引数据所需的最高详细级别
- 如果留空,默认为一米——略少于0.000009度
- 此设置在内部用于计算适当的maxLevels(见下文)
worldBounds
- 可选,默认:无
- 定义x和y的有效数值范围,格式为
ENVELOPE(minX, maxX, maxY, minY)
- 如果
geo="true"
,假设标准经纬度世界边界 - 如果
geo=false
,您应该定义您的边界
distCalculator
- 可选,默认:见描述
- 定义距离计算算法
- 如果
geo=true
,haversine
是默认值 - 如果
geo=false
,cartesian
将是默认值 - 其他可能值为
lawOfCosines
、vincentySphere
和cartesian^2
prefixTree
- 可选,默认:见描述
- 定义空间网格实现
- 由于PrefixTree(如RecursivePrefixTree)将世界映射为网格,每个网格单元在下一级分解为另一组网格单元
- 如果
geo=true
,则默认前缀树是geohash
,否则是quad
- Geohash在每个级别有32个子级,quad有4个
- Geohash只能用于
geo=true
,因为它严格是地理空间的 - 第三个选择是
packedQuad
,如果有许多级别——可能20个或更多,它通常比quad
更高效
maxLevels
- 可选,默认:无
- 设置索引数据的最大网格深度
- 相反,通过指定
maxDistErr
计算适当的maxLevels通常更直观
其他参数:normWrapLongitude
、datelineRule
、validationRule
、autoIndex
、allowMultiOverlap
、precisionModel
。
有关更多信息,请参阅上面引用的spatialContextFactory
实现的注释,特别是基于JTS的链接。
标准形状
RPT字段类型支持一组标准形状:
点、圆(也称为缓冲点)、信封(也称为矩形或边界框)、线串、多边形和这些的”multi”变体。
信封和线串是欧几里得/笛卡尔(平面2D)形状。
Solr底层是实现它们的Spatial4j库。
要支持其他形状,您可以配置字段类型上的spatialContextFactory
属性以引用其他选项。
有两个可用:JTS和Geo3D。
JTS和多边形(平面)
JTS拓扑套件是一个流行的具有欧几里得/笛卡尔(平面2D)模型的计算几何库。
它支持各种形状,包括多边形、缓冲形状和一些无效多边形修复回退。
借助Solr附带的Spatial4j,多边形支持日期线(反子午线)交叉。
您必须下载它(JAR文件)并将其放在Solr内部的特殊位置:SOLR_INSTALL/server/solr-webapp/webapp/WEB-INF/lib/
。
您可以在这里轻松下载:https://mvnrepository.com/artifact/org.locationtech.jts/jts-core/1.17.1。
遗憾的是,如果放在其他更典型的Solr lib目录中,它将不起作用。
在字段类型上将spatialContextFactory
属性设置为JTS
。
激活后,有其他配置属性可用;请参阅org.locationtech.spatial4j.context.jts.JtsSpatialContextFactory的Javadoc,并记得查看超类的选项。
特别是一个选项,您很可能应该启用autoIndex
(即使用JTS的PreparedGeometry),因为它已被证明对非平凡多边形是主要性能提升。
1 | <fieldType name="location_rpt" class="solr.SpatialRecursivePrefixTreeFieldType" |
一旦定义了字段类型,定义使用它的字段。
这里是字段”geo”的示例多边形查询,它可以是solr.SpatialRecursivePrefixTreeFieldType或RptWithGeometrySpatialField:
1 | &q=*:*&fq={!field f=geo}Intersects(POLYGON((-10 30, -40 40, -10 -20, 40 20, 0 0, -10 30))) |
搜索谓词后面括号内是形状定义。该形状的格式由字段类型上的format
属性控制,默认为WKT。如果您更喜欢GeoJSON,可以指定它。
除了本参考指南和Spatial4j的文档外,Solr Wiki上还有一些详细信息:https://cwiki.apache.org/confluence/display/solr/SolrAdaptersForLuceneSpatial4。
Geo3D和多边形(在椭球上)
Geo3D是Lucene spatial-3d模块的俗称,包含在Solr中。
它是一个在球体或WGS84椭球上实现各种形状(包括多边形)的计算几何库。
Geo3D特别适合几何体覆盖全球大距离或靠近极点的空间应用。
Geo3D因其使用地心坐标(X,Y,Z)的内部实现而得名,不是三维几何,它不支持。
尽管有这些内部细节,您在Solr中仍然正常提供纬度和经度。
在字段类型上将spatialContextFactory
属性设置为Geo3D
。
1 | <fieldType name="geom" |
一旦定义了字段类型,定义使用它的字段。
prefixTree="s2"
设置是可选的,只能与Geo3D一起使用。它是为了配合Geo3D开发的,比其他网格更高效。
重要:使用Geo3D时,多边形点的顺序很重要!您必须遵循所谓的”右手规则”:外环必须是逆时针顺序,内洞必须是顺时针。如果顺序错误,则解释被颠倒,因此多边形将被解释为包含大部分地球。
RptWithGeometrySpatialField
RptWithGeometrySpatialField
字段类型是SpatialRecursivePrefixTreeFieldType
的衍生版本,它还在Lucene DocValues中内部存储原始几何体,用于实现精确搜索。
它也可以用于索引点字段。
相交谓词(默认值)特别快,因为许多搜索结果可以作为精确命中返回,而不需要几何检查。
此字段类型配置与RPT相同,只是默认的distErrPct
为0.15(高于0.025),因为网格正方形纯粹是为了性能,而不是从根本上表示形状。
可以在solrconfig.xml
中定义可选的内存缓存,当数据倾向于具有许多顶点的形状时应该这样做。
假设您将字段命名为”geom”,您可以通过添加以下内容在solrconfig.xml
中配置可选缓存——注意缓存名称的后缀:
1 | <cache name="perSegSpatialFieldCache_geom" |
使用此字段类型时,您可能不希望将字段标记为存储,因为它与DocValues数据冗余,并且由于格式化(无论是WKT还是GeoJSON)肯定更大。
要从搜索结果中的DocValues检索空间数据,请使用[geo]
转换器。
热力图分面
RPT字段支持为每个网格单元中具有空间数据的文档生成2D网格分面计数。
对于高详细网格,这可以用于绘制点,对于较少详细网格,它可以用于热力图生成。
网格单元在索引时基于RPT的配置确定。
在分面计数时,遍历兴趣区域中的索引单元,并递增对应于每个单元的网格计数器。
Solr可以以直接的2D整数数组或PNG格式返回数据,PNG对较大数据集压缩更好但必须解码。
热力图功能可从Solr的标准分面功能和JSON分面API访问。
我们现在将继续进行标准分面。
作为分面的一部分,它支持key
本地参数以及排除标记过滤查询,就像其他类型的分面一样。
这允许在同一字段上使用不同过滤器返回多个热力图。
facet
- 可选,默认:
false
- 设置为
true
以启用标准分面
facet.heatmap
- 必需,默认:无
- RPT类型的字段名称
facet.heatmap.geom
- 可选,默认:无
- 要计算热力图的区域,使用矩形范围语法或WKT指定
- 默认为世界
- 例如:
["-180 -90" TO "180 90"]
facet.heatmap.gridLevel
- 可选,默认:见描述
- 特定网格级别,决定每个网格单元有多大
- 默认通过
distErrPct
(或distErr
)计算
facet.heatmap.distErrPct
- 可选,默认:
0.15
- 用于计算gridLevel的geom大小的分数
- 计算方式与RPT的类似命名参数相同
facet.heatmap.distErr
- 可选,默认:无
- 用于间接选择网格级别的单元误差距离
- 计算方式与RPT的类似命名参数相同
facet.heatmap.format
- 可选,默认:
ints2D
- 格式,
ints2D
或png
提示:您将尝试不同的distErrPct
值(可能0.10-0.20)与各种输入几何体,直到默认大小是您正在寻找的。具体计算细节并不重要。对于用于点绘制的高详细网格(大致每个像素一个单元),将distErr
设置为正在显示的地图的几个像素左右的十进制度数。此外,您可能不希望使用基于geohash的网格,因为网格级别之间的单元方向在正方形和矩形之间翻转。Quad是一致的并且有更多级别,尽管以更大索引为代价。
以下是JSON中的一些示例输出(为了简洁插入了”…”):
1 | {gridLevel=6,columns=64,rows=64,minX=-180.0,maxX=180.0,minY=-90.0,maxY=90.0, |
输出显示gridLevel,这很有趣,因为它通常从其他参数计算出来。如果正在开发的界面允许明确的分辨率增加/减少功能,则后续请求可以明确指定gridLevel。
minX
、maxX
、minY
、maxY
报告计数所在的区域。这是在目标网格级别上输入geom
的最小包围边界矩形。这可能包装日期线。columns
和rows
值是输出矩形要均匀划分的列数和行数。注意:不要均匀划分屏幕投影地图矩形来绘制这些矩形/点,因为单元数据在十进制度的坐标空间中(如果geo=true)或在geo=false时给出的任何单位中。这可以安排为与屏幕地图相同,但不一定。
counts_ints2D
键有一个2D整数数组。初始外层按行顺序(从上到下),然后内层数组是列(从左到右)。如果任何数组全为零,出于效率原因返回null。如果没有匹配的空间数据,整个值为null。
如果format=png
,则输出键为counts_png
。它是4字节PNG的base-64编码字符串。PNG逻辑上保存与ints2D格式完全相同的数据。注意alpha通道字节被翻转以便于诊断目的查看PNG,因为否则计数必须超过2^24才能变得不透明。因此大于此值的计数将变得不透明。
BBoxField
BBoxField
字段类型为每个文档字段索引单个矩形(边界框),并支持通过边界框搜索。
它支持大多数空间搜索谓词,它基于搜索矩形和索引矩形之间的重叠或面积具有增强的相关性模式。
它对其相关性模式特别有用。
要在模式中配置它,使用这样的配置:
1 | <field name="bbox" type="bbox" /> |
BBoxField实际上基于numberType引用的另一个字段类型的4个实例。
它还使用布尔值标记日期线交叉。
假设您想使用相关性功能,则需要docValues(默认启用)。
一些属性与RPT字段常见,如geo、units、worldBounds和spatialContextFactory,因为它们共享一些相同的空间基础设施。
要索引框,向bbox字段添加WKT/CQL ENVELOPE语法中的字符串字段值。
示例:ENVELOPE(-10, 20, 15, 10)
,这是minX、maxX、maxY、minY顺序。
参数顺序不直观,但这是规范要求的。
或者,您可以在WKT中提供矩形多边形(如果您设置format="GeoJSON"
,也可以提供GeoJSON)。
要搜索,您可以使用{!bbox}
查询解析器,或范围语法例如[10,-10 TO 15,20]
,或包裹在括号中带有前导搜索谓词的ENVELOPE语法。
后者是选择除相交之外的谓词的唯一方法。
例如:
1 | &q={!field f=bbox}Contains(ENVELOPE(-10, 20, 15, 10)) |
现在要通过相关性模式之一对结果排序,像这样使用:
1 | &q={!field f=bbox score=overlapRatio}Intersects(ENVELOPE(-10, 20, 15, 10)) |
score
本地参数可以是overlapRatio
、area
和area2D
之一。area
使用球面数学(假设geo=true
)按文档面积评分,而area2D
使用简单的宽度*高度。overlapRatio
基于相对于文档面积和查询面积存在多少重叠计算[0-1]范围分数。
BBoxOverlapRatioValueSource的javadoc有关于公式的更多信息。
还有一个额外参数queryTargetProportion
,允许您将公式的查询侧加权到公式的索引(目标)侧。
您也可以使用&debug=results
查看有用的分数计算信息。
本文档翻译自Apache Solr官方参考指南,旨在为中文用户提供完整的地理位置搜索功能使用指南。