Geospatial
Mongo handles geospatial queries natively: store GeoJSON points/polygons, index with `2dsphere`, then ask "what is near this point?" or "which stores cover this delivery address?". No PostGIS, no add-ons — the same database you store orders in answers spatial questions.
2dsphere index, $near, $geoWithin, $geoIntersects
EXAMPLE
// 1) Store GeoJSON locations
db.stores.insertMany([
{ name: 'Sydney', location: { type: 'Point', coordinates: [151.2093, -33.8688] } },
{ name: 'Melbourne', location: { type: 'Point', coordinates: [144.9631, -37.8136] } },
{ name: 'Brisbane', location: { type: 'Point', coordinates: [153.0251, -27.4698] } },
{ name: 'Perth', location: { type: 'Point', coordinates: [115.8605, -31.9505] } },
]);
// 2) 2dsphere index — required for any of the operators below
db.stores.createIndex({ location: '2dsphere' });
// 3) Nearest N — sorted by distance, with distance included
db.stores.aggregate([
{
$geoNear: {
near: { type: 'Point', coordinates: [151.21, -33.86] }, // user pin
distanceField: 'distance_m',
spherical: true,
maxDistance: 1_000_000, // 1000 km
query: { active: { $ne: false } }, // additional filter
}
},
{ $limit: 5 },
{ $project: { name: 1, distance_km: { $round: [{ $divide: ['$distance_m', 1000] }, 1] } } },
]);
// 4) Within a delivery polygon
const region = {
type: 'Polygon',
coordinates: [[
[151.0, -34.0], [151.5, -34.0], [151.5, -33.7], [151.0, -33.7], [151.0, -34.0],
]],
};
db.stores.find({
location: { $geoWithin: { $geometry: region } },
});
// 5) Polygons stored on documents (delivery zones)
db.delivery_zones.insertMany([
{
courier: 'fast-co',
area: {
type: 'Polygon',
coordinates: [[
[151.0, -34.0], [151.5, -34.0], [151.5, -33.7], [151.0, -33.7], [151.0, -34.0],
]],
},
},
]);
db.delivery_zones.createIndex({ area: '2dsphere' });
// 'Which couriers cover this address?'
db.delivery_zones.find({
area: { $geoIntersects: { $geometry: { type: 'Point', coordinates: [151.2, -33.85] } } },
});
// 6) Radius search shortcut
db.stores.find({
location: {
$nearSphere: {
$geometry: { type: 'Point', coordinates: [151.21, -33.86] },
$maxDistance: 50_000, // 50 km
},
},
});
// 7) Within a bounding box (rectangular regions)
db.stores.find({
location: {
$geoWithin: {
$geometry: {
type: 'Polygon',
coordinates: [[[150.5, -34.5], [152.0, -34.5], [152.0, -33.0], [150.5, -33.0], [150.5, -34.5]]],
},
},
},
});
// 8) Aggregations with geospatial
// Group stores into 100 km buckets from the user
db.stores.aggregate([
{
$geoNear: {
near: { type: 'Point', coordinates: [151.21, -33.86] },
distanceField: 'd',
spherical: true,
}
},
{
$bucket: {
groupBy: '$d',
boundaries: [0, 50_000, 100_000, 500_000, 1_000_000, Infinity],
output: { count: { $sum: 1 }, names: { $push: '$name' } },
}
},
]);
// 9) Coordinate convention
// GeoJSON uses [LONGITUDE, LATITUDE] in that order — the most common
// bug is swapping them. Always: longitude, then latitude.
// 10) Limits + tips
// - Distances are in meters (2dsphere always uses spherical earth)
// - $geoNear MUST be the first stage in an aggregation
// - For 'within X km of', use $nearSphere or $geoNear with maxDistance
// - The 2dsphere index understands many shapes (Point, LineString, Polygon,
// MultiPolygon); store the type that matches your domain
// - Polygon coordinates: outer ring counter-clockwise; holes clockwise
// 11) Decision matrix
// - User -> nearest stores $geoNear
// - 'Inside this drawn region' $geoWithin polygon
// - 'Which zones cover this point' $geoIntersects with stored polygons
// - Routing / driving distance NOT mongo. Use Mapbox / Google Maps API.
Why it matters
GeoJSON uses [longitude, latitude] in that order — the single most common Mongo geo bug is swapping them. After that, $geoNear in an aggregation pipeline + a 2dsphere index covers most "nearest" and "within radius" queries cleanly; reach for PostGIS only when routing, driving distance, or large-scale polygon math enters the picture.
Tip: Tweak the snippet with Try it Yourself », then sit the quiz at the bottom of the page.
Example
Example
db.places.createIndex({ loc: '2dsphere' });
db.places.find({ loc: { $near: { $geometry: { type: 'Point', coordinates: [151.2, -33.8] } } } });
Try it Yourself »
Discussion
Loading…