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 normal A to B routings and also can be guided navigation routings. Guided navigation routings are routings that go through a list of SVPs (silent via points).

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

Base

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

Tour calculation

A Tour in the context of this SDK is a route with more than two coordinates. The first coordinate is the start coordinate, the last one is the destination. A tour can contain "regular" stations ([NSDK_WayPoint]) and SVP stations ([NSDK_SVPWayPoint]). 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 way to get to a destination.

To calculate a tour, do the following:

  • Create a NSDK_Tour object with [NSDK_Tour initWithWayPoint:] with the start position
  • Add as much following 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. A SVP destination is a trace of points describing the route from the previous destination to the last point of the SVPs.

Attention

SVP stations need a course. For this example we assume the course is given in the following format: North = 0 and clockwise orientation. The SDK only accepts the course in the following format: 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. First 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

Alternatives are currently only supported for the first destination of your tour. And only if it is a regular destination.

Switch to next destination

Currently you have to create a new tour with one less destination and call [NSDK_Navigation calculateTour:observer:error:] again. This will probably change in a future release.