-
Notifications
You must be signed in to change notification settings - Fork 298
Geo Querying
Couchbase Lite (beta 2 or later) has some basic 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 touch 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.
The implementation uses SQLite's R*tree extension, which provides fast searches even for large data sets.
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 query will find all indexed shapes whose bounding boxes are contained in, or intersect, the query's bounding box.
Note: This test will be accurate for points and rectangles, but not for polygons. If your map function emitted any polygons, you'll need to do some math to test whether they actually intersect your query's bounding box, and skip the ones that don't.
The returned CBLQueryRow
s will have no key
properties but will have two extra properties set:
-
boundingBox
is 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.) -
geometry
is the original emitted shape in GeoJSON form.
Note: The coordinates in the
boundingBox
may be slightly inaccurate due to floating-point roundoff error. This is because SQLite stores the coordinates as 32-bit floats. (It does guarantee that the roundoff pushes the edges of the box outwards, not inwards.) Thegeometry
does preserve the exact input coordinates.
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);
}
To perform a geo-query using the REST API, specify the bounding box by adding the query parameter ?bbox
to the view query's URL. The value of the parameter should be the four coordinate values separated with commas, in the order x0,y0,x1,y1
.
The rows in the response will not have a key
property but will have a geometry
property whose value is the GeoJSON shape that was indexed, and a bbox
property that's the bounding box of the shape in the form [x0, y0, x1, y1]
.
Note: As noted above, the coordinates in the
bbox
may have slight roundoff error.
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...
Only bounding boxes are intersected. That means there may be false positives with non-rectangular polygons. If this is important to you, you'll need to do your own math to verify actual intersection.
- You can't combine key-based and geo-queries in the same view. A view's
emit
calls should either emit regular keys or the special geo objects, not some of each. - For this reason, the key-based properties of
CBLQuery
have no effect in a geo-query:startKey
,endKey
,startKeyDocID
,endKeyDocID
,keys
. - Geo-queries don't support reducing. They don't call the reduce block, and the reduce-based properties have no effect:
mapOnly
,groupLevel
.