-
Notifications
You must be signed in to change notification settings - Fork 301
Geo Querying
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.
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.
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"];
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.
CBLQuery* query = [[db viewNamed: @"places"] query];
query.boundingBox = (CBLGeoRect){{10, 10}, {20, 20}};
The returned CBLQueryRows will have two extra properties set:
-
boundingBoxis the emitted bounding box as a nativeCBLGeoRect. (If themap()call's key was a point instead of a rect, the result will be an empty rect whose min and max coords are equal.) -
geometryis the emitted shape in GeoJSON form, either a rectangle or a point.
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);
}
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.
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...
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.)