用Elasticsearch搜索匹配功能实现基于地理位置的查询

1.Redis,MongoDB,Elasticsearch实现地理位置查询比较

1.1 Redis:

优点:Redis提供了地理空间索引功能,可以通过Geo数据类型进行地理位置查询。这使得Redis在处理地理位置查询时非常高效。

缺点:
Redis的地理空间索引功能相对简单,只能支持二维平面坐标系(经纬度)的查询,对于三维坐标系或者不规则地理区域的查询支持不够好。
功能有限:Redis的地理位置查询功能相对简单,仅支持基本的距离计算、范围查询等操作,无法满足复杂的空间查询需求。
存储容量限制:由于Redis数据存储在内存中,其存储容量受限于物理内存大小,对于大规模地理位置数据,可能需要进行分片或其他优化策略。
扩展性受限:Redis对于数据的扩展能力有限,不如Elasticsearch那样容易水平扩展以适应规模的增长。

使用场景:适用于需要快速查询地理位置信息的场景,小型应用,并且对于快速插入和查询地理位置数据有较高的实时性要求,可以考虑使用Redis geo。

1.2 MongoDB :

优点:
灵活性好:MongoDB支持多种地理位置查询操作,包括点查询、范围查询和多边形查询等。
数据结构简单:MongoDB的文档型结构非常适合存储地理位置数据,容易理解和使用。
高可用性:MongoDB提供了复制集和分片等机制来确保数据的高可用性和扩展性。
然而,MongoDB + 2d索引实现地理位置查询也存在一些缺点:

性能相对较差:相比Elasticsearch,在处理大规模的地理位置查询时,MongoDB的性能可能会受到限制。
功能相对简单:MongoDB的地理位置查询功能较为基础,相比Elasticsearch可能缺乏某些高级查询功能。
不支持部分地理位置操作:例如,MongoDB不支持直接计算两个地理位置之间的距离。

1.3 Elasticsearch geo

使用Elasticsearch geo实现地理位置查询的优点:

高性能:Elasticsearch是一种搜索引擎,使用geo点的经纬度数据可以快速进行空间查询和过滤,具有较高的查询效率。
灵活性:Elasticsearch提供了丰富的地理位置查询功能,例如可以根据距离、范围及其他条件进行查询和排序。
可扩展性:Elasticsearch可以通过分片和副本来实现水平扩展,以应对大规模的地理位置数据查询需求。

使用Elasticsearch geo实现地理位置查询的缺点:
学习成本:学习和配置Elasticsearch需要花费一定的时间和精力。
依赖性:使用Elasticsearch需要安装和维护Elasticsearch服务,这可能增加系统依赖和部署复杂性。
数据存储限制:Elasticsearch适用于小到中等大小的数据集,对于大量地理位置数据,可能需要额外的硬件资源和优化工作。

Elasticsearch为用户提供了基于地理位置的搜索功能。它主要支持两种类型的地理查询:一种是地理点(geo_point),即经纬度查询,另一种是地理形状查询(geo_shape),即支持点、线、圆形和多边形查询等

2.Elasticsearch geo地理位置数据类型

在Elasticsearch中,存在两种地理位置数据类型:geo_point和geo_shape。

geo_point:这是最基本的地理位置类型,通常用于表示一个二维坐标点(经度和纬度)。可以计算落在某个矩形内的点、以某个点为半径(圆)的点、某个多边形内的点等。此外,geo_point还可以用于排序、聚合等操作。
geo_shape:这种数据类型表示一个复杂的图形,使用的是GeoJSON的格式。它可以表达一块地理区域,区域的形状可以是任意多边形,也可以是点、线、面、多点、多线、多面等几何类型。然而,这种数据类型不能进行排序操作。

2.基于geo_point类型实现查询加油站案例

elasticsearch 版本7.12.1

2.1 springboot集成elasticsearch

       <dependency><groupId>org.springframework.boot</groupId><artifactId>spring-boot-starter-data-elasticsearch</artifactId><version>2.5.11</version></dependency><dependency><groupId>org.locationtech.jts</groupId><artifactId>jts-core</artifactId><version>1.18.1</version></dependency><dependency><groupId>org.locationtech.spatial4j</groupId><artifactId>spatial4j</artifactId><version>0.8</version></dependency>

配置文件

# es 服务地址
elasticsearch.host=127.0.0.1
# es 服务端口
elasticsearch.port=9200
# 配置日志级别
logging.level.org.springframework.data.elasticsearch.core=debug
logging.level.org.springframework.data.elasticsearch.client.WIRE=trace

配置类

@ConfigurationProperties(prefix = "elasticsearch")
@Configuration
@Data
public class ElasticsearchConfig extends AbstractElasticsearchConfiguration {private String host ;private Integer port ;//重写父类方法@Overridepublic RestHighLevelClient elasticsearchClient() {RestClientBuilder builder = RestClient.builder(new HttpHost(host, port));RestHighLevelClient restHighLevelClient = newRestHighLevelClient(builder);return restHighLevelClient;}
}

测试实体类

@ApiModel
@Data
@Builder
@AllArgsConstructor
@NoArgsConstructor
@ToString
@Document(indexName = "stationcc", shards = 3, replicas = 1)
public class ChargingStationDTO {//必须有 id,这里的 id 是全局唯一的标识,等同于 es 中的"_id"@Id@ApiModelProperty(value = "id", example = "111111111111")private Long baseId;/*** type : 字段数据类型* analyzer : 分词器类型* index : 是否索引(默认:true)* Keyword : 短语,不进行分词*/@ApiModelProperty(value = "加油站ID", example = "111111111111")@Field(type = FieldType.Keyword)private String stationId;@ApiModelProperty(value = "运营商ID", example = "395815801")@Field(type = FieldType.Keyword)private String operatorId;@ApiModelProperty(value = "加油站名称", example = "测试加油站")@Field(type = FieldType.Keyword)private String stationName;@ApiModelProperty(value = "运营商名称", example = "测试")@Field(type = FieldType.Keyword)private String operatorName;@GeoPointField@ApiModelProperty(value = "经纬度")private GeoPoint location; @Field(type = FieldType.Keyword)@ApiModelProperty(value = "详细地址", example = "山东路154号")private String address;@ApiModelProperty(value = "距离", example = "1.0")private  double distance;
}

初始化数据

 @Testpublic void saveAll() {//起点 111.000,31.000//终点 121.000,31.000//( 121 , 31 )    -    ( 111 , 31 )    之间的距离为    952.8062737420901 km//96-121,23-40List<ChargingStationDTO> chargingStationDTOList = new ArrayList<>();List<String> stringList = CollUtil.newArrayList("招式", "王五", "基于", "好好", "电动", "反复", "第三十", "十三点", "但是");for (int i = 2000; i < 450000; i++) {ChargingStationDTO chargingStationDTO = new ChargingStationDTO();chargingStationDTO.setBaseId(Long.valueOf(i));chargingStationDTO.setStationId(Long.valueOf(i).toString());chargingStationDTO.setOperatorId(Long.valueOf(i).toString());chargingStationDTO.setStationName(RandomUtil.randomEleList(stringList, 1).get(0));chargingStationDTO.setAddress("地址" + i);//经度范围是0-180°,纬度范围是0-90°//纬度double lat = RandomUtil.randomDouble(23.000, 40.000, 3, RoundingMode.DOWN);//经度double lon = RandomUtil.randomDouble(96.000, 121.000, 3, RoundingMode.DOWN);chargingStationDTO.setLocation(new GeoPoint(lat, lon));chargingStationDTOList.add(chargingStationDTO);if (chargingStationDTOList.size() == 1000) {chargingStationDao.saveAll(chargingStationDTOList);chargingStationDTOList.clear();System.out.println("插入1000,i"+i);}}}

2.2 查询附近加油站(圆形查询)

请求参数

@ApiModel
@Data
@Builder
@AllArgsConstructor
@NoArgsConstructor
public class ChargingStationNearbySearchDTO {@ApiModelProperty(value = "id", example = "1111111111")private Long baseId;@ApiModelProperty(value = "加油站名称", example = "测试加油站")private String stationName;@ApiModelProperty(value = "经度")@NotNull(message = "经度不能为空")private Double lon;@ApiModelProperty(value = "纬度")@NotNull(message = "纬度不能为空")private Double lat;@ApiModelProperty(value = "查找半径")private int radius;@ApiModelProperty(value = "page", example = "1")private Integer page;@ApiModelProperty(value = "pageSize", example = "100")private Integer pageSize;
}
 @PostMapping("/nearby")@ApiOperation(value = "查询附近加油站")public Response<ChargingStationVO> nearbySearch(@RequestBody @Valid @Validated ChargingStationNearbySearchDTO searchDTO) {String fieldName = "location";// NativeSearchQuery实现了SearchQuery接口NativeSearchQueryBuilder nativeSearchQueryBuilder = new NativeSearchQueryBuilder();// 分页PageRequest pageRequest = PageRequest.of(searchDTO.getPage() - 1, searchDTO.getPageSize());nativeSearchQueryBuilder.withPageable(pageRequest);// 定义bool查询BoolQueryBuilder boolQueryBuilder = new BoolQueryBuilder();//https://blog.csdn.net/icanlove/article/details/126425788?spm=1001.2101.3001.6661.1&utm_medium=distribute.pc_relevant_t0.none-task-blog-2%7Edefault%7ECTRLIST%7ERate-1-126425788-blog-120678401.235%5Ev38%5Epc_relevant_default_base&depth_1-utm_source=distribute.pc_relevant_t0.none-task-blog-2%7Edefault%7ECTRLIST%7ERate-1-126425788-blog-120678401.235%5Ev38%5Epc_relevant_default_base&utm_relevant_index=1//使用 minimum_should_match 选项,至少匹配一项should子句。if (StringUtils.isNotBlank(searchDTO.getStationName()) || ObjectUtils.isNotEmpty(searchDTO.getBaseId())) {if (StringUtils.isNotBlank(searchDTO.getStationName())) {// //左右模糊查询,其中fuzziness的参数作用是在查询时,es动态的将查询关键词前后增加或者删除一个词,然后进行匹配QueryBuilder queryBuilder = QueryBuilders.fuzzyQuery("stationName", searchDTO.getStationName()).fuzziness(Fuzziness.ONE);boolQueryBuilder.must(queryBuilder);}if (ObjectUtils.isNotEmpty(searchDTO.getBaseId())) {// //关键字不支持分词QueryBuilder queryBuilder = QueryBuilders.termQuery("baseId", searchDTO.getBaseId());boolQueryBuilder.must(queryBuilder);}}// geo查询,定义中心点,指定查询范围GeoDistanceQueryBuilder geoDistanceQueryBuilder = new GeoDistanceQueryBuilder(fieldName);geoDistanceQueryBuilder.point(searchDTO.getLat(), searchDTO.getLon());geoDistanceQueryBuilder.distance(searchDTO.getRadius(), DistanceUnit.METERS);boolQueryBuilder.must(geoDistanceQueryBuilder);//     外部 bool 过滤器BoolQueryBuilder queryBuilder = new BoolQueryBuilder();queryBuilder.filter(boolQueryBuilder);nativeSearchQueryBuilder.withQuery(queryBuilder);// 按照距离升序GeoDistanceSortBuilder geoDistanceSortBuilder = new GeoDistanceSortBuilder(fieldName, searchDTO.getLat(), searchDTO.getLon());geoDistanceSortBuilder.unit(DistanceUnit.METERS); //距离单位geoDistanceSortBuilder.order(SortOrder.ASC); //升序nativeSearchQueryBuilder.withSort(geoDistanceSortBuilder);NativeSearchQuery nativeSearchQuery = nativeSearchQueryBuilder.build();DslLogUtil.log(elasticsearchOperations, nativeSearchQuery);SearchHits<ChargingStationDTO> searchHits = elasticsearchOperations.search(nativeSearchQuery, ChargingStationDTO.class);log.info("响应数据:{}", LogUtil.getLogJson(searchHits));List<ChargingStationDTO> chargingStationDTOList = null;if (CollectionUtil.isNotEmpty(searchHits.getSearchHits())) {chargingStationDTOList = searchHits.getSearchHits().stream().map(o -> {// 计算两点距离//关于GeoDistance.ARC和GeoDistance.PLANE,前者比后者计算起来要慢,但精确度要比后者高,具体区别可以看。double distance = GeoDistance.ARC.calculate(o.getContent().getLocation().getLat(), o.getContent().getLocation().getLon(), searchDTO.getLat(), searchDTO.getLon(), DistanceUnit.KILOMETERS);ChargingStationDTO chargingStationDTO = o.getContent();chargingStationDTO.setDistance(distance);return chargingStationDTO;}).collect(Collectors.toList());}int count = CollectionUtils.isEmpty(chargingStationDTOList) ? 0 : chargingStationDTOList.size();return Response.success(ChargingStationVO.builder().positions(chargingStationDTOList).count(count).build());}

2.3 查询附近加油站( geo_bounding_box 矩形查询)

geo_bounding_box语法又称为地理坐标盒模型,在当前语法中,只需选择一个矩阵范围(输入矩阵的左上角的顶点地理坐标和矩阵的右上角的顶点地理坐标,构建成为一个矩阵),即可计算出当前矩阵中符合条件的元素;
在这里插入图片描述

/*** 给定两个坐标,通过这两个坐标形成对角线,* 平行于地球经纬度从而得到的一个矩阵。* 采用geo_bounding_box语法可以得到坐落于当前矩阵中的元素的信息;** @param searchDTO* @return*/@PostMapping("/box/query")@ApiOperation(value = "矩形查询附近加油站")public Response<ChargingStationVO> boxQuery(@RequestBody @Valid @Validated ChargingStationSearchDTO searchDTO) {// NativeSearchQuery实现了SearchQuery接口NativeSearchQueryBuilder nativeSearchQueryBuilder = new NativeSearchQueryBuilder();// 分页PageRequest pageRequest = PageRequest.of(searchDTO.getPage() - 1, searchDTO.getPageSize());nativeSearchQueryBuilder.withPageable(pageRequest);// 定义bool查询BoolQueryBuilder boolQueryBuilder = new BoolQueryBuilder();//https://blog.csdn.net/icanlove/article/details/126425788?spm=1001.2101.3001.6661.1&utm_medium=distribute.pc_relevant_t0.none-task-blog-2%7Edefault%7ECTRLIST%7ERate-1-126425788-blog-120678401.235%5Ev38%5Epc_relevant_default_base&depth_1-utm_source=distribute.pc_relevant_t0.none-task-blog-2%7Edefault%7ECTRLIST%7ERate-1-126425788-blog-120678401.235%5Ev38%5Epc_relevant_default_base&utm_relevant_index=1//使用 minimum_should_match 选项,至少匹配一项should子句。if (StringUtils.isNotBlank(searchDTO.getStationName()) || ObjectUtils.isNotEmpty(searchDTO.getBaseId())) {if (StringUtils.isNotBlank(searchDTO.getStationName())) {// //左右模糊查询,其中fuzziness的参数作用是在查询时,es动态的将查询关键词前后增加或者删除一个词,然后进行匹配QueryBuilder queryBuilder = QueryBuilders.fuzzyQuery("stationName", searchDTO.getStationName()).fuzziness(Fuzziness.ONE);boolQueryBuilder.must(queryBuilder);}if (ObjectUtils.isNotEmpty(searchDTO.getBaseId())) {// //关键字不支持分词QueryBuilder queryBuilder = QueryBuilders.termQuery("baseId", searchDTO.getBaseId());boolQueryBuilder.must(queryBuilder);}}//给定两个坐标,通过这两个坐标形成对角线,// 平行于地球经纬度从而得到的一个矩阵。// 采用geo_bounding_box语法可以得到坐落于当前矩阵中的元素的信息;// 构造左上点坐标GeoPoint topLeft = new GeoPoint(searchDTO.getPositions().get(0).getLat(), searchDTO.getPositions().get(0).getLon());// 构造右下点坐标GeoPoint bottomRight = new GeoPoint(searchDTO.getPositions().get(1).getLat(), searchDTO.getPositions().get(1).getLon());GeoBoundingBoxQueryBuilder geoBoundingBoxQueryBuilder = new GeoBoundingBoxQueryBuilder("location").setCorners(topLeft, bottomRight);boolQueryBuilder.must(geoBoundingBoxQueryBuilder);//     外部 bool 过滤器BoolQueryBuilder queryBuilder = new BoolQueryBuilder();queryBuilder.filter(boolQueryBuilder);nativeSearchQueryBuilder.withQuery(queryBuilder);NativeSearchQuery nativeSearchQuery = nativeSearchQueryBuilder.build();DslLogUtil.log(elasticsearchOperations, nativeSearchQuery);SearchHits<ChargingStationDTO> searchHits = elasticsearchOperations.search(nativeSearchQuery, ChargingStationDTO.class);log.info("响应数据:{}", LogUtil.getLogJson(searchHits));List<ChargingStationDTO> chargingStationDTOList = null;if (CollectionUtil.isNotEmpty(searchHits.getSearchHits())) {chargingStationDTOList = searchHits.getSearchHits().stream().map(SearchHit::getContent).collect(Collectors.toList());}int count = CollectionUtils.isEmpty(chargingStationDTOList) ? 0 : chargingStationDTOList.size();return Response.success(ChargingStationVO.builder().positions(chargingStationDTOList).count(count).build());}

DSL
请求体:

{"from": 0,"size": 100,"query": {"bool": {"filter": [{"bool": {"must": [{"fuzzy": {"stationName": {"value": "第三十","fuzziness": "1","prefix_length": 0,"max_expansions": 50,"transpositions": true,"boost": 1.0}}}, {"geo_bounding_box": {"location": {"top_left": [120.91224, 30.84623],"bottom_right": [120.93743, 30.8245]},"validation_method": "STRICT","type": "MEMORY","ignore_unmapped": false,"boost": 1.0}}],"adjust_pure_negative": true,"boost": 1.0}}],"adjust_pure_negative": true,"boost": 1.0}},"version": true,"explain": false
}

响应体:

{"code": 200,"message": "成功","data": {"count": 2,"positions": [{"baseId": 431843,"stationId": "431843","operatorId": "431843","stationName": "好好","operatorName": null,"location": {"lat": 30.833,"lon": 120.934,"geohash": "wtmzruvrnry1","fragment": true},"address": "地址431843","distance": 0},{"baseId": 114960,"stationId": "114960","operatorId": "114960","stationName": "第三十","operatorName": null,"location": {"lat": 30.84,"lon": 120.919,"geohash": "wtmzrw680btm","fragment": true},"address": "地址114960","distance": 0}]},"extraData": {}
}

2.4 多边形查询附近加油站(geo-polygon-多边形查询)

ES的geo_polygon语法,可以通过指定多个坐标点,从而构成一个多边形,然后从当前多边形中召回坐落其中的元素进行召回;在当前语法中,最少需要3个坐标,从而构成一个多边形;
在这里插入图片描述

  @PostMapping("/polygon/query")@ApiOperation(value = "多边形查询附近加油站")public Response<ChargingStationVO> polygonQuery(@RequestBody @Valid @Validated ChargingStationSearchDTO searchDTO) throws IOException {// NativeSearchQuery实现了SearchQuery接口NativeSearchQueryBuilder nativeSearchQueryBuilder = new NativeSearchQueryBuilder();// 分页PageRequest pageRequest = PageRequest.of(searchDTO.getPage() - 1, searchDTO.getPageSize());nativeSearchQueryBuilder.withPageable(pageRequest);// 定义bool查询BoolQueryBuilder boolQueryBuilder = new BoolQueryBuilder();//https://blog.csdn.net/icanlove/article/details/126425788?spm=1001.2101.3001.6661.1&utm_medium=distribute.pc_relevant_t0.none-task-blog-2%7Edefault%7ECTRLIST%7ERate-1-126425788-blog-120678401.235%5Ev38%5Epc_relevant_default_base&depth_1-utm_source=distribute.pc_relevant_t0.none-task-blog-2%7Edefault%7ECTRLIST%7ERate-1-126425788-blog-120678401.235%5Ev38%5Epc_relevant_default_base&utm_relevant_index=1//使用 minimum_should_match 选项,至少匹配一项should子句。if (StringUtils.isNotBlank(searchDTO.getStationName()) || ObjectUtils.isNotEmpty(searchDTO.getBaseId())) {if (StringUtils.isNotBlank(searchDTO.getStationName())) {// //左右模糊查询,其中fuzziness的参数作用是在查询时,es动态的将查询关键词前后增加或者删除一个词,然后进行匹配QueryBuilder queryBuilder = QueryBuilders.fuzzyQuery("stationName", searchDTO.getStationName()).fuzziness(Fuzziness.ONE);boolQueryBuilder.must(queryBuilder);}if (ObjectUtils.isNotEmpty(searchDTO.getBaseId())) {// //关键字不支持分词QueryBuilder queryBuilder = QueryBuilders.termQuery("baseId", searchDTO.getBaseId());boolQueryBuilder.must(queryBuilder);}}//可以通过指定多个坐标点,从而构成一个多边形,//然后从当前多边形中召回坐落其中的元素进行召回;//在当前语法中,最少需要3个坐标,从而构成一个多边形;// 创建多边形几何对象CoordinatesBuilder coordinatesBuilder = new CoordinatesBuilder();for (GpsListDTO gpsListDTO : searchDTO.getPositions()) {coordinatesBuilder.coordinate(gpsListDTO.getLon(), gpsListDTO.getLat());}PolygonBuilder pb = new PolygonBuilder(coordinatesBuilder);GeoShapeQueryBuilder geoShapeQueryBuilder = QueryBuilders.geoShapeQuery("location", pb.buildGeometry());// intersects - 查询的形状与索引的形状有重叠(默认), 即图形有交集则匹配。//disjoint - 查询的形状与索引的形状完全不重叠。//within - 查询的形状包含索引的形状。//CONTAINS将返回其geo_shape字段包含查询中指定的几何形状的所有文档。//within与CONTAINS的区别// 它们是反比关系:A包含B,B在A内.// `A`是查询中的形状,而`B`是文档中的形状。//`WITHIN`表示`A包含B`   A.contains(B) True// `CONTAINS`表示`B包含A`  B.within(A)  TruegeoShapeQueryBuilder.relation(ShapeRelation.INTERSECTS);boolQueryBuilder.must(geoShapeQueryBuilder);//     外部 bool 过滤器BoolQueryBuilder queryBuilder = new BoolQueryBuilder();queryBuilder.filter(boolQueryBuilder);nativeSearchQueryBuilder.withQuery(queryBuilder);NativeSearchQuery nativeSearchQuery = nativeSearchQueryBuilder.build();DslLogUtil.log(elasticsearchOperations, nativeSearchQuery);SearchHits<ChargingStationDTO> searchHits = elasticsearchOperations.search(nativeSearchQuery, ChargingStationDTO.class);log.info("响应数据:{}", LogUtil.getLogJson(searchHits));List<ChargingStationDTO> chargingStationDTOList = null;if (CollectionUtil.isNotEmpty(searchHits.getSearchHits())) {chargingStationDTOList = searchHits.getSearchHits().stream().map(SearchHit::getContent).collect(Collectors.toList());}int count = CollectionUtils.isEmpty(chargingStationDTOList) ? 0 : chargingStationDTOList.size();return Response.success(ChargingStationVO.builder().positions(chargingStationDTOList).count(count).build());}

DSL
请求体:

{"from": 0,"size": 100,"query": {"bool": {"filter": [{"bool": {"must": [{"fuzzy": {"stationName": {"value": "好好","fuzziness": "1","prefix_length": 0,"max_expansions": 50,"transpositions": true,"boost": 1.0}}}, {"geo_shape": {"location": {"shape": {"type": "Polygon","coordinates": [[[120.92696, 30.83932],[120.91964, 30.82868],[120.95907, 30.81838],[120.96842, 30.83525],[120.94369, 30.84345],[120.92696, 30.83932]]]},"relation": "intersects"},"ignore_unmapped": false,"boost": 1.0}}],"adjust_pure_negative": true,"boost": 1.0}}],"adjust_pure_negative": true,"boost": 1.0}},"version": true,"explain": false
}

响应体:

{"empty": false,"maxScore": 0.0,"searchHits": [{"content": {"address": "地址431843","baseId": 431843,"distance": 0.0,"location": {"fragment": true,"geohash": "wtmzruvrnry1","lat": 30.833,"lon": 120.934},"operatorId": "431843","stationId": "431843","stationName": "好好"},"highlightFields": {},"id": "431843","index": "stationcc","innerHits": {},"matchedQueries": [],"score": 0.0,"sortValues": []}],"totalHits": 1,"totalHitsRelation": "EQUAL_TO"
}

2.5 查询沿途加油站(一次查询多个圆点)

   @PostMapping("/route")@ApiOperation(value = "查询沿途加油站")public Response<ChargingStationVO> routeSearch(@RequestBody @Valid @Validated ChargingStationSearchDTO searchDTO) {String fieldName = "location";// NativeSearchQuery实现了SearchQuery接口NativeSearchQueryBuilder nativeSearchQueryBuilder = new NativeSearchQueryBuilder();// 分页PageRequest pageRequest = PageRequest.of(searchDTO.getPage() - 1, searchDTO.getPageSize());nativeSearchQueryBuilder.withPageable(pageRequest);// 定义bool查询BoolQueryBuilder boolQueryBuilder = new BoolQueryBuilder();//https://blog.csdn.net/icanlove/article/details/126425788?spm=1001.2101.3001.6661.1&utm_medium=distribute.pc_relevant_t0.none-task-blog-2%7Edefault%7ECTRLIST%7ERate-1-126425788-blog-120678401.235%5Ev38%5Epc_relevant_default_base&depth_1-utm_source=distribute.pc_relevant_t0.none-task-blog-2%7Edefault%7ECTRLIST%7ERate-1-126425788-blog-120678401.235%5Ev38%5Epc_relevant_default_base&utm_relevant_index=1//使用 minimum_should_match 选项,至少匹配一项should子句。boolQueryBuilder.minimumShouldMatch(1);if (StringUtils.isNotBlank(searchDTO.getStationName()) || ObjectUtils.isNotEmpty(searchDTO.getBaseId())) {if (StringUtils.isNotBlank(searchDTO.getStationName())) {// //左右模糊查询,其中fuzziness的参数作用是在查询时,es动态的将查询关键词前后增加或者删除一个词,然后进行匹配QueryBuilder queryBuilder = QueryBuilders.fuzzyQuery("stationName", searchDTO.getStationName()).fuzziness(Fuzziness.ONE);boolQueryBuilder.must(queryBuilder);}if (ObjectUtils.isNotEmpty(searchDTO.getBaseId())) {// //关键字不支持分词QueryBuilder queryBuilder = QueryBuilders.termQuery("baseId", searchDTO.getBaseId());boolQueryBuilder.must(queryBuilder);}}if (CollectionUtil.isNotEmpty(searchDTO.getPositions())) {boolQueryBuilder.minimumShouldMatch(1);for (GpsListDTO position : searchDTO.getPositions()) {// geo查询,定义中心点,指定查询范围GeoDistanceQueryBuilder geoDistanceQueryBuilder = new GeoDistanceQueryBuilder(fieldName);geoDistanceQueryBuilder.point(position.getLat(), position.getLon());geoDistanceQueryBuilder.distance(searchDTO.getRadius(), DistanceUnit.METERS);boolQueryBuilder.should(geoDistanceQueryBuilder);}}//     外部 bool 过滤器
//        Elasticsearch 查询条件和过滤条件的区别?
//        Elasticsearch中的查询条件和过滤条件都是用于搜索和过滤文档的条件,但它们之间有一些区别。
//        查询条件是用于计算文档相关度得分的条件,它会将所有符合条件的文档按照相关度得分从高到低排序,并返回前N个文档。查询条件可以使用各种类型的查询,如match、term、range、bool等。查询条件会计算每个文档的相关度得分,因此查询条件可以用于搜索和排序。
//        过滤条件是用于过滤文档的条件,它会将所有符合条件的文档返回,但不会计算相关度得分。过滤条件可以使用各种类型的过滤器,如term、range、bool、geo_distance等。过滤条件不会计算相关度得分,因此过滤条件可以用于过滤和聚合。
//        查询条件和过滤条件的区别在于,查询条件会计算每个文档的相关度得分,而过滤条件不会计算得分。因此,如果只需要过滤文档而不需要计算得分,应该使用过滤条件。另外,过滤条件可以缓存结果,提高查询性能,而查询条件不能缓存结果。
//        需要注意的是,查询条件和过滤条件都可以使用bool查询和bool过滤器来组合多个条件。bool查询和bool过滤器都是用于组合多个查询或过滤器的逻辑运算符,可以使用must、should、must_not三个子句来组合多个查询或过滤器。BoolQueryBuilder queryBuilder = new BoolQueryBuilder();queryBuilder.filter(boolQueryBuilder);nativeSearchQueryBuilder.withQuery(queryBuilder);NativeSearchQuery nativeSearchQuery = nativeSearchQueryBuilder.build();DslLogUtil.log(elasticsearchOperations, nativeSearchQuery);SearchHits<ChargingStationDTO> searchHits = elasticsearchOperations.search(nativeSearchQuery, ChargingStationDTO.class);log.info("响应数据:{}", LogUtil.getLogJson(searchHits));List<ChargingStationDTO> chargingStationDTOList = null;if (CollectionUtil.isNotEmpty(searchHits.getSearchHits())) {chargingStationDTOList = searchHits.getSearchHits().stream().map(SearchHit::getContent).collect(Collectors.toList());}int count = CollectionUtils.isEmpty(chargingStationDTOList) ? 0 : chargingStationDTOList.size();return Response.success(ChargingStationVO.builder().positions(chargingStationDTOList).count(count).build());}

请求DSL语句:

{"from": 0,"size": 10000,"query": {"bool": {"filter": [{"bool": {"must": [{"fuzzy": {"stationName": {"value": "王五","fuzziness": "1","prefix_length": 0,"max_expansions": 50,"transpositions": true,"boost": 1.0}}}],"should": [{"geo_distance": {"location": [114.7, 31.0],"distance": 10000.0,"distance_type": "arc","validation_method": "STRICT","ignore_unmapped": false,"boost": 1.0}}, {"geo_distance": {"location": [116.935, 31.0],"distance": 10000.0,"distance_type": "arc","validation_method": "STRICT","ignore_unmapped": false,"boost": 1.0}}, {"geo_distance": {"location": [117.261, 31.0],"distance": 10000.0,"distance_type": "arc","validation_method": "STRICT","ignore_unmapped": false,"boost": 1.0}}, {"geo_distance": {"location": [116.569, 31.0],"distance": 10000.0,"distance_type": "arc","validation_method": "STRICT","ignore_unmapped": false,"boost": 1.0}}, {"geo_distance": {"location": [117.639, 31.0],"distance": 10000.0,"distance_type": "arc","validation_method": "STRICT","ignore_unmapped": false,"boost": 1.0}}, {"geo_distance": {"location": [119.236, 31.0],"distance": 10000.0,"distance_type": "arc","validation_method": "STRICT","ignore_unmapped": false,"boost": 1.0}}],"adjust_pure_negative": true,"minimum_should_match": "1","boost": 1.0}}],"adjust_pure_negative": true,"boost": 1.0}},"version": true,"explain": false
}

响应数据:

{"empty": false,"maxScore": 0.0,"searchHits": [{"content": {"address": "地址4031","baseId": 4031,"distance": 0.0,"location": {"fragment": true,"geohash": "wtkzbygzuwxz","lat": 30.932,"lon": 119.218},"operatorId": "4031","stationId": "4031","stationName": "王五"},"highlightFields": {},"id": "4031","index": "stationcc","innerHits": {},"matchedQueries": [],"score": 0.0,"sortValues": []}, {"content": {"address": "地址26708","baseId": 26708,"distance": 0.0,"location": {"fragment": true,"geohash": "wte2df6z32vx","lat": 31.039,"lon": 117.195},"operatorId": "26708","stationId": "26708","stationName": "王五"},"highlightFields": {},"id": "26708","index": "stationcc","innerHits": {},"matchedQueries": [],"score": 0.0,"sortValues": []}, {"content": {"address": "地址156487","baseId": 156487,"distance": 0.0,"location": {"fragment": true,"geohash": "wt988d3zmbcx","lat": 31.039,"lon": 114.634},"operatorId": "156487","stationId": "156487","stationName": "王五"},"highlightFields": {},"id": "156487","index": "stationcc","innerHits": {},"matchedQueries": [],"score": 0.0,"sortValues": []}, {"content": {"address": "地址131631","baseId": 131631,"distance": 0.0,"location": {"fragment": true,"geohash": "wtdb78u6echc","lat": 30.986,"lon": 116.527},"operatorId": "131631","stationId": "131631","stationName": "王五"},"highlightFields": {},"id": "131631","index": "stationcc","innerHits": {},"matchedQueries": [],"score": 0.0,"sortValues": []}, {"content": {"address": "地址265815","baseId": 265815,"distance": 0.0,"location": {"fragment": true,"geohash": "wte8ks47qs3x","lat": 31.004,"lon": 117.623},"operatorId": "265815","stationId": "265815","stationName": "王五"},"highlightFields": {},"id": "265815","index": "stationcc","innerHits": {},"matchedQueries": [],"score": 0.0,"sortValues": []}],"totalHits": 16,"totalHitsRelation": "EQUAL_TO"
}

打印完整DSL语句工具类

@Slf4j
public class DslLogUtil {public static void log(ElasticsearchOperations elasticsearchOperations, NativeSearchQuery nativeSearchQuery) {if (elasticsearchOperations instanceof ElasticsearchRestTemplate) {try {ElasticsearchRestTemplate elasticsearchRestTemplate = (ElasticsearchRestTemplate) elasticsearchOperations;Method searchRequest = ReflectionUtils.findMethod(Class.forName("org.springframework.data.elasticsearch.core.RequestFactory"), "searchRequest", Query.class, Class.class, IndexCoordinates.class);searchRequest.setAccessible(true);Object o = ReflectionUtils.invokeMethod(searchRequest, elasticsearchRestTemplate.getRequestFactory(), nativeSearchQuery, ChargingStationDTO.class, elasticsearchRestTemplate.getIndexCoordinatesFor(ChargingStationDTO.class));Field source =ReflectionUtils.findField(Class.forName("org.elasticsearch.action.search.SearchRequest"), "source");source.setAccessible(true);Object s = ReflectionUtils.getField(source, o);log.info("请求DSL语句:{}", s);} catch (ClassNotFoundException e) {e.printStackTrace();}}}}

参考:
https://www.kancloud.cn/yiyanan/elasticsearch_7_6/1670492

https://www.kancloud.cn/apachecn/elasticsearch-doc-zh/1945207

https://learnku.com/docs/elasticsearch73/7.3/5210-geo-distance-aggregation/8043

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mzph.cn/news/177380.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

融云筑基,移动云加速构建高性能智能算力底座

自2022年11月以来&#xff0c;全球大模型数量迅速增加&#xff0c;以ChatGPT为代表的大模型已经成为世界数字科技领域新热点。大模型带来的算力需求迅速增长&#xff0c;未来智算场景将会有非常大的突破空间。 在“十四五”规划的指引下&#xff0c;各地政府积极投入智算中心建…

基于vue框架的美团类药品点单系统

基于VUE框架的美团类药品点单管理系统 摘要&#xff1a; 2019年12月以来&#xff0c;中国湖北省武汉市爆发新型冠状病毒引发的肺炎疫情&#xff0c;并通过人传人的感染方式快速向全国其他地区扩散。全国上下万众一心抗击病毒&#xff0c;湖北广东浙江等24省市启动重大卫生突发…

运动耳机哪个好?跑步耳机哪个好?蓝牙运动耳机十大名牌排行榜

​相信很多人都喜欢在运动的过程中佩戴着耳机一边锻炼一边听音乐享受过程。在选择运动耳机的时候一定要重点去关注以下几点&#xff0c;佩戴时要绝对稳固舒适、音质表现不差、防水防尘效果要好等&#xff0c;这样就不会容易损伤耳朵。很多朋友还不知道运动耳机该怎么选&#xf…

idea创建spring boot项目,java版本只能选择17和21

1.问题描述 java版本为"11.0.20"&#xff0c;idea2023创建spring boot项目时&#xff08;File->Project->Spring Initializr&#xff09;&#xff0c;java版本无法选择11&#xff0c;导致报错&#xff0c;如下图所示&#xff1a; 2.原因 spring2.X版本在2023…

无效的目标发行版: 21 和springboot爆错

目录 问题描述 原因分析&#xff1a; 问题描述 springboot爆红 调整一下这个&#xff1a;把这里的version调低一点应该就可以了 无效的目标发行版: 21 调整一下这个把这里的Java version调整一下&#xff0c;我是调整到1.8&#xff08;其他没有试过&#xff09; 原因分析&a…

GPT还远远不是真正的智能

GPT是一个基于深度学习的自然语言处理模型&#xff0c;它可以生成逼真的文本。虽然GPT在生成文本方面取得了显著的进展&#xff0c;但它并不具备真正的智能。GPT是通过训练模型来学习语言模式&#xff0c;它不具备理解、推理、判断和主动学习的能力。它只是根据已有的语料库生成…

create-vue 生成式脚手架源码解析

文章目录 命令交互输出渐变标题解析命令行参数命令行交互国际化提示prompts 库实现命令行交互 生成模版创建项目输出文件夹生成 packge.json查找预设的模版文件根据路径生成模块文件render 生成模版填充 ejs 模版数据根据生成项目是 ts 还是 js 后置处理根据需要的模块生成所有…

【算法】七大经典排序(插入,选择,冒泡,希尔,堆,快速,归并)(含可视化算法动图,清晰易懂,零基础入门)

​ 目录 一、排序的概念及其运用1.1 排序的概念1.2 排序的应用1.3 常见的排序算法 二、常见排序算法的实现2.1 插入排序2.1.1 直接插入排序2.1.2 希尔排序2.1.3 直接插入排序和希尔排序的性能对比 2.2 选择排序2.2.1 直接选择排序2.2.2 堆排序2.2.3 直接选择排序和堆排序的性能…

Mysql锁实战详细分析

1.mysql回表查询 在这里提起主要是用于说明mysql数据和索引的结构&#xff0c;有助于理解后续加锁过程中的一些问题。 mysql索引结构和表数据结构是相互独立的&#xff0c;根据索引查询&#xff0c;只能找到索引列和主键聚簇索引。如果select语句中不包含索引列&#xff0c;m…

陪诊系统|沈阳陪诊系统定制|陪诊软件保障患者安全与便利

陪诊系统是一种以专业医疗服务为核心的综合性陪同体系。它涵盖了医院前线咨询、专业陪诊、医后关怀等多个环节&#xff0c;提供全方位的医疗咨询服务和专业的医疗陪同服务。通过陪诊系统&#xff0c;患者可以获得更加便捷、高效、安全的医疗服务体验。陪诊系统的出现&#xff0…

多类场景、遍布各地,融云 IM 支撑多款应用全球增长

&#xff08;全网都在找的《社交泛娱乐出海作战地图》&#xff0c;点击获取&#x1f446;&#xff09; 无论是面向企业场景的工作流协同还是消费场景的网络效应形成&#xff0c;商务社交还是陌生人社交&#xff0c;IM 都是必备组件。IM 遍布互联网各角落&#xff0c;出现在所有…

【c++|SDL】开始使用之---demo

every blog every motto: You can do more than you think. https://blog.csdn.net/weixin_39190382?typeblog 0. 前言 SDL 记录 1. hello word #include<SDL2/SDL.h>SDL_Window* g_pWindow 0; SDL_Renderer* g_pRenderer 0;int main(int argc, char* args[]) {//…

触控板窗口管理软件Swish mac中文版

Swish mac是一款触控板窗口管理工具&#xff0c;它允许用户通过简单的手势来控制窗口。Swish利用MacBook的触控板&#xff0c;使得用户可以更加便捷地管理窗口。它支持多种手势&#xff0c;例如捏合、拖动、放大和缩小等&#xff0c;使得用户可以轻松地实现窗口的切换、最小化、…

Milvus入门手册1.0

一、window环境搭建&#xff08;单机&#xff09; 1、docker安装 略 2、milvus安装 参考文档&#xff1a;https://milvus.io/docs/install_standalone-docker.md tips: &#xff08;1&#xff09;compose.yaml下载比较慢&#xff0c;可以在网络上找一份。 &#xff08;2&…

VScode集成python开发环境和基本插件下载配置

VSCode开发工具 下载VSCode VSCode官方首页&#xff1a;Visual Studio Code - Code Editing. Redefined 点击Download for Windows下载 安装过程一路下一步即可&#xff0c;其中建议勾选 将"通过Code打开"操作添加到Windows资源管理器目录上下文菜单方便我们直接通过…

虹科Pico汽车示波器 | 汽车免拆检修 | 2016款东风悦达起亚K5车发动机怠速抖动严重、加速无力

一、故障现象 一辆2016款东风悦达起亚K5车&#xff0c;搭载G4FJ发动机&#xff0c;累计行驶里程约为8.2万km。该车发动机怠速抖动严重、加速无力&#xff0c;同时发动机故障灯异常点亮&#xff0c;为此在其他维修厂更换了所有点火线圈和火花塞&#xff0c;故障依旧&#xff0c;…

309.最佳卖股票的时机包含冷冻期

一、题目分析 给定一个整数数组prices&#xff0c;其中第 prices[i] 表示第 i 天的股票价格 。​ 设计一个算法计算出最大利润。在满足以下约束条件下&#xff0c;你可以尽可能地完成更多的交易&#xff08;多次买卖一支股票&#xff09;: 卖出股票后&#xff0c;你无法在第二…

Nacos源码本地搭建流程及目录结构解读

下载地址 https://github.com/alibaba/nacos 目录结构 本地单机启动 首先maven编译完成之后在console下面找到Nacos 这个就是主启动类 然后再vm中配置参数-Dnacos.standalonetrue表示单机启动 当控制台没有报错 访问 http://localhost:8848/nacos 控制台界面登录进来之后显…

Linux—进程状态、僵尸进程、孤独进程、优先级

&#x1f4d8;北尘_&#xff1a;个人主页 &#x1f30e;个人专栏:《Linux操作系统》《经典算法试题 》《C》 《数据结构与算法》 ☀️走在路上&#xff0c;不忘来时的初心 文章目录 一、进程状态二、僵尸进程、孤儿进程1、Z(zombie)-僵尸进程2、僵尸进程危害3、孤儿进程 三、进…

String类讲解(1)

&#x1f435;本篇文章将讲解String类及其包含的方法 一、介绍String类 String属于引用类型&#xff0c;String类是Java的一个内置类&#xff0c;用于表示字符串&#xff0c;String类中具有许多方法&#xff0c;可以用来操作和处理字符串 二、字符串的构造 下面介绍三种构造字…