初学者入门:springboot整合elasticsearch实现空间检索实例(根据经纬度坐标查询)

        es的空间检索提供了通过空间距离和位置关系进行检索的能力。将es整合到springboot中,可以轻松实现空间检索模块的编写与使用。

        Springboot整合es的具体流程此处不再赘述,若有需要。可以参考如下文档:

        从零开始:SpringBoot整合ElasticSearch实现简单增删改查

一. 使用 postman 新增实例

        具体使用中,可以自己编写新增接口来向es中批量添加实例。由于上一篇文档中已经详细说明了如何编写增删改查的接口,此处不再重复编写新增接口,而是直接用postman向es中增加了若干条演示实例。

     1. 新建 es 映射

        本文档使用 city 对象来演示空间检索功能。city 对象具有 idname 和坐标 locationPoint 的基本属性。如何使用postman在es中新建映射请查看上面提到的文档。

        city映射:

{
    "mappings": {
        "city": {
            "properties": {
                "id": {
                    "type": "text",
                    "fields": {
                        "keyword": {
                            "type":"keyword",
                            "ignore_above":256
                        }
                    }
                },
                "name": {
                    "type": "text",
                    "fields": {
                        "keyword": {
                            "type":"keyword",
                            "ignore_above":256
                        }
                    }
                },
                "locationPoint": {
                    "type": "geo_point",
                    "fields": {
                        "keyword": {
                            "type":"keyword",
                            "ignore_above":256
                        }
                    }
                }
            }
        }
    }
}

    2. 向es中添加数据

      实例如下:

{
    "took": 1,
    "timed_out": false,
    "_shards": {
        "total": 1,
        "successful": 1,
        "skipped": 0,
        "failed": 0
    },
    "hits": {
        "total": {
            "value": 4,
            "relation": "eq"
        },
        "max_score": 1.0,
        "hits": [
            {
                "_index": "place",
                "_type": "city",
                "_id": "111",
                "_score": 1.0,
                "_source": {
                    "id": "111",
                    "name": "A1",
                    "locationPoint": {
                        "lat": 41.12,
                        "lon": -71.34
                    }
                }
            },
            {
                "_index": "place",
                "_type": "city",
                "_id": "222",
                "_score": 1.0,
                "_source": {
                    "id": "222",
                    "name": "A2",
                    "locationPoint": {
                        "lat": 40.12,
                        "lon": -70.34
                    }
                }
            },
            {
                "_index": "place",
                "_type": "city",
                "_id": "333",
                "_score": 1.0,
                "_source": {
                    "id": "333",
                    "name": "A3",
                    "locationPoint": {
                        "lat": 38.12,
                        "lon": -76.34
                    }
                }
            },
            {
                "_index": "place",
                "_type": "city",
                "_id": "444",
                "_score": 1.0,
                "_source": {
                    "id": "555",
                    "name": "B2",
                    "locationPoint": {
                        "lat": 40,
                        "lon": -74
                    }
                }
            }
        ]
    }
}

二. 在 springboot 中编写相关接口

     1. 新建 city 类

        City:

package com.example.elasticsearchdemo.bean;

import org.springframework.data.annotation.Id;

public class City {

    @Id
    private String id;

    //城市名
    private String name;

    //经纬度
    private EsGeoPoint locationPoint;

    public String getId() {
        return id;
    }

    public void setId(String id) {
        this.id = id;
    }

    public String getName() {
        return name;
    }

    public void setName(String name) {
        this.name = name;
    }

    public EsGeoPoint getLocationPoint() {
        return locationPoint;
    }

    public void setLocationPoint(EsGeoPoint locationPoint) {
        this.locationPoint = locationPoint;
    }

    @Override
    public String toString() {
        return "City{" +
                "id='" + id + '\'' +
                ", name='" + name + '\'' +
                ", locationPoint=" + locationPoint +
                '}';
    }
}

        EsGeoPoint: 

package com.example.elasticsearchdemo.bean;

public class EsGeoPoint {

    //纬度
    private double lat;

    //经度
    private double lon;

    public double getLat() {
        return lat;
    }

    public void setLat(double lat) {
        this.lat = lat;
    }

    public double getLon() {
        return lon;
    }

    public void setLon(double lon) {
        this.lon = lon;
    }

    @Override
    public String toString() {
        return "EsGeoPoint{" +
                "lat=" + lat +
                ", lon=" + lon +
                '}';
    }
}

     2. 新建 GeoSearch 查询类

        GeoSearch:

package com.example.elasticsearchdemo.bean;

import org.elasticsearch.common.geo.GeoPoint;

import java.util.List;

public class GeoSearch {

    private String type;
    // 类型{Polygon: 多边形; Circle: 圆; bounding: 矩形}

    private List<GeoPoint> points;
    // 点集

    private String radius;
    // 半径

    private GeoPoint origin;
    // 原点

    public String getType() {
        return type;
    }

    public void setType(String type) {
        this.type = type;
    }

    public List<GeoPoint> getPoints() {
        return points;
    }

    public void setPoints(List<GeoPoint> points) {
        this.points = points;
    }

    public String getRadius() {
        return radius;
    }

    public void setRadius(String radius) {
        this.radius = radius;
    }

    public GeoPoint getOrigin() {
        return origin;
    }

    public void setOrigin(GeoPoint origin) {
        this.origin = origin;
    }

    @Override
    public String toString() {
        return "GeoSearch{" +
                "type='" + type + '\'' +
                ", points=" + points +
                ", radius='" + radius + '\'' +
                ", origin=" + origin +
                '}';
    }
}

     3. Controller 层

        GeoSearchController:

package com.example.elasticsearchdemo.controller;

import com.alibaba.fastjson.JSONObject;
import com.example.elasticsearchdemo.bean.GeoSearch;
import com.example.elasticsearchdemo.service.GeoSearchService;
import org.springframework.web.bind.annotation.*;

import javax.annotation.Resource;

@RestController
@RequestMapping("/geoSearch")
@CrossOrigin(value = "*",  maxAge = 3600)
public class GeoSearchController {

    @Resource
    private GeoSearchService service;

    @RequestMapping(value = "/search",method = RequestMethod.POST)
    public JSONObject geoSearch(@RequestBody GeoSearch search) throws Exception{
        JSONObject output = new JSONObject();
        JSONObject result = service.geoSearch(search);
        if (result.get("msg") == "succeed") {
            output.put("msg" ,"succeed");
            output.put("data", result.get("data"));
        } else {
            output.put("msg", "failed");
            output.put("data", result.get("data"));
        }
        return output;
    }

}

     2. Service 层

        GeoSearchService:

package com.example.elasticsearchdemo.service;

import com.alibaba.fastjson.JSONObject;
import com.example.elasticsearchdemo.bean.City;
import com.example.elasticsearchdemo.bean.GeoSearch;
import com.example.elasticsearchdemo.dao.common.GeoSearchDao;
import org.elasticsearch.common.geo.GeoPoint;
import org.springframework.stereotype.Service;

import javax.annotation.Resource;
import java.util.ArrayList;
import java.util.List;

@Service
public class GeoSearchService {

    @Resource
    private GeoSearchDao geoSearchDao;

    public JSONObject geoSearch(GeoSearch search) throws Exception{
        JSONObject out = new JSONObject();
        String type = search.getType();
        List result = new ArrayList<>();
        
        //type为空则返回所有数据
        if ((type == null) || (type == "")) {
            List list = geoSearchDao.getAll();
            out.put("msg", "succeed");
            out.put("data", list);
            return out;
        } 
        
        //根据type判断采用何种查询
        else if (type.equals("Polygon") || type == "Polygon") {
            List<GeoPoint> list = search.getPoints();
            result = geoSearchDao.searchGeoPolygon(list);
        }
        
        else if (type.equals("Circle") || type == "Circle") {
            String distance = search.getRadius();
            GeoPoint point = search.getOrigin();
            result = geoSearchDao.searchGeoDistance(distance, point);
        }
        
        else if (type.equals("bounding") ||type == "bounding") {
            if (search.getPoints().size() >= 2) {
                GeoPoint point1 = search.getPoints().get(0);
                GeoPoint point2 = search.getPoints().get(1);
                result = geoSearchDao.searchGeoBoundingBox(point1, point2);
            } else {
                out.put("msg", "failed");
                out.put("data", "Points error");
                return out;
            }
        } else {
            out.put("msg", "failed");
            out.put("data", "Type error");
            return out;
        }
        out.put("msg", "succeed");
        out.put("data", result);
        return out;
    }

}

     3. Dao 层

        GeoSearchDao:

package com.example.elasticsearchdemo.dao.common;

import com.alibaba.fastjson.JSON;
import com.alibaba.fastjson.JSONObject;
import org.elasticsearch.action.search.SearchRequest;
import org.elasticsearch.action.search.SearchResponse;
import org.elasticsearch.client.RequestOptions;
import org.elasticsearch.client.RestHighLevelClient;
import org.elasticsearch.common.geo.GeoPoint;
import org.elasticsearch.index.query.BoolQueryBuilder;
import org.elasticsearch.index.query.MatchAllQueryBuilder;
import org.elasticsearch.index.query.QueryBuilders;
import org.elasticsearch.search.SearchHit;
import org.elasticsearch.search.builder.SearchSourceBuilder;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.stereotype.Repository;

import javax.annotation.Resource;
import java.io.IOException;
import java.util.ArrayList;
import java.util.List;

@Repository
public class GeoSearchDao {

    @Resource
    private RestHighLevelClient client;
    @Value("${elasticsearch.place.index}")
    private String cityIndex;
    @Value("${elasticsearch.city.type}")
    private String cityType;

    @Resource
    private Factory factory;

    /**查询所有*/
    public List getAll() throws IOException {
        SearchSourceBuilder searchSourceBuilder = new SearchSourceBuilder();
        BoolQueryBuilder boolQueryBuilder = QueryBuilders.boolQuery();
        MatchAllQueryBuilder matchAllQueryBuilder = QueryBuilders.matchAllQuery();
        boolQueryBuilder.should(matchAllQueryBuilder);
        searchSourceBuilder.query(boolQueryBuilder);
        SearchRequest searchRequest = new SearchRequest();
        searchRequest.indices(cityIndex);
        searchRequest.types(cityType);
        searchRequest.source(searchSourceBuilder);
        SearchResponse searchResponse = client.search(searchRequest, RequestOptions.DEFAULT);
        List list = new ArrayList();
        for (SearchHit hit: searchResponse.getHits()) {
            String s = JSON.toJSONString(hit);
            JSONObject o = JSON.parseObject(s);
            list.add(o.getJSONObject("sourceAsMap"));
        }
        return list;
    }

    /**
     * 查询多边形中的位置
     * @param points
     * @return
     * @throws Exception
     */
    public List searchGeoPolygon(List<GeoPoint> points) throws Exception {
        SearchSourceBuilder searchSourceBuilder = new SearchSourceBuilder();
        BoolQueryBuilder boolQueryBuilder = factory.builtPolygonQuery("locationPoint",points);
        return search(searchSourceBuilder, boolQueryBuilder);
    }

    /**
     * 查询矩形范围内位置
     * @param point1
     * @param point2
     * @return
     * @throws Exception
     */
    public List searchGeoBoundingBox(GeoPoint point1, GeoPoint point2) throws Exception {
        SearchSourceBuilder searchSourceBuilder = new SearchSourceBuilder();
        BoolQueryBuilder boolQueryBuilder = factory.builderBoundingBox("locationPoint", point1, point2);
        return search(searchSourceBuilder, boolQueryBuilder);
    }

    /**
     * 查询圆内位置
     * @param distance
     * @param point
     * @return
     * @throws Exception
     */
    public List searchGeoDistance(String distance, GeoPoint point) throws Exception {
        SearchSourceBuilder searchSourceBuilder = new SearchSourceBuilder();
        BoolQueryBuilder boolQueryBuilder = factory.builderDistance("locationPoint", distance, point);
        return search(searchSourceBuilder, boolQueryBuilder);
    }

    public List search(SearchSourceBuilder searchSourceBuilder, BoolQueryBuilder boolQueryBuilder) throws Exception {
        searchSourceBuilder.query(boolQueryBuilder);
        SearchRequest searchRequest = new SearchRequest();
        searchRequest.indices(cityIndex);
        searchRequest.types(cityType);
        searchRequest.source(searchSourceBuilder);
        SearchResponse searchResponse = client.search(searchRequest, RequestOptions.DEFAULT);
        List list = new ArrayList();
        for (SearchHit hit: searchResponse.getHits()) {
            String s = JSON.toJSONString(hit);
            System.out.println(s);
            JSONObject o = JSON.parseObject(s);
            list.add(o.getJSONObject("sourceAsMap"));
        }
        return list;
    }


}

        Factory:

package com.example.elasticsearchdemo.dao.common;

import org.elasticsearch.common.geo.GeoPoint;
import org.elasticsearch.common.unit.DistanceUnit;
import org.elasticsearch.index.query.BoolQueryBuilder;
import org.elasticsearch.index.query.QueryBuilder;
import org.elasticsearch.index.query.QueryBuilders;
import org.springframework.stereotype.Repository;

import java.util.List;

@Repository

public class Factory {

    /**
     * 查询多边形内数据
     * @param field
     * @param points
     * @return
     * @throws Exception
     */
    public BoolQueryBuilder builtPolygonQuery(String field, List<GeoPoint> points) throws Exception {
        if (points == null ||points.size() <=0) {
            throw new Exception("bad args of geo points");
        }
        BoolQueryBuilder boolQueryBuilder = QueryBuilders.boolQuery();
        QueryBuilder matchQueryBuilder = QueryBuilders.geoPolygonQuery(field, points);
        boolQueryBuilder.should(matchQueryBuilder);
        return boolQueryBuilder;
    }

    /**
     * 查询矩形框内数据
     * @param field
     * @param point1
     * @param point2
     * @return
     * @throws Exception
     */
    public BoolQueryBuilder builderBoundingBox(String field, GeoPoint point1, GeoPoint point2) throws Exception {
        if (point1 == null ||point2 == null) {
            throw new Exception("bad args of geo points");
        }
        BoolQueryBuilder boolQueryBuilder = QueryBuilders.boolQuery();
        QueryBuilder matchQueryBuilder = QueryBuilders.geoBoundingBoxQuery(field).setCorners(point1, point2);
        boolQueryBuilder.should(matchQueryBuilder);
        return boolQueryBuilder;
    }

    /**
     * 查询圆内数据
     * @param field
     * @param distance
     * @param point
     * @return
     * @throws Exception
     */
    public BoolQueryBuilder builderDistance(String field, String distance, GeoPoint point) throws Exception {
        if (point == null) {
            throw new Exception("bad args of geo points");
        }
        BoolQueryBuilder boolQueryBuilder = QueryBuilders.boolQuery();
        QueryBuilder matchQueryBuilder = QueryBuilders.geoDistanceQuery(field).point(point).distance(distance, DistanceUnit.METERS);
        boolQueryBuilder.should(matchQueryBuilder);
        return boolQueryBuilder;
    }

}

三. 使用 postman 测试空间检索接口

        以 springboot 端口为 9650 为例,实际以自己配置中端口为准。

     1. 返回所有

        当 type 传入为空时,默认返回所有数据。

        POST  http://localhost:9650/geoSearch/search

        传参:

{
    "type": ""
}

        返回值:

{
    "msg": "succeed",
    "data": [
        {
            "name": "A1",
            "id": "111",
            "locationPoint": {
                "lon": -71.34,
                "lat": 41.12
            }
        },
        {
            "name": "A2",
            "id": "222",
            "locationPoint": {
                "lon": -70.34,
                "lat": 40.12
            }
        },
        {
            "name": "A3",
            "id": "333",
            "locationPoint": {
                "lon": -76.34,
                "lat": 38.12
            }
        },
        {
            "name": "B2",
            "id": "555",
            "locationPoint": {
                "lon": -74,
                "lat": 40
            }
        }
    ]
}

     2. 返回处于指定圆形范围中的数据

        传入参数中 radius 为半径(不添加单位默认为m,可指定为km),origin 为圆心坐标。

        POST  http://localhost:9650/geoSearch/search

        传参:

{
    "type": "Circle",
    "radius": "200km",
    "origin": {
        "lat": 41,
        "lon": -71
    }
}

        返回值:

{
    "msg": "succeed",
    "data": [
        {
            "name": "A1",
            "id": "111",
            "locationPoint": {
                "lon": -71.34,
                "lat": 41.12
            }
        },
        {
            "name": "A2",
            "id": "222",
            "locationPoint": {
                "lon": -70.34,
                "lat": 40.12
            }
        }
    ]
}

     3. 返回处于指定矩形范围中的数据

        传入参数中 points 为矩形左上角和右下角的坐标集。

        POST  http://localhost:9650/geoSearch/search

        传参:

{
    "type": "Bounding",
    "points": [
        {
            "lat": "42",
            "lon": "-72"
        },
        {
            "lat": "40",
            "lon": "-71"
        }
    ]
}

        返回值:

{
    "msg": "succeed",
    "data": [
        {
            "name": "A1",
            "id": "111",
            "locationPoint": {
                "lon": -71.34,
                "lat": 41.12
            }
        }
    ]
}

     3. 返回处于指定多边形范围中的数据

        传入参数中 points 为构成多边形点的坐标集。

        POST  http://localhost:9650/geoSearch/search

        传参(以三角形为例):

{
    "type": "Polygon",
    "points": [
        {
            "lat": "42",
            "lon": "-72"
        },
        {
            "lat": "35",
            "lon": "-72"
        },
        {
            "lat": "40",
            "lon": "-60"
        }
    ]
}

        返回值:

{
    "msg": "succeed",
    "data": [
        {
            "name": "A1",
            "id": "111",
            "locationPoint": {
                "lon": -71.34,
                "lat": 41.12
            }
        },
        {
            "name": "A2",
            "id": "222",
            "locationPoint": {
                "lon": -70.34,
                "lat": 40.12
            }
        }
    ]
}

以上即为 springboot 整合 es 实现空间检索的简单实现方法。

  • 0
    点赞
  • 9
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值