Overview

You will learn: how to find the optimal route and directions for multiple stops with the ArcGIS Route service.

The ArcGIS Transportation Routing and Network Analytics Services can find routes, get driving directions, calculate drive times, and solve complicated multiple vehicle routing problems(VRP). If you would like to create an application that can find driving directions and create an optimized route, you can use a route task and the solveRouteAsync method. All you have to do is pass in the "stop" locations, and the service will return a route with directions. Once you have the results you can add the route to a map, display the turn-by-turn directions, or integrate them further into your application. To learn more about the capabilities of the directions and routing service, please visit the documentation.

In this lab you will learn how to use a routing service to calculate an optimal route between two or more places. Points will be created by user when clicking on the map.

Before you begin

Complete or review labs Display point, line, and polygon graphics and Access services with OAuth 2.0 as you will repeat some parts of those labs here.

Reuse the starter project

If you have not done the starter project lab, be sure to review it first so you are familiar with running the app.

In a new or empty folder, make a copy of the Create a starter app solution.

Configure OAuth2 for your app

Reuse the application you previously set up in the Access services with OAuth 2.0 lab. If you did not do that lab, then register your app to generate a client id and set a redirect URI to access the secure service:

  1. Sign into ArcGIS for Developers.

    1. Select drop-down menu Dashboard > New Application to create a new application.

      • Title: Access Private Layer App
      • Tags: OAuth
      • Description: Access Private Layer App
      • Click Register New Application
    2. At the top, click on the tab Authentication and under Redirect URLs add the following.

      • Redirect URL: urn:ietf:wg:oauth:2.0:oob
    3. Make note of your Client ID as you'll need it later in this lab.

Steps

Update the project

  1. Open the settings.gradle file in an editor of your choice and change the rootProject.name value to get-a-route-and-directions.

Imports and variable declarations

  1. Open the App.java file in src/main/java/com/arcgis/developers/labs. Then merge the following with the existing imports.

    import java.net.MalformedURLException;
    import java.util.Arrays;
    import java.util.concurrent.ExecutionException;
    
    import javafx.geometry.Point2D;
    import javafx.scene.control.Alert;
    import javafx.scene.input.MouseButton;
    
    import com.esri.arcgisruntime.concurrent.ListenableFuture;
    import com.esri.arcgisruntime.geometry.Point;
    import com.esri.arcgisruntime.geometry.Polyline;
    import com.esri.arcgisruntime.loadable.LoadStatus;
    import com.esri.arcgisruntime.mapping.view.Graphic;
    import com.esri.arcgisruntime.mapping.view.GraphicsOverlay;
    import com.esri.arcgisruntime.portal.Portal;
    import com.esri.arcgisruntime.security.AuthenticationManager;
    import com.esri.arcgisruntime.security.DefaultAuthenticationChallengeHandler;
    import com.esri.arcgisruntime.security.OAuthConfiguration;
    import com.esri.arcgisruntime.symbology.SimpleLineSymbol;
    import com.esri.arcgisruntime.symbology.SimpleMarkerSymbol;
    import com.esri.arcgisruntime.tasks.networkanalysis.Route;
    import com.esri.arcgisruntime.tasks.networkanalysis.RouteParameters;
    import com.esri.arcgisruntime.tasks.networkanalysis.RouteResult;
    import com.esri.arcgisruntime.tasks.networkanalysis.RouteTask;
    import com.esri.arcgisruntime.tasks.networkanalysis.Stop;
    
  2. Set up fields that will be used later.

    /* ** ADD ** */
    private Point startPoint;
    private Point endPoint;
    private final int hexRed = 0xFFFF0000;
    private final int hexBlue = 0xFF0000FF;
    
    private GraphicsOverlay graphicsOverlay;
    private RouteTask solveRouteTask;
    private RouteParameters routeParameters;
    /* ** ADD ** */
    private MapView mapView;
    

Add graphics overlay to a map view

  1. Add a new private method setupGraphicsOverlay that creates a graphics overlay and attaches it to the map view to render the graphics to the map. Each graphic must have a symbol assigned in order for it to be visible on the map.

    private void setupGraphicsOverlay() {
      if (mapView != null) {
        graphicsOverlay = new GraphicsOverlay();
        mapView.getGraphicsOverlays().add(graphicsOverlay);
      }
    }
    
  2. Make a call to the method just created in the start method.

    setupMap();
    
    /* ** ADD ** */
    setupGraphicsOverlay();
    

Add authentication

  1. Using the ArcGIS Transportation Routing and Network Analytics Services requires an authenticated user and a valid token. The Java SDK handles this for you with AuthenticationManager.

     private void setupAuthentication() {
       String portalURL = "https://www.arcgis.com";
       String clientId = "YOUR_CLIENT_ID";
       String redirectURI = "urn:ietf:wg:oauth:2.0:oob";
    
       try {
         OAuthConfiguration oAuthConfiguration = new OAuthConfiguration(portalURL, clientId, redirectURI);
         AuthenticationManager.setAuthenticationChallengeHandler(new DefaultAuthenticationChallengeHandler());
         AuthenticationManager.addOAuthConfiguration(oAuthConfiguration);
    
       } catch (MalformedURLException e) {
         e.printStackTrace();
       }
     }
    
  2. Create a portal to access the routing service. Once the portal loads, you will have the required authentication method in place when requesting to use the routing service.

     AuthenticationManager.addOAuthConfiguration(oAuthConfiguration);
    
     /* ** ADD ** */
     final Portal portal = new Portal(portalURL, true);
     portal.addDoneLoadingListener(() -> {
       if (portal.getLoadStatus() == LoadStatus.LOADED) {
         //String routeServiceURI = "https://route.arcgis.com/arcgis/rest/services/World/Route/NAServer/Route_World";
         //setupRouteTask(routeServiceURI);
       } else {
         new Alert(Alert.AlertType.ERROR, "Portal: " + portal.getLoadError().getMessage()).show();
       }
     });
     portal.loadAsync();
    
  3. Make a call to the method just created in the start method.

    setupGraphicsOverlay();
    /* ** ADD ** */
    setupAuthentication();
    

Create Route Task

  1. Initialize and load the route task using the routing service URL. The route task will calculate a route between two points using detailed information from service URL. The serivce URL usually gives detailed information about a ceratin area, so if a point was to fall outside that area then the route will only be completed up to that area's boundary.

     private void setupRouteTask(String routeServiceURI) {
       solveRouteTask = new RouteTask(routeServiceURI);
       solveRouteTask.loadAsync();
     }
    
  2. Once the route task has loaded create default route parameters.

    solveRouteTask.loadAsync();
    
    /* ** ADD ** */
    solveRouteTask.addDoneLoadingListener(() -> {
      if (solveRouteTask.getLoadStatus() == LoadStatus.LOADED) {
        final ListenableFuture<RouteParameters> routeParamsFuture = solveRouteTask.createDefaultParametersAsync();
        routeParamsFuture.addDoneListener(() -> {
    
          try {
            routeParameters = routeParamsFuture.get();
          } catch (InterruptedException | ExecutionException e) {
            new Alert(Alert.AlertType.ERROR, "Cannot create RouteTask parameters " + e.getMessage()).show();
          }
        });
        //createRouteAndDisplay();
      } else {
        new Alert(Alert.AlertType.ERROR, "Unable to load RouteTask " + solveRouteTask.getLoadStatus().toString()).show();
      }
    });
    
  3. Uncomment routeServiceURI and setupRouteTask from the setupAuthentication method.

    if (portal.getLoadStatus() == LoadStatus.LOADED) {
      String routeServiceURI = "https://route.arcgis.com/arcgis/rest/services/World/Route/NAServer/Route_World";
      setupRouteTask(routeServiceURI);
    } else {
    

Methods for displaying route start and end locations

  1. Add a private method setMapMarker that creates a graphic at a specified location using a symbol and display it to the map.

     private void setMapMarker(Point location, SimpleMarkerSymbol.Style style, int markerColor, int outlineColor) {
       float markerSize = 8.0f;
       float markerOutlineThickness = 2.0f;
       SimpleMarkerSymbol pointSymbol = new SimpleMarkerSymbol(style, markerColor, markerSize);
       pointSymbol.setOutline(new SimpleLineSymbol(SimpleLineSymbol.Style.SOLID, outlineColor, markerOutlineThickness));
       Graphic pointGraphic = new Graphic(location, pointSymbol);
       graphicsOverlay.getGraphics().add(pointGraphic);
     }
    
  2. Add a new private method setStartMarker that visually displays and stores the starting location.

     private void setStartMarker(Point location) {
       graphicsOverlay.getGraphics().clear();
       setMapMarker(location, SimpleMarkerSymbol.Style.DIAMOND, hexRed, hexBlue);
       startPoint = location;
       endPoint = null;
     }
    
  3. Add a new private method setEndMarker that visually displays and stores the ending location.

     private void setEndMarker(Point location) {
       setMapMarker(location, SimpleMarkerSymbol.Style.SQUARE, hexBlue, hexRed);
       endPoint = location;
       // solveForRoute();
     }
    

Add listener for mouse interactions

  1. Add a private method createRouteAndDisplay that adds a listener to the map view for interpreting user input when the map is clicked. Then call the graphic display methods you coded in the prior step.

     private void createRouteAndDisplay() {
       mapView.setOnMouseClicked(e -> {
         if (e.getButton() == MouseButton.PRIMARY) {
           Point2D point = new Point2D(e.getX(), e.getY());
           Point mapPoint = mapView.screenToLocation(point);
    
           if (startPoint == null) {
             setStartMarker(mapPoint);
           } else if (endPoint == null) {
             setEndMarker(mapPoint);
           } else {
             setStartMarker(mapPoint);
           }
         }
       });
     }
    
  2. Uncomment createRouteAndDisplay method from setupAuthentication method.

    if (solveRouteTask.getLoadStatus() == LoadStatus.LOADED) {
      final ListenableFuture<RouteParameters> routeParamsFuture = solveRouteTask.createDefaultParametersAsync();
      routeParamsFuture.addDoneListener(() ->
    
        try {
          routeParameters = routeParamsFuture.get();
        } catch (InterruptedException | ExecutionException e) {
          new Alert(Alert.AlertType.ERROR, "Cannot create RouteTask parameters " + e.getMessage()).show();
        }
      });
      /* ** Uncomment ** */
      createRouteAndDisplay();
    } else {
    

Create and Display a Route

  1. Add a private method solveForRoute that solves for a route between two locations and displays that route on the map.

     private void solveForRoute() {
    
       if (startPoint != null && endPoint != null) {
         routeParameters.setStops(Arrays.asList(new Stop(startPoint), new Stop(endPoint)));
    
         final ListenableFuture<RouteResult> routeResultFuture = solveRouteTask.solveRouteAsync(routeParameters);
       }
     }
    
  2. Wait for task to be complete before accessing the results.

     final ListenableFuture<RouteResult> routeResultFuture = solveRouteTask.solveRouteAsync(routeParameters);
     /* ** ADD ** */
     routeResultFuture.addDoneListener(() -> {
       try {
         RouteResult routeResult = routeResultFuture.get();
       } catch (InterruptedException | ExecutionException e) {
         new Alert(Alert.AlertType.ERROR, "Solve RouteTask failed " + e.getMessage() + e.getMessage()).show();
       }
     });
    
  3. Check that a route was returned and selected the first one. Display this route to the map using the graphics overlay.

      RouteResult routeResult = routeResultFuture.get();
      /* ** ADD ** */
      if(routeResult.getRoutes().size() > 0) {
        Route firstRoute = routeResult.getRoutes().get(0);
    
        Polyline routePolyline = firstRoute.getRouteGeometry();
        SimpleLineSymbol routeSymbol = new SimpleLineSymbol(SimpleLineSymbol.Style.SOLID, hexBlue, 4.0f);
        Graphic routeGraphic = new Graphic(routePolyline, routeSymbol);
        graphicsOverlay.getGraphics().add(routeGraphic);
       } else {
        new Alert(Alert.AlertType.WARNING, "No routes have been found.").show();
      }
    
  4. Uncomment the call to solveForRoute() in the setEndMarker method.

     endPoint = location;
     /* ** Uncomment ** */
     solveForRoute();
    

Congratulations, you're done!

Run your application and when you click two different locations within the map view a route will be displayed between those points. Compare with our completed solution project.

Challenge

There are several ways this app could be enhanced. Can you take it to the next level?

Explore route parameters

The RouteParameters class offers a lot of options. Explore some of these options and enhance your app. Can you show turn-by-turn directions?

Get additional information about the route

The solution route, called firstRoute in this example, has additional information accessible through a series of .get...() statements. Add the following lines of code to solveForRoute() after getting firstRoute:

long lengthInKm = Math.round(firstRoute.getTotalLength() / 1000);
long timeInMinutes = Math.round(firstRoute.getTravelTime());

new Alert(Alert.AlertType.INFORMATION, "Total length (km): " + lengthInKm + " Travel time (min): " + timeInMinutes).show();

Search and directions

Combine what you learned in Search for an address to allow your user to search for the start and end locations.

Handle multiple results

In this lab you handled the first result but ignored any additional results. Can you create a UI to allow the user to review multiple results?