0、引言
疫情当下,大家几乎每天都在用疫情地图的相关功能,其中的核心功能即地理位置检索,使用 ES 可以非常轻松的实现,下面我简单说一下核心的几个功能如何设计和实现。
本文不涉及前段UI的设计和实现以及客户端调用代码,如果大伙儿需要,可以给我留言,需求量比较大的话可以考虑做一个完整项目开源出来,点赞支持下吧
PS:学习本文需要对 Elasticsearch 地理位置检索 的基本功能有一定的了解,推荐阅读:todo
本文案例中所使用的数据,可在文末下载。
1、功能模块
下图为疫情地图的基本木块,本文目的为将地理位置检索应用于项目落地,因此和核心功能无关的业务模块将不再罗列。主要的实现的功能如图所示
主要功能包含:
- 行政地区信息录入
- 机构信息
- 健康码状态上报
- 搜索附近的核酸检测机构(医院)
- 查询某地区的确诊人数(行政区范围内)
- 查询某地区的确诊人员分布
- 查询某个医院所属的行政区
- 统计各省份城市确诊人数
2、索引结构设计
2.1 地区索引
省市区联动是在很多场景下都是非常常见的,其表结构也非常简单,通过一个pid
或者pcode
即可保存起级联关系。
但是在地理位置检索时,只存在空间关系而不存在逻辑关系,不同的地理位置并不是通过关联字段来保存其关系的,因此在索引设计的时候,是否省市区是否采用结构化逻辑存储,取决于业务。单从地理位置检索上来分析,不管是省份、城市、地区还是街道,在地理位置坐标中均属于几何图形,因此无需结构化存储,每个_doc 保存一个geo_shape即为合理的方式。
我这里仍然是保留了省市区之间的逻辑关系,但是这一点对本文要实现的功能是没有用处的。建议在索引中为每一个省份、城市、地区、街道单独创建一个文档,使用polygon存储。
ES 支持仅支持基本的几何图形,并不支持不规则几何图形,省份地区这种图形如何存储?
其实不规则几何图形可以看成是边很多的多边形,当边的数量足够多,经可以足够精确的描述一个不规则多边形。这其实和你玩游戏的时候一样,一些需要显示曲线的场景,其实就是由无数个多边形来描述的,当边的数量足够多的时候,你就看不出的他是个多边形了。
索引 mapping
PUT area { "mappings": { "properties": { "location": { "type": "geo_shape" }, "city": { "properties": { "location": { "type": "geo_shape" }, "district": { "properties": { "location": { "type": "geo_shape" } } } } } } } }
2.2 机构索引
机构可以是:医院、学校、桥梁、公司、隧道等任意单位,具体包含什么取决于你在做功能时想搜索到什么,一般机构为一个点坐标即可,即:geo_shape:point。为了简化代码,我这里只存储一个医院信息,也可以理解为核酸检测机构的网点。
索引 mapping 如下:
PUT hospital { "mappings": { "properties": { "properties" : { "address" : { "type" : "text", "fields" : { "keyword" : { "type" : "keyword", "ignore_above" : 256 } } }, "district" : { "type" : "text", "fields" : { "keyword" : { "type" : "keyword", "ignore_above" : 256 } } }, "location" : { "type" : "geo_shape" }, "lv" : { "type" : "text", "fields" : { "keyword" : { "type" : "keyword", "ignore_above" : 256 } } }, "name" : { "type" : "text", "fields" : { "keyword" : { "type" : "keyword", "ignore_above" : 256 } } } } } } }
2.3 人员信息索引(上报信息)
人员信息上报其实包含了如健康码、行程码(行程轨迹)、核酸检测报告、健康状态等信息,信息上报基本发生在门禁扫码登记、核酸检测结果发生时期等,行程轨迹上报时间可通过手机连接的基站发生变化而做出轨迹测算和上报。为了简化功能,我们人员的健康信息只保留一个状态字段:及健康或感染(确诊)。
索引 mapping:
PUT case_person { "mappings": { "properties": { "date": { "type": "date" }, "location": { "type": "geo_shape" }, "name": { "type": "text", "fields": { "keyword": { "type": "keyword", "ignore_above": 256 } } }, "status": { "type": "text", "fields": { "keyword": { "type": "keyword", "ignore_above": 256 } } } } } }
3、实现
3.1 统计当日新增
这里只是做一个简单的统计,不再区分本土、外来、无症状等业务字段,统计当日新增,即表示满足
- 确诊时间为当日
- 状态为已确诊
两个条件的所有人员信息,代码如下:
GET case_person/_search { "size": 0, "query": { "bool": { "must": [ {"range": { "date": { "gte": "now/d" } }}, { "term": { "status.keyword": { "value": "确诊" } } } ] } }, "aggs": { "today_add": { "value_count": { "field": "status.keyword" } } } }
3.2 查询周边的确诊病例
查询周边确诊病例或者查询所在行政区的确诊病例,可应用如判断所属地区是否属于中高风险地区等业务,实现起来也非常简单。只需提供两个条件
- 当前所处经纬度坐标
- 搜索半径
如果是在APP中,当前所处坐标直接通过调用SDK中提供的位置接口即可获取,如果是用于测试,可通过百度地图提供的开放接口在地图上选点获取坐标。操作步骤为:
滚动页面,找到页面中开发文档部分,点击坐标拾取器
此时,地图中鼠标经过处便会显示实时坐标:
可以在地图中选出自己心仪的坐标,模拟自己当前所处位置。比如我当前所处位置经纬度为:
- “lon”:116.238334,
- “lat”:39.900112
以图中定位的永辉超市为圆心,半径三公里,搜索到了 八角游乐园、八宝山、玉泉路、五棵松四个地铁站(这里地铁站可以看做是确认病例),即三公里范围内有四个确诊病例。
这样的搜索,同样可用于搜索附近的核酸检测机构:代码如下:
3.2 搜索指定区域内的指定单位
搜索指定区域内单位,可以搜索各种单位,也可以对各个行政地区进行搜索。
比如搜索北京市-海淀区内所有核酸检测机构:
代码如下:
GET province_bak/_search { "query": { "term": { "name.keyword": { "value": "海淀区" } } } } GET hospital/_search { "_source": {"include":["name","district"]}, "query": { "geo_shape": { "location": { "indexed_shape": { "index": "province_bak", "id": "110108", "path": "location" }, "relation": "within" } } } }