My first location using Core Location is almost always invalid.
My code is as follows:
- (void)locationManager:(CLLocationManager *)manager
didUpdateToLocation:(CLLocation *)newLocation
fromLocation:(CLLocation *)oldLocation
{
//for saving the data ACCURATELY for calculations
// make sure the coordinates are valid
if ([self isValidLocation:newLocation withOldLocation:oldLocation])
{
mDistance = [newLocation distanceFromLocation:oldLocation];
[[NSNotificationCenter defaultCenter]postNotificationName:#"locationUpdated" object:nil];
}
}
As you can see it checks to see if it is a valid location with isValidLocation. That code is here:
- (BOOL)isValidLocation:(CLLocation *)newLocation
withOldLocation:(CLLocation *)oldLocation
{
// Throw away first point
if (isFirstPoint)
{
NSLog(#"First point thrown away.");
isFirstPoint = NO; //subsequent updates will NOT be the first point
return NO;
}
// Filter out nil locations
if (!newLocation){
NSLog(#"New location invalid");
return NO;
}
if (!oldLocation)
{
NSLog(#"Old location invalid");
return NO;
}
// Filter out points by invalid accuracy
if (newLocation.horizontalAccuracy < 0)
{
return NO;
}
// Filter out points that are out of order
NSTimeInterval secondsSinceLastPoint = [newLocation.timestamp
timeIntervalSinceDate:oldLocation.timestamp];
if (secondsSinceLastPoint < 0){
return NO;
}
// Filter out points created before the manager was initialized
NSTimeInterval secondsSinceManagerStarted = [newLocation.timestamp
timeIntervalSinceDate:locationManagerStartDate];
if (secondsSinceManagerStarted < 0){
return NO;
}
// If the distance is negative
if ([newLocation distanceFromLocation:oldLocation] < 0)
{
return NO;
}
if(newLocation.speed < 0)
{
return NO;
}
// GIANT ELSE: The newLocation is good to use
return YES;
}
Even after checking all of that, my first point is always invalid. For example, I went from work to home, and turned on my location manager at home, yet my first coordinate was from my office at work. How can I fix this?
Here is what happens when I get an invalid first point:
It really depends on your use case. Core Location often returns the device's last known position in order to give you a result as quickly as possible. This location is passed to you before the device has finished acquiring a new location. The last known position can be several kilometers off your current position.
Depending on the type of your app, this might be a totally negligible difference (e.g. if an app just wants to locate the country a user is in) or it might represent an inacceptable error. Core Location can't tell.
It's important for you to decide how to deal with this behavior. Always throwing the first reported location away might not be the best strategy. You might want to store the time when you call startUpdatingLocation and only throw the first reported location away if the system passes it to you within a very brief period of time (say, 1/10th of a second) that makes it likely to be a cached location. Also, the location's timestamp property might be helpful in judging how (in)accurate the reported location can be.
Your test is wrong. The documentation of locationManager:didUpdateToLocation:fromLocation: clearly states that for the first update, the oldLocation is nil. But you don't test for that: you only look at newLocation and then access oldLocation. This can lead to bad values or crashes.
Related
I initialize the locationManager this way:
if (!self.locManager)
{
self.locManager = [[CLLocationManager alloc] init];
self.locManager.delegate = self;
[locManager startMonitoringSignificantLocationChanges];
}
my device is not moving and still "didUpdateToLocation" is being called every time.
What could be a problem?
Thanks
didUpdateToLocation may update for a number of reasons, a good strategy for handling this is to gradually filter results based on timestamp, then required accuracy.
Apple provide a good example in the LocateMe sample app:
- (void)locationManager:(CLLocationManager *)manager didUpdateToLocation:(CLLocation *)newLocation fromLocation:(CLLocation *)oldLocation
{
// test the age of the location measurement to determine if the measurement is cached
// in most cases you will not want to rely on cached measurements
NSTimeInterval locationAge = -[newLocation.timestamp timeIntervalSinceNow];
if (locationAge > 5.0) return;
// test that the horizontal accuracy does not indicate an invalid measurement
if (newLocation.horizontalAccuracy < 0) return;
// test the measurement to see if it is more accurate than the previous measurement
if (self.bestEffortAtLocation == nil || self.bestEffortAtLocation.horizontalAccuracy > newLocation.horizontalAccuracy)
{
// store the location as the "best effort"
self.bestEffortAtLocation = newLocation;
// test the measurement to see if it meets the desired accuracy
//
// IMPORTANT!!! kCLLocationAccuracyBest should not be used for comparison with location coordinate or altitidue
// accuracy because it is a negative value. Instead, compare against some predetermined "real" measure of
// acceptable accuracy, or depend on the timeout to stop updating. This sample depends on the timeout.
//
if (newLocation.horizontalAccuracy <= locationManager.desiredAccuracy) {
// we have a measurement that meets our requirements, so we can stop updating the location
//
// IMPORTANT!!! Minimize power usage by stopping the location manager as soon as possible.
//
[self stopUpdatingLocation:NSLocalizedString(#"Acquired Location", #"Acquired Location")];
}
}
}
Do you check for location differences? CoreLocation also calls callbacks when other attributes such as accuracy, heading or speed change
startMonitoringSignificantLocationChangesshould give you an initial fix and after that you will get callbacks for "significant changes" (cell tower change etc.)
I'm working on an application which periodically obtains the user's location. The big problem is that sometimes the app gets stuck, no more updates are delivered. Even if I (kill and) restart my app, nothing changes. (The accuracy values set for location manager are near 100-200 meters.)
BUT, when I start the Google Maps App, in a few seconds it gets a very accurate location (which is delivered to my app to if I switch back). Why ?
Here are some relevant code parts :
The timerFiredAction is called periodically by the timer.
-(void) timerFiredAction
{
if (isStillWaitingForUpdate)
{
successiveTimerActivationCount ++;
// force LM restart if value too big , e.g. 30 ( stop + start )
return;
}
successiveTimerActivationCount = 0 ;
isStillWaitingForUpdate = YES;
/* isRecordingX is always true */
if (isSignificant && isRecordingSig) [self startSignificant ];
if (isGPS && isRecordingGPS) [self startGps];
}
// this is called in delegate method only
-(void) timerStopLocationServices
{
isStillWaitingForUpdate = NO;
if (isGPS) [ self stopGps] ;
if (isSignificant) [self stopSignificant];
}
- (void)locationManager:(CLLocationManager *)manager didUpdateToLocation:(CLLocation *)newLocation fromLocation:(CLLocation *)oldLocation
{
// verify accuracy and stuff
if ( isStillWaitingForUpdate && _other_validations_ )
{
// store it
[self timerStopLocationServices] ;
}
}
The start and stop methods simply verifiy if the locationmanager is nil, if yes they call createManager and then call start & stopUpdatingLocation.
The creation of the LM looks like this :
-(void)createManager
{
#synchronized (self)
{
if (locationManager != nil) {
[self releaseManager]; // stop timer, stop updating , reelase previous if exists
}
locationManager = [[CLLocationManager alloc] init];
locationManager.delegate = self;
double desired;
// accuracy is an init param, snap to one smaller constant
// generally 100-200
if (accuracy >= kCLLocationAccuracyThreeKilometers) desired = kCLLocationAccuracyThreeKilometers; else
if (accuracy >= kCLLocationAccuracyKilometer) desired = kCLLocationAccuracyKilometer; else
if (accuracy >= kCLLocationAccuracyHundredMeters) desired = kCLLocationAccuracyHundredMeters; else
if (accuracy >= kCLLocationAccuracyNearestTenMeters) desired = kCLLocationAccuracyNearestTenMeters; else
if (accuracy >= kCLLocationAccuracyBest) desired = kCLLocationAccuracyBest; else
if (accuracy >= kCLLocationAccuracyBestForNavigation) desired = kCLLocationAccuracyBestForNavigation;
locationManager.desiredAccuracy = desired;
locationManager.distanceFilter = distanceFilter;
}
}
Did anyone experienced something like this? Any ideas are welcome :)
Thanks!
I can at least confirm "blocked location managers", though in a slightly different context:
From my experience, if you create two location managers with different accuracy settings, start updates for both and then only stop updates for the one with the higher accuracy requirement, then the other one no longer receives any updates.
You are apparently only using one manager, but the way your manager gets stuck seems to be the same: In the case described above, updates can also be restarted by using the map application (or any similar).
My workaround is the following: Whenever I stop one manager, I also reset the distance filters of all others (stored in an NSSet called myCLLocationManagers), like the following:
CLLocationManager * lm;
CLLocationDistance tmp;
for (lm in myCLLocationManagers){
tmp=lm.distanceFilter;
lm.distanceFilter=kCLDistanceFilterNone;
lm.distanceFilter=tmp;
}
This seems to work more reliably than my previous woraround (which was to stop and restart the managers).
Note that kCLLocationAccuracyBestForNavigation and kCLLocationAccuracyBest are (currently) negative values, and that in general, you shouldn't rely on the specific values of all those constants - there's no guarantee that kCLLocationAccuracyHundredMeters==100 (although currently true). I only mention this because it appears like you are directly comparing an "accuracy" variable in meters with those constants.
I am using the CoreLocation framework to get my speed and distance to calculate average speed.
On the first update that CoreLocation sends out, it shows negative values for both speed and distance traveled. How can I fix this?
Speed is locationController.locationManager.location.speed where locationController holds my CoreLocation delegate. Distance is calculated by taking the old location and the new location and calculating distance.
//Distance
- (void)locationManager:(CLLocationManager *)manager
didUpdateToLocation:(CLLocation *)newLocation
fromLocation:(CLLocation *)oldLocation
{
// make sure the old and new coordinates are different
if ((oldLocation.coordinate.latitude != newLocation.coordinate.latitude) &&
(oldLocation.coordinate.longitude != newLocation.coordinate.longitude))
{
mDistance = [newLocation distanceFromLocation:oldLocation];
}
}
The data returned by Core Location may be invalid for a number of reasons. Before you use the data, run this method to see if it is valid.
// From http://troybrant.net/blog/2010/02/detecting-bad-corelocation-data/
- (BOOL)isValidLocation:(CLLocation *)newLocation
withOldLocation:(CLLocation *)oldLocation
{
// filter out nil locations
if (!newLocation){
return NO;
}
// filter out points by invalid accuracy
if (newLocation.horizontalAccuracy < 0){
return NO;
}
// filter out points that are out of order
NSTimeInterval secondsSinceLastPoint = [newLocation.timestamp
timeIntervalSinceDate:oldLocation.timestamp];
if (secondsSinceLastPoint < 0){
return NO;
}
// filter out points created before the manager was initialized
NSTimeInterval secondsSinceManagerStarted = [newLocation.timestamp
timeIntervalSinceDate:locationManagerStartDate];
if (secondsSinceManagerStarted < 0){
return NO;
}
// newLocation is good to use
return YES;
}
There's a note in the CLLocationManager Class Reference:
Because it can take several seconds to return an initial location, the location manager typically delivers the previously cached location data immediately and then delivers more up-to-date location data as it becomes available. Therefore it is always a good idea to check the timestamp of any location object before taking any actions.
You should check whether you're getting a cached value and ignore it, waiting for the first "real" update.
NSTimeInterval timeSinceLastUpdate = [[newLocation timestamp] timeIntervalSinceNow];
// If the information is older than a minute, or two minutes, or whatever
// you want based on expected average speed and desired accuracy,
// don't use it.
if( timeSinceLastUpdate < -60 ){
return;
}
If you are collecting the first point, it has no reference to calculate speed or distance. I believe it is defined to return -1 if it can't get you what you need.
To resolve this, simply check for the negative values.
if(location.speed > 0) {
return;
}
I am using locationManager:didUpdateToLocation:fromLocation: to get location updates. Most everything is working well but when I check the newLocation.speed propery while standing still I almost always get a speed value greater than 0. Even when I start getting location updates for the first time and have NEVER even moved I will get positive values after a few updates.
Just doing a simple greater than 0 check then setting a UILablel if it is:
if (newLocation.speed > 0) {
self.speed.text = [NSString stringWithFormat:#"%.2f",[self speed:newLocation.speed]];
}
More code from that method:
- (void)locationManager:(CLLocationManager *)manager didUpdateToLocation:(CLLocation *)newLocation fromLocation:(CLLocation *)oldLocation {
// Check first if we are getting the accuracy we want
if (newLocation.horizontalAccuracy < 0) return;
// Make sure the update is new not cached
NSTimeInterval locationAge = -[newLocation.timestamp timeIntervalSinceNow];
if (locationAge > 5.0) return;
// Check to see if old and new are the same
if ((oldLocation.coordinate.latitude == newLocation.coordinate.latitude) &&
(oldLocation.coordinate.longitude == newLocation.coordinate.longitude)) return;
// Make sure our new location is not super far away from old
CLLocationDistance dist = [newLocation distanceFromLocation:oldLocation];
NSLog(#"Distance: %f", dist);
if (dist > 40) return;
// Make sure we have a new location
if (!newLocation) return;
// Check to see if we are stopped
if (newLocation.speed == 0) {
self.inMotion = NO;
} else if (newLocation.speed > 0) {
self.inMotion = YES;
} else {
// Probably an invalid negative value
self.inMotion = NO;
}//end
// Speed
// Should always be updated even when not recording!
if (newLocation.speed > 0 && inMotion) {
self.speed.text = [NSString stringWithFormat:#"%.2f",[self speed:newLocation.speed]];
NSLog(#"Speed: %f", newLocation.speed);
}
// Tons more code below that I removed for this post
}//end
Is this normal?
How much greater than zero? And for how long? Do all the locations that you are comparing have the same accuracy?
In a GPS receiver the speed is usually determined by the change in location between successive location fixes (it can also be computed by measuring doppler shift). Location fixes will vary slightly due to measurement errors or as they become more accurate. The changes in location can make it appear that the device moved.
For example, suppose your first fix has an accuracy of 1000m horizontal and your second fix has an accuracy of 300m horizontal, the first position could have been 1000m off of the real location and the second could be 700m away from that first fix even though the device hasn't moved. This translates to a change in location over time which is "speed".
I have an application where I have to check that whether user's mobile location has changed or for 30 consecutive seconds or not?If it has not changed in 30 seconds then user will be navigated to the other view and if it has changed then user will have a message that your mobile location's coordinates have changed in these 30 seconds.
I am using these code but it does nothing ...
-(void)time
{
for(int i = 0; i<= timeremaining ;i++)
{
if (new1 == old)
{
if(timeremaining == 1)
{
Timer90 *timerview = [[Timer90 alloc] initWithNibName:#"Timer90" bundle:nil];
[self.navigationController pushViewController:timerview animated:YES];
}
}
else
{
[NSTimer cancelPreviousPerformRequestsWithTarget:self selector:#selector(time) object:nil];
}
timeleft.text = [NSString stringWithFormat:#"%d",timeremaining];
}
timeremaining--;
}
-(void)locationManager:(CLLocationManager *)manager didUpdateToLocation:(CLLocation *)newLocation fromLocation:(CLLocation *)oldLocation
{
new1 = newLocation;
old=oldLocation;
latitude = [[NSString alloc]initWithFormat:#"%g",newLocation.coordinate.latitude];
longitude = [[NSString alloc]initWithFormat:#"%g",newLocation.coordinate.longitude];
accuracy = [[NSString alloc] initWithFormat:#"%g",newLocation.horizontalAccuracy];
if(oldLocation == NULL)
{
oldLocation == newLocation;
}
}
here timer90 is name of my view.Please help me out friends...Any help will be appreciated.
You could possibly do something like this (after adding counter and initialLocation to your properties) it will compare location after 30 seconds following the first update, but I think it would be helpful if you added an age check to the entire method so that you aren't comparing to some very old data point.
-(void)locationManager:(CLLocationManager *)manager didUpdateToLocation:(CLLocation *)newLocation fromLocation:(CLLocation *)oldLocation
{
if(self.counter==0){
self.intialLocation=newLocation;
self.counter=1;
}
NSTimeInterval age = [initialLocation.timestamp timeIntervalSinceNow];
if(age>30)
{
self.counter=0;
if(newLocation.latitude==initialLocation.latitude&&newLocation.longitude==initialLocation.longitude){
Timer90 *timerview = [[Timer90 alloc] initWithNibName:#"Timer90" bundle:nil];
[self.navigationController pushViewController:timerview animated:YES];
}
}
}
Two things that I notice. May be the whole issue, or only part of it.
Number 1.
if(oldLocation == NULL)
{
oldLocation == newLocation;
}
oldLocation == newLocation will not set the two equal to each other (you need one equals sign) and your are assigning the parameter pointers to the same object, not your member pointers. For example, if oldLocation points to Object A and newLocation points to Object B at the start of didUpdateToLocation:fromLocation:, then:
new1 is set to point at Object B
old is set to point at Object A
if(Object A is NULL)
oldLocation and newLocation both point at Object B
The values of new1 and old are not set to the same object in the NULL case. Which is what you are checking for in time
Number 2.
In time you are checking if new1 and old are pointing at the same object. No necessarily if the two objects represent the same location. In the case where oldLocation is null and they are set together, this check may pass...however, it is more likely that CoreLocation will call the update method a few times while accuracy gets better, and you will have two separate objects that represent the same lat/long location.
You should compare these two locations for proximity using the CLLocation getDistanceFromLocation: method.
Hope that Helps!