Export a feature layer to shapefile with clipping

141
4
Jump to solution
07-08-2022 01:31 AM
sbayarri_gilytics
New Contributor II

Hi everyone.

We need to export a feature layer in the map to a shapefile, but applying a clipping operation with a code-built polygon. The original layer must not be modified.

We've tried running the "analysis.Clip" tool. Unlike the old "arcpy.Clip_analysis", the ArcGIS Pro SDK version seems to require providing the clipping polygon (second GP value) as a FeatureLayer.

We think we need to either:

  • Make an in-memory FeatureLayer containing the Polygon.
  • Save my polygon to a temporary file whose path can be used as input for the analysis.Clip process.

We could find a way to do either one in C#. Maybe you can provide a sample or have other ideas. We would prefer not to create any intermediate visible layers in the Map.

Thanks!

0 Kudos
1 Solution

Accepted Solutions
CharlesMacleod
Esri Regular Contributor

try this:

 

internal class Button1 : Button
  {
    protected async void OnClickClip()
    {
      var tool_name = "analysis.Clip";
      var extent = MapView.Active.Extent;
      var sel_layer = MapView.Active.Map.GetLayersAsFlattenedList()
                        .OfType<FeatureLayer>().FirstOrDefault(l => l.Name == "GreatLakes");
      if (sel_layer == null) return;

      var gdb = Project.Current.DefaultGeodatabasePath;
      var out_fc = System.IO.Path.Combine(gdb, "clipped_lakes_out");

      var val_array = await QueuedTask.Run(() =>
      {

        var rect = GeometryEngine.Instance.Scale(extent, extent.Center, 0.5, 0.5) as Envelope;
        var clip_poly = PolygonBuilderEx.CreatePolygon(rect, rect.SpatialReference);
        var geom = new List<object>() { clip_poly };
        return Geoprocessing.MakeValueArray(new object[] { sel_layer, geom, out_fc });

      });

      Geoprocessing.ExecuteToolAsync(tool_name, val_array,
        null, null, null, GPExecuteToolFlags.InheritGPOptions);
    }

    protected override void OnClick()
    {
      OnClickClip();
    }
  }

 

View solution in original post

4 Replies
RichardHowe
Occasional Contributor

A feature layer is a temporary construct within an ArcMap/Pro session, and that is absolutely the way I would go. Really easy to call in arcpy via the "Make Feature Layer" tool. There has always been a little bit of inconsistency in both ArcMap and ArcGIS Pro about which tools require Feature Layers as inputs, and while it's a little bit of a faff, it's realistically only a single extra line of code

0 Kudos
sbayarri_gilytics
New Contributor II

Thanks, Richard.

In the end, my solution is quite complex, but I don't see a shorter way. Basically:

  • It creates an empty feature class (shapefile) and opens it as a layer (clipLayer), so the clip polygon can be added to it.
  • It calls the Clip GP tool with this clip layer to do the clipping and save the resulting shapefile.
public static async Task<Layer> CreateLayerFromPolygon(string tempDirectory, Polygon clipArea)
        {
            GPExecuteToolFlags flags = GPExecuteToolFlags.GPThread;  // instruct the tool run non-blocking GPThread

            // Create empty feature class
            var valueArrayCreateFCArea = Geoprocessing.MakeValueArray(tempDirectory, "clip_area.shp", "POLYGON");
            IGPResult gpResult = await Geoprocessing.ExecuteToolAsync("management.CreateFeatureclass", valueArrayCreateFCArea, null, null, null, flags);
            if (!Utils.VerifyGPToolResult(gpResult))
                return null;

            // Load feature class as layer
            Uri clipAreaFile = new Uri(Path.Combine(tempDirectory, "clip_area.shp"));
            Layer clipLayer = null;
            await QueuedTask.Run(() =>
                clipLayer = LayerFactory.Instance.CreateLayer(clipAreaFile, Utils.getActiveMap())
            );
            if (clipLayer == null)
                return null;

            // Add clip area to clip layer
            var editOp = new EditOperation();
            editOp.Create(clipLayer, clipArea);
            if (!editOp.Execute())
                return null;

            return clipLayer;
        }

Layer clipLayer = await Utils.CreateLayerFromPolygon(tempDirectory, clipArea);
GPExecuteToolFlags flags = GPExecuteToolFlags.GPThread;

var valueArray = await QueuedTask.Run(() =>
{
    List<string> inlayers = new List<string>();
    inlayers.Add(layerNameToBeClipped);
    string outpath = Path.Combine(outShpDirectory, layerNameToBeClipped + ".shp");
    return Geoprocessing.MakeValueArray(inlayers, clipLayer, outpath);
});

IGPResult gpResult = await Geoprocessing.ExecuteToolAsync("analysis.Clip", valueArray, null, null, null, flags); 

 

0 Kudos
CharlesMacleod
Esri Regular Contributor

try this:

 

internal class Button1 : Button
  {
    protected async void OnClickClip()
    {
      var tool_name = "analysis.Clip";
      var extent = MapView.Active.Extent;
      var sel_layer = MapView.Active.Map.GetLayersAsFlattenedList()
                        .OfType<FeatureLayer>().FirstOrDefault(l => l.Name == "GreatLakes");
      if (sel_layer == null) return;

      var gdb = Project.Current.DefaultGeodatabasePath;
      var out_fc = System.IO.Path.Combine(gdb, "clipped_lakes_out");

      var val_array = await QueuedTask.Run(() =>
      {

        var rect = GeometryEngine.Instance.Scale(extent, extent.Center, 0.5, 0.5) as Envelope;
        var clip_poly = PolygonBuilderEx.CreatePolygon(rect, rect.SpatialReference);
        var geom = new List<object>() { clip_poly };
        return Geoprocessing.MakeValueArray(new object[] { sel_layer, geom, out_fc });

      });

      Geoprocessing.ExecuteToolAsync(tool_name, val_array,
        null, null, null, GPExecuteToolFlags.InheritGPOptions);
    }

    protected override void OnClick()
    {
      OnClickClip();
    }
  }

 

sbayarri_gilytics
New Contributor II

Thanks, Charles!! That's a much better solution  🙂  

I just needed to pass my clip area to the GP as a List, like you do. I guess the docs could be more clear about accepting clip features as a list, but not a single feature. Anyway, thanks a lot!