I'm trying to add CLLocationDistances and getting strange results. Here is my code:
var locations = [CLLocation]()
var totalDistance = CLLocationDistance()
func locationManager(manager: CLLocationManager, didUpdateLocations locations: [CLLocation]) {
self.locations.append(locations.first!)
if self.locations.count > 1 {
self.calculateTotalDistance()
}
}
func calculateTotalDistance() -> CLLocationDistance {
var y = 0
self.totalDistance = 0
while y < (locations.count - 1) {
let firstLocation = self.locations[y]
let secondLocation = self.locations[y + 1]
let distance = firstLocation.distanceFromLocation(secondLocation)
print(distance)
totalDistance += distance
y++
}
self.updateLabelWithText()
return totalDistance
}
private func updateLabelWithText() {
let distance = String(self.totalDistance)
let message = "Distance in meters: " + distance
self.distanceTextLabel.text = message
}
Essentially, every time I get a new CLLocation object from the system, I append it to my array. Then I iterate through the array, and get the distance between each individual points, and then add them together. I am testing the code on an actual device. As of now, when I run this code, even when I just sit down, the totalDistance variable reaches a count of 100 or so in 10 seconds, despite me not having moved anywhere close to 100 meters.
Also, in the calculateTotalDistance function, I print the distance calculated, and the distances don't seem right at all. Here is an example of what was printed to the console after the app launched for a few seconds:
0.0
1.15645918113224
1.06166528806247
1.06006756503664
1.05847219105153
16.1407724137949
9.67662215264722
0.0
1.15645918113224
1.06166528806247
1.06006756503664
1.05847219105153
16.1407724137949
9.6766221526472
Again, these are values just from when I'm sitting down, so I'm obviously not moving 16 meters, or 9 meters at a time.
Any idea what i'm doing wrong here?
thanks
This is because of accuracy of GPS. Check horizontalAccuracy of CLLocation before adding it to array.
You should also set desiredAccuracy of CLLocationManger to kCLLocationAccuracyBest.
Related
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.
I have problem. My GPS (on iPad mini 2 in Wifi and on iPhone 6 in 3G/4G) the speed return -1.0. Have an idea?
This what i receive in console log:
Long: 12.5245, Lat: 41.9456, Speed:-1.0, kph: -3.6
Here the code in didUpdateLocations()
let userLocation: CLLocation = locations[0]
var speed: CLLocationSpeed = CLLocationSpeed()
speed = (locationManager.location?.speed)!
SpeedLabel.text = String(format: "%.0f km/h", speed * 3.6)
let long = String(Float(userLocation.coordinate.longitude))
let lat = String(Float(userLocation.coordinate.latitude))
print("Long: \(long), Lat: \(lat), Speed:\(speed), kph: \(speed * 3.6) ")
I had this problem too. A negative value means an invalid speed.
This is most of the time occured when you're inside a building and your location is moving a lot due to the building.
A simple fix would be:
if speed < 0 {
speed = 0
}
This checks if the speed is negative. If it is, it'll reset it to 0.
I have a UIImageView representing a simple knob: it has nothing to do with stereo hi-fi, it represents a sort of compass:
My goal is the following: I want the compass to point a precise direction, starting from the current position of the device (and it must update too: if the user turns the device elsewhere, the knob should rotate accordingly).
What I tried until now:
I have this function:
func getRadiansBearing()->Double{
// body of degreesToRadians: return degrees * M_PI / 180.0
// data[1]._lat and _lon contains my current position
let lat1 = degreesToRadians(data[1]._lat)
let lon1 = degreesToRadians(data[1]._lon)
// data[0]._lat and _lon contains the target position
let lat2 = degreesToRadians(data[0]._lat)
let lon2 = degreesToRadians(data[0]._lon)
let dLon = lon2 - lon1;
let y = sin(dLon)*cos(lat2)
let x = cos(lat1)*sin(lat2)-sin(lat1)*cos(lat2)*cos(dLon)
var radiansBearing = atan2(y, x);
if(radiansBearing < 0.0)
{
radiansBearing = radiansBearing+2*M_PI
}
return radiansBearing
}
and I call it this way, using the CoreLocation framework:
func locationManager(manager: CLLocationManager, didUpdateLocations locations: [CLLocation]){
guard let location = locations.last else {
return;
}
// data is an array of structs
data[1]._lat = location.coordinate.latitude
data[1]._lon = location.coordinate.longitude
geoAngle = getRadiansBearing()
}
and:
func locationManager(manager: CLLocationManager, didUpdateHeading newHeading: CLHeading) {
let direction = -newHeading.trueHeading
let dir2 = degreesToRadians(direction)
UIView.animateWithDuration(2.0, animations: {
self.imgView.transform = CGAffineTransformMakeRotation((CGFloat(dir2) * CGFloat(M_PI) / 180) + CGFloat(self.geoAngle))
})
}
Problem is, for some reason the knob turns once into one (wrong) direction, then stops working.
Can you help me? Any tip is appreciated! :-)
P.S.: I red many related posts, none solves this issue
Consider latitude as x, and longitude as y for now.
Pre-requisites:
Destination location must be known.
Current location must be known.
I would show you how to calculate the angle towards the direction now. It is as follows!
Get the current location and destination location's latitude and longitude.
Pseudo Code:
Let, currentLocation = ( lat, long )
Let, destinationLocation = ( lat, long )
angleTowardsDestination = ?
Calculate the base and hypotenuse for Pythagorus theorem.
Now,
base = √((currentLocation.lat - destinationLocation.lat)2 - (currentLocation.long - currentLocation.long)2)
hypotenuse = √((currentLocation.lat - destinationLocation.lat)2 - (currentLocation.long - destinationLocation.long)2)
Calculate the angle now!
angleTowardsDestination = Cos-1( base / hypotenuse )
And, that's the angle you need in degrees. Feel free to convert it to radian if needed.
Kind Regards,
Suman Adhikari
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.
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.