ラスタードライバー実装チュートリアル

全体的なアプローチ

一般的に、新しいフォーマットは, GDALDataset のサブクラスとしてフォーマット固有のドライバーを実装し,バンドアクセサは GDALRasterBand のサブクラスとして実装します.また,フォーマット用の GDALDriver インスタンスを作成し, GDALDriverManager に登録して,システムがフォーマットを認識できるようにします.

このチュートリアルでは,シンプルな読み取り専用ドライバー( JDEM ドライバーをベースにしたもの)の実装から始め,RawRasterBand ヘルパークラスの利用,作成可能および更新可能なフォーマットの実装,およびいくつかの難解な問題に進みます.

GDAL ドライバーの実装を試みる前に, ラスターデータモデル を確認し理解することを強くお勧めします.

データセットの実装

日本の DEM フォーマット用の読み取り専用ドライバーの最小限の実装を示します(jdemdataset.cpp). まず,この場合のフォーマット固有のデータセットクラス JDEMDataset を宣言します.

class JDEMDataset final: public GDALPamDataset
{
    friend class JDEMRasterBand;

    VSILFILE           *m_fp = nullptr;
    GByte               m_abyHeader[HEADER_SIZE];
    OGRSpatialReference m_oSRS{};

  public:
                     JDEMDataset();
                    ~JDEMDataset();

    static GDALDataset *Open( GDALOpenInfo * );
    static int Identify( GDALOpenInfo * );

    CPLErr GetGeoTransform( double * padfTransform ) override;
    const OGRSpatialReference* GetSpatialRef() const override;
};

一般的に,ドライバーの機能を提供するためには, GDALDataset 基底クラスのさまざまな仮想メソッドをオーバーライドします. ただし,Open() メソッドは特別です. これは基底クラスの仮想メソッドではなく,この操作のために独立した関数が必要なので,static として宣言します. JDEMDataset クラスのメソッドとして実装するのは便利ですデータベースオブジェクトの内容を変更する特権的なアクセスがあるためです.

Open メソッド自体は次のようになります:

GDALDataset *JDEMDataset::Open( GDALOpenInfo *poOpenInfo )
{
    // Confirm that the header is compatible with a JDEM dataset.
    if (!Identify(poOpenInfo))
        return nullptr;

    // Confirm the requested access is supported.
    if( poOpenInfo->eAccess == GA_Update )
    {
        CPLError(CE_Failure, CPLE_NotSupported,
                 "The JDEM driver does not support update access to existing "
                 "datasets.");
        return nullptr;
    }

    // Check that the file pointer from GDALOpenInfo* is available.
    if( poOpenInfo->fpL == nullptr )
    {
        return nullptr;
    }

    // Create a corresponding GDALDataset.
    auto poDS = cpl::make_unique<JDEMDataset>();

    // Borrow the file pointer from GDALOpenInfo*.
    std::swap(poDS->m_fp, poOpenInfo->fpL);

    // Store the header (we have already checked it is at least HEADER_SIZE
    // byte large).
    memcpy(poDS->m_abyHeader, poOpenInfo->pabyHeader, HEADER_SIZE);

    const char *psHeader = reinterpret_cast<const char *>(poDS->m_abyHeader);
    poDS->nRasterXSize = JDEMGetField(psHeader + 23, 3);
    poDS->nRasterYSize = JDEMGetField(psHeader + 26, 3);
    if( !GDALCheckDatasetDimensions(poDS->nRasterXSize, poDS->nRasterYSize) )
    {
        return nullptr;
    }

    // Create band information objects.
    poDS->SetBand(1, new JDEMRasterBand(poDS.get(), 1));

    // Initialize any PAM information.
    poDS->SetDescription(poOpenInfo->pszFilename);
    poDS->TryLoadXML();

    // Check for overviews.
    poDS->oOvManager.Initialize(poDS.get(), poOpenInfo->pszFilename);

    return poDS.release();
}

データベースの Open 関数の最初のステップは,渡されたファイルが実際にこのドライバーのタイプであることを確認することです. 重要なのは,各ドライバーの Open 関数が順番に呼び出されることで,成功するまで呼び出されます. ドライバーは,渡されたファイルがそのフォーマットでない場合は静かに nullptr を返す必要があります. ファイルを開くための情報は, GDALOpenInfo オブジェクトに含まれています. GDALOpenInfo には次のパブリックデータメンバが含まれています:

char        *pszFilename;
char**      papszOpenOptions;
GDALAccess  eAccess;  // GA_ReadOnly or GA_Update
int         nOpenFlags;
int         bStatOK;
int         bIsDirectory;
VSILFILE   *fpL;
int         nHeaderBytes;
GByte       *pabyHeader;

ドライバーは,これらを検査してファイルがサポートされているかどうかを確立できます.`pszFilename` がファイルシステムのオブジェクトを参照している場合,`bStatOK` フラグがTRUE に設定されます. また,ファイルが正常に開かれた場合,最初のキロバイト程度が読み込まれ,pabyHeader に格納され,その正確なサイズが nHeaderBytes に格納されます.

この典型的なテスト例では,ファイルが正常に開かれたこと,テストを実行するのに十分なヘッダ情報があること,およびヘッダのさまざまな部分がこのフォーマットに期待される値であることを確認します. この場合,JDEM フォーマットにはマジックナンバーがないため,合理的な世紀値を持つことを確認するためにさまざまな日付フィールドをチェックします.テストに失敗した場合,このファイルがサポートされていないことを示す NULL を静かに返します.

識別は実際には Identify() 静的関数に委任されます:

/************************************************************************/
/*                              Identify()                              */
/************************************************************************/
int JDEMDataset::Identify( GDALOpenInfo * poOpenInfo )
{
    if( poOpenInfo->nHeaderBytes < HEADER_SIZE )
        return FALSE;

    // Confirm that the header has what appears to be dates in the
    // expected locations.
    // Check if century values seem reasonable.
    const char *psHeader = reinterpret_cast<char *>(poOpenInfo->pabyHeader);
    if( (!STARTS_WITH_CI(psHeader + 11, "19") &&
         !STARTS_WITH_CI(psHeader + 11, "20")) ||
        (!STARTS_WITH_CI(psHeader + 15, "19") &&
         !STARTS_WITH_CI(psHeader + 15, "20")) ||
        (!STARTS_WITH_CI(psHeader + 19, "19") &&
         !STARTS_WITH_CI(psHeader + 19, "20")) )
    {
        return FALSE;
    }

    // Check the extent too. In particular, that we are in the first quadrant,
    // as this is only for Japan.
    const double dfLLLat = JDEMGetAngle(psHeader + 29);
    const double dfLLLong = JDEMGetAngle(psHeader + 36);
    const double dfURLat = JDEMGetAngle(psHeader + 43);
    const double dfURLong = JDEMGetAngle(psHeader + 50);
    if( dfLLLat > 90 || dfLLLat < 0 ||
        dfLLLong > 180 ||dfLLLong < 0 ||
        dfURLat > 90 || dfURLat < 0 ||
        dfURLong > 180 || dfURLong < 0 ||
        dfLLLat > dfURLat ||
        dfLLLong > dfURLong )
    {
        return FALSE;
    }

    return TRUE;
}

可能な限り厳格な"これは私のフォーマットですか"テストを行うことが重要です.この特定の場合,日付が 19 世紀または 20 世紀であることをチェックしますが,これも弱すぎる可能性があるため,地理空間範囲が一貫しており,日本に適していることを確認し,ファイルが私たちのフォーマットであることを確認した後,ファイルが使用可能であることを検証するために必要な他のテストを実行し,特に望ましいアクセスレベルを提供できることを確認します. JDEM ドライバーは更新サポートを提供しないため,その場合はエラーが発生します.

if( poOpenInfo->eAccess == GA_Update )
{
    CPLError(CE_Failure, CPLE_NotSupported,
             "The JDEM driver does not support update access to existing "
             "datasets.");
    return NULL;
}

次に,興味のあるさまざまな情報を設定するデータベースクラスのインスタンスを作成する必要があります. エラーコードパスでメモリ管理を容易にするために, std::unique_ptr<JDEMDataset> と cpl::make_unique<> ユーティリティ(C++14 以降で利用可能な std::make_unique<> に相当)で作成します.

// Check that the file pointer from GDALOpenInfo* is available.
if( poOpenInfo->fpL == NULL )
{
    return NULL;
}
auto poDS = cpl::make_unique<JDEMDataset>();

// Borrow the file pointer from GDALOpenInfo*.
std::swap(poDS->m_fp, poOpenInfo->fpL);

この時点で, GDALOpenInfo* が保持していたファイルハンドルを"借ります"(インラインメンバ定義で poDS->m_fp が nullptr に初期化されていることを確認しました).このファイルポインタは,ディスク上のファイルにアクセスするための VSI*L GDAL API を使用します.この仮想化された POSIX スタイルの API には,大きなファイル,インメモリファイル,および圧縮ファイルをサポートするなどの特別な機能があります.

次に,ヘッダから X サイズと Y サイズを抽出します. nRasterXSizenRasterYSize は,GDALDataset 基底クラスから継承されたデータフィールドであり,Open() メソッドで設定する必要があります.

// Store the header (we have already checked it is at least HEADER_SIZE
// byte large).
memcpy(poDS->m_abyHeader, poOpenInfo->pabyHeader, HEADER_SIZE);

const char *psHeader = reinterpret_cast<const char *>(poDS->m_abyHeader);
poDS->nRasterXSize = JDEMGetField(psHeader + 23, 3);
poDS->nRasterYSize = JDEMGetField(psHeader + 26, 3);
if( !GDALCheckDatasetDimensions(poDS->nRasterXSize, poDS->nRasterYSize) )
{
    return nullptr;
}

このデータセットに関連するすべてのバンドは,SetBand() メソッドを使用して作成およびアタッチする必要があります. JDEMRasterBand() クラスについてはすぐに調査します.

// Create band information objects.
poDS->SetBand(1, new JDEMRasterBand(poDS.get(), 1));

最後に,データセットオブジェクトに名前を割り当て,利用可能な場合は .aux.xml ファイルから補助情報を初期化できる GDALPamDataset TryLoadXML() メソッドを呼び出します. また,外部の概要情報(.ovr サイドカーファイル)の初期化も行います. これらのサービスの詳細については,GDALPamDataset および関連クラスを参照してください.

    // Initialize any PAM information.
    poDS->SetDescription( poOpenInfo->pszFilename );
    poDS->TryLoadXML();

    // Check for overviews.
    poDS->oOvManager.Initialize(poDS.get(), poOpenInfo->pszFilename);

    return poDS.release();
}

ラスターバンドの実装

GDALDataset からサブクラス化されたカスタマイズされた JDEMDataset クラスと同様に,JDEM ファイルのバンドにアクセスするための GDALRasterBand から派生したカスタマイズされた JDEMRasterBand を宣言および実装する必要があります. JDEMRasterBand の宣言は次のようになります:

class JDEMRasterBand final: public GDALPamRasterBand
{
    friend class JDEMDataset;

    int          m_nRecordSize = 0;
    char        *m_pszRecord = nullptr;
    bool         m_bBufferAllocFailed = false;

  public:
                JDEMRasterBand( JDEMDataset *, int );
               ~JDEMRasterBand();

    virtual CPLErr IReadBlock( int, int, void * ) override;
};

コンストラクタには任意のシグネチャを持たせることができ,Open() メソッドからのみ呼び出されます.その他の仮想メソッド,例えば GDALRasterBand::IReadBlock() は,gdal_priv.h でのメソッドシグネチャと完全に一致している必要があります.

コンストラクタの実装は次のようになります:

JDEMRasterBand::JDEMRasterBand( JDEMDataset *poDSIn, int nBandIn ) :
    // Cannot overflow as nBlockXSize <= 999.
    m_nRecordSize(poDSIn->GetRasterXSize() * 5 + 9 + 2)
{
    poDS = poDSIn;
    nBand = nBandIn;

    eDataType = GDT_Float32;

    nBlockXSize = poDS->GetRasterXSize();
    nBlockYSize = 1;
}

以下のデータメンバは GDALRasterBand から継承され,通常はバンドコンストラクタで設定する必要があります.

poDS: Pointer to the parent GDALDataset.
nBand: The band number within the dataset.
eDataType: The data type of pixels in this band.
nBlockXSize: The width of one block in this band.
nBlockYSize: The height of one block in this band.

可能な GDALDataType 値の全セットは gdal.h で宣言されており,GDT_Byte,GDT_UInt16,GDT_Int16,および GDT_Float32 が含まれます. ブロックサイズは,データにアクセスするための自然または効率的なブロックサイズを確立するために使用されます. タイルデータセットの場合,これはタイルのサイズになりますが,ほとんどの他のデータセットの場合は,この場合のように1 スキャンラインになります.

次に,実際に画像データを読み取るコードの実装, IReadBlock() を見ます.

CPLErr JDEMRasterBand::IReadBlock( int /* nBlockXOff */,
                                   int nBlockYOff,
                                   void * pImage )

{
    JDEMDataset *poGDS = cpl::down_cast<JDEMDataset *>(poDS);

    if (m_pszRecord == nullptr)
    {
        if (m_bBufferAllocFailed)
            return CE_Failure;

        m_pszRecord = static_cast<char *>(VSI_MALLOC_VERBOSE(m_nRecordSize));
        if (m_pszRecord == nullptr)
        {
            m_bBufferAllocFailed = true;
            return CE_Failure;
        }
    }

    CPL_IGNORE_RET_VAL(
        VSIFSeekL(poGDS->m_fp, 1011 + m_nRecordSize * nBlockYOff, SEEK_SET));

    if( VSIFReadL(m_pszRecord, m_nRecordSize, 1, poGDS->m_fp) != 1 )
    {
        CPLError(CE_Failure, CPLE_AppDefined,
                 "Cannot read scanline %d", nBlockYOff);
        return CE_Failure;
    }

    if( !EQUALN(reinterpret_cast<char *>(poGDS->m_abyHeader), m_pszRecord, 6) )
    {
        CPLError(CE_Failure, CPLE_AppDefined,
                 "JDEM Scanline corrupt.  Perhaps file was not transferred "
                 "in binary mode?");
        return CE_Failure;
    }

    if( JDEMGetField(m_pszRecord + 6, 3) != nBlockYOff + 1 )
    {
        CPLError(CE_Failure, CPLE_AppDefined,
                 "JDEM scanline out of order, JDEM driver does not "
                 "currently support partial datasets.");
        return CE_Failure;
    }

    for( int i = 0; i < nBlockXSize; i++ )
        static_cast<float *>(pImage)[i] =
            JDEMGetField(m_pszRecord + 9 + 5 * i, 5) * 0.1f;

    return CE_None;
}

注意すべき重要な項目は次のとおりです:

  • GDALRasterBand::poDS メンバを所有するデータセットの派生型にキャストするのは一般的です.ラスターバンドクラスが所有するデータセットオブジェクトに特権的なアクセスが必要な場合,それが friend として宣言されていることを確認してください(簡潔さのために省略されています).

  • エラーが発生した場合は,CPLError() で報告し,CE_Failure を返します. それ以外の場合は,CE_None を返します.

  • pImage バッファには,1 ブロックのデータが格納されている必要があります. ブロックは,ラスターバンドの nBlockXSize および nBlockYSize で宣言されたサイズです. pImage 内のデータのタイプは,ラスターバンドオブジェクトの eDataType で宣言されたタイプと一致している必要があります.

  • nBlockXOff および nBlockYOff はブロックオフセットであり,128x128 タイルデータセットの場合,1 と 1 の値は,ブロックが (128,128) から (255,255) になることを示しています.

ドライバー

JDEMDataset と JDEMRasterBand は,画像データを読み取るために使用する準備ができていますが,GDAL システムが新しいドライバーについてどのように知るかはまだ明確ではありません.これは GDALDriverManager を介して実現されます. フォーマットを登録するために,登録関数を実装します. 宣言は gcore/gdal_frmts.h にあります: void CPL_DLL GDALRegister_JDEM(void);

ドライバーファイルの定義は次のようになります:

void GDALRegister_JDEM()

{
    if( !GDAL_CHECK_VERSION("JDEM") )
        return;

    if( GDALGetDriverByName("JDEM") != nullptr )
        return;

    GDALDriver *poDriver = new GDALDriver();

    poDriver->SetDescription("JDEM");
    poDriver->SetMetadataItem(GDAL_DCAP_RASTER, "YES");
    poDriver->SetMetadataItem(GDAL_DMD_LONGNAME, "Japanese DEM (.mem)");
    poDriver->SetMetadataItem(GDAL_DMD_HELPTOPIC, "drivers/raster/jdem.html");
    poDriver->SetMetadataItem(GDAL_DMD_EXTENSIONS, "mem");
    poDriver->SetMetadataItem(GDAL_DCAP_VIRTUALIO, "YES");

    poDriver->pfnOpen = JDEMDataset::Open;
    poDriver->pfnIdentify = JDEMDataset::Identify;

    GetGDALDriverManager()->RegisterDriver(poDriver);
}

GDAL_CHECK_VERSION マクロの使用に注意してください. これは,プラグインとしてビルドできるドライバーで使用する必要があるマクロです. GDAL C++ ABI は,例えば GDAL 3.x.0 から 3.y.0 に変更される可能性があり,変更される必要があります. したがって,ヘッダーファイルのバージョンにしたがってGDALドライバーを再コンパイルする必要があります.GDAL_CHECK_VERSION マクロは,ドライバーがコンパイルされた GDAL バージョンと実行されているバージョンが互換性があるかどうかを確認します(メジャーバージョンとマイナーバージョンが等しいかどうかを確認します). ただし,同じリリースブランチのリリースでは C++ ABI は安定したままです(つまり,特定の機能リリース x.y.0 のバグ修正リリース x.y.z に対して).

登録関数は,最初に呼び出されたときに GDALDriver オブジェクトのインスタンスを作成し,GDALDriverManager に登録します. GDALDriverManager に登録する前に,次のフィールドをドライバーに設定できます.

  • 説明はフォーマットの短い名前です. これは,このフォーマットの一意の名前であり,スクリプトやコマンドラインプログラムでドライバーを識別するためによく使用されます. 通常,3-5 文字の長さで,フォーマットクラスのプレフィックスと一致します. (必須)

  • GDAL_DCAP_RASTER: このドライバーがラスターデータを処理することを示すために YES に設定します. (必須)

  • GDAL_DMD_LONGNAME: ファイルフォーマットのためのより詳細な説明的な名前ですが,50-60 文字を超えないようにします. (必須)

  • GDAL_DMD_HELPTOPIC: このドライバーに表示するヘルプトピックの名前です. この場合,JDEM フォーマットは,gdal/html に保持されているさまざまなフォーマットのウェブページに含まれています. (オプション)

  • GDAL_DMD_EXTENSIONS: このタイプのファイルに使用される拡張子です. 先頭の '.' は含まれません.複数ある場合は,スペースで区切る必要があります. (オプション)

  • GDAL_DMD_MIMETYPE: このファイルフォーマットの標準 MIME タイプ,例えば "image/png" です. (オプション)

  • GDAL_DMD_CREATIONOPTIONLIST: 作成オプションを記述するメカニズムに関する進化する作業があります.これについての例については,geotiff ドライバーを参照してください. (オプション)

  • GDAL_DMD_CREATIONDATATYPES: 新しいデータセットを作成するときにサポートされるスペースで区切られたデータタイプのリストです. Create() メソッドが存在する場合,これらはサポートされます. CreateCopy() メソッドが存在する場合,これは損失なくエクスポートできるタイプのリストですが,最終的に書き込まれるタイプよりも弱いデータタイプが含まれる可能性があります. たとえば,CreateCopy() メソッドを持つフォーマットで,常に Float32 を書き込む場合,Byte,Int16,および UInt16 をリストアップすることがあります. これは,Float32 に損失なく変換できるためです. 例の値は "Byte Int16 UInt16" です. (必須 - 作成がサポートされている場合)

  • GDAL_DCAP_VIRTUALIO: このドライバーが VSI*L GDAL API で開かれたファイルを処理できることを示すために,YES に設定します. それ以外の場合,このメタデータ項目は定義されていない必要があります. (オプション)

  • pfnOpen: このフォーマットのファイルを開こうとするために呼び出す関数です. (オプション)

  • pfnIdentify: このフォーマットのファイルを識別しようとするために呼び出す関数です.ドライバーは,ファイルを自分のフォーマットとして認識する場合は 1 を返し,ファイルを自分のフォーマットとして認識しない場合は 0 を返し,ヘッダバイトを調べるだけでは確定的な結論に達することができない場合は -1 を返します. (オプション)

  • pfnCreate: このフォーマットの新しい更新可能データセットを作成するために呼び出す関数です. (オプション)

  • pfnCreateCopy: 他のソースからコピーされたこのフォーマットの新しいデータセットを作成するために呼び出す関数ですが,必ずしも更新可能ではありません. (オプション)

  • pfnDelete: このフォーマットのデータセットを削除するために呼び出す関数です. (オプション)

  • pfnUnloadDriver: ドライバーが破棄されたときにのみ呼び出される関数です. ドライバーレベルでデータをクリーンアップするために使用できます.ほとんど使用されません. (オプション)

プラグインとしてビルドできるドライバー(つまり,ランタイムで GDAL によってロードされるスタンドアロン共有オブジェクト)の場合,GDAL 3.9 以降と RFC 96: Deferred C++ plugin loading 以降,ドライバーを実装する方法があります. この方法では,プラグインは必要なときにのみロードされ,すぐに GDALAllRegister() 実行時にロードされません.遅延プラグインロードに互換性のあるドライバーにするために必要な変更については, Example of changes to do on a simplified driver を参照してください.

ドライバーを GDAL ツリーに追加

JDEM ドライバーにアクセスするためには,上位レベルのプログラムで GDALRegister_JDEM() メソッドを呼び出す必要があります.新しいドライバーを書くときの通常の方法は次のとおりです:

  • frmts の下にドライバーディレクトリを追加し,ディレクトリ名を短い名前と同じにします.

  • そのディレクトリに他の類似のディレクトリ(つまり jdem ディレクトリ)からモデルとなる CMakeLists.txt を追加します.

  • 新しいドライバーを frmts/CMakeLists.txt で参照し,gdal_optional_format() または gdal_dependent_format() 関数を使用します.外部依存関係がない場合は gdal_optional_format() を使用し,少なくとも 1 つの依存関係がある場合は gdal_dependent_format() を使用します.

  • モジュールにデータセットとラスターバンドの実装を追加します. 一般的には <short_name>dataset.cpp と呼ばれ,GDAL 固有のコードが 1 つのファイルにすべて含まれていますが,これは必須ではありません.

  • 登録エントリーポイント宣言(GDALRegister_JDEM()) を gcore/gdal_frmts.h に追加します.

  • 適切な #ifdef で保護された frmts/gdalallregister.cpp に登録関数を追加します.

これらのすべてが完了すると,GDAL を再構築し,新しいフォーマットをすべてのユーティリティで利用できるようにすることができます. gdalinfo ユーティリティを使用して,フォーマットの開くことと報告が機能しているかどうかをテストし, gdal_translate ユーティリティを使用して画像の読み取りをテストできます.

ジオリファレンシングの追加

次に,ジオリファレンシングサポートを追加して,例をさらに進めます. GDALDataset ベースクラスのメソッドのシグネチャと完全に一致するように,JDEMDataset に次の 2 つの仮想メソッドオーバーライドを追加します.

CPLErr GetGeoTransform( double * padfTransform ) override;
const OGRSpatialReference* GetSpatialRef() const override;

The implementation of GDALDataset::GetGeoTransform() just copies the usual geotransform matrix into the supplied buffer. Note that GDALDataset::GetGeoTransform() may be called a lot, so it isn't generally wise to do a lot of computation in it. In many cases the Open() will collect the geotransform, and this method will just copy it over. Also note that the geotransform return is based on an anchor point at the top left corner of the top left pixel, not the center of pixel approach used in some packages.

CPLErr JDEMDataset::GetGeoTransform( double *padfTransform )
{
    const char *psHeader = reinterpret_cast<const char *>(m_abyHeader);

    const double dfLLLat = JDEMGetAngle(psHeader + 29);
    const double dfLLLong = JDEMGetAngle(psHeader + 36);
    const double dfURLat = JDEMGetAngle(psHeader + 43);
    const double dfURLong = JDEMGetAngle(psHeader + 50);

    padfTransform[0] = dfLLLong;
    padfTransform[3] = dfURLat;
    padfTransform[1] = (dfURLong - dfLLLong) / GetRasterXSize();
    padfTransform[2] = 0.0;

    padfTransform[4] = 0.0;
    padfTransform[5] = -1 * (dfURLat - dfLLLat) / GetRasterYSize();

    return CE_None;
}

The GDALDataset::GetSpatialRef() method returns a pointer to an internal OGRSpatialReference object.

const OGRSpatialReference *JDEMDataset::GetSpatialRef() const
{
    return &m_oSRS;
}

この場合,このフォーマットのすべてのファイルに対して座標系が固定されており, JDEMDataset コンストラクタで初期化されています.しかし,より複雑な場合には,定義を動的に構成する必要がある場合があります. その場合,定義を構築するのに OGRSpatialReference クラスを使用すると便利です.

JDEMDataset::JDEMDataset()
{
    std::fill_n(m_abyHeader, CPL_ARRAYSIZE(m_abyHeader), static_cast<GByte>(0));
    m_oSRS.SetAxisMappingStrategy(OAMS_TRADITIONAL_GIS_ORDER);
    m_oSRS.importFromEPSG(4301); // Tokyo geographic CRS
}

これで,JDEM ドライバーの機能の説明が完了しました. 必要に応じて,jdemdataset.cpp の完全なソースを確認できます.

オーバービュー

GDAL は,ファイルフォーマットが事前に構築されたオーバービューをアプリケーションで利用可能にするために, GDALRasterBand::GetOverview() および関連メソッドを使用することを許可しています. ただし,これを実装することはかなり複雑であり,現時点ではこのドキュメントの範囲を超えています. GeoTIFF ドライバー(gdal/frmts/gtiff/geotiff.cpp) および関連ソースは,オーバービューの報告と作成サポートを実装するファイルフォーマットの例を確認できます.

フォーマットは, GDALRasterBand::HasArbitraryOverviews() メソッドをオーバーライドして,GDALRasterBand で TRUE を返すことで,任意のオーバービューを持っていることを報告することもできます.この場合,ラスターバンドオブジェクトは,効率的なイメージのリサンプリングアクセスを実装するために, GDALRasterBand::RasterIO() メソッド自体をオーバーライドすることが期待されます. これも複雑であり,RasterIO() メソッドの正しい実装には多くの要件があります. これに関する例は,OGDI および ECW フォーマットで見つけることができます.

ただし,オーバービューを実装する最も一般的なアプローチは,データセットと同じ名前の TIFF ファイルに格納された外部オーバービューを使用することですが,拡張子 .ovr が追加されます. このスタイルのオーバービューの読み取りと作成を有効にするためには,GDALDataset が自身内部の oOvManager オブジェクトを初期化する必要があります. これは通常,Open() メソッドの最後の方(PAM GDALDataset::TryLoadXML() の後)に次のような呼び出しで実行されます.

poDS->oOvManager.Initialize(poDS.get(), poOpenInfo->pszFilename);

これにより,フォーマットのオーバービューの読み取りと作成のデフォルト実装が有効になります.カスタムオーバービューメカニズムが結び付けられる場合を除いて,すべてのシンプルなファイルシステムベースのフォーマットに対してこれを有効にすることが推奨されます.

ファイルの作成

ファイルの作成には 2 つのアプローチがあります. 最初の方法は, GDALDriver::CreateCopy() メソッドと呼ばれ,出力フォーマットでファイルを書き込む関数を実装し,ソース GDALDataset から必要なすべてのイメージおよびその他の情報を取得します.2 番目の方法である動的作成方法は,ファイルのシェルを作成する Create メソッドを実装し,その後,アプリケーションがさまざまな情報を設定メソッドを呼び出して書き込むことを含みます.

最初の方法の利点は,出力ファイルが作成されている時点ですべての情報が利用可能であることです.ファイルが作成される時点でカラーマップやジオリファレンシング情報などの情報が必要な外部ライブラリを使用してファイルフォーマットを実装する場合に特に重要です. この方法のもう 1 つの利点は,CreateCopy() メソッドが,最小/最大値,スケーリング,説明および GCP など,設定メソッドが存在しないいくつかの情報を読み取ることができることです.

2 番目の方法の利点は,アプリケーションが空の新しいファイルを作成し,利用可能になるとすぐに結果を書き込むことができることです.望ましいデータの完全なイメージが事前に利用可能である必要はありません.

非常に重要なフォーマットの場合は,両方の方法を実装することができます. それ以外の場合は,より簡単な方法を選択するか,必要な機能を提供します.

CreateCopy

The GDALDriver::CreateCopy() method call is passed through directly, so that method should be consulted for details of arguments. However, some things to keep in mind are:

  • bStrict フラグが FALSE の場合,ドライバーは,ソースデータセットを正確に表現できない場合に,データ型を変換したり,ジオリファレンシングを削除したりするなど,何らかの合理的な処理を試みる必要があります.

  • 進行状況の報告を正しく実装することは,かなり複雑です. 進行状況関数の戻り結果は常にキャンセルされているかどうかを確認する必要があり,進行状況は適切な間隔で報告する必要があります. JPEGCreateCopy() メソッドは,進行状況関数の適切な処理を示しています.

  • 特別な作成オプションは,オンラインヘルプに記載する必要があります. オプションが "NAME=VALUE" 形式を取る場合,JPEGCreateCopy() の QUALITY および PROGRESSIVE フラグの処理で示されているように,papszOptions リストは CPLFetchNameValue() を使用して操作できます.

  • 返された GDALDataset ハンドルは,ReadOnly モードまたは Update モードである可能性があります. 実用的であれば,Update モードで返し,それ以外の場合は ReadOnly モードで問題ありません.

JPEG の CreateCopy 関数の完全な実装(これは GDALDriver オブジェクトの pfnCreateCopy に割り当てられています)はここにあります. static GDALDataset *

JPEGCreateCopy( const char * pszFilename, GDALDataset *poSrcDS,
                int bStrict, char ** papszOptions,
                GDALProgressFunc pfnProgress, void * pProgressData )
{
    const int nBands = poSrcDS->GetRasterCount();
    const int nXSize = poSrcDS->GetRasterXSize();
    const int nYSize = poSrcDS->GetRasterYSize();
    // Some some rudimentary checks
    if( nBands != 1 && nBands != 3 )
    {
        CPLError(CE_Failure, CPLE_NotSupported,
                "JPEG driver doesn't support %d bands.  Must be 1 (grey) "
                "or 3 (RGB) bands.", nBands);
        return NULL;
    }

    if( poSrcDS->GetRasterBand(1)->GetRasterDataType() != GDT_Byte && bStrict )
    {
        CPLError(CE_Failure, CPLE_NotSupported,
                "JPEG driver doesn't support data type %s. "
                "Only eight bit byte bands supported.",
                GDALGetDataTypeName(
                    poSrcDS->GetRasterBand(1)->GetRasterDataType()));
        return NULL;
    }

    // What options has the user selected?
    int nQuality = 75;
    if( CSLFetchNameValue(papszOptions, "QUALITY") != NULL )
    {
        nQuality = atoi(CSLFetchNameValue(papszOptions, "QUALITY"));
        if( nQuality < 10 || nQuality > 100 )
        {
            CPLError(CE_Failure, CPLE_IllegalArg,
                    "QUALITY=%s is not a legal value in the range 10 - 100.",
                    CSLFetchNameValue(papszOptions, "QUALITY"));
            return NULL;
        }
    }

    bool bProgressive = false;
    if( CSLFetchNameValue(papszOptions, "PROGRESSIVE") != NULL )
    {
        bProgressive = true;
    }

    // Create the dataset.
    VSILFILE *fpImage = VSIFOpenL(pszFilename, "wb");
    if( fpImage == NULL )
    {
        CPLError(CE_Failure, CPLE_OpenFailed,
                "Unable to create jpeg file %s.",
                pszFilename);
        return NULL;
    }

    // Initialize JPG access to the file.
    struct jpeg_compress_struct sCInfo;
    struct jpeg_error_mgr sJErr;
    sCInfo.err = jpeg_std_error(&sJErr);
    jpeg_create_compress(&sCInfo);
    jpeg_stdio_dest(&sCInfo, fpImage);
    sCInfo.image_width = nXSize;
    sCInfo.image_height = nYSize;
    sCInfo.input_components = nBands;
    if( nBands == 1 )
    {
        sCInfo.in_color_space = JCS_GRAYSCALE;
    }
    else
    {
        sCInfo.in_color_space = JCS_RGB;
    }
    jpeg_set_defaults(&sCInfo);
    jpeg_set_quality(&sCInfo, nQuality, TRUE);
    if( bProgressive )
        jpeg_simple_progression(&sCInfo);
    jpeg_start_compress(&sCInfo, TRUE);

    // Loop over image, copying image data.
    GByte *pabyScanline = static_cast<GByte *>(CPLMalloc(nBands * nXSize));
    for( int iLine = 0; iLine < nYSize; iLine++ )
    {
        for( int iBand = 0; iBand < nBands; iBand++ )
        {
            GDALRasterBand * poBand = poSrcDS->GetRasterBand(iBand + 1);
            const CPLErr eErr =
                poBand->RasterIO(GF_Read, 0, iLine, nXSize, 1,
                                pabyScanline + iBand, nXSize, 1, GDT_Byte,
                                nBands, nBands * nXSize);
            // TODO: Handle error.
        }
        JSAMPLE *ppSamples = pabyScanline;
        jpeg_write_scanlines(&sCInfo, &ppSamples, 1);
    }
    CPLFree(pabyScanline);
    jpeg_finish_compress(&sCInfo);
    jpeg_destroy_compress(&sCInfo);
    VSIFCloseL(fpImage);
    return static_cast<GDALDataset *>(GDALOpen(pszFilename, GA_ReadOnly));
}

動的作成

動的作成の場合,ソースデータセットはありません. 代わりに,希望するファイルのサイズ,バンド数,およびピクセルデータ型が提供されますが,他の情報(ジオリファレンシングやイメージデータなど)は,後で生成された GDALDataset に対する他のメソッド呼び出しで提供されます.

次のサンプルは,PCI .aux ラベル付きの raw ラスター作成を実装しています. これは,非 GDAL 呼び出しを使用して空のがありますが,有効なファイルを作成し,最後に GDALOpen(,GA_Update) を呼び出して書き込み可能なファイルハンドルを返す一般的なアプローチに従っています.これにより,Open() 関数でさまざまな設定アクションを複製する必要がなくなります.

GDALDataset *PAuxDataset::Create( const char * pszFilename,
                                int nXSize, int nYSize, int nBands,
                                GDALDataType eType,
                                char ** /* papszParamList */ )
{
    // Verify input options.
    if( eType != GDT_Byte && eType != GDT_Float32 &&
        eType != GDT_UInt16 && eType != GDT_Int16 )
    {
        CPLError(
            CE_Failure, CPLE_AppDefined,
            "Attempt to create PCI .Aux labeled dataset with an illegal "
            "data type (%s).",
            GDALGetDataTypeName(eType));
        return NULL;
    }

    // Try to create the file.
    FILE *fp = VSIFOpen(pszFilename, "w");
    if( fp == NULL )
    {
        CPLError(CE_Failure, CPLE_OpenFailed,
                "Attempt to create file `%s' failed.",
                pszFilename);
        return NULL;
    }

    // Just write out a couple of bytes to establish the binary
    // file, and then close it.
    VSIFWrite("\0\0", 2, 1, fp);
    VSIFClose(fp);

    // Create the aux filename.
    char *pszAuxFilename = static_cast<char *>(CPLMalloc(strlen(pszFilename) + 5));
    strcpy(pszAuxFilename, pszFilename);;
    for( int i = strlen(pszAuxFilename) - 1; i > 0; i-- )
    {
        if( pszAuxFilename[i] == '.' )
        {
            pszAuxFilename[i] = '\0';
            break;
        }
    }
    strcat(pszAuxFilename, ".aux");

    // Open the file.
    fp = VSIFOpen(pszAuxFilename, "wt");
    if( fp == NULL )
    {
        CPLError(CE_Failure, CPLE_OpenFailed,
                "Attempt to create file `%s' failed.",
                pszAuxFilename);
        return NULL;
    }

    // We need to write out the original filename but without any
    // path components in the AuxiliaryTarget line.  Do so now.
    int iStart = strlen(pszFilename) - 1;
    while( iStart > 0 && pszFilename[iStart - 1] != '/' &&
        pszFilename[iStart - 1] != '\\' )
        iStart--;
    VSIFPrintf(fp, "AuxilaryTarget: %s\n", pszFilename + iStart);

    // Write out the raw definition for the dataset as a whole.
    VSIFPrintf(fp, "RawDefinition: %d %d %d\n",
            nXSize, nYSize, nBands);

    // Write out a definition for each band.  We always write band
    // sequential files for now as these are pretty efficiently
    // handled by GDAL.
    int nImgOffset = 0;
    for( int iBand = 0; iBand < nBands; iBand++ )
    {
        const int nPixelOffset = GDALGetDataTypeSize(eType)/8;
        const int nLineOffset = nXSize * nPixelOffset;
        const char *pszTypeName = NULL;
        if( eType == GDT_Float32 )
            pszTypeName = "32R";
        else if( eType == GDT_Int16 )
            pszTypeName = "16S";
        else if( eType == GDT_UInt16 )
            pszTypeName = "16U";
        else
            pszTypeName = "8U";
        VSIFPrintf( fp, "ChanDefinition-%d: %s %d %d %d %s\n",
                    iBand + 1, pszTypeName,
                    nImgOffset, nPixelOffset, nLineOffset,
#ifdef CPL_LSB
                    "Swapped"
#else
                    "Unswapped"
#endif
                    );
        nImgOffset += nYSize * nLineOffset;
    }

    // Cleanup.
    VSIFClose(fp);
    return static_cast<GDALDataset *>(GDALOpen(pszFilename, GA_Update));
}

動的作成をサポートするファイルフォーマット,または単に更新インプレースアクセスをサポートするファイルフォーマットは,ラスターバンドクラスに IWriteBlock() メソッドを実装する必要があります. これは, IReadBlock() に類似したセマンティクスを持ちます.さらに,さまざまな難解な理由から,ラスターバンドデストラクタに FlushCache() メソッドを実装することが重要です.これは,デストラクタが呼び出される前にバンドの書き込みキャッシュブロックがフラッシュされることを保証するためです.

RawDataset/RawRasterBand ヘルパークラス

多くのファイルフォーマットは,実際のイメージデータが通常のバイナリ形式でスキャンライン指向の形式で格納されています.各フォーマットに対してこれを再実装する代わりに,効率的かつ便利なアクセスを実装するために,gcore/ で宣言された RawDataset および RawRasterBand クラスが提供されています.

この場合,フォーマット固有のバンドクラスが必要ない場合があります. または,必要な場合は RawRasterBand から派生させることができます.データセットクラスは RawDataset から派生させる必要があります.

データセットの Open() メソッドは,そのレイアウト情報をコンストラクタに渡してラスターバンドをインスタンス化します.たとえば,PNM ドライバーは,次の呼び出しを使用してラスターバンドを作成します.

if( poOpenInfo->pabyHeader[1] == '5' )
{
    poDS->SetBand(
        1, new RawRasterBand(poDS, 1, poDS->fpImage,
                            iIn, 1, nWidth, GDT_Byte, TRUE));
}
else
{
    poDS->SetBand(
        1, new RawRasterBand(poDS, 1, poDS->fpImage,
                            iIn, 3, nWidth*3, GDT_Byte, TRUE));
    poDS->SetBand(
        2, new RawRasterBand(poDS, 2, poDS->fpImage,
                            iIn+1, 3, nWidth*3, GDT_Byte, TRUE));
    poDS->SetBand(
        3, new RawRasterBand(poDS, 3, poDS->fpImage,
                            iIn+2, 3, nWidth*3, GDT_Byte, TRUE));
}

RawRasterBand は,次の引数を取ります.

  • poDS: このバンドが子になる GDALDataset. このデータセットは RawRasterDataset から派生したクラスである必要があります.

  • nBand: そのデータセットのバンド,1 から始まる.

  • fpRaw: ラスターデータを含むファイルへの FILE * ハンドル.

  • nImgOffset: 最初のスキャンラインのラスタデータの最初のピクセルまでのバイトオフセット.

  • nPixelOffset: 1 つのピクセルの開始から次のスキャンライン内の開始までのバイトオフセット.

  • nLineOffset: 1 つのスキャンラインの開始から次の開始までのバイトオフセット.

  • eDataType: ディスク上のデータのタイプのための GDALDataType コード.

  • bNativeOrder: データが GDAL が実行されているマシンと同じエンディアンでない場合は FALSE. データは自動的にバイトスワップされます.

Raw サービスを利用するシンプルなファイルフォーマットは,通常,gdal/frmts/raw ディレクトリ内の 1 つのファイルに配置されます.そこには,フォーマットの実装の多くの例があります.

メタデータおよびその他のエキゾチックな拡張

GDAL データモデルには, GDALDataset および GDALRasterBand に仮想メソッドが存在するさまざまな項目があります.これには次のものが含まれます:

  • メタデータ: データセットまたはバンドに関する名前/値テキスト値. GDALMajorObject (GDALRasterBand および GDALDataset の基本クラス) は,メタデータを保持するための組み込みサポートを持っているため,読み取りアクセスの場合は,Open() で SetMetadataItem() を呼び出すだけでよいです. SAR_CEOS (frmts/ceos2/sar_ceosdataset.cpp) および GeoTIFF ドライバーは,読み取り可能なメタデータを実装しているドライバーの例です.

  • カラーテーブル: GDT_Byte ラスターバンドには,それに関連付けられたカラーテーブルが存在することができます.frmts/png/pngdataset.cpp ドライバーには,カラーテーブルをサポートするフォーマットの例が含まれています.

  • ColorInterpretation: PNG ドライバーには,バンドを赤,緑,青,アルファまたはグレースケールバンドとして扱うかどうかを示すドライバーの例が含まれています.

  • GCPs: GDALDatasets には,ラスターをジオリファレンス座標に関連付けるための一連の地上制御点が関連付けられている場合があります(GetGeotransform() によって返される明示的なアフィン変換とは異なります). MFF2 (gdal/frmts/raw/hkvdataset.cpp) フォーマットは,GCPs をサポートするフォーマットの単純な例です.

  • NoDataValue: 既知の "nodata" 値を持つバンドは,GetNoDataValue() メソッドを実装することができます.これに関する例については,PAux (frmts/raw/pauxdataset.cpp) を参照してください.

  • カテゴリ名: 各クラスに名前を付けた分類画像は,GetCategoryNames() メソッドを使用してそれらを返すことができます.ただし,現在のところ,このメソッドを実装しているフォーマットはありません.