[GIS] DuraMap-Xr, 선택된 도형 하이라이팅 시키기

2011년 새해.. 첫번째 1월입니다! 뭔가 새로운 마음 가짐으로.. 일에 전념하려는 의지와는 다르게 여러가지로 집중하지 못했던 시간이 새해까지 계속 이어져왔는데요. 이런 문제를 극복하기 위한 방법을 여러가지로 고민하다가.. 문득 다음과 같은 아이디어가 떠올랐답니다.

“Be Activated For Yourself”

이 아이디어의 약빨이 조금 먹히는 거 같습니다. 다행이도 말입니다. 해서….. 오랜만에 듀라맵에 대해 그간 질문으로 올라왔던 내용 중에 하나에 대해서 예제를 만들어 글로 올려봅니다.

그 질문이란게 뭔고 하니… SHP 파일로 추가된 지도를 구성하고 있는 도형 중에 하나를 마우스로 클릭했을때 클릭된 도형의 ID 값을 얻어오는 것은 알겠는데.. 방금 선택된 도형을 다른 색상으로 그려 하이라이팅 하고 싶다라나 어쩐데나… 라는 질문입니다. 제 답변은 언제나.. “그래픽 레이어를 이용하세요.” 였습니다. 네, 다른 방법이 더 몇가지 있겠지만.. 바로 이 방법에 대해서 실제로 구현해 보도록 하겠습니다. 먼저 다음과 같이 폼을 디~~~ 자인합니다!

사용자 삽입 이미지
듀라맵과 버튼 그리고 텍스트박스 컨트롤이 조신하게… 자리를 잡고 있습니다. 먼저 Add Layer 버튼은 다음과 같이 코딩합니다.

xr.Layers.AddShapeMapLayer("shp", "D:/__Data__/seoul.shp");
xr.Layers.AddGraphicLayer("gl", "");
xr.WaitForAllConnections();

xr.ZoomFullExtent();
          
xr.MouseMode = XrMapLib.XrMapViewModeEnum.XrNormalMode;

네, shp 파일을 통한 레이어와 그래픽 레이어를 추가하는 코드로 구성되어져 있습니다. 실행하고 버튼을 누르면 짜잔~ 하게 지도가 나타나겠지요. 이제 나타난 지도를 구성하는 도형을 마우스로 눌렀을때 어떤 도형이 눌러졌는지.. ID를 텍스트박스 컨트롤에 표시하고 듀라맵에는 선택된 도형을 다른 심벌로 표시하도록 하겠습니다. 듀라맵의 마우스 업! 이벤트에 대한 코드를 다음과 같이 쭈욱~~ 코딩해 봅니다.

private void xr_OnLButtonUp(object sender, 
    AxXrMapLib._IXrMapControlEvents_OnLButtonUpEvent e)
{
    // 레이어가 존재한다면..
    XrMapLib.ShapeMapLayer lyr = xr.Layers.GetLayerAsShapeMap("shp");
    if(lyr != null)
    {
        // 마우스로 클릭된 (x,y)좌표에 존재하는 shp 레이어의 도형 ID를 구함
        XrMapLib.ValueList FIDs = xr.GetFIDFromMousePoint("shp", e.x, e.y, true);
        // 선택된 도형이 있다면...
        if (FIDs.Count != 0)
        {
            int FID = FIDs.GetValueAsInteger(0);
            // 선택된 도형을 텍스트박스에 표시
            txtFID.Text = FID.ToString();

            // 선택된 도형의 좌표를 얻기 위해 ShapeRow를 구함
            XrMapLib.ShapeRow row = lyr.ShapeTable.GetRow(FID);

            // 그래픽 레이어에 추가할 폴리곤 그래픽 요소를 생성
            XrMapLib.PolygonGraphicElement polygon 
                = new XrMapLib.PolygonGraphicElement();
            XrMapLib.CoordinateListCollection clc 
                = new XrMapLib.CoordinateListCollection();
            
            // 폴리곤 그래픽 요소의 정점을 구성
            int cntParts = row.PartCount;
            for (int iPart = 0; iPart < cntParts; ++iPart)
            {
                XrMapLib.CoordinateList cl = new XrMapLib.CoordinateList();
                int cntVertex = row.GetVertexCountFromPart(iPart);
                for (int iVertex = 0; iVertex < cntVertex; ++iVertex)
                {
                    XrMapLib.Coordinate coord 
                        = row.GetVertexFromPart(iPart, iVertex);
                    cl.Add(coord.X, coord.Y);
                }
                
                clc.Add(cl);
            }

            polygon.SetPolygonList(clc);
 
            // 폴리곤 그래픽 요소의 그리기 심벌을 지정
            polygon.LineSymbol.Width = 3;
            polygon.LineSymbol.Color = RGB(255, 128, 0);
            polygon.FillSymbol.Color = RGB(255, 128, 0);
            polygon.FillSymbol.Alpha = 100;
            
            XrMapLib.GraphicLayer gl = xr.Layers.GetLayerAsGraphic("gl");
            // 예전에 추가한 그래픽 요소는 모두 제거하고 새로운 그래픽 요소를 추가
            gl.RemoveAllElements();
            gl.AddElement(0, polygon as XrMapLib.GraphicElement);

            // 다시 그림!
            xr.Update();
            return;
        }
    }

    txtFID.Text = "NA";
}

먼저 텍스트 박스 컨트롤의 이름은 txtFID입니다. 이 코드의 핵심은 선택된 도형의 ID를 통해 도형의 좌표를 얻어오고.. 이 좌표를 이용해 그래픽 레이어의 요소를 생성해서 그래픽 레이어에 추가한다는 내용입니다. 쉽죠? 물론 여기서는 폴리곤 도형만을 대상으로 하고 있습니다. 포인트, 폴리라인에 대해서는 알아서.. 잘~ 응용을 해보시기 바랍니다! 실행 결과를 하나 살펴보면 아래와 같습니다.

사용자 삽입 이미지

[GIS] DuraMap-Xr, 그림 아이콘을 지도 위에 표시하기

포인트 SHP 파일을 듀라맵에서 표현할때 그리기 심벌로 이미지를 적용하고자 할때가 있습니다. 듀라맵에서 포인트 심벌로 제공하는 이미지 파일의 종류는 bmp, jpg, 투명 배경을 지원하는 gif, 알파값을 지원하는 png입니다.

다음 코드는 포인트 타입의 SHP 파일을 레이어로 추가하고 심벌로 png 파일을 지정하는 코드 예입니다.

axXr1.Layers.AddShapeMapLayer("lyr", "D:/__Data__/주기.shp");
axXr1.WaitForAllConnections();

if (axXr1.Resources.AddImageResource("img", "d:/__data__/redmark.png"))
{
    axXr1.Layers.GetLayerAsShapeMap("lyr").
        PointSymbol.SetImage("img", axXr1.Resources);
}

axXr1.Update();

1번 코드는 주기.shp 파일을 lyr이라는 이름의 레이어로써 추가하는 것이고, 2번 코드는 추가할 레이어의 준비가 완료될때까지 기다리는 코드이며.. 4번 코드에서 readmark.png를 심벌로 사용하기 위해서 img라는 id로 리소스로 추가해 주는 코드입니다. 그리고 6번 코드가 바로.. 가장 핵심적인 코드로 포인트 심벌을 지정해 주는 코드입니다.

사용자 삽입 이미지

위의 그림은 위에서 제시한 코드에 대한 실행 결과로.. 코드는 “SHP Layer” 버튼의 클릭 이벤트에 존재합니다.

경우에 따라서는 SHP 파일처럼 이미 준비된 포인트 데이터가 아닌.. 그때 그때마다 위치가 가변적인 곳에 이미지 심벌을 표시해야하는 경우도 있습니다. 이때는 그래픽 레이어를 사용하며.. 이때의 경우 다음의 예제 코드를 응용하여 기능을 개발할 수 있습니다.

axXr1.Layers.AddGraphicLayer("gl", "");
axXr1.WaitForAllConnections();

IPointGraphicElement gr = new PointGraphicElement();
CoordinateList cl = new CoordinateList();
cl.Add(350, 20);

gr.SetPointList(cl);

axXr1.Resources.AddImageResource("img", "d:/a.png");
gr.PointSymbol.SetImage("img", axXr1.Resources);

axXr1.Layers.GetLayerAsGraphic("gl").AddElement(0, gr as GraphicElement);

axXr1.Update();

그래픽 레이어는 레이어를 구성하는 요소마다 심벌을 지정할 수 있습니다. SHP 파일을 통해서 이미지 심벌을 지정하는 코드처럼 먼저 Resouces의 AddImageResource 매서드를 통해 이미지 리소스를 먼저 추가하고 심벌을 지정해 주면 됩니다. 이미 짐작할 수 있겠지만.. 이미지 리소스는 한번만 추가해주고 재활용할 수 있습니다.

[GIS] DuraMap-Xr 3.0 릴리즈

듀라맵 3.0이 릴리즈되었습니다. 이전 버전을 사용하고 계시는 분들은 최신 버전을 받아 사용하시기 바랍니다. 소소한 버그에서부터 좀더 크고 복잡한 SHP 파일을 읽고 표시할 수 있도록 개선되었습니다. 듀라맵 3.0은 크게 3가지 기능이 추가되었습니다. 3가지 추가된 기능은 다음과 같습니다.

1. 데이터 용량에 제한없이 항공 및 위성 영상 표현

50cm, 25cm급 항공영상에 대해서 전국 범위의 테라급 용량의 항공 및 위성 영상을 듀라맵에서 빠르게 표현할 수 있게 되었습니다.  geotiff 파일로부터 피라미드 시스템을 구축하여 듀라맵에서 사용할 수 있는 포맷(XrR)으로 변경하여 사용할 수 있으며 이렇게 변경된 XrR 파일을 이용해 항공 및 위성영상을 듀라맵에서 읽을 경우 리소스를 거의 사용하지 않으면서 빠르게 화면상에 영상을 표시할 수 있습니다.

사용자 삽입 이미지
듀라맵을 이용하여 대용량의 항공영상을 표시하여 듀라맵의 도형 편집 기능을 이용하여 항공영상을 기반으로 수치지도를 편집할 수 있습니다. 듀라맵에서 사용할 수 있는 항공 및 위성영상에 대한 XrR 포맷으로 변환은 지오서비스로 문의하시기 바랍니다.

2. 공간데이터 서버를 통한 공간 데이터 표현

서버로부터 수치지도와 속성 데이터를 받아 지도를 표시할 수 있는 기능이 추가되었습니다. 기존의 SHP 파일을 통해 레이어를 추가하는 방식과 동일한 매서드인, Layers 객체의 AddShapeMapLayer를 사용하며 서버로부터 레이어를 추가하는 코드 예는 다음과 같습니다.

Xr1.Layers.AddShapeMapLayer("layerName", "XrVtp://abld@geoservice.co.kr:8080/Xr")

위의 코드는 geoservice.co.kr이라는 URL에 존재하는 서버의 8080 포트를 통해 서버가 가지고 있는 레이어중 abld라는 이름의 레이어 리소스를 추가하는 예입니다. 서버를 통해 수치지도를 서비스 받음으로써 수치지도를 네트워크를 통해 여러 사람과 쉽게 공유할 수 있으며 64비트 기반의 서버를 통해 대용량의 수치지도를 관리하고 서비스할 수 있는 기반이 제공됩니다. 듀라맵에 수치지도 및 속성 데이터를 제공하는 서버에 대한 문의는 지오서비스로 하시기 바랍니다.

3. 입체 건물 렌더링 품질 개선

끝으로.. 듀라맵의 장점 중에 하나인 건물의 2.5D 입체 표현에 대해서 그 표현의 품질이 개선되었습니다. 기존에는 건물의 옆면이 모두 동일한 색상으로 표시되어 입체감이 다소 떨어졌으나 3.0 버전에서부터는 태양광의 방향에 따라 옆면의 색상을 다르게 표현하도록 하여 입체감이 향상되었습니다.

사용자 삽입 이미지

[GIS] 자바 API를 이용한 ArcSDE 9.2 연동

환경은 ArcSDE 9.2이고..  ArcSDE 서비스 팩은 설치하지 않았으며 오라클은 10g 버전입니다. 그리고 ArcSDE에 대한 자바 API는 ArcSDE 9.2 서비스팩6에 해당합니다. 참고로 자바 API를 구성하는 jar 파일은 다음과 같으며 모두 반드시.. 필요합니다.

  • jsde92_sdk.jar
  • jpe92_sdk.jar
  • icu4j_3_2.jar

네.. 이제 ArcSDE와 연동하기 위한 자바 API에 대해 살펴보겠습니다.. 먼저 연결에 대한 코드입니다..

import java.util.*;
import com.esri.sde.sdk.client.*;

public class MainEntry {
    @SuppressWarnings("unchecked")
    public static void main(String[] args) {
        SeConnection conn = null;
        String server = "192.168.0.18";
        int service = 5151;
        String database = "";
        String user = "na_gis";
        String password = "na_gis";

        try {
            conn = new SeConnection(server, service, database, user, password);
            Vector layerList = conn.getLayers();
            System.out.println(layerList.size() + " layers existed... ");

            for(int i=0; i

위의 코드는 연결뿐만 아니라 연결된 ArcSDE 서버가 관리하고 있는 레이어 개수와 이름을 화면에 출력합니다.

다음은 레이어를 구성하는 필드 정보를 얻어 오는 코드입니다.

import java.util.*;
import com.esri.sde.sdk.client.*;

public class MainEntry {
    @SuppressWarnings("unchecked")
    public static void main(String[] args) {
        SeConnection conn = null;
        String server = "192.168.0.18";
        int service = 5151;
        String database = "";
        String user = "na_gis";
        String password = "na_gis";

        try {
            conn = new SeConnection(server, service, database, user, password);

            Vector tableList = conn.getTables(SeDefs.SE_SELECT_PRIVILEGE);
            System.out.println(tableList.size() + " table existed... ");
   
            for(int i=0; i

필드 정보는 레이어 개념이 아닌 테이블 개념으로 가져와야 합니다. 레이어는 테이블 + 도형 데이터 쯤으로.. 생각하면 됩니다.

다음은 테이블의 특정 필드(들)의 값을 얻어오는 코드입니다.

import java.util.*;
import com.esri.sde.sdk.client.*;

public class MainEntry {
    @SuppressWarnings("unchecked")
    public static void main(String[] args) {
        SeConnection conn = null;
        String server = "192.168.0.18";
        int service = 5151;
        String database = "";
        String user = "na_gis";
        String password = "na_gis";

        try {
            conn = new SeConnection(server, service, database, user, password);
            Vector tableList = conn.getTables(SeDefs.SE_SELECT_PRIVILEGE);

            SeTable tbl = (SeTable)tableList.get(89);

            SeSqlConstruct sqlConstruct = 
                new SeSqlConstruct(tbl.getQualifiedName());

            String[] cols = new String[1];
            cols[0] = "IST_YMD";

            SeQuery query = new SeQuery(conn, cols, sqlConstruct);
            query.prepareQuery();
            query.execute();

            SeRow row;
            while((row = query.fetch()) != null)
            {
                String value = row.getString(0);
                System.out.println(value);
            }
        } catch(SeException e) {
            e.printStackTrace();
        }
    }
}

얻어온 테이블 리스트(Vector tableList)에서 인덱스 번호 89에 해당하는 테이블의 IST_YMD 필드에 대한 값을 뽑아 내 화면에 표시합니다. 만약 SQL의 Where 구문을 지정하고자 한다면 SeSqlConstruct sqlConstruct 객체의 매서드 setWhere에 구문을 지정하면 됩니다. 예를 들면.. 만약 필드 ADM_NAM에 대해서 '목'으로 시작하는 조건의 경우는 다음과 같습니다.

    ....

    sqlConstruct.setWhere("ADM_NAM LIKE '목%'");

    ....

다음은 레이어에서 도형에 대한 좌표를 표시하는 코드입니다.

import java.util.*;
import com.esri.sde.sdk.client.*;

public class MainEntry {
    @SuppressWarnings("unchecked")
    public static void main(String[] args) {
        SeConnection conn = null;
        String server = "192.168.0.18";
        int service = 5151;
        String database = "";
        String user = "na_gis";
        String password = "na_gis";

        try {
            conn = new SeConnection(server, service, database, user, password);
            Vector layerList = conn.getLayers();

            SeLayer lyr = (SeLayer)layerList.get(4);
            SeSqlConstruct sqlConstruct =
                new SeSqlConstruct(lyr.getQualifiedName());

            String[] cols = new String[3];
            cols[0] = "BD_NM";
            cols[1] = "BD_MGT_SN";
            cols[2] = "SHAPE";

            SeQuery query = new SeQuery(conn, cols, sqlConstruct);
            query.prepareQuery();
            query.execute();

            SeRow row;
            while((row = query.fetch()) != null)
            {
                SeShape shp = row.getShape(2);

                double coordinates[][][] = shp.getAllCoords();
                System.out.println("coordinates.length = " + coordinates.length);
                System.out.println("coordinates[0].length = " + coordinates[0].length);
                System.out.println("coordinates[0][0].length = " + coordinates[0][0].length);

                double partCoords[] = coordinates[0][0];
                for(int i=0; i

얻어온 레이어 리스트(Vector layerList)에서 인덱스 4번에 해당하는 레이어에 대해서 필드 BD_NM, BD_MGT_SN 그리고 좌표에 대한 값이 담긴 SHPAE 필드를 지정해 쿼리를 실행합니다. 위의 코드는 지정된 레이어에 대한 모든 레코드를 읽어 오게 됩니다. 여기에 제한을 두어 지정된 MBR에 포함되거나 교차하는 레코드만을 얻어오는 코드는 다음과 같습니다.

import java.util.*;
import com.esri.sde.sdk.client.*; public class MainEntry {
    @SuppressWarnings("unchecked")
    public static void main(String[] args) {
        SeConnection conn = null;
        String server = "192.168.0.18";
        int service = 5151;
        String database = "";
        String user = "na_gis";
        String password = "na_gis";

        try {
            conn = new SeConnection(server, service, database, user, password);
            Vector layerList = conn.getLayers();

            SeLayer lyr = (SeLayer)layerList.get(4);
            SeSqlConstruct sqlConstruct = 
                new SeSqlConstruct(lyr.getQualifiedName());

            String[] cols = new String[3];
            cols[0] = "BD_NM";
            cols[1] = "BD_MGT_SN";
            cols[2] = "SHAPE";

            SeQuery query = new SeQuery(conn, cols, sqlConstruct);
            query.prepareQuery();

            // Add New Codes
            SeShape filterShape = new SeShape(lyr.getCoordRef());
            SeExtent ext = new SeExtent(148574, 146403, 148587, 146412);
            filterShape.generateRectangle(ext); 

            SeShapeFilter filter = new SeShapeFilter(lyr.getQualifiedName(), 
                lyr.getSpatialColumn(), filterShape, SeShapeFilter.METHOD_AI);

            SeShapeFilter[] filters = new SeShapeFilter[1];
            filters[0] = filter;

            query.setSpatialConstraints(SeQuery.SE_SPATIAL_FIRST, 
                false, filters);
            // At Here!

            query.execute();
   
            SeRow row;
            while((row = query.fetch()) != null)
            {
                SeShape shp = row.getShape(2);

                double coordinates[][][] = shp.getAllCoords();
                System.out.println(
                    "coordinates.length = " + coordinates.length);
                System.out.println(
                    "coordinates[0].length = " + coordinates[0].length);
                System.out.println(
                    "coordinates[0][0].length = " + coordinates[0][0].length);

                double partCoords[] = coordinates[0][0];
                for(int i=0; i

코드 번호 29~42에 해당하는 새로운 코드가 추가되었습니다.

완전하 SELECT 구문에 대한 SQL 문으로 Row을 읽어 오는 코드는 다음과 같습니다.

import java.util.*;   
import com.esri.sde.sdk.client.*; 

public class TestArcSDEQuery {
    public static void main(String[] args) {
        SeConnection conn = null;   
        String server = "222.237.78.208";   
        int service = 5151;   
        String database = "";   
        String user = "na_gis";   
        String password = "na_gis";   

        try {
            conn = new SeConnection(server, service, database, user, password); 
         
            SeQuery query = new SeQuery(conn);   
            query.prepareSql("SELECT * FROM CC_META");
            query.execute();   
  
            SeRow row;
            while((row = query.fetch()) != null)   
            {
                int cntFields = query.getNumColumns();
                SeColumnDefinition[] tableDef = row.getColumns();
             
                for(int iField=0; iField
	

[GIS] DuraMap-Xr, 특정 레이어를 최상단으로 이동시키기

특정 레이어의 이름을 gl이라고 할때.. 이 gl이라는 이름의 레이어를 최상단에 위치시키는 코드입니다.

String lastLayerName = XrMain.Layers.GetLayerName(XrMain.Layers.Count - 1);
if(lastLayerName.CompareTo("gl") != 0)
{
    XrMain.Layers.MoveLayer("gl", lastLayerName, false);
    XrMain.Update();
}

가장 먼저 최상단에 위치하는 레이어를 구하고.. 최상단의 레이어가 gl이 아닐 경우에 4번 코드의 MoveLayer 매서드를 통해 최상단으로 이동시켜 주면 됩니다. MoveLayer의 세번째 인자값을 false로 주었는데.. 이동시킬 위치를 기준으로 앞으로 이동시킬 것인지.. 뒤로 이동시킬 것인지에 대한 값입니다. 여기서는 false로 주었으므로 뒤쪽으로 이동시킨다는 의미입니다.

잠시 듀라맵을 이용하여 작업하던차에… 정리해 봅니다..