Merging Identical Elements in GDB Libraries Under a Directory

Background

A fan raised a request while conducting a geographic information census: they wanted to batch add vector data from multiple databases under several folders and merge them into one based on the same name. As shown in the geographic database below.

Merging Identical Elements in GDB Libraries Under a Directory

Below is the content of the database:

Merging Identical Elements in GDB Libraries Under a Directory

Merge into one file. The fan mentioned they wanted to learn this part, but they had no programming experience. Although I recommended learning programming, this small task shouldn’t affect their work progress, so I decided to help them out and write one.

This request is relatively simple, and there are multiple ways to implement it. Recently, I have been working on ArcGIS AddIn development, so I wrote a script to merge into a union.gdb database.

Prerequisites

Environment: ArcMap 10.8.2, Visual Studio (recommended 2019 or compatible version), ArcGIS Desktop SDK 10.8.

ArcObjects SDK: Ensure that the ArcGIS Desktop SDK is installed, which includes the ArcObjects .NET library.

Permissions: Users must have read permissions for the directory and GDB, and write permissions for the merge operation.

Development Tools: Configure the ArcGIS Add-in template in Visual Studio.

Implementation Steps

Create an Add-in Project:

  • • Create a project using the ArcGIS Add-in template in Visual Studio.
  • • Add a button as the tool entry point.

Write Plugin Logic:

  • • Use ArcObjects to traverse the directory and find .gdb files.
  • • Extract vector layers from the GDB and add them to the map.
  • • Detect layers with the same name and use GeoProcessor to perform the merge.

Packaging and Deployment:

  • • Compile the project to generate the .esriAddIn file.
  • • Install and test in ArcMap.

AddIn Code

using System;
using System.Collections.Generic;
using System.Text;
using System.IO;
using System.Windows.Forms;
using ESRI.ArcGIS.ArcMapUI;
using ESRI.ArcGIS.Carto;
using ESRI.ArcGIS.DataSourcesGDB;
using ESRI.ArcGIS.Geodatabase;
using ESRI.ArcGIS.GeoDatabaseUI;
using ESRI.ArcGIS.Geoprocessing;
using ESRI.ArcGIS.esriSystem;


namespace AddInHelloWorld
{
   public class MergeGDBLayersAddin : ESRI.ArcGIS.Desktop.AddIns.Button
   {
       public MergeGDBLayersAddin()
       {
       }
       private string _folderPath; // Store the user-selected directory path

       protected override void OnClick()
       {
           try
           {
               // Get the current map document and data frame
               IMxDocument mxDoc = ArcMap.Application.Document as IMxDocument;
               IMap map = mxDoc.FocusMap;

               // Pop up folder selection dialog
               using (FolderBrowserDialog folderDialog = new FolderBrowserDialog())
               {
                   folderDialog.Description = "Select the directory to traverse";
                   if (folderDialog.ShowDialog() != DialogResult.OK)
                   {
                       MessageBox.Show("No directory selected!", "Error", MessageBoxButtons.OK, MessageBoxIcon.Error);
                       return;
                   }

                   string folderPath = folderDialog.SelectedPath;
                   _folderPath = folderDialog.SelectedPath;
                   ArcMap.Application.StatusBar.Message[0] = "Starting to traverse directory: " + folderPath;

                   // Store layer names and paths
                   Dictionary<string, List<string>> layerDict = new Dictionary<string, List<string>>();

                   // Traverse the directory
                   ProcessDirectory(folderPath, map, layerDict);

                   // Merge layers with the same name
                   MergeDuplicateLayers(map, layerDict);

                   // Refresh the map and directory tree
                   mxDoc.ActiveView.Refresh();
                   mxDoc.UpdateContents();

                   MessageBox.Show("Processing completed!", "Information", MessageBoxButtons.OK, MessageBoxIcon.Information);
               }
           }
           catch (Exception ex)
           {
               MessageBox.Show("An error occurred: " + ex.Message, "Error", MessageBoxButtons.OK, MessageBoxIcon.Error);
           }
       }

       private void ProcessDirectory(string folderPath, IMap map, Dictionary<string, List<string>> layerDict)
       {
           try
           {
               // Find all subdirectories
               foreach (string dir in Directory.GetDirectories(folderPath))
               {
                   if (dir.EndsWith(".gdb", StringComparison.OrdinalIgnoreCase))
                   {
                       ProcessGDB(dir, map, layerDict);
                   }
                   // Recursively traverse subdirectories
                   ProcessDirectory(dir, map, layerDict);
               }
           }
           catch (Exception ex)
           {
               ArcMap.Application.StatusBar.Message[0] = "Error traversing directory " + folderPath + ": " + ex.Message;
           }
       }

       private void ProcessGDB(string gdbPath, IMap map, Dictionary<string, List<string>> layerDict)
       {
           try
           {
               // Open the file geodatabase
               IWorkspaceFactory wsFactory = new FileGDBWorkspaceFactoryClass();
               IWorkspace workspace = wsFactory.OpenFromFile(gdbPath, 0);
               IFeatureWorkspace featureWorkspace = workspace as IFeatureWorkspace;
               IEnumDataset enumDataset = workspace.get_Datasets(esriDatasetType.esriDTFeatureClass);

               IDataset dataset;
               while ((dataset = enumDataset.Next()) != null)
               {
                   IFeatureClass featureClass = dataset as IFeatureClass;
                   if (featureClass != null)
                   {
                       string fcName = dataset.Name;
                       string fcPath = Path.Combine(gdbPath, fcName);

                       // Record layer path
                       if (layerDict.ContainsKey(fcName))
                       {
                           layerDict[fcName].Add(fcPath);
                       }
                       else
                       {
                           layerDict.Add(fcName, new List<string> { fcPath });
                           // Add layer to the map
                           IFeatureLayer featureLayer = new FeatureLayerClass();
                           featureLayer.FeatureClass = featureClass;
                           featureLayer.Name = fcName;
                           map.AddLayer(featureLayer);
                           ArcMap.Application.StatusBar.Message[0] = "Layer added: " + fcName;
                       }
                   }
               }
           }
           catch (Exception ex)
           {
               ArcMap.Application.StatusBar.Message[0] = "Error processing geodatabase " + gdbPath + ": " + ex.Message;
           }
       }

       private void MergeDuplicateLayers(IMap map, Dictionary<string, List<string>> layerDict)
       {
           try
           {
               // Initialize GeoProcessor
               IGeoProcessor2 gp = new GeoProcessorClass();
               gp.OverwriteOutput = true;

               foreach (var pair in layerDict)
               {
                   string fcName = pair.Key;
                   List<string> fcPaths = pair.Value;

                   if (fcPaths.Count > 1) // Layers that need to be merged
                   {
                       ArcMap.Application.StatusBar.Message[0] = "Merging layer: " + fcName;

                       // Set output path (temporary workspace)
                       string unionGdb = GetUnionGdb(_folderPath);
                       string outputPath = Path.Combine(unionGdb, fcName + "_Merged");

                       // Execute merge
                       IVariantArray parameters = new VarArrayClass();
                       parameters.Add(string.Join(";", fcPaths)); // Input layer paths
                       parameters.Add(outputPath); // Output path

                       IGeoProcessorResult result = gp.Execute("Merge_management", parameters, null);
                       if (result.Status == esriJobStatus.esriJobSucceeded)
                       {
                           // Add merged layer to the map
                           IWorkspaceFactory wsFactory = new FileGDBWorkspaceFactoryClass();
                           IFeatureWorkspace featureWorkspace = wsFactory.OpenFromFile(unionGdb, 0) as IFeatureWorkspace;
                           IFeatureClass mergedFc = featureWorkspace.OpenFeatureClass(fcName + "_Merged");
                           IFeatureLayer mergedLayer = new FeatureLayerClass();
                           mergedLayer.FeatureClass = mergedFc;
                           mergedLayer.Name = fcName + "_Merged";
                           map.AddLayer(mergedLayer);

                           // Remove original layers with the same name
                           IEnumLayer enumLayer = map.Layers; // Get layer enumerator
                           ILayer layer;
                           List<ILayer> layersToRemove = new List<ILayer>(); // Store layers to be removed
                           enumLayer.Reset();
                           while ((layer = enumLayer.Next()) != null)
                           {
                               if (layer.Name == fcName && layer is IFeatureLayer)
                               {
                                   layersToRemove.Add(layer);
                               }
                           }

                           // Remove layers
                           foreach (ILayer layerToRemove in layersToRemove)
                           {
                               map.DeleteLayer(layerToRemove);
                           }

                           ArcMap.Application.StatusBar.Message[0] = "Merged layer " + fcName + " to " + outputPath;
                       }
                       else
                       {
                           ArcMap.Application.StatusBar.Message[0] = "Failed to merge layer " + fcName;
                       }
                   }
               }
           }
           catch (Exception ex)
           {
               ArcMap.Application.StatusBar.Message[0] = "Error merging layers: " + ex.Message;
           }
       }
       private string GetUnionGdb(string folderPath)
       {
           try
           {
               string unionGdbPath = Path.Combine(folderPath, "union.gdb");

               // Check if union.gdb exists
               if (!Directory.Exists(unionGdbPath))
               {
                   // Create union.gdb
                   IWorkspaceFactory wsFactory = new FileGDBWorkspaceFactoryClass();
                   IWorkspaceName workspaceName = wsFactory.Create(folderPath, "union", null, 0);
                   unionGdbPath = Path.Combine(folderPath, "union.gdb");
                   ArcMap.Application.StatusBar.Message[0] = "Created union.gdb at: " + unionGdbPath;
               }

               return unionGdbPath;
           }
           catch (Exception ex)
           {
               throw new Exception("Failed to create or access union.gdb: " + ex.Message);
           }
       }
       private string GetScratchGdb()
       {
           // Get temporary file geodatabase
           IScratchWorkspaceFactory scratchWsFactory = new ScratchWorkspaceFactoryClass();
           IWorkspace scratchWorkspace = scratchWsFactory.DefaultScratchWorkspace;
           return scratchWorkspace.PathName;
       }
       protected override void OnUpdate()
       {
       }
   }
}

Code Explanation

Directory Traversal: Uses Directory.GetDirectories to recursively traverse the directory and find subdirectories ending with .gdb.

GDB Processing: Uses ArcObjects’ IWorkspaceFactory and IFeatureWorkspace to open the GDB and list vector feature classes.

Layer Addition: Adds feature classes to the map using IFeatureLayer and IMap.AddLayer.

Layer Merging: Merges layers with the same name using the GeoProcessor’s Merge_management tool, outputting to GDB (union.gdb).

User Interaction: Uses FolderBrowserDialog to allow users to select the directory path, and MessageBox to display processing results or errors.

Error Handling: Includes exception handling to ensure plugin stability and provide user-friendly prompts.

For those needing the source code project, please leave a message or private message. For those needing the plugin results, please leave a message or private message.

Due to the lack of project data, I wrote some simple data for testing. Please use the tool with caution and consider this a disclaimer.

Leave a Comment