MongoDB Geospatial Exercise

This exercise provides sample collections with GeoJSON data and a sequence of tasks with increasing difficulty.

How to use this file

  1. Copy the JavaScript block below into the shell/editor.
  2. Execute the inserts and indexes.
  3. Solve the tasks by writing your own queries below or in a separate file.
use geoPractice;
 
// Clean up old data
 
db.places.drop();
db.routes.drop();
db.districts.drop();
db.serviceAreas.drop();
db.campuses.drop();
 
// -----------------------------------------------------------------------------
// COLLECTION 1: places
// Stores individual places as GeoJSON Points
// -----------------------------------------------------------------------------
 
db.places.insertMany([
  {
    name: "Central Library",
    category: "library",
    location: { type: "Point", coordinates: [16.3738, 48.2082] }
  },
  {
    name: "North Cafeteria",
    category: "cafeteria",
    location: { type: "Point", coordinates: [16.3760, 48.2090] }
  },
  {
    name: "South Lab",
    category: "lab",
    location: { type: "Point", coordinates: [16.3712, 48.2068] }
  },
  {
    name: "Main Entrance",
    category: "entrance",
    location: { type: "Point", coordinates: [16.3724, 48.2087] }
  },
  {
    name: "Sports Hall",
    category: "sports",
    location: { type: "Point", coordinates: [16.3790, 48.2104] }
  },
  {
    name: "Dormitory",
    category: "housing",
    location: { type: "Point", coordinates: [16.3822, 48.2120] }
  },
  {
    name: "Innovation Hub",
    category: "building",
    location: { type: "Point", coordinates: [16.3688, 48.2096] }
  },
  {
    name: "Metro Stop",
    category: "transport",
    location: { type: "Point", coordinates: [16.3652, 48.2075] }
  }
]);
 
// -----------------------------------------------------------------------------
// COLLECTION 2: routes
// Stores routes as LineString and MultiLineString
// -----------------------------------------------------------------------------
 
db.routes.insertMany([
  {
    name: "Delivery Route A",
    kind: "delivery",
    route: {
      type: "LineString",
      coordinates: [
        [16.3640, 48.2060],
        [16.3700, 48.2075],
        [16.3760, 48.2095],
        [16.3820, 48.2125]
      ]
    }
  },
  {
    name: "Evacuation Route East",
    kind: "evacuation",
    route: {
      type: "LineString",
      coordinates: [
        [16.3810, 48.2055],
        [16.3780, 48.2072],
        [16.3745, 48.2089],
        [16.3695, 48.2107]
      ]
    }
  },
  {
    name: "Campus Shuttle Network",
    kind: "transport",
    route: {
      type: "MultiLineString",
      coordinates: [
        [
          [16.3650, 48.2070],
          [16.3700, 48.2080],
          [16.3750, 48.2090]
        ],
        [
          [16.3760, 48.2095],
          [16.3800, 48.2108],
          [16.3840, 48.2120]
        ]
      ]
    }
  }
]);
 
// -----------------------------------------------------------------------------
// COLLECTION 3: districts
// Stores areas as Polygons
// -----------------------------------------------------------------------------
 
db.districts.insertMany([
  {
    name: "West District",
    area: {
      type: "Polygon",
      coordinates: [[
        [16.3620, 48.2055],
        [16.3725, 48.2055],
        [16.3725, 48.2115],
        [16.3620, 48.2115],
        [16.3620, 48.2055]
      ]]
    }
  },
  {
    name: "East District",
    area: {
      type: "Polygon",
      coordinates: [[
        [16.3725, 48.2055],
        [16.3845, 48.2055],
        [16.3845, 48.2135],
        [16.3725, 48.2135],
        [16.3725, 48.2055]
      ]]
    }
  },
  {
    name: "Campus Core",
    area: {
      type: "Polygon",
      coordinates: [[
        [16.3705, 48.2070],
        [16.3785, 48.2070],
        [16.3785, 48.2108],
        [16.3705, 48.2108],
        [16.3705, 48.2070]
      ]]
    }
  }
]);
 
// -----------------------------------------------------------------------------
// COLLECTION 4: serviceAreas
// Stores disconnected areas as MultiPolygon
// -----------------------------------------------------------------------------
 
db.serviceAreas.insertMany([
  {
    provider: "FixIt Services",
    serviceArea: {
      type: "MultiPolygon",
      coordinates: [
        [[
          [16.3630, 48.2060],
          [16.3680, 48.2060],
          [16.3680, 48.2100],
          [16.3630, 48.2100],
          [16.3630, 48.2060]
        ]],
        [[
          [16.3780, 48.2090],
          [16.3840, 48.2090],
          [16.3840, 48.2130],
          [16.3780, 48.2130],
          [16.3780, 48.2090]
        ]]
      ]
    }
  }
]);
 
// -----------------------------------------------------------------------------
// COLLECTION 5: campuses
// Shows MultiPoint usage for grouped point locations
// -----------------------------------------------------------------------------
 
db.campuses.insertMany([
  {
    name: "Tech Campus",
    entrances: {
      type: "MultiPoint",
      coordinates: [
        [16.3715, 48.2080],
        [16.3732, 48.2086],
        [16.3768, 48.2094]
      ]
    }
  }
]);
 
// -----------------------------------------------------------------------------
// Indexes
// -----------------------------------------------------------------------------
 
db.places.createIndex({ location: "2dsphere" });
db.routes.createIndex({ route: "2dsphere" });
db.districts.createIndex({ area: "2dsphere" });
db.serviceAreas.createIndex({ serviceArea: "2dsphere" });
db.campuses.createIndex({ entrances: "2dsphere" });
 
// -----------------------------------------------------------------------------
// TASKS
// Solve the following tasks with MongoDB geospatial queries.
// Try to use the most appropriate operator for each task.
// -----------------------------------------------------------------------------
 
// Task 1
// Show all documents from db.places.
// Identify which field contains the GeoJSON value.
 
// Task 2
// Find all places in category "cafeteria".
// This task is not geospatial yet; it is only meant to warm up.
 
// Task 3
// Find the place nearest to this point:
// [16.3720, 48.2085]
// Return only one result.
 
// Task 4
// Find all places within 700 meters of this point:
// [16.3720, 48.2085]
// The results should be ordered from nearest to farthest.
 
// Task 5
// Find all places inside the polygon of the district named "Campus Core".
// Hint: first inspect the district document, then use its geometry in a query.
 
// Task 6
// Determine in which district the point
// [16.3810, 48.2110]
// lies.
 
// Task 7
// Find all routes that intersect the area of "Campus Core".
// This should return both LineString and MultiLineString documents if they cross the area.
 
// Task 8
// Find all places that are inside the service area of "FixIt Services".
// This combines point data with a MultiPolygon search area.
 
// Task 9
// Use an aggregation pipeline with $geoNear to find the three nearest places
// to [16.3720, 48.2085].
// Add the computed distance in a field named distanceMeters.
// Return only: name, category, distanceMeters.
 
// Task 10
// Find all districts that intersect the route named "Evacuation Route East".
// Hint: first inspect the route geometry, then use it as the query geometry.
 
// Task 11
// Check whether the point
// [16.3670, 48.2080]
// lies inside the service area of "FixIt Services".
// Return only the matching provider document if it does.
 
// Task 12
// Find whether any route intersects at least one entrance of "Tech Campus".
// This task uses the MultiPoint geometry stored in db.campuses.
 
// Task 13
// Harder task:
// Return all places that are both
// 1. inside "Campus Core"
// 2. and within 900 meters of [16.3720, 48.2085]
// Think carefully about whether you want ordering by distance or pure containment first.
 
// Task 14
// Harder task:
// Return the nearest place in each category to [16.3720, 48.2085].
// Use an aggregation pipeline.
 
// Task 15
// Open design task:
// Extend the data model with one additional collection of your own choice
// (for example: parking zones, bike stations, emergency exits, bus stops).
// Add suitable GeoJSON data and write two meaningful geospatial queries for it.

Suggested reflection questions

  • When is Point enough, and when do you need Polygon?
  • When does $near make more sense than $geoWithin?
  • Why is 2dsphere indexing important?
  • Why can one MultiPolygon document be more useful than many separate Polygon documents?