Route lines styling
Enhance your navigation app by customizing the style of route lines and create visually appealing navigation experiences.
This example shows:
- How to customize the style of Navigation route lines

For all code examples, refer to Navigation Code Examples
RouteLinesStylingViewController view source
1import UIKit
2import NbmapNavigation
3import NbmapCoreNavigation
4import NbmapDirections
5
6class RouteLinesStylingViewController: UIViewController {
7
8 typealias ActionHandler = (UIAlertAction) -> Void
9
10 var trafficUnknownColor: UIColor = #colorLiteral(red: 0.1843137255, green: 0.4784313725, blue: 0.7764705882, alpha: 1)
11 var trafficLowColor: UIColor = #colorLiteral(red: 0.1843137255, green: 0.4784313725, blue: 0.7764705882, alpha: 1)
12 var trafficModerateColor: UIColor = #colorLiteral(red: 1, green: 0.5843137255, blue: 0, alpha: 1)
13 var trafficHeavyColor: UIColor = #colorLiteral(red: 1, green: 0.3019607843, blue: 0.3019607843, alpha: 1)
14 var trafficSevereColor: UIColor = #colorLiteral(red: 0.5607843137, green: 0.1411764706, blue: 0.2784313725, alpha: 1)
15 var routeCasingColor: UIColor = #colorLiteral(red: 0.28, green: 0.36, blue: 0.8, alpha: 1)
16 var routeAlternateColor: UIColor = #colorLiteral(red: 1, green: 0.65, blue: 0, alpha: 1)
17 var routeAlternateCasingColor: UIColor = #colorLiteral(red: 1.0, green: 0.9, blue: 0.4, alpha: 1)
18 var traversedRouteColor: UIColor = #colorLiteral(red: 0.1843137255, green: 0.4784313725, blue: 0.7764705882, alpha: 1)
19
20 var navigationMapView: NavigationMapView!
21
22 var currentRouteIndex = 0 {
23 didSet {
24 showCurrentRoute()
25 }
26 }
27
28 var currentRoute: Route? {
29 return routes?[currentRouteIndex]
30 }
31
32 var routes: [Route]? {
33 didSet {
34
35 guard let routes = routes,
36 let current = routes.first
37 else {
38 navigationMapView?.removeRoutes()
39 navigationMapView?.removeRouteDurationSymbol()
40 if let annotation = navigationMapView?.annotations?.last {
41 navigationMapView?.removeAnnotation(annotation)
42 }
43 return
44
45 }
46
47 navigationMapView?.showRoutes(routes)
48 navigationMapView?.showWaypoints(current)
49 navigationMapView?.showRouteDurationSymbol(routes)
50 }
51 }
52
53 func showCurrentRoute() {
54 guard let currentRoute = currentRoute else { return }
55
56 var routes = [currentRoute]
57 routes.append(contentsOf: self.routes!.filter {
58 $0 != currentRoute
59 })
60 navigationMapView.showRoutes(routes)
61 navigationMapView.showWaypoints(currentRoute)
62 }
63
64 // MARK: - UIViewController lifecycle methods
65 override func viewDidLoad() {
66 super.viewDidLoad()
67
68 setupNavigationMapView()
69 setupPerformActionBarButtonItem()
70 setupGestureRecognizers()
71 }
72
73 // MARK: - Setting-up methods
74 func setupNavigationMapView() {
75 navigationMapView = NavigationMapView(frame: view.bounds)
76 navigationMapView.autoresizingMask = [.flexibleWidth, .flexibleHeight]
77 navigationMapView.navigationMapDelegate = self
78 navigationMapView.userTrackingMode = .follow
79
80 view.addSubview(navigationMapView)
81 }
82
83 func setupPerformActionBarButtonItem() {
84 let settingsBarButtonItem = UIBarButtonItem(title: NSString(string: "\u{2699}\u{0000FE0E}") as String,
85 style: .plain,
86 target: self,
87 action: #selector(performAction))
88 settingsBarButtonItem.setTitleTextAttributes([.font: UIFont.systemFont(ofSize: 30)], for: .normal)
89 settingsBarButtonItem.setTitleTextAttributes([.font: UIFont.systemFont(ofSize: 30)], for: .highlighted)
90 navigationItem.rightBarButtonItem = settingsBarButtonItem
91 }
92
93 @objc func performAction(_ sender: Any) {
94 let alertController = UIAlertController(title: "Perform action",
95 message: "Select specific action to perform it", preferredStyle: .actionSheet)
96
97 let startNavigation: ActionHandler = { _ in self.startNavigation() }
98 let removeRoutes: ActionHandler = { _ in self.routes = nil }
99
100 let actions: [(String, UIAlertAction.Style, ActionHandler?)] = [
101 ("Start Navigation", .default, startNavigation),
102 ("Remove Routes", .default, removeRoutes),
103 ("Cancel", .cancel, nil)
104 ]
105
106 actions
107 .map({ payload in UIAlertAction(title: payload.0, style: payload.1, handler: payload.2) })
108 .forEach(alertController.addAction(_:))
109
110 if let popoverController = alertController.popoverPresentationController {
111 popoverController.barButtonItem = navigationItem.rightBarButtonItem
112 }
113
114 present(alertController, animated: true, completion: nil)
115 }
116
117 func setupGestureRecognizers() {
118 let longPressGestureRecognizer = UILongPressGestureRecognizer(target: self, action: #selector(handleLongPress(_:)))
119 navigationMapView.addGestureRecognizer(longPressGestureRecognizer)
120 }
121
122 @objc func startNavigation() {
123 guard let routes = routes else {
124 print("Please select at least one destination coordinate to start navigation.")
125 return
126 }
127
128 let navigationService = NBNavigationService(routes: routes, routeIndex: currentRouteIndex)
129
130 let navigationOptions = NavigationOptions(navigationService: navigationService)
131 let navigationViewController = NavigationViewController(for: routes,navigationOptions: navigationOptions)
132 navigationViewController.delegate = self
133 navigationViewController.modalPresentationStyle = .fullScreen
134 navigationViewController.routeLineTracksTraversal = true
135 present(navigationViewController, animated: true, completion: nil)
136 }
137
138 @objc func handleLongPress(_ gesture: UILongPressGestureRecognizer) {
139 guard let navigationMapView = navigationMapView, gesture.state == .began else {
140 return
141 }
142 let coordinates = navigationMapView.convert(gesture.location(in: navigationMapView), toCoordinateFrom: navigationMapView)
143 let destination = Waypoint(coordinate: coordinates, name: "\(coordinates.latitude),\(coordinates.longitude)")
144
145 addNewDestinationcon(coordinates: coordinates)
146
147 guard let currentLocation = navigationMapView.userLocation?.coordinate else { return}
148 let currentWayPoint = Waypoint.init(coordinate: currentLocation, name: "My Location")
149
150 requestRoutes(origin: currentWayPoint, destination: destination)
151 }
152
153 func addNewDestinationcon(coordinates: CLLocationCoordinate2D){
154 guard let mapView = navigationMapView else {
155 return
156 }
157
158 if let annotation = mapView.annotations?.last {
159 mapView.removeAnnotation(annotation)
160 }
161
162 let annotation = NGLPointAnnotation()
163 annotation.coordinate = coordinates
164 mapView.addAnnotation(annotation)
165 }
166
167 func requestRoutes(origin: Waypoint, destination: Waypoint){
168
169 let options = NavigationRouteOptions(origin: origin, destination: destination)
170
171 Directions.shared.calculate(options) { [weak self] routes, error in
172 guard let weakSelf = self else {
173 return
174 }
175 guard error == nil else {
176 print(error!)
177 return
178 }
179
180 guard let routes = routes else { return }
181
182 weakSelf.routes = routes
183 }
184 }
185
186 func routeStyleLayer(identifier: String,source: NGLSource) -> NGLStyleLayer {
187 let line = NGLLineStyleLayer(identifier: identifier, source: source)
188 line.lineWidth = NSExpression(format: "ngl_interpolate:withCurveType:parameters:stops:($zoomLevel, 'linear', nil, %@)", NBRouteLineWidthByZoomLevel)
189 line.lineColor = NSExpression(format: "TERNARY(isAlternateRoute == true, %@, NGL_MATCH(congestion, 'low' , %@, 'moderate', %@, 'heavy', %@, 'severe', %@, %@))", routeAlternateColor, trafficLowColor, trafficModerateColor, trafficHeavyColor, trafficSevereColor, trafficUnknownColor)
190 line.lineOpacity = NSExpression(forConstantValue: 1)
191 line.lineCap = NSExpression(forConstantValue: "round")
192 line.lineJoin = NSExpression(forConstantValue: "round")
193
194 return line
195 }
196
197 func routeCasingStyleLayer(identifier: String,source: NGLSource) -> NGLStyleLayer {
198 let lineCasing = NGLLineStyleLayer(identifier: identifier, source: source)
199
200 // Take the default line width and make it wider for the casing
201 lineCasing.lineWidth = NSExpression(format: "ngl_interpolate:withCurveType:parameters:stops:($zoomLevel, 'linear', nil, %@)", NBRouteLineWidthByZoomLevel.multiplied(by: 1.3))
202
203 lineCasing.lineColor = NSExpression(forConstantValue: routeCasingColor)
204
205 lineCasing.lineCap = NSExpression(forConstantValue: "round")
206 lineCasing.lineJoin = NSExpression(forConstantValue: "round")
207
208 lineCasing.lineOpacity = NSExpression(forConditional: NSPredicate(format: "isAlternateRoute == true"),
209 trueExpression: NSExpression(forConstantValue: 1),
210 falseExpression: NSExpression(forConditional: NSPredicate(format: "isCurrentLeg == true"), trueExpression: NSExpression(forConstantValue: 1), falseExpression: NSExpression(forConstantValue: 0.85)))
211
212 return lineCasing
213 }
214}
215
216// MARK: - NavigationMapViewDelegate methods
217extension RouteLinesStylingViewController: NavigationMapViewDelegate {
218
219 func navigationMapView(_ mapView: NavigationMapView, routeStyleLayerWithIdentifier identifier: String, source: NGLSource) -> NGLStyleLayer? {
220
221 return routeStyleLayer(identifier: identifier,source: source)
222 }
223
224 func navigationMapView(_ mapView: NavigationMapView, routeCasingStyleLayerWithIdentifier identifier: String, source: NGLSource) -> NGLStyleLayer? {
225 return routeCasingStyleLayer(identifier: identifier,source: source)
226 }
227}
228
229// MARK: - NavigationViewControllerDelegate methods
230extension RouteLinesStylingViewController: NavigationViewControllerDelegate {
231
232
233 func navigationViewControllerDidDismiss(_ navigationViewController: NavigationViewController, byCanceling canceled: Bool) {
234 dismiss(animated: true, completion: nil)
235 }
236
237 func navigationViewController(_ navigationViewController: NavigationViewController, routeStyleLayerWithIdentifier identifier: String, source: NGLSource) -> NGLStyleLayer? {
238 return routeStyleLayer(identifier: identifier,source: source)
239 }
240
241 func navigationViewController(_ navigationViewController: NavigationViewController, routeCasingStyleLayerWithIdentifier identifier: String, source: NGLSource) -> NGLStyleLayer? {
242 return routeCasingStyleLayer(identifier: identifier,source: source)
243 }
244}
The code first defines a number of colors that will be used to style the route lines. These colors are:
-
trafficUnknownColor: This color will be used to style route lines when the traffic conditions are unknown.
-
trafficLowColor: This color will be used to style route lines when the traffic conditions are light.
-
trafficModerateColor: This color will be used to style route lines when the traffic conditions are moderate.
-
trafficHeavyColor: This color will be used to style route lines when the traffic conditions are heavy.
-
trafficSevereColor: This color will be used to style route lines when the traffic conditions are severe.
-
routeCasingColor: This color will be used to style the casing of the route lines.
-
routeAlternateColor: This color will be used to style alternate route lines.
-
routeAlternateCasingColor: This color will be used to style the casing of alternate route lines.
-
traversedRouteColor: This color will be used to style routes that have already been traversed.
The code then defines a function called routeStyleLayer(). This function takes two parameters: an identifier and a source.
-
The identifier is used to identify the route style layer
-
The source is the source of the route data.
-
The function returns a NGLStyleLayer object that contains the style properties for the route line.
Inside the routeStyleLayer() function
-
It first sets the lineWidth property of the NGLStyleLayer object to a value that varies depending on the zoom level. The lineWidth property controls the width of the route line.
-
then sets the lineColor property of the NGLStyleLayer object to a value that depends on the traffic conditions and whether the route is an alternate route. The lineColor property controls the color of the route line.
-
then sets the lineOpacity property of the NGLStyleLayer object to a value of 1. The lineOpacity property controls the opacity of the route line.
-
then sets the lineCap property of the NGLStyleLayer object to a value of "round". The lineCap property controls the shape of the ends of the route line.
-
then sets the lineJoin property of the NGLStyleLayer object to a value of "round". The lineJoin property controls the shape of the corners of the route line.
The code then defines a function called routeCasingStyleLayer(). This function is similar to the routeStyleLayer() function, but it styles the casing of the route lines.
-
It sets the lineWidth property of the NGLStyleLayer object to a value that is 1.3 times the width of the route line.
-
Then sets the lineColor property of the NGLStyleLayer object to a value of routeCasingColor.
-
Then sets the lineCap property of the NGLStyleLayer object to a value of "round".
-
Then sets the lineJoin property of the NGLStyleLayer object to a value of "round".
-
Then sets the lineOpacity property of the NGLStyleLayer object to a value that depends on whether the route is an alternate route and whether it is the current route leg.
The code then implements
-
navigationMapView(_:routeStyleLayerWithIdentifier:source:) method of the NavigationMapViewDelegate protocol. This method is called by the NavigationMapView object when it needs to create a route style layer. The method returns the NGLStyleLayer object that was created by the routeStyleLayer() function.
-
navigationMapView(_:routeCasingStyleLayerWithIdentifier:source:) method of the NavigationMapViewDelegate protocol. This method is called by the NavigationMapView object when it needs to create a route casing style layer. The method returns the NGLStyleLayer object that was created by the routeCasingStyleLayer() function.