Geospatial Queries with 2dsphere Indexes
Learn to implement geospatial queries with 2dsphere indexes in MongoDB. Step-by-step tutorial covering core concepts, hands-on examples, and troubleshooting tips.
Focus: implement geospatial queries with 2dsphere indexes
Have you ever needed to answer questions like "Which restaurants are within 5 miles of this user?" or "What stores are closest to this warehouse?" — but your current database queries fall flat because you're trying to do math on latitude and longitude yourself? If you've been filtering coordinates with $gte and $lte, you know how painful it is to write bounding-box logic and how inaccurate it can be. MongoDB gives you a far better way: geospatial indexes and queries built right into the database. In this lesson, you'll learn how to implement geospatial queries using a 2dsphere index — the index type that powers location-based features in thousands of applications. By the end, you'll be able to find points within a radius, check if a location is inside a polygon, and get distances calculated for you, all with clean, declarative queries.
The problem this lesson solves
Imagine you're building an app that helps users find nearby coffee shops. Without geospatial support, you'd fetch all the shops in the city and then loop through them in your application code, calculating the haversine distance for each one. That's slow, wasteful, and error-prone — especially if your collection grows to hundreds of thousands of documents. You'd also have to handle edge cases like the International Date Line and the poles, where simple formulas break down.
More importantly, you can't express "find the 10 nearest shops that are open now" as a single query without geospatial operators. You'd need to fetch extra data, sort in memory, and re-query. That's the pain this lesson removes. With a 2dsphere index, MongoDB stores geospatial data in a format that supports efficient querying, and the query engine does the heavy lifting — calculations, sorting by distance, and even checking whether a point lies inside a complex polygon.
By the end of this lesson, you'll be able to:
- Create a 2dsphere index on a field that contains GeoJSON objects.
- Run geospatial queries like $near, $geoWithin, and $geoIntersects.
- Understand when to use each operator and avoid common pitfalls.
Core concept / mental model
Think of a 2dsphere index as a specialized map that MongoDB builds over your location data. Instead of treating latitude and longitude as just two numbers, MongoDB understands them as points on a sphere (the Earth). It then organizes those points in a way that makes proximity and containment queries blazing fast.
Here's the key: the 2dsphere index only works with GeoJSON objects. You can't just store [longitude, latitude] as a plain array — you need a structure like this:
{
"type": "Point",
"coordinates": [-73.97, 40.77]
}
Notice the order: longitude first, then latitude. That's a classic gotcha for newcomers. MongoDB uses GeoJSON's coordinate order, which is [x, y] — longitude is the x-axis (east-west), latitude is the y-axis (north-south). Mixing them up won't throw an error, but your queries will return nonsense results.
A mental model to keep in mind: the 2dsphere index is like a smart grid that lets the database answer "what's near?" without scanning everything. When you create the index, MongoDB precomputes spatial buckets for your coordinates. When you query with $near or $geoWithin, it only looks at the relevant buckets — just like how a library's index lets you jump straight to the shelf instead of walking every aisle.
GeoJSON types you'll use
- Point — a single location (most common).
- LineString — a path, e.g., a road or trail.
- Polygon — an area, e.g., a delivery zone.
- MultiPoint / MultiLineString / MultiPolygon — collections of the above.
You'll mostly work with Points, but understanding Polygon is key for $geoWithin.
How it works step by step
Creating and using a 2dsphere index involves three main stages:
1. Ensure your data is in GeoJSON format
Before you create an index, your documents must store coordinates as GeoJSON objects. If your data has plain [lon, lat] arrays, you'll need to migrate it. Here's what a valid document looks like for a coffee shop:
{
"name": "Central Perk",
"location": {
"type": "Point",
"coordinates": [ -73.983, 40.758 ]
}
}
2. Create the 2dsphere index
Use the createIndex command with "2dsphere" as the index type:
db.places.createIndex({ "location": "2dsphere" })
This index tells MongoDB to treat the location field as geospatial data.
3. Run your geospatial query
Now you can use operators like $near, $geoWithin, and $geoIntersects to answer location-based questions.
The operator family
$near— find points near a given location, sorted by distance automatically.$geoWithin— find points that fall entirely inside a given shape (e.g., a circle or polygon).$geoIntersects— find documents whose GeoJSON geometry intersects with a given shape (works for LineStrings and Polygons as well as Points).
Each operator has its own quirks — we'll dig into them in the hands-on section.
Hands-on walkthrough
Let's get our hands dirty. We'll set up a places collection with a handful of coffee shops in New York City, create a 2dsphere index, and run a few classic geospatial queries.
Step 1: Insert sample data
// Switch to (or create) your database
use exampleDB;
// Insert a few coffee shops with GeoJSON locations
const shops = [
{ name: "Central Perk", location: { type: "Point", coordinates: [ -73.983, 40.758 ] } },
{ name: "Manhattan Roasters", location: { type: "Point", coordinates: [ -73.985, 40.755 ] } },
{ name: "Brooklyn Brew Café", location: { type: "Point", coordinates: [ -73.950, 40.650 ] } },
{ name: "Queens Beans", location: { type: "Point", coordinates: [ -73.800, 40.720 ] } }
];
db.places.insertMany(shops);
Step 2: Create the 2dsphere index
db.places.createIndex({ "location": "2dsphere" });
````
### Step 3: Find coffee shops within 2 kilometers of Times Square
We'll use `$near` with `$maxDistance`. Remember, distances are in **meters**.
```javascript
const timesSquare = { type: "Point", coordinates: [ -73.9857, 40.7577 ] };
db.places.find({
"location": {
"$near": {
"$geometry": timesSquare,
"$maxDistance": 2000 // 2 km in meters
}
}
})
Expected output (order is by distance, closest first):
{ "_id": ..., "name": "Manhattan Roasters", ... }
{ "_id": ..., "name": "Central Perk", ... }
The Brooklyn and Queens shops are outside the radius, so they're omitted.
Step 4: Find shops strictly inside a polygon
What if your delivery zone is a specific area, not a circle? Use $geoWithin with a polygon, or $centerSphere for a circle without sorting.
// Define a square-ish polygon that covers lower Manhattan
const manhattanBox = {
"type": "Polygon",
"coordinates": [[
[-74.00, 40.75],
[-73.97, 40.75],
[-73.97, 40.78],
[-74.00, 40.78],
[-74.00, 40.75] // close the ring
]]
};
db.places.find({
"location": {
"$geoWithin": { "$geometry": manhattanBox }
}
})
Expected output: Central Perk and Manhattan Roasters (both within the box), but not Brooklyn or Queens.
Step 5: Sort by distance and limit results ("nearest 3")
MongoDB's $near doesn't support a limit() inside the operator, but you can chain .limit() on the cursor. It still sorts by distance automatically.
db.places.find({
"location": { "$near": { "$geometry": timesSquare } }
}).limit(3);
That returns the three closest shops, sorted closest-first.
Compare options / when to choose what
You have several geospatial operators, and choosing the right one matters. Here's a quick comparison:
| Operator | What it does | Best for | Notes |
|---|---|---|---|
$near |
Finds points near a point, sorted by distance | Nearest neighbors, auto-sorted | Requires 2dsphere index on the field; can't be used with $maxDistance on a 2d index |
$geoWithin |
Finds points inside a shape (circle, polygon) | Bounding boxes, delivery zones | Does NOT sort by distance; useful for counting or filtering |
$geoIntersects |
Finds documents whose geometry intersects a shape | Routes, borders, complex shapes | Works for any GeoJSON type, not just points |
$nearSphere |
Like $near but uses spherical geometry (on a sphere) |
More accurate for larger distances | Equivalent to $near with a 2dsphere index; you'll rarely use it directly |
Pro tip: If you need both filtering and distance sorting,
$nearis almost always the right choice. If you don't care about order — just "is it inside?" — use$geoWithinbecause it can be more efficient in certain aggregation pipelines.
When to use $geoWithin vs $geoIntersects
- Use
$geoWithinwhen your documents are points and you want to see if they fall inside a shape. - Use
$geoIntersectswhen your documents themselves are polygons or lines (e.g., a hiking trail) and you want to see if they intersect a given area.
Troubleshooting & edge cases
Geospatial queries are powerful, but they come with footguns. Here are the most common ones and how to fix them.
1. "Unsupported projection option" or "unable to find index for $geoNear"
This error usually means you forgot to create the 2dsphere index, or you're using the $near operator without it. Fix: create the index first: db.places.createIndex({ "location": "2dsphere" }).
2. Wrong coordinate order
If you store [latitude, longitude] instead of [longitude, latitude], your queries will silently return wrong results. Fix: always verify your data. A simple findOne() can reveal the order.
3. Using $near with a 2d index (old style)
If you used the older 2d index (for plain arrays), $near expects legacy coordinate pairs, not GeoJSON. Mixing them up gives unexpected results. The 2dsphere index is the modern, recommended choice.
4. Distance units are meters, but you think in kilometers
If you forget to multiply by 1000, your $maxDistance: 2 means 2 meters, not 2 km — you'll get almost no results. Pitfall: always convert to meters.
5. Polygon rings must be closed and wound correctly
In GeoJSON, the first and last coordinate in a polygon ring must be identical, and the ring must be wound counterclockwise for outer rings. If you get an "invalid geometry" error, check your ring.
6. Geographic vs geospatial confusion
Remember, $geoWithin does not sort, while $near does. If you expect sorted results from $geoWithin, you'll be surprised.
What you learned & what's next
In this lesson, you learned how to implement geospatial queries with 2dsphere indexes from scratch:
- You can store coordinates as GeoJSON objects and create a 2dsphere index to enable location-based queries.
- You used
$nearto find nearby points sorted by distance,$geoWithinto filter points inside a shape, and$geoIntersectsfor intersecting geometries. - You compared operators and learned when to choose each one.
- You debugged common issues like coordinate order, missing indexes, and distance units.
You're now equipped to build location-aware features like "find my closest store" or "are we in a delivery zone?" — all with clean, efficient MongoDB queries.
Next up in the MongoDB track: you'll learn about text search with $text indexes, which lets you find documents based on word matches — another powerful feature that complements geospatial search. Building on what you've mastered here, you'll combine multiple index types for even richer applications.
Practice recap
Try this: create a collection of your own favorite locations (e.g., cafes, parks) with proper GeoJSON coordinates, add a 2dsphere index, and run a $near query to find which ones are within 1 kilometer of your home. Then, experiment with $geoWithin using a custom polygon for a delivery zone. See if you can list the results in order of distance.
Common mistakes
- Forgetting to create the 2dsphere index — without it, $near returns an error or scans all documents.
- Storing coordinates as [latitude, longitude] instead of [longitude, latitude] — queries silently return wrong results.
- Using $maxDistance with kilometers directly instead of converting to meters (multiply by 1000).
- Assuming $geoWithin sorts by distance — it doesn't; use $near for distance-sorted results.
- Creating a polygon ring that isn't closed (first and last coordinates must match) or incorrectly wound — causes validation errors.
Variations
- Use the $geoNear aggregation pipeline stage instead of $near — it returns distance in the results and works in aggregations.
- Store legacy coordinate pairs as [lon, lat] arrays and use a 2d index — but this is outdated and lacks GeoJSON flexibility.
- Combine geospatial queries with additional filters (e.g., $and with ''open_now'') to refine results by other criteria.
Real-world use cases
- Ride-sharing apps: find nearby drivers within a 3-mile radius using $near and return the closest ones.
- E-commerce delivery: verify a customer's address falls inside a delivery polygon with $geoWithin.
- Location-based recommendations: show restaurants within 1 km sorted by distance using $near with $maxDistance.
Key takeaways
- Always store coordinates as GeoJSON objects with longitude first, latitude second.
- Create a 2dsphere index on your geospatial field before running queries — it powers all $near, $geoWithin, and $geoIntersects operations.
- Use $near for proximity queries that must be sorted by distance, and $geoWithin for containment checks without sorting.
- Distances are always in meters — convert from miles/kilometers before using maxDistance.
- Polygons must be closed (last point = first point) and follow GeoJSON winding rules.
- The $geoNear aggregation stage is a powerful alternative that returns distance fields and integrates with aggregations.
Keep learning
Related tutorials, quizzes, and articles for this topic.
Discussion
Questions, corrections, and tips help everyone reading this page.
0 comments
Add a comment
No comments yet — start the thread.