问题
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!
回答1:
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.
回答2:
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.
回答3:
To put it into retrospect, 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.
回答4:
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
来源:https://stackoverflow.com/questions/44139786/calculate-distance-between-my-location-and-a-mapkit-pin-on-swift