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 : }
|