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