1. Обзор
В этом руководстве мы рассмотрим поддержку геопространственных данных в MongoDB.
Мы обсудим, как хранить геопространственные данные, геоиндексацию и геопространственный поиск. Мы также будем использовать несколько геопространственных поисковых запросов, таких как Near
, geoWithin
и geoIntersects
.
2. Хранение геопространственных данных
Во-первых, давайте посмотрим, как хранить геопространственные данные в MongoDB.
MongoDB поддерживает несколько типов GeoJSON
для хранения геопространственных данных. В наших примерах мы в основном будем использовать типы Point
и Polygon .
2.1. Точка
Это самый простой и распространенный тип GeoJSON ,
который используется для представления одной конкретной точки в сетке .
Здесь у нас есть простой объект в нашей коллекции places
,
у которого есть поле location
как Point
:
{
"name": "Big Ben",
"location": {
"coordinates": [-0.1268194, 51.5007292],
"type": "Point"
}
}
Обратите внимание, что значение долготы идет первым, затем широта.
2.2. Полигон
Polygon
— это немного более сложный тип GeoJSON
.
Мы можем использовать Polygon
для определения области с ее внешними границами , а также внутренними отверстиями, если это необходимо.
Давайте посмотрим на другой объект, местоположение которого определено как Polygon
:
{
"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. Геопространственное индексирование
Чтобы выполнять поисковые запросы по сохраненным нами геопространственным данным, нам нужно создать геопространственный индекс в нашем поле местоположения .
В основном у нас есть два варианта: 2d
и 2dsphere
.
Но сначала давайте определим нашу коллекцию places
:
MongoClient mongoClient = new MongoClient();
MongoDatabase db = mongoClient.getDatabase("myMongoDb");
collection = db.getCollection("places");
3.1. 2d
геопространственный индекс
2D
- индекс позволяет нам выполнять поисковые запросы, основанные на расчетах 2D-плоскости.
Мы можем создать двумерный
индекс в поле местоположения
в нашем Java-приложении следующим образом:
collection.createIndex(Indexes.geo2d("location"));
Конечно, мы можем сделать то же самое в оболочке mongo
:
db.places.createIndex({location:"2d"})
3.2. Геопространственный индекс 2dsphere
Индекс 2dsphere
поддерживает запросы, работающие на основе вычислений сфер.
Точно так же мы можем создать индекс 2dsphere
в Java, используя тот же класс Indexes
, что и выше:
collection.createIndex(Indexes.geo2dsphere("location"));
Или в оболочке монго
:
db.places.createIndex({location:"2dsphere"})
4. Поиск с использованием геопространственных запросов
Теперь самое интересное: давайте будем искать объекты по их местоположению с помощью геопространственных запросов.
4.1. Рядом с запросом
Начнем с ближнего.
Мы можем использовать запрос Near
для поиска мест на заданном расстоянии.
Ближайший запрос работает как с индексами 2d
, так и с
индексами 2dsphere
.
``
В следующем примере мы будем искать места, которые находятся менее чем в 1 км и более чем в 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"));
}
И соответствующий запрос в оболочке монго
:
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. В запросе
Далее мы рассмотрим запрос geoWithin
.
Запрос geoWithin
позволяет нам искать места, которые полностью существуют в заданной геометрии
, например круг, прямоугольник или многоугольник. Это также работает с индексами 2d
и 2dsphere
.
В этом примере мы ищем места, которые существуют в радиусе 5 км от заданной центральной позиции:
@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"));
}
Обратите внимание, что нам нужно преобразовать расстояние из км в радианы (просто разделить на радиус Земли).
И результирующий запрос:
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"));
}
Вот соответствующий запрос в оболочке монго
:
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]
]
}
}
})
Мы определили многоугольник только с его внешними границами, но мы также можем добавить к нему дыры. Каждое отверстие будет списком
точек :
geoWithinPolygon("location", points, hole1, hole2, ...)
4.3. Пересечение запроса
Наконец, давайте посмотрим на запрос geoIntersects
.
Запрос geoIntersects
находит объекты, которые хотя бы пересекаются с заданной геометрией.
Для сравнения, geoWithin
находит объекты, которые полностью существуют в данной геометрии
.
Этот запрос работает только с индексом 2dsphere
.
Давайте посмотрим на это на практике, на примере поиска любого места, которое пересекается с Polygon
:
@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"));
}
Результирующий запрос:
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 .