8

I’m currently building a Desktop add-in tool with ArcObjects that:

  1. Asks a user to select a feature class
  2. Reprojects the feature class to Web Mercator
  3. Executes some geoprocessing

The initial coordinate system of the feature class could be one of many different geographic or projected systems. As a result I need to also have the user select a GeoTransformation if necessary. Obviously, I could present the user with the huge list of transformation provided in the enumerations of esriSRGeoTransformationType, esriSRGeoTransformation2Type, esriSRGeoTransformation3Type. But that would be a huge list. What I’d like to do is narrow that list based on the input and output Coordinate Systems – but I have not been able to figure out how to do that narrowing.

Anyone have experience doing this? I know there must be some way to do, because the Project Tool UI does exactly this narrowing operation. But I can’t find the method, despite an exhaustive internet search.

3 Answers 3

12

See c# code below. (Updated: refactored)

using System;
using System.Collections.Generic;
using System.Text;
using System.IO;
using ESRI.ArcGIS.Geometry;
using System.Windows.Forms;
using ESRI.ArcGIS.Carto;
using ESRI.ArcGIS.Geodatabase;
using ESRI.ArcGIS.Framework;

namespace HansenAddin
{
    public class PickGeoTransButton : ESRI.ArcGIS.Desktop.AddIns.Button
    {
        public PickGeoTransButton()
        {
        }

        protected override void OnClick()
        {
            try
            {
                // let user choose a transformation for projecting from featureclass's spatial ref
                // into the dataframe's spatial ref.
                var featClass = ((IFeatureLayer)ArcMap.Document.FocusMap.get_Layer(0)).FeatureClass;

                var fromSR = ((IGeoDataset)featClass).SpatialReference;
                var toSR = ArcMap.Document.FocusMap.SpatialReference;

                IGeoTransformation geoTrans;
                esriTransformDirection direction;
                ChooseGeotrans(fromSR, toSR, ArcMap.Application.hWnd, out geoTrans, out direction);
                if (geoTrans != null)
                {
                    MessageBox.Show(String.Format("{0} \n{1} \n{2} \n{3}", geoTrans.Name, fromSR.Name, toSR.Name, direction));
                }
            }
            catch (Exception ex)
            {
                MessageBox.Show(ex.Message);
            }
        }

        public static void ChooseGeotrans(ISpatialReference fromSR, ISpatialReference toSR, int hWnd,
            out IGeoTransformation geoTrans, out esriTransformDirection direction)
        {
            geoTrans = null;
            direction = esriTransformDirection.esriTransformForward;

            var list = GetTransformations(fromSR, toSR);
            if (list.Count == 0)
            {
                MessageBox.Show(String.Format("No geotransforms to go from {0} to {1}", fromSR.Name, toSR.Name));
                return;
            }
            IListDialog dlg = new ListDialogClass();
            foreach (IGeoTransformation gt in list)
                dlg.AddString(gt.Name);
            if (dlg.DoModal("Choose a Geotransformation", 0, hWnd))
            {
                geoTrans = list[dlg.Choice];
                direction = GetDir(geoTrans, fromSR, toSR);
            }
        }

        public static List<IGeoTransformation> GetTransformations(ISpatialReference fromSR, ISpatialReference toSR)
        {
            int fromFactcode = GetGCSFactoryCode(fromSR);
            int toFactcode = GetGCSFactoryCode(toSR);

            var outList = new List<IGeoTransformation>();
            // Use activator to instantiate arcobjects singletons ...
            var type = Type.GetTypeFromProgID("esriGeometry.SpatialReferenceEnvironment");
            var srf = Activator.CreateInstance(type) as ISpatialReferenceFactory2;

            var gtSet = srf.CreatePredefinedGeographicTransformations();
            gtSet.Reset();
            for (int i = 0; i < gtSet.Count; i++)
            {
                ISpatialReference fromGcsSR;
                ISpatialReference toGcsSR;
                var geoTrans = (IGeoTransformation)gtSet.Next();
                geoTrans.GetSpatialReferences(out fromGcsSR, out toGcsSR);
                if ((fromGcsSR.FactoryCode == fromFactcode && toGcsSR.FactoryCode == toFactcode) ||
                    (fromGcsSR.FactoryCode == toFactcode && toGcsSR.FactoryCode == fromFactcode))
                {
                    outList.Add(geoTrans);
                }
            }
            return outList;
        }
        private static esriTransformDirection GetDir(IGeoTransformation geoTrans, ISpatialReference sr1, ISpatialReference sr2)
        {
            int code1 = GetGCSFactoryCode(sr1);
            int code2 = GetGCSFactoryCode(sr2);
            ISpatialReference fromSR;
            ISpatialReference toSR;
            geoTrans.GetSpatialReferences(out fromSR, out toSR);
            if (fromSR.FactoryCode == code1 && toSR.FactoryCode == code2)
                return esriTransformDirection.esriTransformForward;
            else if (fromSR.FactoryCode == code2 && toSR.FactoryCode == code1)
                return esriTransformDirection.esriTransformReverse;
            else
                throw new Exception(String.Format("{0} does not support going between {1} and {2}",
                    geoTrans.Name, sr1.Name, sr2.Name));
        }

        private static int GetGCSFactoryCode(ISpatialReference sr)
        {
            if (sr is IProjectedCoordinateSystem)
                return ((IProjectedCoordinateSystem)sr).GeographicCoordinateSystem.FactoryCode;
            else if (sr is IGeographicCoordinateSystem)
                return ((IGeographicCoordinateSystem)sr).FactoryCode;
            else
                throw new Exception("unsupported spatialref type");
        }
        protected override void OnUpdate()
        {

        }

    }
}
2
  • Thanks very much...pretty much worked perfect. I do wonder about this part though: if ((fromGcsSR.FactoryCode == fromFactcode && toGcsSR.FactoryCode == toFactcode) || (fromGcsSR.FactoryCode == fromFactcode && toGcsSR.FactoryCode == toFactcode)) Both sides of the OR operator are identical.
    – rgwozdz
    Commented Aug 31, 2011 at 20:30
  • @rgwozdz Hmm, I've corrected that line of code. I'm puzzled why it worked perfectly given that that line is an error. Commented Sep 19, 2011 at 1:34
2

For the GetDir method, the second and third parameters should be the factory codes of the base geographic coordinate systems of the source and target coordinate systems. For projected coordinate systems, the ISpatialReference interface will provide the factory code of the coordinate system but not the base geographic coordinate system. For example, NAD27 Zone 12 equates to a factory code 26712 and the base geographic coordinate system is 4267 (NAD27 geographic). The 'out' parameters of geoTrans.GetSpatialReferences(out fromSR, out toSR); will only be factory codes for geographic coordinate systems.

You can get the base geographic coordinate system of a projected coordinate system by creating a projected coordinate system object and then getting its base geogr. coord sys factory code.

THe code below should help. The integers for the original and target coordinate systems would have to be provided to properly create the object. And the code is expecting the type(projected vs. geographic) of source/target coordinate systems has been provided.

//Create Spatial Reference Factory

ISpatialReferenceFactory3 srFactory = new SpatialReferenceEnvironmentClass();
IProjectedCoordinateSystem3 pcsSource; 
IGeographicCoordinateSystem2 gcsSource; 
IProjectedCoordinateSystem3 pcsTarget; 
IGeographicCoordinateSystem2 gcsTarget;
ISpatialReference3 srSourceCoordSys;
ISpatialReference3 srTargetCoordSys;
int OriginalCoordSys;
int TargetCoordSys;

        int SourceGeographicBaseID = 0;
        int TargetGeographicBaseID = 0;

        //Define the source coordinate system
        if (OrigCoordSysType.ToUpper() == "PROJECTED")
        {
            pcsSource = (IProjectedCoordinateSystem3)srFactory.CreateProjectedCoordinateSystem(OriginalCoordSys);
            SourceGeographicBaseID = pcsSource.GeographicCoordinateSystem.FactoryCode;
            srSourceCoordSys = (ISpatialReference3)pcsSource;
        }
        else
        {
            gcsSource = (IGeographicCoordinateSystem2)srFactory.CreateGeographicCoordinateSystem(OriginalCoordSys);
            SourceGeographicBaseID = gcsSource.FactoryCode;
            srSourceCoordSys = (ISpatialReference3)gcsSource;
        }

        //Define the target coordinate system
        if (TargetCoordSysType.ToUpper() == "PROJECTED")
        {
            pcsTarget = (IProjectedCoordinateSystem3)srFactory.CreateProjectedCoordinateSystem(TargetCoordSys);
            TargetGeographicBaseID = pcsTarget.GeographicCoordinateSystem.FactoryCode;
            srTargetCoordSys = (ISpatialReference3)pcsTarget;

        }
        else
        {
            gcsTarget = (IGeographicCoordinateSystem2)srFactory.CreateGeographicCoordinateSystem(TargetCoordSys);
            TargetGeographicBaseID = gcsTarget.FactoryCode;
            srTargetCoordSys = (ISpatialReference3)gcsTarget;
        }
2

The simplest method is write a python script (arcpy) and expose it how toolbox in arcobjects. Then you use arcobjects and call this tool.

In arcpy you have ListTransformations (http://resources.arcgis.com/en/help/main/10.1/index.html#/ListTransformations/018v0000001p000000/)

toolbox tool exposed with a toolbox:

 import arcpy
 try:

     layer = arcpy.GetParameter(0)
     layerDescribe = arcpy.Describe(layer)

     from_sr = layerDescribe.featureClass.spatialReference
     to_sr = arcpy.GetParameter(1)
     extent = layerDescribe.extent

     transformations = arcpy.ListTransformations(from_sr, to_sr, extent)
     arcpy.SetParameter(2, transformations)

 except StandardError, ErrDesc:
     arcpy.AddMessage("Error: " + str(ErrDesc))
 except:
     arcpy.AddMessage("Error: get list transformations")

In arcobjects:

            IVariantArray parameters = new VarArrayClass();

            parameters.Add(this.pathLayer); //path and file lyr example c:\temp\test.lyr
            parameters.Add(this.spatialReferenceOut);

            IGPValue output = null;
            Geoprocessor geoprocessor = new Geoprocessor();
            geoprocessor.AddToolbox(@"c:\Temp\myToolbox.tbx");
            ExecuteTask(this.geoprocessor, parameters, "ListTransformations", 0 ,out output);   //ListTransformations is the name of your toolbox tool

            IGPMultiValue GPMultiValue = output as IGPMultiValue;

            for (int i = 0; i < GPMultiValue.Count;i++ )
            {
                cboDatumTransformation.Items.Add((GPMultiValue.get_Value(i) as IGPString).Value);
            }

So you have only datum transformations available for extent of input

News: now in ArcGIS Server 10.3 API Rest you have available in GeometryService -> Project FindTransformations that return a list of applicable geographic transformations one should use when projecting geometries from the input spatial reference to the output spatial reference

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Not the answer you're looking for? Browse other questions tagged or ask your own question.