0

使用以下代码,我可以计算两个地址之间的距离。是否可以添加将更多地址添加到混合中以计算 3 个或更多之间的距离的功能?

到目前为止的代码:

import Cocoa
import MapKit
import CoreLocation


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

1 回答 1

0

There are multiple ways to achieve this. Since you can make multiple requests at the same time and you need to wait for them I suggest you to create all of them simultaneously. When all return you should then return your result. There are many tools to achieve this but the most direct way is to simply create a counter. You start it with number of expected requests and then decrease it for every returned requests. Once the number turns to zero all are done. A simple example would be:

var count: Int = 10
for _ in 0..<count {
    DispatchQueue.main.asyncAfter(deadline: .now() + .random(in: 1..<10)) {
        count -= 1
        if count == 0 {
            print("All done here")
        }
    }
}

For your case you may want to restructure a bit. I think you should first geocode addresses to get all the placemarks. Then use array of placemarks to create pairs to use directions. Doing so will not duplicate for geocoding: Assume you are having 3 addresses A, B, C. Then to do pairs directly your would do distance(geocode(A), geocode(B)) + distance(geocode(B), geocode(C)) and you are geocoding B twice. So to avoid it rather do distance([A, B, C].map { geocode($0) }).

From your example this is what I got:

private static func generatePlacemarksFromAddresses(_ addresses: [String], completion: @escaping ((_ placemarks: [(address: String, placemark: MKPlacemark)]?, _ error: Error?) -> Void)) {
    guard addresses.count > 0 else {
        completion([], nil)
        return
    }

    var requestsOut: Int = addresses.count
    var placemarks: [String: MKPlacemark?] = [String: MKPlacemark?]()

    addresses.forEach { address in
        let geocoder = CLGeocoder()
        geocoder.geocodeAddressString(address) { foundPlacemarks, error in
            let placemark: MKPlacemark? = {
                guard let location = foundPlacemarks?.first?.location else { return nil }
                return MKPlacemark(coordinate: location.coordinate)
            }()
            placemarks[address] = placemark

            requestsOut -= 1
            if requestsOut == 0 {
                // All are finished
                // Compose ordered array or error
                let erroredAddresses: [String] = placemarks.filter { $0.value == nil }.map { $0.key }
                if erroredAddresses.count > 0 {
                    completion(nil, NSError(domain: "GEOCODING", code: 400, userInfo: ["dev_message": "Not all adresses could be geocoded. Failed with \(erroredAddresses.count) addressess: \(erroredAddresses.joined(separator: " & "))"]))
                } else {
                    completion(addresses.map { ($0, placemarks[$0]!!) }, nil)
                }
            }
        }
    }
}

private static func calculateDirectionDistanceFromPlacemarks(_ placemarks: [(address: String, placemark: MKPlacemark)], completion: @escaping ((_ distance: Double?, _ error: Error?) -> Void)) {
    guard placemarks.count > 1 else {
        completion(0, nil)
        return
    }

    var requestsOut: Int = placemarks.count-1
    var overallDistance: Double = 0.0
    var erroredConnections: [String] = [String]()

    for index in 0..<placemarks.count-1 {
        let directions = MKDirections(request: {
            let request: MKDirections.Request = MKDirections.Request()
            request.source = MKMapItem(placemark: placemarks[index].placemark)
            request.destination = MKMapItem(placemark: placemarks[index+1].placemark)
            request.transportType = MKDirectionsTransportType.automobile
            return request
        }())

        directions.calculate(completionHandler: { (response: MKDirections.Response?, error: Error?) in
            if let distance = response?.routes.first?.distance {
                overallDistance += distance
            } else {
                erroredConnections.append(placemarks[index].address + " -> " + placemarks[index+1].address)
            }

            requestsOut -= 1
            if requestsOut == 0 {
                // All are done
                if erroredConnections.count > 0 {
                    completion(nil, NSError(domain: "GEOCODING", code: 400, userInfo: ["dev_message": "Not all connections returned a route. Failed with \(erroredConnections.count) connections: \(erroredConnections.joined(separator: " & "))"]))
                } else {
                    completion(overallDistance, nil)
                }
            }
        })
    }


}

And usage is pretty simple:

generatePlacemarksFromAddresses(["Ljubljana", "Canada", "1 Pall Mall East, London SW1Y 5AU", "Buckingham Palace, London SW1A 1AA", "1 Pall Mall East, London SW1Y 5AU"]) { placemarks, error in
    guard let placemarks = placemarks else {
        print("Placemarks could not be generated. Got error: \(error)")
        return
    }
    calculateDirectionDistanceFromPlacemarks(placemarks) { distance, error in
        if let distance = distance {
            print("Got distance: \(distance)")
        }
        if let error = error {
            print("Got error: \(error)")
        }

    }
}

Note that a given example produces an error because there is no root across Atlantic which is a valid result. To test with non-error example you can remove "Canada" and try with ["Ljubljana", "1 Pall Mall East, London SW1Y 5AU", "Buckingham Palace, London SW1A 1AA", "1 Pall Mall East, London SW1Y 5AU"].

于 2019-08-21T10:41:59.297 回答