Hit test overlay

Download Sample Viewer

Description

This sample demonstrates use of HitTestOverlay to select cities from a feature layer at the current mouse-click location. A HitTestOverlay is added as a map overlay to the JMap. A listener of type HitTestListener is attached to HitTestOverlay to listen for selected features. The selected features are then selected in the feature layer.

Code snippet


    final HitTestOverlay hitTestOverlay = new HitTestOverlay(featureLayer);
    // add a listener to perform some action when a feature was hit
    hitTestOverlay.addHitTestListener(new HitTestListener() {
        @Override
        public void featureHit(HitTestEvent arg0) {
            // code to perform when a mouse click has hit at least one feature
            // e.g. get the hit features at the current location
            List<Feature> features = hitTestOverlay.getHitFeatures();
            // then loop through list of feature for their attributes, geometry, etc.
        }
    });
    jMap.addMapOverlay(hitTestOverlay);
  

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.toolkit;

import java.awt.BorderLayout;
import java.awt.Color;
import java.awt.Dimension;
import java.awt.Font;
import java.awt.event.WindowAdapter;
import java.awt.event.WindowEvent;
import java.io.File;
import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.io.IOException;
import java.util.List;

import javax.swing.BorderFactory;
import javax.swing.BoxLayout;
import javax.swing.JComponent;
import javax.swing.JFrame;
import javax.swing.JLayeredPane;
import javax.swing.JOptionPane;
import javax.swing.JPanel;
import javax.swing.JScrollPane;
import javax.swing.JTextArea;
import javax.swing.SwingUtilities;
import javax.swing.border.LineBorder;

import com.esri.core.geodatabase.Geodatabase;
import com.esri.core.geodatabase.GeodatabaseFeatureTable;
import com.esri.core.geometry.Envelope;
import com.esri.core.map.Feature;
import com.esri.core.renderer.SimpleRenderer;
import com.esri.core.symbol.SimpleMarkerSymbol;
import com.esri.map.ArcGISTiledMapServiceLayer;
import com.esri.map.FeatureLayer;
import com.esri.map.JMap;
import com.esri.map.LayerList;
import com.esri.runtime.ArcGISRuntime;
import com.esri.toolkit.overlays.HitTestEvent;
import com.esri.toolkit.overlays.HitTestListener;
import com.esri.toolkit.overlays.HitTestOverlay;

/**
 * This sample demonstrates use of {@link HitTestOverlay} to select damage inspection points from a 
 * feature layer at the current mouse-click location.
 * <p>
 * A {@link HitTestOverlay} is added as a map overlay to the JMap. A listener of type 
 * {@link HitTestListener} is attached to {@link HitTestOverlay} to listen for selected features. 
 * The selected features are then selected in the feature layer. In order to make the selected 
 * features more visible, their draw order is updated such that it is equal to the maximum draw 
 * order + 1. Therefore the selected features are drawn on top of the unselected ones.
 */
public class HitTestOverlayApp {

  // resources
  private static final String URL_BASEMAP = "http://services.arcgisonline.com/ArcGIS/rest/services/World_Topo_Map/MapServer";

  private static String FSP = System.getProperty("file.separator");
  private static final String RELATIVE_PATH = 
    "disconnected" + FSP + "geodatabase" + FSP + "DamageInspection6.geodatabase";
  private static final String GEODATABASE_PATH = getPathSampleData() + FSP + RELATIVE_PATH;
  private static final int LAYER_ID = 0;

  // UI components
  private JScrollPane scrollPane;
  private JTextArea txtStatus;

  // JMap
  private JMap map;
  
  // geodatabase
  private Geodatabase geodatabase;

  // symbology
  private SimpleMarkerSymbol symInspection;

  // ------------------------------------------------------------------------
  // Core functionality
  // ------------------------------------------------------------------------
  /**
   * Adds a HitTestOverlay, which is a map overlay returning features which were in the area clicked by the mouse. To
   * use the overlay, a HitTestListener should be implemented which performs some action when one or more features were
   * hit at the current mouse-click location. In this case the listener makes any hit features 'selected' in the
   * featureLayer, such that they will be rendered with the selection symbol set on the layer.
   * 
   * @param jMap map to which the overlay will be added to.
   * @param damageLayer the layer whose features will be selected.
   * @param txtStatus UI where the information of the selected features will be updated.
   */
  private void addHitTestOverlay(final JMap jMap, final FeatureLayer damageLayer) {

    // create a hit test overlay for the feature layer
    final HitTestOverlay hitTestOverlay = new HitTestOverlay(damageLayer);

    // add a listener to perform some action when a feature was hit
    hitTestOverlay.addHitTestListener(new HitTestListener() {

      String NEW_LINE_SEP = System.getProperty("line.separator");

      /**
       * This method is invoked whenever any feature(s) is hit at the current mouse-click location on map.
       */
      @Override
      public void featureHit(HitTestEvent event) {
        // reset the feature layer to remove previously selected cities
        damageLayer.clearSelection();

        // get the hit graphics at the current location
        List<Feature> hitFeatures = hitTestOverlay.getHitFeatures();

        // get the number of features selected for display in UI
        StringBuilder str = new StringBuilder();
        str.append("Number of inspection points selected: " + hitFeatures.size());
        str.append(NEW_LINE_SEP);
        str.append(NEW_LINE_SEP);

        // get city and state name of every city selected
        int index = 1;
        for (Feature feature : hitFeatures) {
          str.append(index++ + ") ");
          // get the damaged place name value from the graphic
          str.append(feature.getAttributeValue("PLACENAME"));
          // get the type of damage value from the graphic
          str.append(" (" + feature.getAttributeValue("TYPDAMAGE") + ")");
          str.append(NEW_LINE_SEP);

          // make the feature selected in the feature layer:
          // the graphic will be rendered with the layer's selection symbol
          // while it is selected in the layer
          damageLayer.selectFeature(feature.getId());
        }

        // update the UI to display the list of cities selected
        txtStatus.setText(str.toString());

        // set scroll bar to top
        SwingUtilities.invokeLater(new Runnable() {
          @Override
          public void run() {
            scrollPane.getVerticalScrollBar().setValue(0);
          }
        });

      }
    });

    // add the overlay to the JMap
    jMap.addMapOverlay(hitTestOverlay);
  }

  // ------------------------------------------------------------------------
  // 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
          HitTestOverlayApp hitTestOverlayApp = new HitTestOverlayApp();

          // create the UI, including the map, for the application.
          JFrame appWindow = hitTestOverlayApp.createWindow();
          appWindow.add(hitTestOverlayApp.createUI());
          appWindow.setVisible(true);
        } catch (Exception e) {
          // on any error, display the stack trace.
          e.printStackTrace();
        }
      }
    });
  }

  // ------------------------------------------------------------------------
  // Public methods
  // ------------------------------------------------------------------------
  /**
   * Creates and displays the UI, including the map, for this application.
   */
  public JComponent createUI() throws Exception {

    // application content
    JComponent contentPane = createContentPane();

    // control panel to display information
    JPanel controlPanel = createControlPanel();
    contentPane.add(controlPanel);

    // map
    map = createMap();
    contentPane.add(map);

    return contentPane;
  }

  // ------------------------------------------------------------------------
  // Private methods
  // ------------------------------------------------------------------------
  /**
   * Creates the map.
   * 
   * @return a map.
   */
  private JMap createMap() throws Exception {
    final JMap jMap = new JMap();

    // -----------------------------------------------------------------------------------------
    // remote map service layer - base layer with US topology
    // -----------------------------------------------------------------------------------------
    final ArcGISTiledMapServiceLayer baseLayer = new ArcGISTiledMapServiceLayer(URL_BASEMAP);
    // focus on San Diego
    jMap.setExtent(new Envelope(-13089107, 3871742, -12848997, 4047955));
    LayerList layers = jMap.getLayers();
    layers.add(baseLayer);

    // -----------------------------------------------------------------------------------------
    // Feature Layer - with data about damage inspection points.
    // -----------------------------------------------------------------------------------------
    // create the geodatabase and geodatabase feature table once
    geodatabase = new Geodatabase(GEODATABASE_PATH);
    GeodatabaseFeatureTable table = geodatabase.getGeodatabaseFeatureTableByLayerId(LAYER_ID);
    final FeatureLayer inspectionLayer = new FeatureLayer(table);
    // set a renderer for the layer
    inspectionLayer.setRenderer(new SimpleRenderer(symInspection));
    // set a selection symbol for when a feature is selected in the layer
    layers.add(inspectionLayer);
    inspectionLayer.setSelectionColor(Color.CYAN);
    // add the hit test overlay
    addHitTestOverlay(jMap, inspectionLayer);

    return jMap;
  }

  /**
   * Creates a panel displaying info on selected features
   * 
   * @return a panel.
   */
  private JPanel createControlPanel() {

    // status UI to display the selected features
    txtStatus = new JTextArea();
    txtStatus.setText("Selected features will be displayed here.");
    txtStatus.setLineWrap(true);
    txtStatus.setWrapStyleWord(true);
    txtStatus.setFont(new Font(txtStatus.getFont().getName(), txtStatus.getFont().getStyle(), 12));
    txtStatus.setBackground(Color.BLACK);
    txtStatus.setForeground(Color.WHITE);
    txtStatus.setBorder(BorderFactory.createEmptyBorder(5, 5, 5, 5));
    txtStatus.setEditable(false);

    // description
    JTextArea description = new JTextArea(
        "Click on a location to view the inspection point's place name and damage type.");
    description.setForeground(Color.WHITE);
    description.setBackground(new Color(0, 0, 0, 80));
    description.setEditable(false);
    description.setLineWrap(true);
    description.setWrapStyleWord(true);
    description.setBorder(BorderFactory.createEmptyBorder(5, 5, 5, 5));
    description.setSize(200, 60);
    description.setMaximumSize(new Dimension(200, 60));

    // group the above UI items into a panel
    JPanel panel = new JPanel();
    BoxLayout boxLayout = new BoxLayout(panel, BoxLayout.Y_AXIS);
    panel.setLayout(boxLayout);
    panel.setLocation(10, 10);
    panel.setSize(200, 190);
    panel.setBackground(new Color(0, 0, 0, 80));
    panel.setBorder(new LineBorder(Color.BLACK, 5, false));

    // Scroll pane for reporting features
    scrollPane = new JScrollPane(txtStatus);

    panel.add(description);
    panel.add(scrollPane);

    return panel;
  }

  /**
   * Creates the application window, and disposes the map and geodatabase 
   * when the window is closed.
   * 
   * @return a window.
   */
  private JFrame createWindow() {
    JFrame window = new JFrame("Hit Test Overlay 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);
        if (map != null) map.dispose();
        if (geodatabase != null) geodatabase.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;
  }

  private static String getPathSampleData() {
    String dataPath = null;
    String javaPath = ArcGISRuntime.getInstallDirectory();
    if (javaPath != null) {
      if (!(javaPath.endsWith("/") || javaPath.endsWith("\\"))){
        javaPath += FSP;
      }
      dataPath = javaPath + "sdk" + FSP + "samples" + FSP + "data" + FSP;
    }
    File dataDir = new File(dataPath);
    if (!dataDir.exists()) { 
      dataPath = ".." + FSP + "data" + FSP;
      dataDir = new File(dataPath);
    }
    
    return copyIfNoCreateFileAccess(dataDir, RELATIVE_PATH);
  }
  
  /**
   * Copies file to java.io.tmpdir if the user does not have permission to create file
   * in the current dataDir.
   * @param dataDir directory containing the file to be copied.
   * @param relativeFilePath path of file to be copied relative to the dataDir.
   * @return the path of directory that contains the file copied. This will be in the java.io.tmpdir
   * if copied, otherwise it will be same as input dataDir.
   */
  private static String copyIfNoCreateFileAccess(File dataDir, String relativeFilePath) {
    // check if can create a file in the data directory.
    // can't use File.canWrite() because create is considered different from write. 
    File tempFolder = null;
    try {
      tempFolder = new File(dataDir.getAbsolutePath() + FSP + "temp");
      if (tempFolder.exists()) {
        tempFolder.delete();
      }
      tempFolder.mkdir();
    } catch (Exception ex) {
      ex.printStackTrace();
    }
    if (tempFolder != null && tempFolder.exists()) {
      tempFolder.delete();
      return dataDir.getAbsolutePath();
    }

    // no permission to create a new file in the existing data folder.
    // so copy the data file to the java.io.tmpdir folder.
    String tempDataDir = System.getProperty("java.io.tmpdir") + FSP + 
        "ArcGIS SDKs" + FSP + "java" + ArcGISRuntime.getAPIVersion() + 
        FSP + "sdk" + FSP + "samples" + FSP + "data";
    
    String fromFilePath = dataDir + FSP + relativeFilePath;
    String toFilePath = tempDataDir + FSP + relativeFilePath;
    FileInputStream in = null;
    FileOutputStream out = null;
    try {
      File toFile = new File(toFilePath);
      if (toFile.exists()) {
        return tempDataDir;
      }
      toFile.getParentFile().mkdirs();
      toFile.createNewFile();
      System.out.println("Copying file from " + fromFilePath + " to " + toFile.getAbsolutePath());
      in = new FileInputStream(fromFilePath);
      out = new FileOutputStream(toFile);
      byte[] byteBuf = new byte[4096]; 
      int numBytesRead;
      while ((numBytesRead = in.read(byteBuf)) != -1) {
        out.write(byteBuf, 0, numBytesRead);
      }
    } catch (Exception ex) {
      JOptionPane.showMessageDialog(null, wrap(ex.getMessage()));
    } finally {
      if (in != null) {
        try {
          in.close();
        } catch (IOException ex) {
          ex.printStackTrace();
        }
      }
      if (out != null) {
        try {
          out.close();
        } catch (IOException ex) {
          ex.printStackTrace();
        }
      }
    }
    System.out.println("Copy completed.");
    return tempDataDir;
  }
  
  private static String wrap(String str) {
    // create a HTML string that wraps text when longer
    return "<html><p style='width:200px;'>" + str + "</html>";
  }
}
Feedback on this topic?