LCOV - code coverage report
Current view: top level - ogr/ogrsf_frmts/shape - ogrshapedriver.cpp (source / functions) Hit Total Coverage
Test: gdal_filtered.info Lines: 164 171 95.9 %
Date: 2025-01-18 12:42:00 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       55699 : 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       55699 :     if (!poOpenInfo->bStatOK)
      35       43588 :         return FALSE;
      36       12111 :     if (poOpenInfo->bIsDirectory)
      37             :     {
      38        1883 :         if (STARTS_WITH(poOpenInfo->pszFilename, "/vsizip/") &&
      39          32 :             (strstr(poOpenInfo->pszFilename, ".shp.zip") ||
      40          31 :              strstr(poOpenInfo->pszFilename, ".SHP.ZIP")))
      41             :         {
      42           1 :             return TRUE;
      43             :         }
      44             : 
      45        1882 :         return GDAL_IDENTIFY_UNKNOWN;  // Unsure.
      46             :     }
      47       10228 :     if (poOpenInfo->fpL == nullptr)
      48             :     {
      49          83 :         return FALSE;
      50             :     }
      51       10145 :     const std::string &osExt = poOpenInfo->osExtension;
      52       10145 :     if (EQUAL(osExt.c_str(), "SHP") || EQUAL(osExt.c_str(), "SHX"))
      53             :     {
      54        4370 :         return poOpenInfo->nHeaderBytes >= 4 &&
      55        2185 :                (memcmp(poOpenInfo->pabyHeader, "\x00\x00\x27\x0A", 4) == 0 ||
      56        2185 :                 memcmp(poOpenInfo->pabyHeader, "\x00\x00\x27\x0D", 4) == 0);
      57             :     }
      58        7960 :     if (EQUAL(osExt.c_str(), "DBF"))
      59             :     {
      60         349 :         if (poOpenInfo->nHeaderBytes < 32)
      61           1 :             return FALSE;
      62         348 :         const GByte *pabyBuf = poOpenInfo->pabyHeader;
      63         348 :         const unsigned int nHeadLen = pabyBuf[8] + pabyBuf[9] * 256;
      64         348 :         const unsigned int nRecordLength = pabyBuf[10] + pabyBuf[11] * 256;
      65         348 :         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         348 :         const unsigned int nFields = (nHeadLen - 32) / 32;
      74         348 :         if (nRecordLength < nFields)
      75           0 :             return FALSE;
      76         348 :         return TRUE;
      77             :     }
      78       15212 :     if (EQUAL(osExt.c_str(), "shz") ||
      79        7601 :         (EQUAL(osExt.c_str(), "zip") &&
      80        7672 :          (CPLString(poOpenInfo->pszFilename).endsWith(".shp.zip") ||
      81        7670 :           CPLString(poOpenInfo->pszFilename).endsWith(".SHP.ZIP"))))
      82             :     {
      83          24 :         return poOpenInfo->nHeaderBytes >= 4 &&
      84          24 :                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       15198 :     if (!STARTS_WITH(poOpenInfo->pszFilename, "/vsitar/") &&
      89        7599 :         EQUAL(CPLGetFilename(poOpenInfo->pszFilename), ".cur_input"))
      90             :     {
      91           4 :         return GDAL_IDENTIFY_UNKNOWN;
      92             :     }
      93             : #endif
      94        7595 :     return FALSE;
      95             : }
      96             : 
      97             : /************************************************************************/
      98             : /*                                Open()                                */
      99             : /************************************************************************/
     100             : 
     101        2187 : static GDALDataset *OGRShapeDriverOpen(GDALOpenInfo *poOpenInfo)
     102             : 
     103             : {
     104        2187 :     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        5623 :     if (poOpenInfo->fpL != nullptr &&
     110        3438 :         !STARTS_WITH(poOpenInfo->pszFilename, "/vsitar/") &&
     111        1253 :         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        4366 :     CPLString osExt(CPLGetExtensionSafe(poOpenInfo->pszFilename));
     122        4353 :     if (!STARTS_WITH(poOpenInfo->pszFilename, "/vsizip/") &&
     123        2170 :         (EQUAL(osExt, "shz") ||
     124        2165 :          (EQUAL(osExt, "zip") &&
     125        2184 :           (CPLString(poOpenInfo->pszFilename).endsWith(".shp.zip") ||
     126        2183 :            CPLString(poOpenInfo->pszFilename).endsWith(".SHP.ZIP")))))
     127             :     {
     128             :         GDALOpenInfo oOpenInfo(
     129          12 :             (CPLString("/vsizip/{") + poOpenInfo->pszFilename + '}').c_str(),
     130          12 :             GA_ReadOnly);
     131           6 :         if (OGRShapeDriverIdentify(&oOpenInfo) == FALSE)
     132           0 :             return nullptr;
     133           6 :         oOpenInfo.eAccess = poOpenInfo->eAccess;
     134           6 :         OGRShapeDataSource *poDS = new OGRShapeDataSource();
     135             : 
     136           6 :         if (!poDS->OpenZip(&oOpenInfo, poOpenInfo->pszFilename))
     137             :         {
     138           0 :             delete poDS;
     139           0 :             return nullptr;
     140             :         }
     141             : 
     142           6 :         return poDS;
     143             :     }
     144             : 
     145        2177 :     OGRShapeDataSource *poDS = new OGRShapeDataSource();
     146             : 
     147        2177 :     if (!poDS->Open(poOpenInfo, true))
     148             :     {
     149         673 :         delete poDS;
     150         673 :         return nullptr;
     151             :     }
     152             : 
     153        1504 :     return poDS;
     154             : }
     155             : 
     156             : /************************************************************************/
     157             : /*                               Create()                               */
     158             : /************************************************************************/
     159             : 
     160         537 : static GDALDataset *OGRShapeDriverCreate(const char *pszName, int /* nBands */,
     161             :                                          int /* nXSize */, int /* nYSize */,
     162             :                                          GDALDataType /* eDT */,
     163             :                                          char ** /* papszOptions */)
     164             : {
     165         537 :     bool bSingleNewFile = false;
     166        1074 :     CPLString osExt(CPLGetExtensionSafe(pszName));
     167             : 
     168             :     /* -------------------------------------------------------------------- */
     169             :     /*      Is the target a valid existing directory?                       */
     170             :     /* -------------------------------------------------------------------- */
     171             :     VSIStatBufL stat;
     172         537 :     if (VSIStatL(pszName, &stat) == 0)
     173             :     {
     174          87 :         if (!VSI_ISDIR(stat.st_mode))
     175             :         {
     176           1 :             CPLError(CE_Failure, CPLE_AppDefined, "%s is not a directory.",
     177             :                      pszName);
     178             : 
     179           1 :             return nullptr;
     180             :         }
     181             :     }
     182             : 
     183             :     /* -------------------------------------------------------------------- */
     184             :     /*      Does it end in the extension .shp indicating the user likely    */
     185             :     /*      wants to create a single file set?                              */
     186             :     /* -------------------------------------------------------------------- */
     187         450 :     else if (EQUAL(osExt, "shp") || EQUAL(osExt, "dbf"))
     188             :     {
     189         423 :         bSingleNewFile = true;
     190             :     }
     191             : 
     192          52 :     else if (EQUAL(osExt, "shz") ||
     193          52 :              (EQUAL(osExt, "zip") && (CPLString(pszName).endsWith(".shp.zip") ||
     194          27 :                                       CPLString(pszName).endsWith(".SHP.ZIP"))))
     195             :     {
     196           3 :         OGRShapeDataSource *poDS = new OGRShapeDataSource();
     197             : 
     198           3 :         if (!poDS->CreateZip(pszName))
     199             :         {
     200           1 :             delete poDS;
     201           1 :             return nullptr;
     202             :         }
     203             : 
     204           2 :         return poDS;
     205             :     }
     206             : 
     207             :     /* -------------------------------------------------------------------- */
     208             :     /*      Otherwise try to create a new directory.                        */
     209             :     /* -------------------------------------------------------------------- */
     210             :     else
     211             :     {
     212          24 :         if (VSIMkdir(pszName, 0755) != 0)
     213             :         {
     214           1 :             CPLError(CE_Failure, CPLE_AppDefined,
     215             :                      "Failed to create directory %s "
     216             :                      "for shapefile datastore.",
     217             :                      pszName);
     218             : 
     219           1 :             return nullptr;
     220             :         }
     221             :     }
     222             : 
     223             :     /* -------------------------------------------------------------------- */
     224             :     /*      Return a new OGRDataSource()                                    */
     225             :     /* -------------------------------------------------------------------- */
     226         532 :     OGRShapeDataSource *poDS = new OGRShapeDataSource();
     227             : 
     228        1064 :     GDALOpenInfo oOpenInfo(pszName, GA_Update);
     229         532 :     if (!poDS->Open(&oOpenInfo, false, bSingleNewFile))
     230             :     {
     231           0 :         delete poDS;
     232           0 :         return nullptr;
     233             :     }
     234             : 
     235         532 :     return poDS;
     236             : }
     237             : 
     238             : /************************************************************************/
     239             : /*                           Delete()                                   */
     240             : /************************************************************************/
     241             : 
     242         165 : static CPLErr OGRShapeDriverDelete(const char *pszDataSource)
     243             : 
     244             : {
     245             :     VSIStatBufL sStatBuf;
     246             : 
     247         165 :     if (VSIStatL(pszDataSource, &sStatBuf) != 0)
     248             :     {
     249          17 :         CPLError(CE_Failure, CPLE_AppDefined,
     250             :                  "%s does not appear to be a file or directory.",
     251             :                  pszDataSource);
     252             : 
     253          17 :         return CE_Failure;
     254             :     }
     255             : 
     256         296 :     CPLString osExt(CPLGetExtensionSafe(pszDataSource));
     257         286 :     if (VSI_ISREG(sStatBuf.st_mode) &&
     258         138 :         (EQUAL(osExt, "shz") ||
     259         137 :          (EQUAL(osExt, "zip") &&
     260         148 :           (CPLString(pszDataSource).endsWith(".shp.zip") ||
     261         148 :            CPLString(pszDataSource).endsWith(".SHP.ZIP")))))
     262             :     {
     263           1 :         VSIUnlink(pszDataSource);
     264           1 :         return CE_None;
     265             :     }
     266             : 
     267             :     const char *const *papszExtensions =
     268         147 :         OGRShapeDataSource::GetExtensionsForDeletion();
     269             : 
     270         284 :     if (VSI_ISREG(sStatBuf.st_mode) &&
     271         137 :         (EQUAL(osExt, "shp") || EQUAL(osExt, "shx") || EQUAL(osExt, "dbf")))
     272             :     {
     273        1620 :         for (int iExt = 0; papszExtensions[iExt] != nullptr; iExt++)
     274             :         {
     275             :             const std::string osFile =
     276        2970 :                 CPLResetExtensionSafe(pszDataSource, papszExtensions[iExt]);
     277        1485 :             if (VSIStatL(osFile.c_str(), &sStatBuf) == 0)
     278         387 :                 VSIUnlink(osFile.c_str());
     279             :         }
     280             :     }
     281          12 :     else if (VSI_ISDIR(sStatBuf.st_mode))
     282             :     {
     283          10 :         char **papszDirEntries = VSIReadDir(pszDataSource);
     284             : 
     285         130 :         for (int iFile = 0;
     286         130 :              papszDirEntries != nullptr && papszDirEntries[iFile] != nullptr;
     287             :              iFile++)
     288             :         {
     289         120 :             if (CSLFindString(
     290             :                     papszExtensions,
     291         240 :                     CPLGetExtensionSafe(papszDirEntries[iFile]).c_str()) != -1)
     292             :             {
     293         104 :                 VSIUnlink(CPLFormFilenameSafe(pszDataSource,
     294         104 :                                               papszDirEntries[iFile], nullptr)
     295             :                               .c_str());
     296             :             }
     297             :         }
     298             : 
     299          10 :         CSLDestroy(papszDirEntries);
     300             : 
     301          10 :         VSIRmdir(pszDataSource);
     302             :     }
     303             : 
     304         147 :     return CE_None;
     305             : }
     306             : 
     307             : /************************************************************************/
     308             : /*                          RegisterOGRShape()                          */
     309             : /************************************************************************/
     310             : 
     311        1682 : void RegisterOGRShape()
     312             : 
     313             : {
     314        1682 :     if (GDALGetDriverByName("ESRI Shapefile") != nullptr)
     315         301 :         return;
     316             : 
     317        1381 :     GDALDriver *poDriver = new GDALDriver();
     318             : 
     319        1381 :     poDriver->SetDescription("ESRI Shapefile");
     320        1381 :     poDriver->SetMetadataItem(GDAL_DCAP_VECTOR, "YES");
     321        1381 :     poDriver->SetMetadataItem(GDAL_DCAP_CREATE_LAYER, "YES");
     322        1381 :     poDriver->SetMetadataItem(GDAL_DCAP_DELETE_LAYER, "YES");
     323        1381 :     poDriver->SetMetadataItem(GDAL_DCAP_CREATE_FIELD, "YES");
     324        1381 :     poDriver->SetMetadataItem(GDAL_DCAP_DELETE_FIELD, "YES");
     325        1381 :     poDriver->SetMetadataItem(GDAL_DCAP_REORDER_FIELDS, "YES");
     326        1381 :     poDriver->SetMetadataItem(GDAL_DCAP_MEASURED_GEOMETRIES, "YES");
     327        1381 :     poDriver->SetMetadataItem(GDAL_DCAP_Z_GEOMETRIES, "YES");
     328        1381 :     poDriver->SetMetadataItem(GDAL_DMD_GEOMETRY_FLAGS,
     329             :                               "EquatesMultiAndSingleLineStringDuringWrite "
     330        1381 :                               "EquatesMultiAndSinglePolygonDuringWrite");
     331             : 
     332        1381 :     poDriver->SetMetadataItem(GDAL_DMD_LONGNAME, "ESRI Shapefile");
     333        1381 :     poDriver->SetMetadataItem(GDAL_DMD_EXTENSION, "shp");
     334        1381 :     poDriver->SetMetadataItem(GDAL_DMD_EXTENSIONS, "shp dbf shz shp.zip");
     335        1381 :     poDriver->SetMetadataItem(GDAL_DMD_HELPTOPIC,
     336        1381 :                               "drivers/vector/shapefile.html");
     337        1381 :     poDriver->SetMetadataItem(GDAL_DMD_SUPPORTED_SQL_DIALECTS, "OGRSQL SQLITE");
     338        1381 :     poDriver->SetMetadataItem(GDAL_DMD_NUMERIC_FIELD_WIDTH_INCLUDES_SIGN,
     339        1381 :                               "YES");
     340        1381 :     poDriver->SetMetadataItem(
     341        1381 :         GDAL_DMD_NUMERIC_FIELD_WIDTH_INCLUDES_DECIMAL_SEPARATOR, "YES");
     342             : 
     343        1381 :     poDriver->SetMetadataItem(
     344             :         GDAL_DMD_OPENOPTIONLIST,
     345             :         "<OpenOptionList>"
     346             :         "  <Option name='ENCODING' type='string' description='to override the "
     347             :         "encoding interpretation of the DBF with any encoding supported by "
     348             :         "CPLRecode or to \"\" to avoid any recoding'/>"
     349             :         "  <Option name='DBF_DATE_LAST_UPDATE' type='string' "
     350             :         "description='Modification date to write in DBF header with YYYY-MM-DD "
     351             :         "format'/>"
     352             :         "  <Option name='ADJUST_TYPE' type='boolean' description='Whether to "
     353             :         "read whole .dbf to adjust Real->Integer/Integer64 or "
     354             :         "Integer64->Integer field types if possible' default='NO'/>"
     355             :         "  <Option name='ADJUST_GEOM_TYPE' type='string-select' "
     356             :         "description='Whether and how to adjust layer geometry type from "
     357             :         "actual shapes' default='FIRST_SHAPE'>"
     358             :         "    <Value>NO</Value>"
     359             :         "    <Value>FIRST_SHAPE</Value>"
     360             :         "    <Value>ALL_SHAPES</Value>"
     361             :         "  </Option>"
     362             :         "  <Option name='AUTO_REPACK' type='boolean' description='Whether the "
     363             :         "shapefile should be automatically repacked when needed' "
     364             :         "default='YES'/>"
     365             :         "  <Option name='DBF_EOF_CHAR' type='boolean' description='Whether to "
     366             :         "write the 0x1A end-of-file character in DBF files' default='YES'/>"
     367        1381 :         "</OpenOptionList>");
     368             : 
     369        1381 :     poDriver->SetMetadataItem(GDAL_DMD_CREATIONOPTIONLIST,
     370        1381 :                               "<CreationOptionList/>");
     371        1381 :     poDriver->SetMetadataItem(
     372             :         GDAL_DS_LAYER_CREATIONOPTIONLIST,
     373             :         "<LayerCreationOptionList>"
     374             :         "  <Option name='SHPT' type='string-select' description='type of "
     375             :         "shape' default='automatically detected'>"
     376             :         "    <Value>POINT</Value>"
     377             :         "    <Value>ARC</Value>"
     378             :         "    <Value>POLYGON</Value>"
     379             :         "    <Value>MULTIPOINT</Value>"
     380             :         "    <Value>POINTZ</Value>"
     381             :         "    <Value>ARCZ</Value>"
     382             :         "    <Value>POLYGONZ</Value>"
     383             :         "    <Value>MULTIPOINTZ</Value>"
     384             :         "    <Value>POINTM</Value>"
     385             :         "    <Value>ARCM</Value>"
     386             :         "    <Value>POLYGONM</Value>"
     387             :         "    <Value>MULTIPOINTM</Value>"
     388             :         "    <Value>POINTZM</Value>"
     389             :         "    <Value>ARCZM</Value>"
     390             :         "    <Value>POLYGONZM</Value>"
     391             :         "    <Value>MULTIPOINTZM</Value>"
     392             :         "    <Value>MULTIPATCH</Value>"
     393             :         "    <Value>NONE</Value>"
     394             :         "    <Value>NULL</Value>"
     395             :         "  </Option>"
     396             :         "  <Option name='2GB_LIMIT' type='boolean' description='Restrict .shp "
     397             :         "and .dbf to 2GB' default='NO'/>"
     398             :         "  <Option name='ENCODING' type='string' description='DBF encoding' "
     399             :         "default='LDID/87'/>"
     400             :         "  <Option name='RESIZE' type='boolean' description='To resize fields "
     401             :         "to their optimal size.' default='NO'/>"
     402             :         "  <Option name='SPATIAL_INDEX' type='boolean' description='To create "
     403             :         "a spatial index.' default='NO'/>"
     404             :         "  <Option name='DBF_DATE_LAST_UPDATE' type='string' "
     405             :         "description='Modification date to write in DBF header with YYYY-MM-DD "
     406             :         "format'/>"
     407             :         "  <Option name='AUTO_REPACK' type='boolean' description='Whether the "
     408             :         "shapefile should be automatically repacked when needed' "
     409             :         "default='YES'/>"
     410             :         "  <Option name='DBF_EOF_CHAR' type='boolean' description='Whether to "
     411             :         "write the 0x1A end-of-file character in DBF files' default='YES'/>"
     412        1381 :         "</LayerCreationOptionList>");
     413             : 
     414        1381 :     poDriver->SetMetadataItem(GDAL_DMD_CREATIONFIELDDATATYPES,
     415        1381 :                               "Integer Integer64 Real String Date");
     416        1381 :     poDriver->SetMetadataItem(GDAL_DMD_CREATIONFIELDDATASUBTYPES, "Boolean");
     417        1381 :     poDriver->SetMetadataItem(GDAL_DMD_CREATION_FIELD_DEFN_FLAGS,
     418        1381 :                               "WidthPrecision");
     419        1381 :     poDriver->SetMetadataItem(GDAL_DMD_ALTER_FIELD_DEFN_FLAGS,
     420        1381 :                               "Name Type WidthPrecision");
     421        1381 :     poDriver->SetMetadataItem(GDAL_DCAP_VIRTUALIO, "YES");
     422        1381 :     poDriver->SetMetadataItem(GDAL_DCAP_RENAME_LAYERS, "YES");
     423             : 
     424        1381 :     poDriver->SetMetadataItem(GDAL_DMD_ALTER_GEOM_FIELD_DEFN_FLAGS, "SRS");
     425             : 
     426        1381 :     poDriver->pfnOpen = OGRShapeDriverOpen;
     427        1381 :     poDriver->pfnIdentify = OGRShapeDriverIdentify;
     428        1381 :     poDriver->pfnCreate = OGRShapeDriverCreate;
     429        1381 :     poDriver->pfnDelete = OGRShapeDriverDelete;
     430             : 
     431        1381 :     GetGDALDriverManager()->RegisterDriver(poDriver);
     432             : }

Generated by: LCOV version 1.14