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 170 95.3 %
Date: 2024-11-21 22:18:42 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       54974 : 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       54974 :     if (!poOpenInfo->bStatOK)
      35       43115 :         return FALSE;
      36       11859 :     if (poOpenInfo->bIsDirectory)
      37             :     {
      38        1877 :         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        1876 :         return GDAL_IDENTIFY_UNKNOWN;  // Unsure.
      46             :     }
      47        9982 :     if (poOpenInfo->fpL == nullptr)
      48             :     {
      49          83 :         return FALSE;
      50             :     }
      51       19798 :     const std::string osExt(CPLGetExtension(poOpenInfo->pszFilename));
      52        9899 :     if (EQUAL(osExt.c_str(), "SHP") || EQUAL(osExt.c_str(), "SHX"))
      53             :     {
      54        4110 :         return poOpenInfo->nHeaderBytes >= 4 &&
      55        2055 :                (memcmp(poOpenInfo->pabyHeader, "\x00\x00\x27\x0A", 4) == 0 ||
      56        2056 :                 memcmp(poOpenInfo->pabyHeader, "\x00\x00\x27\x0D", 4) == 0);
      57             :     }
      58        7844 :     if (EQUAL(osExt.c_str(), "DBF"))
      59             :     {
      60         340 :         if (poOpenInfo->nHeaderBytes < 32)
      61           0 :             return FALSE;
      62         340 :         const GByte *pabyBuf = poOpenInfo->pabyHeader;
      63         340 :         const unsigned int nHeadLen = pabyBuf[8] + pabyBuf[9] * 256;
      64         340 :         const unsigned int nRecordLength = pabyBuf[10] + pabyBuf[11] * 256;
      65         340 :         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         340 :         const unsigned int nFields = (nHeadLen - 32) / 32;
      74         340 :         if (nRecordLength < nFields)
      75           0 :             return FALSE;
      76         340 :         return TRUE;
      77             :     }
      78       14998 :     if (EQUAL(osExt.c_str(), "shz") ||
      79        7494 :         (EQUAL(osExt.c_str(), "zip") &&
      80        7565 :          (CPLString(poOpenInfo->pszFilename).endsWith(".shp.zip") ||
      81        7563 :           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       14984 :     if (!STARTS_WITH(poOpenInfo->pszFilename, "/vsitar/") &&
      89        7492 :         EQUAL(CPLGetFilename(poOpenInfo->pszFilename), ".cur_input"))
      90             :     {
      91           4 :         return GDAL_IDENTIFY_UNKNOWN;
      92             :     }
      93             : #endif
      94        7488 :     return FALSE;
      95             : }
      96             : 
      97             : /************************************************************************/
      98             : /*                                Open()                                */
      99             : /************************************************************************/
     100             : 
     101        2115 : static GDALDataset *OGRShapeDriverOpen(GDALOpenInfo *poOpenInfo)
     102             : 
     103             : {
     104        2115 :     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        5410 :     if (poOpenInfo->fpL != nullptr &&
     110        3297 :         !STARTS_WITH(poOpenInfo->pszFilename, "/vsitar/") &&
     111        1184 :         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        4222 :     CPLString osExt(CPLGetExtension(poOpenInfo->pszFilename));
     122        4209 :     if (!STARTS_WITH(poOpenInfo->pszFilename, "/vsizip/") &&
     123        2098 :         (EQUAL(osExt, "shz") ||
     124        2093 :          (EQUAL(osExt, "zip") &&
     125        2112 :           (CPLString(poOpenInfo->pszFilename).endsWith(".shp.zip") ||
     126        2111 :            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        2105 :     OGRShapeDataSource *poDS = new OGRShapeDataSource();
     146             : 
     147        2105 :     if (!poDS->Open(poOpenInfo, true))
     148             :     {
     149         670 :         delete poDS;
     150         670 :         return nullptr;
     151             :     }
     152             : 
     153        1435 :     return poDS;
     154             : }
     155             : 
     156             : /************************************************************************/
     157             : /*                               Create()                               */
     158             : /************************************************************************/
     159             : 
     160         516 : static GDALDataset *OGRShapeDriverCreate(const char *pszName, int /* nBands */,
     161             :                                          int /* nXSize */, int /* nYSize */,
     162             :                                          GDALDataType /* eDT */,
     163             :                                          char ** /* papszOptions */)
     164             : {
     165         516 :     bool bSingleNewFile = false;
     166        1032 :     CPLString osExt(CPLGetExtension(pszName));
     167             : 
     168             :     /* -------------------------------------------------------------------- */
     169             :     /*      Is the target a valid existing directory?                       */
     170             :     /* -------------------------------------------------------------------- */
     171             :     VSIStatBufL stat;
     172         516 :     if (VSIStatL(pszName, &stat) == 0)
     173             :     {
     174          93 :         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         423 :     else if (EQUAL(osExt, "shp") || EQUAL(osExt, "dbf"))
     188             :     {
     189         397 :         bSingleNewFile = true;
     190             :     }
     191             : 
     192          50 :     else if (EQUAL(osExt, "shz") ||
     193          50 :              (EQUAL(osExt, "zip") && (CPLString(pszName).endsWith(".shp.zip") ||
     194          26 :                                       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          23 :         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         511 :     OGRShapeDataSource *poDS = new OGRShapeDataSource();
     227             : 
     228        1022 :     GDALOpenInfo oOpenInfo(pszName, GA_Update);
     229         511 :     if (!poDS->Open(&oOpenInfo, false, bSingleNewFile))
     230             :     {
     231           0 :         delete poDS;
     232           0 :         return nullptr;
     233             :     }
     234             : 
     235         511 :     return poDS;
     236             : }
     237             : 
     238             : /************************************************************************/
     239             : /*                           Delete()                                   */
     240             : /************************************************************************/
     241             : 
     242         164 : static CPLErr OGRShapeDriverDelete(const char *pszDataSource)
     243             : 
     244             : {
     245             :     VSIStatBufL sStatBuf;
     246             : 
     247         164 :     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         294 :     CPLString osExt(CPLGetExtension(pszDataSource));
     257         284 :     if (VSI_ISREG(sStatBuf.st_mode) &&
     258         137 :         (EQUAL(osExt, "shz") ||
     259         136 :          (EQUAL(osExt, "zip") &&
     260         147 :           (CPLString(pszDataSource).endsWith(".shp.zip") ||
     261         147 :            CPLString(pszDataSource).endsWith(".SHP.ZIP")))))
     262             :     {
     263           1 :         VSIUnlink(pszDataSource);
     264           1 :         return CE_None;
     265             :     }
     266             : 
     267             :     const char *const *papszExtensions =
     268         146 :         OGRShapeDataSource::GetExtensionsForDeletion();
     269             : 
     270         282 :     if (VSI_ISREG(sStatBuf.st_mode) &&
     271         136 :         (EQUAL(osExt, "shp") || EQUAL(osExt, "shx") || EQUAL(osExt, "dbf")))
     272             :     {
     273        1608 :         for (int iExt = 0; papszExtensions[iExt] != nullptr; iExt++)
     274             :         {
     275             :             const char *pszFile =
     276        1474 :                 CPLResetExtension(pszDataSource, papszExtensions[iExt]);
     277        1474 :             if (VSIStatL(pszFile, &sStatBuf) == 0)
     278         383 :                 VSIUnlink(pszFile);
     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(papszExtensions,
     290         240 :                               CPLGetExtension(papszDirEntries[iFile])) != -1)
     291             :             {
     292         104 :                 VSIUnlink(CPLFormFilename(pszDataSource, papszDirEntries[iFile],
     293             :                                           nullptr));
     294             :             }
     295             :         }
     296             : 
     297          10 :         CSLDestroy(papszDirEntries);
     298             : 
     299          10 :         VSIRmdir(pszDataSource);
     300             :     }
     301             : 
     302         146 :     return CE_None;
     303             : }
     304             : 
     305             : /************************************************************************/
     306             : /*                          RegisterOGRShape()                          */
     307             : /************************************************************************/
     308             : 
     309        1595 : void RegisterOGRShape()
     310             : 
     311             : {
     312        1595 :     if (GDALGetDriverByName("ESRI Shapefile") != nullptr)
     313         302 :         return;
     314             : 
     315        1293 :     GDALDriver *poDriver = new GDALDriver();
     316             : 
     317        1293 :     poDriver->SetDescription("ESRI Shapefile");
     318        1293 :     poDriver->SetMetadataItem(GDAL_DCAP_VECTOR, "YES");
     319        1293 :     poDriver->SetMetadataItem(GDAL_DCAP_CREATE_LAYER, "YES");
     320        1293 :     poDriver->SetMetadataItem(GDAL_DCAP_DELETE_LAYER, "YES");
     321        1293 :     poDriver->SetMetadataItem(GDAL_DCAP_CREATE_FIELD, "YES");
     322        1293 :     poDriver->SetMetadataItem(GDAL_DCAP_DELETE_FIELD, "YES");
     323        1293 :     poDriver->SetMetadataItem(GDAL_DCAP_REORDER_FIELDS, "YES");
     324        1293 :     poDriver->SetMetadataItem(GDAL_DCAP_MEASURED_GEOMETRIES, "YES");
     325        1293 :     poDriver->SetMetadataItem(GDAL_DCAP_Z_GEOMETRIES, "YES");
     326        1293 :     poDriver->SetMetadataItem(GDAL_DMD_GEOMETRY_FLAGS,
     327             :                               "EquatesMultiAndSingleLineStringDuringWrite "
     328        1293 :                               "EquatesMultiAndSinglePolygonDuringWrite");
     329             : 
     330        1293 :     poDriver->SetMetadataItem(GDAL_DMD_LONGNAME, "ESRI Shapefile");
     331        1293 :     poDriver->SetMetadataItem(GDAL_DMD_EXTENSION, "shp");
     332        1293 :     poDriver->SetMetadataItem(GDAL_DMD_EXTENSIONS, "shp dbf shz shp.zip");
     333        1293 :     poDriver->SetMetadataItem(GDAL_DMD_HELPTOPIC,
     334        1293 :                               "drivers/vector/shapefile.html");
     335        1293 :     poDriver->SetMetadataItem(GDAL_DMD_SUPPORTED_SQL_DIALECTS, "OGRSQL SQLITE");
     336        1293 :     poDriver->SetMetadataItem(GDAL_DMD_NUMERIC_FIELD_WIDTH_INCLUDES_SIGN,
     337        1293 :                               "YES");
     338        1293 :     poDriver->SetMetadataItem(
     339        1293 :         GDAL_DMD_NUMERIC_FIELD_WIDTH_INCLUDES_DECIMAL_SEPARATOR, "YES");
     340             : 
     341        1293 :     poDriver->SetMetadataItem(
     342             :         GDAL_DMD_OPENOPTIONLIST,
     343             :         "<OpenOptionList>"
     344             :         "  <Option name='ENCODING' type='string' description='to override the "
     345             :         "encoding interpretation of the DBF with any encoding supported by "
     346             :         "CPLRecode or to \"\" to avoid any recoding'/>"
     347             :         "  <Option name='DBF_DATE_LAST_UPDATE' type='string' "
     348             :         "description='Modification date to write in DBF header with YYYY-MM-DD "
     349             :         "format'/>"
     350             :         "  <Option name='ADJUST_TYPE' type='boolean' description='Whether to "
     351             :         "read whole .dbf to adjust Real->Integer/Integer64 or "
     352             :         "Integer64->Integer field types if possible' default='NO'/>"
     353             :         "  <Option name='ADJUST_GEOM_TYPE' type='string-select' "
     354             :         "description='Whether and how to adjust layer geometry type from "
     355             :         "actual shapes' default='FIRST_SHAPE'>"
     356             :         "    <Value>NO</Value>"
     357             :         "    <Value>FIRST_SHAPE</Value>"
     358             :         "    <Value>ALL_SHAPES</Value>"
     359             :         "  </Option>"
     360             :         "  <Option name='AUTO_REPACK' type='boolean' description='Whether the "
     361             :         "shapefile should be automatically repacked when needed' "
     362             :         "default='YES'/>"
     363             :         "  <Option name='DBF_EOF_CHAR' type='boolean' description='Whether to "
     364             :         "write the 0x1A end-of-file character in DBF files' default='YES'/>"
     365        1293 :         "</OpenOptionList>");
     366             : 
     367        1293 :     poDriver->SetMetadataItem(GDAL_DMD_CREATIONOPTIONLIST,
     368        1293 :                               "<CreationOptionList/>");
     369        1293 :     poDriver->SetMetadataItem(
     370             :         GDAL_DS_LAYER_CREATIONOPTIONLIST,
     371             :         "<LayerCreationOptionList>"
     372             :         "  <Option name='SHPT' type='string-select' description='type of "
     373             :         "shape' default='automatically detected'>"
     374             :         "    <Value>POINT</Value>"
     375             :         "    <Value>ARC</Value>"
     376             :         "    <Value>POLYGON</Value>"
     377             :         "    <Value>MULTIPOINT</Value>"
     378             :         "    <Value>POINTZ</Value>"
     379             :         "    <Value>ARCZ</Value>"
     380             :         "    <Value>POLYGONZ</Value>"
     381             :         "    <Value>MULTIPOINTZ</Value>"
     382             :         "    <Value>POINTM</Value>"
     383             :         "    <Value>ARCM</Value>"
     384             :         "    <Value>POLYGONM</Value>"
     385             :         "    <Value>MULTIPOINTM</Value>"
     386             :         "    <Value>POINTZM</Value>"
     387             :         "    <Value>ARCZM</Value>"
     388             :         "    <Value>POLYGONZM</Value>"
     389             :         "    <Value>MULTIPOINTZM</Value>"
     390             :         "    <Value>MULTIPATCH</Value>"
     391             :         "    <Value>NONE</Value>"
     392             :         "    <Value>NULL</Value>"
     393             :         "  </Option>"
     394             :         "  <Option name='2GB_LIMIT' type='boolean' description='Restrict .shp "
     395             :         "and .dbf to 2GB' default='NO'/>"
     396             :         "  <Option name='ENCODING' type='string' description='DBF encoding' "
     397             :         "default='LDID/87'/>"
     398             :         "  <Option name='RESIZE' type='boolean' description='To resize fields "
     399             :         "to their optimal size.' default='NO'/>"
     400             :         "  <Option name='SPATIAL_INDEX' type='boolean' description='To create "
     401             :         "a spatial index.' default='NO'/>"
     402             :         "  <Option name='DBF_DATE_LAST_UPDATE' type='string' "
     403             :         "description='Modification date to write in DBF header with YYYY-MM-DD "
     404             :         "format'/>"
     405             :         "  <Option name='AUTO_REPACK' type='boolean' description='Whether the "
     406             :         "shapefile should be automatically repacked when needed' "
     407             :         "default='YES'/>"
     408             :         "  <Option name='DBF_EOF_CHAR' type='boolean' description='Whether to "
     409             :         "write the 0x1A end-of-file character in DBF files' default='YES'/>"
     410        1293 :         "</LayerCreationOptionList>");
     411             : 
     412        1293 :     poDriver->SetMetadataItem(GDAL_DMD_CREATIONFIELDDATATYPES,
     413        1293 :                               "Integer Integer64 Real String Date");
     414        1293 :     poDriver->SetMetadataItem(GDAL_DMD_CREATIONFIELDDATASUBTYPES, "Boolean");
     415        1293 :     poDriver->SetMetadataItem(GDAL_DMD_CREATION_FIELD_DEFN_FLAGS,
     416        1293 :                               "WidthPrecision");
     417        1293 :     poDriver->SetMetadataItem(GDAL_DMD_ALTER_FIELD_DEFN_FLAGS,
     418        1293 :                               "Name Type WidthPrecision");
     419        1293 :     poDriver->SetMetadataItem(GDAL_DCAP_VIRTUALIO, "YES");
     420        1293 :     poDriver->SetMetadataItem(GDAL_DCAP_RENAME_LAYERS, "YES");
     421             : 
     422        1293 :     poDriver->SetMetadataItem(GDAL_DMD_ALTER_GEOM_FIELD_DEFN_FLAGS, "SRS");
     423             : 
     424        1293 :     poDriver->pfnOpen = OGRShapeDriverOpen;
     425        1293 :     poDriver->pfnIdentify = OGRShapeDriverIdentify;
     426        1293 :     poDriver->pfnCreate = OGRShapeDriverCreate;
     427        1293 :     poDriver->pfnDelete = OGRShapeDriverDelete;
     428             : 
     429        1293 :     GetGDALDriverManager()->RegisterDriver(poDriver);
     430             : }

Generated by: LCOV version 1.14