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
docs-image

For all code examples, refer to Navigation Code Examples

RouteLinesStylingViewController view source

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
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
import UIKit
import NbmapNavigation
import NbmapCoreNavigation
import NbmapDirections

class RouteLinesStylingViewController: UIViewController {
  
   typealias ActionHandler = (UIAlertAction) -> Void
  
   var trafficUnknownColor: UIColor =  #colorLiteral(red: 0.1843137255, green: 0.4784313725, blue: 0.7764705882, alpha: 1)
   var trafficLowColor: UIColor =  #colorLiteral(red: 0.1843137255, green: 0.4784313725, blue: 0.7764705882, alpha: 1)
   var trafficModerateColor: UIColor =  #colorLiteral(red: 1, green: 0.5843137255, blue: 0, alpha: 1)
   var trafficHeavyColor: UIColor =  #colorLiteral(red: 1, green: 0.3019607843, blue: 0.3019607843, alpha: 1)
   var trafficSevereColor: UIColor = #colorLiteral(red: 0.5607843137, green: 0.1411764706, blue: 0.2784313725, alpha: 1)
   var routeCasingColor: UIColor = #colorLiteral(red: 0.28, green: 0.36, blue: 0.8, alpha: 1)
   var routeAlternateColor: UIColor = #colorLiteral(red: 1, green: 0.65, blue: 0, alpha: 1)
   var routeAlternateCasingColor: UIColor = #colorLiteral(red: 1.0, green: 0.9, blue: 0.4, alpha: 1)
   var traversedRouteColor: UIColor = #colorLiteral(red: 0.1843137255, green: 0.4784313725, blue: 0.7764705882, alpha: 1)
  
   var navigationMapView: NavigationMapView!
  
   var currentRouteIndex = 0 {
       didSet {
           showCurrentRoute()
       }
   }
  
   var currentRoute: Route? {
       return routes?[currentRouteIndex]
   }
  
   var routes: [Route]? {
       didSet {
          
           guard let routes = routes,
                 let current = routes.first
           else {
               navigationMapView?.removeRoutes()
               navigationMapView?.removeRouteDurationSymbol()
               if let annotation = navigationMapView?.annotations?.last {
                   navigationMapView?.removeAnnotation(annotation)
               }
               return
              
           }
          
           navigationMapView?.showRoutes(routes)
           navigationMapView?.showWaypoints(current)
           navigationMapView?.showRouteDurationSymbol(routes)
       }
   }
  
   func showCurrentRoute() {
       guard let currentRoute = currentRoute else { return }
      
       var routes = [currentRoute]
       routes.append(contentsOf: self.routes!.filter {
           $0 != currentRoute
       })
       navigationMapView.showRoutes(routes)
       navigationMapView.showWaypoints(currentRoute)
   }
  
   // MARK: - UIViewController lifecycle methods
   override func viewDidLoad() {
       super.viewDidLoad()
      
       setupNavigationMapView()
       setupPerformActionBarButtonItem()
       setupGestureRecognizers()
   }
  
   // MARK: - Setting-up methods
   func setupNavigationMapView() {
       navigationMapView = NavigationMapView(frame: view.bounds)
       navigationMapView.autoresizingMask = [.flexibleWidth, .flexibleHeight]
       navigationMapView.navigationMapDelegate = self
       navigationMapView.userTrackingMode = .follow
      
       view.addSubview(navigationMapView)
   }
  
   func setupPerformActionBarButtonItem() {
       let settingsBarButtonItem = UIBarButtonItem(title: NSString(string: "\u{2699}\u{0000FE0E}") as String,
                                                   style: .plain,
                                                   target: self,
                                                   action: #selector(performAction))
       settingsBarButtonItem.setTitleTextAttributes([.font: UIFont.systemFont(ofSize: 30)], for: .normal)
       settingsBarButtonItem.setTitleTextAttributes([.font: UIFont.systemFont(ofSize: 30)], for: .highlighted)
       navigationItem.rightBarButtonItem = settingsBarButtonItem
   }
  
   @objc func performAction(_ sender: Any) {
       let alertController = UIAlertController(title: "Perform action",
                                               message: "Select specific action to perform it", preferredStyle: .actionSheet)
      
       let startNavigation: ActionHandler = { _ in self.startNavigation() }
       let removeRoutes: ActionHandler = { _ in self.routes = nil }
      
       let actions: [(String, UIAlertAction.Style, ActionHandler?)] = [
           ("Start Navigation", .default, startNavigation),
           ("Remove Routes", .default, removeRoutes),
           ("Cancel", .cancel, nil)
       ]
      
       actions
           .map({ payload in UIAlertAction(title: payload.0, style: payload.1, handler: payload.2) })
           .forEach(alertController.addAction(_:))
      
       if let popoverController = alertController.popoverPresentationController {
           popoverController.barButtonItem = navigationItem.rightBarButtonItem
       }
      
       present(alertController, animated: true, completion: nil)
   }
  
   func setupGestureRecognizers() {
       let longPressGestureRecognizer = UILongPressGestureRecognizer(target: self, action: #selector(handleLongPress(_:)))
       navigationMapView.addGestureRecognizer(longPressGestureRecognizer)
   }
  
   @objc func startNavigation() {
       guard let routes = routes else {
           print("Please select at least one destination coordinate to start navigation.")
           return
       }
      
       let navigationService = NBNavigationService(routes: routes, routeIndex: currentRouteIndex)
      
       let navigationOptions = NavigationOptions(navigationService: navigationService)
       let navigationViewController = NavigationViewController(for: routes,navigationOptions: navigationOptions)
       navigationViewController.delegate = self
       navigationViewController.modalPresentationStyle = .fullScreen
       navigationViewController.routeLineTracksTraversal = true
       present(navigationViewController, animated: true, completion: nil)
   }
  
   @objc func handleLongPress(_ gesture: UILongPressGestureRecognizer) {
       guard let navigationMapView = navigationMapView, gesture.state == .began else {
           return
       }
       let coordinates = navigationMapView.convert(gesture.location(in: navigationMapView), toCoordinateFrom: navigationMapView)
       let destination = Waypoint(coordinate: coordinates, name: "\(coordinates.latitude),\(coordinates.longitude)")
      
       addNewDestinationcon(coordinates: coordinates)
      
       guard let currentLocation =  navigationMapView.userLocation?.coordinate else { return}
       let currentWayPoint = Waypoint.init(coordinate: currentLocation, name: "My Location")
      
       requestRoutes(origin: currentWayPoint, destination: destination)
   }
  
   func addNewDestinationcon(coordinates: CLLocationCoordinate2D){
       guard let mapView = navigationMapView else {
           return
       }
      
       if let annotation = mapView.annotations?.last {
           mapView.removeAnnotation(annotation)
       }
      
       let annotation = NGLPointAnnotation()
       annotation.coordinate = coordinates
       mapView.addAnnotation(annotation)
   }
  
   func requestRoutes(origin: Waypoint, destination: Waypoint){
      
       let options = NavigationRouteOptions(origin: origin, destination: destination)
      
       Directions.shared.calculate(options) { [weak self] routes, error in
           guard let weakSelf = self else {
               return
           }
           guard error == nil else {
               print(error!)
               return
           }
          
           guard let routes = routes else { return }
          
           weakSelf.routes = routes
       }
   }
  
   func routeStyleLayer(identifier: String,source: NGLSource) -> NGLStyleLayer {
       let line = NGLLineStyleLayer(identifier: identifier, source: source)
       line.lineWidth = NSExpression(format: "ngl_interpolate:withCurveType:parameters:stops:($zoomLevel, 'linear', nil, %@)", NBRouteLineWidthByZoomLevel)
       line.lineColor = NSExpression(format: "TERNARY(isAlternateRoute == true, %@, NGL_MATCH(congestion, 'low' , %@, 'moderate', %@, 'heavy', %@, 'severe', %@, %@))", routeAlternateColor, trafficLowColor, trafficModerateColor, trafficHeavyColor, trafficSevereColor, trafficUnknownColor)
       line.lineOpacity = NSExpression(forConstantValue: 1)
       line.lineCap = NSExpression(forConstantValue: "round")
       line.lineJoin = NSExpression(forConstantValue: "round")
      
       return line
   }
  
   func routeCasingStyleLayer(identifier: String,source: NGLSource) -> NGLStyleLayer {
       let lineCasing = NGLLineStyleLayer(identifier: identifier, source: source)
      
       // Take the default line width and make it wider for the casing
       lineCasing.lineWidth = NSExpression(format: "ngl_interpolate:withCurveType:parameters:stops:($zoomLevel, 'linear', nil, %@)", NBRouteLineWidthByZoomLevel.multiplied(by: 1.3))
      
       lineCasing.lineColor = NSExpression(forConstantValue: routeCasingColor)
      
       lineCasing.lineCap = NSExpression(forConstantValue: "round")
       lineCasing.lineJoin = NSExpression(forConstantValue: "round")
      
       lineCasing.lineOpacity = NSExpression(forConditional: NSPredicate(format: "isAlternateRoute == true"),
                                             trueExpression: NSExpression(forConstantValue: 1),
                                             falseExpression: NSExpression(forConditional: NSPredicate(format: "isCurrentLeg == true"), trueExpression: NSExpression(forConstantValue: 1), falseExpression: NSExpression(forConstantValue: 0.85)))
      
       return lineCasing
   }
}

// MARK: - NavigationMapViewDelegate methods
extension RouteLinesStylingViewController: NavigationMapViewDelegate {
  
   func navigationMapView(_ mapView: NavigationMapView, routeStyleLayerWithIdentifier identifier: String, source: NGLSource) -> NGLStyleLayer? {
      
       return routeStyleLayer(identifier: identifier,source: source)
   }
  
   func navigationMapView(_ mapView: NavigationMapView, routeCasingStyleLayerWithIdentifier identifier: String, source: NGLSource) -> NGLStyleLayer? {
       return routeCasingStyleLayer(identifier: identifier,source: source)
   }
}

// MARK: - NavigationViewControllerDelegate methods
extension RouteLinesStylingViewController: NavigationViewControllerDelegate {
  
  
   func navigationViewControllerDidDismiss(_ navigationViewController: NavigationViewController, byCanceling canceled: Bool) {
       dismiss(animated: true, completion: nil)
   }
  
   func navigationViewController(_ navigationViewController: NavigationViewController, routeStyleLayerWithIdentifier identifier: String, source: NGLSource) -> NGLStyleLayer? {
       return routeStyleLayer(identifier: identifier,source: source)
   }
  
   func navigationViewController(_ navigationViewController: NavigationViewController, routeCasingStyleLayerWithIdentifier identifier: String, source: NGLSource) -> NGLStyleLayer? {
       return routeCasingStyleLayer(identifier: identifier,source: source)
   }
}

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.

Have Questions ?