位置,位置,位置! 就像位置在房地产中占主导地位一样,搜索加上位置感知可以在帮助用户有效地找到信息方面产生可观的收益。 例如,如果您是企业目录提供商(例如“黄页”站点),则当用户搜索管道工时,该站点应返回用户家附近管道工的结果。 如果您经营一个旅游网站,则可能希望允许旅行者搜索他们目的地附近的景点,以便找到有助于他们享受旅行的服务。 如果您要建立一个社交网站,则可以使用位置信息来帮助用户与朋友联系。 车载导航系统和支持GPS的摄像头等位置感知设备无处不在,以及大量公开可用的地图数据,为构建包含搜索功能的地理信息系统(GIS)提供了各种机会为最终用户带来卓越的结果。
空间信息的使用远远超出了搜索范围,但是在本文中,我将重点介绍如何利用空间信息来利用Apache Lucene和Apache Solr的功能来增强搜索应用程序。 为什么要使用搜索引擎? 鉴于有许多好的(甚至免费的)GIS工具可供使用,所以这绝对不是要求。 但是,将您的应用程序基于搜索提供了一些强大的功能,而许多其他方法通常都无法使用这些功能。 搜索系统可以有效地组合结构化和非结构化数据,使用户能够输入自由形式的查询,以搜索自由文本(例如描述和标题),同时根据地理数据限制或更改结果。 例如,一个旅游网站可以实施一项功能,用户可以要求查找马萨诸塞州波士顿的所有四星级酒店,并在评论文字中加上舒适的床位一词,并在说明中提供24小时客房服务 -全部亚秒级响应。 的检索系统,如Apache Solr实现也可以提供小面 (见相关主题关于Solr的和方面的信息),突出显示和拼写检查的结果集,这样应用程序可以帮助用户找到他们正在寻找更有效。
我将简要回顾一些Lucene的关键概念,并将更深的细节留给读者进行研究。 接下来,我将介绍地理空间搜索的一些基本概念。 GIS是一个很大的领域,可以很容易地阅读整篇文章,甚至更多,因此,我将重点关注一些基本概念,这些基本概念应该很直观,因为需要每天查找服务,人员和其他感兴趣的项目。 在本文的结尾,我将讨论可用于使用Lucene和Solr索引和搜索空间信息的方法。 我将使用OpenStreetMap(OSM)项目中的数据在一个真实的,尽管简单的示例中将这些概念作为基础(请参阅参考资料 )。
Apache Lucene是基于Java™的高性能搜索库。 Apache Solr是一个搜索服务器,它使用Lucene通过HTTP提供搜索,构面以及更多功能。 两者均根据商业友好型Apache软件许可进行许可。 请参阅相关主题有关的功能和API,每个提供了更多的信息。
Solr和Lucene的内心深处都将内容表示为文档 。 然后,文档由一个或多个字段以及一个可选的表示文档重要性的增强值组成。 一个字段由要索引或存储的实际内容以及告诉Lucene如何处理该内容的元数据和指示该字段重要性的提升值组成。 由您决定如何将内容表示为文档和字段,这取决于您希望如何搜索以及以其他方式访问文档中的信息。 您可以在单个内容单元之间建立一对一的关系,也可以在一对多关系之间建立一对一的关系。 例如,我可能选择将单个网页表示为带有多个字段(例如标题,关键字和正文)的文档。 或者,对于书籍,我可以选择将书籍的每一页表示为单独的文档。 正如您将在后面看到的那样,这种区别在编码用于搜索的空间数据时很重要。 字段中的内容可以被索引或仅被存储以供应用程序使用。 如果内容被索引,则应用程序可以对其进行搜索。 也可以分析索引内容以产生术语 ,通常称为令牌 。 术语是在搜索过程中查找和使用的基础。 术语通常是一个词,但不一定是。 我鼓励您探索参考资料,以了解有关所有这些概念的更多信息。
在硬币的查询方面,Lucene和Solr提供了丰富的功能来表达用户查询,范围从基本的关键字(术语)查询到短语和通配符查询。 Lucene和Solr都提供了通过应用一个或多个过滤器来限制搜索空间的能力,这对于空间搜索至关重要。 范围查询和范围过滤器是限制空间的基本机制。 在范围查询(或过滤器)中,用户指出需要将所有搜索的文档限制在两个具有自然排序的值之间。 例如,通常使用范围查询来查找去年或上个月出现的所有文档。 在引擎盖下,Lucene必须枚举文档中的术语,以识别属于该范围内的文档。 正如我将在后面显示的那样,有效地进行设置是空间搜索应用程序中过滤性能的关键之一。
Lucene和Solr还提供了函数查询的概念,该函数查询使您可以将字段的值(例如纬度和经度)用作评分机制的一部分,而不是简单地使用构成主要评分机制的内部集合统计信息。 当我演示使用Solr的一些基于距离的函数时,这也会在本文的稍后部分讨论。
首先,在构建空间搜索应用程序时,您需要标识要包含在应用程序中的空间数据。 此数据通常采用某些地理编码系统的形式,例如纬度,经度和海拔,或者作为邮政编码或特定的街道地址。 用于编码的系统越正式,在系统中工作就越容易。 例如,一首古老的民歌“在河上和穿过树林”(您知道单词:“我们要去祖母的房子”)将大量空间信息编码到歌词中(请参阅参考资料 )。 但这在GIS系统中并不是那么有用,因为我们不知道树林或河流在哪里。 将其与指向祖母的详细说明(包括起始地址和结束地址)进行对比,您就会明白为什么正确编码的数据很重要。 (有趣的是,可以提取和整理更通用的方向和地理实体(例如, 在河上或棕色房屋附近的系统)及其原因也很有用,但讨论不在本文讨论范围之内。)
除了用于标识位置的原始地理编码数据之外,许多GIS系统还可以添加相对于物理位置出现的信息。 例如,导航系统可以使用在地图上按顺序排列的一系列位置来创建从A点到B点的行驶路线。或者气象学家可能会在整个区域的地图上叠加降雨或恶劣天气数据,允许人们按景点搜索降雨量。 彼此居住的人们经常将区域组合在一起以形成邮政编码,区域代码,甚至是城镇,城市和州。 对于OSM,允许用户在基础地图的顶部编辑和覆盖信息,例如景点或街道。 将这些叠加层组合起来形成关系并随着时间的推移对其进行跟踪,可以进一步产生令人难以置信的动态且功能强大的应用程序。
无论与一个或多个位置相关联的叠加或其他信息如何,搜索应用程序都需要一种有效地表示此数据的方式。 尽管可以用几种方式表示位置信息,但我将重点介绍与Lucene相关的信息。 首先,最重要的是,许多类型的地理空间数据都可以以“原始”格式表示,并且在搜索应用程序中可以正常工作。 例如, 锡拉库扎(Syracuse)是代表锡拉库扎(Syracuse)市的一种绝佳方法,用户在搜索框中键Syracuse会发现包含锡拉库扎(Syracuse)的文档,就像其他搜索词一样。 实际上,原始表示是人们表示命名位置(例如城市,州和邮政编码)的最常见方式。 请注意,尽管我使用了raw raw短语,但仍然可以先对数据进行转换或规范化。 例如,将所有提到纽约的信息都转换为纽约通常是一件完全合理的事情。
之前我给大家介绍一下交涉的是Lucene的可以使用,但要明白,所有的陈述必须考虑到生产(请参阅空间参考是非常重要的相关信息 )。 在美国,最常见的是世界大地测量系统,通常缩写为WGS 84(请参阅参考资料 )。 尽管可以在某些系统之间进行转换,但是最好将所有数据表示在单个系统中。 从这里开始,我将假设一个系统。
在诸如Lucene和Solr的搜索方面,诸如纬度和经度(简称为lat / lon)之类的数字空间信息将在其中发挥更大的作用。 纬度和经度通常以距本初子午线(位于英国格林威治)的度,分和秒表示,并且通常需要使用double精度(或更高精度)的精度。 例如,对于本例中包含的数据(美国纽约州锡拉丘兹市),位于东边76.150026(如果未指定东边,则为-76.150026)和北边43.049648。
根据应用程序的不同,对每个纬度和经度进行编码都会导致要索引大量唯一项。 这不一定是一个节目停止器,但是它可能会大大降低搜索速度,而且,正如您将在本文后面看到的那样,并非总是需要它。 实际上,许多地图绘制应用程序只关心将搜索范围限制在特定区域内,因此存储有关位置的近似信息通常会产生少得多的字词,而不会对搜索结果产生不利影响。 这种精确的折衷方法通常将纬度和经度捕获到(笛卡尔)层中。 您可以将每个层都视为地图特定部分上的缩放级别,这样,以美国为中心的第2层可能涵盖整个北美,而第19层则可能在某人的后院。 更具体地说,每一层将地图分为2 层#框或网格。 然后,可以为每个框指定一个编号,并将其索引到文档上。 我将在后面的部分中说明如何利用此信息进行更快的搜索。
用Lucene术语表示的纬度和经度通常表示为两个不同的字段,但是在某些应用程序中,这可能会对性能产生影响。 如果单个场是期望的,纬度/经度可被编码成一个单一的String使用地理散列编码(参见相关主题 )。 地理哈希通过从哈希的末尾剥离字符而具有任意精度的优势。 在许多情况下,彼此靠近的位置具有共同的前缀。 例如,进入geohash.org中的Syracuse, NY会生成哈希值dr9ughxjkrt4b ,而进入Cicero, NY的锡拉丘兹Cicero, NY郊区则生成哈希值dr9veggs4ptd3 ,它们都共享dr9前缀。
到目前为止,我已经专门讨论了点,但是许多地理空间应用程序也对数据中的形状,路线和其他关系感兴趣。 这些超出了Lucene和Solr中提供的范围; 看到相关主题关于这些概念的更多信息。
一旦数据以索引表示,搜索应用程序在与数据进行交互时通常通常至少具有五个基本需求:
距离计算 :给定一个点,计算到一个或多个其他点的距离。 边界框过滤器 :查找在特定区域内发生的所有匹配项(文档)。 排序 :根据到某个固定点的距离对搜索结果进行排序。 相关性增强 :将距离用作得分中的提升因素,同时允许其他因素也起作用。 查询解析 :给定地址或某个位置的其他用户规范,创建可用于搜索索引数据的编码表示。这些部分中的每一个都可以在基于位置的应用程序中发挥重要作用,但是现在,我将重点关注距离计算,边界框过滤和查询解析。 排序和相关性增强(增强)仅使用距离计算,本文稍后将在实践中展示它们如何发挥作用。
在计算用于GIS应用程序的距离时,重要的是要了解有许多不同的方法,每种方法各有优缺点。 距离计算可分为三组,具体取决于应用程序选择对地球建模的方式。 在某些情况下,完全可以假设地球是平坦的模型,并且会损失一些精度以换取速度。 在平面模型方法中,大多数距离计算都取决于勾股定理的各种变化。 当距离短且位置不靠近极点时,平面模型方法通常足够好。 在其他情况下,将使用球形模型,并且所使用的主要距离计算是大圆距离 (请参阅参考资料 )。 大圆距离计算球面上任意两点之间的最短距离。 当距离更远且需要更高的精度时,球形模型更适合。 最后,可以将地球的椭球模型与Vincenty公式一起使用(请参阅参考资料 ),以在椭球上获得高精度的距离(低至0.5毫米),但是对于大多数应用而言,这种计算的复杂性可能不值得。
在许多本地搜索应用程序中,对准确性的需求取决于应用程序。 在某些情况下,相距一英里没什么大不了的,而在其他情况下,相距仅几毫米可能并不重要。 例如,欧几里得距离通常不够精确,无法跨越更大的距离(比如说两个州之间的距离),甚至在某些情况下,甚至Haversine(大圆)方法也可能不够精确,因为将地球更好地建模为椭球而不是一个领域。 在这种情况下,使用Vincenty公式可能更有意义。 在其他应用程序中,唯一重要的是结果的排序,因此可以使用平方欧几里德距离(实际上不是距离)之类的东西,从而节省了平方根计算。
当然,其他距离通常也有用,例如曼哈顿距离,它反映的是人们穿越一个以街区布置的城市时的距离(例如,穿越纽约市曼哈顿的出租车)。 出于本文的目的,我将使用平地模型和大圆距距离演示距离,其余部分供您探索。 还要注意,我在这里不考虑海拔因素,但是某些应用程序可能需要考虑这一因素。 有关地理距离的更多信息,请参阅参考资料 。
在许多基于位置的应用程序中,可以搜索数百万个位置数据。 仅遍历所有这些数据以查找既包含感兴趣的关键字又在用户指定位置一定距离之内的文档集将非常耗时。 自然,先缩小文档范围然后评估相关子集是有意义的。 如果仅存储了纬度和经度信息,则缩小文档集范围的主要选择是通过传递包含位置周围区域的范围。 如图1所示 ,稍微不透明的方框表示围绕南卡罗来纳州查尔斯顿市区的边界框:
如果应用程序还使用层信息或Geohash信息,则可以使用这些值来更好地缩小要搜索的文档数量。 我将在稍后讨论与Lucene和Solr进行索引和搜索的细节时演示这一点。
查询解析归结为确定查询的哪一部分包含要搜索的关键字以及哪一部分包含位置信息。 此过程的后半部分称为地理编码 (请参阅参考资料 )。 尽管我将在查询解析的上下文中讨论地理编码,但在索引编制过程中它也很有用。 考虑以下用户查询示例:
华盛顿特区宾夕法尼亚大道1600号 华盛顿大道1号 宾夕法尼亚州费城 美国购物中心,布卢明顿市东百老汇路60号,明尼苏达州55425 美国购物中心附近的餐厅 美国购物中心的餐厅检查前两个查询提出了一些有趣的观点:
术语的顺序几乎总是很重要,而在基于纯文本的搜索中,顺序可能并不重要。 名录和其他空间资源像GeoNames的(见相关信息 )可以在地址转换成位置非常有用。 这些资源通常包含景点列表,例如,白宫等地标。 重要的是标准化缩写(例如Ave.和DC),或者使用同义词来覆盖用户输入地址信息的各种方式。其余查询说明了其他一些细微之处。 例如,在第三个查询中,用户指定了完整地址; 如果要针对每个属性的字段进行搜索,则必须小心解析名称,地址,城市,州和邮政编码。 在最后两个查询中,用户选择“ 近”和“ 入”之间的细微差别可能很重要。 在购物中心某个距离内的任何餐馆都适合第四个查询的用户,而最后一个查询的用户仅对购物中心内的结果感兴趣。 由于与位置相关的主题描述非常复杂,更不用说拼写错误,语言歧义和错误的数据,因此查询解析可能非常困难。
尽管地理编码非常复杂,但是仍然可以使用将地址转换为位置的服务。 两种常用的服务是谷歌地图公共API和GeoNames的(参见相关主题 )。 不幸的是,使用此类Web服务使您必须遵守其使用条款(通常包括使用限制)和Internet流量问题。 对于实际的生产系统,最好实现自己的功能。 虽然这样的实现超出了本文的范围,但要记住的是,GeoNames的数据都是免费下载的,因为许多其他空间资源,比如CIA概况(参见相关主题 )。 如果有足够的资源,最好从基础知识(地址,城市,州)进行构建,然后包含感兴趣的地方和强大的异常处理。 随着时间的流逝,您的查询日志对于创建一个健壮的查询解析器将非常有用,该解析器可以优雅地处理用户扔给它的任何内容。 与任何搜索应用程序一样,请记住,在做出最佳猜测的同时还要求用户进行说明是合理的, 如图2的Google Maps屏幕截图所示 :
在本文中,我将演示一个使用GeoNames服务并具有其他一些功能的基本查询解析器,但我将留给读者以实现生产就绪版本。 此时,您应该有足够的背景知识来开始。 因此,本文的其余部分将重点介绍如何使用Lucene和Solr索引和搜索空间信息。
为了运行示例代码,您需要安装以下内容:
JDK 1.5或更高版本 Ant 1.7或更高 现代网络浏览器,例如Firefox您还需要本文的示例代码(请参阅下载 ),其中包括Apache Solr及其依赖项的副本。 请按照以下步骤安装示例代码:
unzip sample.zip cd geospatial-examples ant install 启动Solr: ant start-solr (要稍后停止Solr,请运行ant stop-solr ) 将浏览器指向http:// localhost:8983 / solr / admin并确认Solr正确启动。 您应该看到一个基本的管理屏幕,其中带有一个用于运行查询的框。一旦启动并运行了Solr,就可以开始在Lucene中使用空间数据了。 运行安装步骤将下载从OSM项目生成的一些示例数据,我已将其放在http://people.apache.org/~gsingers/spatial/中。 在本文中,我包括了我感兴趣的来自美国四个位置的示例OSM数据(文件中列出了OSM的永久链接):
纽约州锡拉丘兹 明尼苏达州明尼阿波利斯市中心。 在明尼苏达州布卢明顿的美国购物中心附近。 南卡罗来纳州查尔斯顿市中心为了演示本文中的许多概念,我编写了将OSM数据编入Solr的代码,并将一些简单的事实与特定位置相关联(例如,参见data目录中的syracuse.facts文件。)目标这项工作的目的是展示如何将非结构化文本和空间数据结合起来以创建有效的搜索应用程序。 另请注意,我使用的是Solr 1.5-dev版本(Solr的当前开发主干),而不是最近发布的Solr 1.4。
Lucene 2.9添加了两个新功能,它们在空间搜索中起着重要作用。 首先,Lucene实现了更好的数值范围查询和过滤功能,通常用于边界框方法。 其次,Lucene有一个新的contrib模块,其中包含以前称为Local Lucene的独立项目(请参阅参考资料 )。 (该代码位于Lucene的contrib / spatial中; 示例代码中包括了JAR文件。)spatial contrib提供了用于创建笛卡尔层和Geohash代码的工具,以及用于创建Lucene查询和过滤对象的工具。
在查看索引数据的代码之前,重要的是评估您期望与数据交互的方式以及在应用程序中必须处理多少数据。 例如,对于大多数文档数量较少至中等(例如,少于一千万)的人来说,索引经纬度并使用简单的数值范围查询可能会产生足够的性能,而大型应用程序可能需要做更多特定的事情( (例如添加笛卡尔级)以减少要过滤和评分的字词和文档的数量。 考虑存储信息的格式也很重要。许多空间距离算法要求数字以弧度为单位,而其他算法则可以使用度数。 因此,在索引编制过程中将您的经/纬度值转换为弧度可能是值得的,而不必在搜索过程中一遍又一遍地转换。 当然,如果您同时需要两种格式,则需要更多的空间(磁盘和可能的内存)。 最后,您是否要对位置特征进行分面,排序和评分,而不是仅使用它们进行过滤? 如果是这样,那么您可能还需要其他表示形式。
因为本文只是演示概念,而不是供生产使用,所以我将展示如何在一些示例Java代码中将索引Geohash,笛卡尔层以及纬度和经度全部放在一个位置。 首先,我已经在Solr模式中定义了许多值(位于geospatial-examples / solr / conf / schema.xml中)以捕获OSM数据。 清单1显示了位置的主要字段:
尽管我正在使用Solr模式来演示要索引的字段,但是在Lucene中也可以轻松获得此处的所有概念。 例如,一个tdouble只是Lucene 2.9.1代表NumericField的精度为8。
我将tdouble值存储为tdouble字段。 tdouble是使用Trie结构内部表示的double 。 然后,Lucene可以使用它来显着减少范围计算(边界框)中需要评估的术语数量,尽管事实上它实际上为索引添加了更多术语。 我将Geohash存储为一个简单的string (未分析),因为我只想对其进行完全匹配。 尽管我执行的计算类型严格不需要高程,但是高程存储为tfloat ,它只是存储在Trie结构中的float 。 最后, tier_*动态字段允许应用程序动态添加笛卡尔层字段,而无需预先声明每个字段。 我将留给您探索索引过程捕获的其他元数据字段。
负责索引数据的代码位于sample.zip文件的源树中。 Driver类是用于启动索引过程的命令行实用程序,实际索引是作为名为OSMHandler的SAX ContentHandler实现的一部分进行的。 在OSMHandler代码中,感兴趣的关键行在startElement()方法中。 我将其分为三个部分。 如清单2所示,第一个示例将纬度和经度索引为double ,并将其转换为弧度以进行索引:
索引纬度/经度非常简单。 接下来,我为经纬度索引了Geohash值,如清单3所示:
在清单3的Geohash代码中,我使用Lucene空间contrib包随附的GeoHashUtils.encode() (有一个等效的decode()方法)方法将lat / lon对转换为单个Geohash字符串,然后添加到Solr。 最后,要添加笛卡尔层支持,我在OSMHandler代码中做了两件事:
在构造函数中,我设置了CartesianTierPlotter类的n实例,对于要索引的每一层一个实例。 在startElement()方法中,我遍历了n绘图仪,并获得了包含当前OSM元素的纬度和经度的每个网格元素的标识符。 此代码类似于清单4:通常,查询一次只需要针对一个层,因此拥有多个层通常不会造成问题。 您应该根据要进行搜索的粒度来选择所需的层数。 如果您花时间浏览其余的索引代码,您将看到我在OSM文件中添加了与数据点相关的各种其他元数据值。 我目前仅索引两种OSM数据类型: 节点和方式 。 节点只是在特定纬度和经度上的位置,而一种方式是某种方式都相关的节点的组合,例如街道。 (见OSM数据基元链接相关主题 ,以了解更多有关OSM文件)。
CartesianTierPlotter的工作是获取地球的投影(在我的情况下,我使用正弦投影;请参阅参考资料 )和经纬度信息,将其向下转换为分层系统使用的网格,并分别给出网格单元格的唯一编号。 在搜索期间,应用程序可以指定网格ID以限制搜索以查找结果。
现在您已经了解了创建包含空间信息的Solr文档的基础知识,只需执行它即可。 Driver类接收数据和事实文件以及Solr运行所在的URL,然后将工作交给OSM2Solr类。 然后, OSM2Solr类使用Solr的Java客户端SolrJ来获取OSMHandler SAX解析器创建的文档,并将其批量发送到Solr服务器。 您可以使用命令行自行运行Driver类,也可以简单地运行ant index ,Ant会做必要的工作来运行驱动程序。 完成后,将浏览器指向http:// localhost:8983 / solr / select /?q = *:*并确认Solr找到了68,945个文档。 花一点时间仔细阅读返回的结果并熟悉其内容。
我只是简单地介绍了将OSM数据切片和切块的多种方法,但是现在该继续讨论如何在应用程序中利用此数据了。
现在索引中已有数据了,是时候重新审视使用它的不同方式了。 即,我将演示如何根据索引中的空间信息对文档进行排序,增强和过滤。
对于许多空间应用而言,按距离增强和分类文档是常见的要求。 为此,Lucene和Solr带有多种计算距离的功能(请参阅参考资料 )。 Lucene包括用于基于大圆(Haversine)公式进行排序和过滤的工具(请参阅DistanceUtils和DistanceFieldComparatorSource ),而Solr具有一些用于计算距离的FunctionQuery函数,包括:
大圈子(Haversine和Geohash Haversine) 欧几里得和平方欧几里得 曼哈顿和其他p范数使用Solr的距离功能,按距离增加非常容易。 我将重点介绍Solr的函数查询,因为它们最易于使用并且不需要编程即可利用,但是它们可以轻松地在Lucene中使用或移植到Lucene。
如前所述,我设置了几个字段来存储OSM数据,包括lat/lon , lat_rad/lon_rad和geohash 。 然后,我可以搜索并提高这些值:
hsin (大圆圈): http://localhost:8983/solr/select/?q=name:Minneapolis AND _val_:"recip(hsin(0.78, -1.6, lat_rad, lon_rad, 3963.205), 1, 1, 0)"^100 dist (Euclidean,Manhattan,p-norm): http://localhost:8983/solr/select/?q=name:Minneapolis AND _val_:"recip(dist(2, lat, lon, 44.794, -93.2696), 1, 1, 0)"^10 0 sqedist (Squared Euclidean): http://localhost:8983/solr/select/?q=name:Minneapolis AND _val_:"recip(sqedist(lat, lon, 44.794, -93.2696), 1, 1, 0)"^100 ghhdist (Geohash Haversine): http://localhost:8983/solr/select/?q=_val_:"recip (ghhsin(geohash(44.79, -93), geohash, 3963.205), 1, 1, 0)"^100在每种情况下,我都将关键字查询与基于距离的FunctionQuery组合在一起,以生成一个既包含关键字得分又包含距离得分的结果集。 要查看每个部分的效果,请在每个查询上添加&debugQuery=true并花一些时间检查Solr产生的解释。 These are just examples of their usage. To see the full signatures and documentation for these and other FunctionQuery functions, see Related topics . Of course, you may choose to boost one part over the other, or otherwise change the calls to fit your needs.
As for sorting by distance, Solr offers one primary option, which really is a bit of a workaround to the fact that Solr does not yet have sort-by-function capabilities and the fact that no custom FieldType s are defined at this point. However, the workaround is simple. To sort by function, create your query as above, but zero out, via boost, the keyword clause, as in q=name:Minneapolis^0 AND _val_:... . This will cause the keyword score to be zero (but the matching documents will still be returned) and the function value to be the sole component of the score. Longer term, look for Solr to add FieldType s for better support of sorting without needing to zero out the main query.
With the sorting and scoring out of the way, it's time to move on to filtering.
To filter by location with Solr, the three primary mechanisms in Table 1 are available to application writers for restricting the document space:
The density of data in a specific range often plays an important role in the user search experience. For example, an application providing business search for Manhattan in New York, NY is much denser than one providing results for Buffalo, Minn. (see Related topics ). In fact, it can be useful to incorporate this information into your filtering function such that the application picks an effective distance to ensure good results. Unfortunately, demonstrating how to do this is beyond this article's scope.
Which approach is right for you? It will depend on the density of your points (see A few words on density ), but a good recommendation is to start off with the simple range approach and then move up to the tier approach if need be. The key factor is to look at the number of terms that need to be evaluated at any one time when calculating the range, because that number is what directly controls how much work Lucene must do in order to restrict the result set.
Building a full-fledged query parser for spatial is beyond the scope of this article. Instead, I'll build a simple QParserPlugin that will handle the chores of obtaining results from location information from GeoNames. This parser assumes that an application can split up the user input ahead of time into two parts: the keyword query and the spatial query. In fact, many local search applications ask the user for input in just this way via two input boxes.
A QParserPlugin is Solr-speak for a query parser plug-in module. Just as many pieces of Solr are pluggable, so to are query parser implementations. For this article, I am using three different query parser plug-ins, one that comes with Solr — FunctionRangeQParserPlugin ({!frange}) — and two I wrote: CartesianTierQParserPlugin ({!tier}) and the GeonamesQParserPlugin . The source for the two plug-ins is located under the src tree in the sample download. Both plug-ins are already configured in Solr via the solrconfig.xml file: QParserPlugin s are invoked in the query by specifying {! parserName [ parameters ]}[ query ] , (parameters and query may be optional), as in {!tier x=32 y=-79 dist=50 prefix=tier_} and {!frange l=0 u=400}hsin(0.57, -1.3, lat_rad, lon_rad, 3963.205) .
The parser can take several parameters:
topo : Short for toponym (see the GeoNames documentation). The location to search for in GeoNames. 需要。 rows : The number of rows to get back from GeoNames. 可选的。 Default is 1. start : The result to start on. 可选的。 Default is 0. lat : The latitude field name to use as a ValueSource for the FunctionQuery . If specified, lon must also be set. lon : The longitude field name to use as a ValueSource for the FunctionQuery . If specified, lat must also be set. gh : The Geohash field name to use as a ValueSource for the FunctionQuery . If specified, lat / lon must not be set. dist : The distance function to use. String . One of [hsin, 0-Integer.MAX_VALUE, ghhsin] . If a geohash field is specified, then this field is disregarded and ghhsin is automatic. Default is 2 for the 2-norm (Euclidean). unit - KM|M : The units to use, KM for metric, M for English. Default is M . boost - float : The amount to boost the function query by. Default is 1.The code for this example is in GeonamesQParserPlugin.java in the sample download. (The Solr server is already configured in the Solr version included in the download.) Invoking it is much like invoking the CartesianTierQParserPlugin outlined above. For instance, to search for malls in the index relative to Bloomington, Minn., I did http://localhost:8983/solr/select/?q=text:mall AND _query_:"{!geo topo='Bloomington, MN' lat=lat_rad lon=lon_rad dist=hsin}" .
By taking the QParserPlugin approach, I was able to focus in on handling just the specific syntax that was important to me while location-wise still allowing all the text-based query parsing capabilities to proceed as usual.
From here, the GeonamesQParserPlugin could be expanded significantly to work with postal codes and many other location specifications. Naturally, it also needs more error handling and likely needs to be converted to use the GeoNames dataset (see Related topics ) so that it doesn't rely on Web services calls. Solr also has an open issue in its issue tracker for adding spatial query parser support (see Related topics ).
I've demonstrated Lucene and Solr's capabilities for searching, sorting, and filtering text documents based on a point-based model of location. From here, a real local-search application will need to investigate how best to scale, handle user queries, and visualize the results. For scaling, some of the question is answered by how many terms need to be enumerated when the bounding-box filter is created. Beyond the filter question for scaling, the usual search-related factors will apply, such as whether to distribute the index or simply replicate. See the Lucene and Solr Related topics .
If you are interested in building out a more significant GIS application, you will need to add much more sophisticated capabilities for route finding, shape intersection, and a great deal more. But if you need to build out a solid search application that combines the structure of point-based locations with unstructured text, then look no further than Lucene and Solr.
Special thanks to fellow Lucene/Solr committers Ryan McKinley and Yonik Seeley for their insights and review of this article.
翻译自: https://www.ibm.com/developerworks/java/library/j-spatial/index.html