[GIS] ArcObjects, 속성값으로 도형 선택하기

ESRI에서 정의하는 Feature는 도형+속성입니다. 즉 공간 상의 위치/모양을 나타내는 도형 정보와 이 도형 정보에 대한 속성 정보의 쌍이 Feature라고 할 수 있습니다. GIS 시스템에서 우리는 매우 자주 속성값의 검색을 통해 공간 상의 도형이 어떻게 분포되어 있는지를 파악해 보는 경우가 많습니다. 예를 들어서 서울시를 구성하는 행정구 중에서 인구가 10만명 이상인 지역이라든지… 행정구의 이름 중 ‘동’자가 들어가는 동대문구나 동작구 등과 같은 지역의 도형을 선택하는 것입니다. 여기서는 두번째 예인 ‘동’자가 들어가는 서울시의 행정구를 선택해보는 예를 통해 속성값으로 도형을 선택하는 기능에 대해 정리해 보도록 하겠습니다.

먼저 서울시의 SHP 파일로부터 지도 레이어를 추가합니다. 이 SHP 파일의 속성값은 아래와 같습니다.

사용자 삽입 이미지
살펴보면 SGG_NM이라는 필드명이 행정구의 이름을 담고 있는 필드라는 것을 알 수 있습니다. 이런 경우에 ‘동’자가 들어가는 행정구의 이름을 가진 레코드를 검색하기 위해 우리는 흔히 다음과 같은 SQL문을 던지게 됩니다.

SELECT * FROM TABLE_NAME WHERE SGG_NM LIKE '%동%'

여기서 중요한 것은 WHERE절에 해당하는 SGG_NM LIKE ‘%동%’입니다. 바로 SQL문의 WHERE절에 우리가 원하는 조건을 통해 속성 조건값에 일치하는 도형을 선택할 수 있게 됩니다. 아래는 실제로 속성값에 대한 도형 선택에 대한 ArcObjects의 코드입니다.

ESRI.ArcGIS.Carto.IFeatureLayer pFL = axMapControl1.get_Layer(0) as 
                     ESRI.ArcGIS.Carto.IFeatureLayer;

ESRI.ArcGIS.Geodatabase.IFeatureClass pFC = pFL.FeatureClass;
ESRI.ArcGIS.Geodatabase.QueryFilter pQF = 
                     new ESRI.ArcGIS.Geodatabase.QueryFilter();

pQF.WhereClause = "SGG_NM LIKE '%동%'";

ESRI.ArcGIS.Geodatabase.ISelectionSet pSS = pFC.Select(pQF,
                           esriSelectionType.esriSelectionTypeIDSet,
                           esriSelectionOption.esriSelectionOptionNormal, null);

ESRI.ArcGIS.Carto.IFeatureSelection pFS = 
                    pFL as ESRI.ArcGIS.Carto.IFeatureSelection;
pFS.SelectionSet = pSS;

axMapControl1.ActiveView.Refresh();

아래는 그 실행 결과 화면입니다.

사용자 삽입 이미지

서울시의 행정구 중에 ‘동’자가 들어간 영역에 대한 총 4개의 도형이 선택된 것을 알 수 있습니다.

[GIS] ArcObjects, 지도 화면 조작(확대/축소/이동)

ArcObjects는 ArcGIS의 기반되는 수많은 COM Object입니다. 처음에는 ArcGIS 툴을 설치함으로써 ArcObjects를 사용할 수 있다는 부담이 이었으나 현재는 ArcGIS가 아닌 ArcEngine이라는 개발자 SDK 형태로 ArcObjects만을 설치할 수 있도록 편의를 제공합니다. (이부분은 필자의 추측이므로 틀렸을때는 과감한 하이킥 부탁드립니다) 이번 글에서는 ArcObjects를 이용해 화면상에 표시한 지도를 마우스를 이용하여 확대 또는 축소하거나 이동 시키는 방법에 대해 설명합니다.

이 글은 일단 ArcObjects의 지도 컨트롤에 레이어가 하나 추가되었다고 가정하고 설명합니다. 또한 아래 화면처럼 Zoom In, Zoom Out, Pan, Zoom Full 이라는 텍스트를 가진 버튼이 존재합니다.

사용자 삽입 이미지
각 버튼에 대한 기능을 하나 하나 살펴 보도록 하겠습니다. 먼저 Zoom In 버튼을 클릭했을 때의 코드는 아래와 같습니다.

private void button2_Click(object sender, EventArgs e)
{
    // 0: None
    // 1: Zoom In Mode
    // 2: Zoom Out Mode
    // 3: Pan
    MapViewMode = 1;
}

MapViewMode는 아래와 같이 정의된 parivate 접근자 클래스 맴버 변수입니다.

// 0: None
// 1: Zoom In Mode
// 2: Zoom Out Mode
// 3: Pan
private int MapViewMode = 0;

private bool bMouseDown = false;
private ESRI.ArcGIS.Geometry.IPoint DownPt = new ESRI.ArcGIS.Geometry.Point();

지도의 확대, 축소, 이동 기능은 마우스 이벤트와 밀접하게 연관되어 있기 때문에 마우스 이벤트 안에서 현재 사용자가 어떤 지도 조작 기능을 원하는지를 저정해 놓을 필요가 있습니다. 바로 MoveViewMode가 현재 지도 조작 모드 값을 담고 있으며 1은 확대 모드, 2는 축소 모드, 3은 이동 모드를 의미합니다. 이외에도 현재 마우스 버튼이 눌려진 상태인지를 나타내는 bMouseDown과 마우스가 눌려진 커서의 위치를 지도 좌표로 저장할 DownPt 변수가 있습니다.

Zoom In 버튼을 눌러 MapViewMode를 1로 설정함으로써 마우스 이벤트에 대해 지도 확대 모드 상태임을 파악할 수 있게 되었습니다. 이제 마우스 이벤트에 대해 살펴보도록 하겠습니다.

private void axMapControl1_OnMouseDown(object sender, 
                    AxESRI.ArcGIS.Controls.IMapControlEvents2_OnMouseDownEvent e)
{
    if (MapViewMode == 1) 
    {
        ESRI.ArcGIS.Geometry.IEnvelope pEnv = axMapControl1.TrackRectangle();
        axMapControl1.ActiveView.Extent = pEnv;
        axMapControl1.ActiveView.Refresh();
    }

마우스 다운 이벤트입니다. 지도 확대 모드인MapViewMode가 1에 대해 6, 7, 8번 라인의 코드가 실행됩니다. 여기까지가 지도를 마우스를 통해 사각형 영역을 지정하여 지정된 사각형 영역에 대한 지도 확대 기능입니다.

다음으로 Zoom Out 버튼인 지도 축소 기능에 대해 살펴보겠습니다. 먼저 버튼의 클릭 이벤트의 코드는 아래와 같습니다.

private void button3_Click(object sender, EventArgs e)
{
    // 0: None
    // 1: Zoom In Mode
    // 2: Zoom Out Mode
    // 3: Pan
    MapViewMode = 2;
}

축소 기능도 확대 기능과 마찬가지로 마우스 다운 이벤트에서 지도 축소 기능을 수행합니다. 아래의 코드는 마우스 다운 이벤트에서 지도 축소 기능에 대한 코드 부분입니다.

private void axMapControl1_OnMouseDown(object sender, 
                       AxESRI.ArcGIS.Controls.IMapControlEvents2_OnMouseDownEvent e)
{
    if (MapViewMode == 1) 
    {
        ...
    }
    else if(MapViewMode == 2) 
    {
        ESRI.ArcGIS.Geometry.IEnvelope pEnv = axMapControl1.TrackRectangle();
    
        double OldWidth = axMapControl1.ActiveView.Extent.XMax - 
                                          axMapControl1.ActiveView.Extent.XMin;
        double TrackWidth = pEnv.XMax - pEnv.XMin;
        double RatioWidth = OldWidth / TrackWidth;
        double OldHeight = axMapControl1.ActiveView.Extent.YMax - 
                                           axMapControl1.ActiveView.Extent.YMin;
        double TrackHeight = pEnv.YMax - pEnv.YMin;
        double RatioHeight = OldHeight / TrackHeight;

        ESRI.ArcGIS.Geometry.IEnvelope pNewEnv = new 
                           ESRI.ArcGIS.Geometry.EnvelopeClass();
    
        pNewEnv.XMin = axMapControl1.ActiveView.Extent.XMin 
                                                 - (RatioWidth - 1.0) * OldWidth;
        pNewEnv.XMax = axMapControl1.ActiveView.Extent.XMax 
                                                 + (RatioWidth - 1.0) * OldWidth;
        pNewEnv.YMin = axMapControl1.ActiveView.Extent.YMin 
                                                 - (RatioHeight - 1.0) * OldHeight;
        pNewEnv.YMax = axMapControl1.ActiveView.Extent.YMax 
                                                 + (RatioHeight - 1.0) * OldHeight;
     
        axMapControl1.ActiveView.Extent = pNewEnv;
        axMapControl1.ActiveView.Refresh();
    }

MapViewMode가 2인지를 비교하는 if 문이 새로운 코드입니다. 원리는 먼저 현재의 지도 화면 영역의 크기와 사용자가 마우스를 이용해 지정한 사각 영역의 크기의 비율을 이용하여 현재 지도 화면 영역에 이 크기의 비율을 반영해줍니다.

다음으로 지도 이동에 대한 기능에 대해 살펴 보겠습니다. 먼저 Pan 버튼의 클릭 이벤트의 코드는 아래와 같습니다.

private void button4_Click(object sender, EventArgs e)
{
    // 0: None
    // 1: Zoom In Mode
    // 2: Zoom Out Mode
    // 3: Pan
    MapViewMode = 3;
}

지도 화면의 이동 기능은 확대와 축소 기능처럼 단순히 마우스 다운 이벤트만 사용하는 것이 아닌 마우스 다운 이벤트와 마우스 이동 이벤트 그리고 마우스 업 이벤트를 모두 사용합니다. 먼저 마우스 다운 이벤트를 살펴보겠습니다.

private void axMapControl1_OnMouseDown(object sender, 
              AxESRI.ArcGIS.Controls.IMapControlEvents2_OnMouseDownEvent e)
{
    bMouseDown = true;
    DownPt.X = e.mapX;
    DownPt.Y = e.mapY;

    if (MapViewMode == 1) 
    {
        ...
    }
    else if(MapViewMode == 2) 
    {
        ...
    } 
    else if(MapViewMode == 3) 
    {
        ESRI.ArcGIS.Geometry.IPoint pPt = new ESRI.ArcGIS.Geometry.Point();
        pPt.X = e.mapX;
        pPt.Y = e.mapY;
        axMapControl1.ActiveView.ScreenDisplay.PanStart(pPt);
    }
}

먼저 앞서 정의한 클래스 맴버 변수 bMouseDown를 true로 설정하여 마우스 이동 이벤트에서 현재 마우스 버튼이 눌려진 상태임을 알 수 있게 하며 DownPt에 현재 마우스 버튼이 눌려진 위치를 지도 좌표로 저장합니다. 그리고 이동 기능에 대한 코드를 실행합니다. 다음으로 마우스 이동 이벤트는 아래와 같습니다.

private void axMapControl1_OnMouseMove(object sender, 
                  AxESRI.ArcGIS.Controls.IMapControlEvents2_OnMouseMoveEvent e)
{
    if(MapViewMode==3 && bMouseDown) 
    {
        ESRI.ArcGIS.Geometry.IPoint pPt = new ESRI.ArcGIS.Geometry.Point();
        pPt.X = e.mapX;
        pPt.Y = e.mapY;

        axMapControl1.ActiveView.ScreenDisplay.PanMoveTo(pPt);
    }
}

마우스 이동 이벤트는 마우스가 이동될때마다 항상 발생하는 이벤트이므로 지도 이동 상태를 잡아 내는 것이 중요합니다. 이 상태를 잡것이 바로 MapViewMode가 3인지와 현재 마우스 버튼이 눌려진 상태인지를 검사하는 것입니다. 이 조건에서만 해당되는 지도 이동 기능을 수행합니다. 다음으로 마우스 업 이벤트는 아래와 같습니다.

private void axMapControl1_OnMouseUp(object sender, 
                  AxESRI.ArcGIS.Controls.IMapControlEvents2_OnMouseUpEvent e)
{
    if (MapViewMode == 3 && bMouseDown)
    {
        ESRI.ArcGIS.Geometry.IEnvelope pEnv = 
                               axMapControl1.ActiveView.ScreenDisplay.PanStop();
        if(pEnv != null) 
        {
            axMapControl1.ActiveView.Extent = pEnv;
            axMapControl1.ActiveView.Refresh();
        }
    }

    bMouseDown = false;
}

Up 이벤트에서도 지도 이동 모드 인지를 검사하고 조건에 맞을때만 지도 이동 기능을 최종적으로 완료합니다. 이 상으로 지도 화면 조작에 대한 내용을 간단하게 정리해 보았습니다. 이 글에서 설명한 코드는 아래의 링크를 통해 다운로드 하시기 바랍니다.

[GIS] ArcObjects, 속성값으로 쿼리(Query)하기

GIS의 강력한 기능 중에 하나는 속성 데이터에 기반한 공간적인 처리 및 가시화인데, 이 글은 ArcObjects에서 속성값 조건에 대해 쿼리(Query)하는 방법에 대해 설명하겠습니다. 일단…. 속성값에 대한 쿼리가 목적이므로 먼저 속성값에 접근할 수 있는 ITable 인터페이스에 접근할 필요가 있습니다. 다음은 d:\__data__ 폴더 안의 seoul.dbf 파일로부터 ITable 인터페이스의 구체 클래스 인스턴스를 얻는 코드입니다.

ESRI.ArcGIS.Geodatabase.IWorkspaceFactory pWF;
ESRI.ArcGIS.Geodatabase.IFeatureWorkspace pFW;

pWF = new ESRI.ArcGIS.DataSourcesFile.ShapefileWorkspaceFactory();
pFW = pWF.OpenFromFile("d:/__data__", 0) 
                as ESRI.ArcGIS.Geodatabase.IFeatureWorkspace;

ESRI.ArcGIS.Geodatabase.ITable pTable;
pTable = pFW.OpenTable("seoul");

ITable의 구체 클래스의 인스턴스인 pTable은 속성값으로 검색할 수 있는 매서드인 Search 함수를 가지고 있습니다. 이 Search 함수는 두개의 인수를 가지며 첫번째 인수의 타입은 IQueryFilter로써 SQL 문의 WHERE 절의 조건문을 지정하기 위해 사용됩니다. 그리고 두번째 인수는 검색 결과에 대한 Row에 대한 재활용 여부에 대한 bool 형입니다. 이에 대해서는 이 글의 마지막에 간단히 다시 설명하겠습니다.

속성값으로 쿼리하기 위하여 조건문을 만들기 위해서… 먼저 앞의 코드를 통해 오픈한 seoul.dbf 파일의 필드 구조를 알 필요가 있습니다. 힌트를 준다면 seoul.dbf의 속성 중의 하나는 SGG_NM으로써 서울시를 구성하는 행정구의 이름값이 저장되어 있습니다. 여기서는 행정구 이름에 ‘동’자가 들어가는 모든 Row를 검색하려고 합니다. 이런 조건을 위한 ITable의 Search의 첫번째 인자인 IQueryFilter의 CoClass(구체 클래스)의 정의는 다음과 같습니다.

ESRI.ArcGIS.Geodatabase.IQueryFilter pQF;
pQF = new ESRI.ArcGIS.Geodatabase.QueryFilter();
pQF.WhereClause = "SGG_NM LIKE '%동%'";

IQueryFilter의 WhereClause 프로퍼티가 바로 ‘그것’인데.. 일반적인 SQL의 SELECT 문의 WHERE 절의 바로 정확히 ‘그것’에 해당합니다. SQL문의 SELECT문을 알고 있는 개발자라면 아~!라는 탄식이 나올정도로 ArcObjects는 속성값으로 데이터를 쿼리하는 탁월한 방법을 사용하고 있습니다. 이렇게 만든 IQueryFilter를 이용하여 쿼리하는 코드는 다음과 같습니다.

ESRI.ArcGIS.Geodatabase.IFeatureCursor pFC;
pFC = pTable.Search(pQF, true) as ESRI.ArcGIS.Geodatabase.IFeatureCursor;

ITable의 Search 함수를 사용하며 검색 결과 Row는 IFeatureCursor 타입으로 반환됩니다. 검색 조건과 일치하는 Row를 얻기 위해 IFeatureCursor 타입의 인스턴스 변수인 pFC를 사용하는데.. 아래의 코드가 바로 그것이며, listBox1 이름의 ListBox 컨트롤에 그 결과를 채웁니다.

ESRI.ArcGIS.Geodatabase.IFeature pFeature;
int cntFields = pFC.Fields.FieldCount;

listBox1.Items.Clear();
pFeature = pFC.NextFeature();
while(pFeature != null) {
    for(int iField=0; iField

5번째 코드에서 가장 먼저 IFeatureClass의 NextFeature 매서드를 호출하여 첫번째 검색 결과의 Row에 해당되는 IFeature 클래스 타입의 인스턴스를 반환받고 첫번째 Row에 대한 값을 얻기 위하여 IFeature의 get_Value 매서드를 사용하여 값을 얻습니다. 그 다음 결과 Row를 얻기 위하여 다시 NextFeature를 호출합니다. NextFeature의 반환값이 null이면 더 이상 조건에 맞는 Row가 없다는 의미입니다. 실행 결과는 아래와 같습니다. 강동구, 성동구, 동작구, 동대문구와 같이 '동'자가 들어간 데이터만이 검색된 것을 알 수 있습니다.

사용자 삽입 이미지
끝으로 앞서 언급한... 이 글의 핵심 매서드인 ITable의 Search 매서드의 두번째 인자의 의미에 대해서 설명하면... 만약 true라면 IFeatureClass의 NextFeature의 결과에 대한 인스턴스를 재활용한다는 의미이며 false이면 재활용하지 않고 NextFeature를 호출할때 마다 새로운 인스턴스를 생성한다는 의미입니다. 이를 목적에 대해 생각해보면 true이면 Feature를 읽기 전용으로만 사용해야 하며 false일 경우 IFeature를 통해 속성값을 변경할 수 있습니다.

[GIS] ArcObjects, 속성 테이블(DBF)의 필드 정보 얻기

ArcGIS에서 Feature는 좌표에 대한 도형 데이터와 속성으로 묶어진 단위입니다. ESRI에서 정의한 Shape 파일의 경우 좌표에 대한 도형 데이터는 .SHP 파일에 저장되며 속성은 .DBF 파일에 저장됩니다. 속성은 하나의 DB 테이블 개념과 정확히 일치합니다. DB 테이블의 필드 정보와 각 필드에 대한 값들의 집합의 구성 요소인 로우(Row) 또는 레코드(Record)로 구성됩니다.

필드 정보라 함은 필드의 이름과 필드의 타입 그리고 필드 타입에 대한 데이터 저장 가능 길이 등으로 구성됩니다. 이 포스트는 Shape 파일에 대해서 .DBF에 저장된 필드 정보를 얻어내는 ArcObjects의 API에 대해서 설명합니다.

가장 먼저 해야할 작업은 .DBF 파일을 여는 것에서 시작합니다. 다음 코드는 .DBF 파일을 여는 방법입니다.

ESRI.ArcGIS.Geodatabase.IWorkspaceFactory pWF;
ESRI.ArcGIS.Geodatabase.IFeatureWorkspace pFW;

pWF = new ESRI.ArcGIS.DataSourcesFile.ShapefileWorkspaceFactory();
pFW = pWF.OpenFromFile("d:/__data__", 0) 
              as ESRI.ArcGIS.Geodatabase.IFeatureWorkspace;

ESRI.ArcGIS.Geodatabase.ITable pTable;
pTable = pFW.OpenTable("seoul");

.SHP 파일을 여는 것과 매우 동일하다는 것을 알 수 있습니다. 다른 부분은 8, 9번 라인에서 ITable 타입으로 하여 테이블을 여는 것입니다. 위의 예는 d:/__data__ 폴더 안에 seoul.dbf 파일을 열어 그 테이블을 ITable 타입인 pTable에 저장해 놓는 경우입니다.

이렇게 얻은 pTable을 통해 필드 정보를 얻기 위한 코드는 아래와 같습니다.

ESRI.ArcGIS.Geodatabase.IFields pFields;
pFields = pTable.Fields;

int cntFields = pFields.FieldCount;

테이블의 필드 정보는 IFields 타입에 저장되며 pTable의 Fields 속성을 통해 접근이 가능합니다. 4번째 줄은 필드의 전체 개수를 얻는 코드입니다. 이제 이렇게 얻은 pFields를 통해 각 필드의 이름, 별칭(Alias), 필드길이, 정밀도, 타입을 얻어 ListBox 컨트롤에 채워 보는 코드는 아래와 같습니다.

listBox1.Items.Clear();
for(int iField=0; iField<cntFields; ++iField) 
{
    ESRI.ArcGIS.Geodatabase.IField pField;
    pField = pFields.get_Field(iField);

    string AliasName = pField.AliasName;
    string FieldName = pField.Name;
    int Length = pField.Length;
    int Precision = pField.Precision;
    ESRI.ArcGIS.Geodatabase.esriFieldType FT = pField.Type;
                
    listBox1.Items.Add(FieldName + "(" + AliasName + ")");
    listBox1.Items.Add("  Length: " + Length.ToString());
    listBox1.Items.Add("  Precision: " + Precision.ToString());
    listBox1.Items.Add("  Type: " + FT.ToString());
    listBox1.Items.Add("");
}

참고로 ListBox 컨트롤의 이름은 listBox1입니다. 앞서 구한 전체 필드의 개수를 통해 반복문에서 필드 정보를 IField 타입으로 가져와 필드의 정보를 구할 수 있습니다. 이름은 IField 타입의 Name 속성으로 알 수 있고, 별칭명은 AliasName, 길이는 Length, 정밀도는 Precision 그리고 필드의 타입은 Type으로 알 수 있습니다. 참고로 아래는 실행 결과 화면입니다.

사용자 삽입 이미지