Solr查询:地理位置搜索功能详解

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支持切换indexedstoreddocValuesmultiValued
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
  • 要传入的文件格式
  • 接受的值:WKTgeojson

使用查询解析器搜索

有两个地理空间搜索的Solr”查询解析器”:geofiltbbox
它们采用以下参数:

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

此过滤器返回给定半径初始点周围圆形内的所有结果:

5KM半径

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共享各种功能。这里列出了一些:

  • 纬度/经度索引点数据;可能是多值
  • 使用geofiltbbox过滤器和范围查询语法快速过滤(支持日期线交叉)
  • 众所周知的文本(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类名,用于控制形状定义和解析支持的内部扩展点
  • 有两个已知实现的内置别名:Geo3DJTS
  • 默认空白值不支持多边形

geo

  • 可选,默认:true
  • 如果为true,将使用纬度和经度坐标,数学模型通常是球体
  • 如果为false,坐标将是2D平面上使用欧几里得/笛卡尔几何的通用X和Y

format

  • 可选,默认:WKT
  • 定义要使用的形状语法/格式
  • 默认为WKT,但GeoJSON是另一种流行格式
  • Spatial4j控制此功能并支持其他格式
  • 如果给定形状可解析为”lat,lon”或”x y”,则始终支持

distanceUnits

  • 可选,默认:无
  • 用于指定在使用此字段的整个过程中用于距离测量的单位
  • 可以是degreeskilometersmiles
  • 它适用于涉及字段的几乎所有距离测量:maxDistErrdistErrdgeodist,以及当分数为distanceareaarea2d时的score
  • 但是,它不影响WKT字符串中嵌入的距离,例如BUFFER(POINT(200 10),0.2),它们仍然以度为单位
  • 如果geotruedistanceUnits默认为kilometers,如果geofalse,则默认为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=truehaversine是默认值
  • 如果geo=falsecartesian将是默认值
  • 其他可能值为lawOfCosinesvincentySpherecartesian^2

prefixTree

  • 可选,默认:见描述
  • 定义空间网格实现
  • 由于PrefixTree(如RecursivePrefixTree)将世界映射为网格,每个网格单元在下一级分解为另一组网格单元
  • 如果geo=true,则默认前缀树是geohash,否则是quad
  • Geohash在每个级别有32个子级,quad有4个
  • Geohash只能用于geo=true,因为它严格是地理空间的
  • 第三个选择是packedQuad,如果有许多级别——可能20个或更多,它通常比quad更高效

maxLevels

  • 可选,默认:无
  • 设置索引数据的最大网格深度
  • 相反,通过指定maxDistErr计算适当的maxLevels通常更直观

其他参数normWrapLongitudedatelineRulevalidationRuleautoIndexallowMultiOverlapprecisionModel
有关更多信息,请参阅上面引用的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
2
3
4
5
6
7
<fieldType name="location_rpt"   class="solr.SpatialRecursivePrefixTreeFieldType"
spatialContextFactory="JTS"
autoIndex="true"
validationRule="repairBuffer0"
distErrPct="0.025"
maxDistErr="0.001"
distanceUnits="kilometers" />

一旦定义了字段类型,定义使用它的字段。

这里是字段”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
2
3
4
5
<fieldType name="geom"
class="solr.SpatialRecursivePrefixTreeFieldType"
spatialContextFactory="Geo3D"
prefixTree="s2"
planetModel="WGS84"/><!-- or "sphere" -->

一旦定义了字段类型,定义使用它的字段。

prefixTree="s2"设置是可选的,只能与Geo3D一起使用。它是为了配合Geo3D开发的,比其他网格更高效。

重要:使用Geo3D时,多边形点的顺序很重要!您必须遵循所谓的”右手规则”:外环必须是逆时针顺序,内洞必须是顺时针。如果顺序错误,则解释被颠倒,因此多边形将被解释为包含大部分地球。

RptWithGeometrySpatialField

RptWithGeometrySpatialField字段类型是SpatialRecursivePrefixTreeFieldType的衍生版本,它还在Lucene DocValues中内部存储原始几何体,用于实现精确搜索。
它也可以用于索引点字段。
相交谓词(默认值)特别快,因为许多搜索结果可以作为精确命中返回,而不需要几何检查。
此字段类型配置与RPT相同,只是默认的distErrPct为0.15(高于0.025),因为网格正方形纯粹是为了性能,而不是从根本上表示形状。

可以在solrconfig.xml中定义可选的内存缓存,当数据倾向于具有许多顶点的形状时应该这样做。
假设您将字段命名为”geom”,您可以通过添加以下内容在solrconfig.xml中配置可选缓存——注意缓存名称的后缀:

1
2
3
4
5
6
<cache name="perSegSpatialFieldCache_geom"
class="solr.CaffeineCache"
size="256"
initialSize="0"
autowarmCount="100%"
regenerator="solr.NoOpRegenerator"/>

使用此字段类型时,您可能不希望将字段标记为存储,因为它与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
  • 格式,ints2Dpng

提示:您将尝试不同的distErrPct值(可能0.10-0.20)与各种输入几何体,直到默认大小是您正在寻找的。具体计算细节并不重要。对于用于点绘制的高详细网格(大致每个像素一个单元),将distErr设置为正在显示的地图的几个像素左右的十进制度数。此外,您可能不希望使用基于geohash的网格,因为网格级别之间的单元方向在正方形和矩形之间翻转。Quad是一致的并且有更多级别,尽管以更大索引为代价。

以下是JSON中的一些示例输出(为了简洁插入了”…”):

1
2
{gridLevel=6,columns=64,rows=64,minX=-180.0,maxX=180.0,minY=-90.0,maxY=90.0,
counts_ints2D=[[0, 0, 2, 1, ....],[1, 1, 3, 2, ...],...]}

输出显示gridLevel,这很有趣,因为它通常从其他参数计算出来。如果正在开发的界面允许明确的分辨率增加/减少功能,则后续请求可以明确指定gridLevel。

minXmaxXminYmaxY报告计数所在的区域。这是在目标网格级别上输入geom的最小包围边界矩形。这可能包装日期线。columnsrows值是输出矩形要均匀划分的列数和行数。注意:不要均匀划分屏幕投影地图矩形来绘制这些矩形/点,因为单元数据在十进制度的坐标空间中(如果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
2
3
4
5
<field name="bbox" type="bbox" />

<fieldType name="bbox" class="solr.BBoxField"
geo="true" distanceUnits="kilometers" numberType="pdouble" />
<fieldType name="pdouble" class="solr.DoublePointField" docValues="true"/>

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本地参数可以是overlapRatioareaarea2D之一。
area使用球面数学(假设geo=true)按文档面积评分,而area2D使用简单的宽度*高度。
overlapRatio基于相对于文档面积和查询面积存在多少重叠计算[0-1]范围分数。
BBoxOverlapRatioValueSource的javadoc有关于公式的更多信息。
还有一个额外参数queryTargetProportion,允许您将公式的查询侧加权到公式的索引(目标)侧。
您也可以使用&debug=results查看有用的分数计算信息。


本文档翻译自Apache Solr官方参考指南,旨在为中文用户提供完整的地理位置搜索功能使用指南。

© 2025 Solr Community of China All Rights Reserved. 本站访客数人次 本站总访问量
Theme by hiero