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)); FindIterableresult = 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)); FindIterableresult = 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; FindIterableresult = 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; FindIterableresult = 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() { ArrayListpositions = 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 .