2011년 12월 25일 일요일

[ArcObjects] Layer와 Feature Class/Table의 필드 별칭 설정

▣ 미션
 - ArcMap에서 아래 그림과 같이 테이블과 속성정보 조회시 영문필드에 한글필드로 별칭을 보여주고 싶다.
- 면적 등과 같이 Numeric 필드일 경우 소숫점 2째자리까지만 보여주고 천단위 구분자를 두고싶다.
- GeoDatabase(Personal, File, ArcSDE)에서 레이어를 불러올 경우 한글별칭을 기본값으로 사용하고 싶다.

▣ 설명
 - Layer의 필드정보는 Feature Layer 수준, FeatureClass, Table의 필드정보는 GeoDatabase 수준에서 변경.
 - 따라서 FeatureClass, Table의 필드정보 변경은 물리적으로 저장되고 Layer의 필드정보는 ArcMap과 같이 Application 내에서만 임시 적용됨.
 - FeatureClass에 이미 한글별칭이 적용되어 있더라도 Application에서 실시간 조인이 이루어지는 경우는 별칭이 적용되지 않을 수 있음.

▣ ArcObjects Interface
 - INumberFormat
 - ITableFields
 - IFieldInfo
 - IClassSchemaEdit
 - ISchemaLock

▣ Code Snippet
using System.Runtime.InteropServices;
using ESRI.ArcGIS.esriSystem;
using ESRI.ArcGIS.Geodatabase;
using ESRI.ArcGIS.Carto;
■ 필드이름과 매칭되는 Alias 정보 생성
System.Collections.Hashtable FieldAliasTable = new System.Collections.Hashtable();

public void AddFieldAlias() {
    FieldAliasTable.Add("OBJECTID", "일련번호");
    FieldAliasTable.Add("SHAPE", "공간정보");
    FieldAliasTable.Add("SHAPE_AREA", "면적");
    FieldAliasTable.Add("SHAPE_LENGTH", "둘레");
    
    // 기타 필드 추가
    FieldAliasTable.Add("NAM", "명칭");
}
■ 레이어의 필드 정보 변경
- 다음 코드는 위 Alias 매칭 테이블에 담긴 정보를 이용하여 면적 등과 같이 Numeric 필드일 경우 소숫점 2째자리까지만 보여주고 천단위 구분자 적용
- 테이블과 조인된 필드일 경우에도 한글 별칭을 일괄적으로 적용하는 예임
- 또한 필드 이름 중 NAM 이라는 String 필드가 포함되어 있을 경우 Identify 등에서 사용되는 Primary Display Field로 설정함
public void AlterLayerFieldAlias(IFeatureLayer featureLayer) {
    // Number format for Double, Single Fields
    INumericFormat numberFormat = new NumericFormatClass();
    numberFormat.RoundingOption = esriRoundingOptionEnum.esriRoundNumberOfDecimals;
    numberFormat.RoundingValue = 2;    // Number of decimal places
    numberFormat.ZeroPad = true;      // Pad with zeros
    numberFormat.UseSeparator = true; // Show thousands separators

    IGeoFeatureLayer geoFeatureLayer = (IGeoFeatureLayer)featureLayer;
    ITableFields tableFields = featureLayer as ITableFields;
    for (int fieldId = 0; fieldId < tableFields.FieldCount; fieldId++) {
        IFieldInfo fieldInfo = tableFields.get_FieldInfo(fieldId);
        IField field = tableFields.get_Field(fieldId);

        string fieldName = field.Name.ToUpper();
        string fieldAlias = fieldInfo.Alias;

        if (fieldName.Contains(".")) {
            int pos = fieldName.LastIndexOf(".");
            fieldName = fieldName.Substring(pos + 1).Trim();
        }

        switch (field.Type) {
            case esriFieldType.esriFieldTypeDouble:
            case esriFieldType.esriFieldTypeSingle:
                fieldInfo.NumberFormat = (INumberFormat)numberFormat;
                break;
            case esriFieldType.esriFieldTypeString:
                if (fieldName.Contains("NAM")) {
                    // Primary display field
                    geoFeatureLayer.DisplayField = field.Name;
                }
                break;
        }

        if (FieldAliasTable.Contains(fieldName)) {
            fieldAlias = FieldAliasTable[fieldName].ToString();
        }

        fieldInfo.Alias = fieldAlias;
    }
}
■ ObjectClass(FeatureClass, Table)의 필드 Alias 변경
 - 다음 코드는 ArcCatalog에서 각각의 필드에 대한 별칭을 UI상에서 적용하는 과정을 코드로 구현한 예임
 - 클래스나 필드의 별칭을 변경하기 위해서는 반드시 GeoDatabase에 등록되어 있어야 한다.
public void AlterFieldAlias(IObjectClass objectClass, string fieldName, string aliasName) {
    if (objectClass.ObjectClassID == -1) {
        // string suggestedOIDFieldName = "OBJECTID";
        // RegisterWithGeodatabase(objectClass, suggestedOIDFieldName);
        return;
    }
    
    // Attempt to acquire an exclusive schema lock for the object class.
    ISchemaLock schemaLock = (ISchemaLock)objectClass;
    try {
        schemaLock.ChangeSchemaLock(esriSchemaLock.esriExclusiveSchemaLock);

        IClassSchemaEdit schemaEdit = (IClassSchemaEdit)objectClass;
        if (objectClass.FindField(fieldName) != -1) {
            schemaEdit.AlterFieldAliasName(fieldName, aliasName);
        }
    } catch (COMException comExc) {
        throw comExc;
    } finally {
        // Reset the lock on the object class to a shared lock.
        schemaLock.ChangeSchemaLock(esriSchemaLock.esriSharedSchemaLock);
    }
}
■ ObjectClass의 GeoDatabase 등록
 - 다음 코드는 위 그림과 같이 GeoDatabase에 등록되지 않은 테이블이나 FeatureClass를 등록하는 예임
public void RegisterWithGeodatabase(IObjectClass objectClass, string suggestedOIDFieldName) {
    // Attempt to acquire an exclusive schema lock for the object class.
    ISchemaLock schemaLock = (ISchemaLock)objectClass;
    try {
        schemaLock.ChangeSchemaLock(esriSchemaLock.esriExclusiveSchemaLock);

        IClassSchemaEdit classSchemaEdit = (IClassSchemaEdit)objectClass;
        classSchemaEdit.RegisterAsObjectClass(suggestedOIDFieldName, string.Empty);
    } catch (COMException comExc) {
        throw comExc;
    } finally {
        // Reset the lock on the object class to a shared lock.
        schemaLock.ChangeSchemaLock(esriSchemaLock.esriSharedSchemaLock);
    }
}

2011년 11월 23일 수요일

[ArcObjects]How to identify on a map layer

▣ ArcObjects Interface
 - IIdentify

▣ Code Snippet
using ESRI.ArcGIS.Carto;
using ESRI.ArcGIS.Display;
using ESRI.ArcGIS.Geometry;
using ESRI.ArcGIS.Geodatabase;
using ESRI.ArcGIS.esriSystem;
■ Identify on a map
public static void IdentifyMap(IMap focusMap, IPoint mapPoint) {
    if (focusMap.LayerCount == 0) return;

    ESRI.ArcGIS.esriSystem.UID uid = new UIDClass();
    uid.Value = "{6CA416B1-E160-11D2-9F4E-00C04F6BC78E}";

    IEnumLayer enumLayer = focusMap.get_Layers(uid, true);
    enumLayer.Reset();

    ILayer layer = enumLayer.Next();
    while (layer != null) {
        if (layer.Valid && layer.Visible) {
            IdentifyLayer(focusMap, layer, mapPoint);
        }
        layer = enumLayer.Next();
    }
}
■ Identify on a layer
static void IdentifyLayer(IMap focusMap, ILayer layer, IPoint mapPoint) {
    bool isFeatureLayer = layer is IFeatureLayer;

    IIdentify identify = (IIdentify)layer;
    IGeometry searchGeometry = mapPoint;

    if (isFeatureLayer) {
        IFeatureLayer featureLayer = (IFeatureLayer)layer;
        if (featureLayer.FeatureClass.ShapeType != esriGeometryType.esriGeometryPolygon) {
            ITopologicalOperator topoOpt = (ITopologicalOperator)mapPoint;
            double radius = PixelsToMapUnits((IActiveView)focusMap, 6d);

            searchGeometry = topoOpt.Buffer(radius);
        }
    }

    ESRI.ArcGIS.esriSystem.IArray idArray = identify.Identify(searchGeometry);
    if (idArray == null) {
        return;
    }

    IActiveView activeView = (IActiveView)focusMap;

    for (int k = 0; k < idArray.Count; k++) {
        IIdentifyObj identifyObj = (IIdentifyObj)idArray.get_Element(k);

        if (isFeatureLayer) {
            IFeatureIdentifyObj featureObj = (IFeatureIdentifyObj)identifyObj;
            IRowIdentifyObject rowObj = (IRowIdentifyObject)featureObj;
            IFeature feature = (IFeature)rowObj.Row;

            // flash shape
            identifyObj.Flash(activeView.ScreenDisplay);

            // show attributes
            string infoMsg = string.Format("{0}'s OID = {1}", layer.Name, feature.OID);
            System.Windows.Forms.MessageBox.Show(infoMsg);
        } else {
            IRasterIdentifyObj rasterObj = (IRasterIdentifyObj)identifyObj;

            // show attributes
            string infoMsg = string.Format("{0}'s pixel value = {1}", layer.Name, rasterObj.Name);
            System.Windows.Forms.MessageBox.Show(infoMsg);
        }
    }
}
■ Convert screen pixels to map units
static double PixelsToMapUnits(IActiveView activeView, double pixelUnits) {
    IDisplayTransformation displayTrans = activeView.ScreenDisplay.DisplayTransformation;
    tagRECT deviceRECT = displayTrans.get_DeviceFrame();
    int pixelExtent = deviceRECT.right - deviceRECT.left;

    double realWorldDisplayExtent = displayTrans.VisibleBounds.Width;
    double sizeOfOnePixel = realWorldDisplayExtent / pixelExtent;

    return pixelUnits * sizeOfOnePixel;
}

2011년 11월 14일 월요일

[ArcObjects]GRID VAT(Value Attribute Table) 활용

ArcGIS의 Grid 포맷을 사용할 때 Integer Grid일 경우 아래 그림과 같이 테이블을 열어 조작할 수 있습니다.

다음은 Grid의 VAT(Value Attribute Table)를 C# 코드에서 활용하는 예제입니다.
 - VAT에 필드 추가하기
 - VAT에 셀값의 면적 계산하기

▣ Sample Integer GRID
▣ ArcObjects Interface
 - IGridTableOp

▣ Code Snippet
using System;

using ESRI.ArcGIS.Geodatabase;
using ESRI.ArcGIS.DataSourcesRaster;
using ESRI.ArcGIS.GeoAnalyst;
■ VAT에 필드 추가하기
public static bool AddVatField(IRaster raster, string fieldName, esriFieldType fieldType, int fieldLength) {
    IRasterBandCollection bandCol = (IRasterBandCollection)raster;
    IRasterBand band = bandCol.Item(0);

    bool hasTable = false;
    band.HasTable(out hasTable);

    if (hasTable) {
        ITable attTable = band.AttributeTable;
        if (attTable.FindField(fieldName) != -1) {
            return true;
        }

        IField newField = new FieldClass();
        IFieldEdit fieldEdit = (IFieldEdit)newField;

        fieldEdit.Name_2 = fieldName;
        fieldEdit.Type_2 = fieldType;
        fieldEdit.Editable_2 = true;
        fieldEdit.IsNullable_2 = true;
        fieldEdit.Length_2 = fieldLength;

        IGridTableOp gridTableOp = new GridTableOpClass();
        gridTableOp.AddField(band.RasterDataset, newField);
        System.Runtime.InteropServices.Marshal.ReleaseComObject(gridTableOp);

        return true;
    }

    return false;
}
■ VAT에 면적 필드를 추가하고 계산하기
public static void CalculateAreaField(IRaster raster, string areaField) {
    IRasterBandCollection bandCol = (IRasterBandCollection)raster;
    IRasterBand band = bandCol.Item(0);

    bool hasTable = false;
    band.HasTable(out hasTable);
    if (!hasTable) return;

    // Add Field
    if (AddVatField(raster, areaField, esriFieldType.esriFieldTypeDouble, 38)) {
        // calculate cell size
        IRasterProps rstProps = (IRasterProps)raster;
        IPnt pnt = rstProps.MeanCellSize();
        double cellSize = (pnt.X + pnt.Y) / 2.0;
        
        // get fields index
        ITable attTable = band.AttributeTable;
        int idxArea = attTable.FindField(areaField);
        int idxCount = attTable.FindField("COUNT");

        // using update cursor
        IGridTableOp gridTableOp = new GridTableOpClass();
        ICursor updateCursor = gridTableOp.Update(band.RasterDataset, null, false);
        IRow updateRow = updateCursor.NextRow();
        while (updateRow != null) {
            int cellCount = Convert.ToInt32(updateRow.get_Value(idxCount));
            double cellArea = cellCount * (cellSize * cellSize);

            updateRow.set_Value(idxArea, cellArea);
            updateCursor.UpdateRow(updateRow);

            updateRow = updateCursor.NextRow();
        }
        System.Runtime.InteropServices.Marshal.ReleaseComObject(gridTableOp);
        System.Runtime.InteropServices.Marshal.ReleaseComObject(updateCursor);
    }
}