sql >> Databasteknik >  >> NoSQL >> MongoDB

Geospatialt stöd i MongoDB

1. Översikt

I den här handledningen kommer vi att utforska det geospatiala stödet i MongoDB.

Vi kommer att diskutera hur man lagrar geospatial data, geoindexering och geospatial sökning. Vi kommer också att använda flera geospatiala sökfrågor som nära , geoWithin , och geoIntersects .

2. Lagring av geospatial data

Låt oss först se hur man lagrar geospatial data i MongoDB.

MongoDB stöder flera GeoJSON typer för att lagra geospatial data. I våra exempel kommer vi främst att använda Point och Polygon typer.

2.1. Peka

Detta är den mest grundläggande och vanliga GeoJSON typ, ochden används för att representera en specifik punkt på rutnätet .

Här har vi ett enkelt objekt, på våra platser samling, som har fältet plats som en Punkt :

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

Observera att longitudvärdet kommer först, sedan latituden.

2.2. Polygon

Polygon är lite mer komplex GeoJSON typ.

Vi kan använda Polygon att definiera ett område med dess yttre gränser och även invändiga hål om det behövs.

Låt oss se ett annat objekt som har sin plats definierad som en 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"
  }
}

I det här exemplet definierade vi en rad punkter som representerar yttre gränser. Vi måste också stänga gränsen så att den sista punkten är lika med den första punkten.

Observera att vi måste definiera de yttre gränspunkterna moturs och hålgränserna i medurs riktning.

Utöver dessa typer finns det också många andra typer som LineString, MultiPoint, MultiPolygon, MultiLineString, och GeometryCollection.

3. Geospatial indexering

För att utföra sökfrågor på de geospatiala data vi lagrade måste vi skapa ett geospatialt index på vår plats fält.

Vi har i princip två alternativ:2d och 2dsphere .

Men först, låt oss definiera vår platsförsamling :

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

3.1. 2d Geospatialt index

2d index gör det möjligt för oss att utföra sökfrågor som fungerar baserat på 2d-plansberäkningar.

Vi kan skapa en 2d index på platsen fältet i vår Java-applikation enligt följande:

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

Naturligtvis kan vi göra detsamma i mongo skal:

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

3.2. 2dsphere Geospatialt index

2dsphere index stöder frågor som fungerar baserat på sfärberäkningar.

På liknande sätt kan vi skapa en 2dsphere index i Java med samma index klass enligt ovan:

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

Eller i mongo skal:

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

4. Söka med geospatiala frågor

Nu, för den spännande delen, låt oss söka efter objekt baserat på deras plats med hjälp av geospatiala frågor.

4.1. Nära Fråga

Låt oss börja med nära. Vi kan använda nära fråga för att söka efter platser inom ett givet avstånd.

nära frågan fungerar med både 2d och 2dsphere index.

I nästa exempel söker vi efter platser som är mindre än 1 km och mer än 10 meter från den angivna positionen:

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

Och motsvarande fråga i mongo skal:

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

Observera att resultaten sorteras från närmaste till längst bort.

På samma sätt, om vi använder en mycket långt borta plats, hittar vi inga platser i närheten:

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

Vi har även nearSphere metod, som fungerar precis som nära, förutom att den beräknar avståndet med sfärisk geometri.

4.2. Inom Query

Därefter ska vi utforska geoWithin fråga.

The geoWithin query gör det möjligt för oss att söka efter platser som helt existerar inom en given Geometri , som en cirkel, ruta eller polygon. Detta fungerar också med både 2d och 2dsphere index.

I det här exemplet letar vi efter platser som finns inom en radie på 5 km från den givna mittpositionen:

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

Observera att vi måste omvandla avståndet från km till radian (dela bara med jordens radie).

Och den resulterande frågan:

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

Därefter kommer vi att söka efter alla platser som finns inom en rektangel "ruta". Vi måste definiera rutan genom dess nedre vänstra position och övre högra position:

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

Här är motsvarande fråga i mongo skal:

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

Slutligen, om området vi vill söka inom inte är en rektangel eller en cirkel, kan vi använda en polygon för att definiera ett mer specifikt område :

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

Och här är motsvarande fråga:

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

Vi definierade bara en polygon med dess yttre gränser, men vi kan också lägga till hål till den. Varje hål kommer att vara en lista av Punkt s:

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

4.3. Skärningsfråga

Slutligen, låt oss titta på geoIntersects fråga.

The geoIntersects query hittar objekt som åtminstone skär en given Geometri. Som jämförelse, geoWithin hittar objekt som helt existerar inom en given geometri .

Den här frågan fungerar med 2dsphere endast index.

Låt oss se detta i praktiken, med ett exempel på att leta efter vilken plats som helst som skär en Polygon :

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

Den resulterande frågan:

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

  1. Ändra typ av fält inuti mongoDB-aggregation och använder $lookup index på fält eller inte?

  2. Apache Phoenix för CDH

  3. MongoDB $toLower

  4. Kopiera/klona en samling i MongoDB