Skip to content

10 Guided navigation

Overview

This tutorial will show you how to calculate a tour with multiple destinations. Parts of the tour can be standard A to B routings, but they also can be guided navigation routings. Guided navigation routings are routings that lead over a list of SVPs (silent via points).

To accomplish this we will bundle a file with a simple JSON format into the app.

Base

You can use your results from the previous Tutorial09 as a base for our new project.

Tour calculation

The NavigationSDK offers extended capabilites to calculate tours.

A tour is defined to be a route with one or multiple stations. A tour can contain "regular" stations and SVP stations. Regular stations are stations which are defined solely by a geo-coordinate. SVP stations are defined by a set of coordinates with directions (and other attributes), which describe a preferred way to get to a destination.

To calculate a tour, do the following:

  • Create a NSDK_Tour object with [NSDK_Tour initWithWayPoint:] and pass the start position
  • Add as many stations as you like with [NSDK_Tour addSVPs:error:] or [NSDK_Tour addStation:error:]
  • Call [NSDK_Navigation calculateTour:observer:error:] to calculate the tour

Example JSON data format

Our example data format is a simple json array containing regular and/or svp destinations. A regular destination is basically a destination at point x,y in Mercator coordinates. An SVP destination is a trace of points describing the route from the previous destination to the last point of the SVPs.

Attention

SVP stations require a course. For this example we assume the course is given with North = 0 degrees and clockwise orientation. Please note that the SDK only accepts the course with East = 0 and counter clockwise orientation. The SDK has a helper function to convert this: [NSDK_Navigation convertGeoHeading].

[
    {
        "type" : "svp",
        "points" : [
            {"x":751322,"y":6696841,"course":193},
            {"x":753691,"y":6692971,"course":218},
            {"x":753590,"y":6692930,"course":257},
            {"x":753486,"y":6692925,"course":307},
            {"x":753464,"y":6692964,"course":354},
            {"x":753479,"y":6693010,"course":38},
            {"x":756197,"y":6692896,"course":128}
        ]
    },
    {
        "type" : "regular",
        "point" :
        {
            "x": 756107,
            "y": 6658347
        }
    }
]

JSON File Bundling

For this tutorial the JSON File has be put into the app bundle. The file is already included into the "res" Folder. If you want to edit the file, just open it in "res/data/guidedNavigation.json"

TourCalculator

We implement the tour logic in a new class named "TourCalculator". The implementation is straight forward. First, the destinations from the JSON file are parsed and passed as a new tour to the NavigationSDK. We call createTour with our current position. Then we add all the destinations with addStationToTour and addSVPsToTour. Finally we call prepareNavigationForTour with the tourObserver from our ViewController to calculate the route. If route calculation fails, you can check the error code of prepareNavigationForTour and the section of the tour the error belongs to.

The header file contains just one visible method:

#import <Foundation/Foundation.h>
#import "navicoreFramework/NSDK_Navigation.h"

@interface TourCalculator : NSObject

@property id <NSDK_Observer> tourObserver;

- (BOOL) startTour:(NSError**) error;

@end
#import "TourCalculator.h"
#import "Helper.h"


@implementation TourCalculator

static NSString *const JSON_FILENAME = @"res/data/guidedNavigation";
static NSString *const JSON_FILENAME_TYPE = @"json";


- (BOOL) startTour:(NSError**) error {

    NSArray * jsonArr = [self readJsonFileFromBundleIntoArray];
    if (jsonArr == nil) {
        if (error) {
            NSString * desc = [NSString stringWithFormat:@"Json file %@.%@ not found or not parseable.", JSON_FILENAME, JSON_FILENAME_TYPE ];
            NSDictionary *userInfo = @{NSLocalizedDescriptionKey: NSLocalizedString(desc, nil)};
            *error = [NSError errorWithDomain:@"com.ptvag.iostutorial"
                                         code:-200
                                     userInfo:userInfo];
        }
        return NO;
    }

    NSError * _error;
    NSDK_Tour * tour = [[NSDK_Tour alloc] initWithWayPoint:[Helper getCurrentWayPointWithFallbackKarlsruhe]];

    for (NSDictionary * dict in jsonArr) {
      NSString * type = [dict valueForKey:@"type"];
      if ([type isEqualToString:@"svp"]) {
          NSArray * points = [dict valueForKey:@"points"];
          NSArray * svps = [self getSVPWayPointsFromJson:points];
          [tour addSVPs:svps error:&_error];
      } else if ([type isEqualToString:@"regular"]) {
          NSDictionary * dictPoint = [dict valueForKey:@"point"];
          NSDK_WayPoint * wayPoint = [self getRegularWayPointFromJson:dictPoint];
          [tour addStation:wayPoint error:&_error];
      }
    }

    [NSDK_Navigation calculateTour:tour observer:_tourObserver error:&_error];

    if (_error) {
        NSLog(@"TourCalculator calculateTour returns error:%ld desc:%@", (long)_error.code, _error.localizedDescription);
        if (error) {
            *error = _error;
        }
        return NO;
    }
    return YES;
}



#pragma mark private methods
- (NSArray*) readJsonFileFromBundleIntoArray {
    NSString* path = [[NSBundle mainBundle] pathForResource:JSON_FILENAME
                                                     ofType:JSON_FILENAME_TYPE];
    NSString* content = [NSString stringWithContentsOfFile:path
                                                  encoding:NSUTF8StringEncoding
                                                     error:NULL];
    if (content==nil) {
        return nil;
    }

    NSError * error;
    NSArray* jsonArray = [NSJSONSerialization
                     JSONObjectWithData:[content dataUsingEncoding:NSUTF8StringEncoding ]
                     options:kNilOptions
                     error:&error];

    return jsonArray;
}


- (NSDK_WayPoint*) getRegularWayPointFromJson:(NSDictionary*) dictPoint {
    NSDK_WayPoint* ret;

    int x = [[dictPoint valueForKey:@"x"]  intValue];
    int y = [[dictPoint valueForKey:@"y"]  intValue];
    ret = [[NSDK_WayPoint alloc] initWithX:x y:y];
    return ret;
}


- (NSArray*) getSVPWayPointsFromJson:(NSArray*) points {

    NSMutableArray * ret = [[NSMutableArray alloc] init];

    for (NSDictionary * dictPoints in points) {
        int x = [[dictPoints valueForKey:@"x"]  intValue];
        int y = [[dictPoints valueForKey:@"y"]  intValue];
        int course = [[dictPoints valueForKey:@"course"]  intValue];
        NSDK_SVPWayPoint * svpWayPoint = [self buildSVPWayPointWithX:x y:y course:course];
        [ret addObject:svpWayPoint];
    }
    return ret;
}


- (NSDK_SVPWayPoint*) buildSVPWayPointWithX:(int) x y:(int) y course:(int) course {
    return [[NSDK_SVPWayPoint alloc] initWithX:x y:y direction:[NSDK_Navigation convertGeoHeading:course]];
}
@end

Call the TourCalculator

To actually use the TourCalculator, we have to add a fourth button to the "Search View Controller Scene". Label the button with "Guided Navigation". Add an IBACTION function to the ViewController like that:

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

@implementation ViewController
- (IBAction)unwindToMainViewControllerGuidedNavigation:(UIStoryboardSegue*)sender {
    if (_gpsSimulationIsActive) {
        [[LocationManagerShared sharedManager]  stopGPSSimulation];
    }
    _gpsSimulationIsActive = NO;
    _calcGuidedNavigation = YES;
}

Connect the button to the new method by Ctrl-Drag from the button to the "Exit" Icon and select the new method.

Now we have to check the new flag in our viewDidAppear method and calculate the guided navigation tour:

@implementation ViewController

...

- (void)viewDidAppear:(BOOL)animated {
    [super viewDidAppear:animated];
    ...
    if (_shallCalcNavigation && _destination != nil) {
            _shallCalcNavigation = NO;
            [self initProgressView];
            [self calcNavigation];
    } else if (_calcGuidedNavigation) {
            _calcGuidedNavigation = NO;
            [self initProgressView];
            [self calcGuidedNavigationTour];
    }
}

- (void) calcGuidedNavigationTour {
    TourCalculator * tourCalculator = [[TourCalculator alloc] init];
    [tourCalculator setTourObserver:self];

    NSDK_JobQueue * jobQueue = [NSDK_JobQueue sharedInstance];
    [jobQueue pushWithBlock:^{
        NSError * error;
        if ([tourCalculator startTour:&error]) {
            dispatch_async(dispatch_get_main_queue(), ^{
                [self showRouteInfoController];
            });
        } else {
            dispatch_async(dispatch_get_main_queue(), ^{
                [_progressContainerView removeFromSuperview];
                NSLog(@"TourCalculator startTour returns error:%ld desc:%@",
                                (long)error.code, error.localizedDescription);
            });
        }
    }];
}

Alternatives

Attention

Alternative routes are currently only supported for the first destination of your tour and only if it is a regular destination.

Switch to next destination

Currently yout have to create a new tour, add all destinations except the first and call [NSDK_Navigation calculateTour:observer:error:] again. This will probably change in a future release.