Three Approaches to Projecting Feature Classes in ArcObjects

Assigning a Spatial Reference Using IGeoDatasetSchemaEdit

/// <summary>
/// Assigns or overwrites a spatial reference for a dataset that has an unknown coordinate system.
/// </summary>
/// <param name="dataset">The input geodataset.</param>
/// <param name="targetSpatialRef">The spatial reference to apply.</param>
/// <param name="success">Indicates whether the operation succeeded.</param>
public void ApplySpatialReference(IGeoDataset dataset, ISpatialReference targetSpatialRef, out bool success)
{
    success = false;
    IGeoDatasetSchemaEdit schemaEditor = dataset as IGeoDatasetSchemaEdit;
    if (schemaEditor != null && schemaEditor.CanAlterSpatialReference)
    {
        schemaEditor.AlterSpatialReference(targetSpatialRef);
        success = true;
    }
}

This method mirrors the Define Projection tool found in ArcMap's toolbox. It solely attaches or replaces the coordinate system metadata—it does not tarnsform coordinates.

Define Projection animation

Define Projection result

Reference from ArcObjects documentation:

ArcObjects help for Define Projection

Converting Feature Classes with IFeatureDataConverter

/// <summary>
/// Converts a feature class from its existing projection into a new one.
/// </summary>
/// <param name="sourceClass">The input feature class.</param>
/// <param name="filter">Optional query filter to select a subset of features.</param>
/// <param name="targetSpatialRef">The target spatial reference.</param>
/// <param name="outputDirectory">The output workspace path.</param>
/// <param name="outputName">The name of the resulting feature class.</param>
/// <param name="outputDatasetName">When writing to a geodatabase, the target feature dataset name (optional).</param>
public void ReprojectWithDataConverter(IFeatureClass sourceClass, IQueryFilter filter,
    ISpatialReference targetSpatialRef, string outputDirectory, string outputName,
    string outputDatasetName = null)
{
    var sourceGeoDataset = (IGeoDataset)sourceClass;
    var originalSpatialRef = sourceGeoDataset.SpatialReference;
    IClone cloneChecker = originalSpatialRef as IClone;
    if (cloneChecker.IsEqual((IClone)targetSpatialRef))
        return;

    Type workspaceFactoryType = null;
    string extension = System.IO.Path.GetExtension(outputDirectory);
    if (extension == ".gdb")
    {
        workspaceFactoryType = Type.GetTypeFromProgID("esriDataSourcesGDB.FileGDBWorkspaceFactory");
    }
    else if (extension == "")
    {
        workspaceFactoryType = Type.GetTypeFromProgID("esriDataSourcesFile.ShapefileWorkspaceFactory");
        outputDatasetName = null;
    }
    else
        return;

    var workspaceFactory = (IWorkspaceFactory)Activator.CreateInstance(workspaceFactoryType);
    var outputWorkspace = workspaceFactory.OpenFromFile(outputDirectory, 0);

    // Delete any existing feature class with the same name
    IWorkspace2 workspace2 = (IWorkspace2)outputWorkspace;
    IFeatureWorkspace featureWorkspace = (IFeatureWorkspace)workspace2;
    if (workspace2.get_NameExists(esriDatasetType.esriDTFeatureClass, outputName))
    {
        var existingDataset = (IDataset)featureWorkspace.OpenFeatureClass(outputName);
        existingDataset.Delete();
    }

    IFeatureDataset targetDataset = null;
    IFeatureDatasetName targetDatasetNameObj = null;
    if (!string.IsNullOrWhiteSpace(outputDatasetName))
    {
        if (workspace2.get_NameExists(esriDatasetType.esriDTFeatureDataset, outputDatasetName))
            targetDataset = featureWorkspace.OpenFeatureDataset(outputDatasetName);
        else
            targetDataset = featureWorkspace.CreateFeatureDataset(outputDatasetName, originalSpatialRef);

        var tempGeoDataset = (IGeoDataset)targetDataset;
        var tempClone = tempGeoDataset.SpatialReference as IClone;
        if (!tempClone.IsEqual((IClone)targetSpatialRef))
            return;
        targetDatasetNameObj = (IFeatureDatasetName)targetDataset.FullName;
    }

    IDataset outputDataset = (IDataset)outputWorkspace;
    IWorkspaceName outputWorkspaceName = (IWorkspaceName)outputDataset.FullName;

    IFeatureClassName outputClassName = new FeatureClassNameClass();
    IDatasetName outDatasetName = outputClassName as IDatasetName;
    outDatasetName.WorkspaceName = outputWorkspaceName;
    outDatasetName.Name = outputName;

    IDataset inputDataset = sourceClass as IDataset;
    IFeatureClassName inputClassName = inputDataset.FullName as IFeatureClassName;
    IWorkspace inputWorkspace = inputDataset.Workspace;

    IFieldChecker fieldValidator = new FieldCheckerClass();
    fieldValidator.InputWorkspace = inputWorkspace;
    fieldValidator.ValidateWorkspace = outputWorkspace;
    IFields inputFields = sourceClass.Fields;
    IEnumFieldError fieldErrors;
    IFields validatedFields;
    fieldValidator.Validate(inputFields, out fieldErrors, out validatedFields);

    IGeometryDef geometryDef = new GeometryDefClass();
    IGeometryDefEdit geomDefEditor = (IGeometryDefEdit)geometryDef;
    geomDefEditor.GeometryType_2 = sourceClass.ShapeType;
    geomDefEditor.SpatialReference_2 = targetSpatialRef;

    IFeatureDataConverter converter = new FeatureDataConverterClass();
    converter.ConvertFeatureClass(
        inputClassName,
        filter,
        targetDatasetNameObj,
        outputClassName,
        geometryDef,
        validatedFields,
        "",
        1000,
        0);
}

This method mimics the Project tool in the ArcMap toolbox, but it does not allow a custom geographic (datum) transformation to be specified.

Project tool in ArcMap
Project tool parameters

Manual Projection with Feature-by-Feature Copy and IGeometry5

/// <summary>
/// Re-projects a feature class by creating a new one and transforming each geometry individually.
/// </summary>
/// <param name="sourceClass">The original feature class.</param>
/// <param name="targetSpatialRef">The target spatial reference.</param>
/// <param name="outputDirectory">Path to the destination workspace.</param>
/// <param name="outputName">Name of the output feature class.</param>
/// <param name="outputDatasetName">Optional feature dataset name for geodatabase output.</param>
/// <param name="geoTransformationType">An esriSRGeoTransformationType, esriSRGeoTransformation2Type, or esriSRGeoTransformation3Type value.</param>
/// <returns>The newly created feature class.</returns>
public IFeatureClass ReprojectManually(
    IFeatureClass sourceClass,
    ISpatialReference targetSpatialRef,
    string outputDirectory,
    string outputName,
    string outputDatasetName = null,
    object geoTransformationType = null)
{
    var sourceGeoDataset = (IGeoDataset)sourceClass;
    var originalSpatialRef = sourceGeoDataset.SpatialReference;
    IClone cloneChecker = originalSpatialRef as IClone;
    if (cloneChecker.IsEqual((IClone)targetSpatialRef))
        return null;

    int geoTransCode = int.MinValue;
    if (geoTransformationType is esriSRGeoTransformationType)
        geoTransCode = (int)geoTransformationType;
    else if (geoTransformationType is esriSRGeoTransformation2Type)
        geoTransCode = (int)geoTransformationType;
    else if (geoTransformationType is esriSRGeoTransformation3Type)
        geoTransCode = (int)geoTransformationType;

    IGeoTransformation geoTransformation = null;
    if (geoTransCode != int.MinValue)
    {
        ISpatialReferenceFactory2 factory =
            new SpatialReferenceEnvironmentClass();
        geoTransformation =
            (IGeoTransformation)factory.CreateGeoTransformation(geoTransCode);
    }

    Type workspaceFactoryType = null;
    string extension = System.IO.Path.GetExtension(outputDirectory);
    if (extension == ".gdb")
    {
        workspaceFactoryType = Type.GetTypeFromProgID("esriDataSourcesGDB.FileGDBWorkspaceFactory");
    }
    else if (extension == "")
    {
        workspaceFactoryType = Type.GetTypeFromProgID("esriDataSourcesFile.ShapefileWorkspaceFactory");
        outputDatasetName = null;
    }
    else
        return null;

    var workspaceFactory = (IWorkspaceFactory)Activator.CreateInstance(workspaceFactoryType);
    var targetWorkspace = workspaceFactory.OpenFromFile(outputDirectory, 0);
    var featureWorkspace = (IFeatureWorkspace)targetWorkspace;

    IWorkspace2 workspace2 = (IWorkspace2)targetWorkspace;
    if (workspace2.get_NameExists(esriDatasetType.esriDTFeatureClass, outputName))
    {
        var existingClass = featureWorkspace.OpenFeatureClass(outputName);
        ((IDataset)existingClass).Delete();
    }

    int shapeIndex = sourceClass.FindField(sourceClass.ShapeFieldName);
    IFields outputFields = new FieldsClass();
    IFieldsEdit outputFieldsEditor = (IFieldsEdit)outputFields;

    for (int i = 0; i < sourceClass.Fields.FieldCount; i++)
    {
        if (i == shapeIndex)
        {
            IGeometryDef geomDef = new GeometryDefClass();
            IGeometryDefEdit geomDefEditor = (IGeometryDefEdit)geomDef;
            geomDefEditor.GeometryType_2 = sourceClass.ShapeType;
            geomDefEditor.SpatialReference_2 = targetSpatialRef;

            IField shapeField = new FieldClass();
            IFieldEdit shapeFieldEditor = (IFieldEdit)shapeField;
            shapeFieldEditor.Name_2 = "SHAPE";
            shapeFieldEditor.AliasName_2 = "SHAPE";
            shapeFieldEditor.Type_2 = esriFieldType.esriFieldTypeGeometry;
            shapeFieldEditor.GeometryDef_2 = geomDef;
            outputFieldsEditor.AddField(shapeField);
        }
        else
        {
            outputFieldsEditor.AddField(sourceClass.Fields.Field[i]);
        }
    }

    IFeatureClass newFeatureClass = null;
    if (string.IsNullOrWhiteSpace(outputDatasetName))
    {
        newFeatureClass = featureWorkspace.CreateFeatureClass(
            outputName, outputFields, sourceClass.CLSID, sourceClass.EXTCLSID,
            esriFeatureType.esriFTSimple, "Shape", "");
    }
    else
    {
        IFeatureDataset featDataset = null;
        if (workspace2.get_NameExists(esriDatasetType.esriDTFeatureDataset, outputDatasetName))
            featDataset = featureWorkspace.OpenFeatureDataset(outputDatasetName);
        else
            featDataset = featureWorkspace.CreateFeatureDataset(outputDatasetName, originalSpatialRef);

        newFeatureClass = featDataset.CreateFeatureClass(
            outputName, outputFields, sourceClass.CLSID, sourceClass.EXTCLSID,
            esriFeatureType.esriFTSimple, "Shape", "");
        System.Runtime.InteropServices.Marshal.ReleaseComObject(featDataset);
    }

    var fieldMapping = new Dictionary<int, int>();
    for (int i = 0; i < sourceClass.Fields.FieldCount; i++)
    {
        if (!sourceClass.Fields.Field[i].Editable)
            continue;
        string fieldName = sourceClass.Fields.Field[i].Name.ToUpper();
        for (int j = 0; j < newFeatureClass.Fields.FieldCount; j++)
        {
            string destFieldName = newFeatureClass.Fields.Field[j].Name.ToUpper();
            if (fieldName == destFieldName)
                fieldMapping.Add(i, j);
        }
    }

    var searchCursor = sourceClass.Search(null, false);
    var insertCursor = newFeatureClass.Insert(true);
    IFeatureBuffer insertBuffer = newFeatureClass.CreateFeatureBuffer();
    IFeature currentFeature = searchCursor.NextFeature();
    int counter = 0;

    while (currentFeature != null)
    {
        foreach (var mapping in fieldMapping)
        {
            if (mapping.Key == shapeIndex)
            {
                IGeometry5 geom = currentFeature.ShapeCopy as IGeometry5;
                if (geoTransformation == null)
                    geom.Project(targetSpatialRef);
                else
                    geom.ProjectEx(targetSpatialRef,
                        esriTransformDirection.esriTransformForward,
                        geoTransformation, false, 0, 0);
                insertBuffer.Shape = geom;
            }
            else
            {
                insertBuffer.set_Value(mapping.Value,
                    currentFeature.get_Value(mapping.Key));
            }
        }

        insertCursor.InsertFeature(insertBuffer);
        currentFeature = searchCursor.NextFeature();

        if (counter++ % 1000 == 0)
            insertCursor.Flush();
    }

    insertCursor.Flush();

    System.Runtime.InteropServices.Marshal.ReleaseComObject(originalSpatialRef);
    System.Runtime.InteropServices.Marshal.ReleaseComObject(targetWorkspace);
    System.Runtime.InteropServices.Marshal.ReleaseComObject(searchCursor);
    System.Runtime.InteropServices.Marshal.ReleaseComObject(insertCursor);
    System.Runtime.InteropServices.Marshal.ReleaseComObject(insertBuffer);

    return newFeatureClass;
}

This final approach replicates the full Project tool behaviour from the ArcMap toolbox, including an optional geographic (datum) transformation paarmeter.

Custom Project tool with transformation
Transformation parameter

Tags: ArcObjects gis Projection FeatureClass C#

Posted on Sun, 13 Sep 2026 16:26:47 +0000 by thunderbox