I have an app that revolves around the device's GPS and the information that comes from it. It is important that the location data be accurate and up-to-date. I know that the device is limited by its GPS and the GPS's limits, but I was wondering if there is anything I can do to tweak/improve the performance of the iPhone GPS, particularly in the speed area. Because location updates lag about 3-5 seconds behind the real-time location of the device, the velocity reported by the location manager also lags that far behind the real-time value. In my case, that is simply too long. I understand that there might not be anything I can do, but has anyone had any success in improving the responsiveness of the iPhone GPS? Every little bit makes a difference.
Edit 1:
My location manager is inside a singleton class, as Apple recommends.
Inside SingletonDataController.m:
static CLLocationManager* locationManager;
locationManager = [CLLocationManager new];
locationManager.distanceFilter = kCLDistanceFilterNone;
locationManager.headingFilter = kCLHeadingFilterNone;
if(([[UIDevice currentDevice] batteryState] == UIDeviceBatteryStateCharging) || ([[UIDevice currentDevice] batteryState] == UIDeviceBatteryStateFull)) {
locationManager.desiredAccuracy = kCLLocationAccuracyBestForNavigation;
} else {
locationManager.desiredAccuracy = kCLLocationAccuracyBest;
}
[sharedSingleton setLocationManager:locationManager];
[locationManager release];
Inside MapView.m (where the location manager is actually used):
- (id)initWithNibName:(NSString*)nibNameOrNil bundle:(NSBundle*)nibBundleOrNil {
//setup
[SingletonDataController sharedSingleton].locationManager.delegate = self;
//more setup
}
- (void)batteryChanged {
if(([[UIDevice currentDevice] batteryState] == UIDeviceBatteryStateCharging) || ([[UIDevice currentDevice] batteryState] == UIDeviceBatteryStateFull)) {
[SingletonDataController sharedSingleton].locationManager.desiredAccuracy = kCLLocationAccuracyBestForNavigation;
} else {
[SingletonDataController sharedSingleton].locationManager.desiredAccuracy = kCLLocationAccuracyBest;
}
}
- (void)viewDidLoad {
//setup
[[NSNotificationCenter defaultCenter]
addObserver:self
selector:@selector(batteryChanged)
name:UIDeviceBatteryStateDidChangeNotification
object:nil];
//other setup
}
The data handling happens inside locationManager:didUpdateToLocation:fromLocation:
. I don't believe that inefficiency here is the cause of the lag.
locationManager:didUpdateToLocation:fromLocation:
calls this method to update the UI:
- (void)setLabels:(CLLocation*)newLocation fromOldLocation:(CLLocation*)oldLocation {
//set speed label
if(iterations > 0) {
if(currentSpeed > keyStopSpeedFilter) {
if(isFollowing) {
[mapViewGlobal setRegion:MKCoordinateRegionMake([newLocation coordinate], mapViewGlobal.region.span)];
}
NSString* currentSpeedString;
if(isCustomary) {
currentSpeedString = [[NSString alloc] initWithFormat:@"%.1f miles per hour", (currentSpeed * 2.23693629f)];
} else {
currentSpeedString = [[NSString alloc] initWithFormat:@"%.1f km per hour", (currentSpeed * 3.6f)];
}
[speedLabel setText:currentSpeedString];
[currentSpeedString release];
} else {
speedLabel.text = @"Not moving";
}
}
//set average speed label
if(iterations > 4 && movementIterations > 2) {
NSString* averageSpeedString;
if(isCustomary) {
averageSpeedString = [[NSString alloc] initWithFormat:@"%.1f miles per hour", (float)((speedAverages / (long double)movementIterations) * 2.23693629f)];
} else {
averageSpeedString = [[NSString alloc] initWithFormat:@"%.1f km per hour", (float)((speedAverages / (long double)movementIterations) * 3.6f)];
}
[averageSpeedLabel setText:averageSpeedString];
[averageSpeedString release];
}
//set elapsed time label
NSInteger seconds = [[NSDate date] timeIntervalSinceDate:dataObject.locationManagerStartDate];
NSInteger minutes = seconds / 60;
NSInteger hours = minutes / 60;
//get remainder
seconds %= 60;
NSString* timeString;
NSString* secondsString;
NSString* minutesString;
NSString* hoursString;
if((seconds % 60) < 10) {
secondsString = [[NSString alloc] initWithFormat:@"0%i", seconds];
} else {
secondsString = [[NSString alloc] initWithFormat:@"%i", seconds];
}
if((minutes % 60) < 10) {
minutesString = [[NSString alloc] initWithFormat:@"0%i", minutes];
} else {
minutesString = [[NSString alloc] initWithFormat:@"%i", minutes];
}
if((hours % 60) < 10) {
hoursString = [[NSString alloc] initWithFormat:@"0%i", hours];
} else {
hoursString = [[NSString alloc] initWithFormat:@"%i", hours];
}
timeString = [[NSString alloc] initWithFormat:@"%@:%@:%@", hoursString, minutesString, secondsString];
[elapsedTimeLabel setText:timeString];
[timeString release], timeString = nil;
[secondsString release], secondsString = nil;
[minutesString release], minutesString = nil;
[hoursString release], hoursString = nil;
NSString* totalDistanceString;
if(isCustomary) {
totalDistanceString = [[NSString alloc] initWithFormat:@"Total: %.2f mi", (float)distance * 0.000621371192f];
} else {
totalDistanceString = [[NSString alloc] initWithFormat:@"Total: %.2f km", (float)distance / 1000.0f];
}
[customTopBar setTitle:totalDistanceString];
[totalDistanceString release];
}
With a couple of NSDates and NSLogs I have found that the execution of the entire locationManager:didUpdateToLocation:fromLocation:
(not just the label updating method) never takes more than about 8ms on my iPhone 4; in other words, the data handling isn't the problem.
OK, a couple of things could improve your lag. First of all, use kCLLocationAccuracyBestForNavigation always. There is no real battery usage difference between that and kCLLocationAccuracyBest, they both use the GPS at top speed. The main difference is in the post-processing that Apple does.
Second, there is no need to filter for speed == 0. Apple already does that filtering: if your speed from the GPS drops below a certain threshold (about 4 km/h), the OS assumes you are standing still, and it substitutes the same location value for all subsequent samples. It does that until it thinks you are moving again. I assume they do that to avoid "jittering" on the map when you are standing still. In fact, speed drops to 0 already for the last real value of a sequence of "standing-still" values, so if you filter on speed == 0 than you are missing one real GPS sample.
Unfortunately, they is no way to avoid that filtering and get real GPS samples. I talked to Apple about it, and their response was that they are not going to change the behaviour. kCLLocationAccuracyBestForNavigation does less aggressive filtering than kCLLocationAccuracyBest, so it's best to use that.
Third, you probably are already doing this, but make sure that you call "setNeedsDisplay" on your view right from the "didUpdateFromLocation:", to make sure that the map is actually redrawn.
If you do all that, you should have a lag of about 1 second. If you want to improve on the 1 second than you can try to use predictive techniques. From the last two locations, and the given speed, you can calculate where the next location is likely to be, and already display that location. I have had mixed results with that. It works well for fast movement that does not change speed suddenly, like driving a car. It works less well for slower movement like walking or biking.
Aside from the other good examples of how to use Core Location, also keep in mind the general technique for getting good kinematics results from a not-so-great sensor (e.g. smartphone location services) of Kalman Filtering.
It's a lot of math and testing and tweaking, but it does allow you to get what users would consider better results than simple data from the sensor provides.
This is what's used in avionics and things like radar processing systems.
In iPhone we can configure location services by two methods -
Standard Location Services
, that is satellite GPS which provide you more accurate data.Significant Location Changes
that uses A-GPS or get location through wi-fi which provide less accurate data.We can configure location services by any of these two methods but it depends on what is the requirement of the app. If the app is a
navigation app
or alocation tracking app
then we should useStandard Location Services
but before using standard services we have in mind that if you want more accurate data then you have to suffer withbattery consume more quickly
. If the app don't require location update more frequently and also theaccuracy doesn't matter
a lot then we shouldSignificant Location Changes
because it willsave a lot of battery
consume as compare to Standard Location Service.Standard Location Service uses
desiredAccuracy
anddistanceFilter
value to determine whether and when to deliver event.desiredAccuracy
is the parameter where you can define how much accuracy you want from GPS hardware. It uses some pre-defined constants as -distanceFilter
is the parameter where you have to define distance, means for how much distance gap you want to ask GPS hardware to send a location update.In your case you are dealing with the speed parameter, so i guess its something related to navigation. So you should use
Standard Location Services
. I think you are also doing that but the issue that you are facing is lag between location updates. Here i suggest you to modify yourdesiredAccuracy
anddistanceFilter
value to this -[locationManager setDesiredAccuracy:kCLLocationAccuracyNearestTenMeters]; [locationManager setDistanceFilter:10.0f];
by setting values to this you will get location update in less then 1 sec if your are driving.
One more thing you have to put in your mind that when you get location update you should check its
timestamp
value to ignore old location updates. Its because when you startlocationManager
by callingstartUpdatingLocation
then the first location you get may be your old location. Also you have to check forhorizontalAccuracy
value because first few location updates that you get are always not accurate and might have accuracy in 1000 or more that you are not looking for. So you have to check its value to ignore inaccurate location updates.