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.