MongoDB

MongoDB Geospatial Query Operators Example

1. Introduction to Geospatial Queries

In this post, we feature a comprehensive Example on MongoDB Geospatial Query Operators. MongoDB is a JSON based document storage database. MongoDB stores the records in the form of unstructured JSON documents. It allows the flexibility of having varying number of columns for every record. This feature of MongoDB has been put to action by the Geospatial queries. Geospatial queries, as the name indicates, are queries used to query data using the geological data. These queries help you find places within a specific geospatial area. In addition to querying places, MongoDB is powerful and smart enough to calculate minimum or maximum distance, a geometry around a specific area and also able to find the centre coordinates of a specific region.

2. About GeoJSON

All the Geospatial queries supported by MongoDB are supported using GeoJSON. GeoJSON is a geography related data available in JSON format. A GeoJSON basically contains the details about the coordinates of various places. It is a defined standard to store details of any location. With the maps becoming a need for the day, GeoJSON is being used extensively in applications where location feature is being leveraged.

There are various types of GeoJSON objects possible. These are as listed below:

  • Point
  • LineString
  • Polygon
  • MultiLine
  • MultiLine String
  • MultiPolygon
  • GeometryCollection

2.1 Point

As the name suggests, a point is used to store the exact location or an address. A GeoJSON Point can be defined as shown below.

{ type: "Point", coordinates: [ 1, 2 ] }

As it can be seen from the above example, here the attribute type stands for the type of GeoJSON object while the coordinates confirm which is the object.

2.2 LineString

A LineString is a combination of two points forming a line. A LineString could be used to define boundaries, walls, barriers as well as roads. The possible use cases of LineString can be to find intersections in roads. A GeoJSON LineString can be defined as shown below.

{ type: "LineString", coordinates: [ [ 4, 5 ], [ 5, 6 ] ] }

The array of two points indicate a line geometrically. A GeoJSON LineString can be stored in this manner. A query can be executed to detect the entries crossing this line or overlapping a certain area.

2.3 Polygon

A Polygon is a combination of LineStrings forming a closed area. A Polygon can be defined using multiple points forming a closed loop. For instance, a pentagon can have coordinates. A Polygon is necessarily used to define an area covered by a city, state or a country. A Polygon string will help us locate areas within a specific boundary. A GeoJSON Polgon can be defined as shown below.

{
  type: "Polygon",
  coordinates: [ [ [ 0 , 0 ] , [ 3 , 4 ] , [ 4 , 1 ] , [ 0 , 0  ] ] ]
}

2.4 Multipoint

A GeoJSON multipoint object is used to store a collection of points. This is generally used to locate similar places in maps in the form of coordinates. A GeoJSON query can be used to find out which collection is located within a specific region. A Multipoint GeoJSON can be defined as shown below.

{
  type: "MultiPoint",
  coordinates: [
     [ -73.980, 40.8503 ],
     [ -73.998, 40.4968 ],
     [ -73.737, 40.2648 ],
     [ -73.940, 40.7281 ]
  ]
}

2.5 MultiLineString

MultiLineString has been introduced since version 2.6. MultiLineString GeoJSON objects are used to define a collection of straight lines for a single record. This could be used to store details about roads or path passing through a certain area. A MultiLineString GeoJSON can be defined as shown below.

{
  type: "MultiLineString",
  coordinates: [
     [ [ -73.9643, 40.7859 ], [ -73.9608, 40.7805 ] ],
     [ [ -73.9415, 40.7929 ], [ -73.9554, 40.7884 ] ],
     [ [ -73.9162, 40.7825 ], [ -73.9634, 40.7775 ] ],
     [ [ -73.9780, 40.7724 ], [ -73.9706, 40.7681 ] ]
  ]
}

2.6 MultiPolygon

A MultiPolygon generally defines various group of areas distributed over certain regions. A MultiPolygon can be defined as shown below.

{
  type: "MultiPolygon",
  coordinates: [
     [ [ [ -73.958, 40.8003 ], [ -73.9498, 40.7968 ], [ -73.9737, 40.7648 ], [ -73.9814, 40.7681 ], [ -73.958, 40.8003 ] ] ],
     [ [ [ -73.958, 40.8003 ], [ -73.9498, 40.7968 ], [ -73.9737, 40.7648 ], [ -73.958, 40.8003 ] ] ]
  ]
}

2.7 GeometryCollection

A GeometryCollection GeoJSON object, as the name suggests, is an object containing multiple GeoJSON objects in the form of a single GeoJSON GeometryCollection object. A possible GeometryCollection object is shown below.

{
  type: "GeometryCollection",
  geometries: [
     {
       type: "MultiPoint",
       coordinates: [
          [ -3.9580, 4.803 ],
          [ -3.9498, 4.768 ],
          [ -3.9737, 3.748 ],
          [ -7.9814, 0.761 ]
       ]
     },
     {
       type: "MultiLineString",
       coordinates: [
          [ [ -7.9943, 0.519 ], [ -7.96082, 40.785 ] ],
          [ [ -3.9415, 0.799 ], [ -3.95544, 40.854 ] ],
          [ [ -7.7162, 4.205 ], [ -3.96374, 40.715 ] ],
          [ [ -3.9880, 0.747 ], [ -3.97036, 40.761 ] ]
       ]
     }
  ]
}

3. Working with Geospatial Queries

Now that we have understood the GeoJSON objects that are understood by MongoDB, it is time to proceed with understanding of the possible Geospatial queries. For utilising the power of Geospatial query support, MongoDB provides us with a bunch of operators that helps in building the query. The next section discusses about such Geospatial Query operators that can be used for building Geospatial queries.

3.1 Geospatial indexes

With reference to Geospatial data, MongoDB provides two different types of indexes. These are described in detail below:

3.1.1 2dsphere index

A 2dsphere index is utilised for queries that calculate geometries on object of spherical geometry. The creation of a 2dsphere index is pretty simple. The code snippet below shows the creation of index on a collection.

db.collection.createIndex({<location-field>:"2dsphere"})

As it can be seen, the function createIndex() is the MongoDB function used to create index. The argument to be supplied is the type of index, also known as the location field.

3.1.2 2d index

2D index is a type of index that supports geometry calculations on two-dimensional planes. The creation of 2d index on a collection follows the same syntax as above except for the type of location field. The below snippet create a 2d index on the collection.

db.collection.createIndex({<location-field>:"2d"});

As it can be noticed, just the type of field changes to change the type of index.

3.2 Query Selectors

$geoIntersects: This selector helps in selecting records with intersecting geometries. The interesting fact about this selector is that it support 2D sphere geometry index as well. The syntax of this selector can be written as shown below:

{
  <location>: {
     $geoIntersects: {
        $geometry: {
           type: "<GeoJSON object type>" ,
           coordinates: [<coordinates>]
        }
     }
  }
}

$geoWithin: A replacement of $within selector deprecated in version 2.4, this selector helps in selecting geometries within a certain specified region. This selector takes a Polygon object as an input to decide the area and finds the geometrics within the area. The selector also works with MultiPolygon GeoJSON object. A sample code for a Polygon object as an argument is shown below

{
  <location> : {
      $geoWithin: {
         $geometry: {
            type: "Polygon",
            coordinates: [ <coordinates> ]
         }
      }
   }
}

As it can be seen, the selector uses a geometry object to specify the region in focus. In order to specify a MultiPolygon in the above selector, all we need to do is replace Polygon in the above selector with MultiPolygon and change the co-ordinates accordingly. Thus, $geoWithin supports both Polygon as well as MultiPolygon geometry objects.
Unlike geoIntersects, this selector does not require an index. However, it does speed up the query execution of a 2D sphere index has been provided. The $geoWithin operator doesn’t return results in a sorted form. As such, MongoDB does return $geoWithin queries more quickly than geospatial $near or $nearSphere queries, which do provide sorted results.

$near: This selector helps in finding the places which are nearest to and farthest from a specified Point object. Thus, the selector calculates the distance of geometries in the supplied documents and selects the ones with shortest and longest distance. A sample selector for this selector is shown below:

{
   <location>: {
     $near: {
       $geometry: {
          type: "Point" ,
          coordinates: [ <long> , <lat> ]
       },
       $maxDistance: <distance in mtrs>,
       $minDistance:<distance in mtrs>
     }
   }
}

This selector requires an index of different types depending on the scenario. These scenarios are mentioned below:

  • A 2DSphere index is required if a Point GeoJSON is specified in the selector
  • A 2D index is required if a Point is specified using legacy coordinate system

$nearSphere: The purpose selector is similar to the above selector. However, it differs in the way it treats geometry.  This selectors select a point on the sphere nearest and farthest to the supplied Point object. The requirement for the indexes is similar to the above selector as specified below:

  • A 2DSphere index is required if a Point GeoJSON is specified in the selector
  • A 2D index is required if a Point is specified using legacy coordinate system. To use a 2D index, it is advisable to create index on co-ordinates of the field of GeoJSON object.
  • Although 2D index is supported, it is advisable to use 2dsphere index considering the involvement of spherical geometries.
{
$nearSphere: {
$geometry: {
type : "Point",
coordinates : [ <long>, <lat> ]
},
$minDistance: <distance in mtrs>,
$maxDistance: <distance in mtrs>
}
}

In both the near field selectors, the minimum distance parameter is optional. This parameters ensures that only the objects at the mentioned minimum distance are selected rather than looking for the entire space. Similarly, the maximum distance parameter is optional too. It can be specified as an index to filter out the data within a specific region.

4. Geospatial queries

Now that we have understood the indexes and operator available for geospatial queries, it is time to insert some data and get started with querying. Let us insert some sample data in a collection called destinations. This collection will be a list of places with their geometric locations. Once the data is  inserted, we would try to fetch the list of destinations which are near by specific point.

To insert the data, just put in the code shown below. You may change the details if the need be.

db.destinations.insert( {
    name: "Hongkong",
   location: { type: "Point", coordinates: [ -7.97, 4.77 ] },
} );
db.destinations.insert( {
   name: "Malaysia",
   location: { type: "Point", coordinates: [ -7.992, 4.793 ] },
} );
db.destinations.insert( {
   name: "Dubai",
   location: { type: "Point", coordinates: [ -7.935, 4.303 ] },
} );
db.destinations.insert( {
   name: "Canada",
   location: { type: "Point", coordinates: [ -45.935, 24.303 ] },
} );

As it can be seen, the destinations are located using the GeoJSON Point objects. Thus, they can be visualised to be on a 2d plane. The next step is to create a 2d index considering the two-dimensional plane in focus. To create the index, execute the below statement.

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

Once the index is created, we can proceed towards fetching list of places within a specific radius. The below query fetches the list of places that are within a radius of 1000 to 2000.

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

In the above query, the point (-7,4) acts as center and a radius from 1000 metres to 2000 metres is scanned for any existing points within the region. The above query should return the document with place as Canada as shown below.

{
   name: "Canada",
   location: { type: "Point", coordinates: [ -45.935, 24.303 ] },
}

The rest of the places are in close proximity to the point and hence does not fall in the provided range. This query can also be extended further to adding more query parameters. Consider a field type in each record used to specific the type of destination. The query could be modified to include the additional parameters as shown below.

db.destinations.aggregate( [
   {
      $geoNear: {
         near: { type: "Point", coordinates: [ -7.0, 4.0 ] },
         query: { type: "Beach" },
         distanceField: "calcDistance"
      }
   }
] )

The above query returns the aggregate collection of destinations having type attribute as Beach. The results are sorted from the lowest distance to the highest distance.

Once you have understood the above 2D queries well, it is time to proceed with the queries containing 2d spherical data. Import list of restaurants into the database using this link. The link contains a collection of restaurants with their coordinates in legacy format. To import the data from the link,

  1. Go to the link and copy the complete text.
  2. Open a new file and save it as rest.json
  3. Run the below command to import the rest.json file in the database collection restaurants.
mongoimport  -c restaurants

Since we are looking to perform a 2D spherical query on this collection, create a 2dsphere index using the below command

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

To find the list of restaurants in a specific region, we need the geometry of the region as a GeoJSON polygon object. A polygon object would specific a complete region with joining lines. The below snippet shows a brief version of what a region geometry would look like.

"geometry":{"coordinates":[[[-73.94193078816193,40.70072523469547],[-73.9443878859649,40.70042452378256],[-73.94424286147482,40.69969927964773],[-73.94409591260093,40.69897295461309],[-73.94394947271304,40.69822127983908],[-73.94391750192877,40.69805620211356],[-73.94380383211836,40.697469265449826],[-73.94378455587042,40.6973697290538],[-73.94374306706803,40.69715549995503],[-73.9437245356891,40.697059812179496],[-73.94368427322361,40.696851909818065],[-73.9436842703752,40.69685189440415],
...
...
...
[-73.9438247374114,40.701862007878276],[-73.94322686012315,40.701520709145726],[-73.94306406845838,40.7014244350918],[-73.94220058705264,40.700890667467746],[-73.94193078816193,40.70072523469547]]],"type":"Polygon"}

This geometry of a region can be passed in the query to find out the restaurants in this region. The query would appear to be something like the one shown below.

db.restaurants.find( { location: { $geoWithin: { $geometry: region.geometry } } } )

The variable region here is a JSON representation of a region. The above query return the list of documents containing details of restaurants located within the specified region geometry. Thus, the geospatial queries can be used to find various geometric entities using Geospatial operators discussed above.

In addition to finding restaurants within a region, it is also possible to find restaurants within specific distances. For instance, to find restaurants within a circular radius, execute the below query.

db.restaurants.find({ location:
   { $geoWithin:
      { $centerSphere: [ [ -2.434, 5.323 ], 5 / 3963.2 ] } } })

The above query looks for restaurants within the sphere specified using the coordinates. The second argument of the center sphere json above is radius in radian units.

5. Conclusion

From the above discussion, we can conclude that MongoDB provides an exhaustive list of Geospatial operators and queries to play around within geospatial data. These queries make it easier to query documents containing geometrical or geographical details. Unlike the structured SQL databases, MongoDB enables the users to use dynamic unstructured data as well with these queries which simplifies the process.

Although almost every important aspect has been discussed in the above details, there is more to explore at this link.

Abhishek Kothari

Abhishek is a Web Developer with diverse skills across multiple Web development technologies. During his professional career, he has worked on numerous enterprise level applications and understood the technological architecture and complexities involved in making an exceptional project. His passion to share knowledge among the community through various mediums has led him towards being a Professional Online Trainer, Youtuber as well as Technical Content Writer.
Subscribe
Notify of
guest

This site uses Akismet to reduce spam. Learn how your comment data is processed.

0 Comments
Inline Feedbacks
View all comments
Back to top button