Coverage Report

Created: 2026-03-30 09:00

next uncovered line (L), next uncovered region (R), next uncovered branch (B)
/src/gdal/ogr/ogrsf_frmts/flatgeobuf/ogrflatgeobufdataset.cpp
Line
Count
Source
1
/******************************************************************************
2
 *
3
 * Project:  FlatGeobuf driver
4
 * Purpose:  Implements OGRFlatGeobufDataset class
5
 * Author:   Björn Harrtell <bjorn at wololo dot org>
6
 *
7
 ******************************************************************************
8
 * Copyright (c) 2018-2020, Björn Harrtell <bjorn at wololo dot org>
9
 *
10
 * SPDX-License-Identifier: MIT
11
 ****************************************************************************/
12
13
#include "ogr_flatgeobuf.h"
14
15
#include <memory>
16
17
#include "header_generated.h"
18
19
// For users not using CMake...
20
#ifndef flatbuffers
21
#error                                                                         \
22
    "Make sure to build with -Dflatbuffers=gdal_flatbuffers (for example) to avoid potential conflict of flatbuffers"
23
#endif
24
25
using namespace flatbuffers;
26
using namespace FlatGeobuf;
27
28
static int OGRFlatGeobufDriverIdentify(GDALOpenInfo *poOpenInfo)
29
124k
{
30
124k
    if (STARTS_WITH_CI(poOpenInfo->pszFilename, "FGB:"))
31
0
        return TRUE;
32
33
124k
    if (poOpenInfo->bIsDirectory)
34
5.13k
    {
35
5.13k
        return -1;
36
5.13k
    }
37
38
119k
    const auto nHeaderBytes = poOpenInfo->nHeaderBytes;
39
119k
    const auto pabyHeader = poOpenInfo->pabyHeader;
40
41
119k
    if (nHeaderBytes < 4)
42
42.4k
        return FALSE;
43
44
77.2k
    if (pabyHeader[0] == 0x66 && pabyHeader[1] == 0x67 && pabyHeader[2] == 0x62)
45
27.8k
    {
46
27.8k
        if (pabyHeader[3] == 0x03)
47
27.8k
        {
48
27.8k
            CPLDebug("FlatGeobuf", "Verified magicbytes");
49
27.8k
            return TRUE;
50
27.8k
        }
51
9
        else
52
9
        {
53
9
            CPLError(CE_Failure, CPLE_OpenFailed,
54
9
                     "Unsupported FlatGeobuf version %d.\n",
55
9
                     poOpenInfo->pabyHeader[3]);
56
9
        }
57
27.8k
    }
58
59
49.4k
    return FALSE;
60
77.2k
}
61
62
/************************************************************************/
63
/*                               Delete()                               */
64
/************************************************************************/
65
66
static CPLErr OGRFlatGoBufDriverDelete(const char *pszDataSource)
67
68
112
{
69
112
    VSIStatBufL sStatBuf;
70
71
112
    if (VSIStatL(pszDataSource, &sStatBuf) != 0)
72
0
    {
73
0
        CPLError(CE_Failure, CPLE_AppDefined,
74
0
                 "%s does not appear to be a file or directory.",
75
0
                 pszDataSource);
76
77
0
        return CE_Failure;
78
0
    }
79
80
112
    if (VSI_ISREG(sStatBuf.st_mode))
81
112
    {
82
112
        VSIUnlink(pszDataSource);
83
112
        return CE_None;
84
112
    }
85
86
0
    if (VSI_ISDIR(sStatBuf.st_mode))
87
0
    {
88
0
        char **papszDirEntries = VSIReadDir(pszDataSource);
89
90
0
        for (int iFile = 0;
91
0
             papszDirEntries != nullptr && papszDirEntries[iFile] != nullptr;
92
0
             iFile++)
93
0
        {
94
0
            if (EQUAL(CPLGetExtensionSafe(papszDirEntries[iFile]).c_str(),
95
0
                      "fgb"))
96
0
            {
97
0
                VSIUnlink(CPLFormFilenameSafe(pszDataSource,
98
0
                                              papszDirEntries[iFile], nullptr)
99
0
                              .c_str());
100
0
            }
101
0
        }
102
103
0
        CSLDestroy(papszDirEntries);
104
105
0
        VSIRmdir(pszDataSource);
106
0
    }
107
108
0
    return CE_None;
109
112
}
110
111
/************************************************************************/
112
/*                       RegisterOGRFlatGeobuf()                        */
113
/************************************************************************/
114
115
void RegisterOGRFlatGeobuf()
116
24
{
117
24
    if (GDALGetDriverByName("FlatGeobuf") != nullptr)
118
0
        return;
119
120
24
    GDALDriver *poDriver = new GDALDriver();
121
24
    poDriver->SetDescription("FlatGeobuf");
122
24
    poDriver->SetMetadataItem(GDAL_DCAP_VECTOR, "YES");
123
24
    poDriver->SetMetadataItem(GDAL_DCAP_CREATE_LAYER, "YES");
124
24
    poDriver->SetMetadataItem(GDAL_DCAP_CREATE_FIELD, "YES");
125
24
    poDriver->SetMetadataItem(GDAL_DCAP_DELETE_FIELD, "YES");
126
24
    poDriver->SetMetadataItem(GDAL_DCAP_REORDER_FIELDS, "YES");
127
24
    poDriver->SetMetadataItem(GDAL_DCAP_CURVE_GEOMETRIES, "YES");
128
24
    poDriver->SetMetadataItem(GDAL_DCAP_MEASURED_GEOMETRIES, "YES");
129
24
    poDriver->SetMetadataItem(GDAL_DCAP_Z_GEOMETRIES, "YES");
130
24
    poDriver->SetMetadataItem(GDAL_DCAP_REOPEN_AFTER_WRITE_REQUIRED, "YES");
131
24
    poDriver->SetMetadataItem(GDAL_DCAP_CAN_READ_AFTER_DELETE, "YES");
132
24
    poDriver->SetMetadataItem(GDAL_DCAP_MULTIPLE_VECTOR_LAYERS_IN_DIRECTORY,
133
24
                              "YES");
134
135
24
    poDriver->SetMetadataItem(GDAL_DMD_LONGNAME, "FlatGeobuf");
136
24
    poDriver->SetMetadataItem(GDAL_DMD_EXTENSION, "fgb");
137
24
    poDriver->SetMetadataItem(GDAL_DMD_HELPTOPIC,
138
24
                              "drivers/vector/flatgeobuf.html");
139
24
    poDriver->SetMetadataItem(GDAL_DCAP_VIRTUALIO, "YES");
140
24
    poDriver->SetMetadataItem(
141
24
        GDAL_DMD_CREATIONFIELDDATATYPES,
142
24
        "Integer Integer64 Real String Date DateTime Binary");
143
24
    poDriver->SetMetadataItem(GDAL_DMD_CREATIONFIELDDATASUBTYPES,
144
24
                              "Boolean Int16 Float32");
145
24
    poDriver->SetMetadataItem(GDAL_DMD_CREATION_FIELD_DEFN_FLAGS,
146
24
                              "WidthPrecision Comment AlternativeName");
147
148
24
    poDriver->SetMetadataItem(
149
24
        GDAL_DS_LAYER_CREATIONOPTIONLIST,
150
24
        "<LayerCreationOptionList>"
151
24
        "  <Option name='SPATIAL_INDEX' type='boolean' description='Whether to "
152
24
        "create a spatial index' default='YES'/>"
153
24
        "  <Option name='TEMPORARY_DIR' type='string' description='Directory "
154
24
        "where temporary file should be created'/>"
155
24
        "  <Option name='TITLE' type='string' description='Layer title'/>"
156
24
        "  <Option name='DESCRIPTION' type='string' "
157
24
        "description='Layer description'/>"
158
24
        "</LayerCreationOptionList>");
159
24
    poDriver->SetMetadataItem(
160
24
        GDAL_DMD_OPENOPTIONLIST,
161
24
        "<OpenOptionList>"
162
24
        "  <Option name='VERIFY_BUFFERS' type='boolean' description='Verify "
163
24
        "flatbuffers integrity' default='YES'/>"
164
24
        "</OpenOptionList>");
165
166
24
    poDriver->SetMetadataItem(GDAL_DCAP_COORDINATE_EPOCH, "YES");
167
24
    poDriver->SetMetadataItem(GDAL_DMD_SUPPORTED_SQL_DIALECTS, "OGRSQL SQLITE");
168
24
    poDriver->SetMetadataItem(GDAL_DMD_ALTER_FIELD_DEFN_FLAGS,
169
24
                              "Name WidthPrecision AlternativeName Comment");
170
171
24
    poDriver->pfnOpen = OGRFlatGeobufDataset::Open;
172
24
    poDriver->pfnCreate = OGRFlatGeobufDataset::Create;
173
24
    poDriver->pfnIdentify = OGRFlatGeobufDriverIdentify;
174
24
    poDriver->pfnDelete = OGRFlatGoBufDriverDelete;
175
176
24
    GetGDALDriverManager()->RegisterDriver(poDriver);
177
24
}
178
179
/************************************************************************/
180
/*                        OGRFlatGeobufDataset()                        */
181
/************************************************************************/
182
183
OGRFlatGeobufDataset::OGRFlatGeobufDataset(const char *pszName, bool bIsDir,
184
                                           bool bCreate, bool bUpdate)
185
16.5k
    : m_bCreate(bCreate), m_bUpdate(bUpdate), m_bIsDir(bIsDir)
186
16.5k
{
187
16.5k
    SetDescription(pszName);
188
16.5k
}
189
190
/************************************************************************/
191
/*                       ~OGRFlatGeobufDataset()                        */
192
/************************************************************************/
193
194
OGRFlatGeobufDataset::~OGRFlatGeobufDataset()
195
16.5k
{
196
16.5k
    OGRFlatGeobufDataset::Close();
197
16.5k
}
198
199
/************************************************************************/
200
/*                               Close()                                */
201
/************************************************************************/
202
203
CPLErr OGRFlatGeobufDataset::Close(GDALProgressFunc, void *)
204
30.5k
{
205
30.5k
    CPLErr eErr = CE_None;
206
30.5k
    if (nOpenFlags != OPEN_FLAGS_CLOSED)
207
16.5k
    {
208
16.5k
        if (OGRFlatGeobufDataset::FlushCache(true) != CE_None)
209
0
            eErr = CE_Failure;
210
211
16.5k
        for (auto &poLayer : m_apoLayers)
212
13.0k
        {
213
13.0k
            if (poLayer->Close() != CE_None)
214
0
                eErr = CE_Failure;
215
13.0k
        }
216
217
16.5k
        if (GDALDataset::Close() != CE_None)
218
0
            eErr = CE_Failure;
219
16.5k
    }
220
30.5k
    return eErr;
221
30.5k
}
222
223
/************************************************************************/
224
/*                                Open()                                */
225
/************************************************************************/
226
227
GDALDataset *OGRFlatGeobufDataset::Open(GDALOpenInfo *poOpenInfo)
228
16.4k
{
229
16.4k
    if (OGRFlatGeobufDriverIdentify(poOpenInfo) == FALSE)
230
0
        return nullptr;
231
232
16.4k
    const auto bVerifyBuffers =
233
16.4k
        CPLFetchBool(poOpenInfo->papszOpenOptions, "VERIFY_BUFFERS", true);
234
235
16.4k
    auto isDir = CPL_TO_BOOL(poOpenInfo->bIsDirectory);
236
16.4k
    auto bUpdate = poOpenInfo->eAccess == GA_Update;
237
238
16.4k
    if (isDir && bUpdate)
239
11
    {
240
11
        return nullptr;
241
11
    }
242
243
16.4k
    auto poDS = std::make_unique<OGRFlatGeobufDataset>(poOpenInfo->pszFilename,
244
16.4k
                                                       isDir, false, bUpdate);
245
246
16.4k
    if (poOpenInfo->bIsDirectory)
247
2.51k
    {
248
2.51k
        CPLStringList aosFiles(VSIReadDir(poOpenInfo->pszFilename));
249
2.51k
        int nCountFGB = 0;
250
2.51k
        int nCountNonFGB = 0;
251
28.1k
        for (int i = 0; i < aosFiles.size(); i++)
252
25.5k
        {
253
25.5k
            if (strcmp(aosFiles[i], ".") == 0 || strcmp(aosFiles[i], "..") == 0)
254
34
                continue;
255
25.5k
            if (EQUAL(CPLGetExtensionSafe(aosFiles[i]).c_str(), "fgb"))
256
0
                nCountFGB++;
257
25.5k
            else
258
25.5k
                nCountNonFGB++;
259
25.5k
        }
260
        // Consider that a directory is a FlatGeobuf dataset if there is a
261
        // majority of .fgb files in it
262
2.51k
        if (nCountFGB == 0 || nCountFGB < nCountNonFGB)
263
2.51k
        {
264
2.51k
            return nullptr;
265
2.51k
        }
266
0
        for (int i = 0; i < aosFiles.size(); i++)
267
0
        {
268
0
            if (EQUAL(CPLGetExtensionSafe(aosFiles[i]).c_str(), "fgb"))
269
0
            {
270
0
                const CPLString osFilename(CPLFormFilenameSafe(
271
0
                    poOpenInfo->pszFilename, aosFiles[i], nullptr));
272
0
                VSILFILE *fp = VSIFOpenL(osFilename, "rb");
273
0
                if (fp)
274
0
                {
275
0
                    if (!poDS->OpenFile(osFilename, fp, bVerifyBuffers))
276
0
                        VSIFCloseL(fp);
277
0
                }
278
0
            }
279
0
        }
280
0
    }
281
13.9k
    else
282
13.9k
    {
283
13.9k
        if (poOpenInfo->fpL != nullptr)
284
13.9k
        {
285
13.9k
            if (poDS->OpenFile(poOpenInfo->pszFilename, poOpenInfo->fpL,
286
13.9k
                               bVerifyBuffers))
287
12.9k
                poOpenInfo->fpL = nullptr;
288
13.9k
        }
289
0
        else
290
0
        {
291
0
            return nullptr;
292
0
        }
293
13.9k
    }
294
13.9k
    return poDS.release();
295
16.4k
}
296
297
/************************************************************************/
298
/*                              OpenFile()                              */
299
/************************************************************************/
300
301
bool OGRFlatGeobufDataset::OpenFile(const char *pszFilename, VSILFILE *fp,
302
                                    bool bVerifyBuffers)
303
13.9k
{
304
13.9k
    CPLDebugOnly("FlatGeobuf", "Opening OGRFlatGeobufLayer");
305
13.9k
    auto poLayer = std::unique_ptr<OGRFlatGeobufLayer>(
306
13.9k
        OGRFlatGeobufLayer::Open(pszFilename, fp, bVerifyBuffers));
307
13.9k
    if (!poLayer)
308
938
        return false;
309
310
12.9k
    if (m_bUpdate)
311
0
    {
312
0
        CPLDebugOnly("FlatGeobuf", "Creating OGRFlatGeobufEditableLayer");
313
0
        auto poEditableLayer = std::unique_ptr<OGRFlatGeobufEditableLayer>(
314
0
            new OGRFlatGeobufEditableLayer(poLayer.release(),
315
0
                                           papszOpenOptions));
316
0
        m_apoLayers.push_back(std::move(poEditableLayer));
317
0
    }
318
12.9k
    else
319
12.9k
    {
320
12.9k
        m_apoLayers.push_back(std::move(poLayer));
321
12.9k
    }
322
323
12.9k
    return true;
324
13.9k
}
325
326
GDALDataset *OGRFlatGeobufDataset::Create(const char *pszName, int /* nBands */,
327
                                          CPL_UNUSED int nXSize,
328
                                          CPL_UNUSED int nYSize,
329
                                          CPL_UNUSED GDALDataType eDT,
330
                                          CSLConstList /* papszOptions */)
331
113
{
332
    // First, ensure there isn't any such file yet.
333
113
    VSIStatBufL sStatBuf;
334
335
113
    if (VSIStatL(pszName, &sStatBuf) == 0)
336
0
    {
337
0
        CPLError(CE_Failure, CPLE_AppDefined,
338
0
                 "It seems a file system object called '%s' already exists.",
339
0
                 pszName);
340
341
0
        return nullptr;
342
0
    }
343
344
113
    bool bIsDir = false;
345
113
    if (!EQUAL(CPLGetExtensionSafe(pszName).c_str(), "fgb"))
346
0
    {
347
0
        if (VSIMkdir(pszName, 0755) != 0)
348
0
        {
349
0
            CPLError(CE_Failure, CPLE_AppDefined,
350
0
                     "Failed to create directory %s:\n%s", pszName,
351
0
                     VSIStrerror(errno));
352
0
            return nullptr;
353
0
        }
354
0
        bIsDir = true;
355
0
    }
356
357
113
    return new OGRFlatGeobufDataset(pszName, bIsDir, true, false);
358
113
}
359
360
const OGRLayer *OGRFlatGeobufDataset::GetLayer(int iLayer) const
361
26.9k
{
362
26.9k
    if (iLayer < 0 || iLayer >= GetLayerCount())
363
0
        return nullptr;
364
26.9k
    return m_apoLayers[iLayer]->GetLayer();
365
26.9k
}
366
367
int OGRFlatGeobufDataset::TestCapability(const char *pszCap) const
368
721
{
369
721
    if (EQUAL(pszCap, ODsCCreateLayer))
370
464
        return m_bCreate && (m_bIsDir || m_apoLayers.empty());
371
257
    else if (EQUAL(pszCap, ODsCCurveGeometries))
372
1
        return true;
373
256
    else if (EQUAL(pszCap, ODsCMeasuredGeometries))
374
0
        return true;
375
256
    else if (EQUAL(pszCap, ODsCZGeometries))
376
0
        return true;
377
256
    else if (EQUAL(pszCap, ODsCRandomLayerWrite))
378
0
        return m_bUpdate;
379
256
    else
380
256
        return false;
381
721
}
382
383
/************************************************************************/
384
/*                          LaunderLayerName()                          */
385
/************************************************************************/
386
387
static CPLString LaunderLayerName(const char *pszLayerName)
388
0
{
389
0
    std::string osRet(CPLLaunderForFilenameSafe(pszLayerName, nullptr));
390
0
    if (osRet != pszLayerName)
391
0
    {
392
0
        CPLError(CE_Warning, CPLE_AppDefined,
393
0
                 "Invalid layer name for a file name: %s. Laundered to %s.",
394
0
                 pszLayerName, osRet.c_str());
395
0
    }
396
0
    return osRet;
397
0
}
398
399
OGRLayer *
400
OGRFlatGeobufDataset::ICreateLayer(const char *pszLayerName,
401
                                   const OGRGeomFieldDefn *poGeomFieldDefn,
402
                                   CSLConstList papszOptions)
403
113
{
404
    // Verify we are in update mode.
405
113
    if (!m_bCreate)
406
0
    {
407
0
        CPLError(CE_Failure, CPLE_NoWriteAccess,
408
0
                 "Data source %s opened read-only.\n"
409
0
                 "New layer %s cannot be created.",
410
0
                 GetDescription(), pszLayerName);
411
412
0
        return nullptr;
413
0
    }
414
113
    if (!m_bIsDir && !m_apoLayers.empty())
415
0
    {
416
0
        CPLError(CE_Failure, CPLE_NoWriteAccess,
417
0
                 "Can create only one single layer in a .fgb file. "
418
0
                 "Use a directory output for multiple layers");
419
420
0
        return nullptr;
421
0
    }
422
423
113
    const auto eGType = poGeomFieldDefn ? poGeomFieldDefn->GetType() : wkbNone;
424
113
    const auto poSpatialRef =
425
113
        poGeomFieldDefn ? poGeomFieldDefn->GetSpatialRef() : nullptr;
426
427
    // Verify that the datasource is a directory.
428
113
    VSIStatBufL sStatBuf;
429
430
    // What filename would we use?
431
113
    CPLString osFilename;
432
433
113
    if (m_bIsDir)
434
0
        osFilename = CPLFormFilenameSafe(
435
0
            GetDescription(), LaunderLayerName(pszLayerName).c_str(), "fgb");
436
113
    else
437
113
        osFilename = GetDescription();
438
439
    // Does this directory/file already exist?
440
113
    if (VSIStatL(osFilename, &sStatBuf) == 0)
441
0
    {
442
0
        CPLError(CE_Failure, CPLE_FileIO,
443
0
                 "Attempt to create layer %s, but %s already exists.",
444
0
                 pszLayerName, osFilename.c_str());
445
0
        return nullptr;
446
0
    }
447
448
113
    bool bCreateSpatialIndexAtClose =
449
113
        CPLFetchBool(papszOptions, "SPATIAL_INDEX", true);
450
451
113
    auto poLayer =
452
113
        std::unique_ptr<OGRFlatGeobufLayer>(OGRFlatGeobufLayer::Create(
453
113
            this, pszLayerName, osFilename, poSpatialRef, eGType,
454
113
            bCreateSpatialIndexAtClose, papszOptions));
455
113
    if (poLayer == nullptr)
456
0
        return nullptr;
457
458
113
    m_apoLayers.push_back(std::move(poLayer));
459
460
113
    return m_apoLayers.back()->GetLayer();
461
113
}
462
463
/************************************************************************/
464
//                            GetFileList()                             */
465
/************************************************************************/
466
467
char **OGRFlatGeobufDataset::GetFileList()
468
0
{
469
0
    CPLStringList oFileList;
470
0
    for (const auto &poLayer : m_apoLayers)
471
0
    {
472
0
        oFileList.AddString(poLayer->GetFilename().c_str());
473
0
    }
474
0
    return oFileList.StealList();
475
0
}