LCOV - code coverage report
Current view: top level - ogr/ogrsf_frmts/flatgeobuf - ogrflatgeobufdataset.cpp (source / functions) Hit Total Coverage
Test: gdal_filtered.info Lines: 172 195 88.2 %
Date: 2024-05-14 23:54:21 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             :  * Permission is hereby granted, free of charge, to any person obtaining a
      11             :  * copy of this software and associated documentation files (the "Software"),
      12             :  * to deal in the Software without restriction, including without limitation
      13             :  * the rights to use, copy, modify, merge, publish, distribute, sublicense,
      14             :  * and/or sell copies of the Software, and to permit persons to whom the
      15             :  * Software is furnished to do so, subject to the following conditions:
      16             :  *
      17             :  * The above copyright notice and this permission notice shall be included
      18             :  * in all copies or substantial portions of the Software.
      19             :  *
      20             :  * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS
      21             :  * OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
      22             :  * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL
      23             :  * THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
      24             :  * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
      25             :  * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
      26             :  * DEALINGS IN THE SOFTWARE.
      27             :  ****************************************************************************/
      28             : 
      29             : #include "ogr_flatgeobuf.h"
      30             : 
      31             : #include <memory>
      32             : 
      33             : #include "header_generated.h"
      34             : 
      35             : // For users not using CMake...
      36             : #ifndef flatbuffers
      37             : #error                                                                         \
      38             :     "Make sure to build with -Dflatbuffers=gdal_flatbuffers (for example) to avoid potential conflict of flatbuffers"
      39             : #endif
      40             : 
      41             : using namespace flatbuffers;
      42             : using namespace FlatGeobuf;
      43             : 
      44       43247 : static int OGRFlatGeobufDriverIdentify(GDALOpenInfo *poOpenInfo)
      45             : {
      46       43247 :     if (STARTS_WITH_CI(poOpenInfo->pszFilename, "FGB:"))
      47           0 :         return TRUE;
      48             : 
      49       43247 :     if (poOpenInfo->bIsDirectory)
      50             :     {
      51         969 :         return -1;
      52             :     }
      53             : 
      54       42278 :     const auto nHeaderBytes = poOpenInfo->nHeaderBytes;
      55       42278 :     const auto pabyHeader = poOpenInfo->pabyHeader;
      56             : 
      57       42278 :     if (nHeaderBytes < 4)
      58       39764 :         return FALSE;
      59             : 
      60        2514 :     if (pabyHeader[0] == 0x66 && pabyHeader[1] == 0x67 && pabyHeader[2] == 0x62)
      61             :     {
      62         290 :         if (pabyHeader[3] == 0x03)
      63             :         {
      64         290 :             CPLDebug("FlatGeobuf", "Verified magicbytes");
      65         290 :             return TRUE;
      66             :         }
      67             :         else
      68             :         {
      69           0 :             CPLError(CE_Failure, CPLE_OpenFailed,
      70             :                      "Unsupported FlatGeobuf version %d.\n",
      71           0 :                      poOpenInfo->pabyHeader[3]);
      72             :         }
      73             :     }
      74             : 
      75        2224 :     return FALSE;
      76             : }
      77             : 
      78             : /************************************************************************/
      79             : /*                           Delete()                                   */
      80             : /************************************************************************/
      81             : 
      82         147 : static CPLErr OGRFlatGoBufDriverDelete(const char *pszDataSource)
      83             : 
      84             : {
      85             :     VSIStatBufL sStatBuf;
      86             : 
      87         147 :     if (VSIStatL(pszDataSource, &sStatBuf) != 0)
      88             :     {
      89           0 :         CPLError(CE_Failure, CPLE_AppDefined,
      90             :                  "%s does not appear to be a file or directory.",
      91             :                  pszDataSource);
      92             : 
      93           0 :         return CE_Failure;
      94             :     }
      95             : 
      96         147 :     if (VSI_ISREG(sStatBuf.st_mode))
      97             :     {
      98         146 :         VSIUnlink(pszDataSource);
      99         146 :         return CE_None;
     100             :     }
     101             : 
     102           1 :     if (VSI_ISDIR(sStatBuf.st_mode))
     103             :     {
     104           1 :         char **papszDirEntries = VSIReadDir(pszDataSource);
     105             : 
     106           3 :         for (int iFile = 0;
     107           3 :              papszDirEntries != nullptr && papszDirEntries[iFile] != nullptr;
     108             :              iFile++)
     109             :         {
     110           2 :             if (EQUAL(CPLGetExtension(papszDirEntries[iFile]), "fgb"))
     111             :             {
     112           2 :                 VSIUnlink(CPLFormFilename(pszDataSource, papszDirEntries[iFile],
     113             :                                           nullptr));
     114             :             }
     115             :         }
     116             : 
     117           1 :         CSLDestroy(papszDirEntries);
     118             : 
     119           1 :         VSIRmdir(pszDataSource);
     120             :     }
     121             : 
     122           1 :     return CE_None;
     123             : }
     124             : 
     125             : /************************************************************************/
     126             : /*                       RegisterOGRFlatGeobuf()                        */
     127             : /************************************************************************/
     128             : 
     129        1522 : void RegisterOGRFlatGeobuf()
     130             : {
     131        1522 :     if (GDALGetDriverByName("FlatGeobuf") != nullptr)
     132         301 :         return;
     133             : 
     134        1221 :     GDALDriver *poDriver = new GDALDriver();
     135        1221 :     poDriver->SetDescription("FlatGeobuf");
     136        1221 :     poDriver->SetMetadataItem(GDAL_DCAP_VECTOR, "YES");
     137        1221 :     poDriver->SetMetadataItem(GDAL_DCAP_CREATE_LAYER, "YES");
     138        1221 :     poDriver->SetMetadataItem(GDAL_DCAP_CREATE_FIELD, "YES");
     139        1221 :     poDriver->SetMetadataItem(GDAL_DCAP_DELETE_FIELD, "YES");
     140        1221 :     poDriver->SetMetadataItem(GDAL_DCAP_REORDER_FIELDS, "YES");
     141        1221 :     poDriver->SetMetadataItem(GDAL_DCAP_CURVE_GEOMETRIES, "YES");
     142        1221 :     poDriver->SetMetadataItem(GDAL_DCAP_MEASURED_GEOMETRIES, "YES");
     143        1221 :     poDriver->SetMetadataItem(GDAL_DCAP_Z_GEOMETRIES, "YES");
     144        1221 :     poDriver->SetMetadataItem(GDAL_DMD_LONGNAME, "FlatGeobuf");
     145        1221 :     poDriver->SetMetadataItem(GDAL_DMD_EXTENSION, "fgb");
     146        1221 :     poDriver->SetMetadataItem(GDAL_DMD_HELPTOPIC,
     147        1221 :                               "drivers/vector/flatgeobuf.html");
     148        1221 :     poDriver->SetMetadataItem(GDAL_DCAP_VIRTUALIO, "YES");
     149        1221 :     poDriver->SetMetadataItem(
     150             :         GDAL_DMD_CREATIONFIELDDATATYPES,
     151        1221 :         "Integer Integer64 Real String Date DateTime Binary");
     152        1221 :     poDriver->SetMetadataItem(GDAL_DMD_CREATIONFIELDDATASUBTYPES,
     153        1221 :                               "Boolean Int16 Float32");
     154        1221 :     poDriver->SetMetadataItem(GDAL_DMD_CREATION_FIELD_DEFN_FLAGS,
     155        1221 :                               "WidthPrecision Comment AlternativeName");
     156             : 
     157        1221 :     poDriver->SetMetadataItem(
     158             :         GDAL_DS_LAYER_CREATIONOPTIONLIST,
     159             :         "<LayerCreationOptionList>"
     160             :         "  <Option name='SPATIAL_INDEX' type='boolean' description='Whether to "
     161             :         "create a spatial index' default='YES'/>"
     162             :         "  <Option name='TEMPORARY_DIR' type='string' description='Directory "
     163             :         "where temporary file should be created'/>"
     164             :         "  <Option name='TITLE' type='string' description='Layer title'/>"
     165             :         "  <Option name='DESCRIPTION' type='string' "
     166             :         "description='Layer description'/>"
     167        1221 :         "</LayerCreationOptionList>");
     168        1221 :     poDriver->SetMetadataItem(
     169             :         GDAL_DMD_OPENOPTIONLIST,
     170             :         "<OpenOptionList>"
     171             :         "  <Option name='VERIFY_BUFFERS' type='boolean' description='Verify "
     172             :         "flatbuffers integrity' default='YES'/>"
     173        1221 :         "</OpenOptionList>");
     174             : 
     175        1221 :     poDriver->SetMetadataItem(GDAL_DCAP_COORDINATE_EPOCH, "YES");
     176        1221 :     poDriver->SetMetadataItem(GDAL_DMD_SUPPORTED_SQL_DIALECTS, "OGRSQL SQLITE");
     177        1221 :     poDriver->SetMetadataItem(GDAL_DMD_ALTER_FIELD_DEFN_FLAGS,
     178        1221 :                               "Name WidthPrecision AlternativeName Comment");
     179             : 
     180        1221 :     poDriver->pfnOpen = OGRFlatGeobufDataset::Open;
     181        1221 :     poDriver->pfnCreate = OGRFlatGeobufDataset::Create;
     182        1221 :     poDriver->pfnIdentify = OGRFlatGeobufDriverIdentify;
     183        1221 :     poDriver->pfnDelete = OGRFlatGoBufDriverDelete;
     184             : 
     185        1221 :     GetGDALDriverManager()->RegisterDriver(poDriver);
     186             : }
     187             : 
     188             : /************************************************************************/
     189             : /*                         OGRFlatGeobufDataset()                       */
     190             : /************************************************************************/
     191             : 
     192         724 : OGRFlatGeobufDataset::OGRFlatGeobufDataset(const char *pszName, bool bIsDir,
     193         724 :                                            bool bCreate, bool bUpdate)
     194         724 :     : m_bCreate(bCreate), m_bUpdate(bUpdate), m_bIsDir(bIsDir)
     195             : {
     196         724 :     SetDescription(pszName);
     197         724 : }
     198             : 
     199             : /************************************************************************/
     200             : /*                         ~OGRFlatGeobufDataset()                      */
     201             : /************************************************************************/
     202             : 
     203        1448 : OGRFlatGeobufDataset::~OGRFlatGeobufDataset()
     204             : {
     205         724 :     OGRFlatGeobufDataset::Close();
     206        1448 : }
     207             : 
     208             : /************************************************************************/
     209             : /*                              Close()                                 */
     210             : /************************************************************************/
     211             : 
     212        1028 : CPLErr OGRFlatGeobufDataset::Close()
     213             : {
     214        1028 :     CPLErr eErr = CE_None;
     215        1028 :     if (nOpenFlags != OPEN_FLAGS_CLOSED)
     216             :     {
     217         724 :         if (OGRFlatGeobufDataset::FlushCache(true) != CE_None)
     218           0 :             eErr = CE_Failure;
     219             : 
     220        1034 :         for (auto &poLayer : m_apoLayers)
     221             :         {
     222         310 :             if (poLayer->Close() != CE_None)
     223           0 :                 eErr = CE_Failure;
     224             :         }
     225             : 
     226         724 :         if (GDALDataset::Close() != CE_None)
     227           0 :             eErr = CE_Failure;
     228             :     }
     229        1028 :     return eErr;
     230             : }
     231             : 
     232             : /************************************************************************/
     233             : /*                                Open()                                */
     234             : /************************************************************************/
     235             : 
     236         623 : GDALDataset *OGRFlatGeobufDataset::Open(GDALOpenInfo *poOpenInfo)
     237             : {
     238         623 :     if (OGRFlatGeobufDriverIdentify(poOpenInfo) == FALSE)
     239           0 :         return nullptr;
     240             : 
     241             :     const auto bVerifyBuffers =
     242         623 :         CPLFetchBool(poOpenInfo->papszOpenOptions, "VERIFY_BUFFERS", true);
     243             : 
     244         623 :     auto isDir = CPL_TO_BOOL(poOpenInfo->bIsDirectory);
     245         623 :     auto bUpdate = poOpenInfo->eAccess == GA_Update;
     246             : 
     247         623 :     if (isDir && bUpdate)
     248             :     {
     249          66 :         return nullptr;
     250             :     }
     251             : 
     252             :     auto poDS = std::unique_ptr<OGRFlatGeobufDataset>(new OGRFlatGeobufDataset(
     253        1114 :         poOpenInfo->pszFilename, isDir, false, bUpdate));
     254             : 
     255         557 :     if (poOpenInfo->bIsDirectory)
     256             :     {
     257         415 :         CPLStringList aosFiles(VSIReadDir(poOpenInfo->pszFilename));
     258         415 :         int nCountFGB = 0;
     259         415 :         int nCountNonFGB = 0;
     260       21865 :         for (int i = 0; i < aosFiles.size(); i++)
     261             :         {
     262       21450 :             if (strcmp(aosFiles[i], ".") == 0 || strcmp(aosFiles[i], "..") == 0)
     263         336 :                 continue;
     264       21114 :             if (EQUAL(CPLGetExtension(aosFiles[i]), "fgb"))
     265           2 :                 nCountFGB++;
     266             :             else
     267       21112 :                 nCountNonFGB++;
     268             :         }
     269             :         // Consider that a directory is a FlatGeobuf dataset if there is a
     270             :         // majority of .fgb files in it
     271         415 :         if (nCountFGB == 0 || nCountFGB < nCountNonFGB)
     272             :         {
     273         414 :             return nullptr;
     274             :         }
     275           3 :         for (int i = 0; i < aosFiles.size(); i++)
     276             :         {
     277           2 :             if (EQUAL(CPLGetExtension(aosFiles[i]), "fgb"))
     278             :             {
     279           2 :                 CPLString osFilename(CPLFormFilename(poOpenInfo->pszFilename,
     280           4 :                                                      aosFiles[i], nullptr));
     281           2 :                 VSILFILE *fp = VSIFOpenL(osFilename, "rb");
     282           2 :                 if (fp)
     283             :                 {
     284           2 :                     if (!poDS->OpenFile(osFilename, fp, bVerifyBuffers))
     285           0 :                         VSIFCloseL(fp);
     286             :                 }
     287             :             }
     288             :         }
     289             :     }
     290             :     else
     291             :     {
     292         142 :         if (poOpenInfo->fpL != nullptr)
     293             :         {
     294         142 :             if (poDS->OpenFile(poOpenInfo->pszFilename, poOpenInfo->fpL,
     295             :                                bVerifyBuffers))
     296         142 :                 poOpenInfo->fpL = nullptr;
     297             :         }
     298             :         else
     299             :         {
     300           0 :             return nullptr;
     301             :         }
     302             :     }
     303         143 :     return poDS.release();
     304             : }
     305             : 
     306             : /************************************************************************/
     307             : /*                           OpenFile()                                 */
     308             : /************************************************************************/
     309             : 
     310         144 : bool OGRFlatGeobufDataset::OpenFile(const char *pszFilename, VSILFILE *fp,
     311             :                                     bool bVerifyBuffers)
     312             : {
     313         144 :     CPLDebugOnly("FlatGeobuf", "Opening OGRFlatGeobufLayer");
     314             :     auto poLayer = std::unique_ptr<OGRFlatGeobufLayer>(
     315         288 :         OGRFlatGeobufLayer::Open(pszFilename, fp, bVerifyBuffers));
     316         144 :     if (!poLayer)
     317           0 :         return false;
     318             : 
     319         144 :     if (m_bUpdate)
     320             :     {
     321           1 :         CPLDebugOnly("FlatGeobuf", "Creating OGRFlatGeobufEditableLayer");
     322             :         auto poEditableLayer = std::unique_ptr<OGRFlatGeobufEditableLayer>(
     323           1 :             new OGRFlatGeobufEditableLayer(poLayer.release(),
     324           1 :                                            papszOpenOptions));
     325           1 :         m_apoLayers.push_back(std::move(poEditableLayer));
     326             :     }
     327             :     else
     328             :     {
     329         143 :         m_apoLayers.push_back(std::move(poLayer));
     330             :     }
     331             : 
     332         144 :     return true;
     333             : }
     334             : 
     335         167 : GDALDataset *OGRFlatGeobufDataset::Create(const char *pszName, int /* nBands */,
     336             :                                           CPL_UNUSED int nXSize,
     337             :                                           CPL_UNUSED int nYSize,
     338             :                                           CPL_UNUSED GDALDataType eDT,
     339             :                                           char ** /* papszOptions */)
     340             : {
     341             :     // First, ensure there isn't any such file yet.
     342             :     VSIStatBufL sStatBuf;
     343             : 
     344         167 :     if (VSIStatL(pszName, &sStatBuf) == 0)
     345             :     {
     346           0 :         CPLError(CE_Failure, CPLE_AppDefined,
     347             :                  "It seems a file system object called '%s' already exists.",
     348             :                  pszName);
     349             : 
     350           0 :         return nullptr;
     351             :     }
     352             : 
     353         167 :     bool bIsDir = false;
     354         167 :     if (!EQUAL(CPLGetExtension(pszName), "fgb"))
     355             :     {
     356           1 :         if (VSIMkdir(pszName, 0755) != 0)
     357             :         {
     358           0 :             CPLError(CE_Failure, CPLE_AppDefined,
     359             :                      "Failed to create directory %s:\n%s", pszName,
     360           0 :                      VSIStrerror(errno));
     361           0 :             return nullptr;
     362             :         }
     363           1 :         bIsDir = true;
     364             :     }
     365             : 
     366         167 :     return new OGRFlatGeobufDataset(pszName, bIsDir, true, false);
     367             : }
     368             : 
     369         485 : OGRLayer *OGRFlatGeobufDataset::GetLayer(int iLayer)
     370             : {
     371         485 :     if (iLayer < 0 || iLayer >= GetLayerCount())
     372           2 :         return nullptr;
     373         483 :     return m_apoLayers[iLayer]->GetLayer();
     374             : }
     375             : 
     376          90 : int OGRFlatGeobufDataset::TestCapability(const char *pszCap)
     377             : {
     378          90 :     if (EQUAL(pszCap, ODsCCreateLayer))
     379          37 :         return m_bCreate && (m_bIsDir || m_apoLayers.empty());
     380          53 :     else if (EQUAL(pszCap, ODsCCurveGeometries))
     381          22 :         return true;
     382          31 :     else if (EQUAL(pszCap, ODsCMeasuredGeometries))
     383           2 :         return true;
     384          29 :     else if (EQUAL(pszCap, ODsCZGeometries))
     385           2 :         return true;
     386          27 :     else if (EQUAL(pszCap, ODsCRandomLayerWrite))
     387           0 :         return m_bUpdate;
     388             :     else
     389          27 :         return false;
     390             : }
     391             : 
     392             : /************************************************************************/
     393             : /*                        LaunderLayerName()                            */
     394             : /************************************************************************/
     395             : 
     396           2 : static CPLString LaunderLayerName(const char *pszLayerName)
     397             : {
     398           4 :     std::string osRet(CPLLaunderForFilename(pszLayerName, nullptr));
     399           2 :     if (osRet != pszLayerName)
     400             :     {
     401           1 :         CPLError(CE_Warning, CPLE_AppDefined,
     402             :                  "Invalid layer name for a file name: %s. Laundered to %s.",
     403             :                  pszLayerName, osRet.c_str());
     404             :     }
     405           4 :     return osRet;
     406             : }
     407             : 
     408             : OGRLayer *
     409         184 : OGRFlatGeobufDataset::ICreateLayer(const char *pszLayerName,
     410             :                                    const OGRGeomFieldDefn *poGeomFieldDefn,
     411             :                                    CSLConstList papszOptions)
     412             : {
     413             :     // Verify we are in update mode.
     414         184 :     if (!m_bCreate)
     415             :     {
     416           0 :         CPLError(CE_Failure, CPLE_NoWriteAccess,
     417             :                  "Data source %s opened read-only.\n"
     418             :                  "New layer %s cannot be created.",
     419           0 :                  GetDescription(), pszLayerName);
     420             : 
     421           0 :         return nullptr;
     422             :     }
     423         184 :     if (!m_bIsDir && !m_apoLayers.empty())
     424             :     {
     425          16 :         CPLError(CE_Failure, CPLE_NoWriteAccess,
     426             :                  "Can create only one single layer in a .fgb file. "
     427             :                  "Use a directory output for multiple layers");
     428             : 
     429          16 :         return nullptr;
     430             :     }
     431             : 
     432         168 :     const auto eGType = poGeomFieldDefn ? poGeomFieldDefn->GetType() : wkbNone;
     433             :     const auto poSpatialRef =
     434         168 :         poGeomFieldDefn ? poGeomFieldDefn->GetSpatialRef() : nullptr;
     435             : 
     436             :     // Verify that the datasource is a directory.
     437             :     VSIStatBufL sStatBuf;
     438             : 
     439             :     // What filename would we use?
     440         336 :     CPLString osFilename;
     441             : 
     442         168 :     if (m_bIsDir)
     443             :         osFilename = CPLFormFilename(
     444           2 :             GetDescription(), LaunderLayerName(pszLayerName).c_str(), "fgb");
     445             :     else
     446         166 :         osFilename = GetDescription();
     447             : 
     448             :     // Does this directory/file already exist?
     449         168 :     if (VSIStatL(osFilename, &sStatBuf) == 0)
     450             :     {
     451           0 :         CPLError(CE_Failure, CPLE_FileIO,
     452             :                  "Attempt to create layer %s, but %s already exists.",
     453             :                  pszLayerName, osFilename.c_str());
     454           0 :         return nullptr;
     455             :     }
     456             : 
     457             :     bool bCreateSpatialIndexAtClose =
     458         168 :         CPLFetchBool(papszOptions, "SPATIAL_INDEX", true);
     459             : 
     460             :     auto poLayer =
     461             :         std::unique_ptr<OGRFlatGeobufLayer>(OGRFlatGeobufLayer::Create(
     462             :             this, pszLayerName, osFilename, poSpatialRef, eGType,
     463         336 :             bCreateSpatialIndexAtClose, papszOptions));
     464         168 :     if (poLayer == nullptr)
     465           2 :         return nullptr;
     466             : 
     467         166 :     m_apoLayers.push_back(std::move(poLayer));
     468             : 
     469         166 :     return m_apoLayers.back()->GetLayer();
     470             : }
     471             : 
     472             : /************************************************************************/
     473             : //                            GetFileList()                             */
     474             : /************************************************************************/
     475             : 
     476           2 : char **OGRFlatGeobufDataset::GetFileList()
     477             : {
     478           4 :     CPLStringList oFileList;
     479           5 :     for (const auto &poLayer : m_apoLayers)
     480             :     {
     481           3 :         oFileList.AddString(poLayer->GetFilename().c_str());
     482             :     }
     483           4 :     return oFileList.StealList();
     484             : }

Generated by: LCOV version 1.14