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

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.

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.

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.

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.

Displaying MKAnnotationViews that are within the bounds of the visible mapview

I'm querying my server for a list of locations that are closest to the user based on his observable portion of the mapview.
I'm not entirely sure on how to do this, but should I send the center lat and long of the mapview's visible region and then search for results based on nearest radius?
I know that MKMapView has two properties called region and visibleMapRect. Should I use one of those, and if so, which one is more appropriate based on my question?
EDIT
I'm looking to implement functionality that's identical to the apple maps and Yelp app when you search for nearby locations and it shows you what's relevant based off the visible portion of the map view.
EDIT 2
I have seen a lot of people break up the visible portion of their mapview into quadrants, most commonly NW,NE,SW and SE. However, I'm still not entirely sure why theyr'e doing this. I would like to know the best way to query the back end, which contains a lat and long for each location, to find the locations which exist on the mapview.
I have looked everywhere on stackoverflow and I found a similar question here. However, it doesn't answer my question and there's no mention of visibleMapRect because it's over 5 years old.
Any help is tremendously appreciated.
So you would need to provide your server call with these 2 coord bottomleft and topright 4 points all together.
if lat of location > bottomleft.lat and if lat of location < topright.lat
if long of location > bottomleft.long and if long of location < topright.long
iOS Code would look like this the make server call with these four variables as parameters
fun viewDidLoad() {
super.viewDidLoad()
self.mapView.delegate = self
}
func mapView(mapView: MKMapView, regionDidChangeAnimated animated: Bool) {
// Using region
var span: MKCoordinateSpan = mapView.region.span
var center: CLLocationCoordinate2D = mapView.region.center
// This is the farthest Lat point to the left
var farthestLeft = center.latitude - span.latitudeDelta * 0.5
// This is the farthest Lat point to the Right
var farthestRight = center.latitude + span.latitudeDelta * 0.5
// This is the farthest Long point in the Upward direction
var farthestTop = center.longitude - span.longitudeDelta * 0.5
// This is the farthest Long point in the Downward direction
var farthestBottom = center.longitude + span.longitudeDelta * 0.5
var SWCoord = MKCoordinateForMapPoint(farthestBottom, farthestLeft)
var NECoord = MKCoordinateForMapPoint(farthestTop, farthestRight)
// Using visibleMapRect
var mapRect = mapView.visibleMapRect
// This is the top right Coordinate
var NECoord = getCoordinateFromMapRectanglePoint(MKMapRectGetMaxX(mapRect), y: mapRect.origin.y)
// This is the bottom left Coordinate
var SWCoord = getCoordinateFromMapRectanglePoint(mapRect.origin.x, y: MKMapRectGetMaxY(mapRect))
// Not needed but could be useful
// var NWCoord = getCoordinateFromMapRectanglePoint(MKMapRectGetMinX(mapRect), y: mapRect.origin.y)
// var SECoord = getCoordinateFromMapRectanglePoint(MKMapRectGetMaxX(mapRect), y: MKMapRectGetMaxY(mapRect))
}
func getCoordinateFromMapRectanglePoint(x: Double, y: Double) -> CLLocationCoordinate2D {
var mapPoint = MKMapPointMake(x, y)
return MKCoordinateForMapPoint(mapPoint)
}
If you have anymore questions just comment
EDIT 2 I have seen a lot of people break up the visible portion of their mapview into quadrants, most commonly NW,NE,SW and SE.
However, I'm still not entirely sure why theyr'e doing this. I would
like to know the best way to query the back end, which contains a lat
and long for each location, to find the locations which exist on the
mapview.
I'm not sure if this is what you're referring to but they could be using quadtrees as a way to efficiently search the locations of a huge amount of coordinates.
This post from Thoughtbot demonstrates how to use quadtrees to display thousands of place coordinates as clusters on the map and it includes gifs that very succinctly explain how it works:
A quad tree is a data structure comprising nodes which store a bucket of points and a bounding box. Any point which is contained within the node’s bounding box is added to its bucket. Once the bucket gets filled up, the node splits itself into four nodes, each with a bounding box corresponding to a quadrant of its parents bounding box. All points which would have gone into the parent’s bucket now go into one of its children’s buckets.
Source: How To Efficiently Display Large Amounts of Data on iOS Maps

Resources