22 Replies
      Latest reply on Jul 1, 2019 6:27 AM by Shinren Pan
      Ivan Vassilev Level 1 Level 1 (5 points)

        Is there an issue with distanceToLocation:fromLocation CloudKit predicate?

         

        I am trying to fetch some results from cloud db using the following code:

         

        CGFloat radius = 1000.00; // in meters
        CLLocation *currentLocation =
        [[CLLocation alloc] initWithLatitude:42.713834 longitude:23.264086];
           
        CKLocationSortDescriptor *sortDescriptor =
        [[CKLocationSortDescriptor alloc] initWithKey:@"location" relativeLocation:currentLocation];
           
        [NSPredicate predicateWithFormat:@"distanceToLocation:fromLocation:(location, %@) < %f",
        currentLocation, radius];
        


        I am expecting four records but result contains seven. Three of them exceed my predicate rule.

         

        Result:

        lat:42.713402 long:23.275190 distance:910.919712

        lat:42.710781 long:23.271160 distance:671.444867

        lat:42.734921 long:23.247850 distance:2693.570884

        lat:42.724411 long:23.281340 distance:1837.812711

        lat:42.721882 long:23.270849 distance:1051.719189

        lat:42.714920 long:23.263790 distance:123.055994

        lat:42.715111 long:23.261801 distance:234.863830

         

        Distance in the result above was measured for every record with:

        [[record objectForKey:@"location"] distanceFromLocation:currentLocation];
        

         

        Sort descriptor also does not work properly.

        By documentation sort descriptor should sort results in ascending order - but it is not true

        https://developer.apple.com/library/prerelease/ios/documentation/CloudKit/Reference/CKLocationSortDescriptor_class/index.html#//apple_ref/occ/instm/CKLocationSortDescriptor/initWithKey:relativeLocation:

        • Re: CloudKit - distanceToLocation:fromLocation
          westerlund Level 2 Level 2 (75 points)

          I think the parameter for the radius needs to be a `NSNumber`

           

          This is working in my project:

          // setup predicate
          let distance = NSNumber(integer: 100)
          let location = ...
          let predicate = NSPredicate(format: "distanceToLocation:fromLocation:(location, %@) < %@", location, distance)
          
          • Re: CloudKit - distanceToLocation:fromLocation
            dsm Level 1 Level 1 (0 points)

            I too had a problem, and then I looked at the CloudKitAtlas sample and noticed an interesting variable name in AAPLCloudManager.m:

             

            CGFloat radiusInKilometers = 5;
                NSPredicate *predicate = [NSPredicate predicateWithFormat:@"distanceToLocation:fromLocation:(location, %@) < %f", location, radiusInKilometers];
            


            Convert to Kilometers, not Meters when using this function

             

            Also note that you cannot use <= for some reason.

             

            Hope this helps.

              • Re: CloudKit - distanceToLocation:fromLocation
                SpaceMan Level 1 Level 1 (10 points)

                Oh yeah?  This listing is in the CKQuery Class Reference.

                 

                To test whether two locations are near each other, create a predicate using the distanceToLocation:fromLocation: function as shown in Listing 7. Predicates that use this function must have the structure shown in the listing. In your code, replace the location variable with a field name from one of your records. (This data type for the field must be a CLLocation object.) Similarly, replace the fixedLoc and radius values with appropriate values from your app. The fixedLoc value is the geographic coordinate that marks the center of a circle with the specified radius. In this example, the predicate returns a match if the location in the record is within 10 kilometers of the specified latitude and longitude.

                 

                Listing 7Matching by distance from a location

                1. CLLocation* fixedLoc = [[CLLocation alloc] initWithLatitude:37.331913 longitude:-122.030210];
                2. CGFloat radius = 10000;
                3. NSPredicate predicate = [NSPredicate predicateWithFormat:@"distanceToLocation:fromLocation:(location, %@) < %f", fixedLoc, radius]];
              • Re: CloudKit - distanceToLocation:fromLocation
                PBK Level 7 Level 7 (3,265 points)

                and did you include:

                    query.sortDescriptors=[NSArray arrayWithObject:sortDescriptior];

                • Re: CloudKit - distanceToLocation:fromLocation
                  PBK Level 7 Level 7 (3,265 points)

                  Your code needs to change this line:

                      [NSPredicate predicateWithFormat:@"distanceToLocation:fromLocation:(location, %@) < %f", currentLocation, radius];

                  to:

                     NSPredicate *myPredicate=  [NSPredicate predicateWithFormat:@"distanceToLocation:fromLocation:(location, %@) < %f", currentLocation, radius];

                     CKQuery *query=[[CKQuery alloc] initWithRecordType:@"MyType" predicate:myPredicate];

                     query.sortDescriptors =[NSArray arrayWithObject:sortDescriptor];

                    • Re: CloudKit - distanceToLocation:fromLocation
                      PBK Level 7 Level 7 (3,265 points)

                      And, believe it or not, the location sorts from a CloudKit database get more 'accurate' with time - honestly, mine were giving ordering errors last night and tonight they work perfectly.  Also, it seems to be filtering based on meters not kilometers.

                      • Re: CloudKit - distanceToLocation:fromLocation
                        Ivan Vassilev Level 1 Level 1 (5 points)

                        I have these lines.

                        The code above was just descriptive.

                         

                        NSPredicate *predicate = [NSPredicate predicateWithFormat:@"distanceToLocation:fromLocation:(location, %@) < %f",  currentLocation, radius];
                        CKQuery *query = [[CKQuery alloc] initWithRecordType:@"MyRecord" predicate:predicate];
                        
                        
                        CKLocationSortDescriptor *sortDescriptor =  [[CKLocationSortDescriptor alloc] initWithKey:@"location" relativeLocation:currentLocation];
                        [query setSortDescriptors:[NSArray arrayWithObject:sortDescriptor]];
                        

                         

                        Here is my last test.

                        The radius is 5000m passed to the predicate as CGFloat. Only 12 record match my rule. Lines with distance > 5000m are extra data that I don't need.

                        I filter these lines using distanceFromLocation, but this is extra work.

                         

                        lat:42.713402 long:23.275190 distance:910.919712
                        lat:42.710781 long:23.271160 distance:671.444867
                        lat:42.734921 long:23.247850 distance:2693.570884
                        lat:42.724411 long:23.281340 distance:1837.812711
                        lat:42.721882 long:23.270849 distance:1051.719189
                        lat:42.714920 long:23.263790 distance:123.055994
                        lat:42.715111 long:23.261801 distance:234.863830
                        lat:42.712818 long:23.239189 distance:2042.611038
                        lat:42.695461 long:23.295670 distance:3295.681649
                        lat:42.699039 long:23.315130 distance:4493.304914
                        lat:42.714500 long:23.326611 distance:5122.417117
                        lat:42.698200 long:23.329470 distance:5631.289302
                        lat:42.704750 long:23.328791 distance:5396.354962
                        lat:42.697800 long:23.325090 distance:5305.933690
                        lat:42.723591 long:23.303680 distance:3419.570719
                        lat:42.716091 long:23.296970 distance:2705.267491
                        lat:42.750198 long:23.225439 distance:5131.823377
                        lat:42.688309 long:23.319748 distance:5370.225749
                        lat:42.689999 long:23.331470 distance:6123.168271
                        lat:42.687983 long:23.320285 distance:5426.818538
                        lat:42.688380 long:23.319387 distance:5340.977640
                        lat:42.652191 long:23.280630 distance:6980.647302
                        lat:42.659561 long:23.264830 distance:6029.295483
                        lat:42.658180 long:23.270250 distance:6202.995351
                        lat:42.650871 long:23.289980 distance:7309.198664
                        lat:42.652679 long:23.265970 distance:6795.204816
                        lat:42.759720 long:23.267130 distance:5103.439966
                        lat:42.651409 long:23.220289 distance:7808.524973
                        lat:42.650742 long:23.217840 distance:7967.967831
                        

                         

                        As you can see, the results are somehow ordered, but not ideal.

                          • Re: CloudKit - distanceToLocation:fromLocation
                            PBK Level 7 Level 7 (3,265 points)

                            Yes, I got the same results.  I ended up setting a radius so that the number that comes back is reasonable and then doing this simple sort:

                                             CLLocation *home=[[CLLocation alloc] initWithLatitude:homeLatitude longitude:homeLongitude];    

                                             NSArray *theSortedReuslts=[theResults sortedArrayUsingComparator:^(id obj1, id obj2){

                                                 NSDictionary *object1=obj1;

                                                 NSDictionary *object2=obj2;

                                                 if([[object1 objectForKey:@"HomeLocation"] distanceFromLocation:home1]>

                                                    [[object2 objectForKey:@"HomeLocation"] distanceFromLocation:home1]){

                                                        return (NSComparisonResult)NSOrderedDescending;

                                                 }else{

                                                     return NSOrderedAscending;

                                                 }

                                             }];

                              • Re: CloudKit - distanceToLocation:fromLocation
                                SpaceMan Level 1 Level 1 (10 points)

                                You shouldn't have to do the extra work and adjust the radius.  It needs to be fixed on Apple's end.

                                  • Re: CloudKit - distanceToLocation:fromLocation
                                    sobri Level 1 Level 1 (0 points)

                                    Have you filed a bug report for it? It's probably worth doing so. The more reports, the more likely someone is to spend time on improving it.

                                     

                                    My report for the broken sort descriptor bug is 25822198. I filed it four months ago and it's still open. If more people are filing similar reports, we might see more action on it.

                                     

                                    For the distance limit bug, I'm inclined to believe it's an optimisation thing, that it's optimising for a result set size rather than a closeness to the requested distance limit. That meaning that if the database has reduced the possible results for your query to a small enough set (even if many in that set are outside of your given limit) it will return the results immediately rather than spending time on further refining the result set. So the database is prefering fast results and small result set size rather than strict accuracy to the requested limit.

                                     

                                    If I'm right about that optimisation, then it means that as your database grows larger you should start to see less results that are outside of your limit. Though I haven't spent any time testing that theory yet.

                                  • Re: CloudKit - distanceToLocation:fromLocation
                                    ollieg8 Level 1 Level 1 (0 points)

                                    Just spent hours trying to CKLocationSortDescriptor to work now finding this I am not sure if I am happy that it isn't me doing something wrong or sad that it doesn't work!!!

                                     

                                    Do you have any idea for a workaround to order by distance from user using Cloudkit and in swift.

                                     

                                    I am new to iOS dev so any help would be amazing!

                                     

                                    Thanks

                              • Re: CloudKit - distanceToLocation:fromLocation
                                sobri Level 1 Level 1 (0 points)

                                Anyone had any luck with this?

                                 

                                I'm still having to post sort the results becaus the sort descriptor doesn't work, even after months of development (so the indexes should be mature).

                                 

                                The results are also becoming increasingly slow. There's less than a thousand rows in the table, but I'm now starting to get multi second waits for results, with it sometimes taking upwards of 10 seconds. Something is wrong.

                                 

                                Obviously I do have the location field indexed both for querying and sorting.

                                  • Re: CloudKit - distanceToLocation:fromLocation
                                    SpaceMan Level 1 Level 1 (10 points)

                                    There is a question as to whether the radius is in meters or kilometers.  Also, the thing doesn't work as it gives results for records outside of the search radius.  Me thinks that it's broken.

                                      • Re: CloudKit - distanceToLocation:fromLocation
                                        sobri Level 1 Level 1 (0 points)

                                        Yes I agree that it is broken.

                                         

                                        The docs are clear that the radius is in metres. But it appears that the server is taking the given value only as a hint, and is internally using a wider radius than the given. I suspect this could be an index optimisation, in that it the database doesn't have distances indexed down to tens or hundreds of metres, and instead is only granular to 1000s of metres. Thus you will most likely receive results that are outside of your given metres limit.

                                         

                                        Also the CKLocationSortDescriptor doesn't work at all. Or at least it didn't last time I checked, some months ago. My code and database have been live and active for many months, but the sorting didn't appear to have improved (or indeed be doing anything at all) when I last checked.

                                         

                                        So yeah, that's my conclusion. The CKLocationSortDescriptor doesn't work at all, and while the distanceToLocation predicate is meant to take metres (as the docs state), it is only capable of granularity at the kilometres level. So if you request a 500 metre radius you're going to receive results that are potentially several kilometres outside of that radius.

                                         

                                        The upshot is that you need to test the distance of each result and discard the undesired results, and you need to apply your own sort to the results (though I've personally left the CKLocationSortDescriptor code in, in the vain hope that someday it'll start working).

                                        • Re: CloudKit - distanceToLocation:fromLocation
                                          SpaceMan Level 1 Level 1 (10 points)

                                          I submitted a Bug Report on this.