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