Viewshed

Download Sample Viewer

Description

This sample demonstrates use of the Geoprocessor to call a Viewshed geoprocessing service. To use the sample, specify the maximum distance from the input point for which the viewshed should be calculated, and click a point on the map. Once the viewshed is calculated, it will be shown on the map. In the code-behind, a Geoprocessor is used to pass the distance and click point to the ViewShed service. When the results are returned, they are added to a GraphicsLayer and shown 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.executeAsync(...);
  

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.Dimension;
import java.awt.Font;
import java.awt.event.ComponentAdapter;
import java.awt.event.ComponentEvent;
import java.awt.event.KeyEvent;
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.JLabel;
import javax.swing.JLayeredPane;
import javax.swing.JOptionPane;
import javax.swing.JPanel;
import javax.swing.JProgressBar;
import javax.swing.JTextArea;
import javax.swing.JTextField;
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.Point;
import com.esri.core.geometry.SpatialReference;
import com.esri.core.map.CallbackListener;
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.GPLinearUnit;
import com.esri.core.tasks.ags.geoprocessing.GPParameter;
import com.esri.core.tasks.ags.geoprocessing.Geoprocessor;
import com.esri.map.ArcGISTiledMapServiceLayer;
import com.esri.map.GraphicsLayer;
import com.esri.map.JMap;
import com.esri.map.MapEvent;
import com.esri.map.MapEventListenerAdapter;

/**
 * This sample demonstrates use of the {@link Geoprocessor} to call an online ViewShed 
 * geoprocessing service. To use the sample, specify the maximum distance from the input 
 * point for which the viewshed should be calculated, and click a point on the map. Once 
 * the viewshed is calculated, it will be shown on the map.
 * <p>
 * In the code, a {@link Geoprocessor} is used to pass the distance and clicked point to the
 * ViewShed service. When the results are returned, they are added to a {@link GraphicsLayer}
 * and shown in the map.
 */
public class ViewshedApp {

  // default buffer distance
  private final int DEFAULT_BUFFER = 9;
  // resources
  private final String URL_USA_TOPO =
      "http://services.arcgisonline.com/ArcGIS/rest/services/World_Topo_Map/MapServer";

  private JMap map;
  private JComponent contentPane;
  private SpatialReference srMap;
  private JProgressBar progressBar;
  private AtomicInteger tasksInProgress = new AtomicInteger(0);

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

  // ------------------------------------------------------------------------
  // Core functionality
  // ------------------------------------------------------------------------
  /**
   * Class that executes a remote geoprocessing service to calculate the viewshed.
   */
  class ViewshedExecutor extends MouseInputAdapter {

    // URL to remote geoprocessing service
    private final String URL_GEOPROCESSING_SERVICE =
        "http://sampleserver1.arcgisonline.com/ArcGIS/rest/services/Elevation/" +
            "ESRI_Elevation_World/GPServer/Viewshed";

    // symbology
    private final SimpleMarkerSymbol SYM_VIEWSHED_POINT =
        new SimpleMarkerSymbol(Color.BLACK, 18, Style.X);
    private final SimpleLineSymbol SYM_VIEWSHED_BORDER =
        new SimpleLineSymbol(Color.RED, 1);
    private final SimpleFillSymbol SYM_VIEWSHED =
        new SimpleFillSymbol(new Color(255, 0, 0, 80), SYM_VIEWSHED_BORDER);

    JMap jMap;
    GraphicsLayer graphicsLayer;
    JTextFieldInt txtViewshedBuffer;

    /**
     * Creates an object that executes the geoprocessing service to calculate viewshed.
     * @param jMap map to get the starting point, and to convert between spatial coordinates.
     * @param graphicsLayer graphics layer to which the viewshed will be added.
     * @param txtViewshedBuffer input to the geoprocessing service indicating the maximum
     * distance from the clicked point from which the viewshed will be computed.
     */
    public ViewshedExecutor(
        JMap jMap,
        GraphicsLayer graphicsLayer,
        JTextFieldInt txtViewshedBuffer) {
      this.jMap = jMap;
      this.graphicsLayer = graphicsLayer;
      this.txtViewshedBuffer = txtViewshedBuffer;
    }

    @Override
    public void mouseClicked(MouseEvent mouseEvent) {

      super.mouseClicked(mouseEvent);

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

      int inputValue = txtViewshedBuffer.getIntValue();
      if (inputValue > 12 || inputValue < 1) {
        JOptionPane.showMessageDialog(contentPane,
            "Please enter an integer from 1 to 12.", "", JOptionPane.WARNING_MESSAGE);
        return;
      }

      tasksInProgress.incrementAndGet();
      updateProgressBarUI("Computing the viewshed...", true);

      // the click point is the viewshed point
      Point viewshedPoint = jMap.toMapPoint(mouseEvent.getX(), mouseEvent.getY());
      Graphic viewshedPointGraphic = new Graphic(viewshedPoint, SYM_VIEWSHED_POINT);
      graphicsLayer.addGraphic(viewshedPointGraphic);

      // create a Geoprocessor that points to the remote geoprocessing service.
      // initialize the input parameters. refer to help link in the geoprocessing service URL.
      Geoprocessor geoprocessor = new Geoprocessor(URL_GEOPROCESSING_SERVICE);
      geoprocessor.setProcessSR(srMap);
      geoprocessor.setOutSR(srMap);

      List<GPParameter> gpInputParams = new ArrayList<>();

      GPFeatureRecordSetLayer gpInputViewshedPoint =
          new GPFeatureRecordSetLayer("Input_Observation_Point");
      gpInputViewshedPoint.setSpatialReference(srMap);
      gpInputViewshedPoint.addGraphic(viewshedPointGraphic);

      GPLinearUnit gpInputViewshedDistance = new GPLinearUnit("Viewshed_Distance");
      gpInputViewshedDistance.setUnits("esriMiles");
      gpInputViewshedDistance.setDistance(inputValue);

      gpInputParams.add(gpInputViewshedPoint);
      gpInputParams.add(gpInputViewshedDistance);

      // execute the geoprocessing request

      // -------------------------------------------------------------------------------------
      // Option 1 - execute sync
      // Client execution will happen in this thread. It will be blocked on server.
      // Client side result collection will happen in this thread. It will be blocked on server.
      //
      // This is ideal for jobs that take less processing time and whose result size is small.
      // This will freeze the UI.
      // -------------------------------------------------------------------------------------
      /*GPParameter[] result = geoprocessor.execute(gpInputParams);
            processResult(result);*/

      // -------------------------------------------------------------------------------------
      // Option 2 - execute async
      // Client execution will happen in a new thread. It will be blocked on server.
      // Client side result collection will happen on that thread. It will be blocked on server.
      //
      // This is ideal for jobs that take relatively more processing time than option 1
      // and whose result size is small.
      // -------------------------------------------------------------------------------------
      geoprocessor.executeAsync(
          gpInputParams,
          new CallbackListener<GPParameter[]>() {

            @Override
            public void onError(Throwable th) {
              th.printStackTrace();
            }

            @Override
            public void onCallback(final GPParameter[] result) {
              updateProgressBarUI(null, tasksInProgress.decrementAndGet() > 0);
              processResult(result);
            }
          }
          );
    }

    /**
     * Process result from geoprocessing execution.
     * @param result output of geoprocessing execution.
     */
    private void processResult(GPParameter[] result) {
      for (GPParameter outputParameter : result) {
        if (outputParameter instanceof GPFeatureRecordSetLayer) {
          GPFeatureRecordSetLayer gpLayer = (GPFeatureRecordSetLayer) outputParameter;

          // get all the graphics and add them to the graphics layer.
          for (Graphic resultGraphic : gpLayer.getGraphics()) {
            Graphic theGraphic = new Graphic(resultGraphic.getGeometry(), SYM_VIEWSHED);
            // add to the graphics layer
            graphicsLayer.addGraphic(theGraphic);
          }
        }
      }
    }
  }

  // ------------------------------------------------------------------------
  // 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
          ViewshedApp viewshedApp = new ViewshedApp();

          // create the UI, including the map, for the application
          JFrame appWindow = viewshedApp.createWindow();
          appWindow.add(viewshedApp.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
    contentPane = createContentPane();

    // progress bar
    progressBar = createProgressBar(contentPane);

    // description
    JTextArea description = createDescription();

    // label to input distance
    JLabel lblViewshedBuffer = new JLabel("Viewshed Buffer (miles): ");
    lblViewshedBuffer.setForeground(Color.BLACK);
    lblViewshedBuffer.setFont(new Font("Verdana", Font.BOLD, 12));
    lblViewshedBuffer.setMinimumSize(new Dimension(200, 20));

    // text field to input distance
    JTextFieldInt txtViewshedBuffer = new JTextFieldInt(9, 2, DEFAULT_BUFFER);
    txtViewshedBuffer.setMaximumSize(new Dimension(20, 20));

    // group the above UI item into a panel
    final JPanel controlPanel = new JPanel();
    BoxLayout boxLayout = new BoxLayout(controlPanel, BoxLayout.X_AXIS);
    controlPanel.setLayout(boxLayout);
    controlPanel.setSize(240, 20);
    controlPanel.setBorder(BorderFactory.createEmptyBorder(2,5,2,5));
    controlPanel.setBackground(new Color(255, 255, 255, 255));
    controlPanel.add(lblViewshedBuffer);
    controlPanel.add(txtViewshedBuffer);

    JPanel panel = new JPanel();
    panel.setLayout(new BorderLayout(0, 0));
    panel.setLocation(10, 10);
    panel.setSize(240, 120);
    panel.setBackground(new Color(0, 0, 0, 0));
    panel.setBorder(new LineBorder(Color.BLACK, 3, false));

    // group control and description in a panel
    panel.add(description, BorderLayout.CENTER);
    panel.add(controlPanel, BorderLayout.SOUTH);

    // map
    map = createMap(txtViewshedBuffer);

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

    return contentPane;
  }

  // ------------------------------------------------------------------------
  // Private methods
  // ------------------------------------------------------------------------
  /**
   * Creates a window.
   * @return a window.
   */
  private JFrame createWindow() {
    JFrame window = new JFrame("Viewshed 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 the 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 a map.
   * @param txtViewshedBuffer input to get viewshed buffer
   * @return a map.
   */
  private JMap createMap(JTextFieldInt txtViewshedBuffer) {
    final JMap jMap = new JMap();
    // focus on Yosemite national park
    jMap.setExtent(new Envelope(-13386516, 4516694, -13236394, 4616827));

    // store the spatial reference for later once the 'map ready' event fires
    jMap.addMapEventListener(new MapEventListenerAdapter() {
      @Override
      public void mapReady(final MapEvent mapEvent) {
        SwingUtilities.invokeLater(new Runnable() {
          @Override
          public void run() {
            srMap = mapEvent.getMap().getSpatialReference();
          }
        });
      }
    });

    // -----------------------------------------------------------------------------------------
    // remote map service layer - base layer with US topology
    // -----------------------------------------------------------------------------------------
    final ArcGISTiledMapServiceLayer baseLayer = new ArcGISTiledMapServiceLayer(URL_USA_TOPO);
    jMap.getLayers().add(baseLayer);

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

    // -----------------------------------------------------------------------------------------
    // geoprocessing service executor
    // -----------------------------------------------------------------------------------------
    ViewshedExecutor viewshedExecutor =
        new ViewshedExecutor(jMap, graphicsLayer, txtViewshedBuffer);
    jMap.addMouseListener(viewshedExecutor);

    return jMap;
  }

  /**
   * Creates description UI component.
   * @return description.
   */
  private JTextArea createDescription() {
    JTextArea description = new JTextArea(
        "Left click on the map to set the viewshed point. The viewshed will be computed " +
            "from the specified buffer area around the viewshed point. " +
        "Right click to remove 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,5,5,5));
    return description;
  }

  /**
   * Text field that allows digits only.
   */
  class JTextFieldInt extends JTextField {

    private static final long serialVersionUID = 1L;

    int maxDigits;
    int defaultValue;

    /**
     * Creates a text field that accepts digits only.
     * @param num initial value.
     * @param maxDigits maximum number of digits to be allowed in its input.
     * @param defaultValue value to be returned by {@link #getIntValue()}
     * when the text field is empty.
     */
    public JTextFieldInt(int num, int maxDigits, int defaultValue) {
      super(num + "");
      this.maxDigits = maxDigits;
      this.defaultValue = defaultValue;
    }


    /**
     * Processes the input key event.
     * @param ev input key event.
     */
    @Override
    public void processKeyEvent(KeyEvent ev) {
      if (getText() != null && getText().length() > maxDigits) {
        // if current key will exceed limit,
        // then allow only special keys such as delete, backspace
        if (ev.getKeyCode() == KeyEvent.VK_DELETE ||
            ev.getKeyCode() == KeyEvent.VK_BACK_SPACE) {
          super.processKeyEvent(ev);
        }
        return;
      }
      // if length is within limit, then allow non-letter (digits, delete, backspace, etc)
      if (Character.isDigit(ev.getKeyChar()) ||
          ev.getKeyCode() == KeyEvent.VK_DELETE ||
          ev.getKeyCode() == KeyEvent.VK_BACK_SPACE) {
        super.processKeyEvent(ev);
      }
    }

    /**
     * Returns the corresponding integer value in the text box.
     *
     * @throws NumberFormatException if the string does not contain a parsable integer.
     * @return the corresponding integer value in the text box. Returns the default value
     * initialized in the constructor when the text field is empty.
     */
    public int getIntValue() {
      if (getText() == null) {
        return defaultValue;
      }
      return Integer.parseInt(getText());
    }
  }
}
Feedback on this topic?