*** SHP Format 분석 ***


1. SHP Foramt(여기서 SHP 포맷이라 함은 shp, shx, dbf로 이루어진 전체 포맷을 의미합니다.)

SHP 포맷은 ESRI ArcView의 Native Format으로서, 최근에 2차원 벡터 데이터의 교환을 위한 사실상의 표준으로 사용될 만큼 광범위한 사용자층을 확보하고 있는 GIS 포맷입니다.


SHP 포맷은 도형정보와 속성정보를 따로 관리하는 전형적인 분리형 GIS 포맷입니다.


SHP 포맷은 초기에는 Point, Arc, Polygon, MutliPoint 등의 2차원 사상(feature)만을 지원했으나, 계속적인 발전을 통해 최근에는 3차원 사상(feature)도 지원할 수 있도록 개선되었습니다.


본 강좌에서는 전형적인 2차원 사상인 Point, Arc, Polygon, MutliPoint에 대해서만 다루도록 하겠습니다.

SHP 포맷은 다음과 같은 3개의 파일로 크게 이루어져 있습니다.


xxxx.shp
xxxx.shx
xxxx.dbf


.shp : 이 파일은 도형정보를 담고 있는 파일입니다.
.shx : 이 파일은 일종의 인덱스 파일로서 shx를 이용하여 프로그래머는 쉽게 shp에 담겨있는 도형정보의 위치를 얻을 수 있습니다.
.dbf : 이 파일은 shp에 있는 도형정보에 대한 속성정보를 담고 있습니다.


SHP 포맷은 도형정보와 속성정보의 연결을 위해 별도의 연결고리를 두지 않고 있습니다.(사실 전혀 없다고 하기는 그렇습니다만 여하간..)


shp 파일에 저장되어 있는 도형정보의 순서에 맞게, dbf 파일에 그 속성 정보가 저장되어 있습니다. 즉, 만약 shp 파일에 5번째로 기록되어 있는 도형이 있다면, 이에 관한 속성정보는 dbf 파일에 5번째로 기록되어 있는 식입니다.

dbf 파일은 dBase IV 구조로서 쉽게 접근할 수 있도록 되어 있습니다.



1.1 shp 파일


shp 파일은 다음과 같이 구성되어 있습니다.(shp 사양서 참조)


----------------------------

File Header(100Byte)
Record Header(8Byte)
Record Contents(가변Byte)
Record Header(8Byte)
Record Contents(가변Byte)
.
.
.
.
Record Header(8Byte)
Record Contents(가변Byte)

------------------------



가) File Header


여기에서 File Header는 다시 다음과 같이 구성되어 있습니다.(역시 shp 사양서 참조)


Position   Field        Value       Type    Byte Order
------------------------------------------------------
Byte 0     File Code    9994        Integer Big
Byte 4     Unused       0           Integer Big
Byte 8     Unused       0           Integer Big
Byte 12    Unused       0           Integer Big
Byte 16    Unused       0           Integer Big
Byte 20    Unused       0           Integer Big
Byte 24    File Length  File Length Integer Big
Byte 28    Version      1000        Integer Little
Byte 32    Shape Type   Shape Type  Integer Little
Byte 36    Bounding Box Xmin        Double  Little
Byte 44    Bounding Box Ymin        Double  Little
Byte 52    Bounding Box Xmax        Double  Little
Byte 60    Bounding Box Ymax        Double  Little
Byte 68*   Bounding Box Zmin        Double  Little
Byte 76*   Bounding Box Zmax        Double  Little
Byte 84*   Bounding Box Mmin        Double  Little
Byte 92*   Bounding Box Mmax        Double  Little
-----------------------------------------------------
* Unused, with value 0.0, if not Measured or Z type



위의 헤더에서 32byte부터 시작하는 Shape Type는 다음과 같은 값으로 되어 있습니다.
NULL 0
POINT 1
ARC 3
POLYGON 5
MULTIPOINT 8
POINTZ 11
ARCZ 13
POLYGONZ 15
MULTIPOINTZ 18
POINTM 21
ARCM 23
POLYGONM 25
MULTIPOINTM 28
MULTIPATCH 31



SHP 포맷은 한 파일에 하나의 SHP Type만을 수용하는 구조입니다. 따라서 Point와 라인이 같은 파일에 있을 수가 없답니다.


사실 이와 같이 되는게 말 그래도 Theme의 의미가 있는 것이기는 합니다만 사용하는 입장에서는 불편한 점이 있는 것도 사실입니다.


위의 헤더 중 Bounding Box란 이 SHP 파일의 MBR(Minumub Bounding Rectangle)을 의미합니다.



나) Record Header


Record Header는 다음과 같이 구성되어 있습니다.(SHP 사양서 참조)


Position   Field           Value            Type     Order
----------------------------------------------------------
Byte 0     Record Number   Record Number    Integer  Big
Byte 4     Content Length   Content Length  Integer  Big
----------------------------------------------------------

그러나 사실 Record Header는 거의 사용하지 않습니다. 왜냐면 shx 파일을 이용하여 shp를 포인팅할 수 있기 때문입니다. 여기에서 Record Number가 중요할 수 있으나, 이 또한 0부터 순차적으로 증가하는 값이기 때문에 쉽게 처리할 수 있습니다.



다) Record Contents


Record Contents는 실제 점, 선, 면이 기록되어 있는 부분입니다.

이 부분은 SHP Type에 따라 달라집니다.


 - 포인트
Position   Field          Value      Type     Number    Order
-------------------------------------------------------------
Byte 0     Shape Type      1         Integer   1       Little
Byte 4         X           X         Double    1       Little
Byte 12        Y           Y         Double    1       Little
-------------------------------------------------------------

포인트는 그냥  double로 된 x, y로 구성되어 있는 것입니다.

Point
{
 double x;
 double y;
}

사실 이 포인트 컨턴츠에서도 Byte0 부분의 Shape Type은 의미가 없습니다.
왜냐면 이미 File Header 부분에서 SHP Type이 무엇인지 알려주었으며,
SHP 포맷은 한 파일에는 동일한 SHP Type만을 사용하기 때문입니다. 이는 물론 차후의 확장에는 도움이 될 수 있으리라 생각되는 부분입니다.



 - 다중 포인트 (Multi Point)
 
 다중 포인트는 여러 개의 점을 하나의 개체로 인식하는 것입니다. 위치가 다른 곳에 있는 점이라도 같은 속성을 지니고 있을 경우 이들을 하나로 묶어서 사용하기에 좋을 것입니다.
 
 
Position   Field          Value      Type     Number    Order
---------------------------------------------------------------
Byte 0     Shape Type     8          Integer   1         Little
Byte 4     Box            Box        Double    4         Little
Byte 36    NumPoints      NumPoints  Integer   1         Little
Byte 40    Points         Points     Point     NumPoints Little
---------------------------------------------------------------


Byte4의 Box란 역시 MBR입니다. 이는 여러 점으로 구성되어 있을 경우 이 점들을 둘러싸고 있는 가장 작은 사각형을 의미하는 것입니다.
Byte36의 NumPoints는 개체가 가지고 있는 점의 개수입니다.(여러 점이 하나의 개체로 인식될 때의 여러 점의 개수)
Byte40부터는 실제 점을 NumPoints만큼 담고 있는 것입니다.

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

다중 포인트의 구조는 위와 같습니다.



 - 폴리라인 혹은 아크(Arc)
 
 아크는 라인 혹은 폴리라인을 수용하는 구조체입니다. 다만 다중 포인트처럼 위치가 다른 곳에 있는 선을 같은 개체로 처리할 수 있도록 하였습니다. 이게 Part라는 것입니다.

Position   Field          Value      Type     Number    Order
--------------------------------------------------------------
Byte 0     Shape Type     3          Integer   1        Little
Byte 4     Box            Box        Double    4        Little
Byte 36    NumParts       NumParts   Integer   1        Little
Byte 40    NumPoints      NumPoints  Integer   1        Little
Byte 44    Parts          Parts      Integer   NumParts Little
Byte X     Points         Points     Point     NumPoints Little
--------------------------------------------------------------


Byte4부터는 역시 MBR을 의미합니다. 선들이 있으면 그 선들을 둘러싸는 가장 작은 사각형을 의미합니다. 사실 MBR은 중요합니다. 화면에 알맞게 뿌리기 위해서는 말이죠.
Byte36부터는 파트의 개수입니다. 파트가 뭐냐면 한 Arc개체에 몇개의 폴리라인들이 있느냐 하는 것으로 보면 됩니다.
Byte40부터는 이 개체에 있는 점의 수입니다. 위치나 파트와 관계없이 쭉~~ 저장됩니다. 그래서 그 점의 개수가 몇개인 지 지정하는 것입니다.
점들이 파트에 관계없이 쭉~~ 기록되어 있다면 어디서부터가 어떤 파트의 시작이고 끝인지를 가르쳐줘야 할 것입니다. 일종의 인덱스인데요..
그 정보를 기록하고 있는 부분이 바로 Byte44부터인 Parts가 되겠습니다. 이 부분은 일종의 동적배열입니다.
그리고 Points에는 그냥 점들이 쭉~~ 기록되어 있습니다.



PolyLine
{
 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
}




 - 폴리곤


폴리곤은 사실 폴리라인 혹은 아크의 구조와 같습니다. 다만 파트별로 시작점과 끝점이 같아서 닫혀있다는 것을 강조할 뿐입니다.
따라서 이에 대한 설명은 폴리란인에 대한 설명으로 대체하도록 하겠습니다.


Position   Field          Value      Type     Number    Order
--------------------------------------------------------------
Byte 0     Shape Type     5          Integer   1        Little
Byte 4     Box            Box        Double    4        Little
Byte 36    NumParts       NumParts   Integer   1        Little
Byte 40    NumPoints      NumPoints  Integer   1        Little
Byte 44    Parts          Parts      Integer   NumParts Little
Byte X     Points         Points     Point     NumPoints Little
---------------------------------------------------------------

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
}


오늘은 SHP 포맷 중 shp 파일에 대해 분석을 해봤습니다. 위 설명을 보시며 제가 올린 프로그램의 소스를 보시면 좋을 듯합니다.

CShape 클래스의 Open 루틴을 보시면 됩니다.
아직 shx에 대한 설명을 하지 않았습니다만 미리 한 번 shx에 대해 읽어보시면 많은 도움이 되리라 생각됩니다.

---------------------------

bool CShape::Open(CString _filename)
{
 FILE *fpSHP, *fpSHX;
 int tmp, i, j;
 
 m_strSHPPath = _filename;

 tmp = _filename.GetLength()-4;
 m_strSHXPath = _filename.Mid(0, tmp) + ".shx";


// SHP/SHX를 열자

 fpSHP = fopen(m_strSHPPath, "rb");
 fpSHX = fopen(m_strSHXPath, "rb");

 if(fpSHP == NULL || fpSHX == NULL)
 {
  AfxMessageBox("SHP File Error!!", IDOK | MB_ICONSTOP);
  return false;
 }

 /* release memory of exsiting one */
 if(m_bOpen)
  Close();

 /* Now read a Header of SHP/SHX */
 // Get File size & Number of records
 fseek(fpSHX, 24L, SEEK_SET);
 fread(&tmp, 4, 1, fpSHX);
 SwapWord(4, &tmp); // Byte Order Changing
 
 int m_nFileSize = tmp * 2; // get file size
 m_nRecords = (m_nFileSize - 100)/8; // Get a number of records

 // get shp type
 fseek(fpSHP, 32L, SEEK_SET);
 fread(&m_nShapeType, 4, 1, fpSHP);
 
 // Check a invalid shp type
 if(!(m_nShapeType == 1 || m_nShapeType == 3 || m_nShapeType == 5 || m_nShapeType == 8))
 {
  AfxMessageBox("Invalid SHP Type.\nThis program only support Point, Arc, Polygon & MultiPoint SHP Type.", IDOK | MB_ICONSTOP);
  return false;
 }

 // get a MBR
 fread(&m_MBR, sizeof(MBR), 1, fpSHP);

 // real routine of feature reading(point, arc, polygon, multipoint)
 switch(m_nShapeType)
 {
 // 포인트를 읽어들인다.
 case SHPT_POINT:
  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);
  }
  break;

 // 폴리라인과 폴리곤을 읽어들인다.
 case SHPT_ARC :
 case SHPT_POLYGON:
  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);
  }
  break;
 
 // 다중점을 읽어들인다.
 case SHPT_MULTIPOINT:
  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);
  }
  break;
 }
 // SHP 색을 랜덤하게 할당한다.
 srand((unsigned)time(NULL)); // 랜덤함수 초기화..
 m_lineColor = RGB(rand()%255, rand()%255, rand()%255); // 색..

 fclose(fpSHX);
 fclose(fpSHP);

 m_bOpen = true;
 return true;
}


2001년 11월 14일

Posted by 뚜와띠엔
,