Geodatenunterstützung in MongoDB

1. Übersicht

In diesem Tutorial werden wir die Geospatial-Unterstützung in MongoDB untersuchen.

Wir werden diskutieren, wie Geodaten, Geoindexierung und Geodatensuche gespeichert werden. Wir werden auch mehrere räumliche Suchanfragen wie Near , GeoWithin und GeoIntersects verwenden .

2. Speichern von Geodaten

Lassen Sie uns zunächst sehen, wie Geodaten in MongoDB gespeichert werden.

MongoDB unterstützt mehrere GeoJSON- Typen zum Speichern von Geodaten. In unseren Beispielen werden hauptsächlich die Typen Punkt und Polygon verwendet.

2.1. Punkt

Dies ist der grundlegendste und gebräuchlichste GeoJSON- Typ und wird verwendet, um einen bestimmten Punkt im Raster darzustellen .

Hier haben wir ein einfaches Objekt haben, in unseren Ort Sammlung , hat das Feld Standort als Punkt :

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

Beachten Sie, dass der Längengrad zuerst und dann der Breitengrad an erster Stelle steht.

2.2. Polygon

Polygon ist ein etwas komplexerer GeoJSON- Typ.

Wir können Polygon verwenden , um einen Bereich mit seinen Außenrändern und bei Bedarf auch Innenlöchern zu definieren .

Sehen wir uns ein anderes Objekt an, dessen Position als Polygon definiert ist :

{ "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 diesem Beispiel haben wir ein Array von Punkten definiert, die äußere Grenzen darstellen. Wir müssen auch die Grenze schließen, damit der letzte Punkt dem ersten Punkt entspricht.

Beachten Sie, dass wir die äußeren Begrenzungspunkte gegen den Uhrzeigersinn und die Lochbegrenzungen im Uhrzeigersinn definieren müssen.

Neben diesen Typen gibt es auch viele andere Typen wie LineString, MultiPoint, MultiPolygon, MultiLineString und GeometryCollection.

3. Geospatial Indexing

Um Suchanfragen für die von uns gespeicherten Geodaten durchzuführen, müssen wir einen Geodatenindex für unser Standortfeld erstellen .

Grundsätzlich haben wir zwei Möglichkeiten: 2d und 2dsphere .

Aber zuerst definieren wir unsere Ortssammlung :

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

3.1. 2d Geospatial Index

Der 2d- Index ermöglicht es uns, Suchabfragen durchzuführen, die auf Berechnungen der 2d-Ebene basieren.

Wir können einen 2D- Index für das Standortfeld in unserer Java-Anwendung wie folgt erstellen :

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

Natürlich können wir dasselbe in der Mongo- Shell tun :

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

3.2. 2dsphere Geospatial Index

Der 2dsphere- Index unterstützt Abfragen, die auf Kugelberechnungen basieren.

Ebenso können wir in Java einen 2dsphere- Index mit derselben Indexes- Klasse wie oben erstellen :

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

Oder in der Mongo- Muschel:

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

4. Suchen mit Geodatenabfragen

Lassen Sie uns nun für den aufregenden Teil anhand von räumlichen Abfragen nach Objekten suchen, die auf ihrem Standort basieren.

4.1. Near Query

Beginnen wir mit Near. Wir können die Near- Abfrage verwenden, um nach Orten innerhalb einer bestimmten Entfernung zu suchen.

Die in der Nähe von Abfrage funktioniert sowohl mit 2d und 2dsphere Indizes .

Im nächsten Beispiel suchen wir nach Orten, die weniger als 1 km und mehr als 10 Meter von der angegebenen Position entfernt sind:

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

Und die entsprechende Abfrage in der Mongo- Shell:

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

Beachten Sie, dass die Ergebnisse vom nächsten zum weitesten sortiert sind.

Wenn wir einen sehr weit entfernten Ort verwenden, werden wir auch keine Orte in der Nähe finden:

@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.

Die Abfrage geoIntersects findet Objekte, die sich zumindest mit einer bestimmten Geometrie überschneiden . Im Vergleich dazu findet geoWithin Objekte, die innerhalb einer bestimmten Geometrie vollständig vorhanden sind .

Diese Abfrage funktioniert nur mit dem 2dsphere- Index.

Lassen Sie uns dies in der Praxis anhand eines Beispiels für die Suche nach einem Ort sehen, der sich mit einem Polygon schneidet :

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

Die resultierende Abfrage:

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. Schlussfolgerung

In diesem Artikel haben wir gelernt, wie Geodaten in MongoDB gespeichert werden, und den Unterschied zwischen Geodatenindizes von 2d und 2dsphere untersucht . Wir haben auch gelernt, wie man in MongoDB mithilfe von Geodatenabfragen sucht.

Wie üblich ist der vollständige Quellcode für die Beispiele auf GitHub verfügbar.