1. 概述
本教程中,我们将探索MongoDB对地理空间的支持。
我们将讨论如何在MongoDB中存储地理空间数据,以及如何索引和搜索位置。其中用到了near
,geoWithin
,geoIntersects
地理空间查询方法。
2. 存储地理空间数据
首先,我们来看下如何在MongoDB中存储地理位置信息。
MongoDB支持多种GeoJSON类型来存储位置信息。 本文中,我们将主要使用Point(点)和Polygon(多边形)类型。
2.1. Point 点
这是最基础和常用的GeoJSON
类型,它用来表示网格上的一个特定点。
例如,在places
collection中,有一个location
字段,用来保存我们的坐标点:
{
"name": "Big Ben",
"location": {
"coordinates": [-0.1268194, 51.5007292],
"type": "Point"
}
}
注意第一个表示经度(longitude),第二个为纬度(latitude)。
2.2. Polygon 多边形
多边形是一个稍稍复杂点的GeoJSON类型。
我们可以使用多边形来定义一个具有外边界的区域,如果需要还可以包含内部孔。
例子:
{
"name": "Hyde Park",
"location": {
"coordinates": [
[
[-0.159381, 51.513126],
[-0.189615, 51.509928],
[-0.187373, 51.502442],
[-0.153019, 51.503464],
[-0.159381, 51.513126]
]
],
"type": "Polygon"
}
}
上面例子中,我们定义了一个表示外边界点的数组。 注意最后一个点要等于第一个点,以形成封闭的区域。
请注意,我们需要沿逆时针方向定义外环边界,顺时针方向定义内环(孔)边界。
除了上述的点和多边形外,还有许多其他类型,例如LineString,MultiPoint,MultiPolygon,MultiLineString和GeometryCollection。
3. 地理空间索引
为了搜索我们插入的地理空间数据,我们需要先为location字段创建索引。
有2种索引方式:2d
和 2dsphere
。
开始之前,我们先定义places
collection:
MongoClient mongoClient = new MongoClient();
MongoDatabase db = mongoClient.getDatabase("myMongoDb");
collection = db.getCollection("places");
3.1. 2d 索引
2d
索引用于2D平面位置搜索查询。
下面是通过Java代码的方式,为location
创建2d
索引:
collection.createIndex(Indexes.geo2d("location"));
当然,也可以在mongo
shell中执行:
db.places.createIndex({location:"2d"})
3.2. 2dsphere 索引
2dsphere
索引用于地球表面类型的地图
类似的,用Java代码创建2dspheresuoy
索引:
collection.createIndex(Indexes.geo2dsphere("location"));
或者mongo
shell:
db.places.createIndex({location:"2dsphere"})
4. 地理空间搜索
4.1. Near 附近查询
我们可以使用near
查询来搜索给定距离内的点。
near
查询支持2d
和2dsphere
索引。
在下一个示例中,我们将搜索小于1km且大于10米范围内的记录:
@Test
public void givenNearbyLocation_whenSearchNearby_thenFound() {
Point currentLoc = new Point(new Position(-0.126821, 51.495885));
FindIterable<Document> result = collection.find(
Filters.near("location", currentLoc, 1000.0, 10.0));
assertNotNull(result.first());
assertEquals("Big Ben", result.first().get("name"));
}
对应的mongo
shell查询:
db.places.find({
location: {
$near: {
$geometry: {
type: "Point",
coordinates: [-0.126821, 51.495885]
},
$maxDistance: 1000,
$minDistance: 10
}
}
})
结果从近到远排序。
如果我们搜索的位置非常远,则附近位置结果为空:
@Test
public void givenFarLocation_whenSearchNearby_thenNotFound() {
Point currentLoc = new Point(new Position(-0.5243333, 51.4700223));
FindIterable<Document> result = collection.find(
Filters.near("location", currentLoc, 5000.0, 10.0));
assertNull(result.first());
}
nearSphere
方法,作用与near
完全相同,只是它使用球形几何来计算距离。
4.2. Within 查询
geoWithin
查询完全包含在给定区域中的位置,如圆、长方体或多边形。
同样支持2d和2dsphere
索引。
下面例子中,我们查询以给定位置为中心,5km半径圆内的记录:
@Test
public void givenNearbyLocation_whenSearchWithinCircleSphere_thenFound() {
double distanceInRad = 5.0 / 6371;
FindIterable<Document> result = collection.find(
Filters.geoWithinCenterSphere("location", -0.1435083, 51.4990956, distanceInRad));
assertNotNull(result.first());
assertEquals("Big Ben", result.first().get("name"));
}
备注,我们需要将距离从km转换为弧度(除以地球半径)。
对应的mongo shell查询语句:
db.places.find({
location: {
$geoWithin: {
$centerSphere: [
[-0.1435083, 51.4990956],
0.0007848061528802386
]
}
}
})
下面,我们查询矩形框范围内的所有记录。我们需要通过左下角和右上角坐标定义框的范围:
@Test
public void givenNearbyLocation_whenSearchWithinBox_thenFound() {
double lowerLeftX = -0.1427638;
double lowerLeftY = 51.4991288;
double upperRightX = -0.1256209;
double upperRightY = 51.5030272;
FindIterable<Document> result = collection.find(
Filters.geoWithinBox("location", lowerLeftX, lowerLeftY, upperRightX, upperRightY));
assertNotNull(result.first());
assertEquals("Big Ben", result.first().get("name"));
}
对应的mongo shell查询:
db.places.find({
location: {
$geoWithin: {
$box: [
[-0.1427638, 51.4991288],
[-0.1256209, 51.5030272]
]
}
}
})
最后,如果矩形或圆不能满足你的需求,你还可以使用多边形来定义更具体的区域
@Test
public void givenNearbyLocation_whenSearchWithinPolygon_thenFound() {
ArrayList<List<Double>> points = new ArrayList<List<Double>>();
points.add(Arrays.asList(-0.1439, 51.4952));
points.add(Arrays.asList(-0.1121, 51.4989));
points.add(Arrays.asList(-0.13, 51.5163));
points.add(Arrays.asList(-0.1439, 51.4952));
FindIterable<Document> result = collection.find(
Filters.geoWithinPolygon("location", points));
assertNotNull(result.first());
assertEquals("Big Ben", result.first().get("name"));
}
对应的查询语句:
db.places.find({
location: {
$geoWithin: {
$polygon: [
[-0.1439, 51.4952],
[-0.1121, 51.4989],
[-0.13, 51.5163],
[-0.1439, 51.4952]
]
}
}
})
我们仅定义了一个具有外边界的多边形,我们也可以在上面打孔。每个孔都是Point
类型的List
:
geoWithinPolygon("location", points, hole1, hole2, ...)
4.3. Intersect 交集查询
与geoWithin
完全包含相比,geoIntersects
查找至少与给定几何相交的记录。
只支持2dsphere
索引。
下面例子中,查找与给定多边形相交的记录:
@Test
public void givenNearbyLocation_whenSearchUsingIntersect_thenFound() {
ArrayList<Position> positions = new ArrayList<Position>();
positions.add(new Position(-0.1439, 51.4952));
positions.add(new Position(-0.1346, 51.4978));
positions.add(new Position(-0.2177, 51.5135));
positions.add(new Position(-0.1439, 51.4952));
Polygon geometry = new Polygon(positions);
FindIterable<Document> result = collection.find(
Filters.geoIntersects("location", geometry));
assertNotNull(result.first());
assertEquals("Hyde Park", result.first().get("name"));
}
对应mongo shell查询:
db.places.find({
location:{
$geoIntersects:{
$geometry:{
type:"Polygon",
coordinates:[
[
[-0.1439, 51.4952],
[-0.1346, 51.4978],
[-0.2177, 51.5135],
[-0.1439, 51.4952]
]
]
}
}
}
})
5. 总结
本文中,我们学习了如何在MongoDB中存储地理空间数据,并研究了2d
索引和2dsphere
索引的区别,以及如何在MongoDB使用地理空间查询。
惯例,完整的示例源码可从GitHub上获取。