File: ogr_drivertut.dox

package info (click to toggle)
gdal 1.10.1%2Bdfsg-8
  • links: PTS, VCS
  • area: main
  • in suites: jessie, jessie-kfreebsd
  • size: 84,320 kB
  • ctags: 74,726
  • sloc: cpp: 677,199; ansic: 162,820; python: 13,816; cs: 11,163; sh: 10,446; java: 5,279; perl: 4,429; php: 2,971; xml: 1,500; yacc: 934; makefile: 494; sql: 112
file content (434 lines) | stat: -rw-r--r-- 13,774 bytes parent folder | download
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
#ifndef DOXYGEN_SKIP
/* $Id: gdal_drivertut.dox,v 1.4 2006/10/18 13:26:45 mloskot Exp $ */
#endif /* DOXYGEN_SKIP */

/*!
\page ogr_drivertut OGR Driver Implementation Tutorial

\section odt_overall Overall Approach

In general new formats are added to OGR by implementing format specific
drivers with subclasses of OGRSFDriver, OGRDataSource and OGRLayer.  The
OGRSFDriver subclass is registered with the OGRSFDriverRegistrar at runtime.

Before following this tutorial to implement an OGR driver, please review 
the <a href="ogr_arch.html">OGR Architecture</a> document carefully. 

The tutorial will be based on implementing a simple ascii point format.

\section odt_toc Contents

<ol>
<li> \ref odt_driver_ro
<li> \ref odt_datasource_bro
<li> \ref odt_layer_bro
</ol>

\section odt_driver_ro Implementing OGRSFDriver

The format specific driver class is implemented as a subclass of OGRSFDriver.
One instance of the driver will normally be created, and registered with
the OGRSFDriverRegistrar().  The instantiation of the driver is normally 
handled by a global C callable registration function, similar to the
following placed in the same file as the driver class. 

\verbatim
void RegisterOGRSPF()

{
    OGRSFDriverRegistrar::GetRegistrar()->RegisterDriver( new OGRSPFDriver );
}
\endverbatim

The driver class declaration generally looks something like this for a
format with read or read and update access (the Open() method), creation 
support (the CreateDataSource() method), and the ability to delete a datasource
(the DeleteDataSource() method).

\verbatim
class OGRSPFDriver : public OGRSFDriver
{
  public:
                ~OGRSPFDriver();
                
    const char    *GetName();
    OGRDataSource *Open( const char *, int );
    OGRDataSource *CreateDataSource( const char *, char ** );
    OGRErr         DeleteDataSource( const char *pszName );
    int            TestCapability( const char * );
};
\endverbatim

The constructor generally does nothing.  The OGRSFDriver::GetName() method 
returns a static string with the name of the driver.  This name is specified 
on the commandline when creating datasources so it is generally good to keep 
it short and without any special characters or spaces. 

\verbatim
OGRSPFDriver::~OGRSPFDriver()

{
}

const char *OGRSPFDriver::GetName()
{
    return "SPF";
}
\endverbatim

The Open() method is called by OGRSFDriverRegistrar::Open(), or from the 
C API OGROpen().  The OGRSFDriver::Open() method should quietly return NULL if 
the passed
filename is not of the format supported by the driver.  If it is the target
format, then a new OGRDataSource object for the datasource should be returned.

It is common for the Open() method to be delegated to an Open() method on
the actual format's OGRDataSource class.

\verbatim
OGRDataSource *OGRSPFDriver::Open( const char * pszFilename, int bUpdate )
{
    OGRSPFDataSource   *poDS = new OGRSPFDataSource();

    if( !poDS->Open( pszFilename, bUpdate ) )
    {
        delete poDS;
	return NULL;
    }
    else
        return poDS;
}
\endverbatim

In OGR the capabilities of drivers, datasources and layers are determined
by calling TestCapability() on the various objects with names strings 
representing specific optional capabilities.  For the driver the only two
capabilities currently tested for are the ability to create datasources and
to delete them.  In our first pass as a read only SPF driver, these are
both disabled.  The default return value for unrecognised capabilities 
should always be FALSE, and the symbolic #defines for capability names
(defined in ogr_core.h) should be used instead of the literal strings to 
avoid typos. 

\verbatim
int OGRSPFDriver::TestCapability( const char * pszCap )

{
    if( EQUAL(pszCap,ODrCCreateDataSource) )
        return FALSE;
    else if( EQUAL(pszCap,ODrCDeleteDataSource) )
        return FALSE;
    else
        return FALSE;
}
\endverbatim

Examples of the CreateDataSource() and DeleteDataSource() methods are left 
for the section on creation and update. 

\section odt_datasource_bro Basic Read Only Data Source

We will start implementing a minimal read-only datasource.  No attempt is
made to optimize operations, and default implementations of many methods
inherited from OGRDataSource are used.  

The primary responsibility of the datasource is to manage the list of layers.
In the case of the SPF format a datasource is a single file representing one
layer so there is at most one layer.  The "name" of a datasource should 
generally be the name passed to the Open() method.  

The Open() method below is not overriding a base class method, but we have
it to implement the open operation delegated by the driver class. 

For this simple case we provide a stub TestCapability() that returns FALSE
for all extended capabilities.  The TestCapability() method is pure virtual,
so it does need to be implemented.

\verbatim
class OGRSPFDataSource : public OGRDataSource
{
    char                *pszName;
    
    OGRSPFLayer       **papoLayers;
    int                 nLayers;

  public:
                        OGRSPFDataSource();
                        ~OGRSPFDataSource();

    int                 Open( const char * pszFilename, int bUpdate );
    
    const char          *GetName() { return pszName; }

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

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

The constructor is a simple initializer to a default state.  The Open() will
take care of actually attaching it to a file.  The destructor is responsible
for orderly cleanup of layers.  

\verbatim
OGRSPFDataSource::OGRSPFDataSource()

{
    papoLayers = NULL;
    nLayers = 0;

    pszName = NULL;
}

OGRSPFDataSource::~OGRSPFDataSource()

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

    CPLFree( pszName );
}
\endverbatim

The Open() method is the most important one on the datasource, though
in this particular instance it passes most of it's work off to the
OGRSPFLayer constructor if it believes the file is of the desired format.

Note that Open() methods should try and determine that a file isn't of the 
identified format as efficiently as possible, since many drivers may be 
invoked with files of the wrong format before the correct driver is 
reached.  In this particular Open() we just test the file extension but this
is generally a poor way of identifying a file format.  If available, checking
"magic header values" or something similar is preferrable. 

In the case of the SPF format, update in place is not supported, 
so we always fail if bUpdate is FALSE. 

\verbatim
int  OGRSPFDataSource::Open( const char *pszFilename, int bUpdate )

{
// -------------------------------------------------------------------- 
//      Does this appear to be an .spf file?                           
// --------------------------------------------------------------------
    if( !EQUAL( CPLGetExtension(pszFilename), "spf" ) )
        return FALSE;

    if( bUpdate )
    {
	CPLError( CE_Failure, CPLE_OpenFailed, 
                  "Update access not supported by the SPF driver." );
        return FALSE;
    }

// -------------------------------------------------------------------- 
//      Create a corresponding layer.
// --------------------------------------------------------------------
    nLayers = 1;
    papoLayers = (OGRSPFLayer **) CPLMalloc(sizeof(void*));
    
    papoLayers[0] = new OGRSPFLayer( pszFilename );

    pszName = CPLStrdup( pszFilename );

    return TRUE;
}
\endverbatim

A GetLayer() method also needs to be implemented.  Since the layer list
is created in the Open() this is just a lookup with some safety testing.

\verbatim
OGRLayer *OGRSPFDataSource::GetLayer( int iLayer )

{
    if( iLayer < 0 || iLayer >= nLayers )
        return NULL;
    else
        return papoLayers[iLayer];
}
\endverbatim

\section odt_layer_bro Read Only Layer

The OGRSPFLayer is implements layer semantics for an .spf file.  It provides
access to a set of feature objects in a consistent coordinate system
with a particular set of attribute columns.  Our class definition looks like
this:

\verbatim
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; }
};
\endverbatim

The layer constructor is responsible for initialization.  The most important
initialization is setting up the OGRFeatureDefn for the layer.  This defines
the list of fields and their types, the geometry type and the coordinate 
system for the layer.  In the SPF format the set of fields is fixed - a 
single string field and we have no coordinate system info to set. 

Pay particular attention to the reference counting of the OGRFeatureDefn.
As OGRFeature's for this layer will also take a reference to this definition
it is important that we also establish a reference on behalf of the layer
itself. 

\verbatim
OGRSPFLayer::OGRSPFLayer( const char *pszFilename )

{
    nNextFID = 0;

    poFeatureDefn = new OGRFeatureDefn( CPLGetBasename( pszFilename ) );
    poFeatureDefn->Reference();
    poFeatureDefn->SetGeomType( wkbPoint );
   
    OGRFieldDefn oFieldTemplate( "Name", OFTString );

    poFeatureDefn->AddFieldDefn( &oFieldTemplate );

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

Note that the destructor uses Release() on the OGRFeatureDefn.  This will
destroy the feature definition if the reference count drops to zero, but if
the application is still holding onto a feature from this layer, then that
feature will hold a reference to the feature definition and it will not
be destroyed here (which is good!).  

\verbatim
OGRSPFLayer::~OGRSPFLayer()

{
    poFeatureDefn->Release();
    if( fp != NULL )
        VSIFCloseL( fp );
}
\endverbatim

The GetNextFeature() method is usually the work horse of OGRLayer 
implementations.  It is responsible for reading the next feature according
to the current spatial and attribute filters installed.  

The while() loop is present to loop until we find a satisfactory 
feature.  The first section of code is for parsing a single line of 
the SPF text file and establishing the x, y and name for the line.

\verbatim
OGRFeature *OGRSPFLayer::GetNextFeature()

{
    // --------------------------------------------------------------------
    //	Loop till we find a feature matching our requirements.
    // --------------------------------------------------------------------
    while( TRUE )
    {
        const char *pszLine;
        const char *pszName;
    
        pszLine = CPLReadLineL( fp );

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

        double dfX;
        double dfY;

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

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

\endverbatim

The next section turns the x, y and name into a feature.  Also note that
we assign a linearly incremented feature id.  In our case we started at
zero for the first feature, though some drivers start at 1.  

\verbatim
        OGRFeature *poFeature = new OGRFeature( poFeatureDefn );
        
        poFeature->SetGeometryDirectly( new OGRPoint( dfX, dfY ) );
        poFeature->SetField( 0, pszName );
        poFeature->SetFID( nNextFID++ );
\endverbatim

Next we check if the feature matches our current attribute or 
spatial filter if we have them.  Methods on the OGRLayer base class 
support maintain filters in the OGRLayer member fields m_poFilterGeom
(spatial filter) and m_poAttrQuery (attribute filter) so we can just use
these values here if they are non-NULL.  The following test is essentially
"stock" and done the same in all formats.  Some formats also do some 
spatial filtering ahead of time using a spatial index. 

If the feature meets our criteria we return it.  Otherwise we destroy it,
and return to the top of the loop to fetch another to try.  

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

        delete poFeature;
    }
}
\endverbatim

While in the middle of reading a feature set from a layer, or at any other
time the application can call ResetReading() which is intended to restart
reading at the beginning of the feature set.  We implement this by seeking
back to the beginning of the file, and resetting our feature id counter. 

\verbatim
void OGRSPFLayer::ResetReading()

{
    VSIFSeekL( fp, 0, SEEK_SET );
    nNextFID = 0;
}
\endverbatim

In this implementation we do not provide a custom implementation for the
GetFeature() method.  This means an attempt to read a particular feature
by it's feature id will result in many calls to GetNextFeature() till the 
desired feature is found.  However, in a sequential text format like spf
there is little else we could do anyway.

There! We have completed a simple read-only feature file format driver.  

*/