CLLocationManager - returns wrong speed - ios

I am trying to calculate users current driving speed, but there is a huge difference between cllocationmanager speed and actual driving speed.
As I am driving at 50 kmph and cllocation manager shows ~72/~73 kmph. Below is the code I am using.
locationManager = CLLocationManager()
locationManager.delegate = self
locationManager.desiredAccuracy = kCLLocationAccuracyBestForNavigation
locationManager.requestWhenInUseAuthorization()
locationManager.allowsBackgroundLocationUpdates = true
locationManager.pausesLocationUpdatesAutomatically = false
locationManager.distanceFilter = 1.0
locationManager.headingFilter = 0.1
locationManager.startUpdatingLocation()
And below is the location manager protocol
func locationManager(_ manager: CLLocationManager, didUpdateLocations locations: [CLLocation]){
let speedInKmph = location.speed * 3.6
if speedInKmph > 10 {
MyRide.shared.speedInfo.append(SpeedInfo(speed: speedInKmph))
self.view.showToast("\(speedInKmph) **********", position: .bottom, popTime: 2.0, dismissOnTap: false)
}
}

#Anand, the CLLocationManager could be obtaining speed on one of two ways:
measuring the distance between two lat/long/alt triplets and dividing by some sort of time reference
using the native Doppler speed measurement on the GPS chip. Doppler speed is computed for all of the satellites that the particular GPS chip sees. Maybe Apple publishes the names and model numbers of the GPS chips inside of iPhones, I don't know.
I don't know which technique is used by CLLocationManager.
I think you have to test your system at a couple of speeds and three directions (due north, due east, and 45 degrees angle to due North and due East).
If all measurements show the same fudge factor of 1.45, you are probably good to go with:
let speedInKmph = location.speed * 3.6 * (1/1.45)
If your measurements don't match each other, then more studying required. If I figure it out I will post it here. The linked youtube might be a good idea.

Related

Measure distance between two location inaccurate

am trying to get the distance between two location, but I always get wrong value. Here are the codes:
var startLoction: CLLocation!
var startLoction: CLLocation!
#IBAction func getCurrLocation(){
startLoction = currLocation
}
func locationManager(_ manager: CLLocationManager, didUpdateLocations locations: [CLLocation]) {
currLocation = locations.last!
if currLocation.horizontalAccuracy > 0{
var distance = CLLocationDistance(DBL_MAX)
if let location = self.startLoction {
distance = currLocation.distance(from: location)
realTimeDistanceLabel.text = String(format: "%.2f", distance)
}
}
}
Problems:
1. When I stay still, the distance sometimes will increase, it may be a big number than 15 meters. It always starts at a big number
2. When I walk out 10 or 20 meters and then walk back straight, the distance sometimes increases but not decreases.
3. When I walk around a big circle, the distance goes to a bit more accurate value relatively.
I also tried to addcurrLocation.horizontalAccuracy > 0 && currLocation.horizontalAccuracy < 100, and aslo tried prevLocation >= (currLocation.horizontalAccuracy * 0.5 from stackOverflow answer, still I cannot get a accurate value.
Any other ideas to make it right?
Thanks.
Core Location switch on when used, spend a period triangulating the device location such that it takes a while to settle down. But also you will find it can lose accuracy in certain locations/conditions and will try always to improve accuracy. The device will then be changing it's mind about "where you are located" and if it does this after your method
#IBAction func getCurrLocation() { ... }
is called, naturally it will appear as though the device has moved.
To reduce the effect of this occurring, you could test not just that currLocation.horizontalAccuracy > 0 (so it is a valid reading) but also that it is less than a given positive value (though this will bring it's own problem - see below). The current horizontal location of the device may be between plus or minus the radius of uncertainty as reported by the horizontalAccuracy property (the radius of uncertainty is always a positive number. If it is negative, all bets are off, the device is essentially saying "hang on, I can't say where I am with any certainty yet").
Actually though setting an upper bound for currLocation.horizontalAccuracy will demonstrate the problem, it probably won't be what you want because then your UI will only work under the condition Core Location knows accuracy is greater than x. Generally you wouldn't want to restrict the UI in this way.

How to stop CLLocationManager Heading value change when iPhone roll little bit.

My CLLocationManager is working fine. I can be noticed when the heading value changed.
However, I found the heading value are different when the iPhone roll even little bit angle.
I have set the
locationManager.headingOrientation = CLDeviceOrientation.landscapeRight
I have also set other properties:
locationManager.requestWhenInUseAuthorization()
orientation = getCLDeviceOrientation(by: UIDevice.current.orientation)
locationManager.desiredAccuracy = kCLLocationAccuracyBest
locationManager.headingFilter = 0.1
locationManager.headingOrientation = CLDeviceOrientation.landscapeRight
locationManager.startUpdatingHeading()
locationManager.delegate = self
The headingOrientation property that you are setting is only used as reference point when you don't want the default: the top of the device in portrait mode represents due north (0 degrees)
Having set the reference, you will get changes in degrees for every yaw movement.
You can see it documented here: https://developer.apple.com/reference/corelocation/cllocationmanager/1620556-headingorientation

Google Maps iOS SDK Prevent camera zoom after each location update

I have an app which requires the user's location to be constantly updated so I can display their current coordinates and altitude. I'm doing this using the didUpdateLocations function:
func locationManager(manager: CLLocationManager, didUpdateLocations locations: [CLLocation]) {
if let location = locations.last {
mapView.camera = GMSCameraPosition(target: locationManager.location!.coordinate, zoom: 15, bearing: 0, viewingAngle: 0)
let locValue : CLLocationCoordinate2D = manager.location!.coordinate
let altitude : CLLocationDistance = Double(round(1000*manager.location!.altitude)/1000)
let long = Double(round(10000*locValue.longitude)/10000)
let lat = Double(round(10000*locValue.latitude)/10000)
let alt = String(altitude) + " m"
latitudeLabel.text = String(lat)
longitudeLabel.text = String(long)
altitudeLabel.text = alt
showLearningObjectsWithinRange(location)
}
}
The problem is, when I try to zoom in on a certain spot on the map, if I move the device even slightly the camera zooms back out again. Obviously this is because of the first line in my didUpdateLocations function setting the camera position, but if I remove that line, the map doesn't center to their location at all.
I tried moving the GMSCameraPosition code to the viewDidLoad, viewWillAppear, and several other places, but this caused the app to crash because it couldn't locate the user in time.
Does anyone have any suggestions on how to make this work? Thanks.
use this instead of that certain line (mapview.camera = ...)
mapView.animate(toLocation: CLLocationCoordinate2D(latitude: location.coordinate.latitude, longitude: location.coordinate.longitude))
With regards to implementing location update, there was an issue posted in GitHub - Implement Responsive User Location Tracking Mode due to location update optimization and going through the thread, a given workaround to show the user location on the map is to call
nMapView.setMyLocationEnabled(true);
instead of:
nMapView.setMyLocationTrackingMode(MyLocationTrackingMode.TRACKING_FOLLOW);
Then, with regards to camera zoom, as discussed in Camera and View - Zoom, you can try setting a minimum or maximum zoom to restrict zoom level. As stated,
You can restrict the range of zoom available to the map by setting a min and max zoom level.
You may also try the solutions given in this SO post - Google Maps iOS SDK, Getting Current Location of user. Hope it helps.

iPhone GPS User Location is moving back and forth while the phone is still

I am doing some mapkit and corelocation programming where I map out a users route. E.g. they go for a walk and it shows the path they took.
On the simulator things are working 100% fine.
On the iPhone I've run into a major snag and I don't know what to do. To determine if the user has 'stopped' I basically check if the speed is (almost) 0 for a certain period of time.
However just keeping the phone still spits out this log for newly updated location changes (from the location manager delegate). These are successive updates in the locationManager(_:didUpdateLocations:) callback.
speed 0.021408926025254 with distance 0.192791659974976
speed 0.0532131983839802 with distance 0.497739230237728
speed 11.9876451887096 with distance 15.4555990691609
speed 0.230133198005176 with distance 3.45235789063791
speed 0.0 with distance 0.0
speed 0.984378335092039 with distance 11.245049843458
speed 0.180509147029171 with distance 2.0615615724029
speed 0.429749086272364 with distance 4.91092459284206
Now I have the accuracy set to best:
_locationManager = CLLocationManager()
_locationManager.delegate = self
_locationManager.distanceFilter = kCLDistanceFilterNone
_locationManager.desiredAccuracy = kCLLocationAccuracyBest
Do you know if there is a setting or I can change to prevent this back and forth behaviour. Even the user pin moves wildly left and right every few seconds when the phone is still.
Or is there something else I need to code to account for this wild swaggering?
I check if the user has moved a certain distance within a certain time to determine if they have stopped (thanks to rmaddy for the info):
/**
Return true if user is stopped. Because GPS is in accurate user must pass a threshold distance to be considered stopped.
*/
private func userHasStopped() -> Bool
{
// No stop checks yet so false and set new location
if (_lastLocationForStopAnalysis == nil)
{
_lastLocationForStopAnalysis = _currentLocation
return false
}
// If the distance is greater than the 'not stopped' threshold, set a new location
if (_lastLocationForStopAnalysis.distanceFromLocation(_currentLocation) > 50)
{
_lastLocationForStopAnalysis = _currentLocation
return false
}
// The user has been 'still' for long enough they are considered stopped
if (_currentLocation.timestamp.timeIntervalSinceDate(_lastLocationForStopAnalysis.timestamp) > 180)
{
return true
}
// There hasn't been a timeout or a threshold pass to they haven't stopped yet
return false
}

Accuracy of Core Location

I'm currently working on a location tracking app and I have difficulties with inaccurate location updates from my CLLocationManager. This causes my app to track distance which is in fact only caused by inaccurate GPS readings.
I can even leave my iPhone on the table with my app turned on and in few minutes my app tracks hundreds of meters worth of distance just because of this flaw.
Here's my initialization code:
- (void)initializeTracking {
self.locationManager = [[CLLocationManager alloc] init];
self.locationManager.delegate = self;
self.locationManager.desiredAccuracy = kCLLocationAccuracyBest;
self.locationManager.distanceFilter = 5;
[self.locationManager startUpdatingLocation];
}
Thanks in advance! :-)
One of the ways I solved this in a similar application is to discard location updates where the distance change is somewhat less than the horizontal accuracy reported in that location update.
Given a previousLocation, then for a newLocation, compute distance from the previousLocation. If that distance >= (horizontalAccuracy * 0.5) then we used that location and that location becomes our new previousLocation. If the distance is less then we discard that location update, don't change previousLocation and wait for the next location update.
That worked well for our purposes, you might try something like that. If you still find too many updates that are noise, increase the 0.5 factor, maybe try 0.66.
You may also want to guard against cases when you are just starting to get a fix, where you get a series of location updates that appear to move but really what is happening is that the accuracy is improving significantly.
I would avoid starting any location tracking or distance measuring with a horizontal accuracy > 70 meters. Those are poor quality positions for GNSS, although that may be all you get when in an urban canyon, under heavy tree canopy, or other poor signal conditions.
I've used this method to retrive the desired accuracy of the location (In SWIFT)
let TIMEOUT_INTERVAL = 3.0
func locationManager(manager: CLLocationManager!, didUpdateLocations locations: [AnyObject]!) {
let newLocation = locations.last as! CLLocation
println("didupdateLastLocation \(newLocation)")
//The last location must not be capured more then 3 seconds ago
if newLocation.timestamp.timeIntervalSinceNow > -3 &&
newLocation.horizontalAccuracy > 0 {
var distance = CLLocationDistance(DBL_MAX)
if let location = self.lastLocation {
distance = newLocation.distanceFromLocation(location)
}
if self.lastLocation == nil ||
self.lastLocation!.horizontalAccuracy > newLocation.horizontalAccuracy {
self.lastLocation = newLocation
if newLocation.horizontalAccuracy <= self.locationManager.desiredAccuracy {
//Desired location Found
println("LOCATION FOUND")
self.stopLocationManager()
}
} else if distance < 1 {
let timerInterval = newLocation.timestamp.timeIntervalSinceDate(self.lastLocation!.timestamp)
if timerInterval >= TIMEOUT_INTERVAL {
//Force Stop
stopLocationManager()
}
}
}
Where:
if newLocation.timestamp.timeIntervalSinceNow > -3 &&
newLocation.horizontalAccuracy > 0 {
The last location retrieved must not be captured more then 3 seconds ago and the last location must have a valid horizontal accuracy (if less then 1 means that it's not a valid location).
Then we're going to set a distance with a default value:
var distance = CLLocationDistance(DBL_MAX)
Calculate the distance from the last location retrieved to the new location:
if let location = self.lastLocation {
distance = newLocation.distanceFromLocation(location)
}
If our local last location hasn't been setted yet or if the new location horizontally accuracy it's better then the actual one, then we are going to set our local location to the new location:
if self.lastLocation == nil ||
self.lastLocation!.horizontalAccuracy > newLocation.horizontalAccuracy {
self.lastLocation = newLocation
The next step it's to check whether the accuracy from the location retrieved it's good enough. To do that we check if the horizontalDistance of the location retrieved it lest then the desiredAccurancy. If this is case we can stop our manager:
if newLocation.horizontalAccuracy <= self.locationManager.desiredAccuracy {
//Desired location Found
self.stopLocationManager()
}
With the last if we're going to check if the distance from the last location retrieved and the new location it's less the one (that means that the 2 locations are very close). If this it's the case then we're going to get the time interval from the last location retrieved and the new location retrieved, and check if the interval it's more then 3 seconds. If this is the case, this mean that it's more then 3 seconds that we're not receiving a location which is more accurate of the our local location, and so we can stop the location services:
else if distance < 1 {
let timerInterval = newLocation.timestamp.timeIntervalSinceDate(self.lastLocation!.timestamp)
if timerInterval >= TIMEOUT_INTERVAL {
//Force Stop
println("Stop location timeout")
stopLocationManager()
}
}
This is always a problem with satellite locations. It is an estimate and estimates can vary. Each new report is a new estimate. What you need is a position clamp that ignores values when there is no movement.
You might try to use sensors to know if the device is actually moving. Look at accelerometer data, if it isn't changing then the device isn't moving even though GPS says it is. Of course, there is noise on the accelerometer data so you have to filter that out also.
This is a tricky problem to solve.
There's really not a whole lot more you can do to improve what the operating system and your current reception gives to you. On first look it doesn't look like there's anything wrong with your code - when it comes to iOS location updates you're really at the mercy of the OS and your service.
What you CAN do is control what locations you pay attention to. If I were you in my didUpdateLocations function when you get callbacks from the OS with new locations - you could ignore any locations with horizontal accuracies greater than some predefined threshold, maybe 25m? You would end up with less location updates to use but you'd have less noise.

Resources