Couchbase Lite 1.4 - CBLGeoQuery based on boundingBox


I wanted to query from CB using the startKey and endKey property so that I can get some specific documents from CBdatabase and which can be annotated on the map. These are the steps I’m following,

Step 1:
Emit latitude and longitude from CBLView.

// Open Notifications View from the visible area

            visibleAreaNotificationsView = database.viewNamed(CouchBaseConstants.visibleAreaNotifications)
            visibleAreaNotificationsView.setMapBlock({ (doc, emit) in
                guard  let location = payload["Location"] as? [String: Any],
                    let latitudeString = location["Latitude"] as? String,
                    let longitudeString = location["Longitude"] as? String,
                    let latitudeDouble = Double(latitudeString.trimmingCharacters(in: .whitespaces)),
                    let longitudeDouble = Double(longitudeString.trimmingCharacters(in: .whitespaces))
                    else { return }
                emit([latitudeDouble, longitudeDouble], nil)
            }, version: “0.1.3”)

Step 2:
Figure out maps visible area xMin, yMin and xMax, yMax and specifying the query startKey and endKey so that I hope it will filter the DB result within the range of startKey and endKey.

guard let query = database.existingViewNamed(CouchBaseConstants.visibleAreaNotifications)?.createQuery() else { return }
query.prefetch = true
query.startKey = [xMin.latitude, yMin.longitude]
query.endKey = [xMax.longitude, yMax.longitude]
query.runAsync({ (enumeratorResult, error) in

But unfortunately filtering is not happening. Here, the expectation is startKey will be the xMin, yMin values and endKey is the xMax, yMax values. Based on this couchbase query will filter the documents within the range of startKey and endKey.

Current observations,

  1. enumeratorResult is empty
  2. When I put a comment on the query.startKey and query.endKey, I’m getting the result. But enumeratorResult.nextRow() returns key values as [0,0]. CBLQueryRow[key=[0,0]; value=(null); id=ec-114242454]. I doubt is it because of the duplicate latitude and longitude value? If so, any solution to resolve this and making the query filter work?

Another approach I tried is using bounding box, instead of startKey and endKey I can give
query.boundingBox = CBLGeoRect(min: CBLGeoPoint(x: xMin, y: yMin), max: CBLGeoPoint(x: xMax, y: yMax))

But what should I emit in the View? I tried to emit using
emit(CBLGeoPoint(x: latitudeDouble, y: longitudeDouble), nil)
But this is throwing an error.

Exception caught in map block of view visibleEcNotifications, on doc {
“_id” = “ec-114315401”;
“_local_seq” = 109;
“_rev” = “1-15dba865c944413b0f72ed596fbcf5e7”;
metadata = {
appVersion = nodejs;
backendState = submitted;
lastEditedTime = “2018-11-05T20:51:43.561Z”;
payload = {
AdditionalData = {
IssuedDate = 00000000;
IssuedTo = “”;
“__metadata” = {
type = “ZAI_NOTIFICATION_SRV.AdditionalData”;
Invalid type in JSON write (_SwiftValue)
3 Foundation 0x0000000110b78e50 _writeJSONValue + 706
4 Foundation 0x0000000110b7c7bf ___writeJSONArray_block_invoke + 130
5 CoreFoundation 0x00000001119d6a6a -[__NSSingleObjectArrayI enumerateObjectsWithOptions:usingBlock:] + 58
6 Foundation 0x0000000110b7bf54 _writeJSONArray + 300
7 Foundation 0x0000000110b78b3a -[_NSJSONWriter dataWithRootObject:options:error:] + 124
8 Foundation 0x0000000110b7ae29 +[NSJSONSerialization dataWithJSONObject:options:error:] + 337
9 ConstructQA 0x0000000105d9f604 +[CBLJSON dataWithJSONObject:options:error:] + 335
10 ConstructQA 0x0000000105dcda4a toJSONData + 75
11 ConstructQA 0x0000000105dcd57b -[CBL_SQLiteViewStorage _emitKey:value:valueIsDoc:forSequence:] + 411
12 ConstructQA 0x0000000105dcd170 __39-[CBL_SQLiteViewStorage updateIndexes:]block_invoke.161 + 97
13 ConstructQA 0x0000000105bc1b33 $SyXlyXlSgIeyByy_ypypSgIegnn_TR + 371
14 ConstructQA 0x0000000105bd5d5d $SyXlyXlSgIeyByy_ypypSgIegnn_TRTA + 13
15 ConstructQA 0x0000000105bc385b $S11ConstructQA17PGEConstructStackC5lanID8division3lobACSS_S2StKcfcySDySSypG_yyp_ypSgtctcfU0
+ 7419
16 ConstructQA 0x0000000105bc1987 $SSDySSypGypypSgIegnn_Ieggg_So12NSDictionaryCyXlyXlSgIeyByy_IeyByy_TR + 183
17 ConstructQA 0x0000000105dcc935 __39-[CBL_SQLiteViewStorage updateIndexes:]_block_invoke + 6927

Please enlighten me on these issues and help to resolve it.

This is a well-known limitation of regular database indexes — they can’t do the kind of query you want to do, i.e. inequalities on multiple keys. That requires a more specialized type of index, in this case a geospatial index such as an R-tree. The iOS version of CBL 1.x does support geo-queries, so check out that API.

(The reason a regular index doesn’t work is that when you emit a compound key like [x,y], the x is the primary key and y is secondary. As an analogy, if you want to search an alphabetic index for words whose first letter is M-N and second letter is O-U, you can’t simply take all the words from MO through NU.)

Thanks for the reply, Jens.

So from your reply what I understand is, the emit key always should be a primary key. And when you mentioned about geo-queries, I did search on this and found a category of CBLQuery like CBLGeoQuery which provides a property named boundingBox of type CBLGeoRect. By guessing this will support the geo-query, I tried to implement the same. in the documents its given as,

/** CBLQuery interface for geo-queries.
To use this, the view’s map function must have emitted geometries (points, rects, etc.)
as keys using the functions CBLGeoPointKey(), CBLGeoRectKey(), or CBLGeoJSONKey(). */

/** The geometric bounding box to search. Setting this property causes the query to
search geometries rather than keys. */
@property CBLGeoRect boundingBox;

So based on the document, once the query is created I tried to emit emit(CBLGeoPointKey(latitudeDouble, longitudeDouble), nil) and applied the filter as query.boundingBox = CBLGeoRect(min: CBLGeoPoint(x: extent.xMin, y: extent.yMin), max: CBLGeoPoint(x: extent.xMax, y: extent.yMax)) . But the CBLQueryEnumerator result values were nil.

Is there any problem in my approach or is there any other way to do the geo-queries?

Your view is emitting the coordinates in the wrong order. latitude is y, longitude is x.

I tried it by changing the order emit(CBLGeoPointKey(longitudeDouble, latitudeDouble), nil), still it CBLQueryEnumerator result is nil.

(lldb) po enumeratorResult.nextObject()
▿ Optional<CBLQueryRow>
- some : CBLQueryRow[key=null; value=(null); id=ec-115242009]
(lldb) po enumeratorResult.nextObject()
▿ Optional<CBLQueryRow>
- some : CBLQueryRow[key=null; value=(null); id=ec-115241738]
(lldb) po enumeratorResult.nextObject()
▿ Optional<CBLQueryRow>
- some : CBLQueryRow[key=null; value=(null); id=ec-115160545]

If I’m not applying query.boundingBox , I can see the document count for that particular area of the map. Is there any problem with boundingBox data? Its CBLGeoRect and we are emitting CBLGeoPointKey.

You’re getting results; the transcript shows that nextObject is returning a CBLQueryRow each time. It’s just that the key is null. I’m not sure why that is. Does CBLQueryRow have a category method to get the point or bounding box? Otherwise you can get the document and look up the coords from that.

The transcript shows the result after commenting the query.boundingBox line. I put this to show you whats emitting as a result of emit(CBLGeoPointKey(longitudeDouble, latitudeDouble), nil). The CBLQueryEnumerator result is expected to have some key details. But now in the transcript, you can see this key as null.

As per doc,

/** A result row from a CouchbaseLite geo-query.
    A CBLQuery with its .boundingBox property set will produce CBLGeoQueryRows. */
@interface CBLGeoQueryRow : CBLQueryRow

If we keep adding the query.boundingBox , the result row from a CouchbaseLite geo-query with its .boundingBox property is expected to produce CBLGeoQueryRows. Here the CBLGeoQueryRows(CBLQueryEnumerator) result is empty. Does it imply an issue with filtering logic using boundingBox based on my implementation?

I don’t know what’s going on, sorry. I wrote this code, but as I said it’s been many years.

One thing that might help is to turn on verbose query logging. In the Xcode scheme editor add command-line args -Log YES -LogQueryVerbose YES.

That’s okay, Jens. If we could resolve this issue, we could have improved our app performance. Anyway, I will try to add logs. Thanks for your time and help.

This is very interesting. I’m going to implement a GEO search in my app (.NET) in the near future - and I just realised that this could cause me more pain than I anticipated…

So if you find a solution I would appreciate if you posted it here :slight_smile:

@priya.rajagopal — some customer demand for geo-queries.