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