# MongoDB Geospatial Queries in C#

# 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?
    * GeoJSON
* Prerequisities
* Atlas Setup
    * Download and Import Sample Data
    * Create 2dsphere Indexes
* Creating the Project
* Geospatial Query Code Examples
    * $near
    * $geoWithin
    * $geoIntersects
    * $geoWithin and $center Combined
    * $geoWithin and $centerSphere Combined
* Spherical Geometry Calculations with Radians
## 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:

``` JSON
// Point GeoJSON type
{
    "type" : "Point",
    "coordinates" : -115.20146200000001, 36.114704000000003]
}

// Polygon GeoJSON type
{
    "type": "Polygon",
    "coordinates": [
        [
            [100.0, 0.0], 
            [101.0, 0.0], 
            [101.0, 1.0],
            [100.0, 1.0], 
            [100.0, 0.0]
        ]
    ]
}
```

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

* [Visual Studio Community (2019 or higher)
* MongoDB C#/.NET Driver (latest preferred, minimum 2.11)
* MongoDB Atlas cluster
* mongosh

## 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":

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:

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:

``` JSON
{ "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:

``` JSON
{ "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.

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*

```
Install-Package MongoDB.Driver
```

*.NET CLI*

```
dotnet add package MongoDB.Driver
```

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

``` csharp
using MongoDB.Bson;
using MongoDB.Bson.IO;
using MongoDB.Bson.Serialization;
using MongoDB.Driver;
using MongoDB.Driver.GeoJsonObjectModel;
using System;
```

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

``` csharp
public class Restaurant
{
    public ObjectId Id { get; set; }
    public GeoJsonPoint Location { get; set; }
    public string Name { get; set; }
}
```

``` csharp
public class Neighborhood
{
    public ObjectId Id { get; set; }
    public GeoJsonPoint Geometry { get; set; }
    public string Name { get; set; }
}
```

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:

``` csharp
// Be sure to update yourUsername, yourPassword, yourClusterName, and yourProjectId to your own! 
// Similarly, also update "sample-geo", "restaurants", and "neighborhoods" to whatever you've named your database and collections.
var client = new MongoClient("mongodb+srv://yourUsername:yourPassword@yourClusterName.yourProjectId.mongodb.net/sample-geo?retryWrites=true&w=majority");
var database = client.GetDatabase("sample-geo");
var restaurantCollection = database.GetCollection("restaurants");
var neighborhoodCollection = database.GetCollection("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.

``` csharp
private static void Log(string exampleName, FilterDefinition filter)
{
    var serializerRegistry = BsonSerializer.SerializerRegistry;
    var documentSerializer = serializerRegistry.GetSerializer();
    var rendered = filter.Render(documentSerializer, serializerRegistry);
    Console.WriteLine($"{exampleName} example:");
    Console.WriteLine(rendered.ToJson(new JsonWriterSettings { Indent = true }));
    Console.WriteLine();
}
```

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:

``` cs
private static void NearExample(IMongoCollection collection)
{
    // Instantiate builder
    var builder = Builders.Filter;

    // Set center point to Magnolia Bakery on Bleecker Street
    var point = GeoJson.Point(GeoJson.Position(-74.005, 40.7358879));

    // Create geospatial query that searches for restaurants at most 10,000 meters away,
    // and at least 2,000 meters away from Magnolia Bakery (AKA, our center point)
    var filter = builder.Near(x => x.Location, point, maxDistance: 10000, minDistance: 2000);

    // Log filter we've built to the console using our helper method
    Log("$near", filter);
}
```

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:

```cs
static void Main(string] args)
{
    var client = new MongoClient("mongodb+srv://yourUsername:yourPassword@yourClusterName.yourProjectId.mongodb.net/sample-geo?retryWrites=true&w=majority");
    var database = client.GetDatabase("sample-geo");
    var restaurantCollection = database.GetCollection("restaurants");
    var neighborhoodCollection = database.GetCollection("neighborhoods");

    NearExample(restaurantCollection);
    // Add other methods here as you create them
}
```

> ⚡ 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:

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

``` cs
private static void GeoWithinExample(IMongoCollection collection)
{
    var builder = Builders.Filter;

    // Build polygon area to search within.
    // This must always begin and end with the same coordinate 
    // to "close" the polygon and fully surround the area.
    var coordinates = new GeoJson2DCoordinates]
    {
        GeoJson.Position(-74.0011869, 40.752482),
        GeoJson.Position(-74.007384,  40.743641),
        GeoJson.Position(-74.001856,  40.725631),
        GeoJson.Position(-73.978511,  40.726793),
        GeoJson.Position(-73.974408,  40.755243),
        GeoJson.Position(-73.981669,  40.766716),
        GeoJson.Position(-73.998423,  40.763535),
        GeoJson.Position(-74.0011869, 40.752482),
    };
    var polygon = GeoJson.Polygon(coordinates);

    // Create geospatial query that searches for restaurants that fully fall within the polygon.
    var filter = builder.GeoWithin(x => x.Location, polygon);

    // Log the filter we've built to the console using our helper method.
    Log("$geoWithin", filter);
}
```

### $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:

``` cs
private static void GeoIntersectsExample(IMongoCollection collection)
{
    var builder = Builders.Filter;

    // Set specified point. For example, the location of a user (with granted permission)
    var point = GeoJson.Point(GeoJson.Position(-73.996284, 40.720083));

    // Create geospatial query that searches for neighborhoods that intersect with specified point.
    // In other words, return results where the intersection of a neighborhood and the specified point is non-empty.
    var filter = builder.GeoIntersects(x => x.Geometry, point);

    // Log the filter we've built to the console using our helper method.
    Log("$geoIntersects", filter);
}
```

> 💡 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:

``` cs
private static void GeoWithinCenterExample(IMongoCollection collection)
{
    var builder = Builders.Filter;

    // Set center point to Brooklyn Bridge
    var point = GeoJson.Point(GeoJson.Position(-73.99631, 40.705396));

    // Create geospatial query that searches for restaurants that fall within a radius of 20 (units used by the coordinate system)
    var filter = builder.GeoWithinCenter(x => x.Location, point.Coordinates.X, point.Coordinates.Y, 20);
    Log("$geoWithin.$center", filter);
}
```

### 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:

| Conversion | Description | Example Calculation |
| ---------- | ----------- | ------------------- |
| *distance (miles) to radians* | Divide 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 radians* | Divide 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:

``` cs
private static void GeoWithinCenterSphereExample(IMongoCollection collection)
{
    var builder = Builders.Filter;

    // Set center point to Apollo Theater in Harlem
    var point = GeoJson.Point(GeoJson.Position(-73.949995, 40.81009));

    // Create geospatial query that searches for restaurants that fall within a 3-mile radius of Apollo Theater.
    // Notice how we pass our 3-mile radius parameter as radians (3 / 3963.2). This ensures accurate calculations with the $centerSphere operator.
    var filter = builder.GeoWithinCenterSphere(x => x.Location, point.Coordinates.X, point.Coordinates.Y, 3 / 3963.2);

    // Log the filter we've built to the console using our helper method.
    Log("$geoWithin.$centerSphere", filter);
}
```

## 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!