Optimizing CLLocationManager/CoreLocation to retrieve data points faster on the iPhone
Asked Answered
C

3

17

I'm currently using CoreLocation + CLLocationManager in order to compare a users current location to N amount of other locations. The problem I'm facing is I'm dealing with urban areas where locations are close to each other, so I need to pin point the location of the user as accurately as the device allows without sacrificing too much time. My calculations are darn accurate, the problem is, it takes far too long to collect 10 samples. I'll paste my filters/accuracy respective code below. If anyone can comment on how I can speed this up, that would be great. Until I speed it up, it's rather useless in my app as it currently takes around 3-4 minutes to gather info, a duration that's not accept by my target demographic.

Code looks something like this:

[self.locationManager setDistanceFilter:kCLDistanceFilterNone];
[self.locationManager setDesiredAccuracy:kCLLocationAccuracyBest];

- (void)locationManager:(CLLocationManager *)manager didUpdateToLocation:(CLLocation *)newLocation fromLocation:(CLLocation *)oldLocation
{
    if (newLocation.horizontalAccuracy > 0.0f &&
        newLocation.horizontalAccuracy < 120.0f) { // roughly an accuracy of 120 meters, we can adjust this.

    [myLocs addObject:newLocation];
}

Thanks for any optimization tricks to speed this up.

Christ answered 4/7, 2009 at 0:43 Comment(0)
F
48

I've had lots of issues with CLLocationManager on the iPhone also. From different Apps, and trial and error this is what I've figured out;

1) Use a singleton class for the locationManager, ONLY ONE Instance, follow the examples in:

Apple's LocateMe example

Tweetero : http://tweetero.googlecode.com/svn/trunk

I'm thinking you can only have one location manager running, there's only one GPS chip in the iPhone, thus if you launch multiple location manager object's they'll conflict, and you get errors from the GPS location manager stating it can't update.

2) Be extremely careful how you update the locationManager.desiredAccuracy and locationManager.distanceFilter

Follow the example in Apple's code for this in the FlipsideViewController:

[MyCLController sharedInstance].locationManager.desiredAccuracy = accuracyToSet;
[MyCLController sharedInstance].locationManager.distanceFilter = filterToSet;

This approach works, and causes no errors. If you update the desiredAccuracy or distanceFilter in the main delegate loop:

- (void)locationManager:(CLLocationManager *)manager didUpdateToLocation:(CLLocation *)newLocation  fromLocation:(CLLocation *)oldLocation

Core Location will likely complain that it can't update the values, and give you errors. Also, only use the constants for the desiredAccuracy supplied by Apple, and no others, thus;

kCLLocationAccuracyBest, kCLLocationAccuracyNearestTenMeters,
kCLLocationAccuracyHundredMeters, kCLLocationAccuracyKilometer,
kCLLocationAccuracyThreeKilometers

Use of other values might give you errors from locationManager. For distanceFilter you can use any value, but be aware people have stated it has issues, the main value to default to is: -1 = Best, I've used 10.0 and it seems OK.

3) To force a new update, call the startUpdates method like in Tweetero, this forces locationManager to do it's thing, and it should try and get you a new value.

4) The core location delegate routine is tricky to get accurate updates out of. When your App first initializes you can be off in accuracy by 1000 meters or more, thus one attempt is not going to do it. Three attempts after initialization usually gets you to within 47 or so meters which is good. Remember that each successive attempt is going to take longer and longer to improve your accuracy, thus it gets expensive quickly. This is what you need to do: Take a new value only if it is recent AND If the new location has slightly better accuracy take the new location OR If the new location has an accuracy < 50 meters take the new location OR If the number of attempts has exceeded 3 or 5 AND the accuracy < 150 meters AND the location has CHANGED, then this is a new location and the device moved so take this value even if it's accuracy is worse. Here's my Location code to do this:

- (void)locationManager:(CLLocationManager *)manager didUpdateToLocation:(CLLocation  *)newLocation  fromLocation:(CLLocation *)oldLocation
{
    locationDenied = NO;

    if(![[NSUserDefaults standardUserDefaults] boolForKey:@"UseLocations"])
    {
        [self stopAllUpdates];
        return;
    }

    NSDate* eventDate = newLocation.timestamp;
    NSTimeInterval howRecent = abs([eventDate timeIntervalSinceNow]);
    attempts++;

    if((newLocation.coordinate.latitude != oldLocation.coordinate.latitude) && (newLocation.coordinate.longitude != oldLocation.coordinate.longitude))
        locationChanged = YES;
        else
            locationChanged = NO;

    #ifdef __i386__
            //Do this for the simulator since location always returns Cupertino
            if (howRecent < 5.0)
    #else
                // Here's the theory of operation
                // If the value is recent AND
                // If the new location has slightly better accuracy take the new location OR
                // If the new location has an accuracy < 50 meters take the new location OR
                // If the attempts is maxed (3 -5) AND the accuracy < 150 AND the location has changed, then this must be a new location and the device moved
                // so take this new value even though it's accuracy might be worse
                if ((howRecent < 5.0) && ( (newLocation.horizontalAccuracy < (oldLocation.horizontalAccuracy - 10.0)) || (newLocation.horizontalAccuracy < 50.0)
                                          || ((attempts >= attempt) && (newLocation.horizontalAccuracy <= 150.0) && locationChanged)))
    #endif
                {
                    attempts = 0;
                    latitude = newLocation.coordinate.latitude;
                    longitude = newLocation.coordinate.longitude;
                    [currentLocation release];
                    currentLocation = [newLocation retain];
                    goodLocation = YES;

                    [[NSNotificationCenter defaultCenter] postNotificationName: @"UpdateLocationNotification" object: nil];

                    if (oldLocation != nil) {
                        distanceMoved = [newLocation getDistanceFrom:oldLocation];
                        currentSpeed = newLocation.speed;
                        distanceTimeDelta = [newLocation.timestamp timeIntervalSinceDate:oldLocation.timestamp];
                    }
                }
    // Send the update to our delegate
    [self.delegate newLocationUpdate:currentLocation];
}

If you have a iPhone 3GS getting Heading updates is a lot easier, here's the code in the same module as above:

//This is the Heading or Compass values
- (void)locationManager:(CLLocationManager *)manager didUpdateHeading:(CLHeading *)newHeading {
    if (newHeading.headingAccuracy > 0)
    {
        [newHeading retain];
        // headingType = YES for True North or NO for Magnetic North
        if(headingType) {
            currentHeading = newHeading.trueHeading;
        } else {
            currentHeading = newHeading.magneticHeading;
        }

        goodHeading = YES;
        [[NSNotificationCenter defaultCenter] postNotificationName: @"UpdateHeadingNotification" object: nil];

    }
}

Hope this helps people!

Fatwitted answered 28/8, 2009 at 18:32 Comment(3)
Great code. But as the below answer notes, it can take a long time for didUpdateToLocation: to fire. I have tried and tried and regardless, I am seeing rather slow updates when I am inside - sometimes it takes a minute or more. However, if I switch to Maps.app it instantly finds my location. I wish we could get the same instant accuracy through the API as with MapKit.Cousin
I wonder why it checks "UseLocations" in user defaults? Is this value automatically set by the OS?Pasturage
Great code. When you set locationChanged = YES, the if statement should use || instead of &&. If either the lat or lon has changed, the location has changed.Scever
B
4

didUpdateToLocation only updates when the user moves location. This can be sporadic, as I'm sure you've noticed. CCLocationManager doesn't send out regular updates, but instead sends updates when the user has moved, or it increases it's accuracy. When I'm stationary and I start an app, I usually get about three updates, and then nothing for a while. Google Maps does a similar sort of thing, showing a ring vs point as the accuracy increases. I'd suggest waiting until you've reach your required accuracy ( < 120.0f ) and then performing your action or calculation.

If you're used to a 'normal' GPS, where updates are sent, and it continues to refine it's accuracy, I'm afraid the iPhone isn't design to do this.

Alternatively, you could use the MapKit API, and show the user the map with their location, and allow the to 'accept' once they feel the location is accurate enough. I note sometimes I get a pinpoint, and then 30 seconds later it moves me maybe 50 meters closer to where I actually am.

Byrann answered 4/7, 2009 at 2:27 Comment(0)
A
0

I wrote a GIT repo on this, which you are free to use https://github.com/xelvenone/M6GPSLocationManager

  • If the result accuracy is better than acceptableAccuracy, we are done
  • If we get an update on occuracy, we wait maximumWaitTimeForBetterResult to get a better one, - If this doesn't happen, we are done and take the best one
  • If we are constantly getting updates, which exceed maximumAttempts, we take th best one (probably we are moving anyway)
  • If we don't get any other update in 30 sec, we are done (there won't be probably any other update)

Code

- (void)scopeToCurrentLocationWithAcceptableAccuracy:(CLLocationAccuracy)acceptableAccuracy
              maximumWaitTimeForBetterResult:(NSTimeInterval)maximumWaitTimeForBetterResult
                             maximumAttempts:(NSInteger)maximumAttempts
                                onCompletion:(M6GPSLocationManagerCompletion)completion;
Ancona answered 27/1, 2014 at 17:19 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.