Błażej
Błażej

Reputation: 3635

Realm filter CLLocation

I have warpper class for CLLoaction

Wrapper

class Location: Object {
    dynamic var long: Double = 0
    dynamic var lat: Double = 0
}

I have to filter stored Location for those which are in 1km radius based on my current location. I thought NSPredicate with block would do the job, but realm doesn't support it. So my question is how other way I can achieve it? Of course I could do something like this:

let locations = realm.objects(Location)
var locationsInRadius = [Location]()
for l in locations {
    let location = CLLocation(latitude: l.lat, longitude: l.long)
    if (location.distanceFromLocation(currentLocation) < radius){
        locationsInRadius.append(l)
    }
}

But it feels wrong according to whole realm concept of filters.

Upvotes: 1

Views: 2216

Answers (2)

antoine
antoine

Reputation: 833

For some reason, using a single predicate (lat BETWEEN {%f, %f} AND lon BETWEEN {%f, %f}) doesn't work with the current version of Realm. I'm using this nice lib: https://github.com/mhergon/RealmGeoQueries

This is how the predicate is built inside and it works fine: https://github.com/mhergon/RealmGeoQueries/blob/master/GeoQueries.swift:

 let topLeftPredicate = NSPredicate(format: "%K <= %f AND %K >= %f", latitudeKey, box.topLeft.latitude, longitudeKey, box.topLeft.longitude)
 let bottomRightPredicate = NSPredicate(format: "%K >= %f AND %K <= %f", latitudeKey, box.bottomRight.latitude, longitudeKey, box.bottomRight.longitude)
 let compoundPredicate = NSCompoundPredicate(andPredicateWithSubpredicates: [topLeftPredicate, bottomRightPredicate])

Basically, here is how to use it:

let results = try! Realm()
    .findNearby(YourModelClass.self, origin: location.coordinate, radius: 500, sortAscending: nil)

You can also change the default "lat" and "lng" keys by passing 2 extra parameters with your own naming (latitudeKey and longitudeKey).

Thanks to https://github.com/mhergon for providing this lib.

Upvotes: 2

uɥƃnɐʌuop
uɥƃnɐʌuop

Reputation: 15163

You can't search for objects by distance, but you can search by using a bounding box. Simply add latitude and longitude fields to your object, then:

  1. Get current location
  2. Create a bounding box around that location
  3. Filter your objects by bounding box

In code, that could like this:

    // 0. This example uses MapKit to calculate the bounding box
    import MapKit

    // 1. Plenty of answers for this one...
    let currentLocation = CLLocationCoordinate2DMake(37.7749295, -122.4194155)

    // 2. Create the bounding box with, 1km radius
    let region = MKCoordinateRegionMakeWithDistance(currentLocation, 1000, 1000)
    let northWestCorner = CLLocationCoordinate2DMake(
        currentLocation.latitude + (region.span.latitudeDelta),
        currentLocation.longitude - (region.span.longitudeDelta)
    )
    let southEastCorner = CLLocationCoordinate2DMake(
        currentLocation.latitude - (region.span.latitudeDelta),
        currentLocation.longitude + (region.span.longitudeDelta)
    )

    // 3. Filter your objects
    let predicate = NSPredicate(format: "lat BETWEEN {%f, %f} AND lon BETWEEN {%f, %f}",
        northWestCorner.latitude,
        southEastCorner.latitude,
        northWestCorner.longitude,
        southEastCorner.longitude
    )

    let nearbyLocations = realm.objects(MyLocation).filter(predicate)

Note that you can still store your CLLocation object for other purposes, but you won't need it for the search.

Also note that as this searches a box rather than what you wanted, a circle with a 1km radius, this can return results of greater than 1km. If that's not ok, you would need to reduce the radius or make a fancier predicate.

Upvotes: 4

Related Questions