Skip to content

09 Alternatives

Overview

This tutorial will show you how to calculate a route with up to 3 alternatives.

Base

As always you can use your results from the previous Tutorial08 as a base for our new project.

A route overview

In this tutorial we implement a new UIViewController with name RouteInfoViewController showing the complete route and some metrics. The RouteInfoViewController will be displayed right after the route calculation.

And because we want to show three alternative routes, we put three RouteInfoViewController into a UITabBarController. This enables the user to switch between the alternatives.

Enabling alternative routes

For enabling alternative routes in the SDK, you have to set the appropriate RouteCalculationType NSDK_ROUTE_ALTERNATIVE. You can do this like that in your AppDelegate:

@implementation AppDelegate

...

- (void) setRouteOptions {
    NSDK_RouteOptions * options = [NSDK_Navigation getRoutingOptions:nil];

    ...

    [options setRouteCalculationType:NSDK_ROUTE_ALTERNATIVE];

Handling alternative routes

In this tutorial we show a mechanism to switch between all given alternatives. The following SDK methods are helpful for us:

  • [NSDK_Navigation getAlternativeRouteCount:] receive the amount of alternatives (including the default, currently <= 3)
  • [NSDK_Navigation getAlternativeRouteActive:] receive the active alternative
  • [NSDK_Navigation setAlternativeRouteActive:error:] set the active alternative (also highlights the active route trace)
  • [NSDK_Navigation getRouteInformationWithAlternative:error:] obtain the RouteInformation for the alternatives
  • [NSDK_Navigation getTrafficDelayOnRouteWithCompleteRoute:alternativeRouteId:error:] obtain the delay caused by traffic on the selected alternative

Because there is a lot of boilerplate UI Code in this tutorial, we show and describe only excerpts of the code we use in the Tutorial09 XCode project. Therefore we recommend to copy the following files to your project.

RouteInfoTabBarController.m, RouteInfoTabBarController.h
RouteInfoViewController.m, RouteInfoViewController.h

Furthermore it is recommended that you copy the complete "RouteInfoController" and "Route Info Tab Bar Controller" scene (including the three attached Navigation Controller) from the Tutorial09 XCode sample project storyboard to your storyboard.

Your storyboard should look like in the following picture:

Set the "Custom class" of the "RouteInfoController" scene to "RouteInfoViewController" and of the "Route Info Tab Bar Controller" scene to "RouteInfoTabBarController"

Create a Segue with name segueToRouteInfoTabBar between the ViewController and the UITabBarController. In our [ViewController calcNavigation]method we no longer start the Navigation instantly, but show the UITabBarController with the alternative routes. The following code triggers the aforementioned segue.

@implementation ViewController
...
- (void) showRouteInfoController {
    [self performSegueWithIdentifier: @"segueToRouteInfoTabBar" sender: self];
}

- (void) calcNavigation {

    NSDK_JobQueue * jobQueue = [NSDK_JobQueue sharedInstance];
    [jobQueue pushWithBlock:^{

        NSDK_WayPoint * start = [Helper getCurrentWayPointWithFallbackKarlsruhe];
        NSDK_WayPoint *  end = [[NSDK_WayPoint alloc] initWithX:[_destination  getX] y:[_destination getY]];

        NSError * err = NULL;
        NSDK_Tour * tour = [[NSDK_Tour alloc] initWithWayPoint:start];
        [tour addStation:end error:nil];

        [NSDK_Navigation calculateTour: tour observer:self error:&err];
        if (err) {
            NSLog(@"ViewController calculateTour NSError returns:%@", err);
        } else {
            //call 'searchTrafficInformation' here, because in the RouteInfoController we want to get the traffic delay
            NSDK_Position * startPosition = [[NSDK_Position alloc] initWithX:[start getX] y:[start getY]];
            [NSDK_Navigation searchTrafficInformationWithPosition:startPosition observer:nil error:&err];
            if (err) {
                NSLog(@"ViewController searchTrafficInformationWithPosition NSError returns:%@", err);
            }

            dispatch_async(dispatch_get_main_queue(), ^{
                [self showRouteInfoController];
            });
        }
    }];
}

As you can see in the code above, we added a call to [NSDK_Navigation searchTrafficInformationWithPosition:], because we have successfully calculated a route a few lines before and the SDK is so smart to fetch the traffic situation for the calculated route.

Create a new IBAction method for actually starting the navigation:

@implementation ViewController
...
- (IBAction)unwindToMainViewControllerStartNavigation:(UIStoryboardSegue*)sender {
    if (_gpsSimulationIsActive) {
        [[LocationManagerShared sharedManager] startGPSSimulation];
    }
    [_navigationLoop startNavigation];
}

In the RouteInfoViewController scene in the storyboard we have connected a button (the button with the green arrow) with this method (again through the "Exit" Icon in Interface Builder).

We have to revise our unwindToMainViewController and unwindToMainViewControllerSimulation methods a bit. That's because the iOS runtime often runs in View hierarchy issues, when we switch to another UIViewController (or call a segue) within these methods. Therefore we just set a flag, that the navigation calculation should be started (and after the calculation is finished, the segue to the RouteInfoViewController should be triggered) - and actually check the flag and do the work in our viewDidAppear method.

@interface ViewController ()
...
@property BOOL shallCalcNavigation;
@end

@implementation ViewController
...
- (void)viewDidAppear:(BOOL)animated {
    [super viewDidAppear:animated];

    ...

    if (_shallCalcNavigation && _destination != nil) {
        _shallCalcNavigation = NO;
        [self initProgressView];
        [self calcNavigation];
    }
}


- (IBAction)unwindToMainViewControllerSimulation:(UIStoryboardSegue*)sender {
    _gpsSimulationIsActive = YES;
    _shallCalcNavigation = YES;
}


- (IBAction)unwindToMainViewController:(UIStoryboardSegue*)sender {

    if (_gpsSimulationIsActive) {
        [[LocationManagerShared sharedManager]  stopGPSSimulation];
    }
    _gpsSimulationIsActive = NO;
    _shallCalcNavigation = YES;
}

Attention

Don't call [NSDK_Navigation getGuidanceInformation] before the user has chosen the alternative route. If you still do, the SDK will discard the alternative routes!

Therefore we stop our NavigationLoop when we leave our ViewController. We can stop the loop whenever a Segue will be performed:

@implementation ViewController

...

- (void)prepareForSegue:(UIStoryboardSegue *)segue sender:(id)sender {
    [_navigationLoop stopNavigation];
}

Now connect the UILabels and the UIMapView of the RouteInfoViewController scene to the corresponding IBOutlets in the header file. So Ctrl-Drag to the appropriate property.

#import <UIKit/UIKit.h>
#import "navicoreFramework/NSDK_Navigation.h"
#import "UIMapView.h"


@interface RouteInfoViewController : UIViewController

@property NSInteger routeAlternativeId;

@property (weak, nonatomic) IBOutlet UIMapView *uiMapView;
@property (weak, nonatomic) IBOutlet UILabel *labelDestinationDist;
@property (weak, nonatomic) IBOutlet UILabel *labelDestinationTime;
@property (weak, nonatomic) IBOutlet UILabel *labelTrafficDelay;
@property (weak, nonatomic) IBOutlet UILabel *labelToll;

@end

In RouteInfoViewController we show from left to right

  • the distance to destination,
  • the estimated time of arrival,
  • the delay caused by traffic and
  • the covered distance on toll roads.

All this information about the current route can be obtained by calling [NSDK_Navigation getRouteInformation:]. The [NSDK_RouteInformation] class can give you several infos like distance, duration, toll information and restrictions on your current route. To access the traffic delay we simply call [NSDK_Navigation getTrafficDelayOnRouteWithCompleteRoute:alternativeRouteId:error:] . Like in Tutorial06 we format the distances with [NSDK_RouteInformation getLocalizedDistanceString:quantify:error:].

@implementation RouteInfoViewController

- (void) viewWillAppear:(BOOL)animated {
    [super viewWillAppear:animated];
    NSLog(@"RouteInfoViewController routeAlternativeId:%ld viewWillAppear called", (long)_routeAlternativeId);
    [NSDK_Navigation setAlternativeRouteActive:(int)_routeAlternativeId error:nil];
    [self setLabelText];
}

- (void) setLabelText {
    NSError * error;
    NSDK_RouteInformation * routeInformation = [NSDK_Navigation getRouteInformationWithAlternative:_routeAlternativeId error:&error];

    //Distance
    NSString * distanceString = [NSDK_Navigation getLocalizedDistanceString:[routeInformation getLength] quantify:NO error:&error];
    _labelDestinationDist.text = distanceString;

    //Arrival time
    NSDate * date = [NSDate dateWithTimeIntervalSinceNow:[routeInformation getDuration]];
    NSDateFormatter *formatter = [[NSDateFormatter alloc] init];
    [formatter setDateFormat:@"HH:mm"];
    NSLog(@"RouteInfoViewController RouteInformation arrival date:%@", [formatter stringFromDate:date]);
    _labelDestinationTime.text = [formatter stringFromDate:date];

    //traffic delay
    int trafficTime = [NSDK_Navigation getTrafficDelayOnRouteWithCompleteRoute:YES alternativeRouteId:_routeAlternativeId error:&error];
    int hours = trafficTime / 3600;
    //remaining seconds
    trafficTime = trafficTime - hours * 3600;
    int minutes = trafficTime / 60;
    NSString *trafficDelay = [NSString stringWithFormat:@"+%dh %dmin", hours, minutes];
    _labelTrafficDelay.text = trafficDelay;
    NSLog(@"RouteInfoViewController TrafficDelay hours:%d minutes:%d", hours, minutes);

    //toll length
    int tollLength = [[routeInformation getTollInformation] getTotalLengthTruckToll];
    _labelToll.text = [NSDK_Navigation getLocalizedDistanceString:tollLength quantify:NO error:&error];
}

...
As you can see, we call the setLabelText method from the viewWillAppearmethod (and not the viewDidLoad method), because we want to respect the freshest traffic delay informations whenever the user switches between the different alternative routes within the UITabBarController (The viewDidLoad method is called only once by the runtime, no matter how often the user switches the view within the UITabBarController).

Furthermore we set the active route in the SDK with the call to [NSDK_Navigation setAlternativeRouteActive:error:]

And last, here is the essential code for instantiating and initialization the RouteInfoViewControllers within the RouteInfoTabBarController:

@implementation RouteInfoTabBarController
...

- (void)viewDidLoad {
    [super viewDidLoad];

    int routeCount = [NSDK_Navigation getAlternativeRouteCount:nil];

    NSMutableArray *tabbarViewControllers = [NSMutableArray arrayWithArray: [self viewControllers]];
    if (routeCount < 3) {
        [tabbarViewControllers removeObjectAtIndex:2];
    }
    if (routeCount < 2) {
        [tabbarViewControllers removeObjectAtIndex:1];
    }

    [self setViewControllers: tabbarViewControllers ];

    [self setRouteIdOnController:0];
    [self setRouteIdOnController:1];
    [self setRouteIdOnController:2];
}


- (void) setRouteIdOnController:(int) idx {

    if (idx >= [[self viewControllers] count]) {
        return;
    }

    NSError * error;
    NSDK_RouteInformation * routeInformation = [NSDK_Navigation getRouteInformationWithAlternative:idx error:&error];

    int seconds = [routeInformation getDuration];
    int hour = seconds / 3600;
    int mins = (seconds % 3600) / 60;
    NSString * str = [NSString stringWithFormat:@"%dH %dMIN", hour, mins];

    UITabBarItem * item = [self.tabBar.items objectAtIndex:idx];
    [item setTitle:NSLocalizedString(str, @"comment")];
    [item setTitlePositionAdjustment:UIOffsetMake(0, -10)];
    [item setTitleTextAttributes:[NSDictionary dictionaryWithObjectsAndKeys:
                                            [UIFont boldSystemFontOfSize:16], UITextAttributeFont, nil]
                                  forState:UIControlStateNormal];

    //add RouteInfoViewController to NavigationController
    RouteInfoViewController * routeInfoViewController = [self.storyboard instantiateViewControllerWithIdentifier:@"routeInfoControllerId"];
    UINavigationController * navigationController = (UINavigationController*)[[self viewControllers] objectAtIndex:idx];
    [navigationController addChildViewController:routeInfoViewController];

    routeInfoViewController.routeAlternativeId = idx;
}

It is possible that the SDK delivers less than 3 routes. Therefore we have to check on how many routes we get from the SDK and remove in case the SDK delivers less than 3 routes the superfluous Controller from the UITabBarController (initially the UITabBarController has 3 child controller (UINavigationController) defined in Interface Builder).

Voilà we should have our final result.

Implications on navigation

If a navigation is started with an alternative route higher than 0, this has implications to navigation. Because any of these alternative routes are not the arithmetical optimal route, there is a risk, that reroutes will switch to the optimal one. Therefore a special mechanism is implemented: The chosen route will be stored during the whole navigation and its underlying segments get a small preference in reroutings. This should lead to a good trade-off finding reasonable reroutes, but also keeping the chosen alternative.