ElasticSearch地理位置查询与分组

一、 使用场景
根据用户当前所在的地理位置坐标,按商品关键字查询出附近店铺的相关商品,并按店铺位置远近将搜索结果排序。
二、 场景说明
1、 按商品关键字搜索,比如关键字为“牛奶”,那么需要搜索出附近店铺发布的带有“牛奶”关键字的商品。
2、 商品不会带有位置信息,但是商品所属的店铺是有位置信息的,因此要将店铺的位置信息存放进商品的ES索引中。
三、 具体实现
1、 ES索引和Mapping的创建
地理坐标点不能被动态映射(dynamic mapping)自动检测,而是需要显式声明对应字段类型为geo-point,如下:
在这里插入图片描述
创建索引和mapping具体代码如下(以下代码为单元测试类,可根据业务需要自定义修改):

public class CreateIndex {
    /** 商品索引名称 */
    private static final String INDEX_NAME = "goods_index";
    /** ES  cluster-name*/
    private static final String CLUSTER_NAME = "es_cluster";
    /** ES  cluster-nodes*/
    private static final String CLUSTER_NODES = "127.0.0.1:9300";
    /** ES  用户密码*/
    private static final String USER_PASSWORD = "";
    
    @Test
    public void create() {
        EsTemplateBuilder esTemplateBuilder;
        //如果用户验证
        if(!StringUtil.isEmpty(USER_PASSWORD)){
            esTemplateBuilder = new     DefaultEsTemplateBuilder().setClusterName(CLUSTER_NAME).setClusterNodes(CLUSTER_NODES).setUserPass(USER_PASSWORD);
        }else{
            esTemplateBuilder = new DefaultEsTemplateBuilder().setClusterName(CLUSTER_NAME).setClusterNodes(CLUSTER_NODES);
        }
        ElasticsearchTemplate esTemplate = esTemplateBuilder.build();
        
        //商品索引名称
        String goodsIndexName = INDEX_NAME + "_" + EsSettings.GOODS_INDEX_NAME;
        //先删除商品索引,再创建
        esTemplate.deleteIndex(goodsIndexName);
        esTemplate.createIndex(goodsIndexName);
        //获取商品索引mapping
        Map goodsMapping = createGoodsMapping();
        //创建商品索引mapping
        esTemplate.putMapping(goodsIndexName, EsSettings.GOODS_TYPE_NAME, goodsMapping);
    }
    
    /**
     * 创建商品索引mapping
     * @return
     */
    private Map createGoodsMapping() {
        Map goodsMap = new HashMap();
        goodsMap.put("goodsId", new MyMap().put("type", "long").getMap());
        goodsMap.put("goodsName", new MyMap().put("type", "text").put("analyzer", "ik_max_word").getMap());
        goodsMap.put("sellerId", new MyMap().put("type", "integer").getMap());
        goodsMap.put("location", new MyMap().put("type", "geo_point").getMap());
        //其它字段略...
        return new MyMap().put("properties", goodsMap).getMap();
    }
}
2、 创建商品索引内容

首先可将要放入ES中的商品信息进行封装,如下:

@Document(indexName = "goods_index_"+  EsSettings.GOODS_INDEX_NAME, type = EsSettings.GOODS_TYPE_NAME)
public class GoodsIndex {
    @Id
    private Integer goodsId;
    @Field(type = FieldType.text, analyzer = "ik_max_word")
    private String goodsName;
    @Field(type = FieldType.Integer)
    private Integer sellerId;
    @GeoPointField
    private GeoPoint location;
    //其它字段、get set等内容略...
}

初始化商品索引内容并放入ES中,具体代码如下:

@Service
public class GoodsIndexManagerImpl implements GoodsIndexManager {
    @Autowired
    protected ElasticsearchTemplate elasticsearchOperations;
    
    @Override
    public void addIndex(Map goods) {
        GoodsIndex goodsIndex = new GoodsIndex();
        goodsIndex.setGoodsId(StringUtil.toInt(goods.get("goods_id").toString(), 0));
goodsIndex.setGoodsName(goods.get("goods_name").toString());
goodsIndex.setSellerId(StringUtil.toInt(goods.get("seller_id").toString(), 0));
        //获取店铺所在位置纬度
        double latitude = goods.get("latitude") == null ? 0d :     StringUtil.toDouble(goods.get("latitude").toString(), 0d);
        //获取店铺所在位置经度
        double longitude = goods.get("longitude") == null ? 0d : StringUtil.toDouble(goods.get("longitude").toString(), 0d);
        //设置店铺所在位置经纬度
        GeoPoint location = new GeoPoint(latitude, longitude);
        goodsIndex.setLocation(location);
        
        //索引名字
        String indexName = "goods_index_" + EsSettings.GOODS_INDEX_NAME;
        IndexQuery indexQuery = new IndexQuery();
        indexQuery.setIndexName(indexName);
        indexQuery.setType(EsSettings.GOODS_TYPE_NAME);
        indexQuery.setId(goodsIndex.getGoodsId().toString());
        indexQuery.setObject(goodsIndex);
        elasticsearchOperations.index(indexQuery);
    }
}

3、 搜索商品
@Service
public class GoodsSearchManagerImpl implements GoodsSearchManager {
    /** 查询范围默认20千米内 */
    private static final double SEARCH_DISTANCE = 20.00;
    
    @Override
    public SearchResult searchAllResult(GoodsSearchParams goodsSearch) {
        //获取搜索页数
        Integer pageNo = goodsSearch.getPageNo();
        //获取搜索每页数量
        Integer pageSize = goodsSearch.getPageSize();
        //返回结果
        SearchResult searchResult = new SearchResult();

        SearchRequestBuilder searchRequestBuilder;
        try {
            //创建查询条件
            searchRequestBuilder = this.createGoodsQuery(goodsSearch);

            //设置分页信息
            searchRequestBuilder.setFrom((pageNo - 1) * pageSize).setSize(pageSize);
            //设置是否按查询匹配度排序
            searchRequestBuilder.setExplain(true);
            //获取查询结果
            SearchResponse response =     searchRequestBuilder.execute().actionGet();
            SearchHits searchHits = response.getHits();

            //新建搜索到的商品结果集合
            List<GoodsSearchResult> resultList = new ArrayList<>();
            //店铺到当前坐标位置的距离
            Map<Integer, Double> shopDistanceMap = new HashMap<>();
            for (SearchHit hit : searchHits) {
                Map<String, Object> map = hit.getSource();
                GoodsSearchResult goodsSearchLine = new GoodsSearchResult();
                //设置商品名称
    goodsSearchLine.setName(map.get("goodsName").toString());
               //设置商品ID
                goodsSearchLine.setGoodsId(map.get("goodsId") == null ? 0 : StringUtil.toInt(map.get("goodsId").toString(), 0));
               //获取商家店铺ID
                Integer sellerId = map.get("sellerId") == null ? 0 : StringUtil.toInt(map.get("sellerId").toString(), 0);
                //设置商家ID
                goodsSearchLine.setSellerId(sellerId);
                //将商品信息放入结果集合中
                resultList.add(goodsSearchLine);

                //获取当前坐标与店铺的距离
                Double distance = StringUtil.toDouble(hit.getSortValues()[0], false);
                shopDistanceMap.put(sellerId, distance);
            }
            Page webPage = new Page<>(pageNo, searchHits.getTotalHits(), pageSize, resultList);
            searchResult.setWebPage(webPage);

            //初始化搜索结果
            this.initSearchResult(searchResult, response, shopDistanceMap);
            return searchResult;
        } catch (Exception e) {
            e.printStackTrace();
        }
        return searchResult;
    }
    
    /**
     * 构建索引查询条件
     * @param goodsSearch 查询条件参数
     * @return
     */
    protected SearchRequestBuilder createGoodsQuery(GoodsSearchParams goodsSearch) {
        //获取查询关键字
        String keyword = goodsSearch.getKeyword();
        //获取店铺ID
        Integer sellerId = goodsSearch.getSellerId();
        //获取用户当前所在位置经度
        double userLng = goodsSearch.getLongitude();
        //获取用户当前所在位置纬度
        double userLat = goodsSearch.getLatitude();
    
        SearchRequestBuilder searchRequestBuilder = elasticsearchTemplate.getClient().prepareSearch("goods_index_"+ EsSettings.GOODS_INDEX_NAME);

        BoolQueryBuilder boolQueryBuilder = QueryBuilders.boolQuery();

        //关键字检索
        if (!StringUtil.isEmpty(keyword)) {
            QueryStringQueryBuilder queryString = new QueryStringQueryBuilder(keyword).field("goodsName");
            queryString.defaultOperator(Operator.AND);
            queryString.analyzer("ik_smart");
            queryString.useDisMax(false);
            boolQueryBuilder.must(queryString);
        }

        //卖家搜索
        if (sellerId != null) {
    boolQueryBuilder.must(QueryBuilders.termQuery("sellerId", sellerId));
        }
        //设置查询条件
        searchRequestBuilder.setQuery(boolQueryBuilder);

        // 以某点为中心,搜索指定范围
        GeoDistanceQueryBuilder distanceQueryBuilder = new GeoDistanceQueryBuilder("location");
        distanceQueryBuilder.point(userLat, userLng);
        // 定义查询单位:公里
        distanceQueryBuilder.distance(SEARCH_DISTANCE, DistanceUnit.KILOMETERS);
        boolQueryBuilder.filter(distanceQueryBuilder);

        //添加聚合
        this.addAggregation(searchRequestBuilder);
        //添加排序
        this.addSort(searchRequestBuilder, userLng, userLat);
        return searchRequestBuilder;
    }
    
    /**
     * 添加聚合条件
     * @param searchRequestBuilder
     * @return
     */
    protected void addAggregation(SearchRequestBuilder searchRequestBuilder) {
        //sellerId聚合的子聚合:是一个topHits,只显示一条记录,目的是查出这个聚合的店铺信息
        AggregationBuilder shopDetailAgg = AggregationBuilders.topHits("shopDetail").size(1).fetchSource(new String[]{"sellerName","sellerId","shopLogo","shopPraiseRate", "deliveryScope"},new  String[]{});
        //构建按sellerId聚合
        AggregationBuilder shopAgg = AggregationBuilders.terms("shop").field("sellerId").subAggregation(shopDetailAgg);
        searchRequestBuilder.addAggregation(shopAgg);
    }
    
    /**
     * 添加排序条件
     * @param searchRequestBuilder
     * @param sortField 排序字段
     * @param userLng 用户当前所在位置经度
     * @param userLat 用户当前所在位置纬度
     */
    protected void addSort(SearchRequestBuilder searchRequestBuilder, double userLng, double userLat) {
        //以当前的区为基准排序
        SortBuilder locationOrder = SortBuilders.geoDistanceSort("location",userLat,userLng).unit(DistanceUnit.KILOMETERS).order(SortOrder.ASC);
        searchRequestBuilder.addSort(locationOrder);
    }
    
    /**
     * 初始化搜索结果
     * @param searchResult 搜索结果数据
     * @param response 查询结果
     * @param shopDistanceMap 店铺距离数据
     */
    protected void initSearchResult(SearchResult searchResult, SearchResponse response, Map<Integer, Double> shopDistanceMap) {
        //获取聚合结果数据
        Map<String, Aggregation> aggMap = response.getAggregations().asMap();

        //新建店铺信息集合
        List<Map> shopList = new ArrayList<>();
        //获取店铺聚合结果数据
        LongTerms shopTerms = (LongTerms) aggMap.get("shop");
        List<LongTerms.Bucket> bucketList = shopTerms.getBuckets();
        for (LongTerms.Bucket bucket : bucketList) {
            Aggregations shopDetailAggResult = bucket.getAggregations();
            Map<String, Aggregation> detailAggMap = shopDetailAggResult.asMap();
            InternalTopHits hits =(InternalTopHits) detailAggMap.get("shopDetail");
            SearchHit[] detailHit = hits.getHits().getHits();
            if (detailHit != null && detailHit.length >= 1) {
                SearchHit hit = detailHit[0];
                Map shopDetail = hit.getSource();
                double dis = shopDistanceMap.get(shopDetail.get("sellerId"));
                String disStr = "";
                if (dis >= 1) {
                    disStr = CurrencyUtil.round(dis, 2) + "km";
                } else {
                    disStr = CurrencyUtil.round(CurrencyUtil.mul(dis, 1000.00), 0) + "m";
                }
                shopDetail.put("distance", disStr);
                shopList.add(shopDetail);
            }
        }
        //设置店铺信息集合
        searchResult.setShopList(shopList);
    }
}

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

kingapex1

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值