elasticsearch之十八springboot测试地理信息
个人专题目录](https://www.jianshu.com/p/140e2a59db2c)
1. elasticsearch地理信息搜索
随着生活服务类应用最近一年的崛起和普及,基于地理位置的内容正在日益重要。LBS已是老生常谈,不过在PC、在移动互联网时代,LBS在导航之外都未出现第二个杀手级应用。在没有O2O之前,LBS所依重的POI(Point of Interest)并未真正成为用户的“Interest”,人们的兴趣还是只存在于网络的虚拟内容,游戏、新闻、文学、网页、多媒体,等等。
O2O大热之后,越来越多的杀手级应用开始出现:打车拼车租车等用车服务进去主界面是地图,陌陌这个声名鹊起的后来者基于位置做出了社交的差异化,团购上门分享所有热门O2O应用均离不开地图。线下实体、生活服务、身边内容的呈现形式都基于地图的“点”而不是基于时间的“线”,人们不需刷而是搜索、缩放、点选,乃至不做任何操作根据位置移动来与之交互。我将这类内容称之为LocationPoint,这将成为Timeline之后的又一种至关重要的内容形式。
1.1 地理坐标点
地理坐标点 是指地球表面可以用经纬度描述的一个点。 地理坐标点可以用来计算两个坐标间的距离,还可以判断一个坐标是否在一个区域中,或在聚合中。
地理坐标点不能被动态映射 (dynamic mapping)自动检测,而是需要显式声明对应字段类型为 geo-point
:
PUT /map
{
"settings": {
"number_of_shards": 3,
"number_of_replicas": 1
},
"mappings": {
"cp": {
"properties": {
"name": {
"type": "keyword"
},
"location": {
"type": "geo_point" #经度lon,纬度lat
}
}
}
}
}
1.2 经纬度坐标格式
如上例,location
字段被声明为 geo_point
后,我们就可以索引包含了经纬度信息的文档了。 经纬度信息的形式可以是字符串、数组或者对象:
#### 北科
POST /map/cp/1
{
"name": "北科",
"location": {
"lon": 116.2577,
"lat": 40.1225
}
}
#### 巩华城地铁站
POST /map/cp/2
{
"name": "巩华城地铁站",
"location": {
"lon": 116.3004,
"lat": 40.137399
}
}
#### 生命科学园地铁站
POST /map/cp/3
{
"name": "生命科学园地铁站",
"location": {
"lon": 116.300582,
"lat": 40.101053
}
}
字符串形式以半角逗号分割,如 "lat,lon" 。 |
---|
对象形式显式命名为 lat 和 lon 。 |
数组形式表示为 [lon,lat]
|
大家可以上百度或者高德的地图开放平台了解相关的操作,以百度地图开放为例(http://lbsyun.baidu.com/jsdemo.htm),比如想获取地图上某个点的经纬度,如下图所示:
geo3.png1.3 通过地理坐标点过滤
我们有时候,希望可以根据当前所在的位置,找到自己身边的符合条件的一些商店,酒店之类的。它主要支持两种类型的地理查询:
一种是地理点(geo_point),即经纬度查询,另一种是地理形状查询(geo_shape),即支持点、线、圈、多边形查询.
ES中有3中位置相关的过滤器,用于过滤位置信息:
- geo_distance: 查找距离某个中心点距离在一定范围内的位置
- geo_bounding_box: 查找某个长方形区域内的位置
- geo_polygon: 查找位于多边形内的地点。
1.4 geo_distance
地理距离过滤器( geo_distance
)以给定位置为圆心画一个圆,来找出那些地理坐标落在其中的文档.
广泛地应用在O2O,LBS领域,比如查找附近的酒店、包店、共享单车等。如下图所示,按距用户的距离查询周边酒店。
例如:查询以某个经纬度为中心周围500KM以内的城市
#### 定位一个点,使用长度定义一个圆
POST /map/cp/_search
{
"query": {
"geo_distance": {
"location": { ### 确定坐标位置
"lon": 116.2577,
"lat": 40.1225
},
"distance": 5000, ### 半径
"distance_type": "arc" ### 指定圆
}
}
}
/**
* 以某个经纬度为中心查询周围限定距离的文档
*
* @param indexName 索引
* @param lot 经度
* @param lon 纬度
* @param distance 距离
* @throws Exception
*/
@Override
public void geoDistanceQuery(String indexName, double lot, double lon, int distance) throws Exception {
GeoDistanceQueryBuilder geoDistanceQueryBuilder = QueryBuilders.geoDistanceQuery("location")
.point(lot, lon)
.distance(distance, DistanceUnit.KILOMETERS)
.geoDistance(GeoDistance.ARC);
baseQuery.builder(indexName, geoDistanceQueryBuilder);
}
代码依次执行:
1、建立索引
2、初始化数据
3、执行testGeoDistanceQuery,搜索“距厦门500公里以内的城市”
@RunWith(SpringJUnit4ClassRunner.class)
@SpringBootTest(classes = SearchServiceApplication.class)
@WebAppConfiguration
@Log4j2
public class GeoQueryTest {
private String indexName = "cn_large_cities";
@Autowired
private IndexService indexService;
@Autowired
private DocService docService;
@Autowired
private GeoQuery geoQuery;
@Test
public void testCreateIndex() throws Exception {
CreateIndexRequest request = new CreateIndexRequest(indexName);
buildSetting(request);
buildIndexMapping(request);
indexService.createIndex(indexName, request, false);
}
@Test
public void testDelIndex() throws Exception {
indexService.deleteIndex(indexName, false);
}
//设置分片
private void buildSetting(CreateIndexRequest request) {
request.settings(Settings.builder().put("index.number_of_shards", 3)
.put("index.number_of_replicas", 2));
}
/**
* 生成地理信息表索引结构
* <p>
* city 城市
* state 省
* location 位置
*
* @param request
* @throws IOException
*/
private void buildIndexMapping(CreateIndexRequest request) throws IOException {
XContentBuilder mappingBuilder = JsonXContent.contentBuilder()
.startObject()
.startObject("properties")
.startObject("city")
.field("type", "keyword")
.field("index", "true")
.endObject()
.startObject("state")
.field("type", "keyword")
.field("index", "true")
.endObject()
.startObject("location")
.field("type", "geo_point")
// .field("index", "true")
.endObject()
.endObject()
.endObject();
request.mapping(mappingBuilder);
}
@Test
public void testInitData() throws IOException {
String json1 = "{" +
"\"city\": \"北京\", " +
"\"state\": \"北京\"," +
"\"location\": {\"lat\": \"39.91667\", \"lon\": \"116.41667\"}"
+ "}";
String json2 = "{" +
"\"city\": \"上海\", " +
"\"state\": \"上海\"," +
"\"location\": {\"lat\": \"34.50000\", \"lon\": \"121.43333\"}"
+ "}";
String json3 = "{" +
"\"city\": \"厦门\", " +
"\"state\": \"福建\"," +
"\"location\": {\"lat\": \"24.46667\", \"lon\": \"118.10000\"}"
+ "}";
String json4 = "{" +
"\"city\": \"福州\", " +
"\"state\": \"福建\"," +
"\"location\": {\"lat\": \"26.08333\", \"lon\": \"119.30000\"}"
+ "}";
String json5 = "{" +
"\"city\": \"广州\", " +
"\"state\": \"广东\"," +
"\"location\": {\"lat\": \"23.16667\", \"lon\": \"113.23333\"}"
+ "}";
docService.addDoc(indexName, json1, null);
docService.addDoc(indexName, json2, null);
docService.addDoc(indexName, json3, null);
docService.addDoc(indexName, json4, null);
docService.addDoc(indexName, json5, null);
}
@Test
public void testGeoDistanceQuery() throws Exception {
//距厦门500公里以内的城市
geoQuery.geoDistanceQuery(indexName, 24.46667, 118.0000, 500);
}
@Test
public void testGeoBoundingBox() throws Exception {
geoQuery.geoBoundingBoxQuery(indexName, 40.8, -74.0, 40.715, -73.0);
}
@Test
public void testPolygonQuery() throws Exception {
geoQuery.geoPolygonQuery(indexName);
}
}
1.5 geo_bounding_box
查找某个长方形区域内的位置,以高德地图开放平台为例(https://lbs.amap.com/api/javascript-api/example/overlayers/rectangle-draw-and-edit),通过在地图上用矩形框选取一定范围来搜索。如下图所示:
geo5.png这是目前为止最有效的地理坐标过滤器了,因为它计算起来非常简单。 你指定一个矩形的 顶部 , 底部 , 左边界 ,和 右边界 ,然后过滤器只需判断坐标的经度是否在左右边界之间,纬度是否在上下边界之间:
他可以指定一下几个属性:
top_left
: 指定最左边的经度和最上边的纬度
bottom_right
: 指定右边的经度和最下边的纬度
例如:查询某个矩形范围内的文档
POST /map/cp/_search
{
"query": {
"geo_bounding_box": {
"location": {
"top_left": {
"lon": 116.242461,
"lat": 40.139123
},
"bottom_right": {
"lon": 116.305702,
"lat": 40.11987
}
}
}
}
}
/**
* 搜索矩形范围内的文档
*
* @param indexName 索引
* @param top 最上边的纬度
* @param left 最左边的经度
* @param bottom 最下边的纬度
* @param right 右边的经度
* @throws Exception
*/
@Override
public void geoBoundingBoxQuery(String indexName, double top, double left, double bottom, double right) throws Exception {
GeoBoundingBoxQueryBuilder address = QueryBuilders.geoBoundingBoxQuery("location")
.setCorners(top, left, bottom, right);
baseQuery.builder(indexName, address);
}
@Test
public void testGeoBoundingBoxh() throws Exception {
geoQuery.geoBoundingBoxQuery(indexName,40.8,-74.0,40.715,-73.0);
}
1.6 geo_polygon
根据给定的多个点组成的多边形,查询范围内的点。以高德地图开放平台为例(https://lbs.amap.com/api/javascript-api/example/overlayers/polygon-draw-and-edit),通过多个点来确定一个面积,在些面积区域内搜索。
geo6.png例如:搜索某个多边型区域的文档。
POST /map/cp/_search
{
"query": {
"geo_polygon": {
"location": {
"points": [
{
"lon": 116.292695,
"lat": 40.110104
},
{
"lon": 116.296288,
"lat": 40.096114
},
{
"lon": 116.303798,
"lat": 40.099977
},
{
"lon": 116.304876,
"lat": 40.102902
}
]
}
}
}
}
@Override
public void geoPolygonQuery(String indexName) throws Exception {
List<GeoPoint> points = new ArrayList<>();
points.add(new GeoPoint(40.110104, 116.292695));
points.add(new GeoPoint(40.096114, 116.296288));
points.add(new GeoPoint(40.099977, 116.303798));
points.add(new GeoPoint(40.102902, 116.304876));
GeoPolygonQueryBuilder geoPolygonQueryBuilder = QueryBuilders.geoPolygonQuery("location", points);
baseQuery.builder(indexName, geoPolygonQueryBuilder);
}
@Test
public void testPolygonQuery() throws Exception {
geoQuery.geoPolygonQuery(indexName);
}