Tracking Multiple (20+) Locations with iOS Geofencing

Tracking multiple (20+) locations with iOS geofencing

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.

How to monitor more than 20 regions?

set currentLocation from your didUpdateLocations

var currentLocation : CLLocation?{
didSet{
evaluateClosestRegions()
}
}

var allRegions : [CLRegion] = [] // Fill all your regions

Now calculate and find the closest regions to your current location and only track those.

func evaluateClosestRegions() {

var allDistance : [Double] = []

//Calulate distance of each region's center to currentLocation
for region in allRegions{
let circularRegion = region as! CLCircularRegion
let distance = currentLocation!.distance(from: CLLocation(latitude: circularRegion.center.latitude, longitude: circularRegion.center.longitude))
allDistance.append(distance)
}
// a Array of Tuples
let distanceOfEachRegionToCurrentLocation = zip(allRegions, allDistance)

//sort and get 20 closest
let twentyNearbyRegions = distanceOfEachRegionToCurrentLocation
.sorted{ tuple1, tuple2 in return tuple1.1 < tuple2.1 }
.prefix(20)

// Remove all regions you were tracking before
for region in locationManager.monitoredRegions{
locationManager.stopMonitoring(for: region)
}

twentyNearbyRegions.forEach{
locationManager.startMonitoring(for: $0.0)
}

}

To avoid having the didSet called too many times, I suggest you set the distanceFilter appropriately (not too big so you would catch the region's callbacks too late and not too small so that you won't have redundant code running). Or as this answer suggests, just use startMonitoringSignificantLocationChanges to update your currentLocation

Geofencing Limits in iOS

1 - That means that your app can track 20 geofences. If you want to track 200 geofences your can track always the 20 most closest regions. You need create an strategy to update your geofences list. For example you can update your 20 closes geofences when enter/leaving your geofences. Or can you update them in background when executing a background fetch.

2 - Geofences is not the same that map regions. You can show as much regions as you want in a map (using for example mapkit).

3 - The geofence limitation is given by device. Each user can track up to 20 geofences.

iOS region monitoring, to monitor more than 20 locations

This is actually Apple's suggestion Core Location Programming Guide:

To work around this limit, consider registering only those regions in
the user’s immediate vicinity. As the user’s location changes, you can
remove regions that are now farther way and add regions coming up on
the user’s path.

However, it's not clear how much time you get when didEnterRegion: is called in the background, so it's not clear if you have time to make a server call if running the background. The "significant-change location service" information says:

If you leave the significant-change location service running and your
iOS app is subsequently suspended or terminated, the service
automatically wakes up your app when new location data arrives. At
wake-up time, the app is put into the background and you are given a
small amount of time (around 10 seconds) to manually restart location
services and process the location data.... Because your app is in the
background, it must do minimal work and avoid any tasks (such as
querying the network) that might prevent it from returning before the
allocated time expires
. If it does not, your app will be terminated.
If an iOS app needs more time to process the location data, it can
request more background execution time using the
beginBackgroundTaskWithName:expirationHandler: method of the
UIApplication class.

You could try the combination of region monitoring, making a server call in didEnterEter region and then calling beginBackgroundTaskWithName:expirationHandler: to make sure you have enough time. The combination of region monitoring + server calls + background processing is going to hit battery life, though.

EDIT: You could also create "mega regions" of a large area that contain many smaller regions. When the user enters those mega regions, set up and add all the smaller regions you are interested in, and when they exit, remove them.

iOS Geofence CLCircularRegion monitoring. locationManager:didExitRegion does not seem to work as expected

I don't think region monitoring will work well for such a small radius.

  • The best accuracy with the GPS chip and kCLLocationAccuracyBestForNavigation is often just 10 meters.
  • Apple says (in the Location & Maps PG) that the minimum distance for regions should be assumed to be 200m
  • I've heard that region monitoring is using WiFi to get it's position (which makes sense for power savings). WiFi accuracy is more like 20m-100m. I'm not sure how having another app using background location (i.e. using GPS) would affect this. Probably, the location manager would share information to make the accuracy better.
  • Region monitoring can take 30 seconds to fire once inside a region, and a couple of minutes to fire after leaving a region (to prevent location glitches from triggering it).
  • When region-monitoring was first introduced, they said that it would only work with 100m regions and anything smaller would be bumped up. This probably still happens.
  • There's a deprecated method startMonitoringForRegion:desiredAccuracy: which allowed you to specify the distance past the region border to start generating notifications. Presumably this feature has been rolled into startMonitoringForRegion: but is still there. A 10m region might end up with a 10m buffer.
  • If you want to do this, specify a larger region around where you want to monitor, and when the device wakes up in that region, start background location updates (GPS) and use CLCircularRegion's -containsCoordinate: to trigger when the device is within 10m manually. This method is officially sanctioned by Apple (see WWDC 2013 Session 307).

From the CLCircularRegion docs:

Remember that the location manager does not generate notifications immediately upon crossing a region boundary. Instead, it applies time and distance criteria to ensure that the crossing was intended and should genuinely trigger a notification. So choose a center point and radius that are appropriate and give you enough time to alert the user.

From the Location & Maps PG:

Region events may not happen immediately after a region boundary is crossed. To prevent spurious notifications, iOS doesn’t deliver region notifications until certain threshold conditions are met. Specifically, the user’s location must cross the region boundary, move away from the boundary by a minimum distance, and remain at that minimum distance for at least 20 seconds before the notifications are reported.

The specific threshold distances are determined by the hardware and the location technologies that are currently available. For example, if Wi-Fi is disabled, region monitoring is significantly less accurate. However, for testing purposes, you can assume that the minimum distance is approximately 200 meters.

There's further inside scoop from this post by Kevin McMahon, who asked the Core Location engineers about region monitoring at a lab at WWDC 2012. This info will have changed in the meantime, but the part about region categories is interesting. Here's an edit:

Fine Region (0 - 150m)
- With the floor of 100m this category's range is effectively 100-150m.

- For regions this size performance is heavily dependent on the location-related hardware

- The amount of time that it takes Core Location to detect and call the appropriate delegate method is roughly 2-3 minutes on average after the region boundary has been crossed.

- Some developers have figured out independently that smaller regions would see quicker callbacks and would cluster smaller regions to cover one large area to improve region crossing notifications.



Related Topics



Leave a reply



Submit