Geospatial Queries with 2dsphere Indexes

Advanced
12 min

Geospatial Queries with 2dsphere Indexes

"Restaurants within 2 km", "which delivery zone covers this address", "does this route cross a flood area" — location questions are common in delivery, travel, real-estate and logistics products. MongoDB answers them natively: store shapes as GeoJSON, index them with 2dsphere, and query by distance, containment or intersection. After this lesson you will be able to model points and polygons correctly, run $near, $geoWithin and $geoIntersects queries, and compute distances with the $geoNear aggregation stage.

GeoJSON and the 2dsphere Index

GeoJSON objects have a type and coordinates. MongoDB supports Point, LineString, Polygon, the Multi* variants and GeometryCollection. The one rule everyone gets wrong at least once: coordinates are [longitude, latitude], in that order, with longitude in -180..180 and latitude in -90..90.

javascript
{ type: "Point", coordinates: [73.8567, 18.5204] } { type: "Polygon", coordinates: [[ [73.85, 18.51], [73.87, 18.51], [73.87, 18.53], [73.85, 18.53], [73.85, 18.51] // closed ring ]] }

A polygon ring must be closed (first and last positions equal). Create the index on the field that holds the GeoJSON:

javascript
db.places.createIndex({ location: "2dsphere" }) db.zones.createIndex({ area: "2dsphere" }) db.places.createIndex({ category: 1, location: "2dsphere" }) // compound is allowed

2dsphere calculates on a sphere, so distances are in meters and results are accurate across the globe. The older 2d index is for flat, Euclidean coordinate systems such as game maps.

$near: Sorted by Distance

$near returns documents ordered from nearest to farthest, optionally bounded by $maxDistance and $minDistance (meters):

javascript
db.places.find({ category: "cafe", location: { $near: { $geometry: { type: "Point", coordinates: [73.856, 18.52] }, $minDistance: 50, $maxDistance: 2000 } } }).limit(10)

$near requires a geospatial index, permits only one $near per query, cannot be placed inside $or, and is not supported on sharded collections — use $geoNear there. $nearSphere behaves the same and exists for legacy coordinate pairs.

$geoWithin: Inside an Area

$geoWithin matches documents whose geometry lies entirely inside a shape. Results are not sorted, an index is optional (but recommended), and the operator combines freely with other conditions:

javascript
// Inside a polygon (e.g. a delivery zone drawn on a map) db.places.find({ location: { $geoWithin: { $geometry: zone.area } } }) // Inside a circle: center [lng, lat] and radius in radians (km / 6378.1) db.places.find({ location: { $geoWithin: { $centerSphere: [[73.856, 18.52], 5 / 6378.1] } } })

$geoIntersects: Shapes That Touch

$geoIntersects matches documents whose geometry shares any point with the given one. It answers "which zones contain this address" when the zones are the stored polygons:

javascript
db.zones.find({ area: { $geoIntersects: { $geometry: { type: "Point", coordinates: [73.856, 18.52] } } } }) db.roads.find({ path: { $geoIntersects: { $geometry: floodPolygon } } })

$geoNear: Distances in the Aggregation Pipeline

$geoNear must be the first stage. It sorts by distance like $near and additionally writes the distance into each document:

javascript
db.places.aggregate([ { $geoNear: { near: { type: "Point", coordinates: [73.856, 18.52] }, distanceField: "distanceMeters", maxDistance: 3000, query: { category: "cafe", open: true }, spherical: true } }, { $project: { name: 1, distanceMeters: { $round: ["$distanceMeters", 0] } } }, { $limit: 5 } ])

Use key: "location" when the collection has more than one geospatial index. $geoNear works on sharded collections and is the natural choice for "nearest N with distance shown" features.

| Operator | Sorted by distance | Returns distance | Needs index | Typical question | |---|---|---|---|---| | $near | yes | no | yes | nearest places | | $geoWithin | no | no | no | everything inside this area | | $geoIntersects | no | no | no | which shapes touch this one | | $geoNear (stage) | yes | yes | yes | nearest places with distance |

Common Mistakes

  • Swapping latitude and longitude. Points land in the ocean and $near returns nothing useful.
  • Unclosed or self-intersecting polygons, which the index rejects on insert.
  • Mixing units. $maxDistance and $geoNear use meters for GeoJSON; $centerSphere uses radians.
  • Storing coordinates as strings or as { lat, lng } objects that no geospatial operator understands.
Quick Quiz
Question 1 of 3

What is the correct coordinate order for a GeoJSON point in MongoDB?

Key Takeaways

  • Store locations as GeoJSON with [longitude, latitude] coordinates and index them with 2dsphere.
  • $near returns results nearest-first with distances bounded in meters; it needs an index and one per query.
  • $geoWithin tests containment in a polygon or circle; $geoIntersects tests overlap; both work without sorting.
  • $geoNear is the aggregation equivalent that also outputs the distance and supports sharded collections.
  • Close polygon rings and keep units straight: meters for GeoJSON distances, radians for $centerSphere.

Next lesson: The Node.js Driver in Depth — connection pooling, typed collections, cursors, error handling and production configuration.

Geospatial Queries with 2dsphere Indexes - MongoDB | CodeYourCraft | CodeYourCraft