12/08/2018, 15:16

Hướng Dẫn Google Map SDK Và Chỉ Đường Trong Google Map [Swift 3][Part 2]

Trong phần trước mình đã hướng dẫn các bạn cách để lấy được vị trí hiện tại cũng và hiển thị lên bản đồ. Refer part 1: https://viblo.asia/tienbm92/posts/E375zBLW5GW Trong phần này mình sẽ đi chi tiết về điều hướng sử dụng google API **Step 1: Tổng quan Google API ** Cũng tương tự như khi làm việc ...

Trong phần trước mình đã hướng dẫn các bạn cách để lấy được vị trí hiện tại cũng và hiển thị lên bản đồ. Refer part 1: https://viblo.asia/tienbm92/posts/E375zBLW5GW Trong phần này mình sẽ đi chi tiết về điều hướng sử dụng google API **Step 1: Tổng quan Google API ** Cũng tương tự như khi làm việc với các ứng dụng có kết nối mạng sử dụng web service, cần phải đọc qua document của nó chứ nhỉ Refer link https://developers.google.com/maps/documentation/directions/intro Có điều mấy anh google làm rất chi tiết nên mình có note vài điều theo cá nhân là quan trọng để các bạn tìm hiểu đỡ mất thời gian hơn. Còn cụ thể các bạn nên nghiên cứu hết!! 1: Request Format: Cấu trúc cơ bản của một request khi bạn gửi thông tin để nhận về API. Một sample để các bạn dễ hiểu

https://maps.googleapis.com/maps/api/directions/json?origin=[Latitude_Longtitude]&destination=[Latitude_Longtitude]&mode=walking&key=[API_Key]

Ví dụ này mình sử dụng tham số là kinh độ và vĩ độ của điểm đầu và đích. Thực tế cũng có thể truyền vào địa chỉ của điểm đầu và đích. Như ví dụ của anh Google

https://maps.googleapis.com/maps/api/directions/json?origin=Brooklyn&destination=Queens&mode=transit&key=[API_Key]

Nhưng nếu truyền như vậy cần phải lưu ý rằng địa chỉ này phải có trong google place thì mới sử dụng được, bởi những địa chỉ này đều có toạ độ và được anh google lưu lại. Tóm lại bản chất vẫn là truyền toạ độ. Kế tiếp là tham số mode, tham số này là optional bạn có thể không cần dùng. Tuy nhiên nếu bạn ko truyền thì google sẽ hiểu mode bạn chọn là driving. Có 4 option driving, walking, bicycling, transit. Đặc biệt có transit, nếu bạn sử dụng option mode này bạn có thể thêm thông tin departure_time hoặc arrival_time nếu bạn chỉ truyền arrival_time google sẽ lấy departure_time là current time của bạn Cuối cùng là API_Key đây là key mình đã hướng dẫn lấy từ part 1. Bạn chỉ cần copy và đưa vào request gửi đi là được **2: Response ** Thông tin response trả về có 3 trường lớn geocoded_waypoints, routes, status Các bạn có thể refer link https://developers.google.com/maps/documentation/directions/intro#DirectionsResponseElements để tìm hiểu chi tiết Để vẽ được chỉ đường cần thiết từ 2 điểm trên bản đồ. Chúng ta cần quan tâm routes. Mỗi routes là một mảng các phần tử chứa chỉ dẫn chính xác đường đi giữa 2 điểm trên bản đồ. Có một lưu ý khi là trong số thông tin trả về nằm trong routes có tới 2 thông tin giúp vẽ được đường đi. Đó là overview_polyline và legs[] trong đó overview_polyline cho phép bạn vẽ ra đường đi cơ bản giữa 2 điểm trên bản đồ, còn legs[] cho phép vẽ chi tiết hơn. Về bản chất chỉ khác nhau về số điểm để vẽ. Giống như việc vẽ một đường cong đi qua 10 điểm và 100 điểm, dĩ nhiên vẽ đường cong đi qua 100 điểm sẽ đẹp hơn và nếu là trên map thì dễ tìm ra đường để đi hơn. Ở đây mình sẽ hướng dẫn các bạn sử dụng legs[] để tạo ra chỉ dẫn chi tiết. Step 2: Xử lý Google API Về cơ bản điều hướng sử dụng API của google thực tế là parser response của anh google trả về sau khi đã request điểm đầu và điểm cuối. Đến đây bài toán quy về xử lý web service. Phần này có nhiều hướng dẫn tương tự trên mạng, trong đây mình sẽ hướng dẫn parser thủ công nhằm giúp các bạn hiểu được cơ bản cấu trúc trả về của Direction API kết hợp sử dụng objectMapper để mapping dữ liệu parser được và các models mình dựng sẵn. tìm hiểu thêm về objectmapper refer link https://github.com/Hearst-DD/ObjectMapper Giống như mọi API khác, đặc biệt với những API nhiều thông tin như Direction API. Bước đầu tiên là dựng models để chứa dữ liệu Như trên mình có: 1: 2 models chứa dữ liệu 2 mảng lớn là GeocodedWaypoints và Route 2: Một số model khác để chứa những thành phần còn lại. lưu ý models leg Lưu ý mình có 1 model là GoogleMapValue đây là model định nghĩ những kiểu dữ liệu chỉ có text và int. Refer demo https://github.com/tienbm92/GoogleMapAndDirection.git để nắm được chi tiết các model Sau khi đã dựng những model cần thiết, ta thêm class DirectionService để tiến hành gửi request và xử lý response. Trước tiên chúng ta cần tạo request để gửi đi.

 func getDirections(origin: String?,
                       destination: String?, travelMode: TravelModes,
                       getDirectionStatus: @escaping ((_ success: Bool) -> Void)) {
        guard let originAddress = origin else {
            getDirectionStatus(false)
            return
        }
        guard let destinationAddress = destination else {
            getDirectionStatus(false)
            return
        }
        var directionsURLString = baseURLDirections + "origin=" +
            originAddress + "&destination=" + destinationAddress
        directionsURLString += "&mode=" + travelMode.rawValue + "&key=" + API_KEY

Đây là phần mình tạo request. Tạo 1 function với đối số là kinh độ + vĩ độ của điểm đầu và điểm cuối, phương tiện di chuyển và một closure để thực hiện callback tới service. Kế tiếp mình tạo một function để gửi và đồng thời parser chuỗi json trả về.

func parseJsonGoogleMap(directionsURLString: String,
                            completion: @escaping ((_ success: Bool) -> Void)) {
        if let directionsURL = URL(string: directionsURLString) {
            DispatchQueue.global(qos: .userInitiated).async {
                guard let jsonString = try? String(contentsOf: directionsURL),
                    let direction = DirectionOverview(JSONString: jsonString),
                    direction.status != "" else {
                        completion(false)
                        return
                }
                self.direction = direction
                var success = false
                if direction.status == "OK" {
                    if !direction.routes.isEmpty {
                        if direction.routes[0].overviewPolyline.points != "",
                            !direction.routes[0].legs.isEmpty,
                            !direction.routes[0].legs[0].steps.isEmpty {
                            self.selectLegs = direction.routes[0].legs
                            let result = self.calculateTotalDistanceAndDuration()
                            success = result
                        }
                    }
                }
                completion(success)
            }
        } else {
            completion(false)
            return
        }
    }

Trong hàm này mình tạo một thread để truyền đi urlReuqest và nhận data về. Đồng thời parser data nhận được. Ngoài ra mình có thêm một hàm tính toán những thông số cần thiết

func calculateTotalDistanceAndDuration() -> Bool {
        var status = false
        for leg in self.selectLegs {
            for step in leg.steps {
                self.selectSteps.append(step)
                if let distance = step.distance.value,
                    let duration = step.duration.value {
                    totalDistanceInMeters = totalDistanceInMeters + distance
                    totalDurationInSeconds = totalDurationInSeconds + duration
                }
            }
        }
        status = true
        return status
    }

Trong hàm này lưu ý line self.selectSteps.append(step), Với mỗi step chúng ta coi như đó là một điểm để sau khi nối lại ta sẽ có chỉ đường chính xác. Ở đây chúng ta sẽ có một tập hợp các điểm hay đơn giản là một mảng selectStep. Step 3: Hiển thị chỉ đường trên map Quay lại với view controller. Như bài trước mình đã trình bày cách hiển thị bản đồ trên map và lấy vị trí hiện tại của thiết bị. Demo nay mình sẽ tim đường đi từ vị trí hiện tại của thiết bị tới một điểm bất kỳ trên bản đồ(mỗi khi bạn long press lên map). Sau khi đã xây dựng API Service mình cần gọi nó ra để lấy dữ liệu cần thiết về.

fileprivate func direction() {
        self.mapView.clear()
        let origin: String = "(originLatitude),(originLongtitude)"
        let destination: String =
        "(destinationLatitude),(destinationLongtitude)"
        let marker = GMSMarker(position: CLLocationCoordinate2D(latitude: destinationLatitude, longitude: destinationLongtitude))
        marker.map = self.mapView
        self.directionService.getDirections(origin: origin,
            destination: destination,
            travelMode: travelMode) { [weak self] (success) in
            if success {
                DispatchQueue.main.async {
                    self?.drawRoute()
                    if let totalDistance = self?.directionService.totalDistance,
                        let totalDuration = self?.directionService.totalDuration {
                        self?.detailDirection.text = totalDistance + ". " + totalDuration
                        self?.detailDirection.isHidden = false
                    }
                }
            } else {
                print("error direction")
            }
        }
    }

Trong hàm này bạn cần lưu ý rằng mỗi khi bạn có sự thay đổi giao diện bạn cần đưa đoạn code đó vào một main thread bởi như vậy UI của bạn mới không bị chậm gây trải nghiệm không tốt với người dùng .Sau khi lấy được dữ liệu về, ta cần một hàm để vẽ nó lên map chứ nhỉ

fileprivate func drawRoute() {
        for step in self.directionService.selectSteps {
            if step.polyline.points != "" {
                let path = GMSPath(fromEncodedPath: step.polyline.points)
                let routePolyline = GMSPolyline(path: path)
                routePolyline.strokeColor = UIColor.red
                routePolyline.strokeWidth = 3.0
                routePolyline.map = mapView
            } else {
                return
            }
        }
    }

Để có một điểm bất kỳ trên map bạn càn thêm phần mở rộng sau

extension ViewController: GMSMapViewDelegate {
    func mapView(_ mapView: GMSMapView, didLongPressAt coordinate: CLLocationCoordinate2D) {
        self.destinationLatitude = coordinate.latitude
        self.destinationLongtitude = coordinate.longitude
        let marker = GMSMarker(position: coordinate)
        marker.map = self.mapView
    }
}

Ok! vậy hiện tại bạn đã có thể điều hướng được rồi. Tuy nhiên mình vẫn lưu ý rằng bản chất của điều hướng với google sử dụng direction API là parser Json vậy nên đây cách làm hiện tại của mình mang tính hướng dẫn đơn giản để dễ hiểu, bạn có thể mở rộng bài toán cũng như custom thêm giao diện cho hợp lý. link demo github: https://github.com/tienbm92/GoogleMapAndDirection.git

0