Find Locations custom Java geoprocessing tool
arcgissamples\geoprocessing\customtool\FindLocations.java
/* Copyright 2010 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 arcgissamples.geoprocessing.customtool;

import java.io.IOException;
import java.io.File;

import com.esri.arcgis.interop.AutomationException;
import com.esri.arcgis.system.AoInitialize;
import com.esri.arcgis.system.IArray;
import com.esri.arcgis.system.IName;
import com.esri.arcgis.system.ITrackCancel;
import com.esri.arcgis.system.Array;
import com.esri.arcgis.system.esriLicenseProductCode;
import com.esri.arcgis.system.esriLicenseStatus;
import com.esri.arcgis.geometry.esriGeometryType;
import com.esri.arcgis.geoprocessing.*;
import com.esri.arcgis.datasourcesfile.*;
import com.esri.arcgis.geodatabase.*;
import com.esri.arcgis.system.*;

public class FindLocations extends BaseGeoprocessingTool {
  private String toolName = "FindLocations";
  private String displayName = "Java Location Finder Tool";
  private String metadataFileName = "FindLocations.xml";
  private GeoProcessor gp = null;
  private String relevantFCName = null;
  private FeatureClass relevantFeaturesFC = null;
  private static String outputFolder = null;
  IGPMessages messages = null;
  private static String sw = null;
  

  public FindLocations() {
    try {
      // initialize GeoProcessor
      gp = new GeoProcessor();
      gp.setOverwriteOutput(true);

      relevantFCName = "RelevantFeatures.shp";
    } catch (Exception e) {
      e.printStackTrace();
    }
  }

  /**
   * Returns name of the tool This name appears when executing the tool at the
   * command line or in scripting. This name should be unique to each toolbox
   * and must not contain spaces.
   */
  public String getName() throws IOException, AutomationException {
    return toolName;
  }

  /**
   * Returns Display Name of the tool, as seen in ArcToolbox.
   */
  public String getDisplayName() throws IOException, AutomationException {
    return displayName;
  }

  /**
   * Returns the full name of the tool
   */
  public IName getFullName() throws IOException, AutomationException {
    return (IName) new FLFunctionFactory().getFunctionName(toolName);
  }

  /**
   * Returns an array of paramInfo This is the location where the parameters to
   * the Function Tool are defined. This property returns an IArray of parameter
   * objects (IGPParameter). These objects define the characteristics of the
   * input and output parameters.
   */
  public IArray getParameterInfo() throws IOException, AutomationException {
    IArray parameters = new Array();

    // input features param1
    GPCompositeDataType compositeDataType = new GPCompositeDataType();
    compositeDataType.addDataType(new GPFeatureLayerType());
    compositeDataType.addDataType(new GPFeatureRecordSetLayerType());

    GPParameter parameter1 = new GPParameter();
    parameter1.setName("in_streetfeature");
    parameter1.setDirection(esriGPParameterDirection.esriGPParameterDirectionInput);
    parameter1.setDisplayName("Enter the Streets feature class");
    parameter1.setParameterType(esriGPParameterType.esriGPParameterTypeRequired);
    parameter1.setDataTypeByRef(compositeDataType);
    parameter1.setValueByRef(new GPFeatureLayer());
    parameters.add(parameter1);

    // input Food location query data param3
    GPParameter parameter2 = new GPParameter();
    parameter2.setName("in_streetquery");
    parameter2.setDirection(esriGPParameterDirection.esriGPParameterDirectionInput);
    parameter2.setDisplayName("Enter or build Query to retrieve 1 or more street feature(s)");
    parameter2.setParameterType(esriGPParameterType.esriGPParameterTypeRequired);
    parameter2.setDataTypeByRef(new GPSQLExpressionType());
    parameter2.setValueByRef(new GPSQLExpression());
    parameter2.addDependency("in_streetfeature");
    parameters.add(parameter2);

    // input distance param2
    GPParameter parameter3 = new GPParameter();
    parameter3.setName("in_distance");
    parameter3.setDirection(esriGPParameterDirection.esriGPParameterDirectionInput);
    parameter3.setDisplayName("Enter Search Distance (in Feet) from street");
    parameter3.setParameterType(esriGPParameterType.esriGPParameterTypeRequired);
    parameter3.setDataTypeByRef(new GPLinearUnitType());
    parameter3.setValueByRef(new GPLinearUnit());
    parameters.add(parameter3);

    // input Food location data param3
    GPParameter parameter4 = new GPParameter();
    parameter4.setName("in_foodlocationfeatures");
    parameter4.setDirection(esriGPParameterDirection.esriGPParameterDirectionInput);
    parameter4.setDisplayName("Enter the Food Locations feature class");
    parameter4.setParameterType(esriGPParameterType.esriGPParameterTypeRequired);
    parameter4.setDataTypeByRef(compositeDataType);
    parameter4.setValueByRef(new GPFeatureLayer());
    parameters.add(parameter4);

    // input food type param4
    GPParameter parameter5 = new GPParameter();
    parameter5.setName("in_foodtype");
    parameter5.setDirection(esriGPParameterDirection.esriGPParameterDirectionInput);
    parameter5.setDisplayName("Select type of food");
    parameter5.setParameterType(esriGPParameterType.esriGPParameterTypeRequired);
    parameter5.setDataTypeByRef(new GPStringType());
    parameter5.setValueByRef(new GPString());

    GPCodedValueDomain cvDomain = new GPCodedValueDomain();
    cvDomain.addStringCode("fastfood", "Fast Food");
    cvDomain.addStringCode("lunchdinner", "Lunch or Dinner");
    cvDomain.addStringCode("alltypes", "All Types");

    parameter5.setDomainByRef(cvDomain);
    parameters.add(parameter5);

    // input location type param5
    IGPDataType stringType = new GPStringType();

    GPValueTable valueTable = new GPValueTable();
    valueTable.addDataType(stringType);

    GPValueTableType valueTableType = new GPValueTableType();
    valueTableType.addDataType(stringType, "Type of Food Joint", 260, null);

    GPParameter parameter6 = new GPParameter();
    parameter6.setName("in_locationtypes");
    parameter6.setDirection(esriGPParameterDirection.esriGPParameterDirectionInput);
    parameter6.setDisplayName("These are the types of locations available.");
    parameter6.setParameterType(esriGPParameterType.esriGPParameterTypeRequired);
    parameter6.setDataTypeByRef(valueTableType);
    parameter6.setValueByRef(valueTable);
    parameters.add(parameter6);

    // output locations param6
    GPParameter parameter7 = new GPParameter();
    parameter7.setName("out_locations");
    parameter7.setDirection(esriGPParameterDirection.esriGPParameterDirectionOutput);
    parameter7.setDisplayName("Output Locations Parameter");
    parameter7.setParameterType(esriGPParameterType.esriGPParameterTypeRequired);
    parameter7.setDataTypeByRef(new DEFeatureClassType());
    parameter7.setValueByRef(new DEFeatureClass());
    parameters.add(parameter7);

    return parameters;
  }

  /**
   * Called each time the user changes a parameter in the tool dialog or Command
   * Line. This updates the output data of the tool, which extremely useful for
   * building models. After returning from UpdateParameters(), the GP framework
   * calls its internal validation routine to check that a given set of
   * parameter values are of the appropriate number, DataType, and value.
   */
  public void updateParameters(IArray paramvalues, IGPEnvironmentManager envMgr) {
    try {
      // retrieve the environment value for scratch workspace
      GPEnvironment scratchWorkspaceEnv = (GPEnvironment) envMgr.findEnvironment("scratchWorkspace");
      IGPValue swValue = scratchWorkspaceEnv.getValue();
      if(!swValue.isEmpty())
      {  
        outputFolder = swValue.getAsText();
      }
      
      // Retrieve Input Feature Set parameter
      IGPParameter inputFeatureSetParameter = (IGPParameter) paramvalues.getElement(0);
      IGPValue inputFeatureSetValue = gpUtilities.unpackGPValue(inputFeatureSetParameter);      
      
      String outputFC = outputFolder + File.separator + "findLocationsOutput.shp";

      // Retrieve Food Type Parameter
      IGPParameter foodTypeParameter = (IGPParameter) paramvalues.getElement(4);
      IGPValue foodTypeValue = gpUtilities.unpackGPValue(foodTypeParameter);
      String foodType = foodTypeValue.getAsText();

      // Retrieve Location Type Parameter
      IGPParameter locationTypeParameter = (IGPParameter) paramvalues.getElement(5);
      GPValueTable locationTypeValue = (GPValueTable) gpUtilities.unpackGPValue(locationTypeParameter);

      /*
       * If food type is "Fast Food", use Gas Stations and Pizza Parlours only.
       * If food type is "Lunch or Dinner", use Restaurants and Pizza Parlours
       * only. If food type is "All", use Gas Stations, Pizza Parlours, and
       * Restaurants.
       */
      // create Pizza Parlours array
      GPString pizzaValue = new GPString();
      pizzaValue.setValue("Pizza Parlours");

      Array pizzaArray = new Array();
      pizzaArray.add(pizzaValue);

      // create Gas Stations array
      GPString gasStationValue = new GPString();
      gasStationValue.setValue("Gas Stations");

      Array gasArray = new Array();
      gasArray.add(gasStationValue);

      // create Restaurants array
      GPString restaurantValue = new GPString();
      restaurantValue.setValue("Restaurants");

      Array restaurantArray = new Array();
      restaurantArray.add(restaurantValue);

      // assign a value to Location Type param based on food type's value
      if (foodType.equalsIgnoreCase("fastfood")) {
        emptyValueTable(locationTypeValue);

        locationTypeValue.insertRecord(0, pizzaArray);
        locationTypeValue.insertRecord(1, gasArray);
      } else if (foodType.equalsIgnoreCase("lunchdinner")) {
        emptyValueTable(locationTypeValue);

        locationTypeValue.insertRecord(0, restaurantArray);
      } else if (foodType.equalsIgnoreCase("alltypes")) {
        emptyValueTable(locationTypeValue);

        locationTypeValue.insertRecord(0, restaurantArray);
        locationTypeValue.insertRecord(1, pizzaArray);
        locationTypeValue.insertRecord(2, gasArray);
      }

      // Retrieve Output Parameter
      //String outputFC = "findLocationsOutput.shp";
      IGPParameter outputLocationsParameter = (IGPParameter) paramvalues.getElement(6);
      DEFeatureClass outputLocationsValue = (DEFeatureClass) gpUtilities.unpackGPValue(outputLocationsParameter);
      if (!inputFeatureSetValue.isEmpty()) {
        if (outputLocationsValue.isEmpty()) {
          outputLocationsValue.setAsText(outputFC);
        }

        gpUtilities.packGPValue(outputLocationsValue, outputLocationsParameter);
      }
    } catch (Exception e) {
      e.printStackTrace();
    }
  }

  /**
   * Called after returning from the internal validation routine. You can
   * examine th  e messages created from internal validation and change them if
   * desired.
   */
  public void updateMessages(IArray paramvalues, IGPEnvironmentManager envMgr, IGPMessages gpMessages) {
    try {
      if (gpMessages.getMaxSeverity() == esriGPMessageSeverity.esriGPMessageSeverityError) {
        for (int i = 0; i < gpMessages.getCount(); i++) {
          System.out.println(gpMessages.getMessage(i).getDescription());
        }
      }
    } catch (Exception e) {
      e.printStackTrace();
    }
  }

  /**
   * Executes the tool
   */
  public void execute(IArray paramvalues, ITrackCancel trackcancel, IGPEnvironmentManager envMgr, IGPMessages gpMessages)
    throws IOException, AutomationException {
    try {
      messages = gpMessages;

      // get Input Feature Set parameter
      messages.addMessage("Reading Input Feature Set parameter...");
      IGPParameter inputFeatureSetParameter = (IGPParameter) paramvalues.getElement(0);
      IGPValue inputFeatureSetValue = gpUtilities.unpackGPValue(inputFeatureSetParameter);
      String inputFeatureSet = inputFeatureSetValue.getAsText();
      messages.addMessage("Input Feature Set: " + inputFeatureSet);

      // get Street query parameter
      messages.addMessage("Reading Query parameter...");
      IGPParameter inputQueryParameter = (IGPParameter) paramvalues.getElement(1);
      IGPValue inputQueryValue = gpUtilities.unpackGPValue(inputQueryParameter);
      String inputQuery = inputQueryValue.getAsText();
      messages.addMessage("Input Query: " + inputQuery);

      IFeatureClass[] streetIFC = new IFeatureClass[1];
      gpUtilities.decodeFeatureLayer(inputFeatureSetValue, streetIFC, null);

      // get Distance parameter
      messages.addMessage("Reading Distance parameter...");
      IGPParameter distanceParameter = (IGPParameter) paramvalues.getElement(2);
      IGPValue distanceValue = gpUtilities.unpackGPValue(distanceParameter);
      String distance = distanceValue.getAsText();
      messages.addMessage("Distance: " + distance);

      // get Input Food Locations Feature Set parameter
      messages.addMessage("Reading Input Food locations Feature Set parameter...");
      IGPParameter inputLocationsParameter = (IGPParameter) paramvalues.getElement(3);
      IGPValue inputLocationsValue = gpUtilities.unpackGPValue(inputLocationsParameter);
      String inputLocations = inputLocationsValue.getAsText();
      messages.addMessage("Input Food locations Set: " + inputLocations);

      // get Food Type Parameter
      messages.addMessage("Reading Food types param...");
      IGPParameter foodTypeParameter = (IGPParameter) paramvalues.getElement(4);
      IGPValue foodTypeValue = gpUtilities.unpackGPValue(foodTypeParameter);
      String foodType = foodTypeValue.getAsText();
      messages.addMessage("Food Type: " + foodType);

      // set Location Type Parameter
      messages.addMessage("Reading Location types param...");
      IGPParameter locationTypeParameter = (IGPParameter) paramvalues.getElement(5);
      GPValueTable locationTypeValue = (GPValueTable) gpUtilities.unpackGPValue(locationTypeParameter);
      String locationType = locationTypeValue.getAsText();
      messages.addMessage("locationType: " + locationType);

      // Output Parameter
      messages.addMessage("Reading Output Parameter...");
      IGPParameter outputParameter = (IGPParameter) paramvalues.getElement(6);
      IGPValue outputValue = gpUtilities.unpackGPValue(outputParameter);
      if (gpUtilities.exists(outputValue)) {
        messages.addMessage("Output already exists. Overwriting it...");
        gpUtilities.delete(outputValue);
      }
      String output = outputValue.getAsText();
      messages.addMessage("Output: " + output);

      // get features from Streets FC, based on user specified query
      String streetsFCName = "queryStreets.shp";
      FeatureClass streetsFC = createShapefile(streetsFCName, sw, esriGeometryType.esriGeometryPolyline,
        new FeatureClass(streetIFC[0]));

      // copy features based on query
      copyFeatures(new FeatureClass(streetIFC[0]), streetsFC, inputQuery);

      // buffer the features in streets fc
      String bufferOutput = outputFolder + File.separator + "bufferOutput.shp";
      messages.addMessage("Buffering ... input feature set at " + bufferOutput + ".");
      buffer(streetsFC, bufferOutput, distance);
      messages.addMessage("Done.");

      // retrieve relevant features from food locations FC
      messages.addMessage("Extracting relevant features based on Location Type List...");
      IFeatureClass[] locationsFC = new IFeatureClass[1];
      gpUtilities.decodeFeatureLayer(inputLocationsValue, locationsFC, null);
      // obtain relevant Features based on user specified food locations and
      // return as GPFeatureRecordSetLayer
      GPFeatureRecordSetLayer relevantFeatures = extractRelevantFeatures(outputFolder, locationType, locationsFC[0],
        inputLocations);
      String intersectInput = relevantFeatures.getAsText();
      messages.addMessage("Done.");

      // Intersect GPFeatureRecordSetLayer with buffer output
      messages.addMessage("Intersecting buffer output with relevant features...");
      intersect(bufferOutput + ";" + intersectInput, output);
      messages.addMessage("Done.");

      // delete relevant features
      messages.addMessage("Cleaning up intermediate data...");
      relevantFeaturesFC.delete();
      messages.addMessage("Done.");

      // assign output fc to output param
      messages.addMessage("Assigning output...");
      gpUtilities.packGPValue(outputValue, outputParameter);
      messages.addMessage("Done.");

      messages.addMessage("Proximity tool execution complete.");
    } catch (Exception e) {
      e.printStackTrace();
    }
  }

  /**
   * Returns metadata file
   */
  public String getMetadataFile() throws IOException, AutomationException {
    return metadataFileName;
  }

  /**
   * Returns status of license. If you don't care about which license levels
   * your tool should work at, just return true
   */
  public boolean isLicensed() throws IOException, AutomationException {
    AoInitialize ao = new AoInitialize();
    int available = esriLicenseStatus.esriLicenseAvailable;
    boolean status = false;

    // check which license level is available
    if (ao.isProductCodeAvailable(esriLicenseProductCode.esriLicenseProductCodeEngine) == available
      || ao.isProductCodeAvailable(esriLicenseProductCode.esriLicenseProductCodeEngineGeoDB) == available
      || ao.isProductCodeAvailable(esriLicenseProductCode.esriLicenseProductCodeArcView) == available) {
      status = true;
    }

    // check out extensions, if applicable.

    return status;
  }

  /*************************************************************************************
   * Util methods
   *************************************************************************************/
  /**
   * Empties specified value table
   * 
   * @param table
   * @throws Exception
   */
  private void emptyValueTable(GPValueTable table) {
    try {
      int recordCount = table.getRecordCount();

      if (recordCount > 0) {
        for (int i = 0; i < recordCount; i++) {
          table.removeRecord(i);
        }
      }
    } catch (Exception e) {
      System.out.println(e.getMessage());
    }
  }

  /**
   * Extracts relevant features based on location type and returns as
   * GPFeatureRecordSetLayer
   * 
   * @param workspace
   * @param locationType
   * @param fc
   * @param templateFC
   * @return GPFeatureRecordSetLayer
   * @throws Exception
   */
  private GPFeatureRecordSetLayer extractRelevantFeatures(String workspace, String locationType, IFeatureClass fc,
    String templateFC) throws Exception {
    String[] list = locationType.split(";");
    int[] numList = new int[list.length];

    for (int i = 0; i < numList.length; i++) {
      if (list[i].equalsIgnoreCase("'Gas Stations'")) {
        numList[i] = 1;
      } else if (list[i].equalsIgnoreCase("'Grocery Stores'")) {
        numList[i] = 2;
      } else if (list[i].equalsIgnoreCase("'Pizza Parlours'")) {
        numList[i] = 3;
      } else if (list[i].equalsIgnoreCase("Restaurants")) {
        numList[i] = 4;
      }
    }

    // create query base on types selected by user
    String query = "\"Type\" = ";
    for (int j = 0; j < numList.length; j++) {
      if (j > 0) {
        query += " OR \"Type\" = ";
      }

      query += numList[j];
    }

    messages.addMessage("Query is: " + query);

    // create an empty fc to hold user's selections
    messages.addMessage("Creating Relevant Features FC...");
    relevantFeaturesFC = createShapefile(relevantFCName, workspace, esriGeometryType.esriGeometryPoint,
      new FeatureClass(gpUtilities.openFeatureClassFromString(templateFC)));
    messages.addMessage("Done.");

    // copy only relevant features into it from PointsOfInterest layer
    FeatureClass lineFC = new FeatureClass(fc);
    copyFeatures(lineFC, relevantFeaturesFC, query);

    // create a recordset from the FC that holds user's selections
    RecordSet rs = new RecordSet();
    rs.setSourceTable(relevantFeaturesFC, null);

    // convert rs into gpfrs
    GPFeatureRecordSetLayer gpfrs = new GPFeatureRecordSetLayer();
    gpfrs.setRecordSetByRef(rs);

    return gpfrs;
  }

  /**
   * Copies features from source FC to dest FC using query
   * 
   * @param sourceFC
   * @param destFC
   * @param query
   * @throws Exception
   */
  private void copyFeatures(FeatureClass sourceFC, FeatureClass destFC, String query) throws Exception {
    FeatureCursor insertCursor = new FeatureCursor(destFC.insert(true));
    IFeatureBuffer featureBuffer = destFC.createFeatureBuffer();

    // Loop through all the features in FeatureClassIn
    QueryFilter queryFilter = new QueryFilter();
    queryFilter.setWhereClause(query);

    IFeatureCursor featureCursor = sourceFC.search(queryFilter, true);
    IFeature feature = featureCursor.nextFeature();

    while (feature != null) {
      // Add the original feature's geometry to the feature buffer.
      featureBuffer.setShapeByRef(feature.getShape());

      // Add all the original feature's fields to the feature buffer.
      try {
        // Copy the attributes of the orig feature the new feature
        IFields fieldsNew = featureBuffer.getFields();
        IFields fields = feature.getFields();
        for (int i = 0; i < fields.getFieldCount(); i++) {
          IField field = fields.getField(i);
          // check to make sure we are not trying to work with the geometry or
          // OID field
          if ((field.getType() != esriFieldType.esriFieldTypeGeometry)
            && (field.getType() != esriFieldType.esriFieldTypeOID)) {
            int intFieldIndex = fieldsNew.findField(field.getName());
            if (intFieldIndex != -1) {
              // set the value of the new field equal to the old field value
              featureBuffer.setValue(intFieldIndex, feature.getValue(i));
            }
          }
        }
      } catch (Exception e) {
        messages.addAbort("Threw an exception while adding fields " + e.getMessage());
        e.printStackTrace();
      }

      // Insert the feature into the cursor.
      insertCursor.insertFeature(featureBuffer);

      // Get Next Feature
      feature = featureCursor.nextFeature();
    }

    // Flush the cursor
    insertCursor.flush();
  }

  /**
   * Buffers specified input FC
   * 
   * @param input
   * @param output
   * @param distance
   * @throws Exception
   */
  private void buffer(IFeatureClass input, String output, String distance) throws Exception {
    VarArray bufferParamArray = new VarArray();
    bufferParamArray.add(input);
    bufferParamArray.add(output);
    bufferParamArray.add(distance);
    bufferParamArray.add("FULL");
    bufferParamArray.add("ROUND");
    bufferParamArray.add("NONE");
    bufferParamArray.add("#");

    messages.addMessage("\nBuffering Input Feature...");
    gp.execute("Buffer_analysis", bufferParamArray, null);
    messages.addMessage("Done.");
  }

  /**
   * Intersects specified FCs
   * 
   * @param input
   * @param output
   * @throws Exception
   */
  private void intersect(String input, String output) throws Exception {
    VarArray intersectParamArray = new VarArray();
    intersectParamArray.add(input);
    intersectParamArray.add(output);
    intersectParamArray.add("ALL");
    intersectParamArray.add("#");
    intersectParamArray.add("POINT");

    messages.addMessage("\nIntersecting Buffer and Relevant Points of Interest...");
    gp.execute("Intersect_analysis", intersectParamArray, null);
    messages.addMessage("Done.");
  }

  /**
   * Creates a Point FC based on specified name, path, geometryType and fields
   * from templateFC.
   * 
   * @param name
   * @param path
   * @param geometryType
   * @param templateFC
   * @return
   * @throws Exception
   */
  private FeatureClass createShapefile(String name, String path, int geometryType, FeatureClass templateFC)
    throws Exception {
    // create a FC
    IWorkspaceFactory factory = new ShapefileWorkspaceFactory();
    Workspace ws = (Workspace) factory.openFromFile(path, 0);

    // create and return feature class
    return new FeatureClass(ws.createFeatureClass(name, templateFC.getFields(), null, null,
      esriFeatureType.esriFTSimple, "shape", "default"));
  }
}