Indexing Spatial Data



Create index with spatial field

  • Use CreateSpatialField to index spatial data in a static-index.

  • You can then retrieve documents based on geographical criteria when making a spatial query on this index-field.

  • A spatial index can also be defined from Studio.

Exmaple:

// Define an index with a spatial field
public class Events_ByNameAndCoordinates : AbstractIndexCreationTask<Event>
{
    public Events_ByNameAndCoordinates()
    {
        Map = events => from e in events
            select new
            {
                Name = e.Name,
                // Call 'CreateSpatialField' to create a spatial index-field
                // Field 'Coordinates' will be composed of lat & lng supplied from the document
                Coordinates = CreateSpatialField(e.Latitude, e.Longitude)
                
                // Documents can be retrieved
                // by making a spatial query on the 'Coordinates' index-field
            };
    }
}

public class Event
{
    public string Id { get; set; }
    public string Name { get; set; }
    public double Latitude { get; set; }
    public double Longitude { get; set; }
}
// Define an index with a spatial field
public class EventsWithWKT_ByNameAndWKT : AbstractIndexCreationTask<EventWithWKT>
{
    public EventsWithWKT_ByNameAndWKT()
    {
        Map = events => from e in events
            select new
            {
                Name = e.Name,
                // Call 'CreateSpatialField' to create a spatial index-field
                // Field 'WKT' will be composed of the WKT string supplied from the document
                WKT = CreateSpatialField(e.WKT)

                // Documents can be retrieved
                // by making a spatial query on the 'WKT' index-field
            };
    }
}

public class EventWithWKT
{
    public string Id { get; set; }
    public string Name { get; set; }
    public string WKT { get; set; }
}
public class Events_ByNameAndCoordinates_JS : AbstractJavaScriptIndexCreationTask
{
    public Events_ByNameAndCoordinates_JS()
    {
        Maps = new HashSet<string>
        {
            @"map('events', function (e) {
                    return { 
                        Name: e.Name,
                        Coordinates: createSpatialField(e.Latitude, e.Longitude)
                    };
            })"
        };
    }
}

Syntax:

object CreateSpatialField(double? lat, double? lng); // Latitude/Longitude coordinates
object CreateSpatialField(string shapeWkt);          // Shape in WKT string format

Customize coordinate system and strategy

  • For each spatial index-field, you can specify the coordinate system and strategy to be used
    during indexing and when processing the data at query time.

  • RavenDB supports both the Geography and Cartesian systems with the following strategies:

    • Geography system:

      • BoundingBox
      • GeoHashPrefixTree
      • QuadPrefixTree
    • Cartesian system:

      • BoundingBox
      • QuadPrefixTree
  • By default, the GeoHashPrefixTree strategy is used with GeoHashLevel set to 9.
    Use the Spatial method from AbstractIndexCreationTask to modify this setting.

  • The performance cost of spatial indexing is directly related to the tree level chosen.
    Learn more about each strategy below.

  • Note: Modifying the strategy after the index has been created & deployed will trigger the re-indexing.

Exmaple:

public class Events_ByNameAndCoordinates_Custom : AbstractIndexCreationTask<Event>
{
    public Events_ByNameAndCoordinates_Custom()
    {
        Map = events => from e in events
                        select new
                        {
                            Name = e.Name,
                            // Define a spatial index-field
                            Coordinates = CreateSpatialField(e.Latitude, e.Longitude)
                        };

        // Set the spatial indexing strategy for the spatial field 'Coordinates' 
        Spatial("Coordinates", factory => factory.Cartesian.BoundingBoxIndex());
    }
}
public class Events_ByNameAndCoordinates_Custom_JS : AbstractJavaScriptIndexCreationTask
{
    public Events_ByNameAndCoordinates_Custom_JS()
    {
        // Define index fields
        Maps = new HashSet<string>
        {
            @"map('events', function (e) {
                    return { 
                        Name: e.Name,
                        Coordinates: createSpatialField(e.Latitude, e.Longitude)
                    };
            })"
        };
        
        // Customize index fields
        Fields = new Dictionary<string, IndexFieldOptions>
        {
            ["Coordinates"] = new IndexFieldOptions
            {
                Spatial = new SpatialOptions
                {
                    Type = SpatialFieldType.Cartesian,
                    Strategy = SpatialSearchStrategy.BoundingBox
                }
            }
        };
    }
}

Syntax:

public class SpatialOptionsFactory
{
    public GeographySpatialOptionsFactory Geography;
    public CartesianSpatialOptionsFactory Cartesian;
}

// Default is 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);

public class SpatialBounds
{
    public double MinX;
    public double MaxX;
    public double MinY;
    public double MaxY;
}

Spatial indexing strategies

BoundingBox strategy

  • The bounding box strategy is the simplest.
    Given a spatial shape, such as a point, circle, or polygon, the shape's bounding box is computed
    and the spatial coordinates (minX, minY, maxX, maxY) that enclose the shape are indexed.

  • When making a query,
    RavenDB translates the query criteria to the same bounding box system used for indexing.

  • Bounding box strategy is cheaper at indexing time and can produce quick queries,
    but that's at the expense of the level of accuracy you can get.

  • Read more about bounding box here.


GeoHashPrefixTree strategy

  • Geohash is a latitude/longitude representation system that describes Earth as a grid with 32 cells, assigning an alphanumeric character to each grid cell. Each grid cell is further divided into 32 smaller chunks, and each chunk has an alphanumeric character assigned as well, and so on.

  • 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 the geohash will decrease the precision level.

  • The maxTreeLevel determines the length of the geohash used for the indexing, which in turn affects accuracy.
    By default, it is set to 9, providing a resolution of approximately 2.5 meters.

  • More information about geohash uses, decoding algorithm, and limitations can be found here.

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


QuadPrefixTree strategy

  • The QuadTree represents Earth as a grid consisting of four cells (also known as buckets). Similar to GeoHash, each cell is assigned a letter, and is recursively divided into four more cells, creating a hierarchical structure.

  • By default, the precision level (maxTreeLevel) for QuadPrefixTree is 23.

  • More information about QuadTree can be found here.

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

Remarks

Distance is measured by default in kilometers.