Calculate distance between my location and a MapKit pin on Swift
Asked Answered
A

5

6

I need your help, I'm working on an App where I have some pins (locations) and what I want is to get the distance between each one and my location. My code is the following

let annotation = MKPointAnnotation()
let annotationTwo = MKPointAnnotation()
let saintPaulHospitalBC = MKPointAnnotation()

override func viewDidLoad() {
    super.viewDidLoad()

    mapita.showsUserLocation = true // Mapita is the name of the MapView.

    annotation.coordinate = CLLocationCoordinate2D(latitude: 25.647399800, longitude: -100.334304500)
    mapita.addAnnotation(annotation)

    annotationTwo.coordinate = CLLocationCoordinate2D(latitude: 25.589339000, longitude: -100.257724800)
    mapita.addAnnotation(annotationTwo)

    saintPaulHospitalBC.coordinate = CLLocationCoordinate2D(latitude: 49.280524700, longitude:  -123.128232600)
    mapita.addAnnotation(SaintPaulHospitalBC)
}

When I run the code, the map shows the pins, but what else can I do to start calculating the distance? Thank you!

Atom answered 23/5, 2017 at 16:0 Comment(0)
G
21

You're gonna have to convert the coordinates of your annotations to CLLocation types, then get the distance between them. To ignore the height of the coordinates, as they are 2D, just use the latitude and longitude properties of the 2D coordinates, like so:

let loc1 = CLLocation(latitude: coord1.latitude, longitude: coord1.longitude)

However, CLLocation has some other properties such as speed and height, so if you want to factor those in you'll have to give more information. To find the distance between the two locations, do this:

let distance = loc1.distance(from: loc2)

This will give your answer as a double in meters.

Geometry answered 23/5, 2017 at 16:29 Comment(1)
This should be the accepted answer. It's simple and easy to create two CLLocations and then get their distance using the distance instance method.Scoliosis
S
4

Create a helper function to compute the distance between the user location and a given MKPointAnnotation pin:

/// Returns the distance (in meters) from the
/// user's location to the specified point.
private func userDistance(from point: MKPointAnnotation) -> Double? {
    guard let userLocation = mapita.userLocation.location else {
        return nil // User location unknown!
    }
    let pointLocation = CLLocation(
        latitude:  point.coordinate.latitude, 
        longitude: point.coordinate.longitude
    )
    return userLocation.distance(from: pointLocation)
}

Finally, to get the user distance to Saint Paul hospital:

if let distance = userDistance(from: saintPaulHospitalBC) {
    // Use distance here...
}

Geolocation tracking latency. There is a catch though: the user distance might not always be available at first, since MapKit/CoreLocation geolocation tracking might still be running in the background.

One way around this, is to conform to the MKMapViewDelegate protocol and wait for the mapView(_:didUpdate:) callback before finally computing your distances.

Stretchy answered 23/5, 2017 at 16:43 Comment(1)
Hey @DanielC. if my answer solved your issue pls be sure mark it as the accepted answer when you get a chance. Thanks man!Stretchy
R
3

To put it in perspective, you need to first specify what "distance" are you looking for. If you are looking for simple Euclidean Distance then any of the other answers or using distanceFromLocation would work. According to Apple's documentaion on distanceFromLocation

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.

This means, that the distance derived using this method will not be the actual route/transportation distance between two points. If that is what you are looking for then head over to the answer I linked above, if not then keep reading (but either way, I encourage you to read the whole post :).

If you are looking for "route" distance (drivable, walkable etc.) between your location and the other annotations in the map, it's going to take little more work using MKRoute object. To be more specific you need to first have access to the MKMapItem objects of each of your annotations and then a custom method like below would be able to get the route info between two MapItem objects.

Note - if you don't have MapItems then you can create them just using the coordinates of each of your annotations, like so

ley myCoordinates CLLocationCoordinate2D(latitude: 25.647399800, longitude: -100.334304500)
let myPlacemark = MKPlacemark(coordinate: myCoordinates)
let myMapItem = MKMapItem(placemark: myPlacemark)

Define an MKRoute variable globally in your class (or ViewController class). This var will hold the calculated Route information between two points.

var route: MKRoute!

and then

func getDistanceToDestination(srcMapItem srcmapItem: MKMapItem, destMapItem destmapItem: MKMapItem){
        let request = MKDirectionsRequest() //create a direction request object
        request.source = srcmapItem //this is the source location mapItem object
        request.destination = destmapItem //this is the destination location mapItem object
        request.transportType = MKDirectionsTransportType.automobile //define the transportation method
        
        let directions = MKDirections(request: request) //request directions
        directions.calculate { (response, error) in
            guard let response = response else {
                print(error.debugDescription)
                return
            }
            self.route = response.routes[0] //get the routes, could be multiple routes in the routes[] array but usually [0] is the best route
        }
    }

Usage would be

self.getDistanceToDestination(srcMapItem: yourSourceMapItemObj, destMapItem: yourDestinationMapitemObj)

where yourSourceMapItemObj and yourDestinationMapitemObj are two MapItem objects aka source and destination points.

And then you can access the distance using self.route.distance to get the distance of the first best route returned by MKRoute. There are a whole bunch of other properties for the MKRoute object route which you can use as well to display/calculate other things, and I encourage you to take a look at those too. You can use the function above to also draw a ployLine i.e. a line showing the route between the two locations in the MapView just by adding self.mapView.add(self.route.polyline) in the end of the custom method above and then use the below MKMapViewDelegate function below to render the polyline.

func mapView(_ mapView: MKMapView, rendererFor overlay: MKOverlay) -> MKOverlayRenderer {
        let linerenderer = MKPolylineRenderer(overlay: self.route.polyline)
        linerenderer.strokeColor = .blue
        linerenderer.lineWidth = 3.5
        return linerenderer
    }

And finally, make sure your class (or your class extension) complies to CLLocationManagerDelegate and MKMapViewDelegate protocols and mapview delegate pointed to self (which I assume you already do) in order for everything above to work.

Razzia answered 22/7, 2017 at 3:16 Comment(0)
F
2

Its easy try my code below.

Don't forget to import CoreLocation or MapKit, hope it helps you

func calculateDistancefrom(sourceLocation: MKMapItem, destinationLocation: MKMapItem, doneSearching: @escaping (_ expectedTravelTim: TimeInterval) -> Void) {

        let request: MKDirectionsRequest = MKDirectionsRequest()

        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(lat: Double, lon: Double, completionHandler: @escaping (_ distance: Int) -> Void) {

        let destinationItem =  MKMapItem(placemark: MKPlacemark(coordinate: CLLocationCoordinate2DMake(lat, lon)))
        guard let currentLocation = self.locationManager?.location else { return }
        let sourceItem =  MKMapItem(placemark: MKPlacemark(coordinate: currentLocation.coordinate))

            self.calculateDistancefrom(sourceLocation: sourceItem, destinationLocation: destinationItem, doneSearching: { distance in
                completionHandler(distance)
            })
    }


   //Thereafter get the distance in meters by calling 

         self.getDistance(lat: yourLat, lon: YourLon) { distance in

            }

 //you can divide by 1000 to convert to KM...  .etc 
Fussbudget answered 28/6, 2018 at 20:25 Comment(3)
There is a confusion between distance and travel time. Your getDistance method accepts completion handler with a property called travelTime. In your example you have used distance all around.Likewise
I was previously using it for getting a travel time so I modified it to just get the distance but it should be fine for anyone to use nowFussbudget
Thanks, I have updated your answer and posted updated code with some modification. I hope you don't mind. Thank you againNonchalant
N
0

Using MapKit & Swift 5

Calculate distance between two location location

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)
        })
    }
Nonchalant answered 18/3, 2020 at 7:35 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.