HomeLearnHow-toMongoDB Geospatial Queries in C#

MongoDB Geospatial Queries in C#

Updated: Feb 09, 2022 |

Published: Feb 09, 2022

  • Atlas
  • MongoDB
  • C#
  • ...

By Adrienne Tacke

Rate this article

#MongoDB Geospatial Queries with C#

If you've ever glanced at a map to find the closest lunch spots to you, you've most likely used a geospatial query under the hood! Using GeoJSON objects to store geospatial data in MongoDB Atlas, you can create your own geospatial queries for your application. In this tutorial, we'll see how to work with geospatial queries in the MongoDB C# driver.

#Quick Jump

#
What are Geospatial Queries?

Geospatial queries allow you to work with geospatial data. Whether that's on a 2d space (like a flat map) or 3d space (when viewing a spherical representation of the world), geospatial data allows you to find areas and places in reference to a point or set of points.

These might sound complicated, but you've probably encountered these use cases in everyday life: searching for points of interest in a new city you're exploring, discovering which coffee shops are closest to you, or finding every bakery within a three-mile radius of your current position (for science!).

These kinds of queries can easily be done with special geospatial query operators in MongoDB. And luckily for us, these operators are also implemented in most of MongoDB's drivers, including the C# driver we'll be using in this tutorial.

#
GeoJSON

One important aspect of working with geospatial data is something called the GeoJSON format. It's an open standard for representing simple geographical features and makes it easier to work with geospatial data. Here's what some of the GeoJSON object types look like:

1// Point GeoJSON type
2{
3 "type" : "Point",
4 "coordinates" : [-115.20146200000001, 36.114704000000003]
5}
6
7// Polygon GeoJSON type
8{
9 "type": "Polygon",
10 "coordinates": [
11 [
12 [100.0, 0.0],
13 [101.0, 0.0],
14 [101.0, 1.0],
15 [100.0, 1.0],
16 [100.0, 0.0]
17 ]
18 ]
19}

While MongoDB supports storing your geospatial data as legacy coordinate pairs, it's preferred to work with the GeoJSON format as it makes complicated queries possible and much simpler.

💡 Whether working with coordinates in the GeoJSON format or as legacy coordinate pairs, queries require the longitude to be passed first, followed by latitude. This might seem "backwards" compared to what you may be used to, but be assured that this format actually follows the (X, Y) order of math! Keep this in mind as MongoDB geospatial queries will also require coordinates to be passed in [longitude, latitude] format where applicable.

Alright, let's get started with the tutorial!

#
Prerequisites

#
Atlas Setup

To make this tutorial easier to follow along, we'll work with the restaurants and neighborhoods datasets, both publicly available in our documentation. They are both JSON files that contain a sizable amount of New York restaurant and neighborhood data already in GeoJSON format!

#
Download Sample Data and Import into Your Atlas Cluster

First, download this restaurants.json file and this neighborhoods.json file.

💡 These files differ from the the sample_restaurants dataset that can be loaded in Atlas! While the collection names are the same, the JSON files I'm asking you to download already have data in GeoJSON format, which will be required for this tutorial.

Then, follow these instructions to import both datasets into your cluster.

💡 When you reach Step 5 of importing your data into your cluster (Run mongoimport), be sure to keep track of the database and collection names you pass into the command. We'll need them later! If you want to use the same names as in this tutorial, my database is called sample-geo and my collections are called restaurants and neighborhoods .

#
Create 2dsphere Indexes

Lastly, to work with geospatial data, a 2dsphere index needs to be created for each collection. You can do this in the MongoDB Atlas portal.

First navigate to your cluster and click on "Browse Collections":

Screenshot of MongoDB Atlas UI with green outline spotlighting the "Browse Collections" button.

You'll be brought to your list of collections. Find your restaurant data (if following along, it will be a collection called restaurants within the sample-geo database).

With the collection selected, click on the "Indexes" tab:

Screenshot of MongoDB Atlas UI with green outline spotlighting the "Indexes" tab.

Click on the "CREATE INDEX" button to open the index creation wizard. In the "Fields" section, you'll specify which field to create an index on, as well as what type of index. For our tutorial, clear the input, and copy and paste the following:

1{ "location": "2dsphere" }

Click "Review". You'll be asked to confirm creating an index on sample-geo.restaurants on the field { "location": "2dsphere" } (remember, if you aren't using the same database and collection names, confirm your index is being created on yourDatabaseName.yourCollectionName). Click "Confirm."

Likewise, find your neighborhood data (sample-geo.neighborhoods unless you used different names). Select your neighborhoods collection and do the same thing, this time creating this index:

1{ "geometry": "2dsphere" }

Almost instantly, the indexes will be created. You'll know the index has been successfully created once you see it listed under the Indexes tab for your selected collection.

Screenshot of location_2dsphere created in MongoDB Atlas.

Now, you're ready to work with your restaurant and neighborhood data!

#
Creating the Project

To show these samples, we'll be working within the context of a simple console program. We'll implement each geospatial query operator as its own method and log the corresponding MongoDB Query it executes.

After creating a new console project, add the MongoDB Driver to your project using the Package Manager or the .NET CLI:

Package Manager

1Install-Package MongoDB.Driver

.NET CLI

1dotnet add package MongoDB.Driver

Next, add the following dependencies to your Program.cs file:

1using MongoDB.Bson;
2using MongoDB.Bson.IO;
3using MongoDB.Bson.Serialization;
4using MongoDB.Driver;
5using MongoDB.Driver.GeoJsonObjectModel;
6using System;

For all our examples, we'll be using the following Restaurant and Neighborhood classes as our models:

1public class Restaurant
2{
3 public ObjectId Id { get; set; }
4 public GeoJsonPoint<GeoJson2DCoordinates> Location { get; set; }
5 public string Name { get; set; }
6}
1public class Neighborhood
2{
3 public ObjectId Id { get; set; }
4 public GeoJsonPoint<GeoJson2DCoordinates> Geometry { get; set; }
5 public string Name { get; set; }
6}

Add both to your application. For simplicity, I've added them as additional classes in my Program.cs file.

Next, we need to connect to our cluster. Place the following code within the Main method of your program:

1// Be sure to update yourUsername, yourPassword, yourClusterName, and yourProjectId to your own!
2// Similarly, also update "sample-geo", "restaurants", and "neighborhoods" to whatever you've named your database and collections.
3var client = new MongoClient("mongodb+srv://yourUsername:yourPassword@yourClusterName.yourProjectId.mongodb.net/sample-geo?retryWrites=true&w=majority");
4var database = client.GetDatabase("sample-geo");
5var restaurantCollection = database.GetCollection<Restaurant>("restaurants");
6var neighborhoodCollection = database.GetCollection<Neighborhood>("neighborhoods");

Finally, we'll add a helper method called Log() within our Program class. This will take the geospatial queries we write in C# and log the corresponding MongoDB Query to the console. This gives us an easy way to copy it and use elsewhere.

1private static void Log(string exampleName, FilterDefinition<Restaurant> filter)
2{
3 var serializerRegistry = BsonSerializer.SerializerRegistry;
4 var documentSerializer = serializerRegistry.GetSerializer<Restaurant>();
5 var rendered = filter.Render(documentSerializer, serializerRegistry);
6 Console.WriteLine($"{exampleName} example:");
7 Console.WriteLine(rendered.ToJson(new JsonWriterSettings { Indent = true }));
8 Console.WriteLine();
9}

We now have our structure in place. Now we can create the geospatial query methods!

#
Geospatial Query Code Examples in C#

Since MongoDB has dedicated operators for geospatial queries, we can take advantage of the C# driver's filter definition builder to build type-safe queries. Using the filter definition builder also provides both compile-time safety and refactoring support in Visual Studio, making it a great way to work with geospatial queries.

#
$near Example in C#

The .Near filter implements the $near geospatial query operator. Use this when you want to return geospatial objects that are in proximity to a center point, with results sorted from nearest to farthest.

In our program, let's create a NearExample() method that does that. Let's search for restaurants that are at most 10,000 meters away and at least 2,000 meters away from a Magnolia Bakery (on Bleecker Street) in New York:

1private static void NearExample(IMongoCollection<Restaurant> collection)
2{
3 // Instantiate builder
4 var builder = Builders<Restaurant>.Filter;
5
6 // Set center point to Magnolia Bakery on Bleecker Street
7 var point = GeoJson.Point(GeoJson.Position(-74.005, 40.7358879));
8
9 // Create geospatial query that searches for restaurants at most 10,000 meters away,
10 // and at least 2,000 meters away from Magnolia Bakery (AKA, our center point)
11 var filter = builder.Near(x => x.Location, point, maxDistance: 10000, minDistance: 2000);
12
13 // Log filter we've built to the console using our helper method
14 Log("$near", filter);
15}

That's it! Whenever we call this method, a $near query will be generated that you can copy and paste from the console. Feel free to paste that query into the data explorer in Atlas to see which restaurants match the filter (don't forget to change "Location" to a lowercase "location" when working in Atlas). In a future post, we'll delve into how to visualize these results on a map!

For now, you can call this method (and all other following methods) from the Main method like so:

1static void Main(string[] args)
2{
3 var client = new MongoClient("mongodb+srv://yourUsername:yourPassword@yourClusterName.yourProjectId.mongodb.net/sample-geo?retryWrites=true&w=majority");
4 var database = client.GetDatabase("sample-geo");
5 var restaurantCollection = database.GetCollection<Restaurant>("restaurants");
6 var neighborhoodCollection = database.GetCollection<Neighborhood>("neighborhoods");
7
8 NearExample(restaurantCollection);
9 // Add other methods here as you create them
10}

⚡ Feel free to modify this code! Change your center point by changing the coordinates or let the method accept variables for the point, maxDistance, and minDistance parameters instead of hard-coding it.

In most use cases, .Near will do the trick. It measures distances against a flat, 2d plane (Euclidean plane) that will be accurate for most applications. However, if you need queries to run against spherical, 3d geometry when measuring distances, use the .NearSphere filter (which implements the $nearSphere operator). It accepts the same parameters as .Near, but will calculate distances using spherical geometry.

#
$geoWithin Example in C#

The .GeoWithin filter implements the $geoWithin geospatial query operator. Use this when you want to return geospatial objects that exist entirely within a specified shape, either a GeoJSON Polygon, MultiPolygon, or shape defined by legacy coordinate pairs. As you'll see in a later example, that shape can be a circle and can be generated using the $center operator.

To implement this in our program, let's create a GeoWithinExample() method that searches for restaurants within an area—specifically, this area:

Map view of New York City showing a shaded area.

In code, we describe this area as a polygon and work with it as a list of points:

1private static void GeoWithinExample(IMongoCollection<Restaurant> collection)
2{
3 var builder = Builders<Restaurant>.Filter;
4
5 // Build polygon area to search within.
6 // This must always begin and end with the same coordinate
7 // to "close" the polygon and fully surround the area.
8 var coordinates = new GeoJson2DCoordinates[]
9 {
10 GeoJson.Position(-74.0011869, 40.752482),
11 GeoJson.Position(-74.007384, 40.743641),
12 GeoJson.Position(-74.001856, 40.725631),
13 GeoJson.Position(-73.978511, 40.726793),
14 GeoJson.Position(-73.974408, 40.755243),
15 GeoJson.Position(-73.981669, 40.766716),
16 GeoJson.Position(-73.998423, 40.763535),
17 GeoJson.Position(-74.0011869, 40.752482),
18 };
19 var polygon = GeoJson.Polygon(coordinates);
20
21 // Create geospatial query that searches for restaurants that fully fall within the polygon.
22 var filter = builder.GeoWithin(x => x.Location, polygon);
23
24 // Log the filter we've built to the console using our helper method.
25 Log("$geoWithin", filter);
26}

#
$geoIntersects Example in C#

The .GeoIntersects filter implements the $geoIntersects geospatial query operator. Use this when you want to return geospatial objects that span the same area as a specified object, usually a point.

For our program, let's create a GeoIntersectsExample() method that checks if a specified point falls within one of the neighborhoods stored in our neighborhoods collection:

1private static void GeoIntersectsExample(IMongoCollection<Neighborhood> collection)
2{
3 var builder = Builders<Neighborhood>.Filter;
4
5 // Set specified point. For example, the location of a user (with granted permission)
6 var point = GeoJson.Point(GeoJson.Position(-73.996284, 40.720083));
7
8 // Create geospatial query that searches for neighborhoods that intersect with specified point.
9 // In other words, return results where the intersection of a neighborhood and the specified point is non-empty.
10 var filter = builder.GeoIntersects(x => x.Geometry, point);
11
12 // Log the filter we've built to the console using our helper method.
13 Log("$geoIntersects", filter);
14}

💡 For this method, an overloaded Log() method that accepts a FilterDefinition of type Neighborhood needs to be created.

#
Combined $geoWithin and $center Example in C#

As we've seen, the $geoWithin operator returns geospatial objects that exist entirely within a specified shape. We can set this shape to be a circle using the $center operator.

Let's create a GeoWithinCenterExample() method in our program. This method will search for all restaurants that exist within a circle that we have centered on the Brooklyn Bridge:

1private static void GeoWithinCenterExample(IMongoCollection<Restaurant> collection)
2{
3 var builder = Builders<Restaurant>.Filter;
4
5 // Set center point to Brooklyn Bridge
6 var point = GeoJson.Point(GeoJson.Position(-73.99631, 40.705396));
7
8 // Create geospatial query that searches for restaurants that fall within a radius of 20 (units used by the coordinate system)
9 var filter = builder.GeoWithinCenter(x => x.Location, point.Coordinates.X, point.Coordinates.Y, 20);
10 Log("$geoWithin.$center", filter);
11}

#
Combined $geoWithin and $centerSphere Example in C#

Another way to query for places is by combining the $geoWithin and $centerSphere geospatial query operators. This differs from the $center operator in a few ways:

  • $centerSphere uses spherical geometry while $center uses flat geometry for calculations.
  • $centerSphere works with both GeoJSON objects and legacy coordinate pairs while $center only works with and returns legacy coordinate pairs.
  • $centerSphere uses radians for distance, which requires additional calculations to produce an accurate query. $center uses the units used by the coordinate system and may be less accurate for some queries.

We'll get to our example method in a moment, but first, a little context on how to calculate radians for spherical geometry!

#
Spherical Geometry Calculations with Radians

💡 An important thing about working with $centerSphere (and any other geospatial operators that use spherical geometry), is that it uses radians for distance. This means the distance units used in queries (miles or kilometers) first need to be converted to radians. Using radians properly considers the spherical nature of the object we're measuring (usually Earth) and let's the $centerSphere operator calculate distances correctly.

Use this handy chart to convert between distances and radians:

ConversionDescriptionExample Calculation
distance (miles) to radiansDivide the distance by the radius of the sphere (e.g., the Earth) in miles. The equitorial radius of the Earth in miles is approximately 3,963.2.Search for objects with a radius of 100 miles: 100 / 3963.2
distance (kilometers) to radiansDivide the distance by the radius of the sphere (e.g., the Earth) in kilometers. The equitorial radius of the Earth in kilometers is approximately 6,378.1.Search for objects with a radius of 100 kilometers: 100 / 6378.1
radians to distance(miles)Multiply the radian measure by the radius of the sphere (e.g., the Earth). The equitorial radius of the Earth in miles is approximately 3,963.2.Find the radian measurement of 50 in miles: 50 * 3963.2
radians to distance(kilometers)Multiply the radian measure by the radius of the sphere (e.g., the Earth). The equitorial radius of the Earth in kilometers is approximately 6,378.1.Find the radian measurement of 50 in kilometers: 50 * 6378.1

#Let's Get Back to the Example!

For our program, let's create a GeoWithinCenterSphereExample() that searches for all restaurants within a three-mile radius of Apollo Theater in Harlem:

1private static void GeoWithinCenterSphereExample(IMongoCollection<Restaurant> collection)
2{
3 var builder = Builders<Restaurant>.Filter;
4
5 // Set center point to Apollo Theater in Harlem
6 var point = GeoJson.Point(GeoJson.Position(-73.949995, 40.81009));
7
8 // Create geospatial query that searches for restaurants that fall within a 3-mile radius of Apollo Theater.
9 // Notice how we pass our 3-mile radius parameter as radians (3 / 3963.2). This ensures accurate calculations with the $centerSphere operator.
10 var filter = builder.GeoWithinCenterSphere(x => x.Location, point.Coordinates.X, point.Coordinates.Y, 3 / 3963.2);
11
12 // Log the filter we've built to the console using our helper method.
13 Log("$geoWithin.$centerSphere", filter);
14}

#
Next Time on Geospatial Queries in C#

As we've seen, working with MongoDB geospatial queries in C# is possible through its support for the geospatial query operators. In another tutorial, we'll take a look at how to visualize our geospatial query results on a map!

If you have any questions or get stuck, don't hesitate to post on our MongoDB Community Forums! And if you found this tutorial helpful, don't forget to rate it and leave any feedback. This helps us improve our articles so that they are awesome for everyone!

Rate this article
MongoDB logo
© 2021 MongoDB, Inc.

About

  • Careers
  • Investor Relations
  • Legal Notices
  • Privacy Notices
  • Security Information
  • Trust Center
© 2021 MongoDB, Inc.