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

Generated by: LCOV version 1.14