.shp는 ESRI Shapefile 포맷의 구성 파일 중 하나의 확장자이다.
이름에서 보이듯이 ArcGIS를 만든 ESRI에서 만든 포맷으로, 현재는 GIS(지리정보시스템) 분야에서 보편적으로 쓰이고 있음.
GIS 애플리케이션에서 사용할 벡터 데이터 형태(점/선/폴리곤)의 공간지리정보를 담기 위해 사용된다. 위의 벡터 정보 뿐 아니라, 추가적인 attribute도 함께 저장 가능. 예를 들어 어떤 해당 영역의 지명과 온도, 인구수 등..
GIS 애플리케이션이라 하면 ArcGIS, QGIS 등이 대표적.
ArcGIS의 경우 비상업적 목적의 개인사용자에겐 무료배포, QGIS의 경우 오픈소스+우분투 소프트웨어로 설치 가능해 QGIS가 쓰기 쉬움 (사실 QGIS밖에 안써봤음)
+cloud compare에서도 열리긴 함 (x,y,z 정보만을 사용해 시각화 지원)
shx, dbf, shp 이렇게 세 개가 모여야 GIS 애플리케이션에서 실행됨.
- shp file- 메인 파일로, 벡터 정보를 담고 있다.
- dbf file- 데이터베이스 파일, shape별 모든 attribute을 담고 있음. dBase IV 형식.
- shx file- 인덱스 파일, shp의 벡터 정보와 dbf의 attribute을 연결해주는 역할
그 외에 있어도 되고 없어도 되는 추가파일들은 prj, sbn, sbx, fbn, aih 등등이 있다.
근데 GIS 프로그램 너무 복잡해서 못 쓰겠음.. 그리고 shp파일을 특정 알고리즘으로 처리해줘야 하는 상황들이 있는데 어떤 GIS 프로그램들은 프로그램 상에서 그런 코딩이 지원이 된다고 보긴 한 것 같은데 (확실하지 않음) 알아보기 귀찮다. 언어별로 로더/세이버가 구현되어있는 좋은 오픈소스 라이브러리들이 있으니까 이 라이브러리들 사용법을 알아볼 것이다. 여기서 추천하는 라이브러리들은 내가 그냥 습관적으로 쭉 쓰고 있는 것들이다. 더 좋은 선택이 있을 수도 있음.
header + records 로 이루어져있음.
앞에서부터 100바이트.
5번째 필드인 Shape Type은 아래와 같이 정의되어있음.
하나의 record는 record header (고정 길이) + record contents (가변 길이) 로 이루어져있음.
레코드 번호+길이를 담고있음.
이 record가 나타내는 segment의 shape type, 그리고 위치벡터로 이루어져있음. 하나의 점에 대한 예시를 들자면, 이렇게 구성된다.
여기까지의 Reference:
https://docs.fileformat.com/gis/shp/
더욱 자세한 정보는 아래의 ESRI에서 제공하는 Shapefile Technical Description을 보면 알 수 있음!
https://www.esri.com/content/dam/esrisites/sitecore-archive/Files/Pdfs/library/whitepapers/pdfs/shapefile.pdf
근데 내가 로더를 직접 짤 필욘 없으니까 일단 넘어가고.. c++과 python에서 각각 어떤 라이브러리를 사용하면 shp파일 처리를 편리하게 할 수 있는지 예제와 함께 살펴보자!
이 shapelib이라는 라이브러리를 사용한다.
https://github.com/OSGeo/shapelib
c library긴 하지만 내기준 제일 쓰기 간편했다.
$ git clone https://github.com/OSGeo/shapelib.git
$ cd shapelib
$ chmod +x autogen.sh
$ sudo apt-get install libtool-bin
$ ./autogen.sh
$ make
$ sudo make install
target_link_libraries(proj_name /usr/local/lib/libshp.so)
한줄이면 끝! cmake 안되면 so파일 위치 확인해보기
비어있는 segment의 개수를 세고, 비어있지 않으면 z값을 1로 셋팅해주는 간단한 프로그램을 짜보았다.
#include <shapefil.h>
// loading file
// open file
SHPHandle shp_i = SHPOpen("file path", "rb");
// get header information
int pnEntity, type;
double minbound[4], maxbound[4];
SHPGetInfo(shp_i, &pnEntity, &type, minbound, maxbound);
// create output file
SHPHandle shp_o = SHPCreate("output file path", SHPT_ARCZ);
// process each segment
int count_ = 0;
std::vector<int> blank_seg;
for (int i=0; i < pnEntity; i++) { // single segment
SHPObject *tmp = SHPReadObject(shp_i, i);
int k = tmp->nVertices;
if (k == 0) {
count_++;
continue;
}
int qtj1, qtj2;
double qtj3 = 0;
SHPObject *shp = SHPCreateObject(SHPT_ARCZ, i, 0, &qtj1, &qtj2, k, tmp->padfX, tmp->padfY, 1, &qtj3);
SHPWriteObject(shp_o, -1, shp);
SHPDestroyObject(shp);
SHPDestroyObject(tmp);
}
std::cout << "# of empty segment : " << blank_seg.size() << "/" << pnEntity << std::endl;
if (blank_seg.size() != 0) {
std::ofstream f;
f.open("blank_id.txt".c_str());
for(auto id : blank_seg)
f << id << std::endl;
f.close();
}
SHPClose(shp_i);
SHPClose(shp_o);
간단하쥬? 근데 처음 짤 때는 예제도 인터넷 상에 거의 없다시피하고 document도 구려서 삽질좀 했다는 슬픈 이야기..
GeoPandas를 사용한다.
TBA