LCOV - code coverage report
Current view: top level - ogr/ogrsf_frmts/shape - ogrshapedriver.cpp (source / functions) Hit Total Coverage
Test: gdal_filtered.info Lines: 162 167 97.0 %
Date: 2025-05-31 00:00:17 Functions: 5 5 100.0 %

          Line data    Source code
       1             : /******************************************************************************
       2             :  *
       3             :  * Project:  OpenGIS Simple Features Reference Implementation
       4             :  * Purpose:  Implements OGRShapeDriver class.
       5             :  * Author:   Frank Warmerdam, warmerdam@pobox.com
       6             :  *
       7             :  ******************************************************************************
       8             :  * Copyright (c) 1999,  Les Technologies SoftMap Inc.
       9             :  *
      10             :  * SPDX-License-Identifier: MIT
      11             :  ****************************************************************************/
      12             : 
      13             : #include "ogrshape.h"
      14             : 
      15             : #include <cstring>
      16             : 
      17             : #include "cpl_conv.h"
      18             : #include "cpl_error.h"
      19             : #include "cpl_port.h"
      20             : #include "cpl_string.h"
      21             : #include "cpl_vsi.h"
      22             : #include "gdal.h"
      23             : #include "gdal_priv.h"
      24             : #include "ogrsf_frmts.h"
      25             : 
      26             : /************************************************************************/
      27             : /*                              Identify()                              */
      28             : /************************************************************************/
      29             : 
      30       61398 : static int OGRShapeDriverIdentify(GDALOpenInfo *poOpenInfo)
      31             : {
      32             :     // Files not ending with .shp, .shx, .dbf, .shz or .shp.zip are not
      33             :     // handled by this driver.
      34       61398 :     if (!poOpenInfo->bStatOK)
      35       48361 :         return FALSE;
      36       13037 :     if (poOpenInfo->bIsDirectory)
      37             :     {
      38        2186 :         if (STARTS_WITH(poOpenInfo->pszFilename, "/vsizip/") &&
      39          36 :             (strstr(poOpenInfo->pszFilename, ".shp.zip") ||
      40          35 :              strstr(poOpenInfo->pszFilename, ".SHP.ZIP")))
      41             :         {
      42           1 :             return TRUE;
      43             :         }
      44             : 
      45        2185 :         return GDAL_IDENTIFY_UNKNOWN;  // Unsure.
      46             :     }
      47       10851 :     if (poOpenInfo->fpL == nullptr)
      48             :     {
      49          83 :         return FALSE;
      50             :     }
      51       10768 :     const std::string &osExt = poOpenInfo->osExtension;
      52       10768 :     if (EQUAL(osExt.c_str(), "SHP") || EQUAL(osExt.c_str(), "SHX"))
      53             :     {
      54        5582 :         return poOpenInfo->nHeaderBytes >= 4 &&
      55        2791 :                (memcmp(poOpenInfo->pabyHeader, "\x00\x00\x27\x0A", 4) == 0 ||
      56        2791 :                 memcmp(poOpenInfo->pabyHeader, "\x00\x00\x27\x0D", 4) == 0);
      57             :     }
      58        7977 :     if (EQUAL(osExt.c_str(), "DBF"))
      59             :     {
      60         365 :         if (poOpenInfo->nHeaderBytes < 32)
      61           1 :             return FALSE;
      62         364 :         const GByte *pabyBuf = poOpenInfo->pabyHeader;
      63         364 :         const unsigned int nHeadLen = pabyBuf[8] + pabyBuf[9] * 256;
      64         364 :         const unsigned int nRecordLength = pabyBuf[10] + pabyBuf[11] * 256;
      65         364 :         if (nHeadLen < 32)
      66           0 :             return FALSE;
      67             :         // The header length of some .dbf files can be a non-multiple of 32
      68             :         // See https://trac.osgeo.org/gdal/ticket/6035
      69             :         // Hopefully there are not so many .dbf files around that are not real
      70             :         // DBFs
      71             :         // if( (nHeadLen % 32) != 0 && (nHeadLen % 32) != 1 )
      72             :         //     return FALSE;
      73         364 :         const unsigned int nFields = (nHeadLen - 32) / 32;
      74         364 :         if (nRecordLength < nFields)
      75           0 :             return FALSE;
      76         364 :         return TRUE;
      77             :     }
      78       15210 :     if (EQUAL(osExt.c_str(), "shz") ||
      79        7598 :         (EQUAL(osExt.c_str(), "zip") &&
      80        7674 :          (CPLString(poOpenInfo->pszFilename).endsWith(".shp.zip") ||
      81        7672 :           CPLString(poOpenInfo->pszFilename).endsWith(".SHP.ZIP"))))
      82             :     {
      83          32 :         return poOpenInfo->nHeaderBytes >= 4 &&
      84          32 :                memcmp(poOpenInfo->pabyHeader, "\x50\x4B\x03\x04", 4) == 0;
      85             :     }
      86             : #ifdef DEBUG
      87             :     // For AFL, so that .cur_input is detected as the archive filename.
      88       15192 :     if (!STARTS_WITH(poOpenInfo->pszFilename, "/vsitar/") &&
      89        7596 :         EQUAL(CPLGetFilename(poOpenInfo->pszFilename), ".cur_input"))
      90             :     {
      91           4 :         return GDAL_IDENTIFY_UNKNOWN;
      92             :     }
      93             : #endif
      94        7592 :     return FALSE;
      95             : }
      96             : 
      97             : /************************************************************************/
      98             : /*                                Open()                                */
      99             : /************************************************************************/
     100             : 
     101        2584 : static GDALDataset *OGRShapeDriverOpen(GDALOpenInfo *poOpenInfo)
     102             : 
     103             : {
     104        2584 :     if (OGRShapeDriverIdentify(poOpenInfo) == FALSE)
     105           2 :         return nullptr;
     106             : 
     107             : #ifdef DEBUG
     108             :     // For AFL, so that .cur_input is detected as the archive filename.
     109        6719 :     if (poOpenInfo->fpL != nullptr &&
     110        4137 :         !STARTS_WITH(poOpenInfo->pszFilename, "/vsitar/") &&
     111        1555 :         EQUAL(CPLGetFilename(poOpenInfo->pszFilename), ".cur_input"))
     112             :     {
     113             :         GDALOpenInfo oOpenInfo(
     114           4 :             (CPLString("/vsitar/") + poOpenInfo->pszFilename).c_str(),
     115           6 :             poOpenInfo->nOpenFlags);
     116           2 :         oOpenInfo.papszOpenOptions = poOpenInfo->papszOpenOptions;
     117           2 :         return OGRShapeDriverOpen(&oOpenInfo);
     118             :     }
     119             : #endif
     120             : 
     121        5160 :     CPLString osExt(CPLGetExtensionSafe(poOpenInfo->pszFilename));
     122        5146 :     if (!STARTS_WITH(poOpenInfo->pszFilename, "/vsizip/") &&
     123        2566 :         (EQUAL(osExt, "shz") ||
     124        2559 :          (EQUAL(osExt, "zip") &&
     125        2581 :           (CPLString(poOpenInfo->pszFilename).endsWith(".shp.zip") ||
     126        2580 :            CPLString(poOpenInfo->pszFilename).endsWith(".SHP.ZIP")))))
     127             :     {
     128             :         GDALOpenInfo oOpenInfo(
     129          16 :             (CPLString("/vsizip/{") + poOpenInfo->pszFilename + '}').c_str(),
     130          16 :             GA_ReadOnly);
     131           8 :         if (OGRShapeDriverIdentify(&oOpenInfo) == FALSE)
     132           0 :             return nullptr;
     133           8 :         oOpenInfo.eAccess = poOpenInfo->eAccess;
     134          16 :         auto poDS = std::make_unique<OGRShapeDataSource>();
     135             : 
     136           8 :         if (!poDS->OpenZip(&oOpenInfo, poOpenInfo->pszFilename))
     137             :         {
     138           0 :             return nullptr;
     139             :         }
     140             : 
     141           8 :         return poDS.release();
     142             :     }
     143             : 
     144        5144 :     auto poDS = std::make_unique<OGRShapeDataSource>();
     145             : 
     146        2572 :     if (!poDS->Open(poOpenInfo, true))
     147             :     {
     148         717 :         return nullptr;
     149             :     }
     150             : 
     151        1855 :     return poDS.release();
     152             : }
     153             : 
     154             : /************************************************************************/
     155             : /*                               Create()                               */
     156             : /************************************************************************/
     157             : 
     158         611 : static GDALDataset *OGRShapeDriverCreate(const char *pszName, int /* nBands */,
     159             :                                          int /* nXSize */, int /* nYSize */,
     160             :                                          GDALDataType /* eDT */,
     161             :                                          char ** /* papszOptions */)
     162             : {
     163         611 :     bool bSingleNewFile = false;
     164        1222 :     const CPLString osExt(CPLGetExtensionSafe(pszName));
     165             : 
     166             :     /* -------------------------------------------------------------------- */
     167             :     /*      Is the target a valid existing directory?                       */
     168             :     /* -------------------------------------------------------------------- */
     169             :     VSIStatBufL stat;
     170         611 :     if (VSIStatL(pszName, &stat) == 0)
     171             :     {
     172          94 :         if (!VSI_ISDIR(stat.st_mode))
     173             :         {
     174           1 :             CPLError(CE_Failure, CPLE_AppDefined, "%s is not a directory.",
     175             :                      pszName);
     176             : 
     177           1 :             return nullptr;
     178             :         }
     179             :     }
     180             : 
     181             :     /* -------------------------------------------------------------------- */
     182             :     /*      Does it end in the extension .shp indicating the user likely    */
     183             :     /*      wants to create a single file set?                              */
     184             :     /* -------------------------------------------------------------------- */
     185         517 :     else if (EQUAL(osExt, "shp") || EQUAL(osExt, "dbf"))
     186             :     {
     187         483 :         bSingleNewFile = true;
     188             :     }
     189             : 
     190          65 :     else if (EQUAL(osExt, "shz") ||
     191          65 :              (EQUAL(osExt, "zip") && (CPLString(pszName).endsWith(".shp.zip") ||
     192          34 :                                       CPLString(pszName).endsWith(".SHP.ZIP"))))
     193             :     {
     194           8 :         auto poDS = std::make_unique<OGRShapeDataSource>();
     195             : 
     196           4 :         if (!poDS->CreateZip(pszName))
     197             :         {
     198           1 :             return nullptr;
     199             :         }
     200             : 
     201           3 :         return poDS.release();
     202             :     }
     203             : 
     204             :     /* -------------------------------------------------------------------- */
     205             :     /*      Otherwise try to create a new directory.                        */
     206             :     /* -------------------------------------------------------------------- */
     207             :     else
     208             :     {
     209          30 :         if (VSIMkdir(pszName, 0755) != 0)
     210             :         {
     211           1 :             CPLError(CE_Failure, CPLE_AppDefined,
     212             :                      "Failed to create directory %s "
     213             :                      "for shapefile datastore.",
     214             :                      pszName);
     215             : 
     216           1 :             return nullptr;
     217             :         }
     218             :     }
     219             : 
     220             :     /* -------------------------------------------------------------------- */
     221             :     /*      Return a new OGRDataSource()                                    */
     222             :     /* -------------------------------------------------------------------- */
     223        1210 :     auto poDS = std::make_unique<OGRShapeDataSource>();
     224             : 
     225        1210 :     GDALOpenInfo oOpenInfo(pszName, GA_Update);
     226         605 :     if (!poDS->Open(&oOpenInfo, false, bSingleNewFile))
     227             :     {
     228           0 :         return nullptr;
     229             :     }
     230             : 
     231         605 :     return poDS.release();
     232             : }
     233             : 
     234             : /************************************************************************/
     235             : /*                           Delete()                                   */
     236             : /************************************************************************/
     237             : 
     238         170 : static CPLErr OGRShapeDriverDelete(const char *pszDataSource)
     239             : 
     240             : {
     241             :     VSIStatBufL sStatBuf;
     242             : 
     243         170 :     if (VSIStatL(pszDataSource, &sStatBuf) != 0)
     244             :     {
     245          17 :         CPLError(CE_Failure, CPLE_AppDefined,
     246             :                  "%s does not appear to be a file or directory.",
     247             :                  pszDataSource);
     248             : 
     249          17 :         return CE_Failure;
     250             :     }
     251             : 
     252         306 :     const CPLString osExt(CPLGetExtensionSafe(pszDataSource));
     253         296 :     if (VSI_ISREG(sStatBuf.st_mode) &&
     254         143 :         (EQUAL(osExt, "shz") ||
     255         142 :          (EQUAL(osExt, "zip") &&
     256         153 :           (CPLString(pszDataSource).endsWith(".shp.zip") ||
     257         153 :            CPLString(pszDataSource).endsWith(".SHP.ZIP")))))
     258             :     {
     259           1 :         VSIUnlink(pszDataSource);
     260           1 :         return CE_None;
     261             :     }
     262             : 
     263             :     const char *const *papszExtensions =
     264         152 :         OGRShapeDataSource::GetExtensionsForDeletion();
     265             : 
     266         294 :     if (VSI_ISREG(sStatBuf.st_mode) &&
     267         142 :         (EQUAL(osExt, "shp") || EQUAL(osExt, "shx") || EQUAL(osExt, "dbf")))
     268             :     {
     269        1680 :         for (int iExt = 0; papszExtensions[iExt] != nullptr; iExt++)
     270             :         {
     271             :             const std::string osFile =
     272        3080 :                 CPLResetExtensionSafe(pszDataSource, papszExtensions[iExt]);
     273        1540 :             if (VSIStatL(osFile.c_str(), &sStatBuf) == 0)
     274         405 :                 VSIUnlink(osFile.c_str());
     275             :         }
     276             :     }
     277          12 :     else if (VSI_ISDIR(sStatBuf.st_mode))
     278             :     {
     279          20 :         const CPLStringList aosDirEntries(VSIReadDir(pszDataSource));
     280             : 
     281         130 :         for (const char *pszEntry : cpl::Iterate(aosDirEntries))
     282             :         {
     283         120 :             if (CSLFindString(papszExtensions,
     284         240 :                               CPLGetExtensionSafe(pszEntry).c_str()) != -1)
     285             :             {
     286         104 :                 VSIUnlink(CPLFormFilenameSafe(pszDataSource, pszEntry, nullptr)
     287             :                               .c_str());
     288             :             }
     289             :         }
     290             : 
     291          10 :         VSIRmdir(pszDataSource);
     292             :     }
     293             : 
     294         152 :     return CE_None;
     295             : }
     296             : 
     297             : /************************************************************************/
     298             : /*                          RegisterOGRShape()                          */
     299             : /************************************************************************/
     300             : 
     301        1889 : void RegisterOGRShape()
     302             : 
     303             : {
     304        1889 :     if (GDALGetDriverByName("ESRI Shapefile") != nullptr)
     305         282 :         return;
     306             : 
     307        1607 :     GDALDriver *poDriver = new GDALDriver();
     308             : 
     309        1607 :     poDriver->SetDescription("ESRI Shapefile");
     310        1607 :     poDriver->SetMetadataItem(GDAL_DCAP_VECTOR, "YES");
     311        1607 :     poDriver->SetMetadataItem(GDAL_DCAP_CREATE_LAYER, "YES");
     312        1607 :     poDriver->SetMetadataItem(GDAL_DCAP_DELETE_LAYER, "YES");
     313        1607 :     poDriver->SetMetadataItem(GDAL_DCAP_CREATE_FIELD, "YES");
     314        1607 :     poDriver->SetMetadataItem(GDAL_DCAP_DELETE_FIELD, "YES");
     315        1607 :     poDriver->SetMetadataItem(GDAL_DCAP_REORDER_FIELDS, "YES");
     316        1607 :     poDriver->SetMetadataItem(GDAL_DCAP_MEASURED_GEOMETRIES, "YES");
     317        1607 :     poDriver->SetMetadataItem(GDAL_DCAP_Z_GEOMETRIES, "YES");
     318        1607 :     poDriver->SetMetadataItem(GDAL_DMD_GEOMETRY_FLAGS,
     319             :                               "EquatesMultiAndSingleLineStringDuringWrite "
     320        1607 :                               "EquatesMultiAndSinglePolygonDuringWrite");
     321             : 
     322        1607 :     poDriver->SetMetadataItem(GDAL_DMD_LONGNAME, "ESRI Shapefile");
     323        1607 :     poDriver->SetMetadataItem(GDAL_DMD_EXTENSION, "shp");
     324        1607 :     poDriver->SetMetadataItem(GDAL_DMD_EXTENSIONS, "shp dbf shz shp.zip");
     325        1607 :     poDriver->SetMetadataItem(GDAL_DMD_HELPTOPIC,
     326        1607 :                               "drivers/vector/shapefile.html");
     327        1607 :     poDriver->SetMetadataItem(GDAL_DMD_SUPPORTED_SQL_DIALECTS, "OGRSQL SQLITE");
     328        1607 :     poDriver->SetMetadataItem(GDAL_DMD_NUMERIC_FIELD_WIDTH_INCLUDES_SIGN,
     329        1607 :                               "YES");
     330        1607 :     poDriver->SetMetadataItem(
     331        1607 :         GDAL_DMD_NUMERIC_FIELD_WIDTH_INCLUDES_DECIMAL_SEPARATOR, "YES");
     332             : 
     333        1607 :     poDriver->SetMetadataItem(
     334             :         GDAL_DMD_OPENOPTIONLIST,
     335             :         "<OpenOptionList>"
     336             :         "  <Option name='ENCODING' type='string' description='to override the "
     337             :         "encoding interpretation of the DBF with any encoding supported by "
     338             :         "CPLRecode or to \"\" to avoid any recoding'/>"
     339             :         "  <Option name='DBF_DATE_LAST_UPDATE' type='string' "
     340             :         "description='Modification date to write in DBF header with YYYY-MM-DD "
     341             :         "format'/>"
     342             :         "  <Option name='ADJUST_TYPE' type='boolean' description='Whether to "
     343             :         "read whole .dbf to adjust Real->Integer/Integer64 or "
     344             :         "Integer64->Integer field types if possible' default='NO'/>"
     345             :         "  <Option name='ADJUST_GEOM_TYPE' type='string-select' "
     346             :         "description='Whether and how to adjust layer geometry type from "
     347             :         "actual shapes' default='FIRST_SHAPE'>"
     348             :         "    <Value>NO</Value>"
     349             :         "    <Value>FIRST_SHAPE</Value>"
     350             :         "    <Value>ALL_SHAPES</Value>"
     351             :         "  </Option>"
     352             :         "  <Option name='AUTO_REPACK' type='boolean' description='Whether the "
     353             :         "shapefile should be automatically repacked when needed' "
     354             :         "default='YES'/>"
     355             :         "  <Option name='DBF_EOF_CHAR' type='boolean' description='Whether to "
     356             :         "write the 0x1A end-of-file character in DBF files' default='YES'/>"
     357        1607 :         "</OpenOptionList>");
     358             : 
     359        1607 :     poDriver->SetMetadataItem(GDAL_DMD_CREATIONOPTIONLIST,
     360        1607 :                               "<CreationOptionList/>");
     361        1607 :     poDriver->SetMetadataItem(
     362             :         GDAL_DS_LAYER_CREATIONOPTIONLIST,
     363             :         "<LayerCreationOptionList>"
     364             :         "  <Option name='SHPT' type='string-select' description='type of "
     365             :         "shape' default='automatically detected'>"
     366             :         "    <Value>POINT</Value>"
     367             :         "    <Value>ARC</Value>"
     368             :         "    <Value>POLYGON</Value>"
     369             :         "    <Value>MULTIPOINT</Value>"
     370             :         "    <Value>POINTZ</Value>"
     371             :         "    <Value>ARCZ</Value>"
     372             :         "    <Value>POLYGONZ</Value>"
     373             :         "    <Value>MULTIPOINTZ</Value>"
     374             :         "    <Value>POINTM</Value>"
     375             :         "    <Value>ARCM</Value>"
     376             :         "    <Value>POLYGONM</Value>"
     377             :         "    <Value>MULTIPOINTM</Value>"
     378             :         "    <Value>POINTZM</Value>"
     379             :         "    <Value>ARCZM</Value>"
     380             :         "    <Value>POLYGONZM</Value>"
     381             :         "    <Value>MULTIPOINTZM</Value>"
     382             :         "    <Value>MULTIPATCH</Value>"
     383             :         "    <Value>NONE</Value>"
     384             :         "    <Value>NULL</Value>"
     385             :         "  </Option>"
     386             :         "  <Option name='2GB_LIMIT' type='boolean' description='Restrict .shp "
     387             :         "and .dbf to 2GB' default='NO'/>"
     388             :         "  <Option name='ENCODING' type='string' description='DBF encoding' "
     389             :         "default='LDID/87'/>"
     390             :         "  <Option name='RESIZE' type='boolean' description='To resize fields "
     391             :         "to their optimal size.' default='NO'/>"
     392             :         "  <Option name='SPATIAL_INDEX' type='boolean' description='To create "
     393             :         "a spatial index.' default='NO'/>"
     394             :         "  <Option name='DBF_DATE_LAST_UPDATE' type='string' "
     395             :         "description='Modification date to write in DBF header with YYYY-MM-DD "
     396             :         "format'/>"
     397             :         "  <Option name='AUTO_REPACK' type='boolean' description='Whether the "
     398             :         "shapefile should be automatically repacked when needed' "
     399             :         "default='YES'/>"
     400             :         "  <Option name='DBF_EOF_CHAR' type='boolean' description='Whether to "
     401             :         "write the 0x1A end-of-file character in DBF files' default='YES'/>"
     402        1607 :         "</LayerCreationOptionList>");
     403             : 
     404        1607 :     poDriver->SetMetadataItem(GDAL_DMD_CREATIONFIELDDATATYPES,
     405        1607 :                               "Integer Integer64 Real String Date");
     406        1607 :     poDriver->SetMetadataItem(GDAL_DMD_CREATIONFIELDDATASUBTYPES, "Boolean");
     407        1607 :     poDriver->SetMetadataItem(GDAL_DMD_MAX_STRING_LENGTH, "254");
     408        1607 :     poDriver->SetMetadataItem(GDAL_DMD_CREATION_FIELD_DEFN_FLAGS,
     409        1607 :                               "WidthPrecision");
     410        1607 :     poDriver->SetMetadataItem(GDAL_DMD_ALTER_FIELD_DEFN_FLAGS,
     411        1607 :                               "Name Type WidthPrecision");
     412        1607 :     poDriver->SetMetadataItem(GDAL_DCAP_VIRTUALIO, "YES");
     413        1607 :     poDriver->SetMetadataItem(GDAL_DCAP_RENAME_LAYERS, "YES");
     414             : 
     415        1607 :     poDriver->SetMetadataItem(GDAL_DMD_ALTER_GEOM_FIELD_DEFN_FLAGS, "SRS");
     416             : 
     417        1607 :     poDriver->SetMetadataItem(GDAL_DCAP_UPDATE, "YES");
     418        1607 :     poDriver->SetMetadataItem(GDAL_DMD_UPDATE_ITEMS, "Features");
     419             : 
     420        1607 :     poDriver->pfnOpen = OGRShapeDriverOpen;
     421        1607 :     poDriver->pfnIdentify = OGRShapeDriverIdentify;
     422        1607 :     poDriver->pfnCreate = OGRShapeDriverCreate;
     423        1607 :     poDriver->pfnDelete = OGRShapeDriverDelete;
     424             : 
     425        1607 :     GetGDALDriverManager()->RegisterDriver(poDriver);
     426             : }

Generated by: LCOV version 1.14