How to get accurate speed from accelerometer on iOS? - ios

I would need very accurate speed for my app, even when the user is only walking. I tried using the GPS, it works well but not for speeds that low. So my question is, could I use the accelerometer to get some more accurate values? If yes, how would I do that?

I do not think you will get something from accelerometer. Accelerometer only returns you motion (x,y,z). I do not know how you could able to get speed out of that, unless you will do some linear modelling algorithm that requires heavy research, that could be a Ph.D topic I guess.
I propose the following implementation.
- (id) init
{
self = [super init];
if (self != nil) {
self.manager = [[CLLocationManager alloc] init];
self.manager.delegate = self;
[self.manager startUpdatingLocation];
}
return self;
}
- (void)locationManager:(CLLocationManager *)manager didUpdateToLocation:(CLLocation *)newLocation fromLocation:(CLLocation *)oldLocation
{
NSLog(#"Speed = %f", newLocation.speed);
}
or you could use directly CMPedometer class which I believe that class developed exactly for physical activity applications. I have recently used that class and I am quite satisfied.
import CoreMotion
let lengthFormatter = NSLengthFormatter()
let pedometer = CMPedometer()
pedometer.startPedometerUpdatesFromDate(NSDate(), withHandler: { data, error in
if !error {
println("Steps Taken: \(data.numberOfSteps)")
var distance = data.distance.doubleValue
println("Distance: \(lengthFormatter.stringFromMeters(distance))")
var time = data.endDate.timeIntervalSinceDate(data.startDate)
var speed = distance / time
println("Speed: \(lengthFormatter.stringFromMeters(speed)) / s")
}
})

could I use the accelerometer to get some more accurate values
No, the accelerometer cannot tell you anything about speed. Acceleration and speed are very different things. Indeed, during motion with a constant speed, there is no acceleration (except gravity and similar side effects). You could travel at the speed of light and experience no acceleration (if you could travel at the speed of light).

It really isn't possible to get a decent speed estimate from the Accelerometer. That even if you start with the device at rest and end with it at rest and alsoo using the gyroscope.
Averaging the GPS location is probably the best, possibly a moving average of some sort.

I already have answerded similar question many times.
The apporach with the accelerometer will not work well for more than some seconds. The cause is the dramatic accuracy loss due double integration.
This is well explained in a Goole Talk "Sensor Fusioning" video.
What you can do, for low GPS speed:
A bit simplified:
1. Take locations evry 5 seconds,
2. calculate distance between fix(i-5) and fix(i),
3. calcuate time difference,
and cacluate then the speed in m/s by distanceMeters / timeDeltaSeconds
An advanced solution would use a time window of let's say 5 seconds,
and this evaluation window will move 1 step further every second,
using the same 3 calculation steps as above

Related

Minimizing speed variability with kCLLocationBestForNavigation when sailing

I am working on a GPS Apple Watch app for use when sailing which requires high accuracy for a start line scenario. I suspect the additional gyro/accelerometer inputs are actually hurting, not helping GPS accuracy. For example, CLLocation.speed variability seems very high compared to other instruments. (i.e 4.4 knots, 2.2 knots, 6.8 knots, 4.6 knots ... , when other sailing GPS instruments read 4.5 ,4.3 ,4.6 ,4.5, ...)
I understand that having the iPhone nearby will make the watch offload GPS processing to the iPhone. This definitely helps accuracy, but does not seem to help variability. When I am testing off the water (walking or riding a bike), the variability is much lower than sailing. I get similar results running my code on iPhone app and on watch (with iPhone nearby).
func startReceivingLocationChanges(locationManager: CLLocationManager) -> Bool {
// Do not start services that aren't available.
if !CLLocationManager.locationServicesEnabled() {
// Location services is not available.
return false
}
// Configure and start the service.
locationManager.desiredAccuracy = kCLLocationBestForNavigation // kCLLocationBestForNavigation seems to have accuracy issues when sailing, try kCLLocationBest???
locationManager.distanceFilter = kCLDistanceFilterNone // In meters.
locationManager.delegate = self
locationManager.startUpdatingLocation()
return true
}
I am considering trying kCLLocationAccuracyBest to turn OFF the Gyro/accel in hopes that this may help. I am assuming speed and course are simple calculations based on lat/long changes, but perhaps not? I can do these calculations myself if needed. Also interested in possibly using some 3rd party filtering code to "smooth" the curves. Curious what others have tried. (I suspect this might also be an issue for swimmers, and/or mountain biking tracking scenarios where movement is not "smooth" or easily filtered out.)

CoreLocation find distance travelled and use as variable iOS

I am making an alarm that shuts off only after the phone has been moved a certain distance(to force the user out of bed). I need to be able to find the distance travelled after the alarm has gone off, then take this distance and use it in a method to shut the alarm sound off if the minimum distance has been travelled. Any ideas?
I'm using the following to update the location. Any thoughts on how to incorporate this into disabling the alarm?
-(void)locationManager:(CLLocationManager *)manager didUpdateToLocation:(CLLocation *)newLocation fromLocation:(CLLocation *)oldLocation
{
if(!newLocation) {
NSLog(#"No movement");
}
if ((oldLocation.coordinate.latitude != newLocation.coordinate.latitude) &&
(oldLocation.coordinate.longitude != newLocation.coordinate.longitude))
{
CLLocation *loc1 = [[CLLocation alloc] initWithLatitude:oldLocation.coordinate.latitude longitude:oldLocation.coordinate.longitude];
CLLocation *loc2 = [[CLLocation alloc] initWithLatitude:newLocation.coordinate.latitude longitude:newLocation.coordinate.longitude];
CLLocationDistance distance = [loc2 distanceFromLocation:loc1];
if (distance>=4) {
NSLog(#"Very good!");
}
}
NSLog(#"Location:%#", newLocation);
}
-(void)locationManager:(CLLocationManager *)manager didFailWithError:(NSError *)error
{
NSLog(#"Could not find location: %#", error);
}
A couple of thoughts:
You're not considering horizontalAccuracy. First if this is negative for either coordinate, then you just can't do any comparison. And even if they're both non-negative, you probably want to back out the horizontal accuracies if you really want to determine if the user moved a certain distances.
Look at not only the latitude and longitude, but the horizontalAccuracy, too.
You're not considering that when you turn on location services, there are frequently a whole series of coordinates that come in, and the coordinates may bounce around as the horizontal accuracy gets better and better. You want to make sure you don't consider this slow triangulation as the movement necessary to qualify as having gotten out of bed.
Make sure you do your experimentation on a real device, in real world scenarios. Using the simulator is definitely not a good test. Nor is using a device plugged into your computer (because you you want to see your GPS in real world scenario, where someone has moved around before going to bed).
I'd suggest starting with a simple app that logs location events in a database (or other format) and perhaps show the log in a tableview, so you don't always have to go back to your computer to review the results. Fire up that app, walk around, and then look at the location events you get. That will help you get your arms around the patterns you see as people use their actual devices.
Note that if this app is destined for the app store, you'll want to engage lots of different people in different environments and different devices and different scenarios (poor GPS locations, with wifi, no wifi, etc.). Even when you do your own real-world GPS experience with a physical device, you must appreciate that everyone else's may experience different GPS results.
I must confess to some reservations whether the iPhone GPS is accurate enough for this sort of app as a generalized solution, and you'll probably want to warn the user to make sure to plug in their iPhone before they go to sleep so the GPS doesn't drain the battery. You might also want to play tricks like turning on location services after the user has demonstrated that they're up and about. You might also not even want to turn on location services until a few minutes before the alarm, thereby assuring that the GPS is as accurate as it can be when the alarm goes off, but not draining their battery as they sleep if they don't happen to have it plugged in and charging.
Update:
Probably easier than calculating distances yourself, use the location services distance filter (i.e. only generate events when the location changes by x meters). For example:
- (void)startLocationManager
{
self.locationManager = [[CLLocationManager alloc] init];
self.locationManager.desiredAccuracy = kCLLocationAccuracyBest;
self.locationManager.distanceFilter = 5.0; // detect when I move 5 meters
self.locationManager.delegate = self;
[self.locationManager startUpdatingLocation];
}
When I do that, standing completely still, as soon as I turn on location services, I get a whole bunch of events before I even start moving. You can see the GPS warming up, and getting more and more accurate:
latitude longitude horizontalAccuracy
---------------- ------------------ ------------------
39.9482837548282 (82.4963494095571) 65
39.9482817585030 (82.4964543834170) 10
39.9482392622539 (82.4964914314290) 5
39.9481753502422 (82.4964918505242) 5
39.9481269028419 (82.4964797805836) 5
I wait for that to quite down (it took 15-20 seconds in my case), and then I started walking in a straight line, looking at the distance from the last location I got above.
latitude longitude horizontalAccuracy distance
---------------- ------------------ ------------------ --------
39.9481722908476 (82.4964962091138) 5 5.3
39.9482206544289 (82.4965029146363) 5 10.4
39.9482627315828 (82.4965282279839) 5 15.4
39.9483157471204 (82.4965248752227) 5 21.2
I must confess that while I didn't measure it, these distances didn't feel quite right, but possibly within the 5 meter tolerance I set up with my distanceFilter.
Anyway, as all of this evidences, the right process is probably going to be
turn on location services, setting the distanceFilter appropriate for your app;
wait for the location to settle down;
make sure the horizontalAccuracy is even plausible for this process to work at all; and
wait for the new location notification based upon the GPS determining that the distanceFilter has been exceeded.
This never will be perfect (e.g. I had to walk a good 6-10 meters before my "5 meter" distanceFilter kicked in, probably a combination of the GPS lagging a few seconds and the horizontalAccuracy), but it might be "good enough for government work."
(By the way, I've changed my coordinates in the above log, so don't look for me in this Ohio cornfield, but it gives you and idea of the sort of pattern you may see.)

Tracking multiple (20+) locations with iOS geofencing

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?
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.
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.
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.
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.
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.

Accuracy of "Region Monitoring" on iOS 6

I'm trying to make a location-based app that requires constant updating of the location after opening. I was looking for a way to keep the location in the background when the app is closed and I found that is not the best way because it would drain the battery very quickly from any iPhone.
To cut a long story short I found with the "Region Monitoring" as I have understood they are the same as Apple uses for their App "Reminders" that also works with location. Then I used that option to test my app and I realize that in the simulator "never" enters the method "didEnterRegion" despite it is correctly created:
-(void) locationManager: (CLLocationManager *) manager didEnterRegion: (CLRegion *) region
{
NSLog (# "entering the region");
CLLocation * location1 = manager.location;
CLLocation * location2 = [[CLLocation alloc] initWithLatitude: latitud.doubleValue longitude: longitud.doubleValue];
}
I finally realized that the method is called but only when I walked away much of the area and I was back to the area, indicating that it did not work because I was already in that location (all this in the simulator), the strange thing is that it was not, the location of the simulator was miles from the region to monitor! Which leads me to think it is problem of accuracy, despite creating the region with 50 or 100 meter radius was spreading the 3 or 4 km, so I was useless.
Has anyone experienced this knows if iOS 6 simulator has this error or if precision problem? I don't have a Developer Licence yet to test it directly on my iPhone and I do not want to buy it yet because my app is far from finished, I appreciate any help on this topic.
Thanks in advance.
I would suggest doing actual ground testing. Region monitoring accuracy varies a lot depending on the test location.
Second option is to use region monitoring to just wake up the app and start standard location service. To do this you need permission to run the app in background. Apple may not approve this. Our app got rejected using this approach.
DidEnterRegion and ExitRegion is not very accurate. Around +, - 150m accuracy.
But -(void) locationManager:(CLLocationManager *)manager didUpdateLocations:(NSArray *)locations
is very accurate. Use this to find out your regions have coordinates/current location
CLLocation *currentLocation = [locations lastObject];
if ([self.ARegionYouSetBeforeHand containsCoordinate:currentLocation.coordinate])
{
//you arrived at this region!!
}
Region monitoring works (generally) on the same basis as the "significant location change" location service. That is, it uses cell tower location; Apple suggests you can usually expect accuracy around 1km, but warns that actual accuracy will vary with the density of cell towers in your area. You can find more background on this topic in the WWDC videos on Core Location.
It might be feasible -- I haven't tried -- to use Region Monitoring to (wake up your app and) get a general guess at the user's location, then use a higher accuracy mode in CLLocationManager to get a more precise fix.
At finish I realize that the accuracy of Region Monitoring is good enought for the most purposes, but in the simulator can be tricky because it not have GPS or cellphone hardware, anyway when I used "region monitoring" in my device even in a simulation route the function works correctly.
And for more accuracy even, I invoked the method "startUpdatingLocation" of the CLLocationManager into the method "didEnterRegion", that works okey for me.
I have experienced the same issue with Region Monitoring on iPhone 5C with cellular tower mobile data, disabling the WiFi, I was used to get didEnterRegion called very soon, when I was 1.95 Km away from the monitoring point for which I kept region radius of 100.0 meters. Which is too early.
Apple has some Threshold value determined by the hardware and the location technologies that are currently available. So for me it might be around 1800 meters early value.
I suggest to keep radius minimum (say 25.0 for 500.0 region) OR if it is so precious notification, use didUpdateLocations delegate method and determine your region and notify the same.
Regards,
Kaushik
I found that the built in region monitoring wasn't accurate enough for my needs. If you need higher accuracy, first set your accuracy appropriately, and then you can use something like this:
(Uses the same method as #coolcool1994, but a little more in-depth and using Swift 2.1. Remember to set up CLLocationManager and its Delegate (where the functions below would reside), and start receiving location updates)
var activeRegions = Set<String>()
var monitoredRegions = [
CLCircularRegion(center: CLLocationCoordinate2D(latitude: 0.0, longitude: 0.0),
radius: 300, identifier: "Region1"),
CLCircularRegion(center: CLLocationCoordinate2D(latitude: 0.0, longitude: 0.0),
radius: 300, identifier: "Region2")
]
func manuallyUpdateActiveRegionsFromLocation(location: CLLocation){
// Optionally explicitly reject unwanted location updates
// Setting the location manager delegate's desiredAccuracy should be enough
let maxOldTime = 300 //5 minutes
let minAccuracy = 300 //300 metres
if abs(location.timestamp.timeIntervalSinceNow) > maxOldTime
|| location.horizontalAccuracy > minAccuracy {
return
}
for monitoredRegion in monitoredRegions {
if monitoredRegion.containsCoordinate(location.coordinate) {
activeRegions.insert(monitoredRegion.identifier)
} else {
activeRegions.remove(monitoredRegion.identifier)
}
}
}
func locationManager(manager: CLLocationManager, didUpdateLocations locations: [CLLocation]) {
if let location = locations.last {
manuallyUpdateActiveRegionsFromLocation(location)
}
}
Pre-Defined GPS Accuracy on MK MapView
kCLLocationAccuracyBestForNavigation – Uses the highest possible
level of accuracy augmented by additional sensor data.
kCLLocationAccuracyBest – The highest recommended level of
accuracy for devices running on battery power.
kCLLocationAccuracyNearestTenMeters - Accurate to within 10 meters.
CLLocationAccuracyHundredMeters – Accurate to within 100 meters.
kCLLocationAccuracyKilometer – Accurate to within one kilometer.
kCLLocationAccuracyThreeKilometers – Accurate to within three
kilometers.
Usage:
var locationManager: CLLocationManager = CLLocationManager()
//MapView Location
locationManager.delegate = self
locationManager.desiredAccuracy = kCLLocationAccuracyNearestTenMeters
locationManager.requestWhenInUseAuthorization()
locationManager.startUpdatingLocation()
locationManager.startUpdatingHeading()

distanceFromLocation returns huge value without moving the tiniest bit

I'm trying to call a function in case the distance traveled since last checked is > 30 m. Based on the Class description of CLLocationDistance the value returned is in m.
I use this code:
[locationManager startUpdatingLocation];
if (!startingLocation)
startingLocation = locationManager.location;
// NSLog(#"Latitude:%f Longitude:%f", startingLocation.coordinate.latitude, startingLocation.coordinate.longitude);
updatedLocation = locationManager.location;
CLLocationDistance distance = [updatedLocation distanceFromLocation:startingLocation];
if (distance > 30) {
NSLog(#"%f",distance);
NSLog(#"Latitude:%f Longitude:%f", updatedLocation.coordinate.latitude, updatedLocation.coordinate.longitude);
[self stop];
The console output with NSLog returns 7946754.993111, and I did not even touch the phone.
Any suggestions are appreciated!
8000 km between two points? I'll bet the first point is 0, 0 and you're about 8000 km from there, right?
In any case be sure to only use valid locations by checking to make sure the horizontalAccuracy is >=0 before using the location coordinates. Invalid locations have a negative horizontalAccuracy.
Also, you should probably discard any cached locations by checking the age of the location data and don't use it if it is more than a few seconds old.
See the sample code here that does these checks.
Another thing to watch out for is that you can get locations that show false motion due to changes in accuracy. See this answer for details.
Is it possible that because you are calling startUpdatingLocation then getting that location then immediately asking for an update that the location is still trying to figure out exactly where it is, hence the big movement.
Try calling startUpdatingLocation from another method then implement the delegate method from CLLocationManager locationManager:didUpdateToLocation:fromLocation to get the movement.

Resources