sql >> Base de Datos >  >> NoSQL >> MongoDB

Soporte Geoespacial en MongoDB

1. Resumen

En este tutorial, exploraremos el soporte geoespacial en MongoDB.

Discutiremos cómo almacenar datos geoespaciales, indexación geográfica y búsqueda geoespacial. También usaremos múltiples consultas de búsqueda geoespacial como cerca , geodentro y intersecciones geográficas .

2. Almacenamiento de datos geoespaciales

Primero, veamos cómo almacenar datos geoespaciales en MongoDB.

MongoDB admite múltiples GeoJSON tipos para almacenar datos geoespaciales. A lo largo de nuestros ejemplos, usaremos principalmente el Punto y Polígono tipos.

2.1. Punto

Este es el GeoJSON más básico y común tipo, y se usa para representar un punto específico en la cuadrícula .

Aquí, tenemos un objeto simple, en nuestros lugares colección, que tiene el campo ubicación como un Punto :

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

Tenga en cuenta que el valor de longitud viene primero, luego la latitud.

2.2. Polígono

Polígono es un poco más complejo GeoJSON tipo.

Podemos usar Polígono para definir un área con sus bordes exteriores y también agujeros interiores si es necesario.

Veamos otro objeto que tiene su ubicación definida como un Polígono :

{
  "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"
  }
}

En este ejemplo, definimos una matriz de puntos que representan límites exteriores. También tenemos que cerrar el límite para que el último punto sea igual al primer punto.

Tenga en cuenta que debemos definir los puntos de los límites exteriores en el sentido contrario a las agujas del reloj y los límites de los agujeros en el sentido de las agujas del reloj.

Además de estos tipos, también hay muchos otros tipos como LineString, Multipunto, Multipolígono, Cadena de varias líneas, y GeometryCollection.

3. Indexación geoespacial

Para realizar consultas de búsqueda en los datos geoespaciales que almacenamos, debemos crear un índice geoespacial en nuestra ubicación campo.

Básicamente tenemos dos opciones:2d y 2dsphere .

Pero primero, definamos nuestra colección de lugares colección :

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

3.1. 2d Índice geoespacial

El 2d index nos permite realizar consultas de búsqueda que funcionan en base a cálculos de plano 2d.

Podemos crear un 2d índice en la ubicación campo en nuestra aplicación Java de la siguiente manera:

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

Por supuesto, podemos hacer lo mismo en el mongo concha:

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

3.2. 2dsfera Índice geoespacial

La 2dsfera index admite consultas que funcionan en función de cálculos de esferas.

Del mismo modo, podemos crear una 2dsphere índice en Java usando los mismos Índices clase como arriba:

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

O en el mongo concha:

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

4. Búsqueda mediante consultas geoespaciales

Ahora, para la parte interesante, busquemos objetos en función de su ubicación mediante consultas geoespaciales.

4.1. Consulta cercana

Comencemos con cerca. Podemos usar el cerca consulta para buscar lugares dentro de una distancia dada.

El cerca consulta funciona con ambos 2d y 2desfera índices.

En el siguiente ejemplo, buscaremos lugares que estén a menos de 1 km y más de 10 metros de la posición dada:

@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"));
}

Y la consulta correspondiente en el mongo concha:

db.places.find({
  location: {
    $near: {
      $geometry: {
        type: "Point",
        coordinates: [-0.126821, 51.495885]
      },
      $maxDistance: 1000,
      $minDistance: 10
    }
  }
})

Tenga en cuenta que los resultados se ordenan de más cercano a más lejano.

Del mismo modo, si usamos una ubicación muy lejana, no encontraremos ningún lugar cercano:

@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());
}

También tenemos el nearSphere método, que actúa exactamente como near, excepto que calcula la distancia usando geometría esférica.

4.2. Dentro de la consulta

A continuación, exploraremos el geoWithin consulta.

El geoDentro la consulta nos permite buscar lugares que existen completamente dentro de una Geometría determinada , como un círculo, un cuadro o un polígono. Esto también funciona con 2d y 2desfera índices.

En este ejemplo, estamos buscando lugares que existen dentro de un radio de 5 km desde la posición central dada:

@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"));
}

Tenga en cuenta que necesitamos transformar la distancia de km a radianes (simplemente divida por el radio de la Tierra).

Y la consulta resultante:

db.places.find({
  location: {
    $geoWithin: {
      $centerSphere: [
        [-0.1435083, 51.4990956],
        0.0007848061528802386
      ]
    }
  }
})

A continuación, buscaremos todos los lugares que existen dentro de un "cuadro" rectangular. Necesitamos definir el cuadro por su posición inferior izquierda y su posición superior derecha:

@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"));
}

Aquí está la consulta correspondiente en mongo concha:

db.places.find({
  location: {
    $geoWithin: {
      $box: [
        [-0.1427638, 51.4991288],
        [-0.1256209, 51.5030272]
      ]
    }
  }
})

Finalmente, si el área en la que queremos buscar no es un rectángulo o un círculo, podemos usar un polígono para definir un área más específica :

@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"));
}

Y aquí está la consulta correspondiente:

db.places.find({
  location: {
    $geoWithin: {
      $polygon: [
        [-0.1439, 51.4952],
        [-0.1121, 51.4989],
        [-0.13, 51.5163],
        [-0.1439, 51.4952]
      ]
    }
  }
})

Solo definimos un polígono con sus límites exteriores, pero también podemos agregarle agujeros. Cada hoyo será una Lista de Punto s:

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

4.3. Consulta de intersección

Finalmente, echemos un vistazo a los geoIntersects consulta.

Las intersecciones geográficas la consulta encuentra objetos que al menos se cruzan con una Geometría determinada. En comparación, geoWithin encuentra objetos que existen completamente dentro de una Geometría determinada .

Esta consulta funciona con 2dsphere solo índice.

Veamos esto en la práctica, con un ejemplo de búsqueda de cualquier lugar que se cruce con un Polígono :

@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"));
}

La consulta resultante:

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]
          ]
        ]
      }
    }
  }
})