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 geospaziale come near , geoWithin e geoIntersects .

2. Memorizzazione dei dati geospaziali

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

MongoDB supporta più tipi GeoJSON per archiviare i dati geospaziali. In tutti i nostri esempi, utilizzeremo principalmente i tipi Point e Polygon .

2.1. Punto

Questo è il tipo GeoJSON più semplice e comune e viene utilizzato per rappresentare un punto specifico sulla griglia .

Qui, abbiamo un oggetto semplice, nella nostra raccolta di luoghi , che ha la posizione del campo come Punto :

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

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

2.2. Poligono

Polygon è un tipo GeoJSON leggermente più complesso .

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

Vediamo un altro oggetto che ha la sua posizione definita come 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" } }

In questo esempio, abbiamo definito una serie 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 dei limiti esterni in senso antiorario e i limiti dei fori in senso orario.

Oltre a questi tipi, esistono anche molti altri tipi come LineString, MultiPoint, MultiPolygon, MultiLineString e GeometryCollection.

3. Indicizzazione geospaziale

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

Fondamentalmente abbiamo due opzioni: 2d e 2dsphere .

Ma prima, definiamo la raccolta dei nostri luoghi :

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

3.1. Indice geospaziale 2d

L' indice 2d ci consente di eseguire query di ricerca che funzionano sulla base di calcoli 2d plane.

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

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

Ovviamente, possiamo fare lo stesso nel mongo shell:

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

3.2. Indice geospaziale 2dsphere

L' indice 2dsphere supporta le query che funzionano in base ai calcoli della sfera.

Allo stesso modo, possiamo creare un indice 2dsphere in Java utilizzando la stessa classe Indexes di cui sopra:

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

O nel guscio di mongo :

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

4. Ricerca tramite query geospaziali

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

4.1. Near Query

Cominciamo con near. Possiamo usare la query vicino per cercare luoghi entro una data distanza.

La query near funziona sia con gli indici 2d che con quelli 2dsphere .

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

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

E la query corrispondente nella mongo shell:

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

Notare 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 result = collection.find( Filters.near("location", currentLoc, 5000.0, 10.0)); assertNull(result.first()); }

We also have the nearSphere method, which acts exactly like near, except it calculates the distance using spherical geometry.

4.2. Within Query

Next, we'll explore the geoWithin query.

The geoWithin query enables us to search for places that fully exist within a given Geometry, like a circle, box, or polygon. This also works with both 2d and 2dsphere indices.

In this example, we're looking for places that exist within a 5 km radius from the given center position:

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

Note that we need to transform the distance from km to radian (just divide by Earth's radius).

And the resulting query:

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

Next, we'll search for all places that exist within a rectangle “box”. We need to define the box by its lower left position and upper right position:

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

Here's the corresponding query in mongo shell:

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

Finally, if the area we want to search within isn't a rectangle or a circle, we can use a polygon to define a more specific area:

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

And here's the corresponding query:

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

We only defined a polygon with its exterior bounds, but we can also add holes to it. Each hole will be a List of Points:

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

4.3. Intersect Query

Finally, let's look at the geoIntersects query.

La query geoIntersects trova oggetti che almeno si intersecano con una data geometria. In confronto, geoWithin trova oggetti che esistono completamente all'interno di una data geometria .

Questa query funziona solo con l' indice 2dsphere .

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

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

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

5. conclusione

In questo articolo, abbiamo imparato come archiviare i dati geospaziali in MongoDB e abbiamo esaminato la differenza tra gli indici geospaziali 2d e 2dsphere . Abbiamo anche imparato a cercare in MongoDB utilizzando query geospaziali.

Come al solito, il codice sorgente completo per gli esempi è disponibile su GitHub.