swift calculate distance between two locations - swift

I would like to get the distance between two locations.
I tried this code:
func mapView(_ mapView: MKMapView, didUpdate userLocation: MKUserLocation) {
CLGeocoder().geocodeAddressString("ADDRESS OF LOC 2") { (placemarks, error) in
guard let placemarks = placemarks, let loc2 = placemarks.first?.location
else {
return
}
let loc1 = CLLocation(latitude: userLocation.coordinate.latitude, longitude: userLocation.coordinate.longitude)
var distance = loc1.distance(from: loc2)
print((distance/1000).rounded(), " km")
}
}
My problem is that i get a wrong distance.
The print result will be "2 km"
If i calculate the distance between the same locations in "Maps" I get two route options with the length "2,7 km" and "2,9 km"
What did i wrong?

From the documentation:
This method measures the distance between the two locations by tracing a line between them that follows the curvature of the Earth. The resulting arc is a smooth curve and does not take into account specific altitude changes between the two locations.
So you're calculating straight line distance, "as the crow flies", not following roads, which would lead to a longer route, as you're seeing in Maps. You'd need something like MapKit's MKDirectionsRequest to match the route you see in the Maps app. Ray Wenderlich has a good tutorial.
Here's an example I just knocked up that works in a macOS Playground:
//: Playground - noun: a place where people can play
import Cocoa
import MapKit
import CoreLocation
// As we're waiting for completion handlers, don't want the playground
// to die on us just because we reach the end of the playground.
// See https://stackoverflow.com/questions/40269573/xcode-error-domain-dvtplaygroundcommunicationerrordomain-code-1
import PlaygroundSupport
PlaygroundPage.current.needsIndefiniteExecution = true
let geocoder = CLGeocoder()
geocoder.geocodeAddressString("1 Pall Mall East, London SW1Y 5AU") { (placemarks: [CLPlacemark]? , error: Error?) in
if let placemarks = placemarks {
let start_placemark = placemarks[0]
geocoder.geocodeAddressString("Buckingham Palace, London SW1A 1AA", completionHandler: { ( placemarks: [CLPlacemark]?, error: Error?) in
if let placemarks = placemarks {
let end_placemark = placemarks[0]
// Okay, we've geocoded two addresses as start_placemark and end_placemark.
let start = MKMapItem(placemark: MKPlacemark(coordinate: start_placemark.location!.coordinate))
let end = MKMapItem(placemark: MKPlacemark(coordinate: end_placemark.location!.coordinate))
// Now we've got start and end MKMapItems for MapKit, based on the placemarks. Build a request for
// a route by car.
let request: MKDirectionsRequest = MKDirectionsRequest()
request.source = start
request.destination = end
request.transportType = MKDirectionsTransportType.automobile
// Execute the request on an MKDirections object
let directions = MKDirections(request: request)
directions.calculate(completionHandler: { (response: MKDirectionsResponse?, error: Error?) in
// Now we should have a route.
if let routes = response?.routes {
let route = routes[0]
print(route.distance) // 2,307 metres.
}
})
}
})
}
}

If you want travel distance rather than "as the crow flies", use MKDirections, e.g.:
func routes(to item: MKMapItem, completion: #escaping ([MKRoute]?, Error?) -> Void) {
let request = MKDirections.Request()
request.source = MKMapItem.forCurrentLocation()
request.destination = item
request.transportType = .automobile
let directions = MKDirections(request: request)
directions.calculate { response, error in
completion(response?.routes, error)
}
}
And if you want to format to one decimal place, I'd suggest using NumberFormatter, so it's appropriately formatted for international users for whom the decimal separator is not .:
self.routes(to: item) { routes, error in
guard let routes = routes, error == nil else {
print(error?.localizedDescription ?? "Unknown error")
return
}
let formatter = NumberFormatter()
formatter.numberStyle = .decimal
formatter.minimumFractionDigits = 1
formatter.maximumFractionDigits = 1
for route in routes {
let distance = route.distance / 1000
print(formatter.string(from: NSNumber(value: distance))!, "km")
}
}

Using MapKit & Swift 5
Calculate distance between two location location, It will help anyone
Sample Function : I have tested in Google Map as well as Apple Map
let startLocation : CLLocation = CLLocation.init(latitude: 23.0952779, longitude: 72.5274129)
let endLocation : CLLocation = CLLocation.init(latitude: 23.0981711, longitude: 72.5294229)
let distance = startLocation.distance(from: endLocation)
self.getDistance(departureDate: Date().adjust(hour: 8, minute: 0, second: 0, day: 0, month: 0), arrivalDate: Date().adjust(hour: 8, minute: 10, second: 0, day: 0, month: 0), startLocation: startLocation, endLocation: endLocation) { (distanceInMeters) in
print("fake distance: \(distance)")
let fakedistanceInMeter = Measurement(value: distance, unit: UnitLength.meters)
let fakedistanceInKM = fakedistanceInMeter.converted(to: UnitLength.kilometers).value
let fakedistanceInMiles = fakedistanceInMeter.converted(to: UnitLength.miles).value
print("fakedistanceInKM :\(fakedistanceInKM)")
print("fakedistanceInMiles :\(fakedistanceInMiles)")
print("actualDistance : \(distanceInMeters)")
let distanceInMeter = Measurement(value: distanceInMeters, unit: UnitLength.meters)
let distanceInKM = distanceInMeter.converted(to: UnitLength.kilometers).value
let distanceInMiles = distanceInMeter.converted(to: UnitLength.miles).value
print("distanceInKM :\(distanceInKM)")
print("distanceInMiles :\(distanceInMiles)")
}
Use of functions
self.getDistance(departureDate: trip.departure.dateTime, arrivalDate: trip.arrival.dateTime, startLocation: startLocation, endLocation: endLocation) { (actualDistance) in
print("actualDistance : \(actualDistance)")
}
I am improved above function and added code here, I hope it will help someone.
func calculateDistancefrom(departureDate: Date, arrivalDate: Date, sourceLocation: MKMapItem, destinationLocation: MKMapItem, doneSearching: #escaping (_ distance: CLLocationDistance) -> Void) {
let request: MKDirections.Request = MKDirections.Request()
request.departureDate = departureDate
request.arrivalDate = arrivalDate
request.source = sourceLocation
request.destination = destinationLocation
request.requestsAlternateRoutes = true
request.transportType = .automobile
let directions = MKDirections(request: request)
directions.calculate { (directions, error) in
if var routeResponse = directions?.routes {
routeResponse.sort(by: {$0.expectedTravelTime <
$1.expectedTravelTime})
let quickestRouteForSegment: MKRoute = routeResponse[0]
doneSearching(quickestRouteForSegment.distance)
}
}
}
func getDistance(departureDate: Date, arrivalDate: Date, startLocation : CLLocation, endLocation : CLLocation, completionHandler: #escaping (_ distance: CLLocationDistance) -> Void) {
let destinationItem = MKMapItem(placemark: MKPlacemark(coordinate: startLocation.coordinate))
let sourceItem = MKMapItem(placemark: MKPlacemark(coordinate: endLocation.coordinate))
self.calculateDistancefrom(departureDate: departureDate, arrivalDate: arrivalDate, sourceLocation: sourceItem, destinationLocation: destinationItem, doneSearching: { distance in
completionHandler(distance)
})
}

Related

Get Distance between two points Swift 5

I'm trying to get the distance between tow points, current location, and another point.
All the data is calculate great, but I can't get the value to asign it to the object, and then create the table.
The distance is created later than the object even I hace make an async queue.
I can't find the problem maybe I'm too new in this.
Thanks for the help
DispatchQueue.global(qos: .utility).async {
for snap in snapshot.children {
let postSnap = snap as! DataSnapshot
if let dict = postSnap.value as? [String:AnyObject] {
let farcoordenadas = dict["coordenadas"] as! [Double]
let lat = (farcoordenadas[0] as! Double)
let long = (farcoordenadas[1] as! Double)
let location = CLLocationCoordinate2D(latitude: lat, longitude: long)
let farcalle = (dict["calle"] as! String)
let fartelefono = (dict["telefono"] as! String)
let farpoblacion = (dict["poblacion"] as! String)
let farhorario = (dict["horario"] as! String)
let request = MKDirections.Request()
request.source = MKMapItem(placemark: MKPlacemark(coordinate:self.userLocation!))
request.destination = MKMapItem(placemark: MKPlacemark(coordinate: location))
request.transportType = .automobile
let directions = MKDirections(request: request)
directions.calculate { (response, error) -> Void in
if let response = response, let route = response.routes.first {
print(route.distance)
//converts from meters to miles
var routeDistance = route.distance/1000
//formats the string to two decimals.
distancia = String(format: "%.0f" , routeDistance)
}
return
}
DispatchQueue.main.async {
let farmaciapueblo = Farmacia(fecha:dateString, horario:farhorario, poblacion: farpoblacion, telefono:fartelefono, calle:farcalle, coordenadas:farcoordenadas, distancia:distancia)
self.farmaciasrural.append(farmaciapueblo)
self.farmaciasrural.sort(by: myLocation) // mutating version
self.activityIndicator.stopAnimating()
self.tableView.reloadData()
}
}
}
}

Distance from Current Location to Annotation. (Firebase)

I want to have a the distance from my Currentlocation to a annotation that's in the FireData Base. I tried to make it word but i can't ;(. I would like to have the distance between the two locations in a var. I hope you guys can help me.
func reload(){
//get data
Database.database().reference().child("Rollerbanken").observe(.value, with: { (snapshot) in
for item in snapshot.children{
if let value = snapshot.value as? Dictionary<String, Any> {
for key in value.keys {
if let itemDict = value[key] as? Dictionary<String, AnyObject> {
let annotation = MKPointAnnotation()
annotation.title = itemDict["TypeControle"] as! String
let tijd = itemDict["Tijd"] as! String
annotation.subtitle = "Geplaatst om \(tijd)"
let getLatitude = itemDict["Latitude"] as? String
let getLongitude = itemDict["Longitude"] as? String
if let lat = getLatitude, let long = getLongitude {
annotation.coordinate = CLLocationCoordinate2D(latitude: Double(lat)!, longitude: Double(long)!)
self.map.addAnnotation(annotation)
let directionRequest = MKDirectionsRequest()
directionRequest.source = MKMapItem.forCurrentLocation()
if #available(iOS 10.0, *) {
directionRequest.destination = MKMapItem(placemark: MKPlacemark.init(coordinate: CLLocationCoordinate2DMake(Double(lat)!, Double(long)!)))
} else {
// Fallback on earlier versions
}
directionRequest.transportType = .walking
let direction = MKDirections(request: directionRequest)
direction.calculate(completionHandler: { (response, error) in
if error != nil {
print("Error while build route")
} else {
let route = response?.routes.last
let distance = route?.distance
print(distance)
}
})
}
}
}
}
}
})
}
Here is my Structure:
Try to use this code. Don't forget to enable your current location on map
let directionRequest = MKDirectionsRequest()
directionRequest.source = MKMapItem.forCurrentLocation()
directionRequest.destination = MKMapItem(placemark: MKPlacemark.init(coordinate: CLLocationCoordinate2DMake(YOURPOINTLATITUDE, YOURPOINTLONGITUDE)))
directionRequest.transportType = .walking
let direction = MKDirections(request: directionRequest)
direction.calculate(completionHandler: { (response, error) in
if error != nil {
print("Error while build route")
} else {
let route = response?.routes.last
let distance = route?.distance
I have used similar function, NOTE this was my function therefore it has rider and driver.. however you can change it to use annotation and location from firebase.
if let rideRequestDictionary = snapshot.value as? [String:AnyObject] {
// Getting the rider location and email
if let email = rideRequestDictionary["email"] as? String {
if let lat = rideRequestDictionary["lat"] as? Double{
if let lon = rideRequestDictionary["lon"] as? Double{
// Getting the Driver location and email
let driverCLLocation = CLLocation(latitude: driverLocation.latitude, longitude: driverLocation.longitude)
let riderCLLocation = CLLocation(latitude: lat, longitude: lon)
// getting the distance between the two people
let distance = driverCLLocation.distance(from: riderCLLocation) / 1000
// rounding the distances
let roundedDistance = round(distance * 100) / 100
// putting the rounded distance and email in label
cell.textLabel?.text = "\(email) - \(roundedDistance)km away"
}
}
}

Calculate each distance from user to several points by route in Swift

I'm a newbie in swift and I'm trying to calculate the distance, on route, from userLocation to several Point of interest.
I don’t want, in this part of the app, “draw” the route on a map, but I only want to calculate the distance on route instead the distance between two coordinate and then show this distance as information inside the callout on the map.
The coordinate of the POI (latitude and longitude) are contained in a database.
I have read some threads about this argument here on Stack Overflow:
Measuring distance in meters from a drawn route in MKMapView
MKMapView get distance between coordinates on customized route
and other tutorials:
http://www.technetexperts.com/mobile/draw-route-between-2-points-on-map-with-ios7-mapkit-api/
https://videos.raywenderlich.com/courses/mapkit-and-core-location/lessons/9
then i wrote this code:
for item in items {
if item.latitudine != "" && item.longitudine != "" {
// Point Of Interest coordinate
let latitude = Double(item.latitude!)
let longitude = Double(item.longitude!)
let itemLocation = CLLocation(latitude: latitude!, longitude: longitude!)
let itemLocationPlacemark = MKPlacemark(coordinate: itemLocation.coordinate, addressDictionary: nil)
// user coordinate
let userLocation = CLLocation(latitude: userLocation.coordinate.latitude, longitude: userLocation.coordinate.longitude)
let userLocationPlacemark = MKPlacemark(coordinate: userLocation.coordinate, addressDictionary: nil)
// create Request object
let request = MKDirectionsRequest()
request.source = MKMapItem(placemark: userLocationPlacemark)
request.destination = MKMapItem(placemark: itemLocationPlacemark)
request.requestsAlternateRoutes = false
request.transportType = .automobile
let directions = MKDirections(request: request)
directions.calculate {
[weak self] (response, error) in
if error == nil {
for route in (response?.routes)! {
let distance = (route.distance)/1000
print(distance)
}
}
}
}
}
The problem is when I execute the code from the line directions.calculate.
The program run the line but then don’t execute the rest, don’t execute the control if error == nil and the instructions in the closure.
So now I wonder if my idea is wrong and, if not, how can obtain my goal.
(Posted solution on behalf of the OP).
Reading other threads I understand that the problem was the closure inside che for loop. So after several attempts I found the solution that work for me. I write it here so that can be useful to someone else:
var counter: Int!
...
for item in itemPin {
if item.latitude != "" && item.longitude != "" {
....
....
let directions = MKDirections(request: request)
directions.calculate {
(response, error) in
print("error \(error)")
if error == nil {
counter = self.counter + 1
print(self.counter)
if self.counter >= self.itemPin.count {
let result = (response?.routes[0].distance)!/1000
}
}
}
...
}
}

How to find the closest location from current location in the array locations

I want to find the closest ATM from my current location, then will draw a route on a map, here what I have tried:
func findTheClosestATM() {
var distances = [Double]()
for atm in atms {
let distance = currentLocation.distanceFromLocation(CLLocation(latitude: CLLocationDegrees(atm.latitude!), longitude: CLLocationDegrees(atm.latitude!)))
distances.append(distance)
}
var closestATM = atms[0]
closestATM = atms[distances.indexOf(distances.minElement()!)!]
drawRouteOnMap(sourceLocation: currentLocation, destinationLocation: CLLocation(latitude: CLLocationDegrees(closestATM.latitude!), longitude: CLLocationDegrees(closestATM.latitude!)))
}
func drawRouteOnMap(sourceLocation sourceLocation: CLLocation, destinationLocation: CLLocation) {
mapView.removeOverlays(mapView.overlays)
drawMapActivated = true
let sourcePlacemark = MKPlacemark(coordinate: sourceLocation.coordinate, addressDictionary: nil)
let destinationPlacemark = MKPlacemark(coordinate: destinationLocation.coordinate, addressDictionary: nil)
let sourceMapItem = MKMapItem(placemark: sourcePlacemark)
let destinationMapItem = MKMapItem(placemark: destinationPlacemark)
let destinationAnnotation = MKPointAnnotation()
destinationAnnotation.title = "ATM"
if let location = destinationPlacemark.location {
destinationAnnotation.coordinate = location.coordinate
}
self.mapView.showAnnotations([destinationAnnotation], animated: true)
let directionRequest = MKDirectionsRequest()
directionRequest.source = sourceMapItem
directionRequest.destination = destinationMapItem
directionRequest.transportType = .Automobile
let directions = MKDirections(request: directionRequest)
directions.calculateDirectionsWithCompletionHandler {
(response, error) -> Void in
guard let response = response else {
if let error = error {
print("Error: \(error)")
}
return
}
let route = response.routes[0]
self.mapView.addOverlay((route.polyline), level: MKOverlayLevel.AboveRoads)
let rect = route.polyline.boundingMapRect
self.mapView.setRegion(MKCoordinateRegionForMapRect(rect), animated: true)
}
}
But it shows error:
Error: Error Domain=MKErrorDomain Code=5 "Directions Not Available"
UserInfo={NSLocalizedFailureReason=A route to the destination from its
nearest road cannot be determined, MKErrorGEOError=-403,
MKErrorGEOErrorUserInfo={ }, MKDirectionsErrorCode=7,
NSLocalizedDescription=Directions Not Available}
I draw it perfectly in other function with that drawRouteOnMap(), but in findTheClosestATM(), it doesn't work.

How to get driving distance between two point?

I want to create the application that can calculate the driving distance between point A to point B. I know that the CLLocation has distanceFromLocation: to calculate the distance between 2 points but it calculate only the straight line from point A to B. Does it has the way to calculate the driving distance between 2 points? How? Any sample?
Thanks
Swift 3:
func routingDistance(userNotation: CLLocation, destinationLocation: CLLocation, completion: #escaping (CLLocationDistance) -> Void) {
let request:MKDirectionsRequest = MKDirectionsRequest()
// source and destination are the relevant MKMapItems
let sourceS = CLLocationCoordinate2D(latitude: userNotation.coordinate.latitude, longitude: userNotation.coordinate.longitude)
let destinationD = CLLocationCoordinate2D(latitude: destinationLocation.coordinate.latitude, longitude: destinationLocation.coordinate.longitude)
let sourcePM = MKPlacemark(coordinate: sourceS)
let destinationPM = MKPlacemark(coordinate: destinationD)
request.source = MKMapItem(placemark: sourcePM)
request.destination = MKMapItem(placemark: destinationPM)
// Specify the transportation type
request.transportType = MKDirectionsTransportType.automobile;
// If you're open to getting more than one route,
// requestsAlternateRoutes = true; else requestsAlternateRoutes = false;
request.requestsAlternateRoutes = true
let directions = MKDirections(request: request)
directions.calculate { (response, error) in
if let response = response, let route = response.routes.first {
completion(route.distance)
}
}
}
I don't think there is any functionality in iOS, distanceFromLocation will give the air distance between two co-ordinates.
but yes you can calculate road distance using google APIS.
Swift 5+:
If you are looking for driving distance, you can always use MKDirections.
Here is the code for finding driving distance (You can also find walking, and transit distance by changing transport type).
let sourceP = CLLocationCoordinate2DMake( sourceLat, sourceLong)
let destP = CLLocationCoordinate2DMake( desLat, desLong)
let source = MKPlacemark(coordinate: sourceP)
let destination = MKPlacemark(coordinate: destP)
let request = MKDirections.Request()
request.source = MKMapItem(placemark: source)
request.destination = MKMapItem(placemark: destination)
// Specify the transportation type
request.transportType = MKDirectionsTransportType.automobile;
// If you want only the shortest route, set this to a false
request.requestsAlternateRoutes = true
let directions = MKDirections(request: request)
// Now we have the routes, we can calculate the distance using
directions.calculate { (response, error) in
if let response = response, let route = response.routes.first {
print(route.distance) //This will return distance in meters
}
}
If you are only looking for air distance/bird's eye distance/coordinate distance, you can use this code:
let sourceP = CLLocation(latitude: sourceLat, longitude: sourceLong)
let desP = CLLocation(latitude: desLat, longitude: desLong))
let distanceInMeter = sourceP.distance(from: desP)