MongoDB
 sql >> Database >  >> NoSQL >> MongoDB

Supporto geospaziale in MongoDB

1. Panoramica

In questo tutorial esploreremo il supporto geospaziale in MongoDB.

Discuteremo come archiviare dati geospaziali, indicizzazione geografica e ricerca geospaziale. Utilizzeremo anche più query di ricerca geospaziali come vicino , geoWithin e geoIntersects .

2. Memorizzazione dei dati geospaziali

Per prima cosa, vediamo come archiviare i dati geospaziali in MongoDB.

MongoDB supporta più GeoJSON tipi per memorizzare i dati geospaziali. Nei nostri esempi utilizzeremo principalmente il Punto e Poligono tipi.

2.1. Punto

Questo è il GeoJSON più semplice e comune digita eè usato per rappresentare un punto specifico sulla griglia .

Qui abbiamo un semplice oggetto, nei nostri posti raccolta, che ha il campo posizione come Punto :

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

Nota che viene prima il valore della longitudine, poi la latitudine.

2.2. Poligono

Poligono è un po' più complesso GeoJSON digitare.

Possiamo usare Polygon per definire un'area con i suoi confini esterni e anche fori interni se necessario.

Vediamo un altro oggetto la cui posizione è definita come Poligono :

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

In questo esempio, abbiamo definito una matrice di punti che rappresentano i limiti esterni. Dobbiamo anche chiudere il limite in modo che l'ultimo punto sia uguale al primo punto.

Si noti che è necessario definire i punti limite esterno in senso antiorario e i limiti del foro in senso orario.

Oltre a questi tipi, ci sono anche molti altri tipi come LineString, Multipunto, Multipoligono, MultiLineString, e Collezione Geometria.

3. Indicizzazione geospaziale

Per eseguire query di ricerca sui dati geospaziali che abbiamo archiviato, dobbiamo creare un indice geospaziale sulla nostra posizione campo.

Fondamentalmente abbiamo due opzioni:2d e 2dsphere .

Ma prima, definiamo i nostri luoghi ccollezione :

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

3.1. 2d Indice geospaziale

Il 2d index ci consente di eseguire query di ricerca che funzionano in base a calcoli del piano 2D.

Possiamo creare un 2d indice sulla posizione campo nella nostra applicazione Java come segue:

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

Ovviamente possiamo fare lo stesso nel mongo guscio:

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

3.2. 2dsfera Indice geospaziale

La 2dsfera index supporta query che funzionano in base ai calcoli della sfera.

Allo stesso modo, possiamo creare una 2dsphere index in Java utilizzando gli stessi Indici classe come sopra:

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

O nel mongo guscio:

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

4. Ricerca tramite query geospaziali

Ora, per la parte interessante, cerchiamo gli oggetti in base alla loro posizione utilizzando le query geospaziali.

4.1. Vicino a Query

Iniziamo con vicino. Possiamo utilizzare il vicino query per cercare luoghi entro una determinata distanza.

Il vicino query funziona con entrambi 2d e 2dsphere indici.

Nel prossimo esempio, cercheremo luoghi che si trovano a meno di 1 km e a più di 10 metri dalla posizione indicata:

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

E la query corrispondente nel mongo guscio:

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

Tieni presente che i risultati vengono ordinati dal più vicino al più lontano.

Allo stesso modo, se utilizziamo una posizione molto lontana, non troveremo alcun luogo nelle vicinanze:

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

Abbiamo anche nearSphere metodo, che agisce esattamente come vicino, tranne che calcola la distanza usando la geometria sferica.

4.2. All'interno della query

Successivamente, esploreremo il geoWithin interrogazione.

Il geoWithin query ci consente di cercare luoghi che esistono completamente all'interno di una determinata Geometria , come un cerchio, una casella o un poligono. Funziona anche con entrambi 2d e 2dsphere indici.

In questo esempio, stiamo cercando luoghi che esistono entro un raggio di 5 km dalla posizione centrale indicata:

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

Nota che dobbiamo trasformare la distanza da km a radianti (dividi solo per il raggio terrestre).

E la query risultante:

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

Successivamente, cercheremo tutti i luoghi che esistono all'interno di una "scatola" rettangolare. Dobbiamo definire la scatola dalla sua posizione in basso a sinistra e in alto a destra:

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

Ecco la query corrispondente in mongo guscio:

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

Infine, se l'area in cui vogliamo effettuare la ricerca non è un rettangolo o un cerchio, possiamo utilizzare un poligono per definire un'area più specifica :

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

Ed ecco la query corrispondente:

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

Abbiamo definito solo un poligono con i suoi limiti esterni, ma possiamo anche aggiungere dei buchi. Ogni buca sarà una Elenco di Punto s:

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

4.3. Interseca query

Infine, diamo un'occhiata ai geoIntersects interrogazione.

I geoIntersects query trova oggetti che almeno si intersecano con una determinata Geometria. In confronto, geoWithin trova oggetti che esistono completamente all'interno di una determinata Geometria .

Questa query funziona con la 2dsphere solo indice.

Vediamolo in pratica, con un esempio di ricerca di qualsiasi luogo che si interseca con un Poligono :

@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 query risultante:

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