MongoDB
 sql >> Base de données >  >> NoSQL >> MongoDB

Prise en charge géospatiale dans MongoDB

1. Présentation

Dans ce didacticiel, nous allons explorer la prise en charge géospatiale dans MongoDB.

Nous verrons comment stocker des données géospatiales, l'indexation géographique et la recherche géospatiale. Nous utiliserons également plusieurs requêtes de recherche géospatiales telles que à proximité , géoDans , et géoIntersections .

2. Stockage des données géospatiales

Voyons d'abord comment stocker des données géospatiales dans MongoDB.

MongoDB prend en charge plusieurs GeoJSON types pour stocker des données géospatiales. Tout au long de nos exemples, nous utiliserons principalement le Point et Polygone type.

2.1. Pointer

C'est le GeoJSON le plus basique et le plus courant type, etil est utilisé pour représenter un point spécifique sur la grille .

Ici, nous avons un objet simple, à nos places collecte, qui a le champ lieu en tant que Point :

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

Notez que la valeur de longitude vient en premier, puis la latitude.

2.2. Polygone

Polygone est un peu plus complexe GeoJSON saisir.

Nous pouvons utiliser Polygon délimiter une zone avec ses bordures extérieures et aussi des trous intérieurs si besoin.

Voyons un autre objet dont l'emplacement est défini comme un polygone :

{
  "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"
  }
}

Dans cet exemple, nous avons défini un tableau de points qui représentent des limites extérieures. Nous devons également fermer la borne pour que le dernier point soit égal au premier point.

Notez que nous devons définir les points des limites extérieures dans le sens inverse des aiguilles d'une montre et les limites du trou dans le sens des aiguilles d'une montre.

En plus de ces types, il existe également de nombreux autres types comme LineString, Multipoint, Multipolygone, MultiLineString, et GeometryCollection.

3. Indexation géospatiale

Pour effectuer des requêtes de recherche sur les données géospatiales que nous avons stockées, nous devons créer un index géospatial sur notre emplacement champ.

Nous avons essentiellement deux options :2d et 2dsphere .

Mais d'abord, définissons nos lieux collection :

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

3.1. 2j Index géospatial

Le 2d index nous permet d'effectuer des requêtes de recherche basées sur des calculs de plan 2D.

Nous pouvons créer un 2d index sur l'emplacement champ dans notre application Java comme suit :

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

Bien sûr, nous pouvons faire la même chose dans le mongo coque :

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

3.2. 2dsphère Index géospatial

La 2dsphère index prend en charge les requêtes qui fonctionnent sur la base de calculs de sphère.

De même, nous pouvons créer une 2dsphere index en Java en utilisant les mêmes index classe comme ci-dessus :

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

Ou dans le mongo coque :

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

4. Recherche à l'aide de requêtes géospatiales

Maintenant, pour la partie passionnante, recherchons des objets en fonction de leur emplacement à l'aide de requêtes géospatiales.

4.1. À proximité de la requête

Commençons par near. Nous pouvons utiliser le proche requête pour rechercher des lieux à une distance donnée.

Le près de la requête fonctionne avec les deux 2d et 2dsphère indices.

Dans l'exemple suivant, nous allons rechercher des lieux situés à moins de 1 km et à plus de 10 mètres de la position donnée :

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

Et la requête correspondante dans le mongo coque :

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

Notez que les résultats sont triés du plus proche au plus éloigné.

De même, si nous utilisons un emplacement très éloigné, nous ne trouverons aucun lieu à proximité :

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

Nous avons aussi le nearSphere méthode, qui agit exactement comme near, sauf qu'il calcule la distance en utilisant la géométrie sphérique.

4.2. Dans la requête

Ensuite, nous allons explorer le geoWithin requête.

Le geoWithin la requête nous permet de rechercher des lieux qui existent pleinement dans une Géométrie donnée , comme un cercle, une boîte ou un polygone. Cela fonctionne également avec les deux 2d et 2dsphère indices.

Dans cet exemple, nous recherchons des lieux qui existent dans un rayon de 5 km autour de la position centrale donnée :

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

Notez que nous devons transformer la distance de km en radian (il suffit de diviser par le rayon de la Terre).

Et la requête résultante :

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

Ensuite, nous allons rechercher tous les lieux qui existent dans une "boîte" rectangulaire. Nous devons définir la boîte par sa position en bas à gauche et sa position en haut à droite :

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

Voici la requête correspondante en mongo coque :

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

Enfin, si la zone dans laquelle nous voulons rechercher n'est pas un rectangle ou un cercle, nous pouvons utiliser un polygone pour définir une zone plus spécifique :

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

Et voici la requête correspondante :

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

Nous avons défini un polygone uniquement avec ses limites extérieures, mais nous pouvons également lui ajouter des trous. Chaque trou sera une Liste de Point s :

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

4.3. Requête d'intersection

Enfin, regardons les geoIntersects requête.

Les géointersections la requête trouve des objets qui se croisent au moins avec une Géométrie. donnée En comparaison, geoWithin trouve des objets qui existent pleinement dans une Géométrie donnée .

Cette requête fonctionne avec la 2dsphère index uniquement.

Voyons cela en pratique, avec un exemple de recherche de n'importe quel endroit qui croise un polygone :

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

La requête résultante :

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