需求: 通过指定点搜索附近的人 , 要求可以过滤年龄, 结果按照距离进行排序, 并且展示她/他距离你多远

本文参考:

es官网文档: https://www.elastic.co/guide/cn/elasticsearch/guide/current/sorting-by-distance.html

Spring官网文档:https://docs.spring.io/spring-data/elasticsearch/docs/4.0.3.RELEASE/reference/html/#new-features


设计:

ES提供了很多地理位置的搜索方式 :

  • geo_bounding_box: 找出落在指定矩形框中的点。
  • geo_distance: 找出与指定位置在给定距离内的点。
  • geo_distance_range: 找出与指定点距离在给定最小距离和最大距离之间的点。

一般我们常用的是前两者:

1. 地理坐标盒模型过滤器

这是目前为止最有效的地理坐标过滤器了,因为它计算起来非常简单。 你指定一个矩形的 顶部 , 底部 , 左边界 ,和 右边界 ,然后过滤器只需判断坐标的经度是否在左右边界之间,纬度是否在上下边界之间: 一般只要设定左上的坐标 和右下的坐标即可

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}}}}}}
}

2. 地理距离过滤器(这个就是我们想要的)

地理距离过滤器( geo_distance )以给定位置为圆心画一个圆,来找出那些地理坐标落在其中的文档:

GET /attractions/restaurant/_search
{"query": {"filtered": {"filter": {"geo_distance": {"distance": "1km", "location": { "lat":  40.715,"lon": -73.988}}}}}
}

距离单位es官方给我们提供了很多种: https://www.elastic.co/guide/en/elasticsearch/reference/5.6/common-options.html#distance-units

常用的我们m, km就够了

3. 地理位置排序

检索结果可以按与指定点的距离排序:

注意: 当你 可以 按距离排序时, 按距离打分 通常是一个更好的解决方案。但是我们要计算当前距离,所以还是使用这个排序

搜索示例:

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.4,"lon": -73.0}}}}}},"sort": [{"_geo_distance": {"location": { "lat":  40.715,"lon": -73.998},"order":         "asc","unit":          "km", "distance_type": "plane" }}]
}

解读以下: (注意看sort对象)

  • 计算每个文档中 location 字段与指定的 lat/lon 点间的距离。
  • 将距离以 km 为单位写入到每个返回结果的 sort 键中。
  • 使用快速但精度略差的 plane 计算方式。

代码实操如下:

1. 环境准备

博主当前ElasticSearch版本为7.8.0;

springboot版本

    <parent><groupId>org.springframework.boot</groupId><artifactId>spring-boot-starter-parent</artifactId><version>2.4.0-SNAPSHOT</version><relativePath/> <!-- lookup parent from repository --></parent>

starter依赖

        <dependency><groupId>org.springframework.boot</groupId><artifactId>spring-boot-starter-data-elasticsearch</artifactId><version>2.4.0-SNAPSHOT</version></dependency>

1. 设计数据库字段和ES的字段mapping

数据库字段设计: 添加两个字段经纬度

CREATE TABLE `es_user` (`id` bigint(20) NOT NULL AUTO_INCREMENT COMMENT '主键',`name` varchar(50) COLLATE utf8mb4_bin DEFAULT NULL,`age` int(5) DEFAULT NULL,`tags` varchar(255) COLLATE utf8mb4_bin NOT NULL DEFAULT '' COMMENT '多标签用  ''|'' 分割',`user_desc` varchar(255) COLLATE utf8mb4_bin DEFAULT '' COMMENT '用户简介',`is_deleted` varchar(1) COLLATE utf8mb4_bin NOT NULL DEFAULT 'N',`gmt_create` datetime DEFAULT CURRENT_TIMESTAMP,`gmt_modified` datetime DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP,`lat` decimal(10,6) DEFAULT '0.000000' COMMENT '维度',`lon` decimal(10,6) DEFAULT '0.000000' COMMENT '经度',PRIMARY KEY (`id`)
) ENGINE=InnoDB AUTO_INCREMENT=657 DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_bin;

Java代码:

/*** (EsUser)实体类** @author makejava* @since 2020-08-10 14:23:59*/
@Data
public class EsUserEntity implements Serializable {private static final long serialVersionUID = 578800011612714754L;/*** 主键*/private Long id;private String name;private Integer age;/*** 多标签用  '|' 分割*/private String tags;/*** 用户简介*/private String userDesc;private String isDeleted = "0";private Date gmtCreate;private Date gmtModified;// 经度private Double lat;// 维度private Double lon;}

Java中对象映射: 我这边直接采用了GeoPoint字段, 根据spring官网, 也可以通过字符串表示, 但是我这里还是用es推荐的字段

@Data
@Document(indexName = "es_user")
public class ESUser {@Idprivate Long id;@Field(type = FieldType.Text)private String name;@Field(type = FieldType.Integer)private Integer age;@Field(type = FieldType.Keyword)private List<String> tags;@Field(type = FieldType.Text, analyzer = "ik_max_word")private String desc;@GeoPointFieldprivate GeoPoint location;}

数据的导入的同步是通过rocketmq进行同步的, 代码不展示了, 参考文档https://blog.csdn.net/weixin_38399962/article/details/107918244

2. 准备海量mock数据

这里笔者以经度:120.24 维度:30.3 作为圆心, mock一些附近的点作为mock用户的坐标数据

    @ApiOperation("录入测试")@PostMapping("/content/test-insert")public Long importEsUser(Long num) {for (int i = 0; i < num; i++) {ThreadPoolUtil.execute(() -> {EsUserEntity esUser = generateRandomMockerUser();esUserService.importEsUser(esUser);});}return num;}// mock随机用户数据private EsUserEntity generateRandomMockerUser() {// 120.247589,30.306362EsUserEntity esUserEntity = new EsUserEntity();int age = new Random().nextInt(20) + 5;esUserEntity.setAge(age);boolean flag = age % 2 > 0;esUserEntity.setName(flag ? RandomCodeUtil.getRandomChinese("0") : RandomCodeUtil.getRandomChinese("1"));esUserEntity.setTags(flag ? "善良|Java|帅气" : "可爱|稳重|React");esUserEntity.setUserDesc(flag ? "大闹天宫,南天门守卫, 擅长编程, 烹饪" : "天空守卫,擅长编程,睡觉");String latRandNumber = RandomCodeUtil.getRandNumberCode(4);String lonRandNumber = RandomCodeUtil.getRandNumberCode(4);esUserEntity.setLon(Double.valueOf("120.24" + latRandNumber));esUserEntity.setLat(Double.valueOf("30.30" + lonRandNumber));return esUserEntity;}

数据导入成功.

3. 核心搜索

我们先设计返回给前台的对象, 和搜索条件类

/*** 功能描述:ES的用户搜索结果** @Author: zhouzhou* @Date: 2020/7/30$ 9:57$*/
@Data
public class PeopleNearByVo {private ESUserVo esUserVo;private Double distance;}
/*** 功能描述:ES的用户搜索结果** @Author: zhouzhou* @Date: 2020/7/30$ 9:57$*/
@Data
public class ESUserVo {private Long id;private String name;private Integer age;private List<String> tags;// 高亮部分private List<String> highLightTags;private String desc;// 高亮部分private List<String> highLightDesc;// 坐标private GeoPoint location;}

搜索类

/*** 功能描述: 搜索附近的人** @Author: zhouzhou* @Date: 2020/8/14$ 11:13$*/
@Data
public class ESUserLocationSearch {// 纬度 [3.86, 53.55]private Double lat;// 经度 [73.66, 135.05]private Double lon;// 搜索范围(单位米)private Integer distance;// 年龄大于等于private Integer ageGte;// 年龄小于private Integer ageLt;
}

核心搜索方法:

/*** 搜索附近的人* @param locationSearch* @return*/public Page<PeopleNearByVo> queryNearBy(ESUserLocationSearch locationSearch) {Integer distance = locationSearch.getDistance();Double lat = locationSearch.getLat();Double lon = locationSearch.getLon();Integer ageGte = locationSearch.getAgeGte();Integer ageLt = locationSearch.getAgeLt();// 先构建查询条件BoolQueryBuilder defaultQueryBuilder = QueryBuilders.boolQuery();// 距离搜索条件if (distance != null && lat != null && lon != null) {defaultQueryBuilder.filter(QueryBuilders.geoDistanceQuery("location").distance(distance, DistanceUnit.METERS).point(lat, lon));}// 过滤年龄条件if (ageGte != null && ageLt != null) {defaultQueryBuilder.filter(QueryBuilders.rangeQuery("age").gte(ageGte).lt(ageLt));}// 分页条件PageRequest pageRequest = PageRequest.of(0, 10);// 地理位置排序GeoDistanceSortBuilder sortBuilder = SortBuilders.geoDistanceSort("location", lat, lon);//组装条件NativeSearchQuery searchQuery = new NativeSearchQueryBuilder().withQuery(defaultQueryBuilder).withPageable(pageRequest).withSort(sortBuilder).build();SearchHits<ESUser> searchHits = elasticsearchRestTemplate.search(searchQuery, ESUser.class);List<PeopleNearByVo> peopleNearByVos = Lists.newArrayList();for (SearchHit<ESUser> searchHit : searchHits) {ESUser content = searchHit.getContent();ESUserVo esUserVo = new ESUserVo();BeanUtils.copyProperties(content, esUserVo);PeopleNearByVo peopleNearByVo = new PeopleNearByVo();peopleNearByVo.setEsUserVo(esUserVo);peopleNearByVo.setDistance((Double) searchHit.getSortValues().get(0));peopleNearByVos.add(peopleNearByVo);}// 组装分页对象Page<PeopleNearByVo> peopleNearByVoPage = new PageImpl<>(peopleNearByVos, pageRequest, searchHits.getTotalHits());return peopleNearByVoPage;}

controller层

 @RequestMapping(value = "/query-doc/nearBy", method = RequestMethod.POST)@ApiOperation("根据坐标点搜索附近的人")public Page<PeopleNearByVo> queryNearBy(@RequestBody ESUserLocationSearch locationSearch) {return esUserService.queryNearBy(locationSearch);}

4. swagger测试

下图的搜索条件为, 以北纬30.30,东经120.24为坐标点,搜索附近100米内 ,年龄大于等18岁, 小于25岁的人

结果为展示:

找到了2个, 排序按照距离排序, 年龄区间正确, 第一个兄弟距离39米, 第二个仁兄距离85米, 结果正确

{"content": [{"esUserVo": {"id": 601,"name": "季福林","age": 22,"tags": ["可爱","稳重","React"],"highLightTags": null,"desc": "天空守卫,擅长编程,睡觉","highLightDesc": null,"location": {"lat": 30.300214,"lon": 120.240329,"geohash": "wtms25urd9r8","fragment": true}},"distance": 39.53764107382481},{"esUserVo": {"id": 338,"name": "逄军","age": 20,"tags": ["可爱","稳重","React"],"highLightTags": null,"desc": "天空守卫,擅长编程,睡觉","highLightDesc": null,"location": {"lat": 30.300242,"lon": 120.240846,"geohash": "wtms25uxwy3p","fragment": true}},"distance": 85.56052789780142}],"pageable": {"sort": {"sorted": false,"unsorted": true,"empty": true},"offset": 0,"pageNumber": 0,"pageSize": 10,"paged": true,"unpaged": false},"last": true,"totalPages": 1,"totalElements": 2,"size": 10,"number": 0,"sort": {"sorted": false,"unsorted": true,"empty": true},"numberOfElements": 2,"first": true,"empty": false
}

至此, 结束

Elasticsearch(十三) ElasticSearch搜索附近的人相关推荐

  1. 【沽泡学院07】基于ElasticSearch搜索附近的人

    1. 为什么要选择ElasticSearch 1)ElasticSearch 优点: 分布式.实时的.Push replication 完全支持Apache Lucene的接近实时的搜索 处理多租户( ...

  2. elasticsearch的rest搜索--- 查询

    目录: 一. 针对这次装B 的解释 二.下载,安装插件elasticsearch-1.7.0   三.索引的mapping 四. 查询 五.对于相关度的大牛的文档 四. 查询 1. 查询的官网的文档 ...

  3. 畅购商城(五):Elasticsearch实现商品搜索

    好好学习,天天向上 本文已收录至我的Github仓库DayDayUP:github.com/RobodLee/DayDayUP,欢迎Star,更多文章请前往:目录导航 畅购商城(一):环境搭建 畅购商 ...

  4. ElasticSearch - 玩转搜索之花式查询

    文章目录 ElasticSearch - 玩转搜索之花式查询 1.term的多种查询 1.1 索引以及数据准备 1.2 term精准匹配查询 1.3 Exist非空值文档查询 1.4 Prefix前缀 ...

  5. 第7篇: 一张图让你读懂ElasticSearch强大的搜索能力

    背景:目前国内有大量的公司都在使用 Elasticsearch,包括阿里.京东.滴滴.今日头条.小米.vivo等诸多知名公司.除了搜索功能之外,Elasticsearch还结合Kibana.Logst ...

  6. Elasticsearch:如何使用 Elasticsearch 6.2 搜索中文、日文和韩文文本 - 第 1 部分: 分析工具

    共有 92 个国家和地区的代表队参加了去年的平昌冬奥会. 共有 49 个国家和地区的代表队参加了去年的平昌冬季残奥会. Elastic 的员工遍布全球 34 个国家/地区. 在很多国家/地区内,很多人 ...

  7. python-django框架中使用docker和elasticsearch配合实现搜索功能

    注意:系统环境为Ubuntu18 一.docker安装 0:如果之前有安装过docker使用以下命令卸载: sudo apt-get remove docker docker-engine docke ...

  8. 【Elasticsearch】如何使用 Elasticsearch 6.2 搜索中文、日语和韩语文本 - 第 3 部分:语言检测工具

    1.概述 翻译:https://www.elastic.co/cn/blog/how-to-search-ch-jp-kr-part-3 这是我有关中文.日语和韩语文本搜索的系列文章的第 3 部分.如 ...

  9. 【Elasticsearch】如何使用 Elasticsearch 6.2 搜索中文、日文和韩文文本 - 第 2 部分: 多字段

    1.概述 翻译:https://www.elastic.co/cn/blog/how-to-search-ch-jp-kr-part-2 如何使用 Elasticsearch 6.2 搜索中文.日文和 ...

最新文章

  1. 第五周周记(国庆第三天)
  2. Serverless 时代下大规模微服务应用运维的最佳实践
  3. 在Mac配置adb命令
  4. CTFshow php特性 web142
  5. 从更底层研究C\C++动态内存分配
  6. 撒花!李宏毅机器学习 2021 版正式开放上线
  7. 资源不在java项目和构建路径上_编译单元不在Java项目的构建路径上-Maven
  8. 5G精华问答 | 除了速度,5G还能带来什么?
  9. 数据分箱1——人工手动分箱
  10. .NET程序在运行中创建EXE文件的技术
  11. SQLi LABS Less 10 时间盲注
  12. css滑动星星评分,纯css3滑动星星打分动画特效
  13. dm9000网卡驱动分析
  14. 南京工程学院计算机博士,南京工程学院高层次人才引进工作实施办法(修订)...
  15. 业余草尚硅谷Java视频教程_SpringBoot视频教程免费下载
  16. chm文件打不开,已取消到该网页的导航
  17. layui分页样式改变
  18. 中国RoHS认证流程问题
  19. VB.NET邮件群发纯htlm二维码
  20. 如何用迅雷下载python_用Python调用迅雷批量下载excel表内的链接,并同时对文件重命名(使用的是openpyxl)...

热门文章

  1. box-shadow快速上手
  2. Java一阶段大概复习
  3. python微信推送消息_Python实现微信推送模板消息功能示例
  4. Android锁屏勒索病毒分析(2)免流服务器
  5. 02.从0到1,软件基础平台供应商技术团队建设例程
  6. 通过DuplicateHandle防止文件删除
  7. 猎杀对决计算机丢失进不去,猎杀对决错误代码解决方法汇总 进不去游戏解决方法...
  8. 2006,难以忘怀的一年
  9. OpenCV-Python 图像平滑处理2:blur函数及滤波案例
  10. 编写一个分奇偶的c语言程序,程序设计入门——C语言 第3周编程练习 1 奇偶个数(5分)...