Indexing spatial data
To support the ability to retrieve the data based on spatial coordinates, the spatial search has been introduced.
Creating indexes
To take an advantage of the spatial search, first we need to create an index with a spatial field. To mark field as the spatial field, we need to use SpatialGenerate
method:
object SpatialGenerate(double lat, double lng);
object SpatialGenerate(string fieldName, double lat, double lng);
object SpatialGenerate(string fieldName, string shapeWKT);
object SpatialGenerate(string fieldName, string shapeWKT, SpatialSearchStrategy strategy);
object SpatialGenerate(string fieldName, string shapeWKT, SpatialSearchStrategy strategy, int maxTreeLevel);
public enum SpatialSearchStrategy
{
GeohashPrefixTree,
QuadPrefixTree,
}
where:
- fieldName is a name of the field containing the shape to use for filtering (if the overload with no
fieldName
is used, then the name is set to default value:__spatial
) - lat/lng are latitude/longitude coordinates
- shapeWKT is a shape in the WKT format
- strategy is a spatial search strategy (default:
GeohashPrefixTree
) - maxTreeLevel is a integer that indicates the maximum number of levels to be used in the
PrefixTree
and controls the precision of shape representation (9 forGeohashPrefixTree
and 23 forQuadPrefixTree
)
In our example we will use Event
class and a very simple index defined below.
public class Event
{
public string Id { get; set; }
public string Name { get; set; }
public double Latitude { get; set; }
public double Longitude { get; set; }
}
public class Events_ByNameAndCoordinates : AbstractIndexCreationTask<Event>
{
public Events_ByNameAndCoordinates()
{
Map = events => from e in events
select new
{
Name = e.Name,
__ = SpatialGenerate("Coordinates", e.Latitude, e.Longitude)
};
}
}
If our Event
contains the WKT property already:
public class EventWithWKT
{
public string Id { get; set; }
public string Name { get; set; }
public string WKT { get; set; }
}
then can define our field using the Spatial
method in the AbstractIndexCreationTask
:
public class EventsWithWKT_ByNameAndWKT : AbstractIndexCreationTask<EventWithWKT>
{
public EventsWithWKT_ByNameAndWKT()
{
Map = events => from e in events
select new
{
Name = e.Name,
WKT = e.WKT
};
Spatial(x => x.WKT, options => options.Geography.Default());
}
}
where under options
we got access to our geography and Cartesian factories:
public class SpatialOptionsFactory
{
public GeographySpatialOptionsFactory Geography;
public CartesianSpatialOptionsFactory Cartesian;
}
// GeohashPrefixTree strategy with maxTreeLevel set to 9
SpatialOptions Default(SpatialUnits circleRadiusUnits = SpatialUnits.Kilometers);
SpatialOptions BoundingBoxIndex(SpatialUnits circleRadiusUnits = SpatialUnits.Kilometers);
SpatialOptions GeohashPrefixTreeIndex(int maxTreeLevel, SpatialUnits circleRadiusUnits = SpatialUnits.Kilometers);
SpatialOptions QuadPrefixTreeIndex(int maxTreeLevel, SpatialUnits circleRadiusUnits = SpatialUnits.Kilometers);
SpatialOptions BoundingBoxIndex();
SpatialOptions QuadPrefixTreeIndex(int maxTreeLevel, SpatialBounds bounds);
Spatial search strategies
GeohashPrefixTree
E.g. The location of 'New York' in the United States is represented by the following geohash: DR5REGY6R and it represents the 40.7144 -74.0060
coordinates. Removing characters from the end of geohash will decrease the precision level.
More information about geohash uses, decoding algorithm and limitations can be found here.
QuadPrefixTree
More information about QuadTree can be found here.
BoundingBox
Warning
GeohashPrefixTree
is a default SpatialSearchStrategy
. Doing any changes to the strategy after index has been created will trigger re-indexation process.
Precision
By default the precision level (maxTreeLevel
) for GeohashPrefixTree is set to 9 and for QuadPrefixTree the value is 23, which means that the coordinates are represented by a 9 or 23 character string. The difference exists, because the QuadTree
representation would be much less precise if the level would be the same.
Geohash precision values
Level | E-W distance at equator | N-S distance at equator |
---|---|---|
12 | ~3.7cm | ~1.8cm |
11 | ~14.9cm | ~14.9cm |
10 | ~1.19m | ~0.60m |
9 | ~4.78m | ~4.78m |
8 | ~38.2m | ~19.1m |
7 | ~152.8m | ~152.8m |
6 | ~1.2km | ~0.61km |
5 | ~4.9km | ~4.9km |
4 | ~39km | ~19.6km |
3 | ~157km | ~157km |
2 | ~1252km | ~626km |
1 | ~5018km | ~5018km |
Quadtree precision values
Level | Distance at equator |
---|---|
30 | ~4cm |
29 | ~7cm |
28 | ~15cm |
27 | ~30cm |
26 | ~60cm |
25 | ~1.19m |
24 | ~2.39m |
23 | ~4.78m |
22 | ~9.56m |
21 | ~19.11m |
20 | ~38.23m |
19 | ~76.23m |
18 | ~152.92m |
17 | ~305.84m |
16 | ~611.67m |
15 | ~1.22km |
14 | ~2.45km |
13 | ~4.89km |
12 | ~9.79km |
11 | ~19.57km |
10 | ~39.15km |
9 | ~78.29km |
8 | ~156.58km |
7 | ~313.12km |
6 | ~625.85km |
5 | ~1249km |
4 | ~2473km |
3 | ~4755km |
2 | ~7996km |
1 | ~15992km |
Format support
From version 2.5 RavenDB also supports indexing of GeoJSON objects.
var point = new
{
type = "Point",
coordinates = new[] { -10d, 45d }
};
session.Store(new SpatialDoc { Shape = point });
Beside the WKT and GeoJSON following formats are also supported:
session.Store(new SpatialDoc { Point = new[] { -10d, 45d } });
session.Store(new SpatialDoc { Point = new { X = -10d, Y = 45d } });
session.Store(new SpatialDoc { Point = new { Latitude = 45d, Longitude = -10d } });
session.Store(new SpatialDoc { Point = new { lat = 45d, lon = -10d } });
session.Store(new SpatialDoc { Point = new { lat = 45d, lng = -10d } });
session.Store(new SpatialDoc { Point = new { Lat = 45d, Long = -10d } });
session.Store(new SpatialDoc { Point = "geo:45.0,-10.0;u=2.0" }); // Geo URI
Third-party spatial library integration
To integrate with other spatial libraries, the document store must be configured to use a custom library-specific JsonConverter
which reads/writes WKT or GeoJSON.
Examples of such converters can be found at Simon Bartlett's github repository page.
Example
Let's assume that we have a SpatialDoc
with a corresponding index available:
public class SpatialDoc_ByShapeAndPoint : AbstractIndexCreationTask<SpatialDoc>
{
public SpatialDoc_ByShapeAndPoint()
{
Map = docs => from spatial in docs
select new
{
Shape = spatial.Shape,
Point = spatial.Point
};
Spatial(x => x.Shape, options => options.Geography.Default());
Spatial(x => x.Point, options => options.Cartesian.BoundingBoxIndex());
}
}
To find all results that are within radius of or intersect specified shape query as follows:
IList<SpatialDoc> results = session
.Query<SpatialDoc, SpatialDoc_ByShapeAndPoint>()
.Spatial(x => x.Shape, criteria => criteria.WithinRadius(500, 30, 30))
.ToList();
Information
You can read more about spatial search in a dedicated querying article available here.
Remarks
Warning
From RavenDB 2.0 the distance by default is measured in kilometers in contrast to the miles used in previous versions.