Having trouble calculating accurate total walking/running distance using CLLocationManager
Asked Answered
G

2

16

I'm trying to build an iOS app that displays the total distance travelled when running or walking. I've read and re-read all the documentation I can find, but I'm having trouble coming up with something that gives me an accurate total distance.

When compared with Nike+ GPS or RunKeeper, my app consistently reports a shorter distance. They'll report the same at first, but as I keep moving, the values of my app vs other running apps gradually drift.

For example, if I walk .3 kilometers (verified by my car's odometer), Nike+ GPS and RunKeeper both report ~.3 kilometers every time, but my app will report ~.13 kilometers. newLocation.horizontalAccuracy is consistently 5.0 or 10.0.

Here's the code I'm using. Am I missing something obvious? Any thoughts on how I could improve this to get a more accurate reading?

#define kDistanceCalculationInterval 10 // the interval (seconds) at which we calculate the user's distance
#define kNumLocationHistoriesToKeep 5 // the number of locations to store in history so that we can look back at them and determine which is most accurate
#define kValidLocationHistoryDeltaInterval 3 // the maximum valid age in seconds of a location stored in the location history
#define kMinLocationsNeededToUpdateDistance 3 // the number of locations needed in history before we will even update the current distance
#define kRequiredHorizontalAccuracy 40.0f // the required accuracy in meters for a location.  anything above this number will be discarded

- (id)init {
    if ((self = [super init])) {
        if ([CLLocationManager locationServicesEnabled]) {
            self.locationManager = [[CLLocationManager alloc] init];
            self.locationManager.delegate = self;
            self.locationManager.desiredAccuracy = kCLLocationAccuracyBestForNavigation;
            self.locationManager.distanceFilter = 5; // specified in meters
        }

        self.locationHistory = [NSMutableArray arrayWithCapacity:kNumLocationHistoriesToKeep];
    }

    return self;
}

- (void)locationManager:(CLLocationManager *)manager didUpdateToLocation:(CLLocation *)newLocation fromLocation:(CLLocation *)oldLocation {
    // since the oldLocation might be from some previous use of core location, we need to make sure we're getting data from this run
    if (oldLocation == nil) return;
    BOOL isStaleLocation = [oldLocation.timestamp compare:self.startTimestamp] == NSOrderedAscending;

    [self.delegate locationManagerDebugText:[NSString stringWithFormat:@"accuracy: %.2f", newLocation.horizontalAccuracy]];

    if (!isStaleLocation && newLocation.horizontalAccuracy >= 0.0f && newLocation.horizontalAccuracy < kRequiredHorizontalAccuracy) {

        [self.locationHistory addObject:newLocation];
        if ([self.locationHistory count] > kNumLocationHistoriesToKeep) {
            [self.locationHistory removeObjectAtIndex:0];
        }

        BOOL canUpdateDistance = NO;
        if ([self.locationHistory count] >= kMinLocationsNeededToUpdateDistance) {
            canUpdateDistance = YES;
        }

        if ([NSDate timeIntervalSinceReferenceDate] - self.lastDistanceCalculation > kDistanceCalculationInterval) {
            self.lastDistanceCalculation = [NSDate timeIntervalSinceReferenceDate];

            CLLocation *lastLocation = (self.lastRecordedLocation != nil) ? self.lastRecordedLocation : oldLocation;

            CLLocation *bestLocation = nil;
            CGFloat bestAccuracy = kRequiredHorizontalAccuracy;
            for (CLLocation *location in self.locationHistory) {
                if ([NSDate timeIntervalSinceReferenceDate] - [location.timestamp timeIntervalSinceReferenceDate] <= kValidLocationHistoryDeltaInterval) {
                    if (location.horizontalAccuracy < bestAccuracy && location != lastLocation) {
                        bestAccuracy = location.horizontalAccuracy;
                        bestLocation = location;
                    }
                }
            }
            if (bestLocation == nil) bestLocation = newLocation;

            CLLocationDistance distance = [bestLocation distanceFromLocation:lastLocation];
            if (canUpdateDistance) self.totalDistance += distance;
            self.lastRecordedLocation = bestLocation;
        }
    }
}
Greyso answered 8/1, 2012 at 16:15 Comment(0)
G
10

As it turns out, the code I posted above works great. The problem happened to be in a different part of my app. I was accidentally converting the distance from meters to miles, instead of from meters to kilometers. Oops!

Anyway, hopefully my post will still have some merit, since I feel it's a pretty solid example of how to track a user's distance with Core Location.

Greyso answered 9/1, 2012 at 14:28 Comment(3)
Don't forget to accept this as your answer to keep it from showing as an open question. Nice example by the way.Tulatulip
Hello Daniel, I am fetching same issue since more than one week and still not getting any solution. So, can you help me?Exhibit
@Greyso i will check this PSLocationManager.after i running in road nobody change in meter label.what was the problem in this project.Charlet
H
0

You probably have set kRequiredHorizontalAccuracy too low. If there is no location in the history that has accuracy < kRequiredHorizontalAccuracy, then you ignore all those points and add 0 to the distance.

Heptad answered 8/1, 2012 at 18:15 Comment(3)
Unfortunately I don't think that will do it. The first reason is that I'm logging the horizontalAccuracy, and it stays pretty consistently at 10.0 or 5.0. Also, even though the points > kRequiredHorizontalAccuracy are ignored, as soon as I get a valid location, I compare it with the last valid location to get a distance, so I'm not throwing away distance data.Greyso
@Greyso ... and the last valid location (lastRecordedLocation) is newLocation in case there is no good accuracy in the list. Which means you re-set the lastRecordedLocation without adding distance. By the way, 10 and 5 are pretty low. I get accuracy 100-150 quite regularly in my tests.Heptad
Thanks for taking a look. lastRecordedLocation should only be nil if a valid location has not yet been recorded, since I set self.lastRecordedLocation = bestLocation at the bottom. Thanks also for the suggestion about 100-150 accuracy. I'll do some more testing... may need to up my kRequiredHorizontalAccuracy from 40.Greyso

© 2022 - 2024 — McMap. All rights reserved.