LCOV - code coverage report
Current view: top level - ogr/ogrsf_frmts/flatgeobuf - ogrflatgeobufdataset.cpp (source / functions) Hit Total Coverage
Test: gdal_filtered.info Lines: 178 202 88.1 %
Date: 2026-04-15 22:10:00 Functions: 15 15 100.0 %

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

Generated by: LCOV version 1.14