[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, Graphic Element 추가하기

ArcObjects에서 제공하는 Map 컨트롤은 기본적으로 GraphicsContainer 자식 객체를 가지고 있으며 이 컨테이너에 사용자가 원하는 그래픽 요소를 추가할 수 있는 기능을 제공합니다. 추가할 수 있는 그래픽 요소는 생각할 수 있는 2차원 요소 대부분으로 포인트, 폴리라인, 폴리곤, 사각형, 타원 등입니다. 이 글은 이러한 그래픽 요소를 추가하는 방법를 폴리곤 그래픽 요소의 추가를 예로 들어 정리해 하겠습니다.

그래픽 요소의 구성은 크게 지오메트리(Geometry)와 심벌(Symbol)이며… 지오메트리는 정점으로 구성한 그래픽 요소의 뼈대로 생각할 수 있고 심벌은 그래픽 요소의 채움에 대한 색이나 투명도 그리고 외곽선의 굵기, 색상 등에 대한 정보입니다.

앞서 언급한 폴리곤 그래픽 요소를 추가하기 위하여 먼저 지오메트리를 구성하는 코드를 작성해 보면 다음과 같습니다.

ESRI.ArcGIS.Geometry.Point pPt1, pPt2, pPt3, pPt4;
pPt1 = new ESRI.ArcGIS.Geometry.Point();
pPt1.X = 100; pPt1.Y = 100;

pPt2 = new ESRI.ArcGIS.Geometry.Point();
pPt2.X = 900; pPt2.Y = 100;

pPt3 = new ESRI.ArcGIS.Geometry.Point();
pPt3.X = 900; pPt3.Y = 900;

pPt4 = new ESRI.ArcGIS.Geometry.Point();
pPt4.X = 100; pPt4.Y = 900;

object missing = Type.Missing;
ESRI.ArcGIS.Geometry.Polygon pPg;
pPg = new ESRI.ArcGIS.Geometry.Polygon();
pPg.AddPoint(pPt1, ref missing, ref missing);
pPg.AddPoint(pPt2, ref missing, ref missing);
pPg.AddPoint(pPt3, ref missing, ref missing);
pPg.AddPoint(pPt4, ref missing, ref missing);
pPg.AddPoint(pPt1, ref missing, ref missing);

사각형을 구성하는 4개의 좌표를 지정하기 위해 Point 타입의 변수 pPt1, pPt2, pPt3, pPt4의 X 그리고 Y에 좌표값을 지정합니다. 여기서는 사각형의 구성 좌표는 (100, 100)  (900, 100)  (900, 900)  (100, 900)입니다. 그리고 이렇게 지정한 좌표로 구성된 사각형 폴리곤을 구성하기 위해 Polygon 타입의 pPg 변수를 만들고 앞서 만든 4개의 좌표 변수를 추가합니다. 여기까지가 그래픽 요소의 지오메트리를 정의하는 방법이고… 다음으로 심벌을 정의하는 방법에 대해 정리해 보겠습니다.

폴리곤의 심벌을 간단히 생각해보면 채움과 외곽선으로 구분할 수 있습니다. 아래의 코드가 채움 심벌과 외곽선 심벌을 지정하는 코드입니다.

ESRI.ArcGIS.Display.IRgbColor pFillRGB;
pFillRGB = new ESRI.ArcGIS.Display.RgbColor();
pFillRGB.Red = 127;
pFillRGB.Green = 127;
pFillRGB.Blue = 127;

ESRI.ArcGIS.Display.IRgbColor pLineRGB;
pLineRGB = new ESRI.ArcGIS.Display.RgbColor();
pLineRGB.Red = 0;
pLineRGB.Green = 0;
pLineRGB.Blue = 0;

ESRI.ArcGIS.Display.ISimpleLineSymbol pSLS;
pSLS = new ESRI.ArcGIS.Display.SimpleLineSymbol();
pSLS.Color = pLineRGB;

ESRI.ArcGIS.Display.ISimpleFillSymbol pSFS;
pSFS = new ESRI.ArcGIS.Display.SimpleFillSymbol();
pSFS.Color = pFillRGB;
pSFS.Outline = pSLS;

채움에 대한 색상과 외곽선에 대한 색상을 지정하기 위하여 IRgbColor 타입의 변수 pFillRGB와 pLineRGB를 정의하고 이 변수에 대한 색상을 RGB 요소 값으로 지정합니다. 그리고 이렇게 만든 색상을 실제 심벌에 지정하기 위하여 외곽선 심벌 타입인 ISimpleLineSymbol로 pSLS 변수를 생성하고 이 변수의 Color 속성에 앞서 지정한 색상을 지정합니다. 마찬가지로 채움에 대한 ISimpleFillSymbol 변수인 pSFS를 생성하고 색상을 지정합니다. 여기서는 단순한 채움과 외곽선 스타일이므로 ISimpleFillSymbol과 ISimpleLineSymbol을 사용했지만 다양한 이미지 패턴 채움 등과 같은 복잡한 심벌은 또 다른 심벌 타입을 사용하게 됩니다.

이제 이렇게 생성한 심벌과 지오메트리를 폴리곤 요소로써 그래픽 컨테이너에 추가하는 방법은 아래와 같습니다.

ESRI.ArcGIS.Carto.PolygonElement pPE;
pPE = new ESRI.ArcGIS.Carto.PolygonElement();

pPE.Geometry = pPg as ESRI.ArcGIS.Geometry.IGeometry;

ESRI.ArcGIS.Carto.IFillShapeElement pFSE = pPE as ESRI.ArcGIS.Carto.IFillShapeElement;
pFSE.Symbol = pSFS;

axMapControl1.ActiveView.GraphicsContainer.AddElement(pPE, 0);
axMapControl1.ActiveView.Refresh();

1번과 2번 코드가 폴리곤 요소인 PolygonElement 타입의 pPE를 생성하는 코드이며 4번 코드와 5,6번 코드가 앞서 정의한 지오메트리와 심벌을 pPE 객체에 지정하는 코드입니다. 그리고 이렇게 만든 폴리곤 요소를 그래픽 컨터에너에 추가하는 코드가 6번이며 화면상에 그 결과를 반영하는 코드가 마지막 7번 코드입니다. 참고로 6번 코드의 AddElement의 2번째 인자는 z-order 값으로써 0이 가장 최상단에 그려지게 됩니다. 아래는 실행 결과입니다.

사용자 삽입 이미지

또한 앞의 코드를 통해 살펴본 폴리곤 요소와 지오메트리 그리고 심벌에 대한 클래스 관계도는 아래와 같습니다.

사용자 삽입 이미지

텍스트 효과 단위기능 테스트

구글맵이나 네이버지도, 다음지도 등등 모든 포털 지도사이트에서 제공하는 지도 서비스는 지도 이미지를 256×256 픽셀 크기로 잘려진 타일이미지 형태로 서비스되고 있습니다. 자사만의 지도 타일이미지 렌더링 프로그램을 직접 개발한 곳도 있을테고…. 구글처럼 오픈소스 라이브러리를 이용해 구축한 곳도 있을테고….

현재  RIA 기반의 지도 솔루션 개발에 필요한 지도 타일 이미지 렌더러를 개발하고 있습니다. 오픈소스를 활용하여 개발할까… 생각도 해보았지만… 다양한 사용자가 원하는 자신만의 지도 스타일의 충족이라는 틈새 시장을 놓고 볼때… 오픈소스의 획일화된 지도 이미지 스타일은 제 기획 의도와 맞지 않다고 판단하여 직접 개발하기로 결정하게 되었습니다.

사용자 삽입 이미지
위의 결과 이미지는 현재 개발중인 지도 타일이미지 렌더러에서 텍스트 표현을 위해 고민해본 텍스트 효과입니다. 실제 종이 지도를 살펴보고 글자에 적용된 효과를 살펴보면서 위의 3가지 효과정도면 될듯하여 하나 하나 구현해 보았습니다.

추후 타일 이미지 렌더러… 개발이 완료되면 위의 텍스트 효과가 적용된 지도를 통해 좀더 분명한 결과를 알릴 수 있겠지만.. 지금 단계에서 현재 제 스스로가 무엇을 하고 있는지… 어느 방향으로 가고 있는지에 대해 한 조각이라도 구체화하기 위한 과정으로써의 글을 올려봅니다.