I've been banging my head on this one for days. I have a very simple query I'm trying to run in C#, it looks like this in the shell.
db.runCommand({geoNear: "items", near: {type: "Point", coordinates : [-111.283344899999, 47.4941836]}, spherical : true, distanceMultiplier: 3963.2, maxDistance : 25});
My collection looks like this
{
"_id" : ObjectId(),
"Title" : "arst",
"Description" : "<p>arst</p>",
"Date" : new Date("11/29/2015 09:28:15"),
"Location" : {
"type" : "Point",
"Coordinates" : [-111.28334489999998, 47.4941836]
},
"Zip" : "59405"
}
According to the docs here MongoDB C# API Docs the MongoDB.Driver.Builders.Query object is now legacy. So when I do something like this
var point = new GeoJson2DGeographicCoordinates(double.Parse(longitude), double.Parse(latitude)) ;
var query = Query<Item>.Near(x => x.Location, new GeoJsonPoint<GeoJson2DGeographicCoordinates>(point), distance, true);
var result = collection.Find(query);
The compiler complains that it can't convert from IMongoQuery to FilterDefinition. This tells me that the legacy Query<> builder isn't supported by the new 2.1 library. But for the life of me I can't find anywhere in the api docs that reference a replacement?
Can anyone point me in the right direction on executing this simple geo-spatial query in the 2.1 C# Driver? I'm stumped.
Also, I do have a 2dsphere index created on the collection, if I didn't the shell command wouldn't work. Here's the index output.
{
"v" : 1,
"key" : {
"Location.Coordinates" : "2dsphere"
},
"name" : "Location.Coordinates_2dsphere",
"ns" : "ppn.items",
"2dsphereIndexVersion" : 2
}
EDIT
After digging through a TON of documentation I think I found it. All the examples still show the legacy Query<> method, but it seems that the new method is part of the Builders<>.Filter namespace. So this code block seems to be working for me,
double lng = double.Parse(longitude);
double lat = double.Parse(latitude);
point = new GeoJson2DGeographicCoordinates(lng, lat);
pnt = new GeoJsonPoint<GeoJson2DGeographicCoordinates>(point);
dis = distance * 1609.34;
fil = Builders<Item>.Filter.NearSphere(p => p.Location.Coordinates, pnt, dis);
filter = filter & fil;
var sort = Builders<Item>.Sort.Descending("Date");
// This is the actual query execution
List<Item> items = collection.Find(filter).Sort(sort).ToListAsync().Result;
This code block is very messy, it's the result of try and fail over and over. I'm sure I'll figure out ways to clean it up. It seems a little verbose to me that you have to create a GeoJsonPoint from a GeoJson2DGeographicCoordinates, but I'm sure there's a good reason for it. If anyone knows, please feel free to comment. Any suggestions on improving this answer are very welcome, this has been a frustrating dig through documentation, so hopefully this helps point someone else in the right direction.
this is how I do it on my end:
public IQueryable<TEntity> FindNear<TEntity>(string collectionName, Expression<Func<TEntity, object>> field, double longitude, double latitude, double maxDistanceInKm) where TEntity : IEntity
{
var collection = database.GetCollection<TEntity>(collectionName);
var point = GeoJson.Point(GeoJson.Geographic(longitude, latitude));
var filter = Builders<TEntity>.Filter.Near(field, point, maxDistanceInKm * 1000);
return collection.Find(filter).ToList().AsQueryable();
}
here's the most convenient way to do geospatial aggregation queries for anybody that's interested:
using MongoDB.Driver;
using MongoDB.Entities;
using System;
namespace StackOverflow
{
public class Program
{
public class Place : Entity
{
public string Name { get; set; }
public DateTime Date { get; set; }
public Coordinates2D Location { get; set; }
public double DistanceMeters { get; set; }
}
static void Main(string[] args)
{
//connect to mongodb
new DB("test");
//create a geo2dsphere index
DB.Index<Place>()
.Key(x => x.Location, KeyType.Geo2DSphere)
.Option(x => x.Background = false)
.Create();
//create and save a place
var paris = new Place
{
Name = "paris",
Location = new Coordinates2D(48.8539241, 2.2913515),
Date = DateTime.UtcNow
};
paris.Save();
var eiffelTower = new Coordinates2D(48.857908, 2.295243);
//find all places within 1km of eiffel tower.
var places = DB.GeoNear<Place>(
NearCoordinates: eiffelTower,
DistanceField: x => x.DistanceMeters,
MaxDistance: 1000)
.SortByDescending(x=>x.Date)
.ToList();
}
}
}
it generates the following aggregation pipeline:
{
"$geoNear": {
"near": {
"type": "Point",
"coordinates": [
48.857908,
2.295243
]
},
"distanceField": "DistanceMeters",
"spherical": true,
"maxDistance": NumberInt("1000")
}
},
{
"$sort": {
"Date": NumberInt("-1")
}
}
the above is done using MongoDB.Entities convenience library, of which i'm the author of.
This is how I do it. It's very simple and has the bonus of being able to copy and paste your query from the mongo shell into the query variables.
For a simple find, returning 1 document if within 100 metres:
var nearSphereQuery = "{location: { $nearSphere: {$geometry: { type: \"Point\", coordinates: [-1.50,53.50]}, $maxDistance: 100}} }";
var nearSphereBsonDoc = MongoDB.Bson.Serialization.BsonSerializer.Deserialize<BsonDocument>(nearSphereQuery);
var result = points.Find(nearSphereBsonDoc).Limit(1).SingleOrDefault();
if (result != null) {
// do stuff here
} else {
// nothing found
}
To get the distance from the search point for the same request:
var geoNearQuery = "{$geoNear: { near: { type: \"Point\", coordinates: [-1.50, 53.50]}, spherical: true, distanceField: \"distance\"} } ,{$match: { distance: { $lt: 100} } }";
var geoNearBsonDoc = MongoDB.Bson.Serialization.BsonSerializer.Deserialize<BsonDocument>(geoNearQuery);
var pipeline = new List<BsonDocument>();
var matchQuery = "{ $match: { distance: { $lt: 10} } }";
var matchBsonDoc = MongoDB.Bson.Serialization.BsonSerializer.Deserialize<BsonDocument>(matchQuery);
var pipeline = new List<BsonDocument>();
pipeline.Add(geoNearBsonDoc);
pipeline.Add(matchBsonDoc);
pipeline.Add(new BsonDocument { { "$limit", 1 } });
var result = points.Aggregate<BsonDocument>(pipeline).SingleOrDefault();
if (result != null) {
Double dist = Convert.ToDouble(result["distance"]);
} else {
// nothing found
}