iwantcoding.com
🔥 Daily 👥 Rooms 🏆 Top Log in Sign up

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…