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