MongoDB
 sql >> Datenbank >  >> NoSQL >> MongoDB

Geodatenunterstützung in MongoDB

1. Übersicht

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

Wir besprechen das Speichern von Geodaten, die Geoindizierung und die Geodatensuche. Wir verwenden auch mehrere Geodaten-Suchanfragen wie in der Nähe , geoWithin und geoIntersects .

2. Speichern von Geodaten

Sehen wir uns zunächst an, wie Geodaten in MongoDB gespeichert werden.

MongoDB unterstützt mehrere GeoJSON Typen zum Speichern von Geodaten. In unseren Beispielen verwenden wir hauptsächlich den Punkt und Polygon Typen.

2.1. Punkt

Dies ist das grundlegendste und gebräuchlichste GeoJSON Typ, undwird verwendet, um einen bestimmten Punkt auf dem Raster darzustellen .

Hier haben wir ein einfaches Objekt an unseren Orten Sammlung, das das Feld location hat als Punkt :

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

Beachten Sie, dass der Längengrad zuerst kommt, dann der Breitengrad.

2.2. Polygon

Polygon ist etwas komplexer GeoJSON Typ.

Wir können Polygon verwenden ein Gebiet mit seinen Außengrenzen zu definieren und bei Bedarf auch Innenlöcher.

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. Außerdem müssen wir die Grenze schließen, sodass der letzte Punkt gleich dem ersten Punkt ist.

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

Zusätzlich zu diesen Typen gibt es noch viele andere Typen wie LineString, MultiPoint, MultiPolygon, MultiLineString, und GeometryCollection.

3. Geodaten-Indizierung

Um Suchanfragen zu den von uns gespeicherten Geodaten durchzuführen, müssen wir einen Geoindex für unseren Standort erstellen Feld.

Wir haben grundsätzlich zwei Möglichkeiten:2d und 2dsphere .

Aber zuerst definieren wir unsere Ortskollektion :

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

3.1. 2d Geospatial-Index

Die 2d index ermöglicht es uns, Suchanfragen durchzuführen, die auf 2D-Ebenenberechnungen basieren.

Wir können ein 2d erstellen Index am Standort Feld in unserer Java-Anwendung wie folgt:

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

Natürlich können wir dasselbe im mongo tun Schale:

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

3.2. 2dsphere Geospatial-Index

Die 2dsphere index unterstützt Abfragen, die auf Kugelberechnungen basieren.

Auf ähnliche Weise können wir eine 2dsphere erstellen Index in Java unter Verwendung derselben Indizes Klasse wie oben:

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

Oder im Mongo Schale:

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

4. Suchen mit Geodatenabfragen

Nun, für den spannenden Teil, lassen Sie uns mithilfe von Geodatenabfragen nach Objekten anhand ihres Standorts suchen.

4.1. In der Nähe von Abfrage

Beginnen wir mit in der Nähe. Wir können die nahe verwenden Abfrage, um nach Orten innerhalb einer bestimmten Entfernung zu suchen.

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

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

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

Und die entsprechende Abfrage im mongo Schale:

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 am weitesten entfernten sortiert sind.

Wenn wir einen sehr weit entfernten Standort verwenden, finden wir in ähnlicher Weise keine Orte in der Nähe:

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

Wir haben auch die nearSphere -Methode, die sich genauso verhält wie near, außer es berechnet die Entfernung unter Verwendung der sphärischen Geometrie.

4.2. Innerhalb der Abfrage

Als Nächstes erkunden wir geoWithin Abfrage.

Das geoWithin -Abfrage ermöglicht es uns, nach Orten zu suchen, die innerhalb einer bestimmten Geometrie vollständig existieren , wie ein Kreis, eine Box oder ein Polygon. Dies funktioniert auch mit beiden 2d und 2dsphere Indizes.

In diesem Beispiel suchen wir nach Orten, die sich in einem Umkreis von 5 km um die angegebene Mittelposition befinden:

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

Beachten Sie, dass wir die Entfernung von km in Radiant umwandeln müssen (einfach durch den Erdradius dividieren).

Und die resultierende Abfrage:

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

Als nächstes suchen wir nach allen Orten, die innerhalb einer rechteckigen „Box“ existieren. Wir müssen die Box durch ihre untere linke Position und obere rechte Position definieren:

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

Hier ist die entsprechende Abfrage in mongo Schale:

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

Schließlich, wenn der Bereich, in dem wir suchen möchten, kein Rechteck oder Kreis ist, können wir ein Polygon verwenden, um einen spezifischeren Bereich zu definieren :

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

Und hier ist die entsprechende Abfrage:

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

Wir haben ein Polygon nur mit seinen äußeren Begrenzungen definiert, aber wir können ihm auch Löcher hinzufügen. Jedes Loch wird eine Liste sein von Punkt s:

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

4.3. Intersect-Abfrage

Sehen wir uns abschließend die geoIntersects an Abfrage.

Die geoIntersects -Abfrage findet Objekte, die sich mindestens mit einer bestimmten Geometrie. schneiden Zum Vergleich:geoWithin findet Objekte, die innerhalb einer gegebenen Geometrie vollständig existieren .

Diese Abfrage funktioniert mit der 2dsphere nur Index.

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

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

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