Trouble with CLLocation method distanceFromLocation: Inaccurate results
Asked Answered
G

4

9

I am trying to use the distanceFromLocation: method to calculate the total distance that I am walking with my iPhone in my hand. So far, I have been searching all over to help remedy my confusing, inaccurate, and seemingly arbitrary results. In these code snippets, theLabel is just a label object that is present in my app's interface, distanceMoved is the variable that I am trying to store the total distance I walked in, and locMan is a location manager that is declared in my @interface file.

- (void)viewDidLoad
{
   locMan = [[CLLocationManager alloc] init];
   locMan.delegate = self;
   [locMan startUpdatingLocation];
   isInitial = true;
   distanceMoved = 0.0;
   [super viewDidLoad];
}

-(void)locationManager:(CLLocationManager *)manager didUpdateToLocation:(CLLocation *)newLocation fromLocation:(CLLocation *)oldLocation
{
   distanceMoved += [newLocation distanceFromLocation: oldLocation];
   theLabel.text = [NSString stringWithFormat: @"%f meters", distanceMoved];
}

Any help for fixing what I am doing wrong here would be greatly appreciated. Thank you!

Godliman answered 17/4, 2012 at 3:50 Comment(0)
A
14
  1. Filter out cached (old) location data,
  2. Filter out invalid location results (accuracy < 0),
  3. Set accuracy requirement set to kCLLocationAccuracyBest,
  4. Set a minimum distance filter, preferably at least 10 meters to filter out position noise.
  5. EDIT: Don't compute distance when the oldLocation is nil.

There's more you could do but this should work provided you get a good enough horizonalAccuracy (<30m). You could filter out low accuracy results but then you'd have to keep track of the oldLocation yourself which is a bit more complicated.

Add an NSLog (see below) so you can find out what's happening. If you still don't get good results post the output of the NSLog so we can see what's going on and help more.

-(void)locationManager:(CLLocationManager *)manager didUpdateToLocation:(CLLocation *)newLocation fromLocation:(CLLocation *)oldLocation
{
    NSTimeInterval age = -[newLocation.timestamp timeIntervalSinceNow]; 

    if (age > 120) return;    // ignore old (cached) updates

    if (newLocation.horizontalAccuracy < 0) return;   // ignore invalid udpates

    // EDIT: need a valid oldLocation to be able to compute distance
    if (oldLocation == nil || oldLocation.horizontalAccuracy < 0) return; 

    CLLocationDistance distance = [newLocation distanceFromLocation: oldLocation];

    NSLog(@"%6.6f/%6.6f to %6.6f/%6.6f for %2.0fm, accuracy +/-%2.0fm",
        oldLocation.coordinate.latitude,
        oldLocation.coordinate.longitude,
        newLocation.coordinate.latitude,
        newLocation.coordinate.longitude, 
        distance,
        newLocation.horizontalAccuracy);

    distanceMoved += distance;
    theLabel.text = [NSString stringWithFormat: @"%f meters", distanceMoved];
}

- (void)viewDidLoad
{
   locMan = [[CLLocationManager alloc] init];
   locMan.delegate = self;
   locMan.desiredAccuracy = kCLLocationAccuracyBest;
   locMan.distanceFilter = 10;

   [locMan startUpdatingLocation];
   isInitial = true;
   distanceMoved = 0.0;
   [super viewDidLoad];
}

EDIT for iOS6 if you wanted to do the same using didUpdateLocations: (since the above method is now deprecated) the simplest solution is to simply save each location in a property so that on the next update you have the previous location. Declare an property in the class to hold the oldLocation:

@property (nonatomic, retain) CLLocation* oldLocation;

Then in the delegate method you save each newLocation for use as the oldLocation for the next time the delegate method is called:

-(void)locationManager:(CLLocationManager *)manager didUpdateToLocations:(NSArray *)locations
{
    CLLocation* newLocation = [locations lastObject];

    NSTimeInterval age = -[newLocation.timestamp timeIntervalSinceNow]; 

    if (age > 120) return;    // ignore old (cached) updates

    if (newLocation.horizontalAccuracy < 0) return;   // ignore invalid udpates

    // EDIT: need a valid oldLocation to be able to compute distance
    if (self.oldLocation == nil || self.oldLocation.horizontalAccuracy < 0) {
        self.oldLocation = newLocation;
        return; 
    }

    CLLocationDistance distance = [newLocation distanceFromLocation: self.oldLocation];

    NSLog(@"%6.6f/%6.6f to %6.6f/%6.6f for %2.0fm, accuracy +/-%2.0fm",
        self.oldLocation.coordinate.latitude,
        self.oldLocation.coordinate.longitude,
        newLocation.coordinate.latitude,
        newLocation.coordinate.longitude, 
        distance,
        newLocation.horizontalAccuracy);

    distanceMoved += distance;
    theLabel.text = [NSString stringWithFormat: @"%f meters", distanceMoved];

    self.oldLocation = newLocation;    // save newLocation for next time
}
Amathist answered 17/4, 2012 at 20:0 Comment(11)
I just used this exact code, but I changed the distance filter to 1. For some reason, my distance moved begins at -1. Here is my output after walking just about 2m forward: 2012-04-17 17:58:05.341 LocationManagerTest2[422:707] 0.000000/0.000000 to 39.856110/-74.940113 for -1m, accuracy +/-10m 2012-04-17 17:58:10.248 LocationManagerTest2[422:707] 39.856110/-74.940113 to 39.856165/-74.940107 for 6m, accuracy +/-50m 2012-04-17 17:58:11.248 LocationManagerTest2[422:707] 39.856165/-74.940107 to 39.856223/-74.940010 for 10m, accuracy +/-50m The distanceMoved came out to around 15m – way too muchGodliman
I tried using this code a few times before as well, and I got some seemingly accurate results and some that were far offGodliman
the -1 distance is where your oldLocation was 0, which probably means that the oldLocation param was nil. You'll need to add a check for that too (see my edit above). You can't expect high accuracy when it reports horizontalAccuracy is 50m!!! That means the GPS doesn't have a good enough signal, it means the position reported can be off by 50 meters in any direction.Amathist
distanceFilter of 1 is way too small. You can't expect 1m accuracy when the GPS reports that it only has 50m accuracy signal. Even 10m is too optimistic.Amathist
I updated the code the your edit. After trying it out and walking around a bit, the method never ran, and I received no output. Just for clarification, what exactly are the returns that appear after the if statements doing?Godliman
A return exits the function early, so when a return is executed the rest of the function is skipped. If you didn't get output then you didn't move far enough or else the GPS did not get a good signal. It can take several minutes for GPS to lock onto satellites especially if you have no cell towers or wifi to assist.Amathist
@progrmr: in iOS 6 didUpdateToLocation method is changed. How do we implement it as it has only one NSArray locations as a parameter. I am trying to implement the same feature.Vaccinia
@Amathist : Very much appreciated.. Thanks.. I will give it try and leave comment if any doubt.Vaccinia
btw how do we get self.oldLocation in this method? I am stuck at that point from yesterday. locations array contains only latest location.. :/Vaccinia
Look at the code again, the method sets self.oldLocation in two places.Amathist
@Amathist : Thanks very much. I got it..Your answer has gone unnoticed unfortunately. Deserves many upvotes. :-)Vaccinia
M
0

You are not setting the accuracy of your location manager. Go through apple docs, it is something like accuracy = bestfornavigation. or something. That should drain battery like hell but is meant for this kind of purposes.

edit: just remembered i had it at hand.

// Create the Location Manager
locationManager = [[CLLocationManager alloc] init];

// Configure the Location Manager
locationManager.delegate = self;
locationManager.desiredAccuracy = kCLLocationAccuracyBestForNavigation;
Martyry answered 17/4, 2012 at 3:55 Comment(3)
I tried setting all of the different constants for the desired accuracy, but still no luck. Now, my app will start off with the distanceMoved at -1.00000 for some reason instead of 0.000000. Could the problem possibly lie within my Core Location framework or my iPhone itself?Godliman
NSlog the old and the new locaion to see how they are changing. then tell me so i can have an idea of what is going on. ive worked alot with the location manager and i do remember that if u dont configure it correctly it does weird stuffMartyry
The latitude and longitude coordinates seem to be correct, as each time the locations are updated, the new location from the last time is being stored as the old location. For some reason, the distance between the two locations is being calculate as an extremely high number.Godliman
Y
0
CLLocationDistance distance = [secondLocation distanceFromLocation:firstLocation];  // distance is expressed in meters

CLLocationDistance kilometers = distance / 1000.0;
// or you can also use this..
CLLocationDistance meters = distance;

NSString *distanceString = [[NSString alloc] initWithFormat: @"%f", kilometers];

flot totaldistancecovered = [distanceString floatValue];

//Now,you can use this float value for addition...
// distanceMoved  should be float type variable which is declare in .h file...

 distanceMoved = distanceMoved + totaldistancecovered ;
 theLabel.text = [NSString stringWithFormat: @"%f meters", distanceMoved];

Hope this will help you..

Yuma answered 17/4, 2012 at 3:55 Comment(1)
CLLocationDistance is simply a typedef for double, so there's no reason to convert it to a string and then back again in order to do math.Portal
E
0

The first thing you should do is discharge the first update by checking the timestamp of the first update you receive from the location manager, it usually caches the last known position. Second remember the accuracy, the initial values that you'll get are always in a wide range.

Eggett answered 17/4, 2012 at 4:21 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.