Cumulative distance travelled SwiftUI - ios

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.

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.

Find distance of location to route in Google Maps SDK

I´m developing an iPhone app, and I need some help with this case:
I need to check, if user leave google maps route (GMSPolyline) and if distance from user location to nearest point of route is more than 40 meters -- I need to rebuild route.
I can't find the right algorithm to detect if distance from user to route is more than 40 meters.
I've tried to use this method to find projection of user location (converted to CGPoint by CGPointMake) on route :
+ (CGPoint)projectionOfPoint:(CGPoint)origPoint toSegmentP1:(CGPoint)p1 p2:(CGPoint)p2 {
// for case line is parallel to x axis
if (p2.y == p1.y) {
return CGPointMake(origPoint.x, p1.y);
// for case line is parallel to y axis
} else if (p2.x == p1.x) {
return CGPointMake(p1.x, origPoint.y);
}
// line from segment
CGFloat kKoefLine1 = (p2.x - p1.x)/(p2.y - p1.y);
CGFloat bKoefLine1 = p1.y - kKoefLine1*p1.x;
// perpendicular line
CGFloat kKoefLine2 = -1/kKoefLine1;
CGFloat bKoefLine2 = origPoint.y - kKoefLine2*origPoint.x;
// cross point
CGFloat krossX = (bKoefLine2 - bKoefLine1)/(kKoefLine1 - kKoefLine2);
CGFloat krossY = kKoefLine2*krossX + bKoefLine2;
return CGPointMake(krossX, krossY);}
Then I calculate distance from returned projection (converted to CLLocation) and user location, but it doesn't works.
P.S.: I will be thankful if solution would be written on swift.
There is a GMSGeometryIsLocationOnPath function in the GMSGeometryUtils module in the Google Maps SDK.
You should be able to use that to calculate what you need.
Pseudocode (not tested):
let currentLocation: CLLocationCoordinate2D = ...
let routePath: GMSPath = routePolyline.path
let geodesic = true
let tolerance: CLLocationDistance = 40
let within40Meters = GMSGeometryIsLocationOnPath(currentLocation, routePath, geodesic, tolerance)
for swift 5.0 and based on #Arthur answer I wrote follwoing function
func isInRoute(posLL: CLLocationCoordinate2D, path: GMSPath) -> Bool
{
let geodesic = true
let tolerance: CLLocationDistance = 40
let within40Meters = GMSGeometryIsLocationOnPathTolerance(posLL, path, geodesic, tolerance)
return within40Meters
}
While I don't recall much about the GMS SDK off the top of my head, before I give you an answer, I will say that nobody on here will write your code for you. That's your job and should be done on your time. You haven't given any background as to how far you've gotten in terms of calculating routes, whether or not you've figured out how to calculate distance at all, etc.
With that being said, routes on Google Maps are comprised of "legs", which denote a path to take before a turn is made in efforts to reach the end destination. By querying your "route" dictionary, you can extract an array of dictionaries where each element (which is a dictionary) contains metadata about a "leg". You can then loop through that array, go through each dictionary and extract the "distance" value, and sum them to a single "distance" var.
You can recalculate this as often as needed and use a conditional to check whether or not the leg distance sum is < 40M, else rebuild.
link to an article that should help (I didn't have the time to go through the entire thing for you, so do your due diligence and research) here.

Ordering Geofire results on Distance

I've been experimenting with Geofire for iOS but can't seem to find any way of returning the distance from the search position in a circle query. The GFQueryResultBlock only returns the key and position. Am I right in assuming that I have to calculate the distance myself?
Let's say I am making a nearby restaurant app with Firebase, and want to display the 20 closest restaurants to the user, ordered by how close they are. I could create a circle query and then increase the search radius via a loop until I find 20 restaurants. Then calculate the distance for each one and sort them before displaying them to the user. Is this a reasonable approach, given that a large amount of work is being done in the app to structure the data (calculating distance & sorting)?
I've noticed that javascript Geofire queries return distance from the center, but I guess the iOS and android versions are different from this.
When you are querying from Geofire, relevant results are automatically ordered by ascending order. So in order to get the distance , Im just using the distanceFromLocation function:
Here my code:
func getGeoFirePlaces(){
let geofireRef = FIRDatabase.database().reference().child("testForGeofire")
let geoFire = GeoFire(firebaseRef: geofireRef)
//geoFireRef is pointing to a firebase reference where I previously set all places' location
let userPosition = CLLocation(latitude: 32.0776067, longitude: 34.78912)
let circleQuery = geoFire.queryAtLocation(userPosition, withRadius: 2)
circleQuery.observeEventType(.KeyEntered, withBlock: { (key: String!, location: CLLocation!) in
print("Key '\(key)' entered the search area and is at location '\(location)'")
//getting distance of each Place return with the callBack
let distanceFromUser = userPosition.distanceFromLocation(location)
print(distanceFromUser)
})
}
Hope this help!

Adding CLLocationDistances

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.

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.

Resources