How to develop a point-to-point routing application

In this exercise you will walk through the development of an application using the ArcGIS Runtime SDK for Java. Your application will consume the geoprocessing package created in How to author and publish a geoprocessing model. The application provides users with the ability to specify a route start point and route end point on a basemap and determine an optimal route between the selected points on any device on which your application is deployed.

Create your project

The ArcGIS Runtime SDK for Java installation includes an Eclipse plugin which integrates wizards, tools, and templates with the Eclipse IDE. The steps below assume that the plugin has been installed. Refer to Installing the Eclipse plugin for installation instructions.

Follow the steps below to create a new project using an available ArcGIS Runtime for Java application template.

  1. Launch Eclipse.
  2. From the Eclipse Workbench main menu select File > New > Project.
  3. Expand the 'ArcGIS Runtime for Java' folder.
  4. Select the 'ArcGIS Runtime Java Application' template.
  5. Enter a project name, for example RoutingApp.
  6. Click Next to advance to Java Settings.
  7. Click Finish to accept the default Java settings and dismiss the new project creation wizard.
  8. A new Java project is created containing references to assemblies generally required for an ArcGIS Runtime for Java map application. The project has as empty package folder named 'src' in which you will create several new class files.
  9. Expand the application's folder in the Eclipse Package Explorer.
  10. Right click the src package folder and select New > Class.
  11. In the New Java Class panel, enter the class name Main.
  12. Place a check in the box next to public static void main(String[] args).
  13. Click Finish to create and dismiss the Java Class creation wizard.
  14. Create three additional classes in the default package by right clicking and selecting New > Class to create each class. No 'main' methods are required for these classes, so make sure to untick the box ticked in step 11.
    • Name the first class RouteProcessor
    • Name the second class GPTaskOverlay
    • Name the third class ButtonEvent
    Your project's default package in the src package folder should now have four source code files. The file contains the static main() method which will be used to launch your application. The other three files - RouteProcessor, GPTaskOverlay, and ButtonEvent - each contain a class declaration with an empty class body and no import directives.

Implement the main( ) method

Follow the steps below to update the import directives and provide an implementation for your main( ) method.

  1. Open the code file in the Eclipse IDE.
  2. Add the import directive and update the main( ) method to match the code illustrated below.
  3. import java.awt.EventQueue;
    public class Main {
        public static void main(String[] args) {
            EventQueue.invokeLater(new Runnable() {
                public void run() {
                    try {
                        RouteProcessor application = new RouteProcessor();
                    } catch (Exception e) {
  4. Save your project and project files.

The RouteProcessor

Notice that the only objective of the main( ) method was to instantiate a RouteProcessor.

For the purposes of this walkthrough, consider the RouteProcessor the central component of your implementation. It will create and manage the application's window and maintain the objects which comprise the application's user-interface. It will create the map, using a local tile package to provide a basemap, and maintain a graphics layer to display points selected by the user as well as the route computed by a local geoprocessing service.

All of this implies a significant number of Swing components as well as components defined by the ArcGIS Runtime for Java API. Some of the application's implementation, such as event handling, will be encapsulated within other classes. However, much of what those classes need to do will require access to objects maintained within the RouteProcessor class.

To streamline the code illustrations you will first implement some infrastructure within the RouteProcessor, declaring member variables you will need later and implementing some simple accessors and public methods. You will then shift to implement a class that extends the ArcGIS Runtime for Java MapOverlay and a class which implements the Java ActionListener event interface before returning to RouteProcessor to complete development of your geoprocessing and mapping application.

Implement the RouteProcessor class infrastructure

Follow the steps below to add the import directives, class member variables, and helper methods which you will need to support development later in this exercise.

  1. Open the file in the Eclipse IDE.
  2. Copy the following import directives placing them at the top of the code file above the class declaration.
  3. import java.awt.BorderLayout;
    import java.awt.event.WindowAdapter;
    import java.awt.event.WindowEvent;
    import javax.swing.JComponent;
    import javax.swing.JButton;
    import javax.swing.JPanel;
    import javax.swing.JFrame;
    import javax.swing.JLayeredPane;
    import com.esri.runtime.ArcGISRuntime;
    import com.esri.client.local.ArcGISLocalTiledLayer;
    import com.esri.client.local.LocalGeoprocessingService;

    The code pattern above follows the ArcGIS Runtime SDK for Java Samples included with your installation of the SDK. You can locate and run the samples from the command line:

    cd C:\...<install folder>\sdk\samples\samplecode

    java -jar ArcGIS_Runtime_Java_Samples_10.1.1.jar

    Click any sample's thumbnail and select the Source Code tab at the top of the Samples window to review the Java source code.

  4. In the file, copy the enumeration and variable declarations from the illustration below into the RouteProcessor class body.
  5. public class RouteProcessor {
        // Public enumeration used by a click event handler to
        // indicate the type of route point a user has specified
        public enum PointType {STARTPOINT, ENDPOINT}
        // The application window and map for this application
        private JFrame appWindow = null;
        private JMap map = null;
        // Extends MapOverlay for management of map control 
        private GPTaskOverlay gpTaskOverlay = null;
  6. Add declarations and accessors for the primary UI components and application window.
  7. // Application window accessor
        public JFrame appWindow() { return appWindow; }
        // Primary user interface components
        private JButton startButton = null;
        private JButton endButton = null;
        private JButton solveButton = null;
        // Accessors to UI objects
        public JButton startButton() { return startButton; }
        public JButton endButton()   { return endButton; }
        public JButton solveButton() { return solveButton; }

    A discussion of the design trades inherent when providing access to a class's private members is beyond the scope of this walkthrough. Here, accessors are used to simplify the code without defaulting to use public member variables. Developers should consider the purpose of each class and weigh for themselves the pros and cons of differerent design patterns when exposing the internals of classes as part of their own development.

  8. Add the GraphicsLayer and Graphics declaration and accessors illustrated below.
  9. // Graphics layer and graphics used to display routing
        private GraphicsLayer graphicsLayer = null;
        private Graphic startPoint = null;
        private Graphic endPoint = null;
        // Accessors for Graphics and GraphicsLayer
        public GraphicsLayer graphicsLayer() { return graphicsLayer; }
        public Graphic routeStartPoint() { return startPoint; }
        public Graphic routeEndPoint()   { return endPoint; }
  10. Add code declaring a LocalGeoprocessingService object. This will be the service spun-up using the geoprocessing package you created earlier which provides the routing functionality.
  11. // Geoprocessing service to be instantiated with a
        // locally published geoprocessing package
        private LocalGeoprocessingService localGPRoutingService = null;
        // Accessor used to retrieve service's URL to instantiate
        // a new Geoprocessor which will consume the local service
        public String getLocalGPServiceURL()
          { return localGPRoutingService.getUrlGeoprocessingService(); }
  12. Copy the code blocks below to complete the RouteProcessor public interface.

    The first public method is invoked by the map control event handler. Recall that Graphic objects (which are maintained by the RouteProcessor) record geometry, spatial reference, and symbology. When a UI button is pressed the map control will be enabled and the type of route point being specified will be broadcast to the class handling map control events. When the user clicks the active map control to specify the route point, the event handler returns the graphic and point type to the RouteProcessor using the method below.

    The enableMapControl method is invoked by UI button event handlers when a button is clicked to enable interaction with the map control. The disableMapControl method is invoked by the map control's event handler following specification of a route start/end point to block further interactions with the control.

  13. public void addGraphic(PointType pointType, Graphic graphic)
            if (pointType == PointType.STARTPOINT) { startPoint = graphic; }
            if (pointType == PointType.ENDPOINT)   { endPoint = graphic; }
            if (startPoint != null && endPoint != null)
        public void enableMapControl(PointType pointType) {
        public void disableMapControl() {

    The gpTaskOverlay object used above will be instantiated later when you implement your application's user interface. Code in the methods below will prevent you from compiling your project until you complete implementation of the GPTaskOverlay class.

  14. Add the private string definitions below which locate the tile package (*.tpk) you will use as a local map resource. (You may need to adjust the values to reflect your installation of the ArcGIS Runtime for Java SDK.)
  15. NoteNote:

    You would need to adjust these values prior to deploying your application so that the data packages could be found relative to your application's executable on the device hosting your application. However, we will not be deploying the application in this walkthrough, so locating the tile package in the SDK's sample data folder will suffice.

    // File path separator
        private static final String FSP = System.getProperty("file.separator");
        /*  C:\...\ArcGIS SDKs\java10.1.1\sdk\samples\data  */
        private static final String sdkSampleData = ArcGISRuntime.getInstallDirectory() + FSP +
            "sdk" + FSP + "samples" + FSP + "data";
  16. Complete the infrastructure for the RouteProcessor class by adding the private methods illustrated below. These are helper methods you will invoke as you create and configure elements of your application's user interface.
  17. private JMap createMap() {
            ArcGISLocalTiledLayer localTiledLayer =
                new ArcGISLocalTiledLayer(sdkSampleData + FSP + "tpks" + FSP + "SanFrancisco.tpk");
            // Create a new map and add a local tile package for basemap content
            JMap jMap = new JMap();
            // Add a graphics layer which will display the user's route points
            // and the result of the geoprocessing (the computed route)
            graphicsLayer = new GraphicsLayer();
            return jMap;
    // Method to create a content pane in which to display the map
        private JLayeredPane createContentPane() {
            JLayeredPane contentPane = new JLayeredPane();
            contentPane.setBounds(100, 100, 1000, 700);
            contentPane.setLayout(new BorderLayout(0, 0));
            return contentPane;
    // Create the content pane and map to add to the application window
        private JComponent createUI() {
            map = createMap();
            JComponent contentPane = createContentPane();
            return contentPane;

Implement the RouteProcessor constructor

You are almost done implementing the RouteProcessor class. All that is left is to implement the class constructor. The constructor will:

  • Create an application window as a new Swing JFrame component.
  • Invoke the helper methods you copied above to create the user-interface and map.
  • Add a reference of the map to the application window being managed by the class object.

Recall that the only objective of the main( ) method was to instantiate a RouteProcessor - the object responsible for constructing the application window, user-interface, and map. The infrastructure you have completed above handles all of this.

Follow the steps below to complete the implementation of the RouteProcessor default constructor which will wrap-up development of the RouteProcessor class.

  1. In the file, below the three private helper methods, copy the default constructor illustrated below making sure to place the code within the RouteProcessor class body.
  2. public RouteProcessor()
            // create application's UI including map
            appWindow = new JFrame("My Point-to-Point Routing Solution");
            appWindow.setBounds(100, 100, 1000, 700);
            appWindow.getContentPane().setLayout(new BorderLayout(0, 0));
            appWindow.addWindowListener(new WindowAdapter() {
                public void windowClosing(WindowEvent windowEvent) {
            JComponent appUI = createUI();

Implement the GPTaskOverlay class (extend MapOverlay)

The GPTaskOverlay class will extend MapOverlay from the ArcGIS Runtime Java API, a component used to alter or block interactions with a map control. For this example, the component will be used to get mouse click events and paint graphics such as a route start point and end point on the map.

  1. Open the file in the Eclipse IDE.
  2. Copy the following import directives into the file.
  3. import java.awt.Color;
    import java.awt.event.MouseEvent;
    import com.esri.core.symbol.SimpleMarkerSymbol;
    import com.esri.core.symbol.SimpleMarkerSymbol.Style;
    import com.esri.core.geometry.Point;
  4. Update the class declaration to extend MapOverlay and add class member variables.
    • routeProcessor - An instance of the instantiating class
    • pointType - Records the type of point (route "start" or "end" point) being specified by the user
    • simpleMarkerSymbol - Used to configure symbology for graphic to be displayed on the map
    public class GPTaskOverlay extends MapOverlay {
    	   private static final long serialVersionUID = 0L;
        private RouteProcessor routeProcessor = null;
        private RouteProcessor.PointType pointType = null;
        private SimpleMarkerSymbol simpleMarkerSymbol = null; 
  5. Add an accessor allowing the instantiating class to specify the type of point being specified by the user following enabling of the map control.
  6. public void setPointType(RouteProcessor.PointType value)
          { this.pointType = value; }
  7. Add a constructor to capture a reference to the instantiating class and create the symbology for the specified route points.
  8. public GPTaskOverlay(RouteProcessor instance) {
            routeProcessor = instance;
            simpleMarkerSymbol = new
                SimpleMarkerSymbol(Color.MAGENTA, 20, Style.DIAMOND);
  9. Use Eclipse code assistance to generate a stub override for the onMouseClicked( ) event inherited from MapOverlay.
    1. Right-click anywhere in the class and select Source > Override/Implement Methods.
    2. Place a checkmark next to 'onMouseClicked(MouseEvent)' and click 'OK'.
  10. Use the code illustration below to complete the implementation of onMouseClicked( ).
    1. Use the inherited MapOverlay.getMap( ) method to obtain the JMap component.
    2. Use the JMap component's toMapPoint( ) method to create a Point geometry.
    3. Use the point geometry to create a point graphic.
    4. Call on the RouteProcessor to add a graphic to the map indicating the user's specified route point.
    5. Disable the map control to block additional click events until another UI button is clicked.
  11. @Override
        public void onMouseClicked(MouseEvent event) {
            Point point =  this.getMap().toMapPoint(event.getX(), event.getY());
            Graphic graphic = new Graphic(point, simpleMarkerSymbol);
            if (pointType != null) {
                routeProcessor.addGraphic(pointType, graphic);

Implement the ButtonEvent class (implements ActionListener)

An action event occurs when a user performs an action such as clicking a button, selecting a menu item or modifying the contents of a text field.

The user interface for your point-to-point routing application has three buttons. The first allows the user to specify the route's starting point; the second is used to establish the route's end point; the third button executes the geoprocessing task to "solve" the problem and identify an optimal route between the designated points.

Follow the steps below to complete the code for your ButtonEvent class, implement the ActionListener interface, and provide an actionPerformed( ) implementation for event handling.

  1. Open the file in the Eclipse IDE.
  2. Copy the following import directives into the file.
  3. import java.util.List;
    import java.util.ArrayList;
    import java.awt.Color;
    import java.awt.event.ActionEvent;
    import java.awt.event.ActionListener;
    import javax.swing.JOptionPane;
    import com.esri.core.symbol.SimpleLineSymbol;
    import com.esri.core.geometry.Geometry;
    import com.esri.core.tasks.ags.geoprocessing.GPParameter;
    import com.esri.core.tasks.ags.geoprocessing.Geoprocessor;
    import com.esri.core.tasks.ags.geoprocessing.GPFeatureRecordSetLayer;
  4. Using the code illustrated below:
    • Modify the class declaration to implement the ActionListener interface.
    • Include a code stub override for the required actionPerformed( ) method.
    • Add a member variable to hold an instance of the RouteProcessor.
    • Implement a public constructor which accepts a reference to the RouteProcessor and assigns the reference to the class member variable.
    public	class ButtonEvent implements ActionListener {
        private RouteProcessor routeProcessor = null;
        public ButtonEvent(RouteProcessor instance) {
            routeProcessor = instance;
        public void actionPerformed(ActionEvent e) {
  5. Add a code stub for a private method named executeRoute( ) below the actionPerformed( ) code stub.
  6. private void executeRoute() {

    Next you will implement actionPerformed( ). You will include logic to handle the various user interface button clicks. When the "start route" button or "end route" button is clicked, you will call on the RouteProcessor to enable its map control using its public enumeration to identify which button was clicked. When the "solve route" button is clicked, your code will invoke the private executeRoute( ) method which you will implement in the next step.

  7. Copy the code illustrated below into the actionPerformed( ) method body.
  8. if (e.getSource() == routeProcessor.startButton())
        	if (e.getSource() == routeProcessor.endButton())
        	if (e.getSource() == routeProcessor.solveButton())

    Next you will implement the executeRoute( ) method. This method will need to retrieve the URL from a LocalGeoprocessingService object maintained by the RouteProcessor in order to instantiate a new Geoprocessor which will use the service to complete the geoprocessing. It will also prepare a geoprocessing feature record set, set the route's start point and end point, add the record set to a list of geoprocessing parameters, and execute the geoprocessing task.

  9. Using the code illustrated below, implement executeRoute( ).
    1. Add logic to return immediately if either the route start point or route end point have not been specified. (Recall that the start point and end point are objects maintained by the RouteProcessor and must be retrieved using accessors provided by that class.)
    2. Retrieve the URL from the local geoprocessing service maintained by the RouteProcessor. Use the obtained URL to instantiate a new geoprocessor.
    3. Instantiate a new GPFeatureRecordSetLayer specifying the input parameter name expected by the geoprocessing service as part of the object's construction.
    4. Specify the layer's geometry type and add the route points as graphic elements.
    5. Add the prepared record set layer to a list of GPParameter objects.
    6. Prepare a statement to execute the geoprocessing task asynchronously.
  10. private void executeRoute() {
        if ( (routeProcessor.routeStartPoint() == null) ||
        		 (routeProcessor.routeEndPoint() == null) )
        String url = routeProcessor.getLocalGPServiceURL();
        Geoprocessor gp = new Geoprocessor(url + "/Route");
        GPFeatureRecordSetLayer gpFeatureRecordSetLayer =
          new GPFeatureRecordSetLayer("Input_Locations");
        List<GPParameter> parameters = new ArrayList<GPParameter>();
        gp.executeAsync(parameters, new CallbackListener<GPParameter[]>()

    The final line of code in the code block above is responsible for executing the geoprocessing task. The task will access the local geoprocessing service via the obtained URL and will run asynchronously. Results will be available through the CallbackListener templatized object which is being instantiated inline.

    Follow the steps below to code the inline implementation of the CallbackListener. You will use code assistance provided by the Eclipse IDE to help you generate stubs for the "onCallback" and "onError" methods required to complete the implementation.

  11. In the Eclipse IDE, hover over CallbackListener<GPParameter[]> and select 'Add unimplemented methods' to generate the code stubs illustrated below.

    Pay attention to the method signatures. Code completion may use arg0 in place of the named parameters in the illustration below.

  12. @Override
          public void onCallback(GPParameter[] objs) {
              // TODO Auto-generated method stub
          public void onError(Throwable e) {
              // TODO Auto-generated method stub

    When an unanticipated geoprocessing error occurs, notify the user via a Message dialog.

  13. Replace the "TODO" in the onError method body with the code illustrated below.
  14. JOptionPane.showMessageDialog(
              routeProcessor.appWindow(), "Error:\n" + e.getMessage(),
              "Geoprocessing Error", JOptionPane.ERROR_MESSAGE);

    Handling a successful geoprocessing event will require a bit more code.

    You will need to locate a GPFeatureRecordSetLayer in the returned array of GPParameter objects and examine the graphics contained in that layer. If the geometry of the graphics is a polyline you can assume that the layer is the computed route, create a simple line symbology for the route, and add it to the application's graphics layer.

  15. Replace the "TODO" in the onCallback method body with the code illustrated below.
  16. for (GPParameter outputParameter : objs) {
              if (outputParameter instanceof GPFeatureRecordSetLayer) {
                  GPFeatureRecordSetLayer gpResults = (GPFeatureRecordSetLayer) outputParameter;
                  for (Graphic graphic : gpResults.getGraphics()) {
                      if (graphic.getGeometry().getType() == Geometry.Type.POLYLINE) {
                          SimpleLineSymbol simpleLineSymbol = new SimpleLineSymbol(Color.MAGENTA, 5);
                          Graphic routePolyline = new Graphic(graphic.getGeometry(), simpleLineSymbol);

Complete your implementation of RouteProcessor

There are just a few additional pieces needed to complete your application.

  1. Open the file in the Eclipse IDE.
  2. Locate your private createMap( ) method.
  3. At the bottom of the method's implementation, just before the JMap component is returned, add the code shown below to instantiate a GPTaskOverlay object and add the MapOverlay extension to the map.
  4. // Add an instance of the class providing MapOverlay control to the map
        // Disable the map control initially (wait for a UI button to be clicked) 
        gpTaskOverlay = new GPTaskOverlay(this);
  5. Using the example code below, develop a new private method named startRouteService( ) to instantiate a local geoprocessing service and start the service running asynchronously.
  6. // Helper method to instantiate a local geoprocessing service to provide
        // functionality contained in the Route.gpk geoprocessing package
        private void startRouteService() {
        localGPRoutingService =
            new LocalGeoprocessingService(sdkSampleData + FSP + "gpks" + FSP + "Routing" + FSP + "Route.gpk");
            new LocalServiceStartCompleteListener() {
              public void localServiceStartComplete(LocalServiceStartCompleteEvent e)
              {  solveButton.setEnabled(true);  }

    In the illustration above, an absolute path is provided to locate the geoprocessing package 'Route.gpk' which is included with the SDK installation. Adjust this path if desired to point to your own 'Route.gpk' package.

  7. Locate the RouteProcessor( ) constructor and in the body, before the call to createUI( ), enter the code below to invoke the helper method implemented above
  8. startRouteService();
  9. Locate the private method createUI( ) and rework this method using the code example below.
    • Create a button panel component.
    • Add newly constructed button components to the panel.
    • Add ActionListeners bound to an instance of your ButtonEvent class to each button.
    private JComponent createUI() {
        map = createMap();
        ButtonEvent buttonEvent = new ButtonEvent(this);
        startButton = new JButton("Set Start Point");
        endButton = new JButton("Set End Point");
        solveButton = new JButton("Solve");
        JPanel buttonPanel = new JPanel();
        buttonPanel.setLayout(new BoxLayout(buttonPanel, BoxLayout.Y_AXIS));
        JComponent contentPane = createContentPane();
        contentPane.add(buttonPanel, BorderLayout.WEST);
        return contentPane;

Run your application

Your application should now be complete! Recall that we created a class called 'Main' with just one method whose purpose was to run the application by creating an instance of the RouteProcessor class. To run your application, right click on the file '' in the Package Explorer window of Eclipse, then choose Run As > Java Application. A new window should open displaying a map of San Francisco and the 3 buttons. Try out your application by zooming into the city, setting a start and end point, and clicking the 'Solve' button. The application should look similar to the following image:

screen capture of the routing application

In the console window in Eclipse (if it is not open, open it by clicking Window > Show View > Console), you get useful information about the version of Java running the application, the rendering engine (the default is DirectX, with OpenGL as an alternative), and information about the local server, including its REST endpoint. If you copy-paste this URL, which ends in '.../arcgis/rest', into a browser, you can conveniently view information about the local server and the services which are running. Once your geoprocessing service has successfully started, you should see a link for 'Route.gpk' under 'Services', which you can follow to find information about the geoprocessing service.