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

全体的なアプローチ

一般的に,新しいフォーマットは,フォーマット固有のドライバーを実装し, GDALDriverGDALDataset および OGRLayer のサブクラスをインスタンス化することでOGRに追加されます.GDALDriverインスタンスは,実行時に GDALDriverManager に登録されます.

OGRドライバーを実装するためにこのチュートリアルに従う前に, ベクターデーターモデル ドキュメントを注意深く確認してください.

このチュートリアルは,シンプルなasciiポイントフォーマットを実装することを基にしています.

GDALDriverの実装

フォーマット固有のドライバークラスは, GDALDriverのインスタンスとして実装されます.通常,ドライバーの1つのインスタンスが作成され, GDALDriverManagerに登録されます.ドライバーのインスタンス化は通常,グローバルなC呼び出し可能な登録関数によって処理されます.次のようなものが,ドライバークラスと同じファイルに配置されます.

void RegisterOGRSPF()
{
    if( GDALGetDriverByName("SPF") != NULL )
        return;

    GDALDriver *poDriver = new GDALDriver();

    poDriver->SetDescription("SPF");
    poDriver->SetMetadataItem(GDAL_DCAP_VECTOR, "YES");
    poDriver->SetMetadataItem(GDAL_DMD_LONGNAME, "Long name for SPF driver");
    poDriver->SetMetadataItem(GDAL_DMD_EXTENSION, "spf");
    poDriver->SetMetadataItem(GDAL_DMD_HELPTOPIC, "drv_spf.html");

    poDriver->pfnOpen = OGRSPFDriverOpen;
    poDriver->pfnIdentify = OGRSPFDriverIdentify;
    poDriver->pfnCreate = OGRSPFDriverCreate;

    poDriver->SetMetadataItem(GDAL_DCAP_VIRTUALIO, "YES");

    GetGDALDriverManager()->RegisterDriver(poDriver);
}

GDALDriver::SetDescription() は,ドライバーの名前を設定します.この名前は,データソースを作成する際にコマンドラインで指定されるため,一般的には短くして,特殊文字やスペースを含めないようにすると良いでしょう.

SetMetadataItem( GDAL_DCAP_VECTOR, "YES" ) は,ドライバーがベクターデータを処理することを示すために指定されます.

SetMetadataItem( GDAL_DCAP_VIRTUALIO, "YES" ) は,ドライバーがVSI*L GDAL APIで開かれたファイルを処理できることを示すために指定されます.それ以外の場合,このメタデータ項目は定義されていないはずです.

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

読み取りまたは読み取りおよび更新アクセス(Open() メソッド)と作成サポート(Create() メソッド)を持つフォーマットの場合,ドライバーの宣言は一般的に次のようになります.

static GDALDataset* OGRSPFDriverOpen(GDALOpenInfo* poOpenInfo);
static int          OGRSPFDriverIdentify(GDALOpenInfo* poOpenInfo);
static GDALDataset* OGRSPFDriverCreate(const char* pszName, int nXSize, int nYSize,
                                    int nBands, GDALDataType eDT, char** papszOptions);

Open() メソッドは, GDALOpenEx() によって呼び出されます.渡されたファイル名がドライバーでサポートされているフォーマットでない場合は,静かにNULLを返すべきです.対象のフォーマットである場合は,データセットの新しいGDALDatasetオブジェクトを返すべきです.

Open() メソッドが実際のフォーマットのGDALDatasetクラスのOpen() メソッドに委任されることが一般的です.

static GDALDataset *OGRSPFDriverOpen( GDALOpenInfo* poOpenInfo )
{
    if( !OGRSPFDriverIdentify(poOpenInfo) )
        return NULL;

    OGRSPFDataSource *poDS = new OGRSPFDataSource();
    if( !poDS->Open(poOpenInfo->pszFilename, poOpenInfo->eAccess == GA_Update) )
    {
        delete poDS;
        return NULL;
    }

    return poDS;
}

Identify() メソッドは次のように実装されます:

static int OGRSPFDriverIdentify( GDALOpenInfo* poOpenInfo )
{
    // Does this appear to be an .spf file?
    return EQUAL(CPLGetExtension(poOpenInfo->pszFilename), "spf");
}

Create() メソッドの例は,作成と更新のセクションに残されています.

基本的な読み取り専用データソース

最小限の読み取り専用データソースの実装を開始します.操作を最適化しようとはせず,多くのメソッドのデフォルト実装を使用します.

データソースの主な責任は,レイヤーのリストを管理することです.SPFフォーマットの場合,データソースは1つのレイヤーを表す単一のファイルであるため,最大1つのレイヤーがあります.データソースの"名前"は一般的にOpen() メソッドに渡される名前であるべきです.

以下のOpen() メソッドは基底クラスメソッドをオーバーライドしていませんが,ドライバークラスによって委任されたオープン操作を実装するために持っています.

この単純なケースでは,すべての拡張機能に対してFALSEを返すスタブ GDALDataset::TestCapability() を提供します.TestCapability() メソッドは純粋仮想なので,実装する必要があります.

class OGRSPFDataSource : public GDALDataset
{
    OGRSPFLayer       **papoLayers;
    int                 nLayers;

public:
                        OGRSPFDataSource();
                        ~OGRSPFDataSource();

    int                 Open( const char *pszFilename, int bUpdate );

    int                 GetLayerCount() { return nLayers; }
    OGRLayer            *GetLayer( int );

    int                 TestCapability( const char * ) { return FALSE; }
};

コンストラクタは,デフォルトの状態に初期化するためのシンプルな初期化子です.Open() は,実際にファイルにアタッチすることを担当します.デストラクタは,レイヤーの整理されたクリーンアップを担当します.

OGRSPFDataSource::OGRSPFDataSource()
{
    papoLayers = NULL;
    nLayers = 0;
}

OGRSPFDataSource::~OGRSPFDataSource()
{
    for( int i = 0; i < nLayers; i++ )
        delete papoLayers[i];
    CPLFree(papoLayers);
}

Open() メソッドは,データソース上で最も重要なメソッドですが,この特定のインスタンスでは,ファイルが目的のフォーマットであると信じている場合,ほとんどの作業をOGRSPFLayerコンストラクタに委任します.

Open() メソッドは,多くのドライバーが正しいドライバーに到達する前に,間違ったフォーマットのファイルで多くのドライバーが呼び出される可能性があるため,ファイルが識別されたフォーマットでないことをできるだけ効率的に判断するべきです.この特定のOpen() では,ファイルの拡張子のみをテストしていますが,これは一般的にファイルフォーマットを識別するための悪い方法です.利用可能であれば,"マジックヘッダー値"などをチェックすることが望ましいです.

SPFフォーマットの場合,更新はサポートされていないため,bUpdate が FALSE の場合は常に失敗します.

int  OGRSPFDataSource::Open( const char *pszFilename, int bUpdate )
{
    if( bUpdate )
    {
        CPLError(CE_Failure, CPLE_OpenFailed,
                "Update access not supported by the SPF driver.");
        return FALSE;
    }

    // Create a corresponding layer.
    nLayers = 1;
    papoLayers = static_cast<OGRSPFLayer **>(CPLMalloc(sizeof(void *)));

    papoLayers[0] = new OGRSPFLayer(pszFilename);

    pszName = CPLStrdup(pszFilename);

    return TRUE;
}

GetLayer() メソッドも実装する必要があります.レイヤーリストはOpen() で作成されるため,これは単なるルックアップといくつかの安全テストです.

OGRLayer *OGRSPFDataSource::GetLayer( int iLayer )
{
    if( iLayer < 0 || iLayer >= nLayers )
        return NULL;

    return papoLayers[iLayer];
}

読み取り専用レイヤー

OGRSPFLayer は .spf ファイルのレイヤーセマンティクスを実装します.特定の属性列を持つ一貫した座標系で一連の地物オブジェクトにアクセスします.クラスの定義は次のようになります:

class OGRSPFLayer : public OGRLayer
{
    OGRFeatureDefn     *poFeatureDefn;
    FILE               *fp;
    int                 nNextFID;

public:
    OGRSPFLayer( const char *pszFilename );
~OGRSPFLayer();

    void                ResetReading();
    OGRFeature *        GetNextFeature();

    OGRFeatureDefn *    GetLayerDefn() { return poFeatureDefn; }

    int                 TestCapability( const char * ) { return FALSE; }
};

レイヤーコンストラクタは初期化を担当します.最も重要な初期化は,レイヤーのための OGRFeatureDefn を設定することです.これは,フィールドとそのタイプのリスト,ジオメトリタイプおよびレイヤーの座標系を定義します.SPFフォーマットでは,フィールドのセットが固定されています - 単一の文字列フィールドがあり,設定する座標系情報はありません.

OGRFeatureDefn の参照カウントに特に注意してください.このレイヤーのOGRFeatureもこの定義への参照を取るため,レイヤー自体の代理で参照を確立することが重要です.

OGRSPFLayer::OGRSPFLayer( const char *pszFilename )
{
    nNextFID = 0;

    poFeatureDefn = new OGRFeatureDefn(CPLGetBasename(pszFilename));
    SetDescription(poFeatureDefn->GetName());
    poFeatureDefn->Reference();
    poFeatureDefn->SetGeomType(wkbPoint);

    OGRFieldDefn oFieldTemplate("Name", OFTString);

    poFeatureDefn->AddFieldDefn(&oFieldTemplate);

    fp = VSIFOpenL(pszFilename, "r");
    if( fp == NULL )
        return;
}

デストラクタは,OGRFeatureDefn に OGRFeatureDefn::Release() を使用します.参照カウントがゼロになるとフィーチャ定義が破棄されますが,アプリケーションがこのレイヤーから地物を保持している場合,その地物は地物定義への参照を保持し,ここで破棄されません(これは良いことです!).

OGRSPFLayer::~OGRSPFLayer()
{
    poFeatureDefn->Release();
    if( fp != NULL )
        VSIFCloseL(fp);
}

OGRLayer::GetNextFeature() メソッドは,通常,OGRLayer実装の主要な役割を果たします.現在の空間フィルターと属性フィルターに従って次のフィーチャを読み取る責任があります.

while() ループは,満足のいくフィーチャが見つかるまでループするために存在します.最初のコードセクションは,SPFテキストファイルの1行を解析し,その行のx, y, nameを確立するためのものです.

OGRFeature *OGRSPFLayer::GetNextFeature()
{
    // Loop till we find a feature matching our requirements.
    while( true )
    {
        const char *pszLine = CPLReadLineL(fp);

        // Are we at end of file (out of features)?
        if( pszLine == NULL )
            return NULL;

        const double dfX = atof(pszLine);

        pszLine = strstr(pszLine,"|");
        if( pszLine == NULL )
            continue; // we should issue an error!
        else
            pszLine++;

        const double dfY = atof(pszLine);

        pszLine = strstr(pszLine,"|");

        const char *pszName = NULL;
        if( pszLine == NULL )
            continue; // we should issue an error!
        else
            pszName = pszLine + 1;

次のセクションでは, x, y, name を地物に変換します.また,線形に増加する地物IDを割り当てることに注意してください.この場合,最初の地物はゼロから始めましたが,一部のドライバーは1から始めます.

OGRFeature *poFeature = new OGRFeature(poFeatureDefn);

poFeature->SetGeometryDirectly(new OGRPoint(dfX, dfY));
poFeature->SetField(0, pszName);
poFeature->SetFID(nNextFID++);

次に,現在の属性フィルターまたは空間フィルターがある場合,地物がそれに一致するかどうかをチェックします.OGRLayerベースクラスのメソッドは,OGRLayerメンバーフィールド OGRLayer::m_poFilterGeom (空間フィルター) および OGRLayer::m_poAttrQuery (属性フィルター) をサポートしているため,これらの値がNULLでない場合はここでこれらの値を使用できます.以下のテストは基本的に"標準"であり,すべてのフォーマットで同じように行われます.一部のフォーマットは,空間インデックスを使用して事前に空間フィルタリングも行います.

地物が基準を満たす場合は,それを返します.それ以外の場合は,それを破棄し,もう一度取得して試すためにループの先頭に戻ります.

        if( (m_poFilterGeom == NULL ||
            FilterGeometry(poFeature->GetGeometryRef())) &&
            (m_poAttrQuery == NULL ||
            m_poAttrQuery->Evaluate(poFeature)) )
            return poFeature;

        delete poFeature;
    }
}

レイヤーから地物セットを読み取っている途中,または他の任意の時点でアプリケーションは, OGRLayer::ResetReading() を呼び出すことができます.これは,地物セットの先頭からの読み取りを再開することを意図しています.これは,ファイルの先頭にシークし,地物IDカウンターをリセットすることで実装します.

void OGRSPFLayer::ResetReading()
{
    VSIFSeekL(fp, 0, SEEK_SET);
    nNextFID = 0;
}

この実装では,GetFeature() メソッドにカスタム実装を提供していません.これは,地物IDで特定の地物を読み取ろうとすると,望ましい地物が見つかるまでGetNextFeature() が多く呼び出されることを意味します.しかし,spfのような連続テキストフォーマットでは,他にすることはほとんどありません.

以上! シンプルな読み取り専用地物ファイルフォーマットドライバーが完成しました.