elasticsearch地理位置总结

参考:

https://blog.csdn.net/tang_jian_dong/article/details/104446526

https://blog.csdn.net/u013041642/article/details/94416631

在elasticsearch中默认支持了地理坐标排序,非常方便。

项目中我们位置相关的功能主要有:位置由近到远排序、计算距离值、距离与价格一起权重打分

springboot项目

<!-- es -->
<dependency>
	<groupId>org.springframework.boot</groupId>
	<artifactId>spring-boot-starter-data-elasticsearch</artifactId>
</dependency>

实体类

package com.dto;

import org.springframework.data.annotation.Id;
import org.springframework.data.elasticsearch.annotations.Document;
import org.springframework.data.elasticsearch.annotations.Field;
import org.springframework.data.elasticsearch.annotations.FieldIndex;
import org.springframework.data.elasticsearch.annotations.FieldType;
import org.springframework.data.elasticsearch.annotations.GeoPointField;

//商品
@Document(indexName = "sku", type = "sku", shards = 5, replicas = 1, indexStoreType = "fs", refreshInterval = "-1")
public class EsSku {

	@Id
	@Field(type = FieldType.Integer)
	private Integer skuId;


	/**
	 * 商品名称
	 */
	@Field(type = FieldType.String)
	private String skuName;

	/**
	 * 坐标,sql:CONCAT(ss.latitude,',',ss.longitude) position,坐标值得校验不然保存报错
	 */
	@GeoPointField
	private String position;

	/**
	 * 距离
	 */
	@Field(type = FieldType.String, index = FieldIndex.no)
	private String distance;

	/**
	 * 价格
	 */
	@Field(type = FieldType.Integer, index = FieldIndex.not_analyzed)
	private Integer price;

	get/set

}

dao

package com.repository;

import org.springframework.data.elasticsearch.repository.ElasticsearchRepository;


public interface EsSkuRepository extends ElasticsearchRepository<EsSku, Integer> {

}

service

package com.impl;

import java.util.HashSet;
import java.util.List;
import java.util.Map;
import java.util.Set;

import javax.annotation.Resource;

import org.apache.commons.collections.CollectionUtils;
import org.apache.commons.lang3.StringUtils;
import org.elasticsearch.common.geo.GeoDistance;
import org.elasticsearch.common.lucene.search.function.CombineFunction;
import org.elasticsearch.common.unit.DistanceUnit;
import org.elasticsearch.index.query.BoolQueryBuilder;
import org.elasticsearch.index.query.GeoDistanceQueryBuilder;
import org.elasticsearch.index.query.QueryBuilder;
import org.elasticsearch.index.query.QueryBuilders;
import org.elasticsearch.index.query.functionscore.ScoreFunctionBuilder;
import org.elasticsearch.index.query.functionscore.ScoreFunctionBuilders;
import org.elasticsearch.search.sort.GeoDistanceSortBuilder;
import org.elasticsearch.search.sort.SortBuilders;
import org.elasticsearch.search.sort.SortOrder;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.data.domain.Page;
import org.springframework.data.domain.PageRequest;
import org.springframework.data.domain.Pageable;
import org.springframework.data.elasticsearch.core.query.NativeSearchQueryBuilder;
import org.springframework.data.elasticsearch.core.query.SearchQuery;
import org.springframework.stereotype.Service;

import com.google.common.collect.Maps;
import com.google.common.collect.Sets;


@Service
public class SearchServiceImpl implements SearchService {

	private static final Logger LOGGER = LoggerFactory.getLogger(SearchServiceImpl.class);

	@Resource
	private EsSkuRepository esSkuRepository;

	private PageBean<EsSku> searchSku() {
		String keywords = "";
		Double lat = 39.939915;
		Double lng = 116.469635;
		boolean hasPosition = lat != null && lng != null;
		Double distance = 1000;
		Integer pageNum = 0;
		Integer pageSize = 10;
		// 分页
		Pageable pageable = new PageRequest(pageNum, pageSize);
		// 需要查询的字段
		QueryBuilder keywordsBuilder = null;
		// 如果是拼音则匹配全部同音汉字,如果是汉字则不匹配同音字
		if (StringUtils.isEmpty(keywords)) {
			// 0~10元之间评分为1, 1万元时评分为0.5,价格越高评分越低
			ScoreFunctionBuilder priceFunctionBuilder = ScoreFunctionBuilders.gaussDecayFunction("price", 500, 500000).setOffset(500);
			if (hasPosition) {
				// 1km之内评分为1, 10km时评分为0.5,距离越远评分越低
				Map<String, Double> origin = Maps.newHashMap();
				origin.put("lat", lat);
				origin.put("lon", lng);
				ScoreFunctionBuilder gaussDecayFunction = ScoreFunctionBuilders.gaussDecayFunction("position", origin, "10km").setOffset("1km");
				keywordsBuilder = QueryBuilders.functionScoreQuery(gaussDecayFunction).add(priceFunctionBuilder).boostMode(CombineFunction.MULT);
			} else {
				keywordsBuilder = QueryBuilders.functionScoreQuery(priceFunctionBuilder).boostMode(CombineFunction.MULT);
			}
		} else {
			keywordsBuilder = QueryBuilders.multiMatchQuery(keywords, "skuName");
		}
		// 坐标排序
		GeoDistanceQueryBuilder geoBuilder = null;
		GeoDistanceSortBuilder sortGeoBuilder = null;
		if (hasPosition) {
			geoBuilder = QueryBuilders.geoDistanceQuery("position")// 查询字段
					.point(lat, lng)// 设置经纬度
					.distance(distance, DistanceUnit.KILOMETERS)// 设置距离查询的距离范围
					.geoDistance(GeoDistance.ARC);
			sortGeoBuilder = SortBuilders.geoDistanceSort("position").point(lat, lng).unit(DistanceUnit.KILOMETERS);
			sortGeoBuilder.order(SortOrder.ASC);
		}

		// 创建查询对象
		BoolQueryBuilder queryBuilder = QueryBuilders.boolQuery().must(keywordsBuilder);
		NativeSearchQueryBuilder nativeSearchQueryBuilder = new NativeSearchQueryBuilder();
		nativeSearchQueryBuilder.withQuery(queryBuilder).withPageable(pageable);
		
		// 如果开启定位,则只查询定位内数据
		if (hasPosition) {
			nativeSearchQueryBuilder.withFilter(geoBuilder);
		}
		SearchQuery searchQuery = nativeSearchQueryBuilder.build();
		// 结果
		Page<EsSku> page = esSkuRepository.search(searchQuery);
		List<EsSku> list = page.getContent();
		// 计算距离
		if (CollectionUtils.isNotEmpty(list) && hasPosition) {
			for (EsSku esSku : list) {
				Double latitude = Double.valueOf(esSku.getLatitude());
				Double longitude = Double.valueOf(esSku.getLongitude());
				double calculateDistance = GeoDistance.ARC.calculate(lat, lng, latitude, longitude, DistanceUnit.KILOMETERS);
				String formatDistance = formatDistance(calculateDistance);
				esSku.setDistance(formatDistance);
			}
		}
		return new PageBean<EsSku>(pageNum + 1, pageSize, (int) page.getTotalElements(), list);
	}

	// 美化距离文本
	private String formatDistance(double distance) {
		if (distance == 0) {
			return "0m";
		} else if (distance > 1) {
			distance = (double) Math.round(distance * 100) / 100;
			String s = new StringBuilder().append(distance).append("km").toString();
			return s;
		} else {
			distance = distance * 1000;
			String s = new StringBuilder().append((int) distance).append("m").toString();
			return s;
		}
	}

}

pagebean

package com.dto;

import java.util.List;

/**
 * 分页功能中的一页的信息
 */
public class PageBean<T> {

	// 指定的或是页面参数
	private int pageNum; // 当前页
	private int pageSize; // 每页显示多少条

	// 查询数据库
	private int recordCount; // 总记录数
	private List<T> recordList; // 本页的数据列表

	// 计算
	private int pageCount; // 总页数
	private int beginPageIndex; // 页码列表的开始索引(包含)
	private int endPageIndex; // 页码列表的结束索引(包含)

	/**
	 * 只接受前4个必要的属性,会自动的计算出其他3个属生的值
	 * 
	 * @param pageNum
	 * @param pageSize
	 * @param recordCount
	 * @param recordList
	 */
	public PageBean(int pageNum, int pageSize, int recordCount, List<T> recordList) {
		this.pageNum = pageNum;
		this.pageSize = pageSize;
		this.recordCount = recordCount;
		this.recordList = recordList;

		// 计算总页码
		pageCount = (recordCount + pageSize - 1) / pageSize;

		// 计算 beginPageIndex 和 endPageIndex
		// >> 总页数不多于10页,则全部显示
		if (pageCount <= 10) {
			beginPageIndex = 1;
			endPageIndex = pageCount;
		}
		// >> 总页数多于10页,则显示当前页附近的共10个页码
		else {
			// 当前页附近的共10个页码(前4个 + 当前页 + 后5个)
			beginPageIndex = pageNum - 4;
			endPageIndex = pageNum + 5;
			// 当前面的页码不足4个时,则显示前10个页码
			if (beginPageIndex < 1) {
				beginPageIndex = 1;
				endPageIndex = 10;
			}
			// 当后面的页码不足5个时,则显示后10个页码
			if (endPageIndex > pageCount) {
				endPageIndex = pageCount;
				beginPageIndex = pageCount - 10 + 1;
			}
		}
	}

	public List<T> getRecordList() {
		return recordList;
	}

	public void setRecordList(List<T> recordList) {
		this.recordList = recordList;
	}

	public int getPageNum() {
		return pageNum;
	}

	public void setPageNum(int pageNum) {
		this.pageNum = pageNum;
	}

	public int getPageCount() {
		return pageCount;
	}

	public void setPageCount(int pageCount) {
		this.pageCount = pageCount;
	}

	public int getPageSize() {
		return pageSize;
	}

	public void setPageSize(int pageSize) {
		this.pageSize = pageSize;
	}

	public int getRecordCount() {
		return recordCount;
	}

	public void setRecordCount(int recordCount) {
		this.recordCount = recordCount;
	}

	public int getBeginPageIndex() {
		return beginPageIndex;
	}

	public void setBeginPageIndex(int beginPageIndex) {
		this.beginPageIndex = beginPageIndex;
	}

	public int getEndPageIndex() {
		return endPageIndex;
	}

	public void setEndPageIndex(int endPageIndex) {
		this.endPageIndex = endPageIndex;
	}

}

 

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值