Перейти к основному содержимому

Геопространственная поддержка в MongoDB

· 6 мин. чтения

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 .