最近有个需求是根据经纬度查询地图信息,刚开始有点懵懂,后来了解到es里有个geo-point的类型,这个是存放地理坐标点的信息也就是经纬度的信息,然后通过点位查询距离范围内也就是一个圆内的所有数据。
设置数据格式
地理坐标点(geo-point) 是指地球表面可以用经纬度描述的一个点。地理坐标点可以用来计算两个坐标位置间的距离,或者判断一个点是否在一个区域中。地理坐标点不能被动态映射(dynamic mapping)自动检测,而是需要显式声明对应字段类型为 geo_point ,例子中的es mapping只有location字段
{
"properties": {
"location": {
"type": "geo_point"
}
}
}
半角逗号分割的字符串形式 “lat,lon“
明确以 lat 和 lon 作为属性的对象
数组形式表示 [lon,lat]
需要特别注意的就是纬度在前边经度在后边(latitude,longitude),数组表示形式是经度在前纬度在后([longitude,latitude])
这里作者使用的是作者存的值是"location" : "25.004,102.704"的字符串类型,纬度在前,经度在后
geo_distance 找出指定位置在给定距离内的数据,相当于指定圆心和半径找到圆中点
找出distance范围内的所有点位信息
distance:距离 单位/m
location:坐标点 圆心所在位置
/**
* 查询地图信息
* @author Songsong
* @param latitude 纬度
* @param longitude 经度
* @param distance 距离/单位:m
* @param esIndex es索引
* @param t 返回类型
* @return java.util.List<T>
* @date 2020-11-17 21:27
*/
public <T> List<T> queryMapData(String latitude, String longitude, Integer distance, String esIndex, Class<T> t) {
//筛选坐标指定范围内的数据
GeoDistanceQueryBuilder distanceQueryBuilder =
QueryBuilders.geoDistanceQuery("location")
.point(Double.parseDouble(latitude), Double.parseDouble(longitude))
.distance(distance, DistanceUnit.METERS)
.geoDistance(GeoDistance.PLANE);
SearchRequest searchRequest = new SearchRequest();
//查询条件
SearchSourceBuilder searchSourceBuilder = new SearchSourceBuilder();
BoolQueryBuilder queryBuilder = QueryBuilders.boolQuery()
.filter(distanceQueryBuilder);
searchSourceBuilder.query(queryBuilder);
searchSourceBuilder.from(0);
searchSourceBuilder.size(10000);
//按照距离排序
GeoDistanceSortBuilder sortBuilder = SortBuilders.geoDistanceSort("location",
new GeoPoint(Double.parseDouble(latitude), Double.parseDouble(longitude)))
.order(SortOrder.ASC)
.unit(DistanceUnit.METERS)
.geoDistance(GeoDistance.PLANE);
searchSourceBuilder.sort(sortBuilder);
searchRequest.source(searchSourceBuilder);
searchRequest.indices(esIndex);
List<T> result = new ArrayList<>();
try {
SearchResponse searchResponse = restHighLevelClient.search(searchRequest, RequestOptions.DEFAULT);
SearchHits searchHits = searchResponse.getHits();
SearchHit[] searchHitArray = searchHits.getHits();
for (SearchHit searchHit : searchHitArray) {
//下划线转驼峰
result.add(CamelAndUnderlineUtil.underlineToCamel(searchHit.getSourceAsString(), t));
}
} catch (IOException e) {
log.error("Method:queryMapData,es查询异常:{}", e.getMessage());
}
return result;
}
CamelAndUnderlineUtil.underlineToCamel这个方法参考作者之前写的
java实用型-驼峰下划线互转
以上java代码是根据经纬度查询distance范围内的数据,返回结果是入参中的对象list,结构可以自己定义。
以上最大查询条数为1w条,并且是按照距离中心点升序后的数据,如果想返回更多的数据怎么办,这时候我们可以考虑用scroll查询,每次根据最新的scroll_id去查询,然后封装到一起再返回,这样可以达到返回>1w的数据的效果。
一、什么是游标查询(Scroll)
顾名思义,相当于用一把游标标记查询的位置.
二.、为什么要使用游标查询
在默认情况下,ES查询每次返回的数量最多只有1W条,且只能是前1W条.
这意味着,在不修改配置的情况下,想通过分页的方式(如下)拿到1W条之后的数据是做不到的,所以自然就有了游标查询。
三、如何使用游标查询
DSL的用法:
复制代码
GET 索引/类型/_search?scroll=1m
{
“size”: 10000,
“query”: {
“match_all”: {}
}
}
复制代码
1m表示:过期时间1分钟
查询结果的第一行会有:
"_scroll_id": "DnF1ZXJ5VGhlbkZldGNoBAAAAAAABO-dFmRFSU9NM1VNU2JxNG9UUlNnSmpXMVEAAAAAAL7J_hYxT0dJOVJVMVFxU2I0N2xCR2IyVzJnAAAAAAC- SmQWWk1aN0sxUmRSQmFNS3EwVFh0R0luUQAAAAAAvkplFlpNWjdLMVJkUkJhTUtxMFRYdEdJblE=",
这个_scroll_id就相当于书签,之后的查询带着这个书签,就能根据size不断拿到之后的数据,但是前提是在过期时间之内
之后的查询DSL:
GET _search/scroll
{
"scroll":"1m",
"scroll_id":"DnF1ZXJ5VGhlbkZldGNoBAAAAAAABPP1FmRFSU9NM1VNU2JxNG9UUlNnSmpXMVEAAAAAAL7OTxYxT0dJOVJVMVFxU2I0N2xCR2IyVzJnAAAAAAC-j70WVVlOZkxQRzJRLXlMRlVMbEQtalBfUQAAAAAAyWm-Fk9HdGx1b3VsUXRLZHV4c1E1OExja0E="
}
将获取的scroll_id作为条件继续查询即可,这里不需要再指定索引和类型,因为scroll_id的唯一性.
在过期时间内,之后的查询的scroll_id是不变的.
示例:比如返回5w条数据,每次查询5000条,也就是说总共需要查10次,不多说,上java代码:
/**
* 查询地图信息
* @author Songsong
* @param latitude 纬度
* @param longitude 经度
* @param distance 距离/单位:m
* @param esIndex es索引
* @param t 返回类型
* @return java.util.List<T>
* @date 2020-11-17 21:27
*/
public <T> List<T> queryMapData(String latitude, String longitude, Integer distance, String esIndex, Class<T> t) {
//筛选坐标指定范围内的数据
GeoDistanceQueryBuilder distanceQueryBuilder =
QueryBuilders.geoDistanceQuery("location")
.point(Double.parseDouble(latitude), Double.parseDouble(longitude))
.distance(distance, DistanceUnit.METERS)
.geoDistance(GeoDistance.PLANE);
SearchRequest searchRequest = new SearchRequest();
//查询条件
SearchSourceBuilder searchSourceBuilder = new SearchSourceBuilder();
BoolQueryBuilder queryBuilder = QueryBuilders.boolQuery()
.filter(distanceQueryBuilder);
searchSourceBuilder.query(queryBuilder);
searchSourceBuilder.from(0);
searchSourceBuilder.size(5000);
//scoll滚动查询
Scroll scroll = new Scroll((TimeValue.timeValueMinutes(1L)));
//按照距离排序
GeoDistanceSortBuilder sortBuilder = SortBuilders.geoDistanceSort("location",
new GeoPoint(Double.parseDouble(latitude), Double.parseDouble(longitude)))
.order(SortOrder.ASC)
.unit(DistanceUnit.METERS)
.geoDistance(GeoDistance.PLANE);
searchSourceBuilder.sort(sortBuilder);
searchRequest.source(searchSourceBuilder);
searchRequest.indices(esIndex);
searchRequest.scroll(scroll);
List<T> result = new ArrayList<>();
try {
SearchResponse searchResponse = restHighLevelClient.search(searchRequest, RequestOptions.DEFAULT);
SearchHits searchHits = searchResponse.getHits();
SearchHit[] searchHitArray = searchHits.getHits();
String scrollId = searchResponse.getScrollId();
//限制返回最大数量
while (null != searchHitArray && searchHitArray.length > 0 && result.size() < 50000) {
for (SearchHit searchHit : searchHitArray) {
//下划线转驼峰
result.add(CamelAndUnderlineUtil.underlineToCamel(searchHit.getSourceAsString(), t));
}
//构造滚动查询条件
SearchScrollRequest searchScrollRequest = new SearchScrollRequest(scrollId);
searchScrollRequest.scroll(scroll);
searchResponse = restHighLevelClient.scroll(searchScrollRequest, RequestOptions.DEFAULT);
scrollId = searchResponse.getScrollId();
searchHitArray = searchResponse.getHits().getHits();
}
//处理完成后清除滚动
ClearScrollRequest clearScrollRequest = new ClearScrollRequest();
clearScrollRequest.addScrollId(scrollId);
restHighLevelClient.clearScroll(clearScrollRequest, RequestOptions.DEFAULT);
} catch (IOException e) {
log.error("Method:queryMapData,es查询异常:{}", e.getMessage());
}
return result;
}
CamelAndUnderlineUtil.underlineToCamel这个方法参考作者之前写的
java实用型-驼峰下划线互转
以上就是根据最新的scroll_id查询下一页的数据,此查询有个弊端,就是不能查询指定的页数,每次只能查询下一页的数据,所以具体得看使用场景。
所有数据获取完毕之后,需要手动清理掉 scroll_id 。虽然es 会有自动清理机制,但是 srcoll_id 的存在会耗费大量的资源来保存一份当前查询结果集映像,并且会占用文件描述符。所以用完之后要及时清理。所以就有了这段代码:
//处理完成后清除滚动
ClearScrollRequest clearScrollRequest = new ClearScrollRequest();
clearScrollRequest.addScrollId(scrollId);
restHighLevelClient.clearScroll(clearScrollRequest, RequestOptions.DEFAULT);
进行手动清理scroll_id的操作。