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

Generated by: LCOV version 1.14