Storing and Querying Geospatial Data in MongoDB

This scriptum gives an overview of how MongoDB stores and queries geospatial data with GeoJSON and 2dsphere indexes. It intentionally leaves out legacy coordinate arrays and 2d indexes.

1. Core idea

MongoDB supports geospatial data through two key concepts:

  1. GeoJSON for storing locations, paths, and areas.
  2. 2dsphere indexes for efficient geospatial queries on an Earth-like spherical model.

Typical workflow:

  1. Store geometry in GeoJSON format.
  2. Create a 2dsphere index on the field.
  3. Query with operators such as $near, $geoWithin, $geoIntersects, or with the aggregation stage $geoNear.

2. Important rules and concepts

2.1 Coordinate order

MongoDB expects geographic coordinates in this order:

[longitude, latitude]

Example for Vienna:

[16.3738, 48.2082]

Not the other way around.

2.2 GeoJSON structure

GeoJSON values are stored as embedded documents with two main fields:

{
  type: "Point",
  coordinates: [16.3738, 48.2082]
}

2.3 2dsphere index

For modern geospatial work in MongoDB, the standard index is:

db.collection.createIndex({ geometry: "2dsphere" })

Use it on the field that contains the GeoJSON object.

2.4 Query families

The most important geospatial query patterns are:

  • nearest objects$near
  • objects inside an area$geoWithin
  • objects intersecting another geometry$geoIntersects
  • distance calculation inside aggregation$geoNear

2.5 Distance units

When you use GeoJSON with 2dsphere indexes:

  • distances in $near, $nearSphere, and $geoNear are given in meters
  • coordinates remain longitude/latitude in WGS84 / EPSG:4326 style GeoJSON

3. Storing geospatial data

Below are the GeoJSON geometry types you will commonly use in MongoDB.

3.1 Point

A single geographic location.

Structure

{
  name: "HTL Wien",
  location: {
    type: "Point",
    coordinates: [16.3738, 48.2082]
  }
}

Practical use-case

Use a Point for a thing that has one exact position.

Examples:

  • a school
  • a bus stop
  • a sensor
  • a customer location
  • a restaurant

Example meaning

A school app could store each school building as a Point and later ask:

  • Which building is nearest to the student?
  • Which schools are within 2 km?

3.2 LineString

A connected path consisting of multiple points.

Structure

{
  name: "Morning Delivery Route",
  route: {
    type: "LineString",
    coordinates: [
      [16.3600, 48.2100],
      [16.3680, 48.2140],
      [16.3760, 48.2180]
    ]
  }
}

Practical use-case

Use a LineString for a route or path.

Examples:

  • a bus route
  • a hiking path
  • a delivery route
  • a recorded running or cycling track

Example meaning

A logistics system could store the driven path of a vehicle and ask:

  • Which route crosses a restricted zone?
  • Which route passes through a district?

3.3 Polygon

A closed area described by a ring of coordinates.

Structure

{
  name: "School Campus",
  area: {
    type: "Polygon",
    coordinates: [
	    [ // one ring (there could be more)
	      [16.3700, 48.2070],
	      [16.3780, 48.2070],
	      [16.3780, 48.2110],
	      [16.3700, 48.2110],
	      [16.3700, 48.2070]
	    ]
	  ]
  }
}

Practical use-case

Use a Polygon for a single area.

Examples:

  • a school campus
  • a city district
  • a park
  • a delivery zone
  • a no-fly zone

Example meaning

A school administration system could store the campus border as a Polygon and ask:

  • Is a device currently inside the campus?
  • Which emergency routes intersect the campus?

3.4 MultiPoint

A collection of multiple independent points.

Structure

{
  name: "Bike Sharing Stations on Campus",
  stations: {
    type: "MultiPoint",
    coordinates: [
      [16.3710, 48.2080],
      [16.3730, 48.2090],
      [16.3750, 48.2100]
    ]
  }
}

Practical use-case

Use a MultiPoint when one document should represent several point locations that logically belong together.

Examples:

  • all entrances of a building
  • all pickup points of one service
  • all station locations of one bike-sharing provider in one district

Example meaning

A building document could store all entrances as a MultiPoint and later ask:

  • Does this evacuation route intersect a building access point?
  • Which building has an entrance inside a selected area?

3.5 MultiLineString

A collection of multiple separate paths.

Structure

{
  name: "Tram Network Segment",
  network: {
    type: "MultiLineString",
    coordinates: [
      [
        [16.3600, 48.2100],
        [16.3660, 48.2120],
        [16.3720, 48.2140]
      ],
      [
        [16.3740, 48.2150],
        [16.3800, 48.2170],
        [16.3860, 48.2190]
      ]
    ]
  }
}

Practical use-case

Use a MultiLineString when one document contains several route segments or disconnected line parts.

Examples:

  • disconnected route fragments
  • multiple cable paths of one installation
  • route alternatives belonging to one transport line

Example meaning

A public transport document could store multiple route branches and later ask:

  • Which network branch intersects a construction zone?
  • Which transport network passes through a district?

3.6 MultiPolygon

A collection of multiple polygons.

Structure

{
  name: "City Service Area",
  serviceArea: {
    type: "MultiPolygon",
    coordinates: [
      [[
        [16.3500, 48.2000],
        [16.3600, 48.2000],
        [16.3600, 48.2060],
        [16.3500, 48.2060],
        [16.3500, 48.2000]
      ]],
      [[
        [16.3900, 48.2200],
        [16.4000, 48.2200],
        [16.4000, 48.2260],
        [16.3900, 48.2260],
        [16.3900, 48.2200]
      ]]
    ]
  }
}

Practical use-case

Use a MultiPolygon when one logical region consists of multiple separate areas.

Examples:

  • a delivery company serving several disconnected city zones
  • a school system with multiple campuses
  • a nature reserve consisting of separate protected parts

Example meaning

A company document could store all service islands in one MultiPolygon and later ask:

  • Is the customer inside any served region?
  • Which maintenance route intersects one of the service zones?

4. Creating indexes

Before efficient querying, create a 2dsphere index.

Example for points

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

Example for routes

db.routes.createIndex({ route: "2dsphere" })

Example for areas

db.districts.createIndex({ area: "2dsphere" })

You create the index on the field that stores the GeoJSON object.


5. Querying geospatial data

5.1 Core concepts before the actual queries

A. Nearest-neighbor queries

These answer questions such as:

  • What is closest to this point?
  • Which school is nearest to a student?
  • Which charging station is within 500 meters?

Use:

  • $near in find() queries
  • $geoNear in aggregation pipelines

$near returns results sorted by distance.


B. Containment queries

These answer questions such as:

  • Which points lie inside this area?
  • Which sensors are on campus?
  • Which restaurants are inside this district?

Use:

  • $geoWithin

This is about being inside a boundary.


C. Intersection queries

These answer questions such as:

  • Which route crosses a restricted area?
  • Which district is touched by this road?
  • Which cable path intersects a construction zone?

Use:

  • $geoIntersects

This is about touching or crossing another geometry.


D. Distance-enriched result sets

These answer questions such as:

  • Return the nearest locations and also calculate the distance.
  • Build an API response that already includes meters to the target.

Use:

  • $geoNear

This is especially useful in aggregation pipelines because the computed distance can be added directly into the result documents.


5.2 Practical query examples

Query 1: Find the nearest places to a point

Use-case

A student app wants to find the nearest canteen, library, or lab building.

Example data assumption

Collection: places

{
  name: "Library",
  category: "building",
  location: {
    type: "Point",
    coordinates: [16.3738, 48.2082]
  }
}

Query with $near

db.places.find({
  location: {
    $near: {
      $geometry: {
        type: "Point",
        coordinates: [16.3725, 48.2090]
      },
      $maxDistance: 1000
    }
  }
})

What it does

  • starts at the given point
  • finds matching geometries near that point
  • returns them ordered from nearest to farthest
  • limits matches to 1000 meters

Query 2: Find all places within a polygon

Use-case

Find all sensors or buildings that are inside the campus area.

Query with $geoWithin

db.places.find({
  location: {
    $geoWithin: {
      $geometry: {
        type: "Polygon",
        coordinates: [[
          [16.3700, 48.2070],
          [16.3780, 48.2070],
          [16.3780, 48.2110],
          [16.3700, 48.2110],
          [16.3700, 48.2070]
        ]]
      }
    }
  }
})

What it does

  • defines a search area as a polygon
  • returns all geometries that lie inside that area
  • does not focus on distance ordering

Query 3: Find which area contains a point

Use-case

Determine in which district a reported incident happened.

Example data assumption

Collection: districts

{
  name: "District A",
  area: {
    type: "Polygon",
    coordinates: [ ... ]
  }
}

Query with $geoIntersects

db.districts.find({
  area: {
    $geoIntersects: {
      $geometry: {
        type: "Point",
        coordinates: [16.3745, 48.2088]
      }
    }
  }
})

What it does

  • uses a point as the query geometry
  • returns all stored geometries that intersect that point
  • in practice, this is a common way to find which polygon contains the point

Query 4: Find routes that cross a polygon

Use-case

Find delivery or evacuation routes that pass through a restricted area.

Example data assumption

Collection: routes

{
  name: "Route 1",
  route: {
    type: "LineString",
    coordinates: [ ... ]
  }
}

Query with $geoIntersects

db.routes.find({
  route: {
    $geoIntersects: {
      $geometry: {
        type: "Polygon",
        coordinates: [[
          [16.3700, 48.2070],
          [16.3780, 48.2070],
          [16.3780, 48.2110],
          [16.3700, 48.2110],
          [16.3700, 48.2070]
        ]]
      }
    }
  }
})

What it does

  • checks whether the stored line geometry intersects the polygon
  • returns all routes that touch or cross that area

Query 5: Return nearby places and include the computed distance

Use-case

An API should return the nearest pickup stations together with the distance in meters.

Query with $geoNear

db.places.aggregate([
  {
    $geoNear: {
      near: {
        type: "Point",
        coordinates: [16.3725, 48.2090]
      },
      distanceField: "distanceMeters",
      maxDistance: 1500,
      spherical: true
    }
  },
  {
    $project: {
      _id: 0,
      name: 1,
      category: 1,
      distanceMeters: 1
    }
  }
])

What it does

  • starts the aggregation with a geospatial proximity search
  • calculates distance to each result
  • stores that value in distanceMeters
  • is ideal when distance must be part of the response

Query 6: Limit a nearest query to a category

Use-case

Find the nearest cafeterias, not all places.

db.places.find({
  category: "cafeteria",
  location: {
    $near: {
      $geometry: {
        type: "Point",
        coordinates: [16.3725, 48.2090]
      },
      $maxDistance: 800
    }
  }
})

This combines a normal filter with a geospatial condition.


Query 7: Find all points inside one part of a MultiPolygon service area

Use-case

Check which customers lie inside the regions covered by a provider.

If a collection stores a MultiPolygon, you can still use $geoIntersects with a Point:

db.serviceAreas.find({
  serviceArea: {
    $geoIntersects: {
      $geometry: {
        type: "Point",
        coordinates: [16.3950, 48.2230]
      }
    }
  }
})

This returns the service-area documents whose geometry contains that point.


6. Choosing the right geometry type

A useful rule of thumb:

  • Point → one exact location
  • LineString → one path
  • Polygon → one connected area
  • MultiPoint → multiple related locations in one document
  • MultiLineString → multiple related path segments in one document
  • MultiPolygon → multiple disconnected areas in one document

In practice, the choice depends on what one document is supposed to represent.


7. Typical application patterns

  • nearest restaurant
  • nearest school building
  • nearest public transport stop

Usually modeled with Point and queried with $near or $geoNear.

Geofencing

  • is a user inside campus?
  • did a vehicle enter the service area?

Usually modeled with Polygon or MultiPolygon and queried with $geoIntersects.

Route analysis

  • which routes cross a district?
  • which path touches a danger zone?

Usually modeled with LineString or MultiLineString and queried with $geoIntersects.

Area membership

  • which sensors are inside room zone A?
  • which devices are in building section B?

Usually modeled with Point data queried against Polygon areas with $geoWithin or $geoIntersects.


8. Common mistakes

Mistake 1: Wrong coordinate order

Wrong:

[48.2082, 16.3738]

Correct:

[16.3738, 48.2082]

Mistake 2: Forgetting the index

Without a 2dsphere index, $near and $geoNear will not work correctly and other geospatial queries will be slower.

Mistake 3: Using the wrong geometry type

Do not store an area as a Point or a route as a Polygon just because it is simpler. The geometry should match the real-world meaning.

Mistake 4: Not closing polygons

The first and last coordinate of a polygon ring must be the same.


9. Summary

MongoDB is a strong choice for application-oriented geospatial features when you want to:

  • store GeoJSON directly in documents
  • query nearby points
  • check whether data lies inside an area
  • test whether paths and areas intersect
  • compute distances inside aggregation pipelines

For modern MongoDB geospatial work, the standard combination is:

  • GeoJSON geometry
  • 2dsphere index
  • operators such as $near, $geoWithin, $geoIntersects, and $geoNear

10. Official MongoDB documentation