美文网首页ElasticSearch
ES地理范围查询第一讲:Java操作地理位置信息(geo_poi

ES地理范围查询第一讲:Java操作地理位置信息(geo_poi

作者: 追杀丘比特 | 来源:发表于2018-03-07 10:36 被阅读0次
    - 简要介绍

    我们知道es支持的数据类型是多种多样的,除了我们常见的几种基本数据类型,它也支持记录位置信息的的数据类型。在es中,记录地理位置信息的数据类型有两种,分别为geo_shap和geo_point,下面我针对geo_point类型简要介绍一下
    geo_point支持多种数据传入方式:

    1. 字符串
      位置:lat + "," + lon
    2. 数组
      位置: {"lat": ...,"lon": ...}
    3. 对象
      位置: [lon, lat]

    注意:可能所有人都至少踩过一次这个坑====地理坐标点用字符串形式表示时是纬度在前,经度在后("latitude,longitude"),而数组形式表示时刚好相反,是经度在前,纬度在后([longitude,latitude])。其实,在 Elasticesearch 内部,不管字符串形式还是数组形式,都是纬度在前,经度在后。不过早期为了适配GeoJSON 的格式规范,调整了数组形式的表示方式。这点官网有说明。

    针对geo_point类型一般有如下几种查询需求

    1. distance query
      查找距离中心点范围内的点
    2. distance range query
      查询位于中心点指定range内的点,这个api在新版本的es中去掉了,因此这里不做过多解释
    3. bounding query
      查找指定点组成的矩形范围内的点
    4. 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"
        }
    ]
    

    其他两种查询同上。

    相关文章

      网友评论

        本文标题:ES地理范围查询第一讲:Java操作地理位置信息(geo_poi

        本文链接:https://www.haomeiwen.com/subject/udxjfftx.html