How to get driving distance between two point? - iphone

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)

Related

swift calculate distance between two locations

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)
})
}

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
}
}
}
...
}
}

MapKit - transit type for MKDirectionsRequest() not Working

Now I'm doing somethig nice with Swift 3, I hope: Calculating a route between 2 places.
I am using the Xcode simulator.
I can calculate it by car, walking, or with transit.
This is what I have:
mapview.delegate = self
let request = MKDirectionsRequest()
request.source = MKMapItem(placemark: MKPlacemark(coordinate: CLLocationCoordinate2D(latitude: 40.203314, longitude: -8.410257), addressDictionary: nil))
request.destination = MKMapItem(placemark: MKPlacemark(coordinate: CLLocationCoordinate2D(latitude: 40.112808, longitude: -8.498689), addressDictionary: nil))
request.requestsAlternateRoutes = false
//request.transportType = .automobile
request.transportType = .transit
let directions = MKDirections(request: request)
directions.calculate { [unowned self] response, error in
guard let unwrappedResponse = response else { return }
for route in unwrappedResponse.routes {
self.mapview.add(route.polyline)
self.mapview.setVisibleMapRect(route.polyline.boundingMapRect, animated: true)
print("Distance: \(route.distance/1000) m")
print("ETA: \(route.expectedTravelTime/60) min")
for step in route.steps {
print(step.instructions)
}
}
}
I've set the code, and everything works for car or walking. However, transit options is not doing anything.
Why is that?
Directions are not available for transportType .transit unfortunately, only ETA
Related Post

How to draw a route between current location and destination within Mapkit Swift 3?

I am trying to get the latitude en longitude coordinates of the CLLocationManager instance of the current user's location.
I have those two lines of code in my viewWillAppear method:
locationManager = CLLocationManager()
locationManager.requestWhenInUseAuthorization()
Then I have a method called startRoute to calc the route from the current position to some specific annotation on the map. After that I draw the route between those paths. Unfortunately, this is working with two annotations on the map, but I can't get it work with the current location of the user and some annotation.
I tried below script, but when I print print("LAT \(currentLocation.coordinate.latitude)") it will give me 0.0 as result.
func startRoute() {
// Set the latitude and longtitude of the locations (markers)
let sourceLocation = CLLocationCoordinate2D(latitude: currentLocation.coordinate.latitude, longitude: currentLocation.coordinate.longitude)
let destinationLocation = CLLocationCoordinate2D(latitude: sightseeing.latitude!, longitude: sightseeing.longitude!)
// Create placemark objects containing the location's coordinates
let sourcePlacemark = MKPlacemark(coordinate: sourceLocation, addressDictionary: nil)
let destinationPlacemark = MKPlacemark(coordinate: destinationLocation, addressDictionary: nil)
// MKMapitems are used for routing. This class encapsulates information about a specific point on the map
let sourceMapItem = MKMapItem(placemark: sourcePlacemark)
let destinationMapItem = MKMapItem(placemark: destinationPlacemark)
// Annotations are added which shows the name of the placemarks
let sourceAnnotation = MKPointAnnotation()
sourceAnnotation.title = "Times Square"
if let location = sourcePlacemark.location {
sourceAnnotation.coordinate = location.coordinate
}
let destinationAnnotation = MKPointAnnotation()
destinationAnnotation.title = "Title"
destinationAnnotation.subtitle = "Subtitle"
if let location = destinationPlacemark.location {
destinationAnnotation.coordinate = location.coordinate
}
// The annotations are displayed on the map
self.mapView.showAnnotations([sourceAnnotation,destinationAnnotation], animated: true )
// The MKDirectionsRequest class is used to compute the route
let directionRequest = MKDirectionsRequest()
directionRequest.source = sourceMapItem
directionRequest.destination = destinationMapItem
directionRequest.transportType = .walking
// Calculate the direction
let directions = MKDirections(request: directionRequest)
// The route will be drawn using a polyline as a overlay view on top of the map.
// The region is set so both locations will be visible
directions.calculate {
(response, error) -> Void in
guard let response = response else {
if let error = error {
print("Error: \(error)")
}
return
}
let route = response.routes[0]
self.mapView.add((route.polyline), level: MKOverlayLevel.aboveRoads)
let rect = route.polyline.boundingMapRect
self.mapView.setRegion(MKCoordinateRegionForMapRect(rect), animated: true)
}
}
At the top, directly after the class declaration I create the reference to location manager and 'CLLocation':
var locationManager: CLLocationManager!
var currentLocation = CLLocation()
After all, this isn't working. Only when I change the sourceLocation to hardcoded coordinates, it will draw the route to the destination. What do I wrong or what am I missing?
but when I print print("LAT \(currentLocation.coordinate.latitude)") it will give me 0.0 as result
Of course it does. What else would it do? You have a property
var currentLocation = CLLocation()
That is a zero location. You have no code that ever changes this. Therefore, it is always a zero location.
You say you want the
current user's location
but nowhere do you have any code that obtains the user's location.

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.