Рубрики
Без рубрики

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

Посмотрите, как хранить, индексировать и искать геопространственные данные с помощью MongoDB

Автор оригинала: baeldung.

1. Обзор

В этом уроке мы рассмотрим геопространственную поддержку в MongoDB.

Мы обсудим, как хранить геопространственные данные, геоиндексирование и геопространственный поиск. Мы также будем использовать несколько геопространственных поисковых запросов, таких как near , geoWithin и geoIntersects .

2. Хранение Геопространственных Данных

Во-первых, давайте посмотрим, как хранить геопространственные данные в MongoDB.

MongoDB поддерживает несколько типов GeoJSON для хранения геопространственных данных. В наших примерах мы будем в основном использовать типы Point и Polygon .

2.1. Пункт

Это самый простой и распространенный тип GeoJSON , и он используется для представления одной конкретной точки на сетке .

Здесь у нас есть простой объект в нашей местах коллекции , который имеет поле местоположение в качестве точки :

{
  "name": "Big Ben",
  "location": {
    "coordinates": [-0.1268194, 51.5007292],
    "type": "Point"
  }
}

Обратите внимание, что сначала значение долготы, а затем широта.

2.2. Полигон

Полигон немного сложнее 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, MultiPolygon, MultiLineString, и GeometryCollection.

3. Геопространственная индексация

Для выполнения поисковых запросов по геопространственным данным, которые мы храним, нам необходимо создать геопространственный индекс на нашем сайте. местоположение поле.

У нас в основном есть два варианта: 2d и 2dsphere .

Но сначала давайте определим наши места c ollection :

MongoClient mongoClient = new MongoClient();
MongoDatabase db = mongoClient.getDatabase("myMongoDb");
collection = db.getCollection("places");

3.1. 2d Геопространственный индекс

Индекс 2d позволяет нам выполнять поисковые запросы, которые работают на основе вычислений 2d-плоскости.

Мы можем создать 2d индекс в поле location в нашем Java-приложении следующим образом:

collection.createIndex(Indexes.geo2d("location"));

Конечно, мы можем сделать то же самое в оболочке mongo :

db.places.createIndex({location:"2d"})

3.2. Геопространственный индекс 2dsphere

Индекс 2dsphere поддерживает запросы, которые работают на основе вычислений сферы.

Аналогично, мы можем создать индекс 2dsphere в Java, используя тот же Индексы класс, как указано выше:

collection.createIndex(Indexes.geo2dsphere("location"));

Или в оболочке mongo :

db.places.createIndex({location:"2dsphere"})

4. Поиск С Использованием Геопространственных Запросов

Теперь, для захватывающей части, давайте искать объекты на основе их местоположения с помощью геопространственных запросов.

4.1. Ближний запрос

Давайте начнем с рядом. Мы можем использовать запрос near для поиска мест на заданном расстоянии.

Запрос near работает как с 2d , так и с 2dsphere индексами .

В следующем примере мы будем искать места, которые находятся менее чем в 1 км и более чем в 10 метрах от заданной позиции:

@Test
public void givenNearbyLocation_whenSearchNearby_thenFound() {
    Point currentLoc = new Point(new Position(-0.126821, 51.495885));
 
    FindIterable result = collection.find(
      Filters.near("location", currentLoc, 1000.0, 10.0));

    assertNotNull(result.first());
    assertEquals("Big Ben", result.first().get("name"));
}

И соответствующий запрос в оболочке mongo :

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 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 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 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> points = new ArrayList>();
    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 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]
      ]
    }
  }
})

Мы определили только многоугольник с его внешними границами, но мы также можем добавить к нему отверстия. Каждое отверстие будет представлять собой Список из точек s:

geoWithinPolygon("location", points, hole1, hole2, ...)

4.3. Пересекающийся запрос

Наконец, давайте рассмотрим запрос geoIntersects .

Запрос geoIntersects находит объекты, которые, по крайней мере, пересекаются с заданной геометрией. Для сравнения, geoWithin находит объекты, которые полностью существуют в пределах заданной Геометрии .

Этот запрос работает только с индексом 2dsphere .

Давайте посмотрим на это на практике, на примере поиска любого места, которое пересекается с Многоугольником :

@Test
public void givenNearbyLocation_whenSearchUsingIntersect_thenFound() {
    ArrayList positions = new ArrayList();
    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 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 .