使用ES完成“离我最近”相关功能

11 篇文章 1 订阅
2 篇文章 0 订阅

前言

最近项目中有个需求是完成离我最近的功能,经过讨论决定使用ElasticSearch根据用户所在位置经纬度来完成相关需求,这里使用一个小的Demo来做记录。

准备工作

1.构建索引

创建索引location_test,索引只包含两个字段,一个景点名字另一个是景点经纬度,保存经纬度使用geo_point类型存储。

PUT http://localhost:9200/location_test/_mapping
{
    "properties":{
        "locationName":{
            "type":"text",
            "analyzer":"ik_max_word"
        },
        "location":{
            "type":"geo_point"
        }
    }
}

2.插入数据

因为是Demo所以这里只插入七条上海的数据来进行演示。

POST http://localhost:9200/location_test/_doc
{
	"locationName": "召稼楼古镇",
	"location": {
		"lat": 31.081128,
		"lon": 121.555511
	}
}
{
	"locationName": "上海奇思妙想减压馆",
	"location": {
		"lat": 31.24249,
		"lon": 121.490283
	}
}
{
	"locationName": "上海JOYPOLIS世嘉都市乐园",
	"location": {
		"lat": 31.239446,
		"lon": 121.419129
	}
}
{
	"locationName": "震旦博物馆",
	"location": {
		"lat": 31.240324,
		"lon": 121.506166
	}
}
{
	"locationName": "江南三民文化村",
	"location": {
		"lat": 31.723805,
		"lon": 121.504962
	}
}
{
	"locationName": "海湾3D错觉艺术馆",
	"location": {
		"lat": 30.828349,
		"lon": 121.535577
	}
}
{
	"locationName": "上海迪士尼度假区",
	"location": {
		"lat": 31.148267,
		"lon": 121.671964
	}
}

完成需求

接下来就是完成需求的过程了,主要是完成两块需求:获取距离并排序、根据距离进行筛选。

1.根据经纬度进行距离排序并获取距离

假设你现在正在外滩和女朋友散步(外滩经纬度:31.243453,121.497204),这时你们要找离你们最近的哪些景点可以去看看,使用ES可以很轻松的完成这一功能,ES的请求如下:

POST http://localhost:9200/location_test/_search
{
    "sort": [
        {
            "_geo_distance": {
                "location": [
                    {
                        "lat": 31.243453,
                        "lon": 121.497204
                    }
                ],
                "unit": "m",
                "order": "asc"
            }
        }
    ]
}

这里使用 _geo_distance 关键字进行距离查询,存储文档经纬度的字段是localtion,location中的值就是你们现在位置的经纬度,unit指的是查询的单位,这里是用米,也可以使用千米(km)做单位,排序方式用升序。得到查询结果如下:

{
    "total":{
        "value":7,
        "relation":"eq"
    },
    "max_score":null,
    "hits":[
        {
            "_index":"location_test",
            "_type":"_doc",
            "_id":"Umg6oHQB29lS14JF6ljQ",
            "_score":null,
            "_source":{
                "locationName":"上海奇思妙想减压馆",
                "location":{
                    "lat":31.24249,
                    "lon":121.490283
                }
            },
            "sort":[
                666.6306788021509
            ]
        },
        {
            "_index":"location_test",
            "_type":"_doc",
            "_id":"W2g7oHQB29lS14JFE1jZ",
            "_score":null,
            "_source":{
                "locationName":"震旦博物馆",
                "location":{
                    "lat":31.240324,
                    "lon":121.506166
                }
            },
            "sort":[
                920.3164557098329
            ]
        },
        {
            "_index":"location_test",
            "_type":"_doc",
            "_id":"Wmg7oHQB29lS14JFAlgP",
            "_score":null,
            "_source":{
                "locationName":"上海JOYPOLIS世嘉都市乐园",
                "location":{
                    "lat":31.239446,
                    "lon":121.419129
                }
            },
            "sort":[
                7436.0041059579125
            ]
        },
        {
            "_index":"location_test",
            "_type":"_doc",
            "_id":"8mg1oHQB29lS14JFUFBe",
            "_score":null,
            "_source":{
                "locationName":"召稼楼古镇",
                "location":{
                    "lat":31.081128,
                    "lon":121.555511
                }
            },
            "sort":[
                18883.13042012399
            ]
        },
        {
            "_index":"location_test",
            "_type":"_doc",
            "_id":"RGg9oHQB29lS14JF5Vpq",
            "_score":null,
            "_source":{
                "locationName":"上海迪士尼度假区",
                "location":{
                    "lat":31.148267,
                    "lon":121.671964
                }
            },
            "sort":[
                19706.20435968952
            ]
        },
        {
            "_index":"location_test",
            "_type":"_doc",
            "_id":"bWg7oHQB29lS14JFOlgq",
            "_score":null,
            "_source":{
                "locationName":"海湾3D错觉艺术馆",
                "location":{
                    "lat":30.828349,
                    "lon":121.535577
                }
            },
            "sort":[
                46302.09271440355
            ]
        },
        {
            "_index":"location_test",
            "_type":"_doc",
            "_id":"XWg7oHQB29lS14JFJ1hP",
            "_score":null,
            "_source":{
                "locationName":"江南三民文化村",
                "location":{
                    "lat":31.723805,
                    "lon":121.504962
                }
            },
            "sort":[
                53417.844250374095
            ]
        }
    ]
}

查询结果把文档数据根据location中的经纬度进行计算排序,结果中的sort就是根据经纬度计算出来的距离,单位是米。可以看到目前离你们最近的是上海奇思妙想减压馆距离666米左右。

Java代码如下:

public void searchNearest() throws IOException {
    // 待查询的经纬度
    double lat = 31.243453;
    double lon = 121.497204;
    SearchRequest searchRequest = new SearchRequest("location_test");
    SearchSourceBuilder sourceBuilder = new SearchSourceBuilder();
    // 使用GeoDistanceSortBuilder构建查询条件
    GeoDistanceSortBuilder distanceSortBuilder = new GeoDistanceSortBuilder("location", lat, lon);
    // 设置单位为米,升序排序
 	distanceSortBuilder.unit(DistanceUnit.METERS).order(SortOrder.ASC);
    sourceBuilder.sort(distanceSortBuilder);
    SearchRequest request = searchRequest.source(sourceBuilder);
    SearchResponse response = restHighLevelClient.search(request, RequestOptions.DEFAULT);
    SearchHit[] hitResult = response.getHits().getHits();
    // 打印输出,距离在使用getSortValues()获取
    for (int i = 0; i < hitResult.length; i++) {
        BigDecimal distance = new BigDecimal(hitResult[i].getSortValues()[0].toString())
                .setScale(2, BigDecimal.ROUND_HALF_UP);
        System.out.println(hitResult[i].getSourceAsMap().get("locationName").toString()+"据你"+ distance +"米");
    }
}

使用Java代码计算经纬度主要是使用GeoDistanceSortBuilder来进行组织筛选条件,并且将组织好的GeoDistanceSortBuilder对象传入sourceBuilder.sort()中得到排序结果。上面结果打印出来的结果如下:
在这里插入图片描述

2.获取距离并筛选距离

在得到了周边景点距离后,你和你女朋友发现有些很远有些很近,想再筛选一下距离,比如一公里内的景点有哪些,使用ES同样可以完成这样的功能。
这里主要是在上面查询的基础上使用post_filter做后置过滤,在post_filter中使用distance关键字来决定筛选的距离。

{
    "sort":[
        {
            "_geo_distance":{
                "location":[
                    {
                        "lat":31.243453,
                        "lon":121.497204
                    }
                ],
                "unit":"m",
                "order":"asc"
            }
        }
    ],
    "post_filter":{
        "geo_distance":{
            "distance":"1km",
            "location":{
                "lat":31.243453,
                "lon":121.497204
            }
        }
    }
}

查询筛选结果如下:

{
    "total":{
        "value":2,
        "relation":"eq"
    },
    "max_score":null,
    "hits":[
        {
            "_index":"location_test",
            "_type":"_doc",
            "_id":"Umg6oHQB29lS14JF6ljQ",
            "_score":null,
            "_source":{
                "locationName":"上海奇思妙想减压馆",
                "location":{
                    "lat":31.24249,
                    "lon":121.490283
                }
            },
            "sort":[
                666.6306788021509
            ]
        },
        {
            "_index":"location_test",
            "_type":"_doc",
            "_id":"W2g7oHQB29lS14JFE1jZ",
            "_score":null,
            "_source":{
                "locationName":"震旦博物馆",
                "location":{
                    "lat":31.240324,
                    "lon":121.506166
                }
            },
            "sort":[
                920.3164557098329
            ]
        }
    ]
}

可以看到在你们一公里以内的有两个景点可以去参观,于是你和女朋友就可以愉快的去参观了。使用Java来查询查询部分和上面大致相同,筛选使用GeoDistanceQueryBuilder构建距离筛选条件,并传入sourceBuilder的postFilter()方法中使用,相关Java代码如下:

public void searchFilter() throws IOException {
    double lat = 31.243453;
    double lon = 121.497204;
    SearchRequest request = new SearchRequest("location_test");
    SearchSourceBuilder sourceBuilder = new SearchSourceBuilder();
    // 使用GeoDistanceSortBuilder构建查询条件
    GeoDistanceSortBuilder distanceSortBuilder = new GeoDistanceSortBuilder("location", lat, lon);
    // 设置单位为米,升序排序
    distanceSortBuilder.unit(DistanceUnit.METERS).order(SortOrder.ASC);
    // 构建筛选条件
    GeoDistanceQueryBuilder distanceQueryBuilder = new GeoDistanceQueryBuilder("location");
    distanceQueryBuilder.distance(1,DistanceUnit.KILOMETERS).point(lat,lon);
    sourceBuilder.sort(distanceSortBuilder).postFilter(distanceQueryBuilder);
    request.source(sourceBuilder);
    // 获取结果
    SearchResponse response = restHighLevelClient.search(request, RequestOptions.DEFAULT);
    SearchHit[] hitResult = response.getHits().getHits();
    System.out.println("一公里范围内有以下景点:");
    for (int i = 0; i < hitResult.length; i++) {
        BigDecimal distance = new BigDecimal(hitResult[i].getSortValues()[0].toString())
                .setScale(2, BigDecimal.ROUND_HALF_UP);
        System.out.println(hitResult[i].getSourceAsMap().get("locationName").toString()+",据你"+ distance +"米");
    }
}

上面代码打印的结果如下:
在这里插入图片描述

总结

使用ElasticSearch可以很好的完成经纬度的距离计算和查询筛选,但是使用ElasticSearch计算的距离是直线距离,如果要计算路线距离计算需要使用地图相关API进行计算了,并且使用ElasticSearch的话在数据增量同步的会稍微麻烦一点。

  • 3
    点赞
  • 15
    收藏
    觉得还不错? 一键收藏
  • 1
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值