Skip to content

Geo Querying

Jens Alfke edited this page Sep 23, 2013 · 9 revisions

Couchbase Lite has some preliminary support for geometric/geographical querying, which is very useful for working with real-world locations.

At its simplest, geo-querying involves a data set of 2D points and/or rectangles, and querying to find which of those intersect or lie within a given rectangle. That's all Couchbase Lite supports for now. Once you have that, you can build more complex features on top of it, like polygonal shapes or computing distances.

As of this writing (September 22, 2013) geo-querying is still experimental. You'll need to check out the [inaccurately-named] fulltext branch to use it.

Indexing geometries

Any view can index points and rectangles instead of the regular JSON keys. To do so, you make its map block emit a special key, produced by one of three functions:

  • CBLGeoPointKey(double x, double y) returns a key representing a point.
  • CBLGeoRectKey(double x0, double y0, double x1, double y1) returns a key representing a rectangle, whose range of x values is [x0, x1) and ditto for y.
  • CBLGeoJSONKey(NSDictionary*) takes a parsed GeoJSON object and makes it a key. The object must currently represent a point or a polygon. (In the latter case only its bounding box is indexed.)

Note: Don't emit both geo keys and regular JSON keys in the same view! Use separate views instead.

Example

Using individual x and y properties: [[db viewNamed: @"places"] setMapBlock: MAPBLOCK({ if ([doc[@"type"] isEqualToString: @"place"]) { emit(CBLGeoPointKey([doc[@"lat"] doubleValue], [doc[@"lon"] doubleValue]), doc[@"name"]); } }) reduceBlock: NULL version: @"1"];

Using a GeoJSON property: [[db viewNamed: @"places"] setMapBlock: MAPBLOCK({ if ([doc[@"type"] isEqualToString: @"place"]) { emit(CBLGeoJSONKey(doc[@"geoJSON"]), doc[@"name"]); }) reduceBlock: NULL version: @"1"];

Querying Your Geos

To query a geo view, set the CBLQuery's boundingBox property. Its value is a CBLGeoRect, a simple struct rather like a CGRect or NSRect except that it stores min and max coords rather than a size.

Example:

CBLQuery* query = [[db viewNamed: @"places"] query];
query.boundingBox = (CBLGeoRect){{10, 10}, {20, 20}};

Getting The Results

The returned CBLQueryRows will have two extra properties set:

  • boundingBox is the emitted bounding box as a native CBLGeoRect. (If the map() call's key was a point instead of a rect, the result will be an empty rect whose min and max coords are equal.)
  • geometry is the emitted shape in GeoJSON form, either a rectangle or a point.

Example:

for (CBLQueryRow* row in [query rows]) {
    CBLGeoRect bbox = row.boundingBox;
    NSLog(@"Name: %@, bbox: (%g, %g)--(%g, %g)",
          row.value, bbox.min.x, bbox.min.y, bbox.max.x, bbox.max.y);
}

Issues

Performance

The query implementation is, to be blunt, a hack -- it's just storing coordinates as SQL columns and querying them. This is not going to be very efficient compared to a real geo-querying database like an R-tree. It's probably good enough for small data sets, though.

Limited GeoJSON support

The only GeoJSON types the indexer understands are points and polygons. There are a handful of others it should be able to parse too, like MultiPoint, LineString, MultiPolygon...

Input shape isn't available from query

If you index a GeoJSON shape, the indexer doesn't store it fully, just its bounding box, so the CBLQueryRow can only tell you the bounding box. (Of course you can fetch the document and re-derive the shape from that.)

Clone this wiki locally