안녕하세요.


이번에는 지난 번에 이어서 포인트, 아크, 폴리곤, 그리고 멀티포인트를 읽어들이는 방법에 대해 알아보도록 하겠습니다.



2.1 Point 읽어들이기


포인트는 가장 간단한 사상(feature) 중의 하나입니다. 포인트는 x, y 로만 이루어져 있는 단순한 구조체에 불과합니다. 그래서 읽어들이기도 매우 쉽습니다.


아래와 같이 레코드의 개수만큼 돌면서 값을 가져오면 됩니다.


m_pSHPPoints = new GeoPoint[m_nRecords];

for(i = 0; i < m_nRecords; ++i)
{
      fseek(fpSHX, 100 + i * 8, SEEK_SET);
      fread(&tmp, 4, 1, fpSHX);
      SwapWord(4, &tmp);
      tmp=tmp * 2;

      fseek(fpSHP, tmp + 12, SEEK_SET);
      fread((GeoPoint*)&m_pSHPPoints[i], 16, 1, fpSHP);
}
 

일단 포인트 레코드의 개수만큼 저장공간을 동적으로 할당받습니다.

m_pSHPPoints = new GeoPoint[m_nRecords];


그리고서 shx 파일을 이용하여 각 레코드의 위치를 포인팅하는 것입니다. 저번 시간에 말씀드렸다시피 shx는 shp 파일을 포인팅해주는 일종의 인덱스 파일입니다.


shx 파일은 헤더부분의 100byte 이후부터 8byte씩이 각각 shp에 있는 도형정보의 주소(address)와 레코드 크기를 담고 있다고 했습니다. 그래서 100byte 이후부터 8byte씩 증가하는 것입니다.


여기서 shx의 레코드 중 사실 중요한 부분은 첫 4byte입니다. 그 놈이 바로 실제 shp에 있는 도형정보의 주소(address)를 담고 있기 때문이죠. 그래서 4byte만 읽어들이는 것입니다.      

fseek(fpSHX, 100 + i * 8, SEEK_SET);
fread(&tmp, 4, 1, fpSHX);

     

그런데, 이 shx에 있는 레코드 정보는 모두 Big Endian입니다. 그래서 이 놈을 SwapWord라는 함수를 이용하여 Byte Ordering을 바꿔줍니다. 그리고서 다시 2를 곱했습니다. 왜일까요? 바로 shx 파일의 레코드에 있는 offset의 단위가 16byte인 word이기 때문입니다. 16byte를 8byte로 바꾸려면 당연히 2를 곱해야겠지요.

SwapWord(4, &tmp);
tmp=tmp * 2;

 

이렇게 해서 shp파일의 주소를 받아왔습니다. 그런데, 바로 그 위치에서 값을 읽어들이는 것이 아니라 다시 12byte를 앞으로 전진시킨 후 포인트값을 읽어오고 있습니다. 이는 앞의 12byte가 별 의미없는 값들이기 때문입니다.


저번 강의때 말씀드렸다시피 shp는 레코드 헤더 + 레코드 컨텐츠로 구성되어 있으며, 이 레코드헤더가 8byte를 차지한다고 했습니다. 하지만 이 레코드 헤더는 거의 사용하지 않는다고 했었죠. 그래서 일단 8byte를 지나친 것이구요. 레코드 중 첫 4byte는 SHP의 Type을 의미하기에 지나친 것입니다. 중복된 정보를 읽어들이는 것은 속도만 떨어뜨리는 결과를 낳기 때문입니다.


      fseek(fpSHP, tmp + 12, SEEK_SET);
      fread((GeoPoint*)&m_pSHPPoints[i], 16, 1, fpSHP);

 

이렇게 해서 포인트를 읽어들이는 루틴을 마쳤습니다.


2.2 아크, 폴리곤 읽어들이기


이번에는 아크와 폴리곤을 읽어들이는 루틴에 대해 알아보도록 합시다.


실제 아크와 폴리곤은 그 구조상 거의 같습니다. 다만 아크일 경우는 닫혀 있지 않으며, 폴리곤은 닫혀있는 도형입니다.


이것도 역시 레코드 수만큼 돌면서 읽어오면 됩니다. 포인트와는 차이가 있는데, 살펴보도록 합시다.


m_pSHPPolyObjects = new PolyObject[m_nRecords];

for(i=0;i<m_nRecords;++i)
{
   fseek(fpSHX, 100+i*8, SEEK_SET);
   fread(&tmp, 4, 1, fpSHX);
   SwapWord(4, &tmp);
   tmp=tmp*2;

   fseek(fpSHP, tmp+12, SEEK_SET);
   fread((MBR*)&m_pSHPPolyObjects[i].m_MBR, 32, 1, fpSHP); // get MBR
   fread(&m_pSHPPolyObjects[i].m_nNumParts, 4, 1, fpSHP);   // get parts number
   fread(&m_pSHPPolyObjects[i].m_nNumPoints, 4, 1, fpSHP);  // get points number

   m_pSHPPolyObjects[i].m_pParts = new int[m_pSHPPolyObjects[i].m_nNumParts];
   m_pSHPPolyObjects[i].m_pPoints = new GeoPoint[m_pSHPPolyObjects[i].m_nNumPoints];

   for(j=0;j<m_pSHPPolyObjects[i].m_nNumParts;++j)
    fread(&m_pSHPPolyObjects[i].m_pParts[j], 4, 1, fpSHP);

   for(j=0;j<m_pSHPPolyObjects[i].m_nNumPoints;++j)
    fread(&m_pSHPPolyObjects[i].m_pPoints[j], 16, 1, fpSHP);
}


원리는 포인트를 읽어들이는 것과 동일합니다. 따라서 shx를 이용하여 shp 파일의 주소를 포인팅한 뒤, 그곳에서부터 아크나 폴리곤의 구조체를 읽어들이는 것입니다. 아크나 폴리곤의 구조체는 다음과 같습니다.(저번에 이미 보여 드렸습니다만 학습에는 역시 반복이 최고입니다.)

PolyLine or Polygon { Double[4] Box // Bounding Box Integer NumParts // Number of Parts Integer NumPoints // Total Number of Points Integer[NumParts] Parts // Index to First Point in Part Point[NumPoints] Points // Points for All Parts }

저의 프로그램에서는 다음과 같이 정의되어 있습니다.


/* -------------------------------------------------------------------- */
/*      PoliLine & Polygon                                              */
/* -------------------------------------------------------------------- */
typedef struct

{
    MBR   m_MBR;
    int       m_nNumParts;
    int       m_nNumPoints;
    int*     m_pParts;
    GeoPoint* m_pPoints;
}PolyObject


이 구조체를 이용하여 읽어들이는 것입니다. 대부분의 코드는 이해가 되실 것입니다. 다만 파트의 개수와 포인트의 개수를 읽어들인 뒤, 다시 동적으로 메모리를 할당하여 파트와 포인트를 읽어들이는 코드를 주의해 보시기 바랍니다.


   m_pSHPPolyObjects[i].m_pParts = new int[m_pSHPPolyObjects[i].m_nNumParts];
   m_pSHPPolyObjects[i].m_pPoints = new GeoPoint[m_pSHPPolyObjects[i].m_nNumPoints];

   for(j=0;j<m_pSHPPolyObjects[i].m_nNumParts;++j)
        fread(&m_pSHPPolyObjects[i].m_pParts[j], 4, 1, fpSHP);

   for(j=0;j<m_pSHPPolyObjects[i].m_nNumPoints;++j)
        fread(&m_pSHPPolyObjects[i].m_pPoints[j], 16, 1, fpSHP);

 

이와 같이 함으로써 차례로 아크나 폴리곤을 읽어들이는 것입니다. 포인터를 많이 사용하죠? 포인터 많이 사용하면 나중에 열라 머리아플 때가 있습니다. 어디서 new로 메모리를 잡았는데, 해제 안해주면 바로 메모리 릭이 나거나, 잘못하면 프로그램 죽거나 합니다.


이번 버젼이 아닌 다음 버젼에서는 초심자를 위해 포인터를 될 수 있으면 사용하지 않고서 단순화시켜서 짜보도록 합시다. 여하간..


이렇게 해서 아크나 폴리곤을 읽어들였습니다.



2.3 멀티포인트 읽어들이기


멀티포인트는 여러개의 포인트가 하나의 개체를 이루는 형태입니다. 포인트가 여러개이니 당연히 MBR값을 가지고 있으며, 한 개체에 있는 포인트의 개수가 몇개인지에 대한 정보도 가지고 있답니다.

 

MultiPoint
{
    Double[4] Box   // Bounding Box
    Integer NumPoints  // Number of Points
    Point[NumPoints] Points // The Points in the Set
}


라고 SHP 포맷 사양서에 정의되어 있습니다. 이를 수용하는 컨테이너를 저는 그냥 PolyObject를 사용했습니다.


m_pSHPPolyObjects = new PolyObject[m_nRecords];

for(i=0;i<m_nRecords;++i)
{
   fseek(fpSHX, 100+i*8, SEEK_SET);
   fread(&tmp, 4, 1, fpSHX);
   SwapWord(4, &tmp);
   tmp=tmp*2;

   fseek(fpSHP, tmp+12, SEEK_SET);
   fread((MBR*)&m_pSHPPolyObjects[i].m_MBR, 32, 1, fpSHP); // MBR
   fread(&m_pSHPPolyObjects[i].m_nNumPoints, 4, 1, fpSHP); // get points number

   m_pSHPPolyObjects[i].m_pPoints = new GeoPoint[m_pSHPPolyObjects[i].m_nNumPoints];

   for(j=0;j<m_pSHPPolyObjects[i].m_nNumPoints;++j)
    fread(&m_pSHPPolyObjects[i].m_pPoints[j], 16, 1, fpSHP);
}
 

멀티포인트는 사실 아크나 폴리곤을 이해하시면 그냥 쉽게 이해되는 구조입니다.

shx를 이용해 shp 레코드의 위치를 포인팅한 후 하나의 레코드씩 읽어들이는 식입니다.

 

다만 포인트와 달리 여러 포인트가 하나의 개체를 이루므로 포인트의 개수에 맞게 메모리를 동적으로 할당해서 포인트를 받아야 하는 차이가 있을 뿐입니다. 오히려 아크나 폴리곤과 읽는 루틴이 유사하죠.


포인터를 많이 쓰니 초보자는 조금 어렵게 느껴질 수도 있다고 봅니다.


   fread(&m_pSHPPolyObjects[i].m_pPoints[j], 16, 1, fpSHP);

 

위와 같은 코드들이 그렇겠죠. 조금만 더 이해를 하시면 됩니다. 나중에 포인터나 동적 메모리할당을 안쓰는 방법으로 하기 전에는 일단 이렇게 하도록 합시다.

이렇게 해서 포인트, 아크, 폴리곤, 멀티포인트에 대한 읽어들이기를 알아 보았습니다.


다음에는 이렇게 읽어들인 레코드를 화면에 뿌리는 것에 대해 알아보도록 하겠습니다.

 

2001년 11월 19일

Posted by 뚜와띠엔
,