sql >> Databáze >  >> NoSQL >> MongoDB

Geoprostorová podpora v MongoDB

1. Přehled

V tomto tutoriálu prozkoumáme geoprostorovou podporu v MongoDB.

Probereme, jak ukládat geoprostorová data, geoindexování a geoprostorové vyhledávání. Použijeme také více geoprostorových vyhledávacích dotazů jako blízko , geoWithin a geoIntersects .

>2. Ukládání geoprostorových dat

Nejprve se podívejme, jak uložit geoprostorová data v MongoDB.

MongoDB podporuje více GeoJSON typy pro ukládání geoprostorových dat. V našich příkladech budeme používat hlavně Point a Mnohoúhelník typy.

2.1. Bod

Toto je nejzákladnější a nejběžnější GeoJSON typ apoužívá se k reprezentaci jednoho konkrétního bodu na mřížce .

Zde máme jednoduchý objekt na našich místech kolekce, který má pole location jako Bod :

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

Všimněte si, že na prvním místě je hodnota zeměpisné délky a poté zeměpisná šířka.

2.2. Mnohoúhelník

Mnohoúhelník je o něco složitější GeoJSON typ.

Můžeme použít Polygon k vymezení oblasti pomocí jejích vnějších hranic a v případě potřeby také vnitřní otvory.

Podívejme se na další objekt, jehož umístění je definováno jako 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"
  }
}

V tomto příkladu jsme definovali pole bodů, které představují vnější hranice. Musíme také uzavřít hranici tak, aby se poslední bod rovnal prvnímu bodu.

Všimněte si, že musíme definovat vnější ohraničující body proti směru hodinových ručiček a hranice děr ve směru hodinových ručiček.

Kromě těchto typů existuje také mnoho dalších typů, jako je LineString, MultiPoint, MultiPolygon, MultiLineString, a GeometryCollection.

3. Geoprostorové indexování

Abychom mohli provádět vyhledávací dotazy na geoprostorová data, která jsme uložili, musíme vytvořit geoprostorový index na naší poloze pole.

V zásadě máme dvě možnosti:2d a 2dsphere .

Nejprve si ale definujme naše místa skolekce :

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

3.1. 2d Geoprostorový index

2d index nám umožňuje provádět vyhledávací dotazy, které fungují na základě výpočtů 2D roviny.

Můžeme vytvořit 2d index na umístění pole v naší aplikaci Java takto:

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

Totéž samozřejmě můžeme udělat v mongo shell:

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

3.2. 2dsphere Geoprostorový index

2dsphere index podporuje dotazy, které fungují na základě výpočtů sfér.

Podobně můžeme vytvořit 2dsphere index v Javě pomocí stejných indexů třída jako výše:

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

Nebo v mongo shell:

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

4. Vyhledávání pomocí geoprostorových dotazů

Nyní, pro vzrušující část, pojďme hledat objekty na základě jejich polohy pomocí geoprostorových dotazů.

4.1. Poblíž dotazu

Začněme s blízkým. Můžeme použít blízko dotaz pro vyhledání míst v dané vzdálenosti.

poblíž dotaz funguje s oběma 2d a 2dsphere indexy.

V dalším příkladu budeme hledat místa, která jsou méně než 1 km a více než 10 metrů od dané pozice:

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

A odpovídající dotaz v mongo shell:

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

Všimněte si, že výsledky jsou seřazeny od nejbližší po nejvzdálenější.

Podobně, pokud použijeme velmi vzdálenou polohu, nenajdeme žádná blízká místa:

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

Máme také nearSphere metoda, která funguje přesně jako blízko, kromě toho počítá vzdálenost pomocí sférické geometrie.

4.2. V rámci dotazu

Dále prozkoumáme geoWithin dotaz.

The geoWithin dotaz nám umožňuje hledat místa, která plně existují v dané geometrii , jako je kruh, rámeček nebo mnohoúhelník. To také funguje s oběma2d a 2dsphere indexy.

V tomto příkladu hledáme místa, která existují v okruhu 5 km od dané středové polohy:

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

Všimněte si, že potřebujeme transformovat vzdálenost z km na radián (stačí vydělit poloměrem Země).

A výsledný dotaz:

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

Dále budeme hledat všechna místa, která existují v obdélníkovém „boxu“. Potřebujeme definovat rámeček jeho levou dolní a pravou horní pozicí:

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

Zde je odpovídající dotaz v mongo shell:

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

A konečně, pokud oblast, ve které chceme hledat, není obdélník nebo kruh, můžeme k definování konkrétnější oblasti použít mnohoúhelník :

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

A zde je odpovídající dotaz:

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

Polygon jsme definovali pouze s jeho vnějšími hranicemi, ale můžeme k němu přidat i díry. Každá díra bude Seznam z bodu s:

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

4.3. Intersect Query

Nakonec se podívejme na geoIntersects dotaz.

geoIntersects dotaz najde objekty, které se alespoň protínají s danou geometrií. Pro srovnání geoWithin najde objekty, které plně existují v dané geometrii .

Tento dotaz funguje s 2dsphere pouze index.

Podívejme se na to v praxi na příkladu hledání jakéhokoli místa, které se protíná s polygonem :

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

Výsledný dotaz:

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. Filtr s ovladačem regex MongoDB C#

  2. Přeložte Queryable<T> zpět do IMongoQuery

  3. Potřebujete pomoc s konceptualizací v Redis/NoSQL

  4. Nutí použití indexu 2dsphere na schéma mongoose, aby bylo vyžadováno pole umístění?