一、需求

在前一篇文章中,我们学会了geo_point的使用,此处使用地理位置查询并使用java语言实现一下。

功能:
1、实现查询、过滤。
2、实现聚合。
3、实现排序。
4、实现后置过滤。

二、对应的query语句

GET geo_index/_search
{
    "from": 0,
    "size": 10,
    "timeout": "20s",
    "query": {
        "bool": {
            "must": [
                {
                    "match_all": {
                        "boost": 1.0
                    }
                }
            ],
            "filter": [
                {
                    "geo_distance": {
                        "location": [
                            121.462311,
                            31.256224
                        ],
                        "distance": 3000.0,
                        "distance_type": "arc",
                        "validation_method": "STRICT",
                        "ignore_unmapped": false,
                        "boost": 1.0,
                        "_name": "optional_name"
                    }
                }
            ],
            "adjust_pure_negative": true,
            "boost": 1.0
        }
    },
    "post_filter": {
        "geo_distance": {
            "location": [
                121.462311,
                31.256224
            ],
            "distance": 1000.0,
            "distance_type": "arc",
            "validation_method": "STRICT",
            "ignore_unmapped": false,
            "boost": 1.0
        }
    },
    "sort": [
        {
            "_geo_distance": {
                "location": [
                    {
                        "lat": 31.256224,
                        "lon": 121.462311
                    }
                ],
                "unit": "m",
                "distance_type": "arc",
                "order": "desc",
                "validation_method": "STRICT",
                "ignore_unmapped": false
            }
        }
    ],
    "aggregations": {
        "distanceAgg": {
            "geo_distance": {
                "field": "location",
                "origin": {
                    "lat": 31.256224,
                    "lon": 121.462311
                },
                "ranges": [
                    {
                        "key": "first",
                        "from": 0.0,
                        "to": 500.0
                    },
                    {
                        "key": "second",
                        "from": 500.0,
                        "to": 1000.0
                    },
                    {
                        "key": "third",
                        "from": 1000.0
                    }
                ],
                "keyed": true,
                "unit": "m",
                "distance_type": "ARC"
            }
        }
    }
}

3、实现查询

1、方式一-使用api
import com.huan.study.esapi.AbstractEsApi;
import org.elasticsearch.action.search.SearchRequest;
import org.elasticsearch.action.search.SearchResponse;
import org.elasticsearch.action.search.ShardSearchFailure;
import org.elasticsearch.client.RequestOptions;
import org.elasticsearch.common.geo.GeoDistance;
import org.elasticsearch.common.geo.GeoPoint;
import org.elasticsearch.common.unit.DistanceUnit;
import org.elasticsearch.common.unit.TimeValue;
import org.elasticsearch.index.query.QueryBuilders;
import org.elasticsearch.rest.RestStatus;
import org.elasticsearch.search.SearchHit;
import org.elasticsearch.search.aggregations.AggregationBuilders;
import org.elasticsearch.search.aggregations.Aggregations;
import org.elasticsearch.search.aggregations.bucket.range.ParsedGeoDistance;
import org.elasticsearch.search.aggregations.bucket.range.Range;
import org.elasticsearch.search.builder.SearchSourceBuilder;
import org.elasticsearch.search.sort.GeoDistanceSortBuilder;
import org.elasticsearch.search.sort.SortOrder;
import org.junit.jupiter.api.DisplayName;
import org.junit.jupiter.api.Test;

import java.io.IOException;
import java.util.Arrays;

/**
 * distance query 类型的 地理位置查询
 *
 * @author huan.fu 2021/4/22 - 下午4:00
 */
public class DistanceQueryApi extends AbstractEsApi {

    @DisplayName("距离查询")
    @Test
    public void distanceQueryTest() throws IOException {

        // 查询请求
        SearchRequest searchRequest = new SearchRequest("geo_index");

        SearchSourceBuilder searchSourceBuilder = new SearchSourceBuilder();
        // 从索引那个开始返回数据
        searchSourceBuilder.from(0);
        // 查询多少条记录
        searchSourceBuilder.size(10);
        // 查询超时时间
        searchSourceBuilder.timeout(TimeValue.timeValueSeconds(20));

        // 构造查询和过滤数据
        searchSourceBuilder.query(
                // 构造布尔查询
                QueryBuilders.boolQuery()
                        // 查询语句
                        .must(
                                QueryBuilders.matchAllQuery()
                        )
                        // 过滤语句
                        .filter(
                                // name 是过滤的字段
                                QueryBuilders.geoDistanceQuery("location")
                                        // 在3km之内
                                        .distance("3", DistanceUnit.KILOMETERS)
                                        // 以那个点为中心
                                        .point(31.256224D, 121.462311D)
                                        .geoDistance(GeoDistance.ARC)
                                        // 一个查询的名字,可选
                                        .queryName("optional_name")
                        )
        );
        // 后置过滤
        searchSourceBuilder.postFilter(
                // name 是过滤的字段
                QueryBuilders.geoDistanceQuery("location")
                        // 在3km之内
                        .distance("1", DistanceUnit.KILOMETERS)
                        // 以那个点为中心
                        .point(31.256224D, 121.462311D)
        );

        // 排序
        searchSourceBuilder.sort(
                // 不同的类型使用不同的SortBuilder
                new GeoDistanceSortBuilder("location", 31.256224D, 121.462311D)
                        .order(SortOrder.DESC)
                        .unit(DistanceUnit.METERS)
                        .geoDistance(GeoDistance.ARC)
        );

        // 聚合操作
        searchSourceBuilder.aggregation(
                // name 聚合的名字 point 以那个点为中心开始聚合
                AggregationBuilders.geoDistance("distanceAgg", new GeoPoint(31.256224D, 121.462311D))
                        // 字段
                        .field("location")
                        .unit(DistanceUnit.METERS)
                        .distanceType(GeoDistance.ARC)
                        .keyed(true)
                        // 范围
                        .addRange("first", 0.0D, 500D)
                        .addRange("second", 500D, 1000D)
                        .addRange("third", 1000D, Double.NEGATIVE_INFINITY)
        );

        searchRequest.source(searchSourceBuilder);
        System.out.println("查询语句:" + searchSourceBuilder.toString());

        SearchResponse searchResponse = client.search(searchRequest, RequestOptions.DEFAULT);
        System.out.println("查询结果:" + searchResponse);

        RestStatus status = searchResponse.status();
        System.out.println(status.getStatus());
        System.out.println(searchResponse.isTerminatedEarly());
        System.out.println(searchResponse.isTimedOut());

        for (ShardSearchFailure shardFailure : searchResponse.getShardFailures()) {
            System.out.println(shardFailure);
        }

        // 匹配到的结果
        for (SearchHit hit : searchResponse.getHits().getHits()) {
            // 数据
            System.out.println(hit.getSourceAsMap());
            // 排序距离
            Object[] sortValues = hit.getSortValues();
            System.out.println(Arrays.toString(sortValues));
            System.out.println("=====");
        }

        // 获取聚合的结果
        Aggregations aggregations = searchResponse.getAggregations();
        aggregations.getAsMap().forEach((key, value) -> {
            System.out.println("key:" + key);
            for (Range.Bucket bucket : ((ParsedGeoDistance) value).getBuckets()) {
                System.out.println("from:" + bucket.getFromAsString() + " to:" + bucket.getToAsString() + " value:" + bucket.getDocCount());
            }
            System.out.println("------------");
        });
    }
}
2、方式二-之内传入json查询串
Request request = new Request("GET", "geo_index/_search");
        // 需要查询的 json 字符串
        String queryJsonEntity = "{\"from\":0,\"size\":10,\"timeout\":\"20s\",\"query\":{\"bool\":{\"must\":[{\"match_all\":{\"boost\":1.0}}],\"filter\":[{\"geo_distance\":{\"location\":[121.462311,31.256224],\"distance\":3000.0,\"distance_type\":\"arc\",\"validation_method\":\"STRICT\",\"ignore_unmapped\":false,\"boost\":1.0,\"_name\":\"optional_name\"}}],\"adjust_pure_negative\":true,\"boost\":1.0}},\"post_filter\":{\"geo_distance\":{\"location\":[121.462311,31.256224],\"distance\":1000.0,\"distance_type\":\"arc\",\"validation_method\":\"STRICT\",\"ignore_unmapped\":false,\"boost\":1.0}},\"sort\":[{\"_geo_distance\":{\"location\":[{\"lat\":31.256224,\"lon\":121.462311}],\"unit\":\"m\",\"distance_type\":\"arc\",\"order\":\"desc\",\"validation_method\":\"STRICT\",\"ignore_unmapped\":false}}],\"aggregations\":{\"distanceAgg\":{\"geo_distance\":{\"field\":\"location\",\"origin\":{\"lat\":31.256224,\"lon\":121.462311},\"ranges\":[{\"key\":\"first\",\"from\":0.0,\"to\":500.0},{\"key\":\"second\",\"from\":500.0,\"to\":1000.0},{\"key\":\"third\",\"from\":1000.0}],\"keyed\":true,\"unit\":\"m\",\"distance_type\":\"ARC\"}}}}";
        request.setJsonEntity(queryJsonEntity);
        Response response = client.getLowLevelClient().performRequest(request);
        String result = EntityUtils.toString(request.getEntity(), StandardCharsets.UTF_8);
        System.out.println("响应结果: " + result);
    
3、输出查询语句

使用输出 SearchSourceBuilder 的结果即可。

4、输出响应结果

直接输出 SearchResponse的结果即可。

至此就完成了上方的查询。

四、注意事项

  1. java high level rest client 的版本最好和我们的es的版本一致,如果不一致,那么最好要和主版本一致。
  2. jdk的版本最少要是1.8的版本。
  3. 不推荐使用 TransportClient,这个已经过时了,在es8中将会移除。
  4. 输出我们自己的查询语句,直接输出 SearchSourceBuilder即可。
  5. 输出响应语句,直接输出SearchResponse即可。

五、完整代码路径

https://gitee.com/huan1993/spring-cloud-parent/blob/master/es/es-api/src/test/java/com/huan/study/esapi/dslapi/geoapi/DistanceQueryApi.java

六、参考文档

1、https://www.elastic.co/guide/en/elasticsearch/client/java-rest/7.12/java-rest-high-query-builders.html
2、https://www.elastic.co/guide/en/elasticsearch/client/java-rest/7.12/java-rest-high-search.html


huan1993
218 声望34 粉丝

java工程师