We are moving from MongoDb to RavenDb, and ran into an issue.
We have a simple model (C# DotNet)
using System.Globalization;
public class Location
{
#region Properties
public double Latitude { get; set; }
public double Longitude { get; set; }
#endregion Properties
#region Methods
public override string ToString()
{
var numberFormatInfo = new NumberFormatInfo { NumberDecimalSeparator = "." };
return $"{Longitude.ToString(numberFormatInfo)} {Latitude.ToString(numberFormatInfo)}";
}
#endregion Methods
}
public class Place
{
public Location[] Area {get;set;} // Please note the array
}
A customer can either select a place as a single pair, or they can draw a polygon on an interactive map, which represents the place, that would get stored in the Area property above.
In MongoDb this could not have been simpler
var locationQuery = new FilterDefinitionBuilder<Place>()
.GeoWithinBox(
field: x => x.Area,
lowerLeftX: query.Criteria.LongitudeBottomLeft,
lowerLeftY: query.Criteria.LatitudeBottomLeft,
upperRightX: query.Criteria.LongitudeTopRight,
upperRightY: query.Criteria.LatitudeTopRight);
The query class above, represents the currently visible area on the map, the customer is looking at.
Despite my best attempts, I cannot decipher the documentation on how to achieve the same. I am hoping someone may be able to shed some light on the matter, on how one would achieve this using the RavenDb Dotnet client (RavenDB.Client)
Thanks
Update & Sample!
After the great answer below from Ayende Rahien, I thought I would add some details here in attempt to simplify the journey for others landing here.
This is what the index looked like.
using System.Linq;
using Raven.Client.Documents.Indexes;
public class PlaceAreaIndex : AbstractIndexCreationTask<Place>
{
public PlaceAreaIndex()
{
Map = places => from place in places
select new
{
Area = place.Area.Select(location => CreateSpatialField(location.Latitude, location.Longitude))
};
}
}
You will need to register your index on the server, before using the query.
new PlaceAreaIndex().Execute(store);
I added some helper classes to assist me testing this out, I am adding them here so the sample gets you as close as possible to a running code
public class Box
{
#region Constructor
public Box(Location topRight, Location bottomLeft)
{
TopRight = topRight;
BottomLeft = bottomLeft;
TopLeft = new Location { Latitude = topRight.Latitude, Longitude = bottomLeft.Longitude };
BottomRight = new Location { Latitude = bottomLeft.Latitude, Longitude = topRight.Longitude };
}
#endregion Constructor
#region Properties
public Location TopRight { get;}
public Location TopLeft { get; }
public Location BottomLeft { get; }
public Location BottomRight { get; }
#endregion Properties
#region Methods
public override string ToString()
{
return $"POLYGON (({TopRight}, {TopLeft}, {BottomLeft}, {BottomRight}, {TopRight}))";
}
#endregion Methods
}
Then using the helpers
var topRight = Location.New(latitude: -22.674847351188916, longitude: 31.25061035156253);
var bottomLeft = Location.New(latitude: -28.9600886880069, longitude: 25.1422119140623);
var box = new Box(topRight: topRight, bottomLeft: bottomLeft);
After that you can query it like this
var places = await session
.Query<Place, PlaceAreaIndex>()
.Spatial(
factory => factory.Area,
criteria => criteria.RelatesToShape(
shapeWkt: box.ToString(),
relation: SpatialRelation.Within))
.ToArrayAsync();
Hope it helps you.

