navigation

1187
4
10-07-2020 04:31 AM
AfrozAlam
New Contributor III

Hi Everyone,

I have created a Navigation application by using the Code from Navigation Route example and my routing data using the URL as below;

        "https://services.gisqatar.org.qa/server/rest/services/Routing/RouteTime/NAServer/Route"

I am following step by step as per the example. It generates the Stops, Route and starts tracking immediately after tapping the Navigation button but not updating the Status Label and changing the graphic of Travelled Route.

as shown in the screenshot. Actually, after debugging, I notice that it is not entering the AGSRouteTrackerDelegate

and also not generating the voice guidance. I am unable to figure out what mistake is being done.

Starts navigation after executing this code

 mapView.locationDisplay.start()

Where as it should enter to delegate code as below;

extension NavigateRouteViewController: AGSRouteTrackerDelegate {

    func routeTracker(_ routeTracker: AGSRouteTracker, didGenerateNewVoiceGuidance voiceGuidance:    AGSVoiceGuidance) {

        setSpeakDirection(with: voiceGuidance.text)

    }

I would be very much grateful, if anyone can help me in this regard.

Thanks

Afroz Alam

The Center for GIS, MME,

Qatar

Afroz Alam
0 Kudos
4 Replies
Nicholas-Furness
Esri Regular Contributor

Hi.

Make sure you're setting the delegate properly and haven't accidentally neglected to do that. See arcgis-runtime-samples-ios/NavigateRouteViewController.swift at 1111077da0c1d6f4d95772596174e699ffa3... 

If you're doing that, and you're passing the routeTracker to the AGSRouteTrackerLocationDataSource constructor, and you're setting the mapView.locationDisplay.dataSource to the new AGSRouteTrackerLocationDataSource, then the tracker will remain allocated and you should receive delegate calls.

If you still can't track it down, we'd need to see more of your code.

Nick

AfrozAlam
New Contributor III

Hi Nick,

At the outset, I would like express my sincere thanks for such a prompt response and suggestions. 

Here, I want to specifically mention that when I fetch my data through URLs, as mentioned above, in the original sample of ESRI (arcgis-ios-sdk-samples), I am surprise to see that it is working fine. It means there is some setting of the project which I am missing. I am attaching my full code as herewith for your ready reference.

Warm regards

Afroz Alam
0 Kudos
AfrozAlam
New Contributor III

import UIKit

import AVFoundation

import ArcGIS

let Hybrid_url = AGSArcGISTiledLayer(url:URL(string: "https://services.gisqatar.org.qa/server/rest/services/Vector/Qatar_StreetMap_Hybrid_E/MapServer")!)

// MARK: - Navigate route View Controller

class NavigateRouteViewController: UIViewController {

    // MARK: Instance properties

    

    /// The route result solved by the route task.

    var routeResult: AGSRouteResult!

    /// The original view point that can be reset to later on.

    var defaultViewPoint: AGSViewpoint?

    /// The graphic (with a dashed line symbol) to represent the route ahead.

    var routeAheadGraphic: AGSGraphic!

    /// The graphic to represent the route that's been traveled (initially empty).

    var routeTraveledGraphic: AGSGraphic!

    /// A list to keep track of directions solved by the route task.

    var directionsList: [AGSDirectionManeuver] = []

    /// The route tracker for navigation. Use delegate methods to update tracking status.

    var routeTracker: AGSRouteTracker!

    /// The route task to solve the route between stops, using the online routing service.

    var routeTask: AGSRouteTask!

    /// The initial location for the solved route.

    var initialLocation: AGSLocation!

    /// A formatter to format a time value into human readable string.

    lazy var timeFormatter: DateComponentsFormatter = {

        let formatter = DateComponentsFormatter()

        formatter.allowedUnits = [.hour, .minute, .second]

        formatter.unitsStyle = .full

        return formatter

    }()

    /// An AVSpeechSynthesizer for text to speech.

    lazy var speechSynthesizer = AVSpeechSynthesizer()

    

    // MARK: Storyboard views

    

    /// The button to initiate navigation.

    @IBOutlet weak var navigateButtonItem: UIBarButtonItem!

    /// The button to reset navigation.

    @IBOutlet weak var resetButtonItem: UIBarButtonItem!

    /// The button to recenter the map to navigation pan mode.

    @IBOutlet weak var recenterButtonItem: UIBarButtonItem!

    /// The label to display navigation status.

    @IBOutlet weak var statusLabel: UILabel!

    /// The map view managed by the view controller.

    @IBOutlet weak var mapView: AGSMapView! {

        didSet {

            

            mapView.map = AGSMap(basemap: AGSBasemap(baseLayer: Hybrid_url))

   //         mapView.map = AGSMap(basemap: .navigationVector())

            mapView.graphicsOverlays.add(makeRouteOverlay())

        }

    }

    

    // MARK: Instance methods

    

    /// A wrapper function for operations after the route is solved by an `AGSRouteTask`.

    ///

    /// - Parameter routeResult: The result from `AGSRouteTask.solveRoute(with:completion:)`.

    func didSolveRoute(with routeResult: Result<AGSRouteResult, Error>) {

        switch routeResult {

        case .success(let routeResult):

            self.routeResult = routeResult

            let firstRoute = routeResult.routes.first!

            mapView.locationDisplay.dataSource = makeDataSource(route: firstRoute)

            routeTracker = makeRouteTracker(result: routeResult)

            updateRouteGraphics(remaining: firstRoute.routeGeometry!)

            updateViewpoint(geometry: firstRoute.routeGeometry!)

            // Enable bar button item.

            navigateButtonItem.isEnabled = true

        case .failure(let error):

            presentAlert(error: error)

            setStatus(message: "Failed to solve route.")

        }

    }

    

    /// Create the stops for the navigation.

    ///

    /// - Returns: An array of `AGSStop` objects.

    func makeStops() -> [AGSStop] {

        

        let stop1 = AGSStop(point: AGSPoint(x: 231345.91, y: 397052.85, spatialReference: mapView.spatialReference))

        stop1.name = "Bldg:122, Zone:61, Street:836"

        let stop2 = AGSStop(point: AGSPoint(x: 231069.80, y: 397486.90, spatialReference: mapView.spatialReference))

        stop2.name = "Bldg:49, Zone:60, Street:200"

        return [stop1, stop2]

        

//        let stop1 = AGSStop(point: AGSPoint(x: -117.160386727, y: 32.706608, spatialReference: .wgs84()))

//        stop1.name = "San Diego Convention Center"

//        let stop2 = AGSStop(point: AGSPoint(x: -117.173034, y: 32.712329, spatialReference: .wgs84()))

//        stop2.name = "USS San Diego Memorial"

//        let stop3 = AGSStop(point: AGSPoint(x: -117.147230, y: 32.730467, spatialReference: .wgs84()))

//        stop3.name = "RH Fleet Aerospace Museum"

//        return [stop1, stop2, stop3]

        

    }

    

    /// Make the simulated data source for this demo.

    ///

    /// - Parameter route: An `AGSRoute` object whose geometry is used to configure the data source.

    /// - Returns: An `AGSSimulatedLocationDataSource` object.

    func makeDataSource(route: AGSRoute) -> AGSSimulatedLocationDataSource {

        directionsList = route.directionManeuvers

        let densifiedRoute = AGSGeometryEngine.geodeticDensifyGeometry(route.routeGeometry!, maxSegmentLength: 50.0, lengthUnit: .meters(), curveType: .geodesic) as! AGSPolyline

        // The mock data source to demo the navigation. Use delegate methods to update locations for the tracker.

        let mockDataSource = AGSSimulatedLocationDataSource()

        mockDataSource.setLocationsWith(densifiedRoute)

        mockDataSource.locationChangeHandlerDelegate = self

        initialLocation = mockDataSource.locations?.first

        return mockDataSource

    }

    

    /// Make a route tracker to provide navigation information.

    ///

    /// - Parameter result: An `AGSRouteResult` object used to configure the route tracker.

    /// - Returns: An `AGSRouteTracker` object.

    func makeRouteTracker(result: AGSRouteResult) -> AGSRouteTracker {

        let tracker = AGSRouteTracker(routeResult: result, routeIndex: 0, skipCoincidentStops: true)!

        tracker.delegate = self

        return tracker

    }

    

    /// Make a graphic overlay and add graphics to it.

    ///

    /// - Returns: An `AGSGraphicsOverlay` object.

    func makeRouteOverlay() -> AGSGraphicsOverlay {

        // The graphics overlay for the polygon and points.

        let graphicsOverlay = AGSGraphicsOverlay()

        // Add stops graphics to the graphic overlay.

        let stopSymbol = AGSSimpleMarkerSymbol(style: .diamond, color: .orange, size: 20)

        graphicsOverlay.graphics.addObjects(from: makeStops().map { AGSGraphic(geometry: $0.geometry, symbol: stopSymbol) })

        routeAheadGraphic = AGSGraphic(geometry: nil, symbol: AGSSimpleLineSymbol(style: .dash, color: .systemPurple, width: 5))

        routeTraveledGraphic = AGSGraphic(geometry: nil, symbol: AGSSimpleLineSymbol(style: .solid, color: .systemBlue, width: 3))

        graphicsOverlay.graphics.addObjects(from: [

            routeAheadGraphic!,

            routeTraveledGraphic!

        ])

        return graphicsOverlay

    }

    

    /// Update the viewpoint so that it reflects the original viewpoint when the example is loaded.

    ///

    /// - Parameter result: An `AGSGeometry` object used to update the view point.

    func updateViewpoint(geometry: AGSGeometry) {

        // Show the resulting route on the map and save a reference to the route.

        if let viewPoint = defaultViewPoint {

            // Reset to initial view point with animation.

            mapView.setViewpoint(viewPoint, completion: nil)

        } else {

            mapView.setViewpointGeometry(geometry) { [weak self] _ in

                // Get the initial zoomed view point.

                self?.defaultViewPoint = self?.mapView.currentViewpoint(with: .centerAndScale)

            }

        }

    }

    

    // MARK: UI

    

    func setStatus(message: String) {

        statusLabel.text = message

    }

    

    // MARK: Actions

    

    /// Called in response to the "Navigate" button being tapped.

    @IBAction func startNavigation() {

        navigateButtonItem.isEnabled = false

        resetButtonItem.isEnabled = true

        mapView.locationDisplay.autoPanMode = .navigation

        // If the user navigates the map view away from the location display, activate the recenter button.

        mapView.locationDisplay.autoPanModeChangedHandler = { [weak self] _ in self?.recenterButtonItem.isEnabled = true }

        // Start the location data source and location display.

        mapView.locationDisplay.start()

    }

    

    /// Called in response to the "Reset" button being tapped.

    @IBAction func reset() {

        // Stop the speech, if there is any.

        speechSynthesizer.stopSpeaking(at: .immediate)

        // Reset to the starting location for location display.

        mapView.locationDisplay.dataSource.didUpdate(initialLocation)

        // Stop the location display as well as datasource generation, if reset before the end is reached.

        mapView.locationDisplay.stop()

        mapView.locationDisplay.autoPanMode = .off

        directionsList.removeAll()

        setStatus(message: "Directions are shown here.")

        // Reset the navigation.

        let firstRoute = routeResult.routes.first!

        mapView.locationDisplay.dataSource = makeDataSource(route: firstRoute)

        routeTracker = makeRouteTracker(result: routeResult)

        updateRouteGraphics(remaining: firstRoute.routeGeometry!)

        updateViewpoint(geometry: firstRoute.routeGeometry!)

        // Reset buttons state.

        recenterButtonItem.isEnabled = false

        resetButtonItem.isEnabled = false

        navigateButtonItem.isEnabled = true

    }

    

    /// Called in response to the "Recenter" button being tapped.

    @IBAction func recenter() {

        mapView.locationDisplay.autoPanMode = .navigation

        recenterButtonItem.isEnabled = false

    }

    

    // MARK: UIViewController

    

    override func viewDidLoad() {

        super.viewDidLoad()

        // Avoid the overlap between the label and the map content.

        mapView.contentInset.top = CGFloat(statusLabel.numberOfLines) * statusLabel.font.lineHeight

        // Solve the route as map loads.

//        routeTask = AGSRouteTask(url: URL(string: "https://sampleserver6.arcgisonline.com/arcgis/rest/services/NetworkAnalysis/SanDiego/NAServer/Route")!)

        

        routeTask = AGSRouteTask(url: URL(string: "https://services.gisqatar.org.qa/server/rest/services/Routing/RouteTime/NAServer/Route")!)

        

        routeTask.defaultRouteParameters { [weak self] (params: AGSRouteParameters?, error: Error?) in

            guard let self = self else { return }

            if let error = error {

                self.presentAlert(error: error)

                self.setStatus(message: "Failed to get route parameters.")

            } else if let params = params {

                // Explicitly set values for parameters.

                params.returnDirections = true

                params.returnStops = true

                params.returnRoutes = true

                params.outputSpatialReference = .wgs84()

                params.setStops(self.makeStops())

                self.routeTask.solveRoute(with: params) { [weak self] (result, error) in

                    if let error = error {

                        self?.didSolveRoute(with: .failure(error))

                    } else if let result = result {

                        self?.didSolveRoute(with: .success(result))

                    }

                }

            }

        }

        // Add the source code button item to the right of navigation bar.

//        (self.navigationItem.rightBarButtonItem as? SourceCodeBarButtonItem)?.filenames = ["NavigateRouteViewController"]

    }

    

    override func viewWillDisappear(_ animated: Bool) {

        super.viewWillDisappear(animated)

        // Only reset when the route is successfully solved.

        if routeResult != nil {

            reset()

        }

    }

}

// MARK: - AGSRouteTrackerDelegate

extension NavigateRouteViewController: AGSRouteTrackerDelegate {

    func routeTracker(_ routeTracker: AGSRouteTracker, didGenerateNewVoiceGuidance voiceGuidance: AGSVoiceGuidance) {

        setSpeakDirection(with: voiceGuidance.text)

    }

    

    func routeTracker(_ routeTracker: AGSRouteTracker, didUpdate trackingStatus: AGSTrackingStatus) {

        updateTrackingStatusDisplay(with: trackingStatus)

    }

    

    func setSpeakDirection(with text: String) {

        speechSynthesizer.stopSpeaking(at: .word)

        speechSynthesizer.speak(AVSpeechUtterance(string: text))

    }

    

    func updateTrackingStatusDisplay(with status: AGSTrackingStatus) {

        var statusText: String

        switch status.destinationStatus {

        case .notReached, .approaching:

            let distanceRemaining = status.routeProgress.remainingDistance.displayText + " " + status.routeProgress.remainingDistance.displayTextUnits.pluralDisplayName

            let timeRemaining = timeFormatter.string(from: TimeInterval(status.routeProgress.remainingTime * 60))!

            statusText = """

            Distance remaining: \(distanceRemaining)

            Time remaining: \(timeRemaining)

            """

            if status.currentManeuverIndex + 1 < directionsList.count {

                let nextDirection = directionsList[status.currentManeuverIndex + 1].directionText

                statusText.append("\nNext direction: \(nextDirection)")

            }

        case .reached:

            if status.remainingDestinationCount > 1 {

                statusText = "Intermediate stop reached, continue to next stop."

                routeTracker?.switchToNextDestination()

            } else {

                statusText = "Final destination reached."

                mapView.locationDisplay.stop()

            }

        default:

            return

        }

        updateRouteGraphics(remaining: status.routeProgress.remainingGeometry, traversed: status.routeProgress.traversedGeometry)

        setStatus(message: statusText)

    }

    

    func updateRouteGraphics(remaining: AGSGeometry?, traversed: AGSGeometry? = nil) {

        routeAheadGraphic.geometry = remaining

        routeTraveledGraphic.geometry = traversed

    }

}

// MARK: - AGSLocationChangeHandlerDelegate

extension NavigateRouteViewController: AGSLocationChangeHandlerDelegate {

    func locationDataSource(_ locationDataSource: AGSLocationDataSource, locationDidChange location: AGSLocation) {

        // Update the tracker location with the new location from the simulated data source.

        routeTracker?.trackLocation(location)

    }

}

Afroz Alam
0 Kudos
JayBowman12
New Contributor II

In the sample code you need to set the routeTracker.delegate = self. this is done in the setNavigation function.  I don't see where you are using a AGSRouteTrackerLocationDataSource. In the didsolveRoute you should take you

let routeTrackerLocationDataSource = AGSRouteTrackerLocationDataSource(routeTracker: routeTracker, locationDataSource: mockDataSource)

and then assign this data source to the mapView locationDisplay

mapView.locationDisplay.dataSource = routeTrackerLocationDataSource

0 Kudos