【问题标题】:Calculating distance between 3 or more addresses using MapKit使用 MapKit 计算 3 个或更多地址之间的距离
【发布时间】:2019-08-21 05:22:25
【问题描述】:

使用以下代码,我可以计算两个地址之间的距离。是否可以添加将更多地址添加到混合中以计算 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.
                    }
                })
            }
        })
    }
}

【问题讨论】:

  • 只需为不同的目标点再次运行相同的代码,将前一个目标点的源点作为新源点,然后对 n 个位置重复该过程 n-1 次。
  • Recussion 用于这些目的 :) 将上述函数与递归一起使用。

标签: ios swift maps


【解决方案1】:

有多种方法可以实现这一目标。由于您可以同时发出多个请求并且需要等待它们,因此我建议您同时创建所有请求。当全部返回时,您应该返回结果。有很多工具可以实现这一点,但最直接的方法是简单地创建一个计数器。您以预期请求的数量开始它,然后为每个返回的请求减少它。一旦数字变为零,一切就完成了。一个简单的例子是:

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

对于您的情况,您可能需要进行一些重组。我认为您应该首先对地址进行地理编码以获取所有地标。然后使用地标数组创建对以使用方向。这样做不会复制地理编码:假设您有 3 个地址 ABC。然后直接做对,你会做distance(geocode(A), geocode(B)) + distance(geocode(B), geocode(C)),你对B进行两次地理编码。所以为了避免它宁愿做distance([A, B, C].map { geocode($0) })

从你的例子中我得到了:

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


}

而且用法也很简单:

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

    }
}

请注意,给定的示例会产生错误,因为没有跨大西洋的根是有效结果。要使用非错误示例进行测试,您可以删除“Canada”并尝试使用["Ljubljana", "1 Pall Mall East, London SW1Y 5AU", "Buckingham Palace, London SW1A 1AA", "1 Pall Mall East, London SW1Y 5AU"]

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 2015-01-21
    • 1970-01-01
    • 1970-01-01
    • 2014-10-21
    • 1970-01-01
    • 1970-01-01
    • 2020-01-28
    相关资源
    最近更新 更多