实战ELK(9) Elasticsearch地理位置
地理坐标点(geo-point) 是指地球表面可以用经纬度描述的一个点。地理坐标点可以用来计算两个坐标位置间的距离,或者判断一个点是否在一个区域中。地理坐标点不能被动态映射(dynamic mapping)自动检测,而是需要显式声明对应字段类型为 geo_point 。
PUT /attractions
{
"mappings": {
"restaurant": {
"properties": {
"name": {
"type": "string"
},
"location": {
"type": "geo_point"
}
}
}
}
}
如上例,location 被声明为 geo_point 后,我们就可以索引包含了经纬度信息的文档了。经纬度信息的形式可以是字符串,数组或者对象。
PUT /attractions/restaurant/
{
"name": "Chipotle Mexican Grill",
"location": "40.715, -74.011" <>
} PUT /attractions/restaurant/
{
"name": "Pala Pizza",
"location": { <>
"lat": 40.722,
"lon": -73.989
}
} PUT /attractions/restaurant/
{
"name": "Mini Munchies Pizza",
"location": [ -73.983, 40.719 ] <>
}
- <1> 以半角逗号分割的字符串形式 “lat,lon”;
- <2> 明确以 lat 和 lon 作为属性的对象;
- <3> 数组形式表示 [lon,lat]。
注意 :
可能所有人都至少踩过一次这个坑:地理坐标点用字符串形式表示时是纬度在前,经度在后(”latitude,longitude”),而数组形式表示时刚好相反,是经度在前,纬度在后([longitude,latitude])。其实,在 Elasticesearch 内部,不管字符串形式还是数组形式,都是纬度在前,经度在后。不过早期为了适配 GeoJSON 的格式规范,调整了数组形式的表示方式。因此,在使用地理位置(geolocation)的路上就出现了这么一个“捕熊器”,专坑那些不了解这个陷阱的使用者。
通过地理坐标点过滤
有四种地理坐标点相关的过滤方式可以用来选中或者排除文档:
geo_bounding_box::
找出落在指定矩形框中的坐标点
geo_distance::
找出与指定位置在给定距离内的点
geo_distance_range::
找出与指定点距离在给定最小距离和最大距离之间的点
geo_polygon::
找出落在多边形中的点。这个过滤器使用代价很大。当你觉得自己需要使用它,最好先看看 geo-shapes
所有这些过滤器的工作方式都相似: 把 索引中所有文档(而不仅仅是查询中匹配到的部分文档,见 fielddata-intro)的经纬度信息都载入内存,然后每个过滤器执行一个轻量级的计算去判断当前点是否落在指定区域。
提示
地理坐标过滤器使用代价昂贵 —— 所以最好在文档集合尽可能少的场景使用。 你可以先使用那些简单快捷的过滤器,比如 term 或者 range,来过滤掉尽可能多的文档,最后才交给地理坐标过滤器处理。
布尔型过滤器(bool filter)会自动帮你做这件事。 它会优先让那些基于“bitset”的简单过滤器(见 filter-caching)来过滤掉尽可能多的文档,然后依次才是地理坐标过滤器或者脚本类的过滤器。
地理坐标盒模型过滤器
这是目前为止最有效的 地理坐标过滤器了,因为它计算起来非常简单。 你指定一个矩形的 顶部(top), 底部(bottom), 左边界(left), 和 右边界(right), 然后它只需判断坐标的经度是否在左右边界之间,纬度是否在上下边界之间。
GET /attractions/restaurant/_search
{
"query": {
"filtered": {
"filter": {
"geo_bounding_box": {
"location": { <>
"top_left": {
"lat": 40.8,
"lon": -74.0
},
"bottom_right": {
"lat": 40.7,
"lon": -73.0
}
}
}
}
}
}
}
- <1> 盒模型信息也可以用 bottom_left(左下方点)和 top_right(右上方点) 来表示。
优化盒模型
地理坐标盒模型过滤器不需要把所有坐标点都加载到内存里。 因为它要做的只是简单判断 纬度 和 经度 坐标数值是否在给定的范围内,所以它可以用倒排索引来做一个范围(range)过滤。
要使用这种优化方式,需要把 geo_point 字段用 纬度(lat)和经度(lon)方式表示并分别索引。
PUT /attractions
{
"mappings": {
"restaurant": {
"properties": {
"name": {
"type": "string"
},
"location": {
"type": "geo_point",
"lat_lon": true <>
}
}
}
}
}
- <1> location.lat 和 location.lon 字段将被分别索引。它们可以被用于检索,但是不会在检索结果中返回。
然后,查询时你需要告诉 Elasticesearch 使用已索引的 lat和lon。
GET /attractions/restaurant/_search
{
"query": {
"filtered": {
"filter": {
"geo_bounding_box": {
"type": "indexed", <>
"location": {
"top_left": {
"lat": 40.8,
"lon": -74.0
},
"bottom_right": {
"lat": 40.7,
"lon": -73.0
}
}
}
}
}
}
}
<1> 设置 type 参数为 indexed (默认为 memory) 来明确告诉 Elasticsearch 对这个过滤器使用倒排索引。
注意:
geo_point 类型可以包含多个地理坐标点,但是针对经度纬度分别索引的这种优化方式(lat_lon)只对单个坐标点的方式有效。
地理距离过滤器
地理距离过滤器(geo_distance)以给定位置为圆心画一个圆,来找出那些位置落在其中的文档:
GET /attractions/restaurant/_search
{
"query": {
"filtered": {
"filter": {
"geo_distance": {
"distance": "1km", <>
"location": { <>
"lat": 40.715,
"lon": -73.988
}
}
}
}
}
}
<1> 找出所有与指定点距离在1公里(1km)内的 location 字段。访问 Distance Units 查看所支持的距离表示单位
<2> 中心点可以表示为字符串,数组或者(如示例中的)对象。详见 lat-lon-formats。
地理距离过滤器计算代价昂贵。 为了优化性能,Elasticsearch 先画一个矩形框(边长为2倍距离)来围住整个圆形, 这样就可以用消耗较少的盒模型计算方式来排除掉那些不在盒子内(自然也不在圆形内)的文档, 然后只对落在盒模型内的这部分点用地理坐标计算方式处理。
提示
你需要判断你的使用场景,是否需要如此精确的使用圆模型来做距离过滤? 通常使用矩形模型是更高效的方式,并且往往也能满足应用需求。
更快的地理距离计算
两点间的距离计算,有多种性能换精度的算法:
arc::
最慢但是最精确是弧形(arc)计算方式,这种方式把世界当作是球体来处理。 不过这种方式精度还是有限,因为这个世界并不是完全的球体。
plane::
平面(plane)计算方式,(((“plane distance calculation”)))把地球当成是平坦的。 这种方式快一些但是精度略逊;在赤道附近位置精度最好,而靠近两极则变差。
sloppy_arc::
如此命名,是因为它使用了 Lucene 的 SloppyMath 类。 这是一种用精度换取速度的计算方式,它使用 Haversine formula 来计算距离; 它比弧形(arc)计算方式快4~5倍, 并且距离精度达99.9%。这也是默认的计算方式。
你可以参考下例来指定不同的计算方式:
GET /attractions/restaurant/_search
{
"query": {
"filtered": {
"filter": {
"geo_distance": {
"distance": "1km",
"distance_type": "plane", <>
"location": {
"lat": 40.715,
"lon": -73.988
}
}
}
}
}
}
- <1> 使用更快但精度稍差的平面(plane)计算方式。
提示: 你的用户真的会在意一个宾馆落在指定圆形区域数米之外了吗? 一些地理位置相关的应用会有较高的精度要求;但大部分实际应用场景中,使用精度较低但响应更快的计算方式可能就挺好。
地理距离区间过滤器
地理距离过滤器(geo_distance)和地理距离区间过滤器(geo_distance_range)的唯一差别在于后者是一个环状的,它会排除掉落在内圈中的那部分文档。
指定到中心点的距离也可以换一种表示方式: 指定一个最小距离(使用 gt或者gte)和最大距离(使用lt或者lte),就像使用区间(range)过滤器一样。
GET /attractions/restaurant/_search
{
"query": {
"filtered": {
"filter": {
"geo_distance_range": {
"gte": "1km", <>
"lt": "2km", <>
"location": {
"lat": 40.715,
"lon": -73.988
}
}
}
}
}
}
- 匹配那些距离中心点超过1公里而小于2公里的位置。
缓存地理位置过滤器#
因为如下两个原因,地理位置过滤器默认是不被缓存的:
地理位置过滤器通常是用于查找用户当前位置附近的东西。但是用户是在移动的,并且没有两个用户的位置完全相同,因此缓存的过滤器基本不会被重复使用到。
过滤器是被缓存为比特位集合来表示段(segment)内的文档。假如我们的查询排除了几乎所有文档,只剩一个保存在这个特别的段内。一个未缓存的地理位置过滤器只需要检查这一个文档就行了,但是一个缓存的地理位置过滤器则需要检查所有在段内的文档。
缓存对于地理位置过滤器也可以很有效。 假设你的索引里包含了所有美国的宾馆。一个在纽约的用户是不会对旧金山的宾馆感兴趣的。 所以我们可以认为纽约是一个热点(hot spot),然后画一个边框把它和附近的区域围起来。
如果这个地理盒模型过滤器(geo_bounding_box)被缓存起来,那么当有位于纽约市的用户访问时它就可以被重复使用了。 它可以直接排除国内其它区域的宾馆。然后我们使用未缓存的,更加明确的地理盒模型过滤器(geo_bounding_box)或者地理距离过滤器(geo_distance)来在剩下的结果集中把范围进一步缩小到用户附近:
GET /attractions/restaurant/_search
{
"query": {
"filtered": {
"filter": {
"bool": {
"must": [
{
"geo_bounding_box": {
"type": "indexed",
"_cache": true, <>
"location": {
"top_left": {
"lat": ,,
"lon": -74.1
},
"bottom_right": {
"lat": 40.4,
"lon": -73.7
}
}
}
},
{
"geo_distance": { <>
"distance": "1km",
"location": {
"lat": 40.715,
"lon": -73.988
}
}
}
]
}
}
}
}
}
- <1> 缓存的地理盒模型过滤器把结果集缩小到了纽约市。
- <2> 代价更高的地理距离过滤器(geo_distance)让结果集缩小到1km内的用户。
减少内存占用
每一个 经纬度(lat/lon)组合需要占用16个字节的内存。要知道内存可是供不应求的。 使用这种占用16字节内存的方式可以得到非常精确的结果。不过就像之前提到的一样,实际应用中几乎都不需要这么精确。
你可以通过这种方式来减少内存使用量: 设置一个压缩的(compressed)数据字段格式并明确指定你的地理坐标点所需的精度。 即使只是将精度降低到1毫米(1mm)级别,也可以减少1/3的内存使用。 更实际的,将精度设置到3米(3m)内存占用可以减少62%,而设置到1公里(1km)则节省75%之多。
这个设置项可以通过 update-mapping API 来对实时索引进行调整:
POST /attractions/_mapping/restaurant
{
"location": {
"type": "geo_point",
"fielddata": {
"format": "compressed",
"precision": "1km" <>
}
}
}
<1> 每一个经纬度(lat/lon)组合现在只需要4个字节,而不是16个。
另外,你还可以这样做来避免把所有地理坐标点全部同时加载到内存中: 使用在优化盒模型(optimize-bounding-box)中提到的技术, 或者把地理坐标点当作文档值(doc values)来存储。
PUT /attractions
{
"mappings": {
"restaurant": {
"properties": {
"name": {
"type": "string"
},
"location": {
"type": "geo_point",
"doc_values": true <>
}
}
}
}
}
<1> 地理坐标点现在不会被加载到内存,而是保存在磁盘中。
将地理坐标点映射为文档值的方式只能是在这个字段第一次被创建时。 相比使用字段值,使用文档值会有一些小的性能代价,不过考虑到它对内存的节省,这种方式通常是还值得的。
按距离排序
检索结果可以按跟指定点的距离排序:
提示 当你可以(can)按距离排序时,按距离打分(scoring-by-distance)通常是一个更好的解决方案。
GET /attractions/restaurant/_search
{
"query": {
"filtered": {
"filter": {
"geo_bounding_box": {
"type": "indexed",
"location": {
"top_left": {
"lat": ,,
"lon": -74.0
},
"bottom_right": {
"lat": 40.4,
"lon": -73.0
}
}
}
}
}
},
"sort": [
{
"_geo_distance": {
"location": { <>
"lat": 40.715,
"lon": -73.998
},
"order": "asc",
"unit": "km", <>
"distance_type": "plane" <>
}
}
]
}
- <1> 计算每个文档中 location 字段与指定的 lat/lon 点间的距离。
- <2> 以 公里(km)为单位,将距离设置到每个返回结果的 sort 键中。
- <3> 使用快速但精度略差的平面(plane)计算方式。
你可能想问:为什么要制定距离的单位(unit)呢? 用于排序的话,我们并不关心比较距离的尺度是英里,公里还是光年。 原因是,这个用于排序的值会设置在每个返回结果的 sort 元素中。
...
"hits": [
{
"_index": "attractions",
"_type": "restaurant",
"_id": "",
"_score": null,
"_source": {
"name": "New Malaysia",
"location": {
"lat": 40.715,
"lon": -73.997
}
},
"sort": [
0.08425653647614346 <>
]
},
...
- <1> 宾馆距离我们的指定位置距离是 0.084km。
- 你可以通过设置单位(unit)来让返回值的形式跟你应用中想要的匹配。
提示
地理距离排序可以对多个坐标点来使用,不管(这些坐标点)是在文档中还是排序参数中。 使用 sort_mode 来指定是否需要使用位置集合的 最小(min),最大(max)或者平均(avg)距离。 这样就可以返回离我的工作地和家最近的朋友这样的结果了。
按距离打分
有可能距离只是决定返回结果排序的唯一重要因素,不过更常见的情况是距离会和其它因素, 比如全文检索匹配度,流行程度或者价格一起决定排序结果。
遇到这种场景你需要在查询分值计算(function_score query)中指定方式让我们把这些因子处理得到一个综合分。 decay-functions中有个一个例子就是地理距离影响排序得分的。
另外按距离排序还有个缺点就是性能:需要对每一个匹配到的文档都进行距离计算。 而 function_score请求,在 rescore phase阶段有可能只需要对前 n 个结果进行计算处理。
从数据库导入ES的地理位置信息
我们前面已经有篇文章介绍了怎么导入mysql的数据,这里不讲太多。
1、先要为location字段设置type的映射。
必须先定义这个location的映射,再去导入mysql数据。
PUT /coupons
{
"mappings": {
"_doc": {
"properties": {
"location": {
"type": "geo_point"
}
}
}
}
}
2、使用logstash同步mysql数据到ElasticSearch
这里的sql脚本有点特殊,如下。
SELECT Id,CreationTime,CreatorId,LastModifierId,LastModificationTime,
CONCAT(Latitude , ',',Longitude) as location
FROM
qls_couponentitys
3、聚合查询,指定距离范围内有多少优惠
POST /coupons/_search
{
"aggs" : {
"rings_around_amsterdam" : {
"geo_distance" : {
"field" : "location",
"origin" : "24.507098,118.133738 ",
"ranges" : [
{ "to" : },
{ "from" : , "to" : },
{ "from" : }
]
}
}
}
}
4、获取200米范围内的优惠,并根据距离排序
GET /coupons/_search
{
"query": {
"bool" : {
"must" : {
"match_all" : {}
},
"filter" : {
"geo_distance" : {
"distance" : "200m",
"location" : {
"lat" : 24.507098,
"lon" : 118.133738
}
}
}
}
},
"sort": [
{
"_geo_distance": {
"location": {
"lat" : 24.5070,
"lon" : 118.133738
},
"order": "asc",
"unit": "m",
"distance_type": "plane"
}
}
]
}
实战ELK(9) Elasticsearch地理位置的更多相关文章
- ElasticSearch实战系列四: ElasticSearch理论知识介绍
前言 在前几篇关于ElasticSearch的文章中,简单的讲了下有关ElasticSearch的一些使用,这篇文章讲一下有关 ElasticSearch的一些理论知识以及自己的一些见解. 虽然本人是 ...
- ElasticSearch实战系列二: ElasticSearch的DSL语句使用教程---图文详解
前言 在上一篇中介绍了ElasticSearch集群和kinaba的安装教程,本篇文章就来讲解下 ElasticSearch的DSL语句使用. ElasticSearch DSL 介绍 Elastic ...
- K8S(15)监控实战-ELK收集K8S内应用日志
K8S监控实战-ELK收集K8S内应用日志 目录 K8S监控实战-ELK收集K8S内应用日志 1 收集K8S日志方案 1.1 传统ELk模型缺点: 1.2 K8s容器日志收集模型 2 制作tomcat ...
- ElasticSearch实战系列十: ElasticSearch冷热分离架构
前言 本文主要介绍ElasticSearch冷热分离架构以及实现. 冷热分离架构介绍 冷热分离是目前ES非常火的一个架构,它充分的利用的集群机器的优劣来实现资源的调度分配.ES集群的索引写入及查询速度 ...
- ElasticSearch实战系列十一: ElasticSearch错误问题解决方案
前言 本文主要介绍ElasticSearch在使用过程中出现的各种问题解决思路和办法. ElasticSearch环境安装问题 1,max virtual memory areas vm.max_ma ...
- elasticsearch地理位置查询
elasticsearch地理位置查询 一.背景 二.geo数据类型 1.geo_point 2.geo_shape 三.此处对geo_point类型实战 1.背景 2.插入地点数据 1.创建索引 2 ...
- ELK stack elasticsearch/logstash/kibana 关系和介绍
ELK stack elasticsearch 后续简称ES logstack 简称LS kibana 简称K 日志分析利器 elasticsearch 是索引集群系统 logstash 是日志归集集 ...
- ElasticSearch实战系列三: ElasticSearch的JAVA API使用教程
前言 在上一篇中介绍了ElasticSearch实战系列二: ElasticSearch的DSL语句使用教程---图文详解,本篇文章就来讲解下 ElasticSearch 6.x官方Java API的 ...
- ELK 安装Elasticsearch
章节 ELK 介绍 ELK 安装Elasticsearch ELK 安装Kibana ELK 安装Beat ELK 安装Logstash ELK栈要安装以下开源组件: Elasticsearch Ki ...
- ElasticSearch实战系列五: ElasticSearch的聚合查询基础使用教程之度量(Metric)聚合
Title:ElasticSearch实战系列四: ElasticSearch的聚合查询基础使用教程之度量(Metric)聚合 前言 在上上一篇中介绍了ElasticSearch实战系列三: Elas ...
随机推荐
- vue环境配置
wind系统下需要安装node.js 和git 1.安装npm 因为淘宝镜像较快,所以可以使用淘宝镜像安装npm npm install -g cnpm --registry=https://regi ...
- vue 动态循环出的多个select 不能重复选择相同的数据
看图说话 HTML: JS: 1) 2) 3) 有更好的方法可以相互学习.
- VS 2017 + opencv4.0
完全参考这一文章, 非常详细: https://blog.csdn.net/qq_41175905/article/details/80560429 记录自己遇到的问题: 1. VS 2017 专业版 ...
- JavaScript 查找图中连接两点的所有路径算法
1.把图看成以起点为根节点的树 2.使用深度遍历算法遍历路径 3.遍历到节点为目标节点时,保存这条路径 find2PointsPath(sourceId, targetId) { const { no ...
- InsertSort
#include <bits/stdc++.h> using namespace std; #define MAXSIZE 200000 typedef int KeyType; type ...
- CCNet: Criss-Cross Attention for Semantic Segmentation 里的Criss-Cross Attention计算方法
论文地址:https://arxiv.org/pdf/1811.11721v1.pdf code address: https://github.com/speedinghzl/CCNet 相关论文 ...
- day02代码
1.while.....else.....语句,break,continue,if,while,用户交互,输出,运算符 # 用户有三次登录机会 _username = 'tom' _password ...
- 密码疑云 (3)——详解RSA的加密与解密
上一篇文章介绍了RSA涉及的数学知识,本章将应用这些知识详解RSA的加密与解密. RSA算法的密钥生成过程 密钥的生成是RSA算法的核心,它的密钥对生成过程如下: 1. 选择两个不相等的大素数p和q, ...
- Day 22 初识面向对象
一.两种编程思想 1.面向过程编程 核心是'过程',过程指的是解决问题的步骤,就是先干什么再干什么 基于面向过程思想编写程序相当于写一条流水线,是一种机械式的思维方式 优点:解决问题的思路清晰,可以把 ...
- @cacheevict 清除多个key
@Caching(evict={@CacheEvict(“a1”),@CacheEvict(“a2”,allEntries=true)}): 参考:https://www.cnblogs.com/ha ...