GEO地理位置查询
提示
地理位置查询,与Es官方提供的功能完全一致,共支持4种类型的地理位置查询:
- GeoBoundingBox
- GeoDistance
- GeoPolygon
- GeoShape
通过这4类查询,可以实现各种强大实用的功能
应用场景
- 外卖类APP 附近的门店
- 社交类APP 附近的人
- 打车类APP 附近的司机
- 区域人群画像类APP 指定范围内的人群特征提取
- 健康码等
- ...
EE对ES地理位置相关功能支持覆盖100%,且使用更为简单.
注意事项
- 在使用地理位置查询API之前,需要提前创建或更新好索引
- 划重点:其中前三类API(GeoBoundingBox,GeoDistance,GeoPolygon)字段索引类型必须为geo_point
- GeoShape字段索引类型必须为geo_shape,否则将导致功能无法正常使用,具体可参考下图
- 字段类型推荐使用String,因为wkt文本格式就是String,非常方便,至于字段名称,见名知意即可.
public class Document {
// 省略其它字段...
@IndexField(fieldType = FieldType.GEO_POINT)
private String location;
@IndexField(fieldType = FieldType.GEO_SHAPE)
private String geoLocation;
}
2
3
4
5
6
7
# 数据录入
数据插入es方法非常多,这里仅介绍如何使用EE开箱即用API将地理位置数据插入es
@Test
public void testInsert() {
// 省略其它无关字段...
Document document = new Document();
// GEO POINT
document.setLocation("40.171975,116.587105");
// 上面也可以写为下面这样,效果是一样的:
GeoPoint geoPoint = new GeoPoint(40.171975,116.587105);
document.setLocation(geoPoint.toString());
// GEO SHAPE
Rectangle rectangle = new Rectangle(39.084509D, 41.187328D, 70.610461D, 20.498353D);
document.setGeoLocation(rectangle.toString());
int successCount = documentMapper.insert(document);
}
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
插入的数据必须是String,其格式可以直接是经纬度,也可以先创建具体的GeoPoint或Geometry对象,然后调用它的toString方法将其转换为wkt坐标字符串后再插入, 如果直接插入GeoPoint或Geometry对象,会导致插入失败,提示UnknownSource.
# GeoBoundingBox
GeoBoundingBox: 直译为地理边界盒,由左上点和右下点构成的矩形范围,在此范围内的点均可以被查询出来,实际使用的并不多,可参考下图:
# API
// 在矩形内
geoBoundingBox(R column, GeoPoint topLeft, GeoPoint bottomRight);
// 不在矩形内 (0.9.7+版本支持)
notInGeoBoundingBox(R column, GeoPoint topLeft, GeoPoint bottomRight);
2
3
4
5
# 使用示例
@Test
public void testGeoBoundingBox() {
// 查询位于左上点和右下点坐标构成的长方形内的所有点
LambdaEsQueryWrapper<Document> wrapper = new LambdaEsQueryWrapper<>();
// 假设左上点坐标
GeoPoint leftTop = new GeoPoint(41.187328D, 115.498353D);
// 假设右下点坐标
GeoPoint bottomRight = new GeoPoint(39.084509D, 117.610461D);
wrapper.geoBoundingBox(Document::getLocation, leftTop, bottomRight);
// 查不在此长方形内的所有点
// wrapper.notInGeoBoundingBox(Document::getLocation, leftTop, bottomRight);
List<Document> documents = documentMapper.selectList(wrapper);
documents.forEach(System.out::println);
}
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
温馨提示
- 上面使用示例仅演示了其中一种,实际上本框架中坐标点的语法支持非常多种,ElasticSearch官方提供的几种数据格式都支持,用户可按自己习惯自行选择对应的api进行查询参数构造:
- GeoPoint:上面Demo中使用的经纬度表示方式
- 经纬度数组: [116.498353, 40.187328],[116.610461, 40.084509]
- 经纬度字符串: "40.187328, 116.498353","116.610461, 40.084509"
- 经纬度边界框WKT: "BBOX (116.498353,116.610461,40.187328,40.084509)"
- 经纬度GeoHash(哈希): "xxx"
其中,经纬度哈希的转换可参考此网站:GeoHash坐标在线转换 (opens new window)
# GeoDistance
GeoDistance:直译为地理距离,实际上就是以给定的点为圆心,给定的半径画个圆,处在此圆内的点都能被查出来,使用较为高频,比如像我们用的外卖软件,查询周围3公里内的所有店铺,就可以用此功能去实现,没错你还可以用来写YP软件,查询下附近三公里内的PLMM...
# API
// 查圆形内的所有点
geoDistance(R column, Double distance, DistanceUnit distanceUnit, GeoPoint centralGeoPoint);
// 查不在圆形内的所有点 (0.9.7+ 版本支持)
notInGeoDistance(R column, Double distance, DistanceUnit distanceUnit, GeoPoint centralGeoPoint);
2
3
4
5
# 使用示例
@Test
public void testGeoDistance() {
// 查询以纬度为41.0,经度为115.0为圆心,半径168.8公里内的所有点
LambdaEsQueryWrapper<Document> wrapper = new LambdaEsQueryWrapper<>();
// 其中单位可以省略,默认为km
wrapper.geoDistance(Document::getLocation, 168.8, DistanceUnit.KILOMETERS, new GeoPoint(41.0, 116.0));
//查询不在圆形内的所有点
// wrapper.notInGeoDistance(Document::getLocation, 168.8, DistanceUnit.KILOMETERS, new GeoPoint(41.0, 116.0));
// 上面语法也可以写成下面这几种形式,效果是一样的,兼容不同用户习惯而已:
// wrapper.geoDistance(Document::getLocation,"1.5km",new GeoPoint(41.0,115.0));
// wrapper.geoDistance(Document::getLocation, "1.5km", "41.0,115.0");
List<Document> documents = documentMapper.selectList(wrapper);
System.out.println(documents);
}
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
温馨提示
- 同样的对于坐标点的表达形式也支持多种,和GeoBondingBox中的Tips介绍的一样,这里不再赘述.
- 对于宠粉的EE来说,兼容各种用户的不同习惯是理所当然的,所以你在使用时会发现大量方法重载,选一种最符合你使用习惯或符合指定使用场景的api进行调用即可.
# GeoPolygon
GeoPolygon:直译为地理多边形,实际上就是以给定的所有点构成的多边形为范围,查询此范围内的所有点,此功能常被用来做电子围栏,使用也较为高频,像共享单车可以停放的区域就可以通过此技术实现,可参考下图:
# API
geoPolygon(R column, List<GeoPoint> geoPoints)
# 使用示例
@Test
public void testGeoPolygon() {
// 查询以给定点列表构成的不规则图形内的所有点,点数至少为3个
LambdaEsQueryWrapper<Document> wrapper = new LambdaEsQueryWrapper<>();
List<GeoPoint> geoPoints = new ArrayList<>();
GeoPoint geoPoint = new GeoPoint(40.178012, 116.577188);
GeoPoint geoPoint1 = new GeoPoint(40.169329, 116.586315);
GeoPoint geoPoint2 = new GeoPoint(40.178288, 116.591813);
geoPoints.add(geoPoint);
geoPoints.add(geoPoint1);
geoPoints.add(geoPoint2);
wrapper.geoPolygon(Document::getLocation, geoPoints);
List<Document> documents = documentMapper.selectList(wrapper);
System.out.println(documents);
}
2
3
4
5
6
7
8
9
10
11
12
13
14
15
温馨提示
- 同样的,关于坐标点的入参形式,也支持多种,与官方一致,可以参考GeoBoundingBox中的Tips,这里不赘述.值得注意的是多边形的点数不能少于3个,否则Es无法勾勒出多边形,本次查询会报错.
- 索引类型和字段类型与GeoBondingBox中的Tips介绍的一样
# GeoShape
GeoShape:直译为地理图形,怎么理解?乍一看好像和GeoPolygon很像,但实际上,前面三种类型查询的都是坐标点,而此方法查询的是图形,比如一个园区,从世界地图上看可以把它当做一个点,但如果放得足够大,比如把地图具体到杭州市滨江区,园区就可能变成若干个点构成的一个区域,在一些特殊的场景中,需要查询此完整的区域,以及两个区域的交集之类的,就需要用到GeoShape了,如果你还不理解,不妨先接着往下看,以杭州为例,我举一个健康码的例子,假设黑色圈内区域为中风险地区,我现在要查出ES中所有在市民中心且处于中风险区域的人,把他们的健康码统统变成橙色,那实际上我要找的就是下图中橙色那块区域,此时红色箭头所构成的区域是整个市民中心,我可以把整个市民中心作为一个地理图形,然后把黑色大圆作为查询的图形,找出它们的交集即可.
上图对应的ShapeRelation为INTERSECTS,看以看下面API.
# API
// 查询符合已索引图形的图形
geoShape(R column, String indexedShapeId);
// 查询不符合已索引图形的图形 (0.9.7+ 版本支持)
notInGeoShape(R column, String indexedShapeId);
// 查询符合指定图形和图形关系的图形列表
geoShape(R column, Geometry geometry, ShapeRelation shapeRelation);
// 查询不符合指定图形和图形关系的图形列表
notInGeoShape(R column, Geometry geometry, ShapeRelation shapeRelation);
2
3
4
5
6
7
8
9
10
11
# 使用示例
此API不常用,也可直接跳过看下面通过图形查询的.
/**
* 已知图形索引ID(不常用)
* 在一些高频场景下,比如一个已经造好的园区,其图形坐标是固定的,因此可以先把这种固定的图形先存进es
* 后续可根据此图形的id直接查询,比较方便,故有此方法,但不够灵活,不常用
*/
@Test
public void testGeoShapeWithShapeId() {
LambdaEsQueryWrapper<Document> wrapper = new LambdaEsQueryWrapper<>();
// 这里的indexedShapeId为用户事先已经在Es中创建好的图形的id
wrapper.geoShape(Document::getGeoLocation, "edu");
// 不符合的情况
// wrapper.notInGeoShape(Document::getGeoLocation, "edu");
List<Document> documents = documentMapper.selectList(wrapper);
System.out.println(documents);
}
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
此API相较上面方式更常用,即用户可以自行指定要查询的图形是矩形,圆形,还是多边形...(具体看代码中注释):
/**
* 图形由用户自定义(常用),本框架支持Es所有支持的图形:
* (Point,MultiPoint,Line,MultiLine,Circle,LineaRing,Polygon,MultiPolygon,Rectangle)
*/
@Test
public void testGeoShape() {
// 注意,这里查询的是图形,所以图形的字段索引类型必须为geoShape,不能为geoPoint,故这里用geoLocation字段而非location字段
LambdaEsQueryWrapper<Document> wrapper = new LambdaEsQueryWrapper<>();
// 这里以圆形为例演示,其中x,y为圆心坐标,r为半径. 其它图形请读者自行演示,篇幅原因不一一演示了
Circle circle = new Circle(13,14,100);
// shapeRelation支持多种,如果不传则默认为within
wrapper.geoShape(Document::getGeoLocation, circle, ShapeRelation.INTERSECTS);
// 不符合的情况
// wrapper.notInGeoShape(Document::getGeoLocation, circle, ShapeRelation.INTERSECTS);
List<Document> documents = documentMapper.selectList(wrapper);
System.out.println(documents);
}
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
上述地图中的 市民中心(多边形)的WKT(Well-Known Text)坐标(模拟的数据,真实数据可从高德地图/百度地图等通过 调用它们提供的开放API获取):
"POLYGON((108.36549282073975 22.797566864832092,108.35974216461182 22.786093175673713,108.37265968322754 22.775963875498206,108.4035587310791 22.77600344454008,108.41003894805907 22.787557113881462,108.39557647705077 22.805360509802284,108.36549282073975 22.797566864832092))";
已经存储在Es中了,实际上我们在项目中都会把可能用到的数据或业务数据都事先存入Es了,否则查询也就无意义了,查个空气? 所以上面API根据GeoShape查询时,需要传入的参数的仅是你圈定的范围的图形(上面该参数是圆).
温馨提示
GeoShape容易和GeoPolygon混淆,需要特别注意,它俩其实是两种不同的东西,其索引类型也需要区别,前者为geo_point,后者为geo_shape。