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