关于Elasticsearch 地理坐标类型 ("type": "geo_point") 在 Spring Data ES 中的使用以及遇到的问题

1,213 阅读3分钟

最近朋友在使用Elasticsearch的地理坐标类型 ("type": "geo_point") 的时候遇到了一些问题,在求助我之后我顺便整理了下一些使用方法和可能遇到的一些特殊问题。

首先,大家们在平时使用es的过程里,可能会遇到一些地理坐标相关的搜索和场景。其实,es提供了一种类型(geo_point)。并提供了相关的查询工具,在这里我仅简述下在 Spring Data ES 中的使用与问题。

官方文档的描述:地理坐标点(geo-point)是指地球表面可以用经纬度描述的一个点。地理坐标点可以用来计算两个坐标位置间的距离,或者判断一个点是否在一个区域中。

一、核心依赖:

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

二、对应的Java类型映射:

Spring Data ES中定义了GeoPoint这个类来实现两者之间的类型映射,GeoPoint类在org.springframework.data.elasticsearch.core.geo包下。同时要为此字段加上@GeoPointField注解。

    /**
     * 坐标
     */
    @GeoPointField
    private GeoPoint location;

在这里需要注意一点,官网上说:

地理坐标点不能被动态映射(dynamic mapping)自动检测,而是需要显式声明对应字段类型为geo_point

怎么理解呢,在java里使用Spring Data ES时候,建立索引的实体类的时候,假如一些比较常见的类型,例如String类型,我们不需要显式的去声明它对应着的是es里面的text。在我们使用createIndex()方法去创建索引的时候,会动态映射成es的text类型。而geo_point这个类型不能被动态映射,需要显式的,也就是手动的去映射,这是很容易被忽视的一点。动态映射的话类型会变成这样:

"location":{
    "properties":{
        "lat":{
            "type":"float"
        },
        "lon":{
            "type":"float"
        }
    }
}

那怎么去显式的声明呢?

1.这里在创建完索引后,可以通过下面的方式去声明


PUT /attractions
{
  "mappings": {
    "restaurant": {
      "properties": {
        "name": {
          "type": "string"
        },
        "location": {
          "type": "geo_point"
        }
      }
    }
  }
}

2.也可以在代码里,通过createIndex()方法创建完索引后,利用putMapping()方法去显式的声明,@GeoPointField注解告诉了这是一个地理坐标类型。

elasticsearchTemplate.createIndex(DwsLabelV1DO.class);
elasticsearchTemplate.putMapping(DwsLabelV1DO.class);

三、基本的使用

1.根据一个点的坐标,搜索给定范围内的其他点
        BoolQueryBuilder boolQueryBuilder = new BoolQueryBuilder();

        // 指定坐标的字段location,需要是地理坐标类型的
        GeoDistanceQueryBuilder distanceQueryBuilder = new GeoDistanceQueryBuilder("location");
        //经纬度坐标
        distanceQueryBuilder.point(latitude, longitude);
        // 范围,第二个参数是单位
        distanceQueryBuilder.distance(distance, DistanceUnit.KILOMETERS);
        boolQueryBuilder.filter(distanceQueryBuilder);
        
        //上面是查询条件的组合,执行可以用通过继承ElasticsearchRepository<T, ID extends Serializable>来简化完成相关查询
        repository.search(boolQueryBuilder, pageable);
        //也可以使用ElasticsearchTemplate
        SearchQuery searchQuery = new NativeSearchQueryBuilder().withIndices("xxx").withQuery(boolQueryBuilder).build();
        elasticsearchTemplate.queryForList(searchQuery, xxx.class);
2.计算两个点之间的距离
//在org.elasticsearch.common.geo.GeoDistance类里定义了相关计算方法
double distance = GeoDistance.ARC.calculate(srcLat, srcLon, dstLat, dstLon, DistanceUnit.KILOMETERS);

关于GeoDistance.ARC和GeoDistance.PLANE,前者比后者计算起来要慢,但精确度要比后者高,具体区别可以看这里

3.以某个坐标点为中心,按距离近远排序搜索指定范围
        // 实现了SearchQuery接口,用于组装QueryBuilder和SortBuilder以及Pageable等
        NativeSearchQueryBuilder nativeSearchQueryBuilder = new NativeSearchQueryBuilder();
        nativeSearchQueryBuilder.withPageable(pageable)
        // 间接实现了QueryBuilder接口
        BoolQueryBuilder boolQueryBuilder = new BoolQueryBuilder();
        // 以某点为中心,搜索指定范围
        GeoDistanceQueryBuilder distanceQueryBuilder = new GeoDistanceQueryBuilder("location");
        distanceQueryBuilder.point(latitude, longitude);
	// 定义查询单位:公里
        distanceQueryBuilder.distance(distance, DistanceUnit.KILOMETERS);
        boolQueryBuilder.filter(distanceQueryBuilder);
	nativeSearchQueryBuilder.withQuery(boolQueryBuilder);
	
	// 按距离升序
	GeoDistanceSortBuilder distanceSortBuilder =
	new GeoDistanceSortBuilder("location", latitude, longitude);
	distanceSortBuilder.unit(DistanceUnit.KILOMETERS);
	distanceSortBuilder.order(SortOrder.ASC);
	nativeSearchQueryBuilder.withSort(distanceSortBuilder);

        testRepository.search(nativeSearchQueryBuilder.build());

部分引用此篇文章