Skip to content

05 Route calculation

Overview

In this tutorial you will learn how to calculate a route from A to B and how to influence the routing by setting your vehicle parameters.

Base

You can use your results from Tutorial04 as a base for our new project.

Retrieving the destination

In the last tutorial we added two UIViewControllers (SearchViewController, ResultViewController) to search for addresses. To start a navgigation with the search result we first add a UIButton to the SearchViewController. Therefore, you can copy the UIButton within the Main.storyboard from ViewController to SearchViewController.

When the user clicks the Button we want to go back to the ViewController. So once agein, we need an Exit-Method. For that, define the following method in ViewController.m.

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

Open Main.storyboard and Ctrl-Drag from the UIButton you just created in the Search View Controller Scene to the Exit icon within the same Scene. Choose the method unwindToMainViewController in the Dialog that pops up.

Start the app and verify that the segue to the ViewController is working.

In the next step, we pass the search result to the ViewController. First, define a property destination in ViewController.h

@property (retain, nonatomic) NSDK_Position *destination;

... and extend the "prepareForSegue" Method in the SearchViewController like that:

@implementation SearchViewController

...

- (NSDK_SearchResult*) getCurrentBestSearchResult {
    if (_mCurrentHNRResult != nil) {
        return _mCurrentHNRResult;
    }
    if (_mCurrentStreetResult != nil){
        return _mCurrentStreetResult;
    }
    return _mCurrentTownResult;
}

- (void)prepareForSegue:(UIStoryboardSegue *)segue sender:(id)sender {

    if ([[segue destinationViewController] isKindOfClass:[ResultViewController class]]) {
        ResultViewController * resultViewController = [segue destinationViewController];
        resultViewController.searchViewController = self;

        if ([[segue identifier] isEqualToString:@"segueSearchViewToResultViewTown"]) {
            _searchKind = NSDK_SK_TOWNBYNAME_SORTED;
            _mCurrentStreetResult = nil;
            _mCurrentHNRResult = nil;
        } else if ([[segue identifier] isEqualToString:@"segueSearchViewToResultViewStreet"]) {
            _searchKind = NSDK_SK_STREETBYNAME;
        }  else if ([[segue identifier] isEqualToString:@"segueSearchViewToResultViewHNR"]) {
            _searchKind = NSDK_SK_HNR;
        }
    } else if ([[segue destinationViewController] isKindOfClass:[ViewController class]]) {
        ViewController * viewController = [segue destinationViewController];
        [viewController setDestination:[[self getCurrentBestSearchResult] getPosition]];
    }
}

In the new "else" branch we check which of the three search results are set. The finest/best result will be passed to our ViewController for the route calculation.

Calculating a route

Every long lasting operation should be performed in a background thread to avoid stalling the GUI thread and therefore the user interactions. So we put long lasting operations in a seperate thread. The NavigationSDK helps you with a simple worker thread (the [NSDK_JobQueue]) where you can queue your long lasting NavigationSDK operations.

Hint

You can easily use your own asynchronous mechanism. But be aware that most of the NavigationSDK functions are currently mutual exclusive and cannot be called concurrently. So try to call only one NavigationSDK method at a time to avoid locks in your application.

So we will wrap our route calculation call in an Objective-C Block and push that Block into the NSDK_JobQueue.

To calculate a route from our current position to our given destination we create a start and end [NSDK_WayPoint] object and put these into a new created NSDK_Tour object. The tour object is then passed to the function [NSDK_Navigation calculateTour:observer:error:] which actually calculates the route.

The appropriate functions in ViewController.m look like this:

- (IBAction)unwindToMainViewController:(UIStoryboardSegue*)sender {
     if (_destination != nil ) {
         [self calcNavigation];
     }
}


- (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:nil error:&err];
        if (err) {
            NSLog(@"calculateTour NSError returns:%@", err);
        }
    }];
}

To get this compiled, copy the File "Helper.m" and "Helper.h" from the Tutorial05 Xcode Project to your Project.

The result for an example route from Karlsruhe to Stuttgart:

Attention

Try to avoid calling other NavigationSDK methods on another thread while the route calculation is running. For example drawing the map on the GUI thread while calculating the route will lock the GUI thread because most NavigationSDK methods are protected by the same mutex and cannot be called in a concurrent way.

To avoid calling other NavigationSDK methods on another thread while the route calculation is running, we push our [NavigationLoop _run] and our [NSDK_GPSManager pushLocation:] call also to the [NSDK_JobQueue]. So we are save that all recurring NavigationSDK methods are called on the same thread.

@implementation NavigationLoop
...
- (void)run:(NSTimer*)theTimer {
    NSDK_JobQueue * jobQueue = [NSDK_JobQueue sharedInstance];
    [jobQueue pushWithSelector:@selector(_run) target:self];
}
@implementation LocationManagerShared
    ...
- (void) pushLocationInSDK:(CLLocation*) location {
  NSDK_JobQueue * jobQueue = [NSDK_JobQueue sharedInstance];
  [jobQueue pushWithBlock:^{
      [[NSDK_GPSManager sharedInstance] pushLocation:location error:nil];
  }];
}

And because we do the work in a Non-UI Thread, the call to [_uiMapView refresh] has to be dispatched to the UI Thread. So change the last line of the method _run like this:

@implementation NavigationLoop
...
- (void) _run {
    NSDK_GPSData * nsdk_gpsdata = [[NSDK_GPSManager sharedInstance] getCurrentPosition:nil];
    if (nsdk_gpsdata != nil && [nsdk_gpsdata getFix] >= 0) {

        NSLog(@"update MapMarker with course:%d mercX:%d mercY:%d", [nsdk_gpsdata getCourse], [[nsdk_gpsdata getGPSPositionMerc] getX], [[nsdk_gpsdata getGPSPositionMerc] getY]);
        [_uiMapView setMapMarkerWithCenter:[nsdk_gpsdata getGPSPositionMerc] orientation:[nsdk_gpsdata getCourse] northernAdjust:NO style:MapMarkerStyleDirected];

        [_uiMapView setCenterWithPosition:[nsdk_gpsdata getGPSPositionMerc]];
        dispatch_async(dispatch_get_main_queue(), ^{[_uiMapView refresh];});
    }
}

Retrieving progress information

Users should be notified about the progress of every long lasting operation. So most of the long lasting operations in the NavigatioSDK can be called with an [NSDK_Observer] to get feedback about their progress.

For preparation, we have to include some UI Code to be ready to show a progress bar. Add a progressView and a progressContainerView property in ViewController.m:

@interface ViewController ()
...
@property UIView * progressContainerView;
@property UIProgressView *progressView;
@end

Add an initialization method for the properties:

- (void) initProgressView {
    _progressContainerView = [[UIView alloc] initWithFrame:self.view.frame];
    [_progressContainerView setBackgroundColor:[UIColor blackColor]];
    [_progressContainerView setAlpha:0.6];

    self.progressView = [[UIProgressView alloc] initWithProgressViewStyle:UIProgressViewStyleDefault];
    self.progressView.center = _progressContainerView.center;
    [self.progressView setTransform:CGAffineTransformMakeScale(2.0, 4.0)];

    [_progressContainerView addSubview:self.progressView];

    [self.view addSubview:_progressContainerView];
}

...

- (IBAction)unwindToMainViewController:(UIStoryboardSegue*)sender {
     if (_destination != nil ) {
         [self initProgressView];
         [self calcNavigation];
     }
}

To monitor the route calculation progress we implement the [NSDK_Observer] protocol in ViewController and pass self to calculateRoute.

@interface ViewController : UIViewController <NSDK_Observer>
@implementation ViewController
...
- (void) calcNavigation {
    ...
        [NSDK_Navigation calculateTour: tour observer:self error:&err];
    ...    
}

The protocol method implementation actually sets the progress in the UIProgressView and removes the view when the route calculation is finished:

@implementation ViewController
...
- (void) onProgress:(int) current total:(int) total jobid:(int) job {

    dispatch_async(dispatch_get_main_queue(), ^{
        [_progressView setProgress:(float) current / total animated:YES];

        if (total == current) {
            [_progressContainerView removeFromSuperview];
            _progressContainerView = nil;

        }
    });
}
- (void) onFinished:(NSError*) error jobid:(int) job {
    NSLog(@"ViewController onFinished called (NSDK_Observer protocol function)");
}

Attention

The onProgress callback will be called from the thread in which the route calculation was started from. So in our example from the [NSDK_JobQueue] thread. So changes to the UI have to be performed by dispatching them to the main queue (the UI Thread) - as you can see in the method above with the call to dispatch_async(dispatch_get_main_queue(), ^{ ....

RoutingOptions

The NavigationSDK brings several methods to influence the result of the route calculation. For example you can avoid toll roads, decide, what kind of vehicle you are driving or if you want a routing optimized for short way or short time. You can manually change every single routing parameter if you wish, but many of the parameters need a deeper knowledge on how they influence the routing to get the wanted effect on the route (like the speed tables).

Loading a vehicle profile

To avoid setting the routing options manually and to get a quick way to set them for a particular vehicle, we use so called "profiles" which describes certain standard vehicles like trucks, cars, transporters and so on. In these profiles (which are text files) the values for the relevant routing and lorry options are preset (some of the options are not in the profiles file, but will be adapted internally to the used vehicle). We highly recommend to use the profile loading mechanism which the SDK provides instead of setting the options manually, especially the speed tables.

Loading a profile is done by calling the method [NSDK_Navigation loadVehicleProfile: error:]. The function takes as parameter the full path to the profile file which should be loaded.

We deliver a set of profiles in a directory called 'profiles' under the data directory of the tutorial app data.

Add the following to the AppDelegate.m file:

- (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions {

...

  [NSDK_Navigation loadVehicleProfile:[srcPath stringByAppendingString:@"/data/profiles/truck75.dat"] error:&error];
}

We load the profile "truck75.dat" which has all options set for a 7.5t truck. For making the profile selectable by the user of the app, we can retrieve a localized name of every profile with the following method:

  NSString * profileName = [NSDK_Navigation getProfileName:[srcPath stringByAppendingString:@"/data/profiles/trucktrailer40.dat"] countryCode:@"de" error:&error];    
  NSLog(@"ProfileName:%@", profileName);

The country code parameter determines the translation. If nil is passed in, english will be returned (if available).

Manually setting the routing options

Setting the routing options manually is also possible but only recommended for experts. By calling [NSDK_Navigation getRoutingOptions] we retrieve the default [NSDK_RouteOptions] which we can alter to our needs afterwards. The following example is only a starting point to play with and alter it to your custom wishes.

@implementation AppDelegate

...

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

    BOOL avoidToll = YES;

    if (avoidToll) {
        [options setTollFactor:90];
        [options setTruckTollFactor:90];
        [options setUseTollLayer:YES];
        [options setUseTruckTollLayer:YES];
    } else {
        [options setUseTollLayer:NO];
        [options setUseTruckTollLayer:NO];
    }

    [options setTimeFactor:80];

    NSDK_LorryOptions * lorry = [NSDK_Navigation getLorryOptions:nil];
    int vehicle = 0;

    if (vehicle == 0) {
        [options setLorry:YES];
        [lorry setWeight:((short)75)];
        [lorry setHeight:((short)37)];
    } else if (vehicle == 1) {
        [options setLorry:YES];
    } else {
        options = nil;
    }


    //NSDK_RVT_USER is member of NS_ENUM NSDK_RoutingVehicleType
    [options setRoutingVehicle:NSDK_RVT_USER];

    NSArray * speedTableRouteList = @[@82, @75, @74, @66, @60, @45, @56, @51, @48, @38, @35, @23, @30, @30, @15];
    NSArray * speedTableCalc = @[@84, @76, @74, @66, @60, @40, @50, @38, @26, @22, @11, @5, @5, @5, @2];

    [options setSpeedTable_RouteList:speedTableRouteList];
    [options setSpeedTable_Calc:speedTableCalc];

    NSError * error;
    [NSDK_Navigation setRoutingOptions:options error:&error];
    if (error) {
        NSLog(@"setRoutingOptions error:%@", error);
    }
    [NSDK_Navigation setLorryOptions:lorry error:&error];
    if (error) {
        NSLog(@"setLorryOptions error:%@", error);
    }
}