LCOV - code coverage report
Current view: top level - ogr/ogrsf_frmts/flatgeobuf - ogrflatgeobufdataset.cpp (source / functions) Hit Total Coverage
Test: gdal_filtered.info Lines: 175 199 87.9 %
Date: 2025-09-10 17:48:50 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       53412 : static int OGRFlatGeobufDriverIdentify(GDALOpenInfo *poOpenInfo)
      29             : {
      30       53412 :     if (STARTS_WITH_CI(poOpenInfo->pszFilename, "FGB:"))
      31           0 :         return TRUE;
      32             : 
      33       53412 :     if (poOpenInfo->bIsDirectory)
      34             :     {
      35        1303 :         return -1;
      36             :     }
      37             : 
      38       52109 :     const auto nHeaderBytes = poOpenInfo->nHeaderBytes;
      39       52109 :     const auto pabyHeader = poOpenInfo->pabyHeader;
      40             : 
      41       52109 :     if (nHeaderBytes < 4)
      42       48898 :         return FALSE;
      43             : 
      44        3211 :     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        2905 :     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        2024 : void RegisterOGRFlatGeobuf()
     116             : {
     117        2024 :     if (GDALGetDriverByName("FlatGeobuf") != nullptr)
     118         283 :         return;
     119             : 
     120        1741 :     GDALDriver *poDriver = new GDALDriver();
     121        1741 :     poDriver->SetDescription("FlatGeobuf");
     122        1741 :     poDriver->SetMetadataItem(GDAL_DCAP_VECTOR, "YES");
     123        1741 :     poDriver->SetMetadataItem(GDAL_DCAP_CREATE_LAYER, "YES");
     124        1741 :     poDriver->SetMetadataItem(GDAL_DCAP_CREATE_FIELD, "YES");
     125        1741 :     poDriver->SetMetadataItem(GDAL_DCAP_DELETE_FIELD, "YES");
     126        1741 :     poDriver->SetMetadataItem(GDAL_DCAP_REORDER_FIELDS, "YES");
     127        1741 :     poDriver->SetMetadataItem(GDAL_DCAP_CURVE_GEOMETRIES, "YES");
     128        1741 :     poDriver->SetMetadataItem(GDAL_DCAP_MEASURED_GEOMETRIES, "YES");
     129        1741 :     poDriver->SetMetadataItem(GDAL_DCAP_Z_GEOMETRIES, "YES");
     130        1741 :     poDriver->SetMetadataItem(GDAL_DCAP_REOPEN_AFTER_WRITE_REQUIRED, "YES");
     131        1741 :     poDriver->SetMetadataItem(GDAL_DCAP_CAN_READ_AFTER_DELETE, "YES");
     132        1741 :     poDriver->SetMetadataItem(GDAL_DMD_LONGNAME, "FlatGeobuf");
     133        1741 :     poDriver->SetMetadataItem(GDAL_DMD_EXTENSION, "fgb");
     134        1741 :     poDriver->SetMetadataItem(GDAL_DMD_HELPTOPIC,
     135        1741 :                               "drivers/vector/flatgeobuf.html");
     136        1741 :     poDriver->SetMetadataItem(GDAL_DCAP_VIRTUALIO, "YES");
     137        1741 :     poDriver->SetMetadataItem(
     138             :         GDAL_DMD_CREATIONFIELDDATATYPES,
     139        1741 :         "Integer Integer64 Real String Date DateTime Binary");
     140        1741 :     poDriver->SetMetadataItem(GDAL_DMD_CREATIONFIELDDATASUBTYPES,
     141        1741 :                               "Boolean Int16 Float32");
     142        1741 :     poDriver->SetMetadataItem(GDAL_DMD_CREATION_FIELD_DEFN_FLAGS,
     143        1741 :                               "WidthPrecision Comment AlternativeName");
     144             : 
     145        1741 :     poDriver->SetMetadataItem(
     146             :         GDAL_DS_LAYER_CREATIONOPTIONLIST,
     147             :         "<LayerCreationOptionList>"
     148             :         "  <Option name='SPATIAL_INDEX' type='boolean' description='Whether to "
     149             :         "create a spatial index' default='YES'/>"
     150             :         "  <Option name='TEMPORARY_DIR' type='string' description='Directory "
     151             :         "where temporary file should be created'/>"
     152             :         "  <Option name='TITLE' type='string' description='Layer title'/>"
     153             :         "  <Option name='DESCRIPTION' type='string' "
     154             :         "description='Layer description'/>"
     155        1741 :         "</LayerCreationOptionList>");
     156        1741 :     poDriver->SetMetadataItem(
     157             :         GDAL_DMD_OPENOPTIONLIST,
     158             :         "<OpenOptionList>"
     159             :         "  <Option name='VERIFY_BUFFERS' type='boolean' description='Verify "
     160             :         "flatbuffers integrity' default='YES'/>"
     161        1741 :         "</OpenOptionList>");
     162             : 
     163        1741 :     poDriver->SetMetadataItem(GDAL_DCAP_COORDINATE_EPOCH, "YES");
     164        1741 :     poDriver->SetMetadataItem(GDAL_DMD_SUPPORTED_SQL_DIALECTS, "OGRSQL SQLITE");
     165        1741 :     poDriver->SetMetadataItem(GDAL_DMD_ALTER_FIELD_DEFN_FLAGS,
     166        1741 :                               "Name WidthPrecision AlternativeName Comment");
     167             : 
     168        1741 :     poDriver->pfnOpen = OGRFlatGeobufDataset::Open;
     169        1741 :     poDriver->pfnCreate = OGRFlatGeobufDataset::Create;
     170        1741 :     poDriver->pfnIdentify = OGRFlatGeobufDriverIdentify;
     171        1741 :     poDriver->pfnDelete = OGRFlatGoBufDriverDelete;
     172             : 
     173        1741 :     GetGDALDriverManager()->RegisterDriver(poDriver);
     174             : }
     175             : 
     176             : /************************************************************************/
     177             : /*                         OGRFlatGeobufDataset()                       */
     178             : /************************************************************************/
     179             : 
     180         845 : OGRFlatGeobufDataset::OGRFlatGeobufDataset(const char *pszName, bool bIsDir,
     181         845 :                                            bool bCreate, bool bUpdate)
     182         845 :     : m_bCreate(bCreate), m_bUpdate(bUpdate), m_bIsDir(bIsDir)
     183             : {
     184         845 :     SetDescription(pszName);
     185         845 : }
     186             : 
     187             : /************************************************************************/
     188             : /*                         ~OGRFlatGeobufDataset()                      */
     189             : /************************************************************************/
     190             : 
     191        1690 : OGRFlatGeobufDataset::~OGRFlatGeobufDataset()
     192             : {
     193         845 :     OGRFlatGeobufDataset::Close();
     194        1690 : }
     195             : 
     196             : /************************************************************************/
     197             : /*                              Close()                                 */
     198             : /************************************************************************/
     199             : 
     200        1163 : CPLErr OGRFlatGeobufDataset::Close()
     201             : {
     202        1163 :     CPLErr eErr = CE_None;
     203        1163 :     if (nOpenFlags != OPEN_FLAGS_CLOSED)
     204             :     {
     205         845 :         if (OGRFlatGeobufDataset::FlushCache(true) != CE_None)
     206           0 :             eErr = CE_Failure;
     207             : 
     208        1171 :         for (auto &poLayer : m_apoLayers)
     209             :         {
     210         326 :             if (poLayer->Close() != CE_None)
     211           0 :                 eErr = CE_Failure;
     212             :         }
     213             : 
     214         845 :         if (GDALDataset::Close() != CE_None)
     215           0 :             eErr = CE_Failure;
     216             :     }
     217        1163 :     return eErr;
     218             : }
     219             : 
     220             : /************************************************************************/
     221             : /*                                Open()                                */
     222             : /************************************************************************/
     223             : 
     224         741 : GDALDataset *OGRFlatGeobufDataset::Open(GDALOpenInfo *poOpenInfo)
     225             : {
     226         741 :     if (OGRFlatGeobufDriverIdentify(poOpenInfo) == FALSE)
     227           0 :         return nullptr;
     228             : 
     229             :     const auto bVerifyBuffers =
     230         741 :         CPLFetchBool(poOpenInfo->papszOpenOptions, "VERIFY_BUFFERS", true);
     231             : 
     232         741 :     auto isDir = CPL_TO_BOOL(poOpenInfo->bIsDirectory);
     233         741 :     auto bUpdate = poOpenInfo->eAccess == GA_Update;
     234             : 
     235         741 :     if (isDir && bUpdate)
     236             :     {
     237          71 :         return nullptr;
     238             :     }
     239             : 
     240             :     auto poDS = std::unique_ptr<OGRFlatGeobufDataset>(new OGRFlatGeobufDataset(
     241        1340 :         poOpenInfo->pszFilename, isDir, false, bUpdate));
     242             : 
     243         670 :     if (poOpenInfo->bIsDirectory)
     244             :     {
     245         520 :         CPLStringList aosFiles(VSIReadDir(poOpenInfo->pszFilename));
     246         520 :         int nCountFGB = 0;
     247         520 :         int nCountNonFGB = 0;
     248       22914 :         for (int i = 0; i < aosFiles.size(); i++)
     249             :         {
     250       22394 :             if (strcmp(aosFiles[i], ".") == 0 || strcmp(aosFiles[i], "..") == 0)
     251         508 :                 continue;
     252       21886 :             if (EQUAL(CPLGetExtensionSafe(aosFiles[i]).c_str(), "fgb"))
     253           2 :                 nCountFGB++;
     254             :             else
     255       21884 :                 nCountNonFGB++;
     256             :         }
     257             :         // Consider that a directory is a FlatGeobuf dataset if there is a
     258             :         // majority of .fgb files in it
     259         520 :         if (nCountFGB == 0 || nCountFGB < nCountNonFGB)
     260             :         {
     261         519 :             return nullptr;
     262             :         }
     263           3 :         for (int i = 0; i < aosFiles.size(); i++)
     264             :         {
     265           2 :             if (EQUAL(CPLGetExtensionSafe(aosFiles[i]).c_str(), "fgb"))
     266             :             {
     267           0 :                 const CPLString osFilename(CPLFormFilenameSafe(
     268           4 :                     poOpenInfo->pszFilename, aosFiles[i], nullptr));
     269           2 :                 VSILFILE *fp = VSIFOpenL(osFilename, "rb");
     270           2 :                 if (fp)
     271             :                 {
     272           2 :                     if (!poDS->OpenFile(osFilename, fp, bVerifyBuffers))
     273           0 :                         VSIFCloseL(fp);
     274             :                 }
     275             :             }
     276             :         }
     277             :     }
     278             :     else
     279             :     {
     280         150 :         if (poOpenInfo->fpL != nullptr)
     281             :         {
     282         150 :             if (poDS->OpenFile(poOpenInfo->pszFilename, poOpenInfo->fpL,
     283             :                                bVerifyBuffers))
     284         150 :                 poOpenInfo->fpL = nullptr;
     285             :         }
     286             :         else
     287             :         {
     288           0 :             return nullptr;
     289             :         }
     290             :     }
     291         151 :     return poDS.release();
     292             : }
     293             : 
     294             : /************************************************************************/
     295             : /*                           OpenFile()                                 */
     296             : /************************************************************************/
     297             : 
     298         152 : bool OGRFlatGeobufDataset::OpenFile(const char *pszFilename, VSILFILE *fp,
     299             :                                     bool bVerifyBuffers)
     300             : {
     301         152 :     CPLDebugOnly("FlatGeobuf", "Opening OGRFlatGeobufLayer");
     302             :     auto poLayer = std::unique_ptr<OGRFlatGeobufLayer>(
     303         304 :         OGRFlatGeobufLayer::Open(pszFilename, fp, bVerifyBuffers));
     304         152 :     if (!poLayer)
     305           0 :         return false;
     306             : 
     307         152 :     if (m_bUpdate)
     308             :     {
     309           1 :         CPLDebugOnly("FlatGeobuf", "Creating OGRFlatGeobufEditableLayer");
     310             :         auto poEditableLayer = std::unique_ptr<OGRFlatGeobufEditableLayer>(
     311           1 :             new OGRFlatGeobufEditableLayer(poLayer.release(),
     312           1 :                                            papszOpenOptions));
     313           1 :         m_apoLayers.push_back(std::move(poEditableLayer));
     314             :     }
     315             :     else
     316             :     {
     317         151 :         m_apoLayers.push_back(std::move(poLayer));
     318             :     }
     319             : 
     320         152 :     return true;
     321             : }
     322             : 
     323         175 : GDALDataset *OGRFlatGeobufDataset::Create(const char *pszName, int /* nBands */,
     324             :                                           CPL_UNUSED int nXSize,
     325             :                                           CPL_UNUSED int nYSize,
     326             :                                           CPL_UNUSED GDALDataType eDT,
     327             :                                           char ** /* papszOptions */)
     328             : {
     329             :     // First, ensure there isn't any such file yet.
     330             :     VSIStatBufL sStatBuf;
     331             : 
     332         175 :     if (VSIStatL(pszName, &sStatBuf) == 0)
     333             :     {
     334           0 :         CPLError(CE_Failure, CPLE_AppDefined,
     335             :                  "It seems a file system object called '%s' already exists.",
     336             :                  pszName);
     337             : 
     338           0 :         return nullptr;
     339             :     }
     340             : 
     341         175 :     bool bIsDir = false;
     342         175 :     if (!EQUAL(CPLGetExtensionSafe(pszName).c_str(), "fgb"))
     343             :     {
     344           1 :         if (VSIMkdir(pszName, 0755) != 0)
     345             :         {
     346           0 :             CPLError(CE_Failure, CPLE_AppDefined,
     347             :                      "Failed to create directory %s:\n%s", pszName,
     348           0 :                      VSIStrerror(errno));
     349           0 :             return nullptr;
     350             :         }
     351           1 :         bIsDir = true;
     352             :     }
     353             : 
     354         175 :     return new OGRFlatGeobufDataset(pszName, bIsDir, true, false);
     355             : }
     356             : 
     357         586 : const OGRLayer *OGRFlatGeobufDataset::GetLayer(int iLayer) const
     358             : {
     359         586 :     if (iLayer < 0 || iLayer >= GetLayerCount())
     360           2 :         return nullptr;
     361         584 :     return m_apoLayers[iLayer]->GetLayer();
     362             : }
     363             : 
     364          93 : int OGRFlatGeobufDataset::TestCapability(const char *pszCap) const
     365             : {
     366          93 :     if (EQUAL(pszCap, ODsCCreateLayer))
     367          38 :         return m_bCreate && (m_bIsDir || m_apoLayers.empty());
     368          55 :     else if (EQUAL(pszCap, ODsCCurveGeometries))
     369          22 :         return true;
     370          33 :     else if (EQUAL(pszCap, ODsCMeasuredGeometries))
     371           2 :         return true;
     372          31 :     else if (EQUAL(pszCap, ODsCZGeometries))
     373           2 :         return true;
     374          29 :     else if (EQUAL(pszCap, ODsCRandomLayerWrite))
     375           0 :         return m_bUpdate;
     376             :     else
     377          29 :         return false;
     378             : }
     379             : 
     380             : /************************************************************************/
     381             : /*                        LaunderLayerName()                            */
     382             : /************************************************************************/
     383             : 
     384           2 : static CPLString LaunderLayerName(const char *pszLayerName)
     385             : {
     386           4 :     std::string osRet(CPLLaunderForFilenameSafe(pszLayerName, nullptr));
     387           2 :     if (osRet != pszLayerName)
     388             :     {
     389           1 :         CPLError(CE_Warning, CPLE_AppDefined,
     390             :                  "Invalid layer name for a file name: %s. Laundered to %s.",
     391             :                  pszLayerName, osRet.c_str());
     392             :     }
     393           4 :     return osRet;
     394             : }
     395             : 
     396             : OGRLayer *
     397         192 : OGRFlatGeobufDataset::ICreateLayer(const char *pszLayerName,
     398             :                                    const OGRGeomFieldDefn *poGeomFieldDefn,
     399             :                                    CSLConstList papszOptions)
     400             : {
     401             :     // Verify we are in update mode.
     402         192 :     if (!m_bCreate)
     403             :     {
     404           0 :         CPLError(CE_Failure, CPLE_NoWriteAccess,
     405             :                  "Data source %s opened read-only.\n"
     406             :                  "New layer %s cannot be created.",
     407           0 :                  GetDescription(), pszLayerName);
     408             : 
     409           0 :         return nullptr;
     410             :     }
     411         192 :     if (!m_bIsDir && !m_apoLayers.empty())
     412             :     {
     413          16 :         CPLError(CE_Failure, CPLE_NoWriteAccess,
     414             :                  "Can create only one single layer in a .fgb file. "
     415             :                  "Use a directory output for multiple layers");
     416             : 
     417          16 :         return nullptr;
     418             :     }
     419             : 
     420         176 :     const auto eGType = poGeomFieldDefn ? poGeomFieldDefn->GetType() : wkbNone;
     421             :     const auto poSpatialRef =
     422         176 :         poGeomFieldDefn ? poGeomFieldDefn->GetSpatialRef() : nullptr;
     423             : 
     424             :     // Verify that the datasource is a directory.
     425             :     VSIStatBufL sStatBuf;
     426             : 
     427             :     // What filename would we use?
     428         352 :     CPLString osFilename;
     429             : 
     430         176 :     if (m_bIsDir)
     431           4 :         osFilename = CPLFormFilenameSafe(
     432           6 :             GetDescription(), LaunderLayerName(pszLayerName).c_str(), "fgb");
     433             :     else
     434         174 :         osFilename = GetDescription();
     435             : 
     436             :     // Does this directory/file already exist?
     437         176 :     if (VSIStatL(osFilename, &sStatBuf) == 0)
     438             :     {
     439           0 :         CPLError(CE_Failure, CPLE_FileIO,
     440             :                  "Attempt to create layer %s, but %s already exists.",
     441             :                  pszLayerName, osFilename.c_str());
     442           0 :         return nullptr;
     443             :     }
     444             : 
     445             :     bool bCreateSpatialIndexAtClose =
     446         176 :         CPLFetchBool(papszOptions, "SPATIAL_INDEX", true);
     447             : 
     448             :     auto poLayer =
     449             :         std::unique_ptr<OGRFlatGeobufLayer>(OGRFlatGeobufLayer::Create(
     450             :             this, pszLayerName, osFilename, poSpatialRef, eGType,
     451         352 :             bCreateSpatialIndexAtClose, papszOptions));
     452         176 :     if (poLayer == nullptr)
     453           2 :         return nullptr;
     454             : 
     455         174 :     m_apoLayers.push_back(std::move(poLayer));
     456             : 
     457         174 :     return m_apoLayers.back()->GetLayer();
     458             : }
     459             : 
     460             : /************************************************************************/
     461             : //                            GetFileList()                             */
     462             : /************************************************************************/
     463             : 
     464           3 : char **OGRFlatGeobufDataset::GetFileList()
     465             : {
     466           6 :     CPLStringList oFileList;
     467           7 :     for (const auto &poLayer : m_apoLayers)
     468             :     {
     469           4 :         oFileList.AddString(poLayer->GetFilename().c_str());
     470             :     }
     471           6 :     return oFileList.StealList();
     472             : }

Generated by: LCOV version 1.14