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.)
Related
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.
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.
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'm logging gps points during a walk. Below it shows the function that the coordinates are saved each 5 seconds.
i Did several tests but i cannot get the right accuracy i want. (When testing the sky is clear also tests in google maps shows me that the gps signal is good).
here is the code:
-(void)viewDidAppear:(BOOL)animated{
if (self.locationManager == nil){
self.locationManager = [[[CLLocationManager alloc] init] autorelease];
locationManager.delegate = self;
// only notify under 100 m accuracy
locationManager.distanceFilter = 100.0f;
locationManager.desiredAccuracy= kCLLocationAccuracyBest;
[locationManager startUpdatingLocation];
}
}
- start logging
[NSTimer scheduledTimerWithTimeInterval:5 target:self selector:#selector(getData) userInfo:nil repeats:YES];
</code>
<code>
-(void)getData{
int distance;
// re-use location.
if ([ [NSString stringWithFormat:#"%1.2f",previousLat] isEqualToString:#"0.00"]){
// if previous location is not available, do nothing
distance = 0;
}else{
CLLocation *loc1 = [[CLLocation alloc] initWithLatitude:previousLat longitude:previousLong];
CLLocation *loc2 = [[CLLocation alloc] initWithLatitude:latGlobal longitude:longGlobal];
distance = [loc1 getDistanceFrom: loc2];
}
// overwrite latGlobal with new variable
previousLat = latGlobal;
previousLong = longGlobal;
// store location and save data to database
// this part goes ok
}
- (void)locationManager:(CLLocationManager *)manager didUpdateToLocation:(CLLocation *)newLocation fromLocation:(CLLocation *)oldLocation {
// track the time to get a new gps result (for gps indicator orb)
lastPointTimestamp = [newLocation.timestamp copy];
// test that the horizontal accuracy does not indicate an invalid measurement
if (newLocation.horizontalAccuracy < 0) return;
// test the age of the location measurement to determine if the measurement is cached
// don't rely on cached measurements
NSTimeInterval locationAge = -[newLocation.timestamp timeIntervalSinceNow];
if (locationAge > 5.0) return;
latGlobal = fabs(newLocation.coordinate.latitude);
longGlobal= fabs(newLocation.coordinate.longitude);
}
I have taken a screenshot of the plot results (the walk takes 30 minutes) and an example of what i'am trying to acomplish:
http://www.flickr.com/photos/21258341#N07/4623969014/
i really hope someone can put me in the right direction.
Looking at your plots - This is exactly what I see too in an app I am working on.
Looking at your code. Whenever location is updated, locationManager calls didUpdateToLocationFromLocation - polling at 5s intervals will give you a lot of points at the same location. (but not relevant to the question)
Also I think you are leaking a lastPointTimestamp (not relevant to the question)
Right - the jumping around - you can look at the accuracy of the points : you do this in locationManager anyway but only check for <0. Is there a pattern of what the horizontal accuracy is on the jumping points ? The accuracy figure may be much worse then for other points, or one specific value (caused by a switch to cell tower triangulation).
Finally, you may need to implement filtering. Kalman filters are the over-the-top way to go, but I am looking at using low-pass filters combined with some basic physics (max acceleration & speed for walking, cycling, running, driving etc) to improve results.
Happy to collaborate on this.