Measure distance between two location inaccurate - ios

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.

Related

Cumulative distance travelled SwiftUI

There are a few Q/A's on this topic e.g. here and here but I'm trying to adapt these in Swift UI to calculate an array of distances between consecutive points during a run. The idea being that eventually I can get a list of 'distance travelled every 0.2 miles' or similar.
But what I want first is... distance between location 1 and location 2, location 2 and location 3, location 3 and location 4 etc - to calculate total distance travelled on a run.
To do this, I'm trying this code :
func locationManager(_ manager: CLLocationManager, didUpdateLocations locations: [CLLocation]) {
lastSeenLocation = locations.last
fetchCountryAndCity(for: locations.last)
print (locations)
This works fine and prints an updating list of locations to simulator but then the below is meant to get each location that is added, use that as the start point and the next one as the end point and for now simply print each distance to console.
Then calculate the distance between them using the getDistance function below.
This doesn't work with error "Type of expression is ambiguous without more context" on the let distance = ... line.
var total: Double = 00.00
for i in 0..<locations.count - 1 {
let start = locations[i]
let end = locations[i + 1]
let distance = getDistance(from: start,to: end)
total += distance
}
print (total)
This is my function for getting the actual distance between 2 points, which I've taken from one of the other questions/answers posted above.
func getDistance(from: CLLocationCoordinate2D, to: CLLocationCoordinate2D) -> CLLocationDistance {
let from = CLLocation(latitude: from.latitude, longitude: from.longitude)
let to = CLLocation(latitude: to.latitude, longitude: to.longitude)
return from.distance(from: to)
}
Help greatly appreciated! I have tried to format my question and code carefully after some feedback from another question but please tell me if I'm doing something wrong or can make it easier!
The reason you are getting the error about an ambiguous expression is because the arguments you are passing doesn't match the type of the arguments in your function. locations[i]is a CLLocation while you function wants CLLocationCoordinate2D.
Your function then creates CLLocations anyway, so you can just fix the parameter types for your function.
You have a bigger problem, however. You are relying on the locations array that is passed to the delegate function. Although this may contain multiple locations in the case where location updates were deferred for some reason, in practice it will only contain a single location update.
You will need to create your own locations array to keep the history for calculation purposes.

Maintain correct SCNNode position in ARKit while walking, without calling run and .resetTracking on each CLLocation update

I'm building a simple navigation app with ARKit. The app shows a pin at a destination, which can be far away or nearby. The user is able to walk toward the pin to navigate.
In my ARSCNView I have an SCNNode called waypointNode, which represents the pin at the destination.
To determine where to place the waypointNode, I calculate the distance to the destination in meters, and the bearing (degrees away from North) to the destination. Then, I create and multiply some transformations and apply them to the node to put it in the proper position.
There's also some logic establish a maximum distance away for the waypointNode so it's not too small for the user to see.
This is how I configure the ARSCNView, so the axes line up with the real-world compass directions:
func setUpSceneView() {
let configuration = ARWorldTrackingConfiguration()
configuration.worldAlignment = .gravityAndHeading
configuration.planeDetection = .horizontal
session.run(configuration, options: [.resetTracking])
}
Every time the device gets a new CLLocation from CoreLocation, I update the distance and bearing then call this function to update the position of the waypointNode:
func updateWaypointNode() {
// limit the max distance so the node doesn't become invisible
let distanceLimit: Float = 80
let translationDistance: Float
if navigationInfo.distance > distanceLimit {
translationDistance = distanceLimit
} else {
translationDistance = navigationInfo.distance
}
// transform matrix to adjust node distance
let distanceTranslation = SCNMatrix4MakeTranslation(0, 0, -translationDistance)
// transform matrix to rotate node around y-axis
let rotation = SCNMatrix4MakeRotation(-1 * GLKMathDegreesToRadians(Float(navigationInfo.bearing)), 0, 1, 0)
// multiply the rotation and distance translation matrices
let distanceTimesRotation = SCNMatrix4Mult(distanceTranslation, rotation)
// grab the current camera transform
guard let cameraTransform = session.currentFrame?.camera.transform else { return }
// multiply the rotation and distance translation transform by the camera transform
let finalTransform = SCNMatrix4Mult(SCNMatrix4(cameraTransform), distanceTimesRotation)
// update the waypoint node with this updated transform
waypointNode.transform = finalTransform
}
This works fine when the user first starts the session, and when the user moves less than about 100m.
Once the user covers a significant distance, like over 100m walking or driving, just calling updateWaypointNode() is not enough to maintain the proper position of the node at the destination. When walking toward the node, for example, it's possible for the user to eventually reach the node, even though the user has not reached the destination. Note: This incorrect positioning happens while the session open the whole time, not if the session is interrupted.
As a workaround, I'm also calling setUpSceneView() every time the device gets a location update.
Even though this works OK, it feels wrong to me. It doesn't seem like I should have to call run with the .resetTracking option every time. I think I might just be overlooking something in my translations. I also see some jumpiness in the camera that seems to happen every time run is called when the session is running, so that's less desirable than simply updating translations.
Is there something different I could do to avoid calling run on the session and resetting the tracking every time the device gets a location update?

Calculating Distance, converting to km and then cutting out decimal places

Edit 3: Thank you beyowulf, I implemented your line of code and this is the result! Exactly what I was hoping for. Thank you for all the suggestions.
Edit 2: Interesting, as per user27388882 suggestion I changed my code to:
//convert to kilometers
let kilometers = Double(round(traveledDistance) / 1000)
The result is three decimal places. Ideally I would like only two at most, but this is a step in the right direction! Thank you!
Edit 1: for clarification of "does not work": I guess I can't post a picture, but here is a link when viewed in the simulator. The distance still appears as a super long string of decimals, despite using code to try and shorten the number of decimal places. What I perceive to not be working in my code is where the decimal places should be cut off.
I am essentially creating an app that tracks a users location while riding their bicycle. One feature is to take the distance travelled by the user and display it in KM. I have gotten the distance function to work by searching through other posts. I have also looked at NSNumberFormatter help documents, but implementing code I have seen does not work. Is this an issue of distance being a double which is calculated from CLLocation? Another piece of potentially relevant information is that I am working in Xcode 7.2 and Swift2.
I don't want to post my whole code since I want to highlight where I am stuck, but not sure if more of my code is needed to solve this.
import UIKit
import CoreLocation
// Global variables
var startLocation:CLLocation!
var lastLocation: CLLocation!
var traveledDistance:Double = 0
// Identify Labels
#IBOutlet weak var distanceLabel: UILabel!
// Create a location manager
func locationManager(manager: CLLocationManager, didUpdateLocations locations: [CLLocation]) {
//Calculate the distance between points as a total distance traveled...
if startLocation == nil {
startLocation = locations.first! as CLLocation
} else {
let lastLocation = locations.last! as CLLocation
let distance = startLocation.distanceFromLocation(lastLocation)
startLocation = lastLocation
traveledDistance += distance
//convert to kilometers
let kilometers = traveledDistance / 1000
//Convert to only two decimal places
let nf = NSNumberFormatter()
nf.minimumSignificantDigits = 1
nf.maximumFractionDigits = 2
nf.numberStyle = .DecimalStyle
nf.stringFromNumber(kilometers)
//Update the distance label
self.distanceLabel.text = "\(kilometers) kilometers"
Help me, stackOverFlow. You're my only hope.
tl;dr round out decimal places from distance value calculated from user location using swift2.
Shouldn't wait to round until you are ready to display the results? You can say something like:
let formatedString = String(format:"%.2f",Float(traveledDistance / 1000.0 + .005))
To get traveledDistance rounded to the neared hundredth of a kilometer.

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