ES实现GEO位置搜索

Elasticsearch-7.15.2
附近查询,也叫做距离查询(geo_distance):查询到指定中心点小于某个距离值的所有文档。

创建索引 (my_geo),直接设置mapping

GEO字段的创建:添加一个字段location,类型为 geo_point。

GEO类型的字段是不能使用动态映射自动生成的,我们需要在创建索引时指定字段的类型为geo_point,geo_point 类型的字段存储的经纬度。

curl -X PUT http://192.168.11.21:9200/my_geo -H 'Content-Type:application/json' -d'
{
  "mappings": {
    "properties": {
      "name": {"type": "text"},
      "location": {"type":"geo_point"}
    }
  }
}'

插入2条数据

curl -X POST 192.168.11.21:9200/my_geo/_doc/1 -H 'Content-Type: application/json' -d '{
  "name": "路人甲北京站",
  "location": {
    "lat": 39.90279998006104,
    "lon": 116.42703999493406
  }
}'

curl -X POST 192.168.11.21:9200/my_geo/_doc/2 -H 'Content-Type: application/json' -d '{
  "name": "路人乙朝阳公园",
  "location": {
    "lat": 39.93367367974064,
    "lon": 116.47845257733152
  }
}'
查询语句 curl

我的位置在“工体”,“北京站”的路人甲和“朝阳公园”的路人乙都在5km的范围内,查询5km和3km范围内都有谁。

把范围缩短distance改为3km,请求如下:

curl -XGET '192.168.11.21:9200/my_geo/_search?pretty=true' -H 'Content-Type:application/json' -d '
{
  "query":{
      "bool":{
          "must":{"match_all":{ }},
          "filter":{
              "geo_distance":{
                  "distance":"3km",
                  "location":{"lat": 39.93031708627304,"lon": 116.4470385453491}
              }
          }
      }
  }}'

结果:在“朝阳公园”的路人乙被搜索了出来。

{
  "took" : 4,
  "timed_out" : false,
  "_shards" : {"total" : 1, "successful" : 1, "skipped" : 0, "failed" : 0},
  "hits" : {"total" : { "value": 1, "relation" : "eq"},
    "max_score" : 1.0,
    "hits" : [
      {
        "_index" : "my_geo",
        "_type" : "_doc",
        "_id" : "2",
        "_score" : 1.0,
        "_source" : {
          "name" : "路人乙朝阳公园",
          "location" : {"lat" : 39.93367367974064,"lon": 116.47845257733152}
        }
      }
    ]
  }}

距离排序

5公里范围内排序查询。

curl -XGET  'http://192.168.11.21:9200/my_geo/_search?pretty=true' -H 'Content-Type:application/json' -d '
{
  "query":{
      "bool":{
          "must":{
              "match_all":{ }
          },
          "filter":{
              "geo_distance":{ // 按距离搜索
                  "distance":"5km", // 搜索范围
                  "location":{"lat": 39.93031708627304,"lon": 116.4470385453491} // 当前纬度 经度
              }
          }
      }
  },
    "sort": [
    {
      "_geo_distance": { // _geo_distance代表根据距离排序
        "location": { // 根据location存储的经纬度计算距离
            "lat": 39.93031708627304, // 当前纬度 经度
            "lon": 116.4470385453491
        },
        "order": "asc"
      }
    }
  ]
}' 

curl查询结果:离我“工体”比较近的“路人乙”排在了第一个,也是符合预期的。

{
  "took" : 10,
  "timed_out" : false,
  "_shards" : {
    "total" : 1,
    "successful" : 1,
    "skipped" : 0,
    "failed" : 0
  },
  "hits" : {
    "total" : {
      "value" : 2,
      "relation" : "eq"
    },
    "max_score" : null,
    "hits" : [
      {
        "_index" : "my_geo",
        "_type" : "_doc",
        "_id" : "2",
        "_score" : null,
        "_source" : {
          "name" : "路人乙",
          "location" : {
            "lat" : 39.93367367974064,
            "lon" : 116.47845257733152
          }
        },
        "sort" : [
          2704.400492813901
        ]
      },
      {
        "_index" : "my_geo",
        "_type" : "_doc",
        "_id" : "1",
        "_score" : null,
        "_source" : {
          "name" : "路人甲",
          "location" : {
            "lat" : 39.90279998006104,
            "lon" : 116.42703999493406
          }
        },
        "sort" : [
          3503.0165324004943
        ]
      }
    ]
  }}

JAVA程序中使用GEO搜索

在定义实体类时,对应的GEO字段要使用特殊的类型。location的类型是GeoPoint,添加数据时转成Json存储。

import lombok.Data;
import org.springframework.data.annotation.Id;
import org.springframework.data.elasticsearch.annotations.Document;
import org.springframework.data.elasticsearch.annotations.Field;
import org.springframework.data.elasticsearch.annotations.FieldType;
import org.springframework.data.elasticsearch.annotations.GeoPointField;
import org.springframework.data.elasticsearch.core.geo.GeoPoint;

@Data
@Document(indexName = "my_geo")
public class MyGeo {

    @Field(type = FieldType.Keyword)
    private String goodsName;

    @Field(store = true)
    @GeoPointField
    private GeoPoint location;
}

geo距离查询

    public void geoDistanceQuery(){
        //创建查询请求对象
        SearchRequest request = new SearchRequest("my_geo");
        SearchSourceBuilder sourceBuilder = new SearchSourceBuilder();
        GeoPoint geoPoint = new GeoPoint(39.93031708627304, 116.4470385453491);//工体的坐标
        //geo距离查询
        QueryBuilder queryBuilder = QueryBuilders.geoDistanceQuery("location")
                .distance(5, DistanceUnit.KILOMETERS)
                .point(geoPoint);

        sourceBuilder.query(queryBuilder);
        request.source(sourceBuilder);
        try {
            SearchResponse response = client.search(request, RequestOptions.DEFAULT);
            for(SearchHit hit : response.getHits().getHits()){
                
                System.out.println(hit.getSourceAsString());
            }
        }catch (Exception e){
            e.printStackTrace();
        }
    }
结果:
{"name":"路人甲","location":{"lat":39.90279998006104,"lon":116.42703999493406}}
{"name":"路人乙","location":{"lat":39.93367367974064,"lon":116.47845257733152}}

距离排序

    public void geoDistanceSortQuery(){
        SearchRequest request = new SearchRequest("my_geo"); //创建查询请求对象
        SearchSourceBuilder sourceBuilder = new SearchSourceBuilder();

        GeoPoint geoPoint = new GeoPoint(39.93031708627304, 116.4470385453491);//工体的坐标
        
        GeoDistanceSortBuilder sortBuilder = SortBuilders.geoDistanceSort("location", geoPoint).order(SortOrder.ASC);
        sourceBuilder.sort(sortBuilder);
        request.source(sourceBuilder);
        
        SearchResponse response = client.search(request, RequestOptions.DEFAULT);
            for(SearchHit hit : response.getHits().getHits()){
                System.out.println(hit.getSourceAsString());
    }
结果:
{"name":"路人乙","location":{"lat":39.93367367974064,"lon":116.47845257733152}}
{"name":"路人甲","location":{"lat":39.90279998006104,"lon":116.42703999493406}}

其他

距离排序(带分页)
GeoDistanceQueryBuilder

    /**
     *  ElasticSearchRepository和 RestHighLevelClient ElasticsearchRestTemplate的区别
     *  https://blog.csdn.net/zhiyikeji/article/details/128908596
     *
     *  从名字就能看出来,QueryBuilder主要用来构建查询条件、过滤条件,SortBuilder主要是构建排序。
     *  譬如,我们要查询距离某个位置100米范围内的所有人、并且按照距离远近进行排序:
     */
    public void findGeoDistanceSort(){
        double lat = 39.93031708627304, lng = 116.4470385453491; //工体

        //设定搜索半径
        GeoDistanceQueryBuilder queryBuilder = QueryBuilders.geoDistanceQuery("location")
                //.geoDistance(GeoDistance.PLANE)
                .point(lat, lng).distance(300, DistanceUnit.KILOMETERS);

        //计算距离多少公里 获取点与点之间的距离
        GeoDistanceSortBuilder sortBuilder = SortBuilders.geoDistanceSort("location", lat, lng)
                .point(lat, lng).unit(DistanceUnit.METERS).order(SortOrder.ASC);

        Pageable pageable = PageRequest.of(0, 10);

        NativeSearchQueryBuilder builder = new NativeSearchQueryBuilder().withPageable(pageable)
                .withFilter(queryBuilder).withSort(sortBuilder);

        NativeSearchQuery nativeSearchQuery = builder.build();

        org.springframework.data.elasticsearch.core.SearchHits<MyGeo> searchHits = elasticsearchRestTemplate.search(nativeSearchQuery, MyGeo.class);
        List<org.springframework.data.elasticsearch.core.SearchHit<MyGeo>> searchHitList = searchHits.getSearchHits();
        if(searchHitList.isEmpty()){
            System.out.println("没有查询到数据!");
            return;
        }

        searchHitList.forEach(hit ->{
            // 此处的索引和查询返回结果中sort集合的索引一致,目的在于取返回结果中的距离计算结果,以免二次计算,造成资源浪费
            //Object geoDistance = hit.getSortValues().get(2);
            System.out.println("hit -- " + JSONObject.toJSONString(hit));
        });
    }

结果:
{"name":"路人乙","location":{"lat":39.93367367974064,"lon":116.47845257733152}}
{"name":"路人甲","location":{"lat":39.90279998006104,"lon":116.42703999493406}}

参考资料

ES7学习笔记(十三)GEO位置搜索
https://www.modb.pro/db/73991

ES GEO地理空间查询 基于geo-point的多边形查询
https://huaweicloud.csdn.net/637eedd2df016f70ae4c9b19.html

通过ElasticsearchRestTemplate 完成地理搜索 矩形搜索,附近人搜索, 距离搜索
https://blog.csdn.net/qq_41712271/article/details/134881584

###复杂查询包含ES按距离排序
https://blog.csdn.net/m0_56726104/article/details/120785048


geo 距离排序检索
https://blog.csdn.net/wenxingchen/article/details/95448215/

GEO位置搜索 https://www.modb.pro/db/73991
ElasticsearchTemplate 经纬度按距离排序 http://www.javashuo.com/article/p-uqiafsey-hx.html

ES 位置查询之geo_point
https://blog.csdn.net/weixin_43918355/article/details/118366065

01-10 11:56