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

news2024/11/24 7:20:59

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 ;
    //重写父类方法
    @Override
    public RestHighLevelClient elasticsearchClient() {
        RestClientBuilder builder = RestClient.builder(new HttpHost(host, port));
        RestHighLevelClient restHighLevelClient = new
                RestHighLevelClient(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;
}

初始化数据

 @Test
    public void saveAll() {
        //起点 111.000,31.000
        //终点 121.000,31.000
        //( 121 , 31 )    -    ( 111 , 31 )    之间的距离为    952.8062737420901 km

        //96-121,23-40
        List<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)  True
        geoShapeQueryBuilder.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.coloradmin.cn/o/1260855.html

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

相关文章

【单调栈】最大宽度坡

public int maxWidthRamp(int[] nums) {/* 此方法思路正确&#xff0c;但超时int n nums.length;Deque<Integer> stack;int max 0;for (int i 0; i < n; i) {stack new LinkedList<>();stack.push(nums[i]);int j i 1;while (j < n) {stack.push(nums…

华为拆分零部件业务,长安入股,赛力斯接洽中

作者 |德新 编辑 |王博 11月26日&#xff0c;长安汽车官宣与华为在智能汽车零部件业务上的投资与合作&#xff1a; 华为拟成立一家新的公司&#xff0c;并将其在智能汽车解决方案业务上的核心技术和资源注入新公司&#xff0c;长安汽车及关联方有意投资该新公司。 参照目前长…

如何保护PPT文件禁止修改?

PPT文件会应用在会议、演讲、课件等工作生活中&#xff0c;当我们制作好了PPT之后&#xff0c;保护内容防止在演示时出错是很重要的&#xff0c;那么如何将PPT文件设置成禁止修改模式呢&#xff1f;今天分享几个方法给大家。 方法一 将PPT文件直接保存或者另存为一份文件&…

Gitea和Jenkins安装

Gitea Gitea&#xff1a;https://dl.gitea.com/gitea/1.21.0/ Jenkins&#xff1a;https://www.jenkins.io/download/ 数据库配置 可以参考官方文档-https://docs.gitea.cn/1.20/installation/database-prep&#xff0c;这里以MySQL作为讲解 MySQL 在数据库实例上&#xf…

小航助学题库蓝桥杯题库stem选拔赛(21年1月)(含题库教师学生账号)

需要在线模拟训练的题库账号请点击 小航助学编程在线模拟试卷系统&#xff08;含题库答题软件账号&#xff09;_程序猿下山的博客-CSDN博客 需要在线模拟训练的题库账号请点击 小航助学编程在线模拟试卷系统&#xff08;含题库答题软件账号&#xff09;_程序猿下山的博客-CSD…

电子学会C/C++编程等级考试2022年03月(三级)真题解析

C/C++等级考试(1~8级)全部真题・点这里 第1题:和数 给定一个正整数序列,判断其中有多少个数,等于数列中其他两个数的和。 比如,对于数列1 2 3 4, 这个问题的答案就是2, 因为3 = 2 + 1, 4 = 1 + 3。 时间限制:10000 内存限制:65536输入 共两行,第一行是数列中数的个数…

使用vscode中编写c语言——无法打开 源 文件 “stdlib.h“C/C++(1696)问题

出现这个问题原因如下&#xff1a; 1、没有下载编辑器或者是没有配置好该编辑器的环境变量。 可以通过如下方法检查是否安装并配置好编辑器&#xff1a;打开终端&#xff1a;按winR cmd&#xff0c;然后输入gcc-v&#xff0c;查看是否有mingw64编辑器&#xff0c;如下图是已经…

HNU 练习八 结构体编程题1. 评委打分

【问题描述】 校园卡拉OK比赛设置了7名评委&#xff0c;当一名选手K完歌之后&#xff0c;主持人报出歌手名字后&#xff0c;7位评委同时亮分&#xff0c;按照惯例&#xff0c;去掉一个最高分和一个最低分后&#xff0c;其余5位评委评分总和为该选手的最终得分。 一共有n组选手参…

【面试题】JavaScript高级循环方法

给大家推荐一个实用面试题库 1、前端面试题库 &#xff08;面试必备&#xff09; 推荐&#xff1a;★★★★★ 地址&#xff1a;web前端面试题库 除了for循环♻️&#xff0c;for-of,for-each循环♻️也是一个不错的选择 先说for-of循环♻️ 认识for-of循环♻️…

bmp文件操作和详解

一 BMP文件格式 BMP&#xff08;Bitmap&#xff09;是Windows操作系统中的标准图像文件格式。 由于windows操作系统的发布时机远早于Linux、Android、IOS等操作系统&#xff0c;因此windows中很多数据格式和算法的标准也是当今所有操作系统必须要兼容的标准数据结构和算法。BM…

推动企业数字化转型,如何更好地规避失败风险?

随着科技的飞速发展&#xff0c;数字化转型已成为企业持续发展的必然选择&#xff0c;然而有相关数据显示&#xff0c;超过80%的企业在数字化转型过程中都遭遇失败。本文将揭示企业数字化转型常见的失败原因&#xff0c;并探讨如何帮助企业规避转型失败风险。 一、企业数字化转…

Android修行手册 - 使用ViewPager2实现画廊效果

Unity3D特效百例案例项目实战源码Android-Unity实战问题汇总游戏脚本-辅助自动化Android控件全解手册再战Android系列Scratch编程案例软考全系列Unity3D学习专栏蓝桥系列ChatGPT和AIGC &#x1f449;关于作者 专注于Android/Unity和各种游戏开发技巧&#xff0c;以及各种资源分…

青少年CTF之PHP特性练习(1-5)

青少年CTF-PHP特性练习 文章目录 青少年CTF-PHP特性练习PHP特性01PHP特性02PHP特性03PHP特性04PHP特性05 PHP特性01 看给出的源码&#xff0c;两个变量的值加密后的MD5相同 <?php$s1 "%af%13%76%70%82%a0%a6%58%cb%3e%23%38%c4%c6%db%8b%60%2c%bb%90%68%a0%2d%e9%47…

使用Arthas排查性能问题

Arthas 是一款线上监控诊断产品&#xff0c;通过全局视角实时查看应用 load、内存、gc、线程的状态信息&#xff0c;并能在不修改应用代码的情况下&#xff0c;对业务问题进行诊断&#xff0c;包括查看方法调用的出入参、异常&#xff0c;监测方法执行耗时&#xff0c;类加载信…

小程序----使用图表显示数据--canvas

需求&#xff1a;在小程序上实现数据可视化 思路&#xff1a;本来想用的是echarts或者相关的可视化插件&#xff0c;但因为用的是vue3&#xff0c;大多数插件不支持&#xff0c;所以用了echarts&#xff0c;但最后打包的时候说包太大超过2M无法上传&#xff0c;百度了一下&…

汽车功能安全ISO26262

一、功能安全基本概念及功能安全管理 什么是功能安全 相关标准&#xff1a; 现状&#xff1a; 功能安全的目的和范围&#xff1a; 总体框架&#xff1a; 基本定义&#xff1a;

vue3中toRaw 与 markRaw

toRaw 返回由 reactive 或 readonly 方法转换成响应式代理的普通对象。 这是一个还原方法&#xff0c;可用于临时读取&#xff0c;访问不会被代理/跟踪&#xff0c;写入时也不会触发界面更新。 markRaw 标记一个对象&#xff0c;使其永远不会转换为代理。返回对象本身 应…

企业软件手机app定制开发新趋势|网站小程序搭建

企业软件手机app定制开发新趋势|网站小程序搭建 随着移动互联网的快速发展和企业数字化转型的加速&#xff0c;企业软件手机App定制开发正成为一个新的趋势。这种趋势主要是由于企业对于手机App的需求增长以及现有的通用应用不能满足企业特定需求的情况下而产生的。 首先&#…

解决:AttributeError: module ‘os’ has no attribute ‘mknod’

解决&#xff1a;AttributeError: module ‘os’ has no attribute ‘mknod’ 文章目录 解决&#xff1a;AttributeError: module os has no attribute mknod背景报错问题报错翻译报错位置代码报错原因解决方法今天的分享就到此结束了 背景 在使用之前的代码时&#xff0c;报错…

小航助学题库蓝桥杯题库stem选拔赛(21年3月)(含题库教师学生账号)

需要在线模拟训练的题库账号请点击 小航助学编程在线模拟试卷系统&#xff08;含题库答题软件账号&#xff09;_程序猿下山的博客-CSDN博客 需要在线模拟训练的题库账号请点击 小航助学编程在线模拟试卷系统&#xff08;含题库答题软件账号&#xff09;_程序猿下山的博客-CSD…