IDL/Mapping

ESRI Shapefile의 활용 및 해독에 관하여 (Part 2)

이상우_IDL 2018. 3. 27. 10:30
728x90

(지난 회 내용에서 바로 이어집니다)


앞서 지난 회에서는 예제 Shape 파일에 대한 IDLffShape 클래스의 객체를 다음과 같이 obj라는 이름으로 만들어놓고, 이 객체를 통하여 Shape 파일 내부에 담긴 정보들을 추출해보기 시작했습니다.


obj = OBJ_NEW('IDLffShape', file)


그런 다음, 가장 먼저 Entity가 249개이고 Attribute가 17개가 존재한다는 것을 다음과 같은 방법을 사용하여 확인했습니다.


obj -> GetProperty, N_ENTITIES=n_ent, N_ATTRIBUTES=n_att

PRINT, n_ent, n_att


그러면 17개가 존재하고 있는 Attribute들은 각각 어떤 종류의 정보인지 확인해보겠습니다. 이를 위하여 다음과 같은 방식으로 obj 객체로부터 ATTRIBUTE_INFO라는 속성에 해당되는 정보를 att_info라는 이름으로 추출해 봅시다.


obj -> GetProperty, ATTRIBUTE_INFO=att_info

HELP, att_info


HELP 명령을 사용하여 att_info에 관하여 확인해보면 실제로 다음과 같은 내용이 출력됩니다.


ATT_INFO        STRUCT    = -> IDL_SHAPE_ATTRIBUTE Array[17]


이 내용에 의하면 att_info는 17개의 구조체(Structure)들로 구성된 배열입니다. 그러면 여기서 "구조체"가 대체 무엇인가에 대한 의문이당연히 나올 수 있습니다. 사실 이걸 제대로 얘기하려면 좀 복잡한데, 그냥 자료형(Data Type)에 상관없이 여러 종류의 정보들을 하나의묶음으로 만든 데이터 형태 정도라고 해두겠습니다. 여기서는 att_info라는 구조체 배열의 원소들은 각각 하나의 구조체이며, 이 구조체 내에 몇가지 종류의 항목들(items)이 포함되어 있다고 보면 됩니다. 실제로는 4개의 항목들이 포함되어 있는데 그 중에서도 가장 중요한 것들이 바로 Name과 Type입니다. 일단 Name이라는 항목을 먼저 주목해봅시다. 참고로 구조체 내의 특정 항목을 접근(access)하려면 다음과 같이 dot(.) 기호를 사용합니다.


HELP, att_info.name


그리고 출력된 내용은 다음과 같습니다.


<expression>    STRING    = Array[17]


이와 같이 att_info는 구조체이지만 att_info.name은 문자값 배열입니다. 이 배열에 포함된 문자값들을 PRINT로 출력해봅시다.


PRINT, att_info.name


출력된 내용은 다음과 같습니다.


FIPS_CNTRY GMI_CNTRY ISO_2DIGIT ISO_3DIGIT ISO_NUM CNTRY_NAME LONG_NAME ISOSHRTNAM UNSHRTNAM LOCSHRTNAM LOCLNGNAM STATUS

POP2007 SQKM SQMI LAND_SQKM COLORMAP


이와 같이 총 17개의 문자값들이 출력되어 있는데요. 이들 중 6번째에 있는 "CNTRY_NAME"이라는 항목을 주목해야 합니다. 바로 국가 이름에 해당되는 정보입니다. 즉 17개의 attribute들 중 국가명에 해당되는 정보는 6번째 attribute에 해당됨을 알 수 있습니다. 따라서 일단 다음과 같이 정리해 볼 수 있습니다.


1) 총 249개의 entity들이 있다.

2) 각 entity마다 17종의 attribute들이 있다.

3) 17종의 attribute들 중 국가명에 해당되는 문자 정보가 6번째 attribute로 존재한다.


이 얘기는 결국 각 entity마다 6번째 attribute에 국가명이 있다는 뜻입니다. 그러면 249개의 entity들 중 임의로 하나의 entity를 골라서 국가명을 확인해보겠습니다. 80번째 entity를 선택해봅시다. 80번째라면 인덱스로는 79가 됩니다. 이 과정은 다음과 같습니다.


j = 79

att = obj->GetAttributes(j)

HELP, att


여기서는 obj 내에 존재하는 249개의 entity들 중 80번째 entity 하나에 대하여 그 안에 포함된 attribute들을 att라는 이름으로 추출하였습니다. 그리고 이 과정에서는 GetAttribute라고 하는 명령이 사용되었습니다. 사실 att는 구조체의 형태로 추출됩니다. 물론 그 안에는 앞서 언급했던 것과 같이 17종의 항목들(items)이 포함되어 있습니다. HELP에 의하여 출력된 내용을 보면 다음과 같습니다.


** Structure <29f5ac58>, 17 tags, length=216, data length=208, refs=2:

   ATTRIBUTE_0     STRING    'MO'

   ATTRIBUTE_1     STRING    'MAR'

   ATTRIBUTE_2     STRING    'MA'

   ATTRIBUTE_3     STRING    'MAR'

   ATTRIBUTE_4     LONG               504

   ATTRIBUTE_5     STRING    'Morocco'

   ATTRIBUTE_6     STRING    'Kingdom of Morocco'

   ATTRIBUTE_7     STRING    'Morocco'

   ATTRIBUTE_8     STRING    'Morocco'

   ATTRIBUTE_9     STRING    'Al Maghrib'

   ATTRIBUTE_10    STRING    'Al Mamlakah al Maghribiyah'

   ATTRIBUTE_11    STRING    'UN Member State'

   ATTRIBUTE_12    LONG          33757175

   ATTRIBUTE_13    DOUBLE           406452.22

   ATTRIBUTE_14    DOUBLE           156931.19

   ATTRIBUTE_15    LONG            446300

   ATTRIBUTE_16    LONG                 5


여기서 국가명에 해당되는 6번째 항목을 보면 'Morocco'라고 되어 있고, 그 항목의 태그(Tag)는 ATTRIBUTE_5라고 되어 있습니다. 따라서 위와 같이 HELP로 모든 정보들이 출력되게 하기보다는, 이 항목만 뽑아내서 다음과 같이 출력하도록 하는 것이 더 간략하고 알아보기가 더 쉬울 것 같습니다.


PRINT, j, ' : ', att.ATTRIBUTE_5


그러면 위와 같이 장황한 내용 대신 다음과 같이 필요한 정보만 출력됩니다.


      79 : Morocco


어쨌든 249개의 entity들 중 80번째 entity는 모로코(Morocco)라는 국가에 해당됨을 알 수 있습니다. 그러면 우리나라는 대체 어디에 있을까요? 탐색을 위하여 다음과 같이 그냥 반복문을 사용하여 249개 모든 entity들에 대하여 위와 같은 요령으로 국가명들을 모두 출력하게 하는 방법을 시도해봅시다.


FOR j = 0, n_ent-1 DO BEGIN

  att = obj->GetAttributes(j)

  PRINT, j, ' : ', att.ATTRIBUTE_5

ENDFOR


출력된 결과는 총 249줄이나 되므로, 제가 여기서 우리나라가 있는 부분만 제시해보면 다음과 같습니다.


     183 : Brunei

     184 : China

     185 : Japan

     186 : North Korea

     187 : Palau

     188 : Philippines

     189 : South Korea

     190 : Cambodia


여기서 Korea라는 명칭이 들어간 항목을 찾아보면 두개가 나오는데, 인덱스로는 남한(South Korea)이 189이고 북한(North Korea)이 186이 됩니다. 그 외에 일본이 185이고 중국이 184입니다. 따라서 이전 게시물에서 지도로 표출했던 아시아 지역 내 주요 국가들에 대한 entity 인덱스가 얼마인지를 일단 확인해두었습니다.


여기까지는 249개의 entity들에 대한 국가명 정보를 확인하여 관심 국가들의 인덱스 번호를 확인하는 과정이었습니다. 결과만 놓고 보면 단순해 보이긴하지만, entity와 attribute 정보의 저장 방식에 대한 이해가 필요했기 때문에 다소 설명이 길어졌습니다. 그러면 지금부터는 특정 국가에 대한 국경선 데이터를 추출하는 작업을 진행해보고자 합니다. 앞선 작업이 주로 attribute 위주였다고 본다면, 지금부터의 작업은 entity 위주가 됩니다. 왜냐하면 국경선 데이터는 entity 자체로부터 추출해야 하기 때문입니다. 그러면 앞서 알아낸 정보를 바탕으로 남한 지역에 해당되는 국경선 정보를 추출해봅시다. 그 과정은 다음과 같습니다.


j = 189

ent = obj->GetEntity(j)

HELP, ent


앞서 attribute 정보를 얻기 위하여 GetAttributes 명령을 사용했었는데, entity 정보를 얻기 위해서는 위와 같이 GetEntity라는 명령을 사용해야 합니다. 이 명령으로 추출된 entity 정보를 ent라는 이름으로 저장하였는데, HELP에 의하여 출력된 ent에 관한 정보는 다음과 같습니다.


** Structure IDL_SHAPE_ENTITY, 10 tags, length=104, data length=100:

   SHAPE_TYPE      LONG                 5

   ISHAPE          LONG               189

   BOUNDS          DOUBLE    Array[8]

   N_VERTICES      LONG               502

   VERTICES        POINTER   <PtrHeapVar234227>

   MEASURE         POINTER   <NullPointer>

   N_PARTS         LONG                 7

   PARTS           POINTER   <PtrHeapVar234228>

   PART_TYPES      POINTER   <NullPointer>

   ATTRIBUTES      POINTER   <NullPointer>


사실 ent 역시 구조체(Structure)의 형태로 얻어집니다. 앞서 attribute의 경우와 유사합니다. 다만 entity 구조체의 경우는 보시는 것처럼 내부적으로 총 10개의 항목들(items)이 존재하는데, 우리에게 실질적으로 필요한 정보는 바로  N_VERTICES와 VERTICES입니다. 먼저 N_VERTICES 정보는 위에서도 확인이 가능하고 다음과 같이 따로 출력할 수도 있습니다.


PRINT, ent.N_Vertices


물론 어차피 어떤 식으로 보든 502라는 숫자인 것으로 확인이 됩니다. 그렇다면 이 숫자의 의미는 무엇일까요? 바로 국경선의 윤곽을 구성하는 지점 포인트들(Vertices)의 갯수를 의미합니다. 실제로 이러한 지점 포인트들은 경도 및 위도 좌표값으로 존재합니다. 이렇게 경위도 좌표를 갖는 지점들의 갯수가 502개라는 의미입니다. 당연히 큰 나라일수록 이 값이 더 클 것이고 작은 나라일수록 그 값은 적을것입니다 (참고로 중국의 경우 4886개이고 일본은 2485개입니다). 그러면 이제는 우리에게 실제로 필요한 국경선 지점 포인트들의 좌표값 데이터를 추출해봅시다. 위의 내용에서 바로 VERTICES라는 항목이 여기에 해당됩니다. 따라서 ent.Vertices가 될텐데, 일단 다음과 같이 HELP를 사용하여 먼저 확인해봅시다.


HELP, ent.Vertices


그러면 다음과 같은 정보가 출력됩니다.


<expression>    POINTER   = <PtrHeapVar266093>


사실 당초의 기대로는 좌표값들이 담긴 배열같은 것이 되어야 할 것 같은데, 대체 이 내용은 무슨 얘기일까요? 이 부분이 좀 당황스러운데 사실 이것은 포인터(Pointer)라고 부르는 데이터 형태에 해당됩니다. 다만 여기서 "포인터(Pointer)란 과연 무엇인가"에 관해 얘기하자면 내용도 복잡해지고 방향도 옆길로 샐 수 밖에 없습니다. 따라서, 그냥 우리가 원하는 형태의 데이터를 얻으려면 다음과 같은 문법을 사용하면 된다는 정도만 언급해도 충분할 것 같습니다.


vts = *(ent.Vertices)

HELP, vts


이렇게 얻어진 vts에 대하여 HELP를 적용한 출력 결과를 보면 다음과 같습니다.


VTS             DOUBLE    = Array[2, 502]


즉 2배 정밀도(Double Precision) 실수들이 2x502의 형태로 구성된 2차원 배열임을 알 수 있습니다. PRINT 명령을 사용하여 vts의 값들을 실제로 출력해보면 대략 다음과 같은 형태로 총 502줄이 출력되는데, 이 값들이 바로 각 포인트의 경도 및 위도 좌표값입니다. 여기서는 처음 5줄만 제시하였습니다.


       126.86922       36.060600

       126.85910       36.055545

       126.73947       36.005809

       126.68877       36.001364

       126.54442       36.136409


따라서 경도값들로만 구성된 1차원 배열 및 위도값들로만 구성된 1차원 배열을 다음과 같이 vts로부터 각각 만들 수 있습니다.


lons = REFORM(vts[0, *])

lats = REFORM(vts[1, *])


그러면 이렇게 따로 만든 lons, lats를 활용할 수 있게 됩니다. 예를 들면, lons와 lats를 POLYGON 또는 POLYLINE 함수에 이용하면 이전 게시물에서 그렸던 지도 위에 남한 지역 윤곽선만 따로 그려보는 것도 가능합니다. 지난 게시물에서 지도 표출에 사용되었던 예제 코드의 뒷부분에 내용을 약간 추가하면 되겠지만, 그냥 코드의 내용을 모두 적어보면 다음과 같습니다.


limit = [10, 70, 60, 150]

win = WINDOW(DIMENSIONS=[800, 500], /NO_TOOLBAR)

m = MAP('Geographic', LIMIT=limit, FILL_COLOR='light blue', $

  MARGIN=0.1, CLIP=0, /CURRENT)

mc = MAPCONTINENTS(file, FILL_COLOR='gold')

m.MapGrid.LABEL_POSITION = 0

m.MapGrid.LINESTYLE = 1

lons = REFORM(vts[0, *])

lats = REFORM(vts[1, *])

plg = POLYLINE(lons, lats, COLOR='red', THICK=3, /DATA)


맨 아랫 부분에서 POLYLINE 함수를 추가적으로 사용하여 남한 지역의 윤곽선만 따로 굵은 선으로 덧그려본 것입니다. 그 결과는 다음 그림과 같습니다.



그런데 그림이 뭔가 좀 이상하다는 느낌이 듭니다. 좀 더 자세히 보기 위하여 지도의 경위도 범위를 한반도 부근으로 한정지어봅시다. 이를 위해서 맨 윗줄에서 limit가 정의된 부분만 다음과 같이 수정해보았습니다.


limit = [33, 124, 43, 132]


이렇게 경위도 범위를 수정하여 표출된 모습은 다음 그림과 같습니다.



이렇게 보면 뭔가 이상하다는 것이 더욱 확연하게 드러납니다. 사실 이 그림을 자세히 보면, 전반적으로 남한 지역의 윤곽선이 제대로 반영된 것은 맞습니다. 하지만 포인트들을 이어주는데 있어서 부자연스러운 부분들이 여기저기 눈에 띕니다. 사실 앞서 entity로부터 추출했던 502개의 포인트들의 좌표 데이터에는 남한 지역의 본체(?)뿐 아니라 섬 지역들까지도 모두 한꺼번에 포함되어 있습니다. 그런데 502개의 점들을 무조건 순서대로 다 이어주다보니 이렇게 군데군데 이상한 부분들이 나타나게 된 것입니다. 따라서 남한 지역에 속하는 모든 영역들을 따로따로 분리해서 묘사해야 하는데, 이를 위해서는 각 부분영역들(Parts)이 어떻게 구분되는가에 관한 정보가 추가적으로 필요합니다.


다만 오늘은 내용이 너무 길어져서 여기까지만 하고, 부분영역에 대한 구분 및 표출 방법은 다음 회차에서 이어서 진행하도록 하겠습니다.

LIST