使用Swift 3在自定义视图中使用Google Maps绘制折线

6

我正在尝试在自定义的UIView上使用Google Maps绘制两个地点之间的路线,但无法正确实现。我的自定义视图为mapViewX。我使用pods安装了google sdk,其中包括pod 'GoogleMaps'和pod 'GooglePlaces'。我将自定义视图类设置为'GMSMapView'。我的代码如下:

    @IBOutlet weak var mapViewX: GMSMapView!

override func viewDidLoad() {
    super.viewDidLoad()
    // Do any additional setup after loading the view, typically from a nib.

    let path = GMSMutablePath()
    path.add(CLLocationCoordinate2D(latitude: 37.778483, longitude: -122.513960))
    path.add(CLLocationCoordinate2D(latitude: 37.706753, longitude: -122.418677))
    let polyline = GMSPolyline(path: path)
    polyline.strokeColor = .black
    polyline.strokeWidth = 10.0
    polyline.map = mapViewX

}

请帮忙!

3个回答

10

这里正常工作。请确保您正确设置了 GMSCameraPosition 的坐标。

编辑

要在两个坐标之间绘制路线,请使用 Google 地图方向 API

类似这样:

    let origin = "\(37.778483),\(-122.513960)"
    let destination = "\(37.706753),\(-122.418677)"
    let url = "https://maps.googleapis.com/maps/api/directions/json?origin=\(origin)&destination=\(destination)&mode=driving&key=[YOUR-API-KEY]"

    Alamofire.request(url).responseJSON { response in
        let json = JSON(data: response.data!)
        let routes = json["routes"].arrayValue

        for route in routes
        {
            let routeOverviewPolyline = route["overview_polyline"].dictionary
            let points = routeOverviewPolyline?["points"]?.stringValue
            let path = GMSPath.init(fromEncodedPath: points!)

            let polyline = GMSPolyline(path: path)
            polyline.strokeColor = .black
            polyline.strokeWidth = 10.0
            polyline.map = mapViewX

        }
    }

了解更多信息 - Directions API 开发者指南


通过添加path.addCoordinate,它会出现错误:"GMSMutablePath类型的值没有成员'addCoordinate'"。 - user6500031
我在super.viewDidLoad()下面添加了以下代码: let camera = GMSCameraPosition.camera(withLatitude: 37.778483, longitude: -122.513960, zoom: 8) mapViewX.animate(to: camera)但是,当地图为空时,折线绘制成一条直线,而我需要的是从一个点到另一个点的路径。 - user6500031
是的,这是预期的行为,因为您正在提供两个坐标来绘制折线。要沿路线绘制路径,您需要使用Google Maps Direction API,它将返回路线的路径数组。我正在编辑答案以添加Directions API的基本用法。 - Muhammad Abdul Subhan
嗨,Abdul,我试过了,但是路由数组在响应中得到0值。 - user6500031
工作正常,我只添加了几行代码来更新相机位置,其余部分都运行良好。 - user6500031
显示剩余3条评论

2
要在两个坐标之间绘制路线,您需要向Google Maps Directions API发出请求并解析其响应。因此,您首先需要为您的请求获取一个API密钥。您可以通过创建一个项目并在该项目中启用Google Maps Directions API来获得一个here
假设您已经安装了Google Maps SDK,您需要向Directions API发出请求,然后解析其响应。一旦您解析了响应JSON,就可以创建一个GMSPath对象。我喜欢使用具有两个输入(start和end CLLocationCoordinate2D对象)的函数来完成这项工作,并在成功时返回GMSPath或在出现错误时返回错误。下面的代码是Swift 3的。
我的类及其函数如下:
import Foundation
import CoreLocation
import GoogleMaps

class SessionManager {
    let GOOGLE_DIRECTIONS_API_KEY = "INSERT_YOUR_API_KEY_HERE"

    func requestDirections(from start: CLLocationCoordinate2D, to end: CLLocationCoordinate2D, completionHandler: @escaping ((_ response: GMSPath?, _ error: Error?) -> Void)) {
        guard let url = URL(string: "https://maps.googleapis.com/maps/api/directions/json?origin=\(start.latitude),\(start.longitude)&destination=\(end.latitude),\(end.longitude)&key=\(GOOGLE_DIRECTIONS_API_KEY)") else {
            let error = NSError(domain: "LocalDomain", code: 0, userInfo: [NSLocalizedDescriptionKey: "Failed to create object URL"])
            print("Error: \(error)")
            completionHandler(nil, error)
            return
        }

        // Set up the session
        let config = URLSessionConfiguration.default
        let session = URLSession(configuration: config)

        let task = session.dataTask(with: url) { (data, response, error) in
            // Check if there is an error.
            guard error == nil else {
                DispatchQueue.main.async {
                    print("Google Directions Request Error: \((error!)).")
                    completionHandler(nil, error)
                }
                return
            }

            // Make sure data was received.
            guard let data = data else {
                DispatchQueue.main.async {
                    let error = NSError(domain: "GoogleDirectionsRequest", code: 0, userInfo: [NSLocalizedDescriptionKey: "Failed to receive data"])
                    print("Error: \(error).")
                    completionHandler(nil, error)
                }
                return
            }

            do {
                // Convert data to dictionary.
                guard let json = try JSONSerialization.jsonObject(with: data, options: []) as? [String: Any] else {
                    DispatchQueue.main.async {
                        let error = NSError(domain: "GoogleDirectionsRequest", code: 2, userInfo: [NSLocalizedDescriptionKey: "Failed to convert JSON to Dictionary"])
                        print("Error: \(error).")
                        completionHandler(nil, error)
                    }
                    return
                }

                // Check if the the Google Direction API returned a status OK response.
                guard let status: String = json["status"] as? String, status == "OK" else {
                    DispatchQueue.main.async {
                        let error = NSError(domain: "GoogleDirectionsRequest", code: 3, userInfo: [NSLocalizedDescriptionKey: "Google Direction API did not return status OK"])
                        print("Error: \(error).")
                        completionHandler(nil, error)
                    }
                    return
                }

                print("Google Direction API response:\n\(json)")

                // We only need the 'points' key of the json dictionary that resides within.
                if let routes: [Any] = json["routes"] as? [Any], routes.count > 0, let routes0: [String: Any] = routes[0] as? [String: Any], let overviewPolyline: [String: Any] = routes0["overview_polyline"] as? [String: Any], let points: String = overviewPolyline["points"] as? String {
                    // We need the get the first object of the routes array (route0), then route0's overview_polyline and finally overview_polyline's points object.

                    if let path: GMSPath = GMSPath(fromEncodedPath: points) {
                        DispatchQueue.main.async {
                            completionHandler(path, nil)
                        }
                        return
                    } else {
                        DispatchQueue.main.async {
                            let error = NSError(domain: "GoogleDirections", code: 5, userInfo: [NSLocalizedDescriptionKey: "Failed to create GMSPath from encoded points string."])
                            completionHandler(nil, error)
                        }
                        return
                    }

                } else {
                    DispatchQueue.main.async {
                        let error = NSError(domain: "GoogleDirections", code: 4, userInfo: [NSLocalizedDescriptionKey: "Failed to parse overview polyline's points"])
                        completionHandler(nil, error)
                    }
                    return
                }


            } catch let error as NSError  {
                DispatchQueue.main.async {
                    completionHandler(nil, error)
                }
                return
            }

        }

        task.resume()
    }
}

然后你可以在你的viewDidLoad中使用它,就像这样:
@IBOutlet weak var mapView: GMSMapView!

override func viewDidLoad() {
    super.viewDidLoad()

    let sessionManager = SessionManager()
    let start = CLLocationCoordinate2D(latitude: 37.778483, longitude: -122.513960)
    let end = CLLocationCoordinate2D(latitude: 37.706753, longitude: -122.418677)

    sessionManager.requestDirections(from: start, to: end, completionHandler: { (path, error) in

        if let error = error {
            print("Something went wrong, abort drawing!\nError: \(error)")
        } else {
            // Create a GMSPolyline object from the GMSPath
            let polyline = GMSPolyline(path: path!)

            // Add the GMSPolyline object to the mapView
            polyline.map = self.mapView

            // Move the camera to the polyline
            let bounds = GMSCoordinateBounds(path: path!)
            let cameraUpdate = GMSCameraUpdate.fit(bounds, with: UIEdgeInsets(top: 40, left: 15, bottom: 10, right: 15))
            self.mapView.animate(with: cameraUpdate)
        }

    })

}

希望你觉得它有用。

它崩溃了。显示错误:异常'GMSThreadException',原因:'API方法必须从主线程调用'。 - user6500031
崩溃是因为回调返回的结果没有在主线程(UI 线程)上运行。我已经编辑了答案,现在你不应该有任何问题了。 - e_pie
可以,它起作用了。但是你能解释一下为什么我们需要DispatchQueue.main.async吗?难道我们不在主线程上吗? - user6500031
是的,抱歉,我的错误。如果您在dataTask(with:)中设置断点,您会注意到它在后台线程上运行。因此,当您调用其completionHandler时,它将返回在与其运行的线程相同的线程上(即在后台线程中)。但是,您不能在后台线程上执行UI操作(例如绘制折线)。我编辑了我的答案,因此完成处理程序始终在主线程上调用,因此您不需要在viewDidLoad中的requestDirections块中调用DispatchQueue - e_pie

2

Swift 4

创建全局变量。

var sourceLat = 0.0
var sourceLong = 0.0
var DestinationLat = 0.0
var DestinationLong = 0.0
var startLOC = CLLocation()
var endLOC = CLLocation()

安装Pod Alamofire和SwiftJSON。

pod 'Alamofire', '~> 4.5'
pod 'SwiftyJSON'

制作一个函数,用于绘制起点和终点之间的路线。

func drawPath(startLocation: CLLocation, endLocation: CLLocation)
{
    let origin = "\(startLocation.coordinate.latitude),\(startLocation.coordinate.longitude)"
    let destination = "\(endLocation.coordinate.latitude),\(endLocation.coordinate.longitude)"
    let url = "https://maps.googleapis.com/maps/api/directions/json?origin=\(origin)&destination=\(destination)&mode=driving"

    Alamofire.request(url).responseJSON { response in
        //print(response.request as Any)  // original URL request
        //print(response.response as Any) // HTTP URL response
        //print(response.data as Any)     // server data
        //print(response.result as Any)   // result of response serialization

        let json = JSON(data: response.data!)
        let routes = json["routes"].arrayValue
        print(json)
        // print route using Polyline

        DispatchQueue.global(qos: .default).async(execute: {() -> Void in
            // Do something...
            DispatchQueue.main.async(execute: {() -> Void in
               // self.hideHUD()
            })
        })
        for route in routes
        {
            let routeOverviewPolyline = route["overview_polyline"].dictionary
            let points = routeOverviewPolyline?["points"]?.stringValue
            let path = GMSPath.init(fromEncodedPath: points!)
            let polyline = GMSPolyline.init(path: path)
            polyline.strokeWidth = 4
            polyline.strokeColor =  UIColor.black
            polyline.map = self.mapViewBus

        }

    }
}

创建按钮操作,提供源路由和目标路由。给出经度和纬度。之后粘贴代码。

 // Route Source & Destination
            self.startLOC = CLLocation(latitude: sourceLat, longitude: sourceLong)
            self.endLOC = CLLocation(latitude: DestinationLat, longitude: DestinationLong)

            drawPath(startLocation: startLOC, endLocation: endLOC)


            let marker = GMSMarker()
            marker.position = CLLocationCoordinate2D(latitude: sourceLat, longitude: sourceLong)
           // marker.icon = userImage.af_imageScaled(to: CGSize(width: 50, height: 50)).af_imageRoundedIntoCircle()
            marker.title = "Source"
            marker.map = mapViewBus


            let markerr = GMSMarker()
            markerr.position = CLLocationCoordinate2D(latitude: DestinationLat, longitude: DestinationLong)
           // markerr.icon =  washerImage.af_imageScaled(to: CGSize(width: 50, height: 50)).af_imageRoundedIntoCircle()
            markerr.title = "Desintation"
            markerr.map = mapViewBus

            let camera = GMSCameraPosition.camera(withLatitude: sourceLat, longitude: sourceLong, zoom: 14.0)
            self.mapViewBus.camera = camera
            self.mapViewBus.animate(to: camera)

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接