32

An iOS application uses the geofencing for notifying the user about predefined nearby locations. The application is allowed to miss some location (the user is not getting a notification about a nearby location), but it is desirable to keep the missing rate low.

One way to implement this would be to start monitoring for significant change locations with startMonitoringSignificantLocationChanges and each time the "location change" event is fired, look for locations within, let say, 500m radius of the reported location.

What worries me is the requirement to perform the query for the nearby regions each time the significant location change occurs and it impact on the battery.

The other way to do it would be to register the locations with startMonitoringForRegion but Apple has put a (reasonable) limitation on the number of simultaneously tracked regions which is 20 and we have significantly more than 20 locations. So some sort of dynamic updating of the tracked regions is required but I am still unsure what is the best way to do it.

Any ideas on how can it be done so that it keeps the battery consumption low but also has the low missing rate for locations?

silentser
  • 1,991
  • 2
  • 21
  • 27

5 Answers5

24

Since there was not much activity on the question I will describe how we are currently solving this problem.

We tied the reloading of the new regions to significant location change (SLC) events. When an SLC takes place, we check for 20 neighbouring regions that should be "geofenced". To find the 20 closest regions we are simply approximating 1'' of the latitude and longitude according to the following formulae:

Latitude: 1 deg = 110.54 km

Longitude: 1 deg = 111.320 * cos(latitude) km

and just check the bounding square of the current position of the device for the centers of the monitored regions (see: Simple calculations for working with lat/lon + km distance?)

So, for example, if (10N,10E) is the current location of the device we start with the bounding square with vertices at (10-1',10-1'), (X-10',10+1'), (10+1',10+1'), (10+1',10-1') (at latitude (10N,10E) one latitude/longitude minute approximates 1,85 km).

If there are 20 (or almost 20) - we register them for the geofencing and wait for the next SCL. If less/more, just increase/decrease the size of the bounding rectangle and repeat the search.

You can tweak this search algorithm for a better performance, but the one described here will already do the job.

Community
  • 1
  • 1
silentser
  • 1,991
  • 2
  • 21
  • 27
  • 1
    I am trying to do the same but not working for me when the application is closed it is just working when the application is in background. I really appreciate posting some sample code thank u. – HEH Aug 20 '13 at 06:13
  • 2
    Since you are already using significant location change events, why don't you skip iOS geofencing altogether? You could just check whether you're inside one of your geofences yourself, instead of finding the nearest geofences. – Dag Høidahl Sep 25 '13 at 08:51
  • 2
    SLC events don't trigger often enough to use as a full replacement for geofences. I will add we had the same issue but solved it a different way. We use the location object in the SLC and calculate the nearest 20 to those coordinates using built in location manager methods. Throw out the old 20 and add the new 20 closest. – Bill Burgess Jan 06 '16 at 21:31
8

You could reserve a location for a "meta-geofence" encompassing all the currently monitored locations. When the user leaves this geofence, the app will be notified. Then the app can update itself and stop tracking the farthest areas and start tracking new areas in the vicinity.

Dag Høidahl
  • 6,759
  • 7
  • 46
  • 62
  • 2
    I've written a solution which follows the above method here: http://stackoverflow.com/questions/22297995/add-more-than-20-regions-to-geofencing-ios/24080059#24080059 – uofc Jun 06 '14 at 13:11
  • 1
    @uofc where is your solution in that page ? – JAHelia Oct 23 '16 at 06:27
  • If phone will leave that meta-geofence in some bad state (e.g. turned off) you won't get notified and will die until next app launch. – DanSkeel Feb 07 '17 at 22:37
6

I thought I would add another option for using more than 20 Geofences in your app. This way has been working well in our app for a long time now and uses CLLocation methods that are built-in.

- (void)locationManager:(CLLocationManager *)manager didUpdateLocations:(NSArray *)locations {
    if (locations.count > 0) {
        CLLocation *location = locations[0];

        NSMutableArray *sortedFences = [[NSMutableArray alloc] init];

        // add distance to each fence to be sorted
        for (GeofenceObject *geofence in enabledFences) {
            // create a CLLocation object from my custom object
            CLLocationCoordinate2D coordinate = CLLocationCoordinate2DMake(geofence.latitude, geofence.longitude);
            CLLocation *fenceLocation = [[CLLocation alloc] initWithLatitude:coordinate.latitude longitude:coordinate.longitude];
            // calculate distance from current location
            CLLocationDistance distance = [location distanceFromLocation:fenceLocation];
            // save distance so we can filter array later
            geofence.distance = distance;
            [sortedFences addObject:geofence];
        }

        // sort our array of geofences by distance and add we can add the first 20

        NSSortDescriptor *sortByName = [NSSortDescriptor sortDescriptorWithKey:@"distance" ascending:YES];
        NSArray *sortDescriptors = [NSArray arrayWithObject:sortByName];
        NSArray *sortedArray = [sortedFences sortedArrayUsingDescriptors:sortDescriptors];

       // should only use array of 20, but I was using hardcoded count to exit

        for (GeofenceObject *geofence in sortedArray) {
            CLLocationCoordinate2D coordinate = CLLocationCoordinate2DMake(geofence.latitude, geofence.longitude);
            CLLocationDistance radius = geofence.radius;
            NSString *ident = geofence.geofenceId;

            CLCircularRegion *fenceRegion = [[CLCircularRegion alloc] initWithCenter:coordinate radius:radius identifier:ident];
            fenceRegion.notifyOnEntry = geofence.entry;
            fenceRegion.notifyOnExit = geofence.exit;
            [locationController.locationManager startMonitoringForRegion:fenceRegion];
        }
    }
}

Hopefully this will help someone or steer them on the right path.

Bill Burgess
  • 13,768
  • 6
  • 47
  • 85
  • 2
    Hi Bill I just was reading your solution. You are only calling "startMonitoringForRegion:" and never "stopMonitoringForRegion:". What happens if 20 regions are already registered but in a next call I try to register a new region? Wouldn't it make sense to store all registered regions and before calling "startMonitoringForRegion:" for the new 20 closest coordinates to stop all already registered before? – Robert Weindl Jan 15 '16 at 12:56
  • 1
    I only call startMonitoring when I have more than 20 and when my app goes to the background. When my app returns (-becomeActive) I turn it off. I am constantly turning Geofences off and turning them back on. When I add a Geofence, I'm not directly adding it to the point where it can fail. I basically set it to enabled, then tell my manager to add the 20 closest. YMMV – Bill Burgess Jan 15 '16 at 17:06
  • Could you please answer in Swift – SaiPavanParanam Nov 08 '16 at 13:02
  • Everything used in Obj-C should be available in Swift. The conversion shouldn't be too difficult. – Bill Burgess Nov 08 '16 at 13:48
  • @RobertWeindl Using `locationManager.monitoredRegions` you could just get the regions being currently monitored and remove them all. – Honey Jul 29 '17 at 16:57
3

If you are concerned about performing the proximity check on each significant location change, you could use a spatial indexing/search method like R-trees or R*-tree to reduce the number of comparisons needed for each location change, as those search algorithms will filter out (possibly large) spatially irrelevant regions. That should reduce the time/battery power needed to perform the proximity checks.

Martin Thorsen Ranang
  • 2,241
  • 1
  • 30
  • 43
2

I know this post is old, but for those looking to do something similar, Skyhook offers the ability to geofence an infinite number of venues.

From their marketing: Skyhook’s Context Accelerator enables app developers and advertisers to instantly deploy Infinite Geofences to any brand chain (such as CVS) or venue category (such as convenience stores) through a simple web interface. Using the same patented technology from Skyhook’s first-party location network, the Context Accelerator SDK manages those active geofences on-device, regardless of OS limitations allowing for infinite geofencing.

ChrisD
  • 87
  • 8