日期:2014-05-16  浏览次数:20821 次

使用 Apache Lucene 和 Solr 进行位置感知搜索
http://www.ibm.com/developerworks/cn/java/j-spatial/#
地理空间搜索概念
在构建空间搜索应用程序时,最重要的是识别需要添加到应用程序中的空间数据。这些数据通常以某些地理编码的形式出现,比如纬度、经度和海拔,或以邮政编码或街道地址的形式出现。编码系统的格式越规范,它在您的系统中的使用就越容易。例如,民歌 “Over the River and Through the Woods”(其中有这样的歌词:“to Grandmother's house we go”)就将很多空间信息编码到了歌词中(参见 参考资料)。但这些信息在 GIS 系统中就没有多大用处,因为我们不知道小河和森林的位置。该信息与到外婆家的详细方向(包含出发地址和到达地址的)相比,您将了解到为什么正确编码地址如此重要。(有趣的是,能够提取和编码更常用的方向和地理实体 —— 比如渡过小河 或在棕色房子附近 —— 并根据它们进行推断的系统也是非常有用的,但这不属于本文的讨论范围)。
除了用于识别地理位置的原始地理编码数据之外,许多 GIS 系统还可以添加与实际位置相关的信息。例如,导航系统可以使用在地图上按顺序列出的一系列位置来创建一条从 A 点到 B 点的路线。或者气象学家可以将降雨或恶劣的天气数据添加到特定区域的地图上,从而允许用户搜索到特定区域的降雨量。居住地点相邻的人通常将小的区域合并起来,从而形成 ZIP 编码、地区编码,甚至是城镇、市或州。例如在 OSM 中,用户可以编辑和覆盖地图顶层的信息,比如旅游景点或街道。通过合并各层的信息建立它们之间的关系并进行跟踪,可以生成更加动态和强大的应用程序。
表示空间数据
不管与一个或多个位置相关的信息是什么,搜索应用程序都需要通过一种高效的方式来表示这些数据。尽管可以通过几种方式来表示位置信息,但我仅关注与 Lucene 相关的方式。首先需要注意的是,许多类型的地理空间数据都可以用它们的 “原始” 格式表示,并且能够在搜索应用程序中很好地发挥作用。例如,Syracuse 表示城市 Syracuse 的完美方式,用户只要在搜索栏中输入 Syracuse 就可以找到包含 Syracuse 的所有文档,输入其他搜索关键词也将取得类似的结果。实际上,原始格式是用于表示带名称的位置,比如城市、州和 ZIP 编码的最常用方法。不过要注意,尽管我使用了术语原始表示,您仍然可以先对数据进行转换或格式化。例如,将 New York 转换成 NY 通常是一种合理的做法。
在我介绍 Lucene 能够使用的表示方式之前,您一定要理解所有表示方式都必须考虑到生成它们的空间引用(参见 参考资料)。在美国,最常见的是 World Geodetic System,它通常缩写为 WGS 84(参见 参考资料)。尽管在某些系统之间允许进行转换,但最好用一个系统来表示您的所有数据。本文假设使用同一个系统表示数据。
使用 Lucene 和 Solr 进行搜索时,纬度和经度(缩写为 lat/lon)等数字空间信息的表示方式是最有趣的。纬度和经度通常使用与本初子午线(位于英国的格林威治)相距的度、分和秒来表示,并且通常需要使用 double(或更高的精度)来表示。例如,对于我的例子中使用的数据 —— 美国纽约州的 Syracuse 市 —— 它的经度为东经 76.150026(如果没有指定东方,则为 -76.150026)和北纬 43.049648。
编码每个纬度和经度可能导致索引大量唯一的词汇,这取决于应用程序。这会显著减慢搜索速度,并且您将在本文的后面看到,这通常是不必要的。事实上,许多地图应用程序将搜索与特定领域关联起来,因此储存关于特定区域的适当信息会生成更少的词汇,并且不对搜索结果产生很大的负面影响。这种在精确度上采取折衷的方法通常将纬度和经度封装到层中。您可以将每个层看作是地图的特定部分的缩放级别,比如位于美国中央上方的第 2 层几乎包含了整个北美,而第 19 层可能只是某户人家的后院。尤其是,每个层都将地图分成 2层 # 的箱子或网格。然后给每个箱子分配一个号码并添加到文档索引中。我将在下一小节解释如何利用该信息加快搜索速度。
Lucene 词汇中的纬度和经度通常表示为两个不同的字段,但是这在一些应用程序中可能会影响性能。如果希望使用一个字段,那么可以使用 Geohash 编码方式将纬度/经度编码到一个 String 中(参见 参考资料)。Geohash 的好处是能够通过切去散列码末尾的字符来实现任意的精度。在许多情况下,相邻的位置通常有相同的前缀。例如,在 geohash.org 中输入 Syracuse, NY 将生成散列码 dr9ughxjkrt4b,而输入 Syracuse 的郊区 Cicero, NY 生成散列码 dr9veggs4ptd3,它们的前缀都是 dr9。
到目前为止,我只是谈到几个单独的点,但是许多地理空间应用程序在图像、路线和数据中的其他关系方面都很有趣。Lucene 和 Solr 不具备这些功能;参见 参考资料 了解关于这些概念的更多信息。
回页首
在搜索中将空间数据与文本合并
一旦在索引中添加了数据之后,搜索应用程序在与数据交互时至少有 5 种基本要求:
距离计算:根据给定点计算它到其他点的距离。
限定框过滤器:查找某些特定区域内所有匹配项(文档)。
排序:根据到固定点的距离对搜索结果进行排序。
相关度改进:使用距离作为记录中的增强因素,同时允许其他因素发挥作用。
查询解析:在给出位置的地址或其他一些用户规定时,创建可用于根据索引数据进行搜索的编码表示。
这 5 个因素都可以在基于位置的应用程序中扮演重要的角色,但是我在这里主要关注距离计算、限定框过滤和查询解析。排序和相关度改进仅使用距离计算,我将在本文的后面介绍它们的实际应用。
距离计算
当计算用于 GIS 应用程序的距离时,一定要知道有许多不同的实现方法,并且每种方法都有其优缺点。距离计算可以划分成 3 个组,这取决于应用程序选择以什么方式对地球进行建模。在一些情况下,完全可以采用平面地球模型,通过牺牲一些精确性来获取速度。在平面地球模型中,大部分距离计算都是勾股定理的变体。在其他情况下使用球面模型,所使用的主要距离计算为大圆弧长(参见 参考资料)。大圆弧长计算球面两点之间的最短距离。当两点之间的距离相隔很远和要求更高的准确度时,需要使用球面模型。最后,可以使用椭圆的地球模型和 Vincenty 公式(参见 参考资料)来获取高度精确的距离(精确到 0.5 毫米),但是在许多应用程序中用不上这种复杂的模型。
差之毫厘,失之千里
在许多本地搜索应用程序中,精度的需求由应用程序本身决定。在某些情况下,偏离一公里问题并不大,而在另一些情况下,偏离几毫米就会导致严重的问题。例如,欧几里得距离计算对于跨度很长的距离(比如跨州)通常不够精确,即使是半正矢(大圆)方法也不足以为某些场合提供所需的精度,因为将地球建模成椭圆体比建模成球体更精确。对于这些情况,使用 Vincenty 公式将得到更加满意的结果。在其他应用程序中,唯一需要注意的事情是对结果的排序,因此可以使用 Squared Euclidean Distance(实际不是距离),从而避免平方根计算。
当然,其他距离计算也是有用的,比如曼哈顿距离,它反映在由街区组成的城市中行走的距离(例如在一辆出租车中穿越纽约城的曼哈顿)。但是为了实现本文的目的,我将使用平面地球模型和大圆弧长距离来演示距离,其他方法留给读者探索。此外,本文不将海拔作为影响因素,但是一些应用程序可能需要考虑海拔。要获取更多关于地理距离的信息,请参见 参考资料。
限定框过滤器
在许多基于位置的应用程序中,可以搜索到数百万条地址信息。遍历所有这些数据来查找既包含关键字又在用户指定的距离之内的文档集将需要花费大量时间。一种合理的做法是先缩小文档集的范围然后再计算相关的子集。如果仅储存了纬度和经度信息,那么缩小文档集的首选方法是传入包含指定位置的周边区域的范围。这可以通过 图 1 来表示,其中不完全透明的方框表示包含南卡罗来纳州的查尔斯顿(Charleston)市及其周边地区的限定框:

图 1. 位于 Charleston 中央上方的限定框

如果应用程序还使用层信息或 Geohash 信息,那么可以使用这些值来更好地缩小需要搜索的文档的范围。我将在讨论使用 Lucene 和 Solr 建立索引和搜索的细节时演示这点。
查询解析
查询解析的目的是确定查询的哪个部分包含所搜索的关键字,哪个部分包含位置信息。这个过程的后半部分称为地理编码(geocoding)(参见 参考资料)。尽管我在这里在查询解析的上下文中讨论地理编码,它在索引期间也非常有用。请考虑下面的用户查询例子:
1600 Pennsylvania Ave. Washington, DC
1 Washington Av. Philadelphia Pennsylvania
Mall of America, 60 East Broadway Bloomi