- 简要介绍
我们知道es支持的数据类型是多种多样的,除了我们常见的几种基本数据类型,它也支持记录位置信息的的数据类型。在es中,记录地理位置信息的数据类型有两种,分别为geo_shap和geo_point,下面我针对geo_point类型简要介绍一下
geo_point支持多种数据传入方式:
- 字符串
位置:lat + "," + lon
- 数组
位置: {"lat": ...,"lon": ...}
- 对象
位置: [lon, lat]
注意:可能所有人都至少踩过一次这个坑====地理坐标点用字符串形式表示时是纬度在前,经度在后("latitude,longitude"),而数组形式表示时刚好相反,是经度在前,纬度在后([longitude,latitude])。其实,在 Elasticesearch 内部,不管字符串形式还是数组形式,都是纬度在前,经度在后。不过早期为了适配GeoJSON 的格式规范,调整了数组形式的表示方式。这点官网有说明。
针对geo_point类型一般有如下几种查询需求
- distance query
查找距离中心点范围内的点 -
distance range query
查询位于中心点指定range内的点,这个api在新版本的es中去掉了,因此这里不做过多解释 - bounding query
查找指定点组成的矩形范围内的点 - polygon query
查找多个点组成的一个多边形中的点
针对不同的查询Java构造的SearchSourceBuilder factory如下:
public class QueryFactory {
/**
* 针对geo_point类型的查找,查询符合多边形内的数据
* @param field
* @param points
* @return
* @throws Exception
*/
public SearchSourceBuilder builtPolygonQuery(String field, List<GeoPoint> points) throws Exception {
if (points == null || points.size() <= 0) {
throw new Exception("bad args of geo points");
}
SearchSourceBuilder srb = new SearchSourceBuilder();
GeoPolygonQueryBuilder qb = QueryBuilders.geoPolygonQuery(field, points);
srb.query(qb);
return srb;
}
/**
* 针对geo_point类型
* 获取在指定矩形框内的数据
* @param field 字段
* @param point1 矩形左上边界
* @param point2 矩形右下边界
* @return
* @throws Exception
*/
public SearchSourceBuilder builtBoundingBoxQuery(String field, GeoPoint point1,GeoPoint point2) throws Exception {
if (point1 == null || point2 == null) {
throw new Exception("bad args of geo points");
}
SearchSourceBuilder srb = new SearchSourceBuilder();
GeoBoundingBoxQueryBuilder qb = QueryBuilders.geoBoundingBoxQuery(field)
.setCorners(point2,point1);
srb.query(qb);
return srb;
}
/**
* 针对geo_point类型
* 查找在给定的中心点确定范围内的数据
* @param field
* @param distance
* @param point
* @return
* @throws Exception
*/
public SearchSourceBuilder builtDistanceQuery( String field,String distance, GeoPoint point) throws Exception {
if (point == null) {
throw new Exception("bad args of geo points");
}
SearchSourceBuilder srb = new SearchSourceBuilder();
QueryBuilder qb = QueryBuilders.geoDistanceQuery(field)
.point(point)
.distance(distance, DistanceUnit.KILOMETERS);
srb.query(qb);
GeoDistanceSortBuilder sort = SortBuilders.geoDistanceSort(field,point)
.order(SortOrder.ASC)
.unit(DistanceUnit.KILOMETERS);
srb.sort(sort);
return srb;
}
}
service如下:
/**
* 查找位于多边形中的位置
* @param index 索引
* @param type 类型
* @param field 索引字段
* @param points 构成多边形的点
*/
public List<Map<String, Object>> searchGeoPolygon(String index, String type, String field, List<GeoPoint> points) throws Exception {
SearchSourceBuilder builder = factory.builtPolygonQuery(field, points);
return search(index, type, builder);
}
/**
* 查询矩形范围内的数据
* @param index index
* @param type type
* @param field 索引字段
* @param bottom_right 右上坐标
* @param top_left 左下坐标
*/
public List<Map<String, Object>> searchGeoBoundingBox(String index, String type, String field, GeoPoint bottom_right,GeoPoint top_left) throws Exception {
SearchSourceBuilder builder = factory.builtBoundingBoxQuery(field,bottom_right,top_left);
return search(index,type, builder);
}
/**
* 查询距离中心点指定的范围内的位置
* @param index index
* @param type type
* @param field 索引字段
* @param distance 距离
* @param point 中心点
* @return
* @throws Exception
*/
public List<Map<String, Object>> searchGeoDistance(String index, String type, String field,String distance, GeoPoint point) throws Exception {
SearchSourceBuilder builder = factory.builtDistanceQuery(field,distance,point);
return search(index, type, builder);
}
/**
* 执行查询
* @param index 索引
* @param type type
* @param builder 查询语句
* @return List<Map>
*/
private List<Map<String, Object>> search(String index, String type, SearchSourceBuilder builder) {
try {
client = oldclient.setupTransportClient();
List<Map<String, Object>> list = new ArrayList<>();
SearchRequestBuilder srb = client.prepareSearch(index);
if (type != null && type.length() != 0){
srb.setTypes(type);
}
srb.setSource(builder);
SearchResponse searchResponse = srb.execute().actionGet();
SearchHits hits = searchResponse.getHits();
long time = searchResponse.getTookInMillis()/1000;
logger.info("query result size:"+hits.totalHits+",spend time:"+time+"s");
for (SearchHit hit : hits) {
Map<String, Object> map = hit.getSource();
//获取distance数据时,获取距离具体值
if (hit.getSortValues().length != 0){
BigDecimal geoDis = new BigDecimal((Double) hit.getSortValues()[0]);
map.put("距离",geoDis.setScale(4, BigDecimal.ROUND_HALF_DOWN)+"km");
}
list.add(map);
logger.info("hits:" + map);
}
return list;
} catch (Exception e) {
logger.error("error!", e);
}
return null;
}
controller层代码如下:
@RestController
@RequestMapping("/geo")
public class GeoDataRestController {
private Logger logger = Logger.getLogger(GeoDataRestController.class);
@Autowired
private EsSearchService service;
@RequestMapping(value = "/distance",method = RequestMethod.POST)
public List<Map<String,Object>> searchGeoWithDistance(@RequestBody RequestBean bean,
@Param("distance") String distance){
Map<String,GeoPoint> map = bean.getPoints();
GeoPoint point = map.get("point");
if (distance == null || point == null){
logger.error("request param error!");
return null;
}
List<Map<String,Object>> result= null;
try {
result = service.searchGeoDistance(bean.getIndex(),bean.getType(),bean.getField(),distance,point);
}catch (Exception e){
logger.error("query error!",e);
}
return result;
}
@RequestMapping(value = "/bounding",method = RequestMethod.POST)
public List<Map<String,Object>> searchGeoWithBoundingBox(@RequestBody RequestBean bean){
Map<String,GeoPoint> map = bean.getPoints();
GeoPoint bottom_point = map.get("bottom_right");
GeoPoint top_left = map.get("top_left");
if (bottom_point == null || top_left == null){
logger.error("request param error!");
return null;
}
List<Map<String,Object>> result= null;
try {
result = service.searchGeoBoundingBox(bean.getIndex(),bean.getType(),bean.getField(),bottom_point,top_left);
}catch (Exception e){
logger.error("query error!",e);
}
return result;
}
@RequestMapping(value = "/coordinate",method = RequestMethod.POST)
public List<Map<String,Object>> searchGeoWithCoordinate(@Param("index") String index,
@Param("type") String type,
@Param("field") String field,
@RequestBody List<GeoPoint> points){
logger.info("data:"+points);
List<Map<String,Object>> result= null;
try {
result = service.searchGeoPolygon(index,type,field,points);
}catch (Exception e){
logger.error("query error!",e);
}
return result;
}
}
[1].如何使用
我们在使用geo_point类型之前,首先应该在创建index时通过mapping显式指定该字段为geo_point类型,如下:
{
"restraunt": {
"mappings": {
"info": {
"properties": {
"位置": {
"type": "geo_point"
},
"房间号": {
"type": "text",
"analyzer": "ik_max_word",
"search_analyzer": "ik_smart"
},
"描述信息": {
"type": "text",
"analyzer": "ik_max_word",
"search_analyzer": "ik_smart"
},
"电话号码": {
"type": "text"
}
}
}
}
}
}
以上我们新建了一个名字为restraunt的index,在它的mapping文件中显式的指定了“位置”字段的type为“geo_point”类型
[2]准备一些数据
在kibana上我们准备的数据如下:
1.{
"_index": "restraunt",
"_type": "info",
"_id": "1-0-0-6",
"_version": 1,
"_score": 1,
"_source": {
"房间号": "1-0-0-6",
"描述信息": "李三吱的房子,依法查封",
"位置": "32.4428148,105.7701854",
"电话号码": "18212345693"
}
}
2.{
"_index": "restraunt",
"_type": "info",
"_id": "1-0-0-5",
"_version": 1,
"_score": 1,
"_source": {
"房间号": "1-0-0-5",
"描述信息": "李三吱的房子,依法查封",
"位置": "31.4673768,104.582626",
"电话号码": "18212345693"
}
}
3.{
"_index": "restraunt",
"_type": "info",
"_id": "1-0-0-1",
"_version": 1,
"_score": 1,
"_source": {
"房间号": "1-0-0-1",
"描述信息": "李三吱的房子,依法查封",
"位置": "31.1120885,104.2664256",
"电话号码": "18212345693"
}
}
4.{
"_index": "restraunt",
"_type": "info",
"_id": "1-0-0-2",
"_version": 1,
"_score": 1,
"_source": {
"房间号": "1-0-0-2",
"描述信息": "李三吱的房子,依法查封",
"位置": "30.6587488,103.9354626",
"电话号码": "18212345693"
}
}
5.{
"_index": "restraunt",
"_type": "info",
"_id": "1-0-0-3",
"_version": 1,
"_score": 1,
"_source": {
"房间号": "1-0-0-3",
"描述信息": "李三吱的房子,依法查封",
"位置": "30.1260332,104.6028895",
"电话号码": "18212345693"
}
}
6.{
"_index": "restraunt",
"_type": "info",
"_id": "1-0-0-4",
"_version": 1,
"_score": 1,
"_source": {
"房间号": "1-0-0-4",
"描述信息": "李三吱的房子,依法查封",
"位置": "30.5128458,105.5106333",
"电话号码": "18212345693"
}
}
7.{
"_index": "restraunt",
"_type": "info",
"_id": "1-0-0-7",
"_version": 1,
"_score": 1,
"_source": {
"房间号": "1-0-0-7",
"描述信息": "李三吱的房子,依法查封",
"位置": "30.0013669,102.9725838",
"电话号码": "18212345693"
}
}
以上我们准备了7条数据,接下来我们需要执行查询,看看我们的接口是否符合我们需求。
[3]查询
- 查询distance
post : http://localhost:8081/geo/distance?distance=300 //查询距离points 300km之间的点
body:
{
"index": "restraunt",
"field": "位置",
"points": {
"point": {
"lat": 32.4428148,
"lon": 105.7701854
}
}
}
response result:
[
{
"房间号": "1-0-0-6",
"描述信息": "李三吱的房子,依法查封",
"位置": "32.4428148,105.7701854",
"电话号码": "18212345693",
"距离": "0.0000km"
},
{
"房间号": "1-0-0-5",
"描述信息": "李三吱的房子,依法查封",
"位置": "31.4673768,104.582626",
"电话号码": "18212345693",
"距离": "155.9380km"
},
{
"房间号": "1-0-0-1",
"描述信息": "李三吱的房子,依法查封",
"位置": "31.1120885,104.2664256",
"电话号码": "18212345693",
"距离": "205.1788km"
},
{
"房间号": "1-0-0-4",
"描述信息": "李三吱的房子,依法查封",
"位置": "30.5128458,105.5106333",
"电话号码": "18212345693",
"距离": "216.0097km"
},
{
"房间号": "1-0-0-2",
"描述信息": "李三吱的房子,依法查封",
"位置": "30.6587488,103.9354626",
"电话号码": "18212345693",
"距离": "263.7685km"
},
{
"房间号": "1-0-0-3",
"描述信息": "李三吱的房子,依法查封",
"位置": "30.1260332,104.6028895",
"电话号码": "18212345693",
"距离": "280.4747km"
}
]
其他两种查询同上。