CLLocationManager always returning speed: -1 - ios

I'm trying to determine the location of my users and I have everything setup properly but my devices return -1 for speed and course all the time. When I run the exact same setup on the simulator speed and course values are just fine.
I have calibrated the compass on my test devices and the maps show the correct locations. I have also reset location and privacy settings and I have used 5 different phones. It's always the same story on all devices.
manager = [[CLLocationManager alloc] init];
self.manager.delegate = self;
manager.desiredAccuracy = kCLLocationAccuracyBest;
manager.distanceFilter = 500;
manager.activityType = CLActivityTypeOther;
[manager startUpdatingLocation];
-(void)locationManager:(CLLocationManager *)manager didUpdateLocations:(NSArray *)locations
{
for (CLLocation *location in locations) {
//double speed = 0.0;
//speed = location.speed;
//NSLog(#"%f", s);
NSLog(#"%#", location.description);
}
}
This returns this:
2015-05-07 12:16:57.703 Health_[323:84932] Location: (
"<+52.55719238,+11.02049967> +/- 165.00m (speed -1.00 mps / course -1.00) # 07.05.15 12:16:57 Mitteleurop\U00e4ische Sommerzeit"
)
Does anyone have an idea what's going on?

As it turns out CLLocationManager does not return any speed or course information if you
a. don't move at all. A fixed location always has -1 values (should return something else in my opinion but oh well).
b. haven't let the location manager run for a little while. If you stop the location manager immediately after you have created and started it, it does not yet know any of these information (again should return something different in my opinion then "invalid value").
In my case I was doing both of these things and as it turns out none of my devices are broken... (phew)
I made a little application and put it on Github to help others understand this problem. Just put it on your phone and move around for a little while. It will eventually return something but just not immediately and not if you don't move and haven't moved in a while.

Related

Getting really bad accuracy from CLLocationManager

I'm using CLLocationManager to get users location.
I want to get a single location update.
My problem is that I'm getting really bad horizontalAccuracy
location is %# <+xx.xxxxxx,+yy.yyyyyyy> +/- 3881.91m
verticalAccuracy: 65.4401861912846, horizontalAccuracy: 3881.90892434957
Code:
fileprivate lazy var locationManager: CLLocationManager = {
let manager = CLLocationManager()
manager.desiredAccuracy = kCLLocationAccuracyBest
manager.delegate = self
manager.requestAlwaysAuthorization()
manager.pausesLocationUpdatesAutomatically = false
manager.desiredAccuracy = kCLLocationAccuracyBest
manager.distanceFilter = kCLDistanceFilterNone
return manager
}()
override init() {
super.init()
locationManager.startUpdatingLocation()
}
func locationManager(_ manager: CLLocationManager, didUpdateLocations locations: [CLLocation]) {
guard let mostRecentLocation = locations.last else {
return
}
let verticalAccuracy = mostRecentLocation.verticalAccuracy
let horizontalAccuracy = mostRecentLocation.horizontalAccuracy
print("location is %#", mostRecentLocation)
print("verticalAccuracy: \(verticalAccuracy), horizontalAccuracy:\(horizontalAccuracy)")
}
Any suggestions why is this happening?
I'm in a room next to a window so i except to get bad accuracy but not that bad.
Thanks
I'm getting ridiculous results.
I got horizontalAccuracy of 15,000 m.
When i go out doors it works great but in doors should not be as bad as this.
Using Cellular triangulation and wifi should give a lot better results.
after 20 minutes i started to get good results of +- 50 m accuracy in doors.
I suggest you add a condition to make sure that you will just be using a location that have a better accuracy. On my case I use 20 as the desired horizontal accuracy on below example.
Example:
func locationManager(_ manager: CLLocationManager, didUpdateLocations locations: [CLLocation]) {
locationManager.stopUpdatingLocation()
if locations.last!.horizontalAccuracy < 20 {
//Only use location that enters here
}
else {
//If the accuracy is not met then start updating location again and if possible increase more the accuracy (use kCLLocationAccuracyBestForNavigation if you really need it). Make sure that you use the desired accuracy and filter properly to avoid draining battery.
locationManager.startUpdatingLocation()
}
}
I always have found that when you first start location updates, the first few readings are quite poor, and then the accuracy improves as the GPS settles down.
What I do is to first check if the timestamp on the location is more than a few seconds old. If it is, I discard it. (Not sure if recent OS versions still send "stale" GPS readings, but the system used to give you a location from the last time the GPS was powered up, sometimes several hours old.)
Once I get current locations, I check the accuracy, and discard any location updates who's accuracy reading is too poor. Only when I get a reading that's good enough do I use it (and stop location updates in your case, since you only want 1 update.)
Bear in mind that it can take 30 seconds (or more) for the GPS to settle down, and in an area with poor GPS signal, you may never get a good enough reading.

Location timestamp accuracy in ios

After analysing location services in iOS 10, found that some inconsistency is in the caching behaviour.
Fetching locations in a periodic time (in my case every 20 secs) returns locations but their timestamps are not in chronologically ordered. This indicates that the caching locations might have issues. So if you are checking accuracy through location-timestamp better to save the previous timestamps also. So that you could decide that the location fetched can be used or not.
Below image is taken from my console log. Here I used the format "Lat Long : latitude_longitude | location_timestamp | Now : current_timestamp"
Yes some time in best accuracy ios take the location from the cache so you need to avoid that location here is the code for accurate locationtion.
Update :
"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."
Reference :
https://developer.apple.com/reference/corelocation/cllocationmanager
Note: you can vary the accuracy for the device like ipod and ipad
//MARK: Location delgates
func locationManager(_ manager: CLLocationManager, didUpdateLocations locations: [CLLocation])
{
if locations.count > 0
{
let location = locations[locations.count-1]
let maxAge:TimeInterval = 60;
let requiredAccuracy:CLLocationAccuracy = 100;
let locationIsValid:Bool = Date().timeIntervalSince(location.timestamp) < maxAge && location.horizontalAccuracy <= requiredAccuracy;
if locationIsValid
{
NSLog(",,, location : %#",location);
NSLog("valid locations.....");
}
}
}
The problem behind this is that sometimes the timestamps do not match to the location! E.g. while traveling you suddenly records speeds > 300km/h and best accuracy.
I would sort the locations and only would take the last one if not too old:
func locationManager(_ manager: CLLocationManager, didUpdateLocations locations: [CLLocation]) {
let sortedLocations = locations.sorted { (l1, l2) -> Bool in
return l1.timestamp.compare(l2.timestamp) != .orderedDescending
}
if let newestLocation = sortedLocations.last{
if Date().timeIntervalSince(newestLocation.timestamp) < 60{
//TODO: Use the location
}
}
}
Yes like #chirag shah commented we definitely need to do that check. My suggestion is we should know about that the caching technique has been modified. And also it is not enough only checking the timestamp, we have to focus on the failure case. Here is the objective C code
-(void)locationManager:(CLLocationManager *)manager didUpdateLocations:(NSArray *)locations{
CLLocation* location = [locations lastObject];
NSDate* locationTimestamp = location.timestamp;
NSTimeInterval cachedBefore = [locationTimestamp timeIntervalSinceNow];
if (fabs(cachedBefore) < 60.0) {
// Implement your code here
}else{
// Try again or wait for fetching location
}
}

GPS Accuracy Resetting on its own on iOS

In an iPhone app that I'm building, GPS accuracy is a user-controlled value that can be set to either KCLLocationAccuracyNearestTenMeters or KCLLocationAccuracyHundred Meters. The code works fine to capture a track, but when monitoring the app in the Location Energy Impact Instrument I’m seeing some unexpected behavior. When accuracy is set to nearest 10 meters the app works as expected, however, if it’s set to 100 meters the setting remains OK for between a half and two seconds, as shown by the Instrument, but then it switches to KCLLocationAccuracy Best. This setting isn't an option in the app, or even a String anywhere in the code.
One twist here, I am running this as Swift 3.0 using Xcode 8, but it's working as it did before and the new platform doesn't seem to be an issue (and shouldn't have this kind of impact). It does mean that some API's have changed and may look different (and to my taste, better) below.
Obviously, this resetting has a very counterproductive impact on energy consumption, which shows in the Instrument which goes from Low to High energy usage at the point where it switches. I can't identify any point where the app would be doing this unintentionally - in fact, during this one-second period it should only be appending points returned by didUpdateLocations to a pending buffer for later processing, as shown below.
In a shared constants declaration:
let defaultTrackingAccuracy = kCLLocationAccuracyNearestTenMeters
let alternateTrackingAccuracy = kCLLocationAccuracyHundredMeters
var trackingAccuracy = kCLLocationAccuracyNearestTenMeters
var waypointInterval = 100
In a settings ViewController (can also reset waypointInterval but did not):
#IBAction func accuracySwitched(_ sender: UISwitch) {
if accuracySwitch.isOn {
Set.shared.trackingAccuracy = Set.shared.defaultTrackingAccuracy
} else {
Set.shared.trackingAccuracy = Set.shared.alternateTrackingAccuracy
}
}
LocationManager instantiation in a Model portion of code:
lazy var locationManager: CLLocationManager = {
var _locationManager = CLLocationManager()
_locationManager.delegate = self
_locationManager.desiredAccuracy = Set.shared.trackingAccuracy
_locationManager.allowsBackgroundLocationUpdates = true
_locationManager.distanceFilter = Double(Set.shared.waypointInterval)
return _locationManager
}()
Starting the location manager updates in the Model:
func setupLocationManager()
{
if CLLocationManager.authorizationStatus() != .authorizedAlways {
locationManager.requestAlwaysAuthorization()
if CLLocationManager.authorizationStatus() != .authorizedAlways {
delegate?.displayNotice("Unable to Capture Track", alertMessage: "This device requires authorization to use location services in order to capture a track. \n\nPress Continue to return to the Track List.", buttonText: "Continue")
}
}
if !CLLocationManager.locationServicesEnabled() {
delegate?.abortTrackCapture(.gpsUnavailable)
}
locationManager.startUpdatingLocation()
}
Processing points returned by the location manager instance, in the same Model portion:
#objc(locationManager:didUpdateLocations:) func locationManager(_ manager: CLLocationManager, didUpdateLocations locations: [CLLocation])
{
assert(locations.first != nil, "didUpdateLocations called with empty array.")
if recordStatus == .record {
pendingPointsBuffer = pendingPointsBuffer + locations
}
lastPosition.latitude = locations.last!.coordinate.latitude
lastPosition.longitude = locations.last!.coordinate.longitude
lastPosition.elevation = locations.last!.altitude
}
Every few seconds, there is a routine that takes points from the buffer and posts them into a CoreData database, but that only accesses the buffer - it has no interaction with the location manager (so it can sleep through periods when the app is in background while the code above keeps loading points into the buffer).
The user interface is in a separate ViewController module, but shouldn’t be doing anything during the two seconds in question.
I'm thinking that there may be some OS based setting or process that could be doing this, but I haven't found anything that seems to do that.
Thanks for your input - any thoughts are appreciated.

Get Altitude, Bearing, speed and GPS Accuracy

Hy guys, i need to find altitude, bearing and gps accuracy in my iPhone app. i've done this on my android app. in android, i did it like this :
public void onLocationChanged(Location location) {
if (isBetterLocation(location, lastValidLocation)
&& isEnable) {
lastValidLocation = location;
longitude = location.getLongitude();
latitude = location.getLatitude();
altitude = location.getAltitude();
speed = location.getSpeed();
accuracy = location.getAccuracy();
bearing = location.getBearing();
locationProvider = location.getProvider();
onPositionChanged(
LocationService.this,
new GeoPoint(location.getLongitude(), location
.getLatitude()));
}
}
how can i do the same thing in iOS ?
i've tried to get it from CLLocation, but it return null/0,0000/-1.0000 ..
btw, i get this result when i test it on iOS Simulator.
Thanks.
Regards.
The simulator is not a good place to test Core Location stuff. It only simulates a specific location but if you want to test speed, bearing etc. you're better of trying on a real device.
You need to use the class CLLocationManager (Apple doc) and fetch the data using the delegate protocol available. For example, if you want the heading you implement the method locationManager:didUpdateHeading: and call the method startUpdatingHeading on the location manager.

How To Handle Incorrect CLLocation Data

I'm developing a fitness tracking app like Runtastic, Nike+ etc. I'll be mapping the entire activity from start to finish. I don't start updating location changes when the app launches, instead I'm starting when the user starts a workout. But when the locationManager start updating, first 3 to 5 CLLocations are very incorrect. Up to a kilometer in errors. I'm using the following code to initialize the location manager:
self.locationManager = [(PFAppDelegate *)[[UIApplication sharedApplication] delegate] locationManager];
self.locationManager.delegate = self;
self.locationManager.desiredAccuracy = kCLLocationAccuracyBestForNavigation;
self.locationManager.distanceFilter = 5.0;
[self.locationManager startUpdatingLocation];
And in the locationManagerDidUpdateToLocation method:
if( didFindGPS == NO )
{
if( [[lastLocation timestamp] timeIntervalSinceNow] < 10.0 )
{
if( [lastLocation horizontalAccuracy] < 20)
{
didFindGPS = YES;
}
}
}
else
{
//process data here
}
This doesn't filter out those first incorrect locations. I've also tried ignoring locations that have horizontalAccury values that are smaller than 20, but then the app doesn't process any locations.
What can be done to improve the first locations or to handle the first incorrect ones?
Change
if ([lastLocation horizontalAccuracy] < 20)...
to
if (([lastLocation horizontalAccuracy] > 0) && ([lastLocation horizontalAccuracy] < 20))...
According to documentation
A negative value indicates that the location’s latitude and longitude
are invalid.
A negative value of horizontalAccuracy that is.
If you want to set the condition for processing data (seems like you do) you should rewrite the code to:
if (([lastLocation horizontalAccuracy] > 0) && ([lastLocation horizontalAccuracy] < 20))
{
didFindGPS = YES;
//process the data here... since here the location fits your limitations
//and you don't loose the first location (as in original code)
}
else
{
didFindGPS = NO;
}
Note that this code can give you some false alarms for loosing the GPS so you might want to omit the else block.

Resources