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

Sponsored

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, $near is almost always the right choice. If you don't care about order — just "is it inside?" — use $geoWithin because it can be more efficient in certain aggregation pipelines.

When to use $geoWithin vs $geoIntersects

  • Use $geoWithin when your documents are points and you want to see if they fall inside a shape.
  • Use $geoIntersects when 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 $near to find nearby points sorted by distance, $geoWithin to filter points inside a shape, and $geoIntersects for 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

  1. Use the $geoNear aggregation pipeline stage instead of $near — it returns distance in the results and works in aggregations.
  2. Store legacy coordinate pairs as [lon, lat] arrays and use a 2d index — but this is outdated and lacks GeoJSON flexibility.
  3. 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.

Sponsored

Sponsored

Discussion

Questions, corrections, and tips help everyone reading this page.

0 comments

Add a comment

Shown publicly with your comment.

Be constructive · max 4,000 characters

No comments yet — start the thread.

Related tutorials, quizzes, and articles for this topic.