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
import UIKit
2
import NbmapNavigation
3
import NbmapCoreNavigation
4
import NbmapDirections
5
6
class 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
217
extension 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
230
extension 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.

© 2024 NextBillion.ai all rights reserved.