Find route

View on GitHub

Display directions for a route between two points.

Screenshot of find route sample with solved route Screenshot of find route sample with list of directions

Use case

Find routes with driving directions between any number of locations. You might use the ArcGIS platform to create a custom network for routing on private roads.

How to use the sample

For simplicity, the sample comes loaded with a start and end stop. You can click on the Find Route button to display a route between these stops. Once the route is generated, tap the directions icon for turn-by-turn directions shown in a list.

How it works

  1. Create a RouteTask instance using a URL to an online route service.
  2. Create default RouteParameters using RouteTask.createDefaultParameters().
  3. Set returnDirections on the parameters to true.
  4. Create Stop instances for each destination and assign the stops to the route parameters using RouteParameters.setStops(_:).
  5. Solve the route using RouteTask.solveRoute(routeParameters:) to get a RouteResult.
  6. Create GraphicsOverlay and Graphic instances for the route and stops.
  7. Iterate through the result's Routes. To display the route, update the route graphic's geometry with the route's routeGeometry. To display directions, get the direction maneuvers from the route's directionManeuvers property, and, for each maneuver, display the maneuver's directionText.

Relevant API

  • DirectionManeuver
  • Route
  • RouteParameters
  • RouteParameters.setStops(_:)
  • RouteResult
  • RouteTask
  • RouteTask.createDefaultParameters()
  • RouteTask.solveRoute(routeParameters:)
  • Stop

Tags

directions, driving, navigation, network, network analysis, route, routing, shortest path, turn-by-turn

Sample Code

FindRouteView.swift
Use dark colors for code blocksCopy
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
// Copyright 2022 Esri
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
//   https://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.

import ArcGIS
import SwiftUI

struct FindRouteView: View {
    /// The error shown in the error alert.
    @State private var error: Error?

    /// A Boolean value indicating whether to show the directions.
    @State private var isShowingDirections = false

    /// A Boolean value indicating whether to solve the route.
    @State private var isSolvingRoute = false

    /// The view model for this sample.
    @StateObject private var model = Model()

    var body: some View {
        MapView(map: model.map, graphicsOverlays: model.graphicsOverlays)
            .errorAlert(presentingError: $error)
            .task {
                do {
                    try await model.initializeRouteParameters()
                } catch {
                    self.error = error
                }
            }
            .toolbar {
                ToolbarItemGroup(placement: .bottomBar) {
                    Spacer()
                    Button("Route") {
                        isSolvingRoute = true
                    }
                    .disabled(model.isRouteDisabled || isSolvingRoute)
                    .task(id: isSolvingRoute) {
                        // Ensures that solving the route is true.
                        guard isSolvingRoute else { return }
                        // Finds the route.
                        do {
                            try await model.findRoute()
                        } catch {
                            self.error = error
                        }
                        // Sets solving the route to false.
                        isSolvingRoute = false
                    }
                    Spacer()
                    Button {
                        isShowingDirections = true
                    } label: {
                        Image(systemName: "arrow.triangle.turn.up.right.diamond")
                    }
                    .disabled(model.directions.isEmpty)
                    .popover(isPresented: $isShowingDirections) {
                        NavigationStack {
                            List(model.directions, id: \.text) { directionManeuver in
                                Text(directionManeuver.text)
                            }
                            .navigationTitle("Directions")
                            .navigationBarTitleDisplayMode(.inline)
                            .toolbar {
                                ToolbarItem(placement: .confirmationAction) {
                                    Button("Done") {
                                        isShowingDirections = false
                                    }
                                }
                            }
                        }
                        .frame(idealWidth: 320, idealHeight: 428)
                    }
                }
            }
    }
}

private extension FindRouteView {
    /// The model used to store the geo model and other expensive objects
    /// used in this view.
    @MainActor
    class Model: ObservableObject {
        /// The directions for the route.
        @Published var directions: [DirectionManeuver] = []

        /// The parameters for the route.
        @Published var routeParameters: RouteParameters!

        /// A Boolean value indicating whether to disable the route button.
        var isRouteDisabled: Bool { routeParameters == nil }

        /// The route task.
        private let routeTask = RouteTask(url: .routeTask)

        /// A map with a topographic basemap style and an initial viewpoint.
        let map: Map

        /// The stops for the route.
        private let stops: [Stop]

        /// The graphics overlay for the route.
        private let routeGraphicsOverlay: GraphicsOverlay

        /// The graphics overlay for the stops.
        private let stopGraphicsOverlay: GraphicsOverlay

        /// The graphics overlays for the route and stops.
        var graphicsOverlays: [GraphicsOverlay] { [routeGraphicsOverlay, stopGraphicsOverlay] }

        /// The graphic for the route.
        private var routeGraphic: Graphic { routeGraphicsOverlay.graphics.first! }
        /// The graphic for the first stop.
        private var stopOneGraphic: Graphic { stopGraphicsOverlay.graphics.first! }
        /// The graphic for the second stop.
        private var stopTwoGraphic: Graphic { stopGraphicsOverlay.graphics.last! }

        init() {
            // Initializes the map
            map = Map(basemapStyle: .arcGISTopographic)
            map.initialViewpoint = Viewpoint(
                center: Point(
                    x: -13041154.7153,
                    y: 3858170.2368,
                    spatialReference: .webMercator
                ),
                scale: 1e5
            )

            // Initializes the graphics overlay for the route.
            routeGraphicsOverlay = GraphicsOverlay(graphics: [
                Graphic(symbol: SimpleLineSymbol(style: .solid, color: .yellow, width: 5))
            ])

            // Initializes the stops and the graphics overlay for them.
            let stopOne = Stop.one
            let stopTwo = Stop.two
            stops = [stopOne, stopTwo]

            let stopOneGraphic = Graphic(
                geometry: stopOne.geometry,
                symbol: TextSymbol(text: stopOne.name, color: .blue, size: 20)
            )

            let stopTwoGraphic = Graphic(
                geometry: stopTwo.geometry,
                symbol: TextSymbol(text: stopTwo.name, color: .red, size: 20)
            )

            stopGraphicsOverlay = GraphicsOverlay(graphics: [stopOneGraphic, stopTwoGraphic])
        }

        /// Initializes the route parameters.
        func initializeRouteParameters() async throws {
            guard routeParameters == nil else { return }

            // Creates the default parameters.
            let parameters = try await routeTask.makeDefaultParameters()

            // Sets the return directions on the parameters to true.
            parameters.returnsDirections = true

            // Sets the stops for the route.
            parameters.setStops(stops)

            // Initializes the route parameters.
            routeParameters = parameters
        }

        /// Finds the route from stop one to stop two.
        func findRoute() async throws {
            // Resets the route geometry and directions.
            routeGraphic.geometry = nil
            directions.removeAll()
            // Solves the route based on the route parameters.
            let routeResult = try await routeTask.solveRoute(using: routeParameters)
            if let firstRoute = routeResult.routes.first {
                // Updates the route geometry and directions.
                routeGraphic.geometry = firstRoute.geometry
                directions = firstRoute.directionManeuvers
            }
        }
    }
}

private extension Stop {
    /// The stop for the origin.
    static var one: Stop {
        let stop = Stop(point: Point(x: -13041171.537945, y: 3860988.271378, spatialReference: .webMercator))
        stop.name = "Origin"
        return stop
    }
    /// The stop for the destination.
    static var two: Stop {
        let stop = Stop(point: Point(x: -13041693.562570, y: 3856006.859684, spatialReference: .webMercator))
        stop.name = "Destination"
        return stop
    }
}

private extension URL {
    /// The URL for the route task.
    static var routeTask: URL {
        URL(string: "https://sampleserver6.arcgisonline.com/arcgis/rest/services/NetworkAnalysis/SanDiego/NAServer/Route")!
    }
}

#Preview {
    NavigationStack {
        FindRouteView()
    }
}

Your browser is no longer supported. Please upgrade your browser for the best experience. See our browser deprecation post for more details.