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:
- GeoJSON for storing locations, paths, and areas.
2dsphereindexes for efficient geospatial queries on an Earth-like spherical model.
Typical workflow:
- Store geometry in GeoJSON format.
- Create a
2dsphereindex on the field. - 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$geoNearare 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:
$nearinfind()queries$geoNearin 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
Nearby search
- 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
2dsphereindex- operators such as
$near,$geoWithin,$geoIntersects, and$geoNear
10. Official MongoDB documentation
- GeoJSON Objects: https://www.mongodb.com/docs/manual/reference/geojson/
- Geospatial Queries: https://www.mongodb.com/docs/manual/geospatial-queries/
$near: https://www.mongodb.com/docs/manual/reference/operator/query/near/$geoWithin: https://www.mongodb.com/docs/manual/reference/operator/query/geowithin/$geoIntersects: https://www.mongodb.com/docs/manual/reference/operator/query/geointersects/$geoNear: https://www.mongodb.com/docs/manual/reference/operator/aggregation/geonear/