RFC 63 : 희소 데이터셋 개선
저자: 이벤 루올
연락처: even.rouault@spatialys.com
상태: 승인, GDAL 2.2버전에 구현
요약
이 RFC는 희소(sparse) 데이터셋을, 다시 말해 비어 있는 영역들을 상당히 많이 담고 있을 수 있는 데이터셋을 관리하기 위한 개선 사항을 제안합니다.
점근법
매우 큰 공간 범위를 커버하지만 상당한 부분에 데이터가 없는 데이터셋을 읽어오거나 생성할 필요가 있는 사용례가 존재합니다. GDAL API에는 데이터가 어떤 영역을 커버하고 어떤 영역을 커버하지 않는지를 빠르게 알 수 있는 방법이 전혀 없기 때문에 모든 픽셀을 처리해야만 합니다. 이런 방식은 비효율적입니다. 반면 GeoTIFF, VRT 또는 GeoPackage 같은 일부 포맷들은 픽셀들을 처리하지 않고서도 이런 정보를 제공할 수 있는 가능성이 있습니다.
따라서 GDALRasterBand
클래스에 관심 영역의 창을 입력받고 해당 영역이 데이터로 이루어져 있는지, 비어 있는 블록인지, 또는 두 상태가 혼합되어 있는지를 반환하는 새로운 GetDataCoverageStatus() 메소드를 추가할 것을 제안합니다.
산출 드라이버가 그렇게 명령하는 경우 (CreateCopy() 및 gdal_translate가 사용하는) GDALDatasetCopyWholeRaster() 메소드가 이 메소드를 사용해서 희소 영역을 처리하는 일을 막을 것입니다.
C++ API
GDALRasterBand
클래스에 새 가상 메소드를 추가합니다:
virtual int IGetDataCoverageStatus( int nXOff, int nYOff,
int nXSize, int nYSize,
int nMaskFlagStop,
double* pdfDataPct);
/**
* \brief 래스터 하위 창의 커버리지 상태를 가져옵니다.
*
* 래스터의 하위 창이 데이터만 담고 있는지, 비어 있는 블록들만 담고 있는지,
* 또는 두 상태가 혼합되어 있는지 여부를 반환합니다. 희소할 수도 있는
* 데이터셋에 RasterIO / ReadBlock 요청을 발행할 가치가 있는지를 빠르게
* 판단하는 데 이 함수를 사용할 수 있습니다.
*
* 비어 있는 블록이란 설정 시 값이 NODATA 값인 픽셀만 담고 있는 블록 또는
* NODATA 값을 설정하지 않은 경우 값이 0인 픽셀만 담고 있는 블록을 말합니다.
*
* 실제 픽셀 값을 읽지 않는 효율적인 방식으로 쿼리를 수행합니다.
* 이렇게 수행할 수 없거나 드라이버가 이를 전혀 구현하지 않는 경우,
* GDAL_DATA_COVERAGE_STATUS_UNIMPLEMENTED | GDAL_DATA_COVERAGE_STATUS_DATA를
* 반환할 것입니다.
*
* 함수가 반환할 수 있는 값은 다음과 같습니다. 이때 값이 바이너리 또는
* 연산자와 결합되어 있을 수도 있습니다:
* <ul>
* <li>GDAL_DATA_COVERAGE_STATUS_UNIMPLEMENTED: 드라이버가 GetDataCoverageStatus()를
* 구현하고 있지 않습니다. 이 플래그는 GDAL_DATA_COVERAGE_STATUS_DATA와 함께
* 반환되어야 합니다.</li>
* <li>GDAL_DATA_COVERAGE_STATUS_DATA: 쿼리 창에 데이터가 있을 가능성이 있습니다.</li>
* <li>GDAL_DATA_COVERAGE_STATUS_EMPTY: 쿼리 창에 데이터가 없습니다. 일반적으로
* 누락된 블록이라는 개념을 지원하는 포맷에 있는 누락된 블록으로 식별됩니다.
* </li>
* </ul>
*
* GDAL_DATA_COVERAGE_STATUS_DATA가 가짜 양의 값을 가질 수도 있으며 데이터가 존재할
* 가능성에 대한 힌트로 해석해야 한다는 사실을 기억하십시오. 예를 들어
* GeoTIFF 파일이 0으로 채워진 블록들로 생성되었다면 (또는 NODATA 값으로
* 설정되었다면) 누락된 블록 메커니즘을 사용하는 대신
* GDAL_DATA_COVERAGE_STATUS_DATA를 반환할 것입니다. 이와는 반대로,
* GDAL_DATA_COVERAGE_STATUS_EMPTY는 가짜 양의 값을 가지지 않을 것입니다.
*
* nMaskFlagStop은 일반적으로 0으로 설정해야 합니다. 계산된 마스크가 nMaskFlagStop과
* 일치하는 즉시 함수를 빠르게 종료할 수 있도록 앞에서 언급한 값들을
* Binary OR 처리한 마스크로 설정할 수 있습니다. 예를 들어
* 래스터 전체에 대해 nMaskFlagStop = GDAL_DATA_COVERAGE_STATUS_EMPTY로 요청을
* 발행할 수 있습니다. 누락된 블록 하나를 발견하는 즉시 함수를 종료할 것이기
* 때문에, 어떤 특정 영역(들)에 누락된 블록이 있는지를 찾을 수 있도록
* 요청 영역을 세밀하게 구분할 수 있습니다.
*
* @see GDALGetDataCoverageStatus() 참조
*
* @param nXOff 쿼리할 밴드 영역의 좌상단 모서리에 대한 픽셀 오프셋입니다.
* 좌측에서 시작하는 0일 것입니다.
*
* @param nYOff 쿼리할 밴드 영역의 좌상단 모서리에 대한 줄 오프셋입니다.
* 상단에서 시작하는 0일 것입니다.
*
* @param nXSize 쿼리할 밴드 영역의 픽셀 단위 너비입니다.
*
* @param nYSize 쿼리할 밴드 영역의 픽셀 단위 높비입니다.
*
* @param nMaskFlagStop 0, 또는 GDAL_DATA_COVERAGE_STATUS_UNIMPLEMENTED,
* 또는 GDAL_DATA_COVERAGE_STATUS_DATA 및
* GDAL_DATA_COVERAGE_STATUS_EMPTY의 가능한 값들을
* Binary OR 처리한 마스크입니다. 커버리지 계산이
* 마스크와 일치하는 즉시 계산을 종료할 것입니다.
* 이 경우 *pdfDataPct가 무결하지 않을 것입니다.
*
* @param pdfDataPct 이 파라미터가 가리키는 값이 무결한 값을 가진 쿼리된
* 하위 창에 있는 픽셀들의 [0,100] 범위의 (근사치) 백분율로
* 설정될 선택적인 산출 파라미터입니다. 이 구현이 항상 이를
* 계산할 수 없을 수도 있는데, 이런 경우 음의 값으로 설정될 것입니다.
*
* @return GDAL_DATA_COVERAGE_STATUS_UNIMPLEMENTED, 또는 GDAL_DATA_COVERAGE_STATUS_DATA 및
* GDAL_DATA_COVERAGE_STATUS_EMPTY의 가능한 값들을 Binary OR 처리한 조합을
* 반환합니다.
*
* @note GDAL 2.2버전에 추가
*/
이 메소드는 GDAL_DATA_COVERAGE_STATUS_UNIMPLEMENTED | GDAL_DATA_COVERAGE_STATUS_DATA
를 반환하는 비지능형(dumb) 기본 구현을 가지고 있습니다.
공개(public) API는 다음과 같이 구성됩니다:
/** 드라이버가 GetDataCoverageStatus()를 구현하지 않은 경우
* GDALGetDataCoverageStatus()가 반환하는 플래그입니다.
* 이 플래그는 GDAL_DATA_COVERAGE_STATUS_DATA와 함께 반환되어야 합니다. */
#define GDAL_DATA_COVERAGE_STATUS_UNIMPLEMENTED 0x01
/** 쿼리 창에 데이터가 있을 가능성이 있는 경우
* GDALGetDataCoverageStatus()가 반환하는 플래그입니다.
* GDAL_DATA_COVERAGE_STATUS_UNIMPLEMENTED 또는
* GDAL_DATA_COVERAGE_STATUS_EMPTY를 가진 바이너리 또는 연산자와
* 결합될 수 있습니다. */
#define GDAL_DATA_COVERAGE_STATUS_DATA 0x02
/** 쿼리 창에 데이터가 없는 경우 GDALGetDataCoverageStatus()가
* 반환하는 플래그입니다. 일반적으로 누락된 블록이라는 개념을
* 지원하는 포맷에 있는 누락된 블록으로 식별됩니다.
* GDAL_DATA_COVERAGE_STATUS_DATA를 가진 바이너리 또는 연산자와
* 결합될 수 있습니다. */
#define GDAL_DATA_COVERAGE_STATUS_EMPTY 0x04
C++ :
int GDALRasterBand::GetDataCoverageStatus( int nXOff,
int nYOff,
int nXSize,
int nYSize,
int nMaskFlagStop,
double* pdfDataPct)
C :
int GDALGetDataCoverageStatus( GDALRasterBandH hBand,
int nXOff, int nYOff,
int nXSize,
int nYSize,
int nMaskFlagStop,
double* pdfDataPct);
GDALRasterBand::GetDataCoverageStatus()
는 IGetDataCoverageStatus()를 호출하기 전에 창의 무결성에 대한 기본적인 확인을 수행합니다.
변경 사항
GDALDatasetCopyWholeRaster() 및 GDALRasterBandCopyWholeRaster()는 산출 드라이버가 YES로 설정해서 소스 데이터셋이 구멍(hole)만 담고 있는지 또는 그렇지 않은지 여부를 판단하기 위해 소스 데이터셋의 각 덩어리(chunk)에 대해 GetDataCoverageStatus()를 호출하게 만드는 SKIP_HOLES 옵션을 받아들입니다.
드라이버
이 RFC는 GeoTIFF 및 VRT 드라이버가 IGetDataCoverageStatus() 메소드를 구현하도록 업그레이드합니다.
GeoTIFF 드라이버에는 이 주제와 관련된, 예를 들어 CreateCopy() 모드에서 SPARSE_OK=YES 생성 옵션을 입력받을 수 있는 (또는 업데이트 모드에서 SPARSE_OK 열기 옵션을 입력 받을 수 있는) 기능을 포함하는 수많은 이전의 개선 사항들도 적용합니다.
다음은 GeoTIFF 드라이버 문서에서 발췌한 내용입니다:
GDAL은 오프셋과 바이트 개수가 0으로 설정된 TIFF 타일 또는 스트립, 즉
대응하는 할당된 물리적 저장소가 없는 타일 또는 스트립을 특별하게
해석합니다. 이런 파일을 읽어오는 경우, 이런 타일 또는 스트립이
정의되었을 때 명확하게 0 또는 NODATA 값으로 설정되었다고 간주합니다.
이런 파일을 작성할 때 Create() 인터페이스를 통해 SPARSE_OK 생성
옵션을 YES로 설정하면 이런 타일 또는 스트립 생성을 활성화할 수
있습니다. 그 다음, IWriteBlock()/IRasterIO() 인터페이스를 통해
작성되지 않은 블록들의 오프셋과 바이트 개수를 0으로 설정할 것입니다.
파일의 값을 채우기 위한 이후 처리 과정으로 넘기기 전에 파일을 초기화
해야만 하는 경우 이 옵션이 디스크 용량 및 시간을 절약하는 데 특히 유용
합니다. 다음 문단에서 논의할 또다른 희소 메커니즘과 혼동하지 않기 위해,
이런 내포 타일/스트립을 가진 파일을 "TIFF 희소 파일"이라고 부를 것입니다.
GDAL 기반이 아닌 TIFF 판독기는 이런 파일을 상호 작업할 수 **없을**
것이며, 이런 내포 타일/스트립을 가진 파일을 결함이 있다고 간주할 것입니다.
GDAL 2.2버전부터, 이 메커니즘이 (업데이트 모드를 위해) CreateCopy()는 물론
Open() 인터페이스까지 확장되었습니다. SPARSE_OK 생성 옵션을 (또는 Open()의
경우 SPARSE_OK 열기 옵션을) YES로 설정하면, 모든 0 또는 NODATA 블록을
작성하려는 시도조차 탐지해서 이런 타일 또는 스트립이 물리적 저장소에
할당되지 않도록 할 것입니다. (이미 저장소에 할당되어 있는 경우, 그 내용을
0 또는 NODATA 내용으로 대체할 것입니다.)
GDAL 2.2버전부터, SPARSE_OK를 정의하지 않으면 (또는 기본값 FALSE로 설정하면)
NODATA 값이 설정되지 않았거나 0으로 설정된 비압축 파일의 경우, Create() 및
CreateCopy() 모드에서 이 드라이버는 파일의 가장 마지막 부분에 작성하기 위해
그리고 어떤 의미에서는 파일 시스템의 희소 파일 메커니즘과 호환될 수 있도록
(앞에서 논의했던 TIFF 희소 파일 확장 사양과 구별할 수 있도록) 파일이 닫힐
때까지 0값 블록의 할당을 지연시킬 것입니다. 즉 TIFF 관점에서는 모든 비어 있는
블록이 제대로 할당된 것처럼 보이지만 (대응하는 타일/스트립이 무결한 오프셋과
바이트 개수를 가질 것이지만) 대응하는 물리적 저장소는 없을 것입니다. 물론
파일 시스템이 이런 희소 파일을 지원하는 경우에 말입니다. 리눅스의 유명한
파일 시스템 대부분(ext2/3/4, xfs, btfs 등등) 또는 윈도우의 NTFS가 희소 파일을
지원합니다. 파일 시스템이 희소 파일을 지원하지 않는다면 물리적 저장소를
할당하고 0으로 채울 것입니다.
바인딩
파이썬 바인딩에 GDALGetDataCoverageStatus()의 매핑을 추가합니다. 다른 바인딩들도 업데이트할 수 있습니다. (상태 플래그와 백분율을 둘 다 반환할 수 있는 방법을 알아내야 합니다.)
유틸리티
유틸리티에는 직접적인 변경 사항이 없습니다.
결과물
이 새로운 케이퍼빌리티를 사용하면, 각각 20x20 픽셀 크기의 영역을 2개 담고 있는 200,000 x 200,000 픽셀 크기의 VRT를 gdal_translate를 통해 2초만에 희소 타일 GeoTIFF로 변환할 수 있습니다. 산출되는 GeoTIFF 자체도 또다른 희소 타일 GeoTIFF로 동시에 변환할 수 있습니다.
향후 작업
향후 오버뷰 작성 및 왜곡 작업에 새 케이퍼빌리티를 사용하도록 할 수 있습니다. 다른 드라이버들도 이 새 케이퍼빌리티로부터 혜택을 볼 수 있습니다:
GeoPackage
ERDAS Imagine
…
문서화
새로운 메소드를 문서화합니다.
테스트 스위트
VRT 및 GeoTIFF 드라이버의 테스트가 IGetDataCoverageStatus() 구현을 테스트하도록 확장합니다.
호환성 문제
C++ ABI를 변경했지만, 기능적으로 호환되지 않는 부분은 예상되지 않습니다.
구현
이벤 루올이 이 RFC를 구현할 것입니다.
제안한 구현은 “희소 데이터셋” 브랜치 에 있습니다.
투표 이력
이벤 루올 +1
대니얼 모리셋 +1