Prise en charge géospatiale dans MongoDB

1. Vue d'ensemble

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

Nous discuterons de la manière de stocker des données géospatiales, de l'indexation géographique et de la recherche géospatiale. Nous utiliserons également plusieurs requêtes de recherche géospatiale telles que near , geoWithin et geoIntersects .

2. Stockage des données géospatiales

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

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

2.1. Point

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

Ici, nous avons un objet simple, dans notre collection de lieux , qui a un emplacement de champ comme 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

Polygon est un type GeoJSON un peu plus complexe .

Nous pouvons utiliser Polygon pour définir une zone avec ses bordures extérieures et également des trous intérieurs si nécessaire.

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 les 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 antihoraire et les limites des trous dans le sens horaire.

En plus de ces types, il existe également de nombreux autres types tels que LineString, MultiPoint, MultiPolygon, 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 champ de localisation .

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

Mais d' abord, nous allons définir nos lieux c ollection :

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

3.1. 2d Index géospatiale

L' index 2d nous permet d'effectuer des requêtes de recherche qui fonctionnent sur la base de calculs de plan 2D.

Nous pouvons créer un index 2d sur le champ de localisation dans notre application Java comme suit:

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

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

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

3.2. Indice géospatial 2dsphere

L' index 2dsphere prend en charge les requêtes qui fonctionnent sur la base de calculs de sphères.

De même, nous pouvons créer un index 2dsphere en Java en utilisant la même classe Indexes que ci-dessus:

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

Ou dans la coque mongo :

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. Requête à proximité

Commençons par près. Nous pouvons utiliser la requête de proximité pour rechercher des lieux à une distance donnée.

La requête proche fonctionne avec les index 2d et 2dsphere .

Dans l'exemple suivant, nous rechercherons 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 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 shell mongo :

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 endroit à proximité:

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

La requête geoIntersects recherche les objets qui au moins se croisent avec une géométrie donnée . Par comparaison, geoWithin trouve des objets qui existent pleinement dans une géométrie donnée .

Cette requête fonctionne uniquement avec l' index 2dsphere .

Voyons cela en pratique, avec un exemple de recherche de tout endroit qui croise un polygone :

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

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

5. Conclusion

Dans cet article, nous avons appris à stocker des données géospatiales dans MongoDB et examiné la différence entre les indices géospatiaux 2d et 2dsphere . Nous avons également appris à rechercher dans MongoDB à l'aide de requêtes géospatiales.

Comme d'habitude, le code source complet des exemples est disponible à l'adresse over sur GitHub.