Wednesday, December 5, 2012

ARCGIS JavaScript API – ADVANCED EDITING TOOLS PART VI – Part 1

1.     Introduction

In this document I will treat the creation of more advanced editing tools. In the ArcGis JavaScript API it is difficult to perform geometric operations on geometry in code.  However to perform advanced geometry operations we can use the geometry services available at an ArcGis server. Using this technique you are able to perform actions as split and merge of geometries.  By combining geometry service methods you can even more complex operations. Some tools will using the basic edit commands as building blocks for creation of advanced tools is.  In another example  I will show how you can use the JavaScript API to build tools in native JavaScript code without geometry services.
Another solution for extending editing by using Service Object Extensions (SOE). They can play the same role as does the geometry services of the ArcGis Server.

2.     Primary geometry tools

Two important tools in web editing are the split and merge tool. Like other commands explained in previous documents, the tools are triggered through the use of the MVVM (Model View ViewModel+)  library and buttons on the web page.
2.1 Split tool

The split tool for a polygon was implemented by doing the following steps:
·         Select a feature (polygon)
·         Draw a polygon that is used for the split action
·         Perform the method cut of the geometry service
·         Update the feature with one part of the split
·         Add the remaining parts as new features to the layer, attribute information is copied from the 
        selected feature.

 


The first two actions are already part of the framework. I only has to implement the use of the geometry service ‘cut’ with a callback method. The update and add of features are implemented in the callback method.

The split command in the ViewModel  looks contains the following code:
splitCommand: function () {
   try {
   // Verify a polygon is selected on the layer specified
     var featureLayer = null;    
     if (commonData.ViewModel.selectedLayer() != null)     
          featureLayer = gisOperation.getMap().getLayer(
     commonData.ViewModel.selectdLayer().layerName);
    if (featureLayer == null 

       || featureLayer.geometryType != "esriGeometryPolygon"
       || featureLayer.getSelectedFeatures().length < 1) {
     // Invalid choice                                                    
       commonData.ViewModel.setEditActive(false);
       return;
     }
     // Draw line
     gisCommon.drawGeometry(esri.toolbars.Draw.POLYLINE, splitLineComplete);
   } catch (err) {      
     logging.logMessage("E", "error in splitCommand ->" + err.name + " - "
          + err.message, "editToolbarAdvancedVM");
   }
 }


After the line has been drawn, in the callback the cut operation is executed through a call to the gisEditing class. A callback method is added  in case some past processing is needed at the business level. All tools contains also an error processing method, very important there are errors during the geometry service method calls.

function splitLineComplete(polyline) {
// Do now second part , doing the split
   if (polyline) {
     var featureLayer =
     gisOperation.getMap().getLayer(commonData.ViewModel.selectedLayer().layerName);
     var selectGraphics = featureLayer.getSelectedFeatures();
     gisEditing.splitOperation(featureLayer, selectGraphics, polyline,
     splitComplete, geometryServiceFailed);
   } else {
     return;
   }
}
The real job is done in the editing framework I implemented through the use of the cut method of the rest service.

splitOperation: function (featureLayer, selectGraphics, polyline,
   geometryServiceComplete, geometryServiceFailed) {
   try {
     if (geometryServiceFailed == null)
       geometryServiceFailed = defaultErrorHdl;
       var selectGeometries = new Array();
       for (var i = 0; i < selectGraphics.length; i++) {
         selectGeometries.push(selectGraphics[i].geometry);
       }
       currentCallBack = geometryServiceComplete;
       currentFeatureLayer = featureLayer;
       gisOperation.getGeometryService().cut(selectGeometries, polyline,
       splitCompleted, geometryServiceFailed);
   } catch (err) {
       logging.logMessage("E", "split failed ->" + err.message,
       "gisEditing/splitOperation");
   }
}
In the edit framework I do also care about the replication of the attribute information and update and add the features. Therefor the callback of the cut is treated within the edit class, and after the replication and feature update  is done, the callback is executed of the calling command.

function splitCompleted(results) {
   try {
     if (results.geometries != null && results.geometries.length > 1) {
     // Update first geometry
       var updateGraphic =
       currentFeatureLayer.getSelectedFeatures()[0].setGeometry(results.geometries[0]);
       // Create for the next
       currentSymbol = currentFeatureLayer.getSelectedFeatures()[0].symbol;
       var newGraphics = new Array();
       for (var i = 1; i < results.geometries.length; i++) {
         newGraphics.push(new esri.Graphic(results.geometries[i],

         currentSymbol,currentAttributes));
       }
       currentFeatureLayer.applyEdits(newGraphics, [updateGraphic], null, 

         function (addResults, updateResults, deleteResults) {
           var updateCount = updateResults.length + addResults.length;
           if (currentCallBack != null)
             currentCallBack(results.geometries);
         }, function (err) {
           logging.logMessage("E", "applyEdits failed for first feature -->"
            + err.message, "gisEditing/splitCompleted");
           if (currentCallBack != null)
             currentCallBack(null);
       });
     }
   } catch (err) {
      logging.logMessage("E", "splitcomplete failed failed -->"
        + err.message, "gisEditing/splitCompleted");
   }
}
 

The procedure outlined in this split tool is used for all the advanced tools I implemented. In general what I do is
1.       Do the necessary selection of features.
2.       Activate the tool , in general done through a button click.
3.       If needed, start  a drawing action through the general draw tool.
4.       Use a callback to step to a next action.
5.       Repeat step 4 if different actions are required.

This is in general the steps  you will do if you would add a tool to ArcMap as an extension. In development of extension you will use events instead of callback methods.
It is also very important to encapsulate all code into a try catch structure. During writing tools often the asynchronous working of geometry operations can results in unexpected behaviors.

2.2 Merge tool

The merge tool can be considered as a command in terms of ArcMap. This tool is much simpler than the split tool. For the merge I only require the selection of a number of features of a feature layer. As is the case with the split tool, the geometry service of an ArcGis server will do the hard work of doing a union of the geometries.
The only asynchronous process required after the merge is the post processing of the features on the feature layer, consisting of an update of a feature (geometry) and a delete of a feature.
The startup of the merge tool is similar as that with the split tool:

unionGeometries: function (featureLayer, selectGraphics, geometryServiceComplete,      geometryServiceFailed) {
   try {
     if (geometryServiceFailed == null)
       geometryServiceFailed = defaultErrorHdl;
     currentCallBack = geometryServiceComplete;
     currentFeatureLayer = featureLayer;
     var selectGeometries = new Array();
     for (var i = 0; i < selectGraphics.length; i++) {
       selectGeometries.push(selectGraphics[i].geometry);
     }
     gisOperation.getGeometryService().union(selectGeometries,
     unionGeometriesCompleted, geometryServiceFailed);
   } catch (err) {
      logging.logMessage("E", "merge polygons failed -->"
       + err.message, "gisEditing/unionGeometries");
   }
}
As you can see, a lot of code is similar to the code used for the split tool. The most important line is the call to the geometry service ‘union’. The callback function will gives us the merged geometry. The only thing that remains is the update and delete of features so that only one feature remains as the result of the merge.

3.     Native JavaScript tools
It is not always an easy job to create geometry tools entirely in JavaScript. However the ArcGis JavaScript API offers some possibilities through the composition of the polygon and polyline classes. One example of a tool from ArcMap that can be reproduced in JavaScript is the explode tool.  The principle is very simple :

·         For polygons, isolate each ring into a separate geometry (polygon)
·         For polylines, Isolate each path into a separate geometry (polyline)

You only need to delete the original feature and replace it by a number of new features corresponding to the number of rings or paths. No round trip is required to the ArcGis server for creating the different geometries, only an asynchronous operation is required to update the feature class with the new features.

explode: function (featureLayer, feature, callback) {
   try {
     var updateFeatures = new Array();
     var addFeatures = new Array();
     var deleteFeatures = new Array();
     var actionRequired = false;
     var newGeometry;
     var newGraphic;
     currentAttributes = clone(feature.attributes);
     currentAttributes[featureLayer.objectIdField] = null;
     currentSymbol = feature.symbol;
     currentFeatureLayer.clearSelection();
     switch (feature.geometry.type) {
     case "polygon":
     // Create number of features of rings in the geometry
       if (feature.geometry.rings.length > 1) {
         actionRequired = true;
         var ringCount = feature.geometry.rings.length;
         for (var i = 0; i < ringCount; i++) {
         newGeometry =
           new esri.geometry.Polygon(feature.geometry.spatialReference);
         newGeometry.addRing(feature.geometry.rings[i]);
         newGraphic =
           new esri.Graphic(newGeometry, currentSymbol, currentAttributes);
         addFeatures.push(newGraphic);
         }
         deleteFeatures.push(feature);
      }
      break;
    case "polyline":
    // Create number of features of paths in the geometry
      if (feature.geometry.paths.length > 1) {
        for (var i = 0; i < feature.geometry.paths.length; i++) {
          newGeometry =
            new esri.geometry.Polyline(feature.geometry.spatialReference);
          newGeometry.addPath(feature.geometry.paths[i]);
          newGraphic =
            new esri.Graphic(newGeometry, currentSymbol, currentAttributes);
          addFeatures.push(newGraphic);
        }
        deleteFeatures.push(feature);
        actionRequired = true;
      }
      break;
    default:
      break;
    }
    if (actionRequired) {
      featureLayer.applyEdits(addFeatures, updateFeatures, deleteFeatures,
      function (addResults, updateResults, deleteResults) {
        var updateCount = updateResults.length + addResults.length;
        callbackExplode(updateCount, callback);
        }, function (err) {
          logging.logMessage("E", "explode failed -->" +
            err.message, "gisEditing/explode");
       });
    }
    else {
      callbackExplode(0, callback);
    }
  } catch (err) {
      logging.logMessage("E", "explode failed -->" +
       err.message, "gisEditing/explode");
  }
}



As you can see not very complex to realize. This tool will work in polygons and polylines that consist of simple multipart geometries. If the geometry consist of a complex structure, this tool could fail, in the current implementation failures are logged.
Another example of a tool that can be realized in JavaScript is the lengthening or shortening of a polyline over a given distance ,at the start or at the end of the polyline. This is a tool that I previous developed as an ArcMap tool. Here you can use methods exposed in the esri.geometry namespace.
4.     Complex geometry tools

A first tool is the creation of a polygon based on points added on the map. This tools involves two buttons, one to start creation of points on the map. A second button will create the polygon using the method convexHul l of the geometry service of an ArcGis server.  Even as the tool looks complex, the implementation is very simple, thanks to the geometry service of an ArcGis server.

 
createConvexHull: function (featureLayer, pointList, geometryServiceComplete,
   geometryServiceFailed) {
   try {
     gisOperation.disableDrawMode();
     if (geometryServiceFailed == null)
       geometryServiceFailed = defaultErrorHdl;
     currentCallBack = geometryServiceComplete;
     currentFeatureLayer = featureLayer;
     var points = new Array();
     for (var i = 0; i < pointList.length; i++) {
       points.push(pointList[i].geometry);
     }
     gisOperation.getGeometryService().convexHull(points, convexHullCompleted,

        geometryServiceFailed);
   } catch (err) {
      logging.logMessage("E", "ConvexHull failed -->"
        + err.message, "gisEditing/createConvexHull");
   }
}


In the callback we can now add the resulting geometry as new feature to the feature class selected using default values for the attributes.
5.     Conclusion

In this first part I illustrated how easy you can add custom tools to an editing ArcGis application. Doing some advanced editing requires the use of a geometry service at an ArcGis Server. When you required some functionality not found in the geometry service, you can move towards the creation of Server Object Extensions(SOE)  and use them in the same way as using geometry services. Knowledge of ArcObjects is a must when starting with writing Server Object Extensions.  Use always rest services when using SOE’s, you don’t want  XML from a SOAP web service, but Json. Adding SOE’s is best done in a separate Dojo class, so it can only be loaded when needed through AMD.