1 地图兴趣点搜索
1. 地图搜索无处不在
随着本世纪初Google Map的诞生,地图以一个全新形式进入人们的视野,大家发现原来地图不只是躺在课本里的彩页,还可以与我们互动。今天地图在生活中已经无处不在。导航地图,搜索附近美食,看一下我的外卖距离我还有多远。。。这些我们已经习以为常的功能充满了地图的奇思妙用,也蕴藏了大量时空数据技术的运用。这篇开始我们将揭秘一些地图原理,让大家了解更多地图背后的故事。
本文要介绍是地图地址搜索,专业名词为POI(Point Of Interest)检索。这个功能相信大多数人都使用过,比如,朋友约我到一个地点吃饭,我只记得这个饭馆的大致名称,于是会在地图搜索框里输入一些关键词,地图软件就很神奇的给为我推荐出了很多相近的地点,并且在地图上展示相应的位置。我们要做的就是结合地图上的位置和这些地点名字找出那个饭馆。
这个我们几乎每天都会用到的功能如果要实现,大致有三个步骤,1、地址数据清洗:去除包括经纬度异常点,重复点,空值等等;2、地址信息索引与搜索;3、经纬度地图打点。简单来讲就是,处理数据、存储与查询数据、展示数据。这三个步骤里最枯燥繁琐的就是数据的清洗,最有意思也是最有挑战性的的是地址信息索引与搜索,最简单的就是结果的展示。下面我们通过简单的实践,看看如何搭建一个基础的地址搜索服务来实现存储与搜索。
2. 如何匹配地址
地址信息索引与搜索大致流程可以用下面简单的示意图表示。将带有位置信息的地名地址数据导入数据存储,并建立索引,一般需要建立全文倒排索引+空间索引。当客户端输入关键词时,会利用索引进行搜索,最终返回匹配的结果集。
这样看来和普通的业务系统也没有太大区别,关键是如何建立这个索引库以及怎样筛选结果。通常,在一个大型的地图引擎内部因为要服务的场景非常复杂,而且对准确率要求非常高,因此通常不会只建立一个索引库,不会只有一种匹配规则。本文,我们只关注主要的流程,建立一个最简单的框架,方便大家理解。
我们不难发现,地址信息的搜索最关键的步骤是地址文本的匹配。简单讲,就是如何用输入的词语检索出我们真正想要的地址文本。这和普通的关系数据库有些不同,因为我们的地址数据通常不会有很明确的结构,同时,用户输入信息也不一定规范。这种场景更类似于全文检索,通过对比查询词和数据库内的地址,返回最相似的文本结果。因此建立一个含有空间坐标信息的地址全文索引库才是实现这个功能的重点。
3. 地址搜索简单实现
3.1 准备数据集
此次实验我们使用开源OSM数据,它是由全球用户共同协作的wiki地图数据,里面的数据种类非常全面,虽然准确性不是特别高,但是作为实验数据集完全够用。下面这幅图是在QGIS截取的北京经开区某个住宅区的地图,黄色圆点是OSM的POI数据,红字是POI的地址名称。
为了有更好的搜索效果,需要对数据进行一定的处理,这里我们需要把POI数据和行政区划数据进行叠加,使得POI的地址文本增加行政区域的信息。比如,地图上的“肯德基”,处理完之后就变成了“北京市经开区永华街道肯德基”,这样在我们搜索某个区域的肯德基时,就更有可能得到准确的结果。
3.2 ES搭建地址全文索引
全文索引有很多实现方案,比较成熟的产品有solr、elasticsearch、sphinx等等,还可以基于搜索框架Lucene直接扩展。这里我们方便起见直接用ES来搭建,它内部也支持空间信息的存储,并且分布式架构更方便后面扩展。
3.2.1 安装搜索引擎
ES的安装非常简单,下载然后直接启动就能用,可以参考官网:https://www.elastic.co/guide/en/elasticsearch/reference/current/setup.html。但是,这里要说明的是,光有原生的ES还不够,我们需要一个中文分词器,因为原生的分词器只适用于英文,对于中文信息完全不能用。关于中文分词器的安装可以参考:http://t.zoukankan.com/chenmc-p-9525163.html,我们使用比较流行的IK分词器。
IK分词器内部有两种分词方式,ik_max_word和ik_smart,以“中华人民共和国国歌”为例,
- ik_max_word会拆分的非常细:中华人民共和国、中华人民、中华、华人、人民共和国、人民、人、民、共和国、共和、和、国国、国歌…
- ik_smart则会比较克制:中华人民共和国、国歌
很难说哪个更好,这要取决于你的场景和你的词典,如果你没有足够丰富的词典,并且对准确性要求没有特别高,只要能返回沾边的信息就行,那就可以用ik_max_word。如果你有很强大的词典,而且不想要太傻的结果,那你可以尝试ik_smart,这还不够,因为你需要了解更多原理知识来调整匹配策略,这些内容会在后面文章中探讨。为了能匹配的更准确,这里建立了一个政区词典,把所有政区的名词全部导了进去,下面图一是词典内的一些行政区名称,图二是在ik配置文件内放置好的词典文件。(IK分词器扩展词典:https://github.com/medcl/elasticsearch-analysis-ik)
3.2.2 创建索引
创建索引类似于mysql中创建一个表,我们要定义表结构,以及分词的方式。这里分词有两个方面,一个是构建索引词库的分词,一个是对搜索词语的分词,我们都设置为ik_smart。另外,将经纬度进行整合,构造一个geo_point字段,方便后面空间搜索使用。
3.2.3 导入地址信息
地址信息的导入建议写一个脚本,方便后面重复使用。ES提供了相应的rest api,我们只需要读取数据映射到对应的property字段即可。可参考java版本:https://www.elastic.co/guide/en/elasticsearch/client/java-rest/current/java-rest-high-document-index.html
3.3 搜索地址
搜索地址可以直接调用ES的rest接口,利用match函数进行全文匹配。如下例子,搜索“通州区万达广场”,ES会返回最可能的几个结果。我们还可以把解释器打开(在match命令后面加上?explain=true),看一下返回记录的分数和计算方式。关于解释器和分数计算,会在后面文章介绍。
可以看到返回的结果基本符合我们的常识,只是排名第一的“建国路万达广场”有点突兀,查了一下它竟然位于朝阳区,而第二第三名里面有很明显的“通州区”字样,为啥反而排名很低呢?关于怎样让结果更符合我们的预期,后面文章再聊!
3.4 展示
结果的展示非常简单,只需要将获取的location坐标通过前端组件展示到地图上即可,openlayers、mapbox、leaflet都是不错的开源框架,这里不再赘述。
4. JUST轻松实现
4.1 导入POI数据
在时空数据接入模块,选择本地shpfile数据导入到服务器上,这里选择了通州区的地址数据。
4.2 查看已有数据表
在时空数据查询模块,通过JUST-SQL检验数量是否一致
4.3 发布POI检索服务
4.3.1 建立一个正逆地理编码的场景
4.3.2 配置服务参数
4.3.3 查看服务文档
参考文章
- https://www.elastic.co/guide/en/elasticsearch/reference/7.9/query-dsl.html.
- Goldberg D W, Wilson J P, Knoblock C A. From text to geographic coordinates: the current state of geocoding[J]. URISA journal, 2007, 19(1): 33-46.
- https://lucene.apache.org/core/9_3_0/index.html
- 京东城市时空数据引擎JUST主页:https://just.urban-computing.cn/#/productCenter