Calculate drive times

Download Sample Viewer

Description

This sample demonstrates using geoprocessing to calculate drive times (or 'service areas') from an input point. To use the sample, simply click a point in the map; you may click another input point right away as the results are sumitted and returned asynchronously. Drive time polygons of 1, 2, and 3 minutes will be calculated and shown on the map. In the code, a Geoprocessor is used to pass the clicked point, the drive time break values (1, 2, and 3), and the break units (minutes) to the geoprocessing service.

Note: these are the 3 required input parameters - there is a substantial list of optional parameters which can be added, see the service URL for details. When the results are returned, they are added to a GraphicsLayer in the map.

Code snippet


    // create a geoprocessor
    Geoprocessor geoprocessor = new Geoprocessor(URL_GEOPROCESSING_SERVICE);
 
    // provide input arguments
    List<GPParameter> gpInputParams = new ArrayList<GPParameter>(); 
    gpInputParams.add(<appropriate instance of GPParameter>);
    // repeat for each input parameter
    
    // run geoprocessing task and process result
    // one of the options to run the geoprocessing task
    geoprocessor.submitJobAndGetResultsAsync(...);
  

Sample Code

/* Copyright 2014 Esri

All rights reserved under the copyright laws of the United States
and applicable international laws, treaties, and conventions.

You may freely redistribute and use this sample code, with or
without modification, provided you include the original copyright
notice and use restrictions.

See the use restrictions.*/
package com.esri.client.samples.geoprocessing;

import java.awt.BorderLayout;
import java.awt.Color;
import java.awt.Font;
import java.awt.event.ComponentAdapter;
import java.awt.event.ComponentEvent;
import java.awt.event.MouseEvent;
import java.awt.event.WindowAdapter;
import java.awt.event.WindowEvent;
import java.util.ArrayList;
import java.util.List;
import java.util.concurrent.atomic.AtomicInteger;

import javax.swing.BorderFactory;
import javax.swing.BoxLayout;
import javax.swing.JComponent;
import javax.swing.JFrame;
import javax.swing.JLayeredPane;
import javax.swing.JPanel;
import javax.swing.JProgressBar;
import javax.swing.JTextArea;
import javax.swing.SwingUtilities;
import javax.swing.border.LineBorder;
import javax.swing.event.MouseInputAdapter;

import com.esri.core.geometry.Envelope;
import com.esri.core.geometry.Geometry.Type;
import com.esri.core.geometry.Point;
import com.esri.core.map.Graphic;
import com.esri.core.symbol.SimpleFillSymbol;
import com.esri.core.symbol.SimpleLineSymbol;
import com.esri.core.symbol.SimpleMarkerSymbol;
import com.esri.core.symbol.SimpleMarkerSymbol.Style;
import com.esri.core.tasks.ags.geoprocessing.GPFeatureRecordSetLayer;
import com.esri.core.tasks.ags.geoprocessing.GPJobResource;
import com.esri.core.tasks.ags.geoprocessing.GPJobResultCallbackListener;
import com.esri.core.tasks.ags.geoprocessing.GPParameter;
import com.esri.core.tasks.ags.geoprocessing.GPString;
import com.esri.core.tasks.ags.geoprocessing.Geoprocessor;
import com.esri.map.ArcGISTiledMapServiceLayer;
import com.esri.map.GraphicsLayer;
import com.esri.map.JMap;

/**
 * This sample demonstrates using geoprocessing to calculate drive times (or 'service 
 * areas') from an input point. To use the sample, simply click a point in the map; 
 * you may click another input point right away as the results are sumitted and returned 
 * asynchronously. Drive time polygons of 1, 2, and 3 minutes will be calculated and 
 * shown on the map.
 * <p>
 * In the code, a Geoprocessor is used to pass the clicked point, the drive
 * time break values (1, 2, and 3), and the break units (minutes) to the geoprocessing 
 * service. Note that these are the 3 required input parameters - there is a substantial 
 * list of optional parameters which can be added, see the service URL for details. When 
 * the results are returned, they are added to a GraphicsLayer in the map.
 */
public class DriveTimesApp {

  // resources
  private static final String URL_STREETS =
      "http://services.arcgisonline.com/ArcGIS/rest/services/ESRI_StreetMap_World_2D/MapServer";
  // JMap
  private JMap map;
  // progress bar
  private JProgressBar progressBar;
  private AtomicInteger tasksInProgress = new AtomicInteger(0);

  // ------------------------------------------------------------------------
  // Constructor
  // ------------------------------------------------------------------------
  public DriveTimesApp() {
  }

  // ------------------------------------------------------------------------
  // Core functionality
  // ------------------------------------------------------------------------
  /**
   * Class that submits a task to an online geoprocessing service to calculate 
   * zones with different drive times.
   */
  class DriveTimesExecutor extends MouseInputAdapter {

    // URL to remote geoprocessing service
    private final String URL_GEOPROCESSING_SERVICE =
        "http://sampleserver6.arcgisonline.com/arcgis/rest/services/NetworkAnalysis/SanDiego/GPServer/Generate Service Areas";

    // symbology
    private final SimpleMarkerSymbol SYM_START_POINT =
        new SimpleMarkerSymbol(Color.RED, 16, Style.DIAMOND);
    private final SimpleLineSymbol SYM_ZONE_BORDER =
        new SimpleLineSymbol(Color.BLACK, 1);
    private final SimpleFillSymbol SYM_DARK =
        new SimpleFillSymbol(new Color(255, 0, 0, 80), SYM_ZONE_BORDER); // red
    private final SimpleFillSymbol SYM_MED =
        new SimpleFillSymbol(new Color(255, 120, 0, 80), SYM_ZONE_BORDER); // orange
    private final SimpleFillSymbol SYM_LIGHT =
        new SimpleFillSymbol(new Color(255, 255, 0, 80), SYM_ZONE_BORDER); // yellow
    private final SimpleFillSymbol[] zoneFillSymbols =
        new SimpleFillSymbol[] {SYM_DARK, SYM_MED, SYM_LIGHT};

    JMap jMap;
    GraphicsLayer graphicsLayer;

    /**
     * Creates an object that submits a task to the online geoprocessing service to
     * calculate zones with different drive times.
     * @param jMap map to get the start point of drive, and to get the spatial reference
     * @param graphicsLayer graphics layer to which the results will be added
     */
    public DriveTimesExecutor(JMap jMap, GraphicsLayer graphicsLayer) {
      this.jMap = jMap;
      this.graphicsLayer = graphicsLayer;
    }

    /**
     * Computes the drive time zones on click of the mouse.
     */
    @Override
    public void mouseClicked(MouseEvent mapEvent) {

      super.mouseClicked(mapEvent);

      if (mapEvent.getButton() == MouseEvent.BUTTON3) {
        // remove zones from previous computation
        graphicsLayer.removeAll();
        return;
      }

      tasksInProgress.incrementAndGet();
      updateProgressBarUI("Computing drive time zones...", true);

      // the clicked point is the starting point
      Point startPoint = jMap.toMapPoint(mapEvent.getX(), mapEvent.getY());
      Graphic startPointGraphic = new Graphic(startPoint, SYM_START_POINT);
      graphicsLayer.addGraphic(startPointGraphic);

      executeDriveTimes(startPointGraphic);
    }

    private void executeDriveTimes(Graphic startPointGraphic) {

      // create a Geoprocessor that points to the remote geoprocessing service.
      final Geoprocessor geoprocessor = new Geoprocessor(URL_GEOPROCESSING_SERVICE);
      // set the output and process spatial reference to the map's spatial reference
      geoprocessor.setOutSR(jMap.getSpatialReference());
      geoprocessor.setProcessSR(jMap.getSpatialReference());

      // initialize the required input parameters: refer to help link in the
      // geoprocessing service URL for a list of required parameters
      List<GPParameter> gpInputParams = new ArrayList<>();

      GPFeatureRecordSetLayer gpInputRecords = new GPFeatureRecordSetLayer("Facilities");
      gpInputRecords.addGraphic(startPointGraphic);
      gpInputRecords.setGeometryType(Type.POINT);
      gpInputRecords.setSpatialReference(jMap.getSpatialReference());

      GPString breakValues = new GPString("Break_Values");
      breakValues.setValue("1 2 3"); // 3 breaks: we will get 3 output polygons back

      GPString breakUnits = new GPString("Break_Units");
      breakUnits.setValue("Minutes");

      gpInputParams.add(gpInputRecords);
      gpInputParams.add(breakValues);
      gpInputParams.add(breakUnits);

      // execute the geoprocessing request - service supports 'Submit Job' execution type
      geoprocessor.submitJobAndGetResultsAsync(gpInputParams, 
          new String[] {"ServiceAreas"}, 
          null, 
          new GPJobResultCallbackListener() {

        @Override
        public void onError(Throwable e) {
          e.printStackTrace();
          updateProgressBarUI(null, tasksInProgress.decrementAndGet() > 0);
        }

        @Override
        public void onCallback(GPJobResource jobResource, GPParameter[] outParameters) {
          updateProgressBarUI(null, tasksInProgress.decrementAndGet() > 0);
          // process results!
          for (GPParameter param : outParameters) {
            if (param instanceof GPFeatureRecordSetLayer) {
              // get all the graphics and add them to the graphics layer.
              // there will be one graphic per zone.
              int zone = 2;
              for (Graphic graphic : ((GPFeatureRecordSetLayer) param).getGraphics()) {
                // add to the graphics layer
                graphicsLayer.addGraphic(new Graphic(graphic.getGeometry(), zoneFillSymbols[zone--]));
              }
            }
          }
        }
      });
    }
  }

  // ------------------------------------------------------------------------
  // Static methods
  // ------------------------------------------------------------------------
  /**
   * Starting point of this application.
   * @param args arguments to this application.
   */
  public static void main(String[] args) {

    SwingUtilities.invokeLater(new Runnable() {
      @Override
      public void run() {
        try {
          // instance of this application
          DriveTimesApp driveTimesApp = new DriveTimesApp();

          // create the UI, including the map, for the application
          JFrame appWindow = driveTimesApp.createWindow();
          appWindow.add(driveTimesApp.createUI());
          appWindow.setVisible(true);
        } catch (Exception e) {
          e.printStackTrace();
        }
      }
    });
  }

  // ------------------------------------------------------------------------
  // Public methods
  // ------------------------------------------------------------------------
  /**
   * Creates and displays the UI, including the map, for this application.
   * @return the UI for this sample.
   */
  public JComponent createUI() {
    // application content
    JComponent contentPane = createContentPane();

    // progress bar
    progressBar = createProgressBar(contentPane);

    // map
    map = createMap();

    // description
    JPanel description = createDescription();
    description.setLocation(10, 10);

    contentPane.add(description);
    contentPane.add(progressBar);
    contentPane.add(map);

    return contentPane;
  }

  // ------------------------------------------------------------------------
  // Private methods
  // ------------------------------------------------------------------------
  /**
   * Creates a map.
   * @return a map.
   */
  private JMap createMap() {

    final JMap jMap = new JMap();

    ArcGISTiledMapServiceLayer tiledLayer = new ArcGISTiledMapServiceLayer(URL_STREETS);
    jMap.getLayers().add(tiledLayer);
    jMap.setExtent(new Envelope(new Point(-117.1750, 32.727), 0.1, 0.1));

    // -----------------------------------------------------------------------------------------
    // graphics layer - to add result from the geoprocessing execution
    // -----------------------------------------------------------------------------------------
    GraphicsLayer graphicsLayer = new GraphicsLayer();
    jMap.getLayers().add(graphicsLayer);

    // -----------------------------------------------------------------------------------------
    // geoprocessing service executor
    // -----------------------------------------------------------------------------------------
    DriveTimesExecutor driveTimesExecutor = new DriveTimesExecutor(jMap, graphicsLayer);
    jMap.addMouseListener(driveTimesExecutor);

    return jMap;
  }

  /**
   * Creates a window.
   * @return a window.
   */
  private JFrame createWindow() {
    JFrame window = new JFrame("Drive Time Geoprocessing Application");
    window.setBounds(100, 100, 1000, 700);
    window.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
    window.getContentPane().setLayout(new BorderLayout(0, 0));
    window.addWindowListener(new WindowAdapter() {
      @Override
      public void windowClosing(WindowEvent windowEvent) {
        super.windowClosing(windowEvent);
        map.dispose();
      }
    });
    return window;
  }

  /**
   * Creates a content pane.
   * @return a content pane.
   */
  private static JLayeredPane createContentPane() {
    JLayeredPane contentPane = new JLayeredPane();
    contentPane.setBounds(100, 100, 1000, 700);
    contentPane.setLayout(new BorderLayout(0, 0));
    contentPane.setVisible(true);
    return contentPane;
  }

  /**
   * Creates a progress bar.
   * @param parent progress bar's parent. The horizontal axis of the progress bar will be
   * center-aligned to the parent.
   * @return a progress bar.
   */
  private static JProgressBar createProgressBar(final JComponent parent) {
    final JProgressBar progressBar = new JProgressBar();
    progressBar.setSize(260, 20);
    parent.addComponentListener(new ComponentAdapter() {
      @Override
      public void componentResized(ComponentEvent e) {
        progressBar.setLocation(
            parent.getWidth()/2 - progressBar.getWidth()/2,
            parent.getHeight() - progressBar.getHeight() - 20);
      }
    });
    progressBar.setStringPainted(true);
    progressBar.setIndeterminate(true);
    progressBar.setVisible(false);
    return progressBar;
  }

  /**
   * Updates progress bar UI from Swing's Event Dispatch Thread.
   * @param str string to be set.
   * @param visible flag to indicate visibility of the progress bar.
   */
  private void updateProgressBarUI(final String str, final boolean visible) {
    SwingUtilities.invokeLater(new Runnable() {
      @Override
      public void run() {
        if (str != null) {
          progressBar.setString(str);
        }
        progressBar.setVisible(visible);
      }
    }); 
  }

  /**
   * Creates description UI component.
   * @return description.
   */
  private JPanel createDescription() {
    JPanel descriptionContainer = new JPanel();
    descriptionContainer.setLayout(new BoxLayout(descriptionContainer, 0));
    descriptionContainer.setSize(230, 120);
    JTextArea description = new JTextArea(
        "Left click on the map to set the start point. Zones with drive times of 1, 2 and " +
            "3 minutes will be displayed. The UI will not freeze while computing the zones. " +
        "Right click to remove all graphics.");
    description.setFont(new Font("Verdana", Font.PLAIN, 11));
    description.setForeground(Color.WHITE);
    description.setBackground(new Color(0, 0, 0, 180));
    description.setEditable(false);
    description.setLineWrap(true);
    description.setWrapStyleWord(true);
    description.setBorder(BorderFactory.createEmptyBorder(5,10,5,5));
    descriptionContainer.add(description);
    descriptionContainer.setBackground(new Color(0, 0, 0, 0));
    descriptionContainer.setBorder(new LineBorder(new Color(0, 0, 0, 60), 5, false));
    return descriptionContainer;
  }
}
Feedback on this topic?