mapbox / mapbox-directions-swift

Traffic-aware directions and map matching in Swift on iOS, macOS, tvOS, watchOS, and Linux
https://www.mapbox.com/navigation/
ISC License
178 stars 87 forks source link

Directions calculation failing for some waypoints #815

Closed RL-William-Coates closed 6 months ago

RL-William-Coates commented 7 months ago

Between some sets of two waypoints, the directions calculation is failing.

Error:

unknown(response: Optional(<NSHTTPURLResponse: 0x28106f840> { URL: https://api.mapbox.com/directions/v5/mapbox/driving-traffic/-2.694463,51.99472;-2.09525,57.185101?alternatives=true&continue_straight=true&roundabout_exits=true&enable_refresh=true&geometries=polyline6&overview=full&steps=true&language=en-GB&voice_instructions=true&voice_units=imperial&banner_instructions=true&annotations=duration,maxspeed,congestion_numeric&waypoint_names=Origin%20(user%20location);Destination&access_token={[REMOVED]} { Status Code: 200, Headers {…} }),
underlying: Optional(Swift.DecodingError.keyNotFound(CodingKeys(stringValue: "description", intValue: nil),
Swift.DecodingError.Context(codingPath: [CodingKeys(stringValue: "routes", intValue: nil), _JSONKey(stringValue: "Index 0", intValue: 0), CodingKeys(stringValue: "legs", intValue: nil), _JSONKey(stringValue: "Index 0", intValue: 0), CodingKeys(stringValue: "incidents", intValue: nil), _JSONKey(stringValue: "Index 0", intValue: 0)], debugDescription: "No value associated with key CodingKeys(stringValue: \"description\", intValue: nil) (\"description\").", underlyingError: nil))), code: nil, message: nil)

The localizedDescription is The operation couldn’t be completed. server error.

How to reproduce the problem:

// Define two waypoints to travel between
let origin = Waypoint(coordinate: CLLocationCoordinate2D(latitude: 51.99472, longitude: -2.694463), name: "Origin")
let destination = Waypoint(coordinate: CLLocationCoordinate2D(latitude: 57.185101, longitude: -2.09525), name: "Destination")

// Set options
let routeOptions = NavigationRouteOptions(waypoints: [origin, destination])

// Request a route using MapboxDirections
Directions.shared.calculate(routeOptions) { [weak self] (session, result) in
    switch result {
    case .failure(let error):
        print(error.localizedDescription)
    case .success(let response):
        guard let strongSelf = self else {
            return
        }
        // Pass the generated route response to the the NavigationViewController
        let viewController = NavigationViewController(for: response, routeIndex: 0, routeOptions: routeOptions)
        viewController.modalPresentationStyle = .fullScreen
        strongSelf.present(viewController, animated: true, completion: nil)
    }
}
santoso8118 commented 7 months ago

I am using Mapbox Directions 2.10.0 and I got the same issue and I tried to debug inside this below function:

@discardableResult open func calculate(_ options: RouteOptions, completionHandler: @escaping RouteCompletionHandler) -> URLSessionDataTask {
        options.fetchStartDate = Date()
        let session = (options: options as DirectionsOptions, credentials: self.credentials)
        let request = urlRequest(forCalculating: options)
        let requestTask = urlSession.dataTask(with: request) { (possibleData, possibleResponse, possibleError) in

            if let urlError = possibleError as? URLError {
                DispatchQueue.main.async {
                    completionHandler(session, .failure(.network(urlError)))
                }
                return
            }

            guard let response = possibleResponse, ["application/json", "text/html"].contains(response.mimeType) else {
                DispatchQueue.main.async {
                    completionHandler(session, .failure(.invalidResponse(possibleResponse)))
                }
                return
            }

            guard let data = possibleData else {
                DispatchQueue.main.async {
                    completionHandler(session, .failure(.noData))
                }
                return
            }

            self.processingQueue.async {
                do {
                    let decoder = JSONDecoder()
                    decoder.userInfo = [.options: options,
                                        .credentials: self.credentials]

                    guard let disposition = try? decoder.decode(ResponseDisposition.self, from: data) else {
                        let apiError = DirectionsError(code: nil, message: nil, response: possibleResponse, underlyingError: possibleError)

                        DispatchQueue.main.async {
                            completionHandler(session, .failure(apiError))
                        }
                        return
                    }

                    guard (disposition.code == nil && disposition.message == nil) || disposition.code == "Ok" else {
                        let apiError = DirectionsError(code: disposition.code, message: disposition.message, response: response, underlyingError: possibleError)
                        DispatchQueue.main.async {
                            completionHandler(session, .failure(apiError))
                        }
                        return
                    }

                    let result = try decoder.decode(RouteResponse.self, from: data)
                    guard result.routes != nil else {
                        DispatchQueue.main.async {
                            completionHandler(session, .failure(.unableToRoute))
                        }
                        return
                    }

                    DispatchQueue.main.async {
                        completionHandler(session, .success(result))
                    }
                } catch {
                    DispatchQueue.main.async {
                        let bailError = DirectionsError(code: nil, message: nil, response: response, underlyingError: error)
                        completionHandler(session, .failure(bailError))
                    }
                }
            }
        }
        requestTask.priority = 1
        requestTask.resume()

        return requestTask
    }

This is my configs for NavigationRouteOptions:

var routeOptions:NavigationRouteOptions?
routeOptions =  NavigationRouteOptions(waypoints: waypoints, profileIdentifier: .automobileAvoidingTraffic)
routeOptions?.includesSteps = true
routeOptions?.attributeOptions = [.congestionLevel, .expectedTravelTime, .maximumSpeedLimit]
routeOptions?.shapeFormat = .polyline6
routeOptions?.locale = Locale.enSGLocale() 
routeOptions?.distanceMeasurementSystem = .metric 
routeOptions?.includesAlternativeRoutes = true

Results: The func throws a DirectionsError saying: "No value associated with key CodingKeys(stringValue: \"description\", intValue: nil) (\"description\")."

I notice that this issue happens when I choose a destination that there are car crashed and heavy traffic near the destination

shivamadhavan-ncs commented 7 months ago

The issue has been observed only recently we have not seen this issue in the past in the same SDK version as @santoso8118 mentioned above (2.10).

santoso8118 commented 7 months ago

I tried to capture the response from Mapbox Directions API that causing the issue:

MapboxDirectionsAPI_Response.json

After that I try to parser the response using below code:

let origin = Waypoint(coordinate: CLLocationCoordinate2D(latitude: 1.329578, longitude: 103.875469), coordinateAccuracy: -1, name: "Start")
        let destination = Waypoint(coordinate: CLLocationCoordinate2D(latitude: 1.295799, longitude: 103.625809), coordinateAccuracy: -1, name: "P.I.E. Electroplating Pte Ltd")

        let url = Bundle.main.url(forResource: "MapboxDirectionsAPI_Response", withExtension: "json")!

        let routeOptions = setRouteOptions(routeType: Constants.fast, waypoints: [origin, destination])
        do {

            let jsonData = try Data(contentsOf: url)

            let decoder = JSONDecoder()
            decoder.userInfo = [.options: routeOptions,
                                .credentials: Directions.shared.credentials]

            let result = try decoder.decode(RouteResponse.self, from: jsonData)
            guard result.routes != nil else {
                return
            }
        } catch (let error) {
            print("Not found route error: \(error.localizedDescription)")
        }

We got error description: The operation couldn’t be completed. Unable to decode the string as a polyline with precision 1000000.0

Note: The routeOptions we are config the same as I mention in previous comment and we are using shape format .polyline6 to request routes

RL-William-Coates commented 7 months ago

As of this morning, I am no longer experiencing this issue. I haven't changed anything. @santoso8118 is it still not working for you?

I opened a mapbox support request, this was their response:

I just tested out your code using our examples app and I was able to get it to run correctly. Therefore, I don't think that the issue has to do with the coordinates, rather, something in your implementation. I'd recommend taking a look at our example code to see if there's something in your implementation that you're not doing correctly and also upgrading to the most recent version of the SDK.

You can do this by clicking on File > Swift Packages > Update To Latest Package Versions in Xcode. If that doesn't work, you could also try selecting File > Swift Packages > Reset Package Cache.

santoso8118 commented 7 months ago

@William-Coates : We notice that this issue happens when the traffic is so heavy and there are some car crashed happen in the road near the destination. It is reproducible in our app. This issue only happens in that situation and in a short time (around 15 minutes) After that we can navigate normally to the destination

RL-William-Coates commented 7 months ago

@santoso8118 For us, it was an issue that lasted at least 48 hours, for multiple sets of origins and destinations. I couldn't find any pattern in sets of waypoints that worked with those that didn't.