Skip To Content

Get driving directions

In this topic

This topic describes how to build an OS X application to find driving directions. It's assumed that you've already added a map to your application as described in Add a map to your app.

The API provides a route task (class name AGSRouteTask) that allows you to find driving directions between two or more locations. You can customize the route by specifying preferences, such as whether to find the shortest or the fastest route, or by adding constraints, such as time windows for visiting each location, barriers representing road closures, and so on.

Learn more about the route task

The route task relies on ArcGIS Server Network Analyst services. These services can be hosted in ArcGIS Online, on the Esri cloud platform, or on your on-premises servers. ArcGIS Online provides worldwide Network Analyst services you can use with a subscription to ArcGIS Online. You can also create your own services using ArcGIS for Server. In this topic, you'll use a sample routing web service on ArcGIS Online covering North America.

1. Input and set the start of the route

There are a number of ways in which a user can provide the start and end points of a route. The user can input an address, tap a location on the map, or use the computer's location. For the purpose of this Tutorial, you will programmatically set the start point but allow the user to specify the destination by clicking on the map.

Firstly, set up a tiled map service layer and a graphics layer then add a start location and zoom to an area in San Diego, USA.

- (void)applicationDidFinishLaunching:(NSNotification *)aNotification{
   
 ...
    
 //add a graphics layer to the map to show the start, destination and route
 self.graphicsLayer = [AGSGraphicsLayer graphicsLayer];
 [self.mapView addMapLayer:self.graphicsLayer withName:@"Graphics Layer"];
   
 //initiatlise an array of stops to hold the start and destination points
 self.stops = [NSMutableArray array];

 //create the start point
 AGSPoint *startLocation = [[AGSPoint alloc] initWithX:-13042365 y:3856910 spatialReference:[AGSSpatialReference webMercatorSpatialReference]];

 //add the start point (stop 1) to the array and onto the map
 [self addStop:startLocation sequence:1];
 
//zoom to San Diego, USA
 AGSEnvelope *envelope = [AGSEnvelope envelopeWithXmin:-13044000 ymin:3855000 xmax:-13040000 ymax:3858000 spatialReference:[AGSSpatialReference webMercatorSpatialReference]];
 [self.mapView zoomToEnvelope:envelope animated:YES];
    
}

Add the starting point graphic (class name AGSStopGraphic) to the graphics layer and to an array of stops. The sequence property of this graphic object determines the order in which the stops are to be visited. This array of stops will be processed by the Route Task. Note this code will also be used when the destination point is generated.

-(void)addStop:(AGSPoint *)point sequence:(NSUInteger)sequence {
    
 AGSGeometry *geometry = [[AGSGeometry alloc] initWithSpatialReference:[AGSSpatialReference webMercatorSpatialReference] ];
 geometry = point;
	
 //prepare a dictionary to hold the attributes of the stop
	NSMutableDictionary *attributes = [NSMutableDictionary dictionary];
    
 //create a stop symbol
 AGSSymbol *symbol = [self stopSymbolWithNumber:sequence];
 
 //create a stop graphic
 AGSStopGraphic *stopGraphic = [AGSStopGraphic graphicWithGeometry:geometry symbol:symbol attributes:attributes];
 stopGraphic.sequence = sequence;
 
 //add the stop to an array of stops
 [self.stops addObject:stopGraphic];
 
 //add the stop graphic to the map
 [self.graphicsLayer addGraphic:stopGraphic];
    
}

//create the stop symbol
- (AGSCompositeSymbol*)stopSymbolWithNumber:(NSInteger)stopNumber {
    
 //init composite symbol
	AGSCompositeSymbol *cs = [AGSCompositeSymbol compositeSymbol];
	
 // create main circle
	AGSSimpleMarkerSymbol *sms = [AGSSimpleMarkerSymbol simpleMarkerSymbol];
	sms.color = [NSColor greenColor];
	sms.size = CGSizeMake(20, 20);
	sms.style = AGSSimpleMarkerSymbolStyleCircle;
	[cs addSymbol:sms];
	
 // add number as a text symbol
 AGSTextSymbol *ts = [[AGSTextSymbol alloc] initWithText:[NSString stringWithFormat:@"%i", (int)stopNumber] color:[NSColor blackColor]];
 ts.vAlignment = AGSTextSymbolVAlignmentMiddle;
 ts.hAlignment = AGSTextSymbolHAlignmentCenter;
 ts.fontSize	= 16;
 [cs addSymbol:ts];
	
	return cs;
}

If you run your application at this stage it should look like this with the start location (1) displayed.

Start Location

2. Allow the user to set the route's destination

To allow the user pick a destination point on the map, the map has to respond to the user click. Add a button (class NSButton) of Type 'On Off' to the user interface. Add an IBOutlet and IBAction outlets for the button. When the button is in On state, set the current class to be the map’s touch delegate and implement the delegate method didClickAtPoint:mapPoint:. When the user clicks on the map, didClickAtPoint:mapPoint: is called. The method returns a mapPoint (class AGSPoint) which you’ll pass to addStops:sequence: as the destination point (i.e. the second stop).

Button Type

Insert both an IBOutlet and IBAction outlets for the button and in its method set the mapView's touchDelegate property to be self. Ensure that AGSMapViewTouchDelegate protocol has been implemented so that the mapView's didClickAtPoint: method is fired when the user clicks on the map. In here the addStop method will then add the destination point to both the graphic layer and the array of stops.

You can then reset the map’s touch delegate to nil so that didClickAtPoint:mapPoint: doesn’t get called on further user interaction with the map. Also reset the state of the button.

//header file
...
@interface AppDelegate : NSObject <NSApplicationDelegate, AGSMapViewTouchDelegate>
@property (weak) IBOutlet NSButton *btnDestination;
- (IBAction)addDestination:(id)sender;
...

//implementation file
....
- (IBAction)addDestination:(id)sender {
    self.mapView.touchDelegate = self;
}

- (void)mapView:(AGSMapView *)mapView didClickAtPoint:(CGPoint)screen mapPoint:(AGSPoint *)mappoint features:(NSDictionary *)features {

 //add the destination location (stop 2) to the map and the array of stops
 [self addStop:mappoint sequence:2];
    
 //ensure the touchdelegate property is set to nil
 self.mapView.touchDelegate = nil;

 //unclick the button
 self.btnDestination.state = NSOffState;
    
}

If you run your application it should look like this with the start (1) and destination (2) locations displayed.

Destination location

3. Solve and display the route

In this section, you'll implement the solveWithParameters: method and calculate a route from the start to the destination.

To calculate a route, you first need to instantiate the route task. To do this, you need the URL of a routing service, and optionally, the user credentials to access the service. Since the service you'll be using in this topic is public, you don't need credentials to access it.

You also need to set the task's delegate. A route task delegate is required to handle the results of the route task. We must assign one of our classes to do the job, in this case, we simply assign the current class (self) to be the delegate. It must then implement the methods of AGSRouteTaskDelegate necessary to handle the results of a successful result, as well as to inform the user of any error.

In this example, you'll adopt the AGSRouteTaskDelegate protocol by adding the declaration to the header file (.h) and setting the task's delegate property to self in the implementation file (.m).

You define input parameters and format of results using AGSRouteTaskParameters class. The retrieveDefaultRouteTaskParameters: method is an async method that upon completions returns a default route task parameters object for you to use.

//header file
@interface AppDelegate : NSObject <NSApplicationDelegate, AGSRouteTaskDelegate, AGSMapViewTouchDelegate>
@property (strong) AGSRouteTask *routeTask;
@property (strong) AGSRouteResult *routeResult;
@property (strong) AGSRouteTaskParameters *routeTaskParameters;
...

//implementation file
- (void)applicationDidFinishLaunching:(NSNotification *)aNotification
{
...
 //setup the route task and set delegate
 self.routeTask = [AGSRouteTask 
  routeTaskWithURL:[NSURL URLWithString:@"http://sampleserver6.arcgisonline.com/arcgis/rest/services/NetworkAnalysis/SanDiego/NAServer/Route"]];
 self.routeTask.delegate = self;
    
 //kick off asynchronous method to retrieve default parameters for the route task
 [self.routeTask retrieveDefaultRouteTaskParameters];
...
}

//sets the default route task parameters (as specified by the service)
- (void)routeTask:(AGSRouteTask *)routeTask operation:(NSOperation *)op didRetrieveDefaultRouteTaskParameters:(AGSRouteTaskParameters *)routeParams {
	self.routeTaskParameters = routeParams;
}

Now that you have a route task and the default AGSRouteTaskParameters, you need to provide the stops that will be analysed to create the route. Invoke the setStopsWithFeatures: method and pass in the array of stops containing the start point and the destination location as the end point. Set some additional route parameters, and call the solveWithParameters: method on the route task to calculate the route.

Add an NSButton for the user to click that will calculate the route. Create an IBAction method showRoute that will call the solveWithParameters.

//header file
- (IBAction)showRoute:(id)sender;

//implementation file
- (IBAction)showRoute:(id)sender {
 // set the stops on the parameters object 
 if (self.stops.count > 0) {
	 [self.routeTaskParameters setStopsWithFeatures:self.stops];
 }
	
 // this generalizes the route graphics that are returned
 self.routeTaskParameters.outputGeometryPrecision = 5.0;
 self.routeTaskParameters.outputGeometryPrecisionUnits = AGSUnitsMeters;
    
 // return the graphic representing the entire route, generalized by the previous
 // 2 properties: outputGeometryPrecision and outputGeometryPrecisionUnits
 self.routeTaskParameters.returnRouteGraphics = YES;
    
 // this returns turn-by-turn directions
 self.routeTaskParameters.returnDirections = YES;
	
 // ensure the graphics are returned in our map's spatial reference
 self.routeTaskParameters.outSpatialReference = self.mapView.spatialReference;
	
 // execute the route task using the solveWithParameters method
 [self.routeTask solveWithParameters:self.routeTaskParameters];

}

When the route has been solved successfully, the routeTask:operation:didSolveWithResult: is invoked, and an AGSRouteTaskResult is returned. If the object is not nil (make sure to inform the user if it is nil), you’ll have to get hold of its ‘graphic’ property. This graphic contains the route. Add a symbol for the graphic, and add it to the graphics layer of the map.

Also implement routeTask:operation:didFailSolveWithError: delegate method in case the route task fails.

- (void)routeTask:(AGSRouteTask *)routeTask operation:(NSOperation *)op didSolveWithResult:(AGSRouteTaskResult *)routeTaskResult {
 //there is only one route
 self.routeResult = [routeTaskResult.routeResults lastObject];
	if (self.routeResult) {
        
	// make symbol for this route
	self.routeResult.routeGraphic.symbol = [self routeSymbol];
        
 //set direction graphics. These will be used to get the directions along the route
 self.directionGraphics = self.routeResult.directions.graphics;
        
 // add the route graphic to the graphic's layer
	[self.graphicsLayer addGraphic:self.routeResult.routeGraphic];
	
 //zoom to graphics layer
 [self.mapView zoomToGeometry:self.graphicsLayer.fullEnvelope withPadding:150 animated:YES];

}

//let the user know if the solve failed
- (void)routeTask:(AGSRouteTask *)routeTask operation:(NSOperation *)op didFailSolveWithError:(NSError *)error {
 ...
}

//get the route symbol
- (AGSCompositeSymbol*)routeSymbol {
    
    //init composite symbol
	AGSCompositeSymbol *cs = [AGSCompositeSymbol compositeSymbol];
	
	AGSSimpleLineSymbol *sls1 = [AGSSimpleLineSymbol simpleLineSymbol];
    sls1.color = [NSColor blueColor];
	sls1.style = AGSSimpleLineSymbolStyleSolid;
	sls1.width = 6;
	[cs addSymbol:sls1];
	
	AGSSimpleLineSymbol *sls2 = [AGSSimpleLineSymbol simpleLineSymbol];
	sls2.color = [NSColor colorWithRed:0/255.0f green:170/255.0f blue:255/255.0f alpha:1.0];
	sls2.style = AGSSimpleLineSymbolStyleSolid;
	sls2.width = 4;
	[cs addSymbol:sls2];
	
	return cs;
}

If you run your application it should show the best route bewteen the start (1) and the destination (2).

Start and Destination

4. Show the route's driving directions

The driving directions are returned along with the route if you set the AGSRouteTaskParameters property returnDirections to be YES. The direction information is returned as an array of AGSDirectionGraphicsin AGSRouteResult.

Iterate through the directions graphics. In this tutorial the directions text was added to a text view that was displayed in an NSPopover invoked by the click of a button called "Directions" in the UI.

- (IBAction)displayDirections:(id)sender {

 //empty the text view   
 [self.textView setString:@""];
    
 //iterate through the directions and add the text to a text view
 for (AGSDirectionGraphic *directionGraphic in self.directionGraphics) {
  [self.textView insertText:directionGraphic.text];
  [self.textView insertText:@"\n"];
 }

 //show the popover
 [self.popover showRelativeToRect:[sender bounds] ofView:sender preferredEdge:NSMaxYEdge];

}

If you run your application the driving directions will be displayed in the Popover directly below the Directions button.

Directions

This tutorial has provided you with the fundamental steps for creating a route and retrieving the directions. Please examine the OS X sample code for more examples involving multiple stops, for creating a route with barriers in place and for both online and offline routing.