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

For all code examples, refer to Navigation Code Examples

RouteLinesStylingViewController view source

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

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.