Line data Source code
1 : /******************************************************************************
2 : *
3 : * Project: OpenGIS Simple Features Reference Implementation
4 : * Purpose: Implements Open FileGDB OGR driver.
5 : * Author: Even Rouault, <even dot rouault at spatialys.com>
6 : *
7 : ******************************************************************************
8 : * Copyright (c) 2014, Even Rouault <even dot rouault at spatialys.com>
9 : *
10 : * SPDX-License-Identifier: MIT
11 : ****************************************************************************/
12 :
13 : #include "ogrsf_frmts.h"
14 :
15 : #include "ogropenfilegdbdrivercore.h"
16 :
17 : #define ENDS_WITH(str, strLen, end) \
18 : (strLen >= strlen(end) && EQUAL(str + strLen - strlen(end), end))
19 :
20 : /************************************************************************/
21 : /* OGROpenFileGDBDriverIdentify() */
22 : /************************************************************************/
23 :
24 51698 : GDALIdentifyEnum OGROpenFileGDBDriverIdentify(GDALOpenInfo *poOpenInfo,
25 : const char *&pszFilename)
26 : {
27 51698 : if (STARTS_WITH(pszFilename, "OpenFileGDB:"))
28 44 : return GDAL_IDENTIFY_TRUE;
29 :
30 : // FUSIL is a fuzzer
31 : #ifdef FOR_FUSIL
32 : CPLString osOrigFilename(pszFilename);
33 : #endif
34 :
35 : // First check if we have to do any work.
36 51654 : size_t nLen = strlen(pszFilename);
37 51654 : if (ENDS_WITH(pszFilename, nLen, ".gdb") ||
38 49414 : ENDS_WITH(pszFilename, nLen, ".gdb/"))
39 : {
40 : // Check that the filename is really a directory, to avoid confusion
41 : // with Garmin MapSource - gdb format which can be a problem when the
42 : // driver is loaded as a plugin, and loaded before the GPSBabel driver
43 : // (http://trac.osgeo.org/osgeo4w/ticket/245)
44 1398 : if (STARTS_WITH(pszFilename, "/vsicurl/https://github.com/") ||
45 1398 : !poOpenInfo->bStatOK || !poOpenInfo->bIsDirectory)
46 : {
47 : // In case we do not manage to list the directory, try to stat one
48 : // file.
49 : VSIStatBufL stat;
50 486 : if (!(STARTS_WITH(pszFilename, "/vsicurl/") &&
51 0 : VSIStatL(
52 : CPLFormFilename(pszFilename, "a00000001", "gdbtable"),
53 : &stat) == 0))
54 : {
55 486 : return GDAL_IDENTIFY_FALSE;
56 0 : }
57 : }
58 912 : else if (poOpenInfo->nOpenFlags == GDAL_OF_RASTER &&
59 2 : (STARTS_WITH(pszFilename, "/vsimem/") ||
60 2 : !STARTS_WITH(pszFilename, "/vsi")))
61 : {
62 : // If asked to identify only a raster dataset, and this is a
63 : // local dataset, query the file list to find extensions that
64 : // are used by raster layers.
65 2 : constexpr int MAX_FILE_COUNT = 1000;
66 2 : CPLStringList aosFiles(VSIReadDirEx(pszFilename, MAX_FILE_COUNT));
67 2 : if (!aosFiles.empty() && aosFiles.size() < MAX_FILE_COUNT)
68 : {
69 2 : bool bBandIndexFound = false;
70 2 : bool bBlkKeyIndexFound = false;
71 2 : bool bColIndexFound = false;
72 2 : bool bRowIndexFound = false;
73 44 : for (int i = 0; i < aosFiles.size(); ++i)
74 : {
75 42 : if (strstr(aosFiles[i], ".band_index.atx"))
76 0 : bBandIndexFound = true;
77 42 : else if (strstr(aosFiles[i], ".blk_key_index.atx"))
78 0 : bBlkKeyIndexFound = true;
79 42 : else if (strstr(aosFiles[i], ".col_index.atx"))
80 0 : bColIndexFound = true;
81 42 : else if (strstr(aosFiles[i], ".row_index.atx"))
82 0 : bRowIndexFound = true;
83 : }
84 0 : return bBandIndexFound && bBlkKeyIndexFound && bColIndexFound &&
85 : bRowIndexFound
86 2 : ? GDAL_IDENTIFY_TRUE
87 2 : : GDAL_IDENTIFY_FALSE;
88 : }
89 : }
90 910 : return GDAL_IDENTIFY_TRUE;
91 : }
92 : /* We also accept zipped GDB */
93 50256 : else if (ENDS_WITH(pszFilename, nLen, ".gdb.zip") ||
94 50113 : ENDS_WITH(pszFilename, nLen, ".gdb.tar") ||
95 : /* Canvec GBs */
96 49275 : (ENDS_WITH(pszFilename, nLen, ".zip") &&
97 12 : (strstr(pszFilename, "_gdb") != nullptr ||
98 12 : strstr(pszFilename, "_GDB") != nullptr)))
99 : {
100 134 : return GDAL_IDENTIFY_TRUE;
101 : }
102 : /* We also accept tables themselves */
103 50122 : else if (ENDS_WITH(pszFilename, nLen, ".gdbtable"))
104 : {
105 112 : return GDAL_IDENTIFY_TRUE;
106 : }
107 : #ifdef FOR_FUSIL
108 : /* To be able to test fuzzer on any auxiliary files used (indexes, etc.) */
109 : else if (strlen(CPLGetBasename(pszFilename)) == 9 &&
110 : CPLGetBasename(pszFilename)[0] == 'a')
111 : {
112 : pszFilename = CPLFormFilename(CPLGetPath(pszFilename),
113 : CPLGetBasename(pszFilename), "gdbtable");
114 : return GDAL_IDENTIFY_TRUE;
115 : }
116 : else if (strlen(CPLGetBasename(CPLGetBasename(pszFilename))) == 9 &&
117 : CPLGetBasename(CPLGetBasename(pszFilename))[0] == 'a')
118 : {
119 : pszFilename = CPLFormFilename(
120 : CPLGetPath(pszFilename),
121 : CPLGetBasename(CPLGetBasename(pszFilename)), "gdbtable");
122 : return GDAL_IDENTIFY_TRUE;
123 : }
124 : #endif
125 :
126 : #ifdef DEBUG
127 : /* For AFL, so that .cur_input is detected as the archive filename */
128 50010 : else if (EQUAL(CPLGetFilename(pszFilename), ".cur_input"))
129 : {
130 : // This file may be recognized or not by this driver,
131 : // but there were not enough elements to judge.
132 9 : return GDAL_IDENTIFY_UNKNOWN;
133 : }
134 : #endif
135 :
136 49996 : else if (EQUAL(pszFilename, "."))
137 : {
138 3 : GDALIdentifyEnum eRet = GDAL_IDENTIFY_FALSE;
139 3 : char *pszCurrentDir = CPLGetCurrentDir();
140 3 : if (pszCurrentDir)
141 : {
142 3 : const char *pszTmp = pszCurrentDir;
143 3 : eRet = OGROpenFileGDBDriverIdentify(poOpenInfo, pszTmp);
144 3 : CPLFree(pszCurrentDir);
145 : }
146 3 : return eRet;
147 : }
148 :
149 : else
150 : {
151 49993 : return GDAL_IDENTIFY_FALSE;
152 : }
153 : }
154 :
155 51095 : static int OGROpenFileGDBDriverIdentify(GDALOpenInfo *poOpenInfo)
156 : {
157 51095 : const char *pszFilename = poOpenInfo->pszFilename;
158 102166 : return OGROpenFileGDBDriverIdentify(poOpenInfo, pszFilename);
159 : }
160 :
161 : /************************************************************************/
162 : /* OGROpenFileGDBDriverSetCommonMetadata() */
163 : /************************************************************************/
164 :
165 1293 : void OGROpenFileGDBDriverSetCommonMetadata(GDALDriver *poDriver)
166 : {
167 1293 : poDriver->SetDescription(DRIVER_NAME);
168 1293 : poDriver->SetMetadataItem(GDAL_DMD_LONGNAME,
169 1293 : "ESRI FileGeodatabase (using OpenFileGDB)");
170 1293 : poDriver->SetMetadataItem(GDAL_DMD_EXTENSION, "gdb");
171 1293 : poDriver->SetMetadataItem(GDAL_DMD_HELPTOPIC,
172 1293 : "drivers/vector/openfilegdb.html");
173 1293 : poDriver->SetMetadataItem(GDAL_DCAP_RASTER, "YES");
174 1293 : poDriver->SetMetadataItem(GDAL_DCAP_VECTOR, "YES");
175 1293 : poDriver->SetMetadataItem(GDAL_DCAP_CREATE_LAYER, "YES");
176 1293 : poDriver->SetMetadataItem(GDAL_DCAP_DELETE_LAYER, "YES");
177 1293 : poDriver->SetMetadataItem(GDAL_DCAP_CREATE_FIELD, "YES");
178 1293 : poDriver->SetMetadataItem(GDAL_DCAP_DELETE_FIELD, "YES");
179 1293 : poDriver->SetMetadataItem(GDAL_DCAP_VIRTUALIO, "YES");
180 1293 : poDriver->SetMetadataItem(GDAL_DCAP_CURVE_GEOMETRIES, "YES");
181 1293 : poDriver->SetMetadataItem(GDAL_DCAP_MEASURED_GEOMETRIES, "YES");
182 1293 : poDriver->SetMetadataItem(GDAL_DCAP_Z_GEOMETRIES, "YES");
183 1293 : poDriver->SetMetadataItem(GDAL_DMD_GEOMETRY_FLAGS,
184 : "EquatesMultiAndSingleLineStringDuringWrite "
185 1293 : "EquatesMultiAndSinglePolygonDuringWrite");
186 1293 : poDriver->SetMetadataItem(GDAL_DMD_CREATIONFIELDDATATYPES,
187 : "Integer Real String Date DateTime Binary "
188 1293 : "Integer64 Date Time");
189 1293 : poDriver->SetMetadataItem(GDAL_DMD_CREATIONFIELDDATASUBTYPES,
190 1293 : "Int16 Float32");
191 1293 : poDriver->SetMetadataItem(GDAL_DMD_CREATION_FIELD_DEFN_FLAGS,
192 : "Nullable Default "
193 1293 : "AlternativeName Domain");
194 1293 : poDriver->SetMetadataItem(
195 : GDAL_DMD_ALTER_FIELD_DEFN_FLAGS,
196 1293 : "Name Type Nullable Default Domain AlternativeName");
197 : // see https://support.esri.com/en/technical-article/000010906
198 1293 : poDriver->SetMetadataItem(
199 : GDAL_DMD_ILLEGAL_FIELD_NAMES,
200 : "ADD ALTER AND BETWEEN BY COLUMN CREATE DELETE DROP EXISTS FOR FROM "
201 : "GROUP IN INSERT INTO IS LIKE NOT NULL OR ORDER SELECT SET TABLE "
202 1293 : "UPDATE VALUES WHERE");
203 1293 : poDriver->SetMetadataItem(GDAL_DCAP_NOTNULL_FIELDS, "YES");
204 1293 : poDriver->SetMetadataItem(GDAL_DCAP_DEFAULT_FIELDS, "YES");
205 1293 : poDriver->SetMetadataItem(GDAL_DCAP_NOTNULL_GEOMFIELDS, "YES");
206 1293 : poDriver->SetMetadataItem(GDAL_DCAP_MULTIPLE_VECTOR_LAYERS, "YES");
207 1293 : poDriver->SetMetadataItem(GDAL_DCAP_FIELD_DOMAINS, "YES");
208 1293 : poDriver->SetMetadataItem(GDAL_DCAP_RENAME_LAYERS, "YES");
209 :
210 1293 : poDriver->SetMetadataItem(GDAL_DCAP_RELATIONSHIPS, "YES");
211 1293 : poDriver->SetMetadataItem(GDAL_DCAP_CREATE_RELATIONSHIP, "YES");
212 1293 : poDriver->SetMetadataItem(GDAL_DCAP_DELETE_RELATIONSHIP, "YES");
213 1293 : poDriver->SetMetadataItem(GDAL_DCAP_UPDATE_RELATIONSHIP, "YES");
214 1293 : poDriver->SetMetadataItem(GDAL_DMD_RELATIONSHIP_FLAGS,
215 : "OneToOne OneToMany ManyToMany Composite "
216 1293 : "Association ForwardPathLabel BackwardPathLabel");
217 1293 : poDriver->SetMetadataItem(GDAL_DMD_RELATIONSHIP_RELATED_TABLE_TYPES,
218 1293 : "features media");
219 :
220 1293 : poDriver->SetMetadataItem(GDAL_DMD_SUPPORTED_SQL_DIALECTS, "OGRSQL SQLITE");
221 :
222 1293 : poDriver->SetMetadataItem(GDAL_DMD_CREATION_FIELD_DOMAIN_TYPES,
223 1293 : "Coded Range");
224 :
225 1293 : poDriver->SetMetadataItem(GDAL_DMD_ALTER_GEOM_FIELD_DEFN_FLAGS, "Name SRS");
226 1293 : poDriver->SetMetadataItem(GDAL_DCAP_HONOR_GEOM_COORDINATE_PRECISION, "YES");
227 :
228 1293 : poDriver->SetMetadataItem(
229 : GDAL_DMD_OPENOPTIONLIST,
230 : "<OpenOptionList>"
231 : " <Option name='LIST_ALL_TABLES' type='string-select' scope='vector' "
232 : "description='Whether all tables, including system and internal tables "
233 : "(such as GDB_* tables) should be listed' default='NO'>"
234 : " <Value>YES</Value>"
235 : " <Value>NO</Value>"
236 : " </Option>"
237 : " <Option name='NODATA_OR_MASK' type='string' scope='raster' "
238 : "description='AUTO, MASK, NONE or numeric nodata value'/>"
239 1293 : "</OpenOptionList>");
240 :
241 1293 : poDriver->SetMetadataItem(
242 : GDAL_DS_LAYER_CREATIONOPTIONLIST,
243 : "<LayerCreationOptionList>"
244 : " <Option name='TARGET_ARCGIS_VERSION' type='string-select' "
245 : "default='ALL'>"
246 : " <Value>ALL</Value>"
247 : " <Value>ARCGIS_PRO_3_2_OR_LATER</Value>"
248 : " </Option>"
249 : " <Option name='FEATURE_DATASET' type='string' "
250 : "description='FeatureDataset folder into which to put the new layer'/>"
251 : " <Option name='LAYER_ALIAS' type='string' description='Alias of "
252 : "layer name'/>"
253 : " <Option name='GEOMETRY_NAME' type='string' description='Name of "
254 : "geometry column' default='SHAPE'/>"
255 : " <Option name='GEOMETRY_NULLABLE' type='boolean' "
256 : "description='Whether the values of the geometry column can be NULL' "
257 : "default='YES'/>"
258 : " <Option name='FID' type='string' description='Name of OID column' "
259 : "default='OBJECTID'/>"
260 : " <Option name='XYTOLERANCE' type='float' description='Snapping "
261 : "tolerance, used for advanced ArcGIS features like network and "
262 : "topology rules, on 2D coordinates, in the units of the CRS'/>"
263 : " <Option name='ZTOLERANCE' type='float' description='Snapping "
264 : "tolerance, used for advanced ArcGIS features like network and "
265 : "topology rules, on Z coordinates, in the units of the CRS'/>"
266 : " <Option name='MTOLERANCE' type='float' description='Snapping "
267 : "tolerance, used for advanced ArcGIS features like network and "
268 : "topology rules, on M coordinates'/>"
269 : " <Option name='XORIGIN' type='float' description='X origin of the "
270 : "coordinate precision grid'/>"
271 : " <Option name='YORIGIN' type='float' description='Y origin of the "
272 : "coordinate precision grid'/>"
273 : " <Option name='ZORIGIN' type='float' description='Z origin of the "
274 : "coordinate precision grid'/>"
275 : " <Option name='MORIGIN' type='float' description='M origin of the "
276 : "coordinate precision grid'/>"
277 : " <Option name='XYSCALE' type='float' description='X,Y scale of the "
278 : "coordinate precision grid'/>"
279 : " <Option name='ZSCALE' type='float' description='Z scale of the "
280 : "coordinate precision grid'/>"
281 : " <Option name='MSCALE' type='float' description='M scale of the "
282 : "coordinate precision grid'/>"
283 : " <Option name='COLUMN_TYPES' type='string' description='A list of "
284 : "strings of format field_name=fgdb_field_type (separated by comma) to "
285 : "force the FileGDB column type of fields to be created'/>"
286 : " <Option name='DOCUMENTATION' type='string' description='XML "
287 : "documentation'/>"
288 : " <Option name='CONFIGURATION_KEYWORD' type='string-select' "
289 : "description='Customize how data is stored. By default text in UTF-8 "
290 : "and data up to 1TB' default='DEFAULTS'>"
291 : " <Value>DEFAULTS</Value>"
292 : " <Value>MAX_FILE_SIZE_4GB</Value>"
293 : " <Value>MAX_FILE_SIZE_256TB</Value>"
294 : " <Value>TEXT_UTF16</Value>"
295 : " </Option>"
296 : " <Option name='TIME_IN_UTC' type='boolean' description='Whether "
297 : "datetime fields should be considered to be in UTC' default='NO'/>"
298 : " <Option name='CREATE_SHAPE_AREA_AND_LENGTH_FIELDS' type='boolean' "
299 : "description='Whether to create special Shape_Length and Shape_Area "
300 : "fields' default='NO'/>"
301 1293 : "</LayerCreationOptionList>");
302 :
303 : // Setting to another value than the default one doesn't really work
304 : // with the SDK
305 : // Option name='AREA_FIELD_NAME' type='string' description='Name of
306 : // the column that contains the geometry area' default='Shape_Area'
307 : // Option name='length_field_name' type='string' description='Name of
308 : // the column that contains the geometry length'
309 : // default='Shape_Length'
310 :
311 1293 : poDriver->pfnIdentify = OGROpenFileGDBDriverIdentify;
312 1293 : poDriver->SetMetadataItem(GDAL_DCAP_OPEN, "YES");
313 1293 : poDriver->SetMetadataItem(GDAL_DCAP_CREATE, "YES");
314 1293 : }
315 :
316 : /************************************************************************/
317 : /* DeclareDeferredOGROpenFileGDBPlugin() */
318 : /************************************************************************/
319 :
320 : #ifdef PLUGIN_FILENAME
321 : void DeclareDeferredOGROpenFileGDBPlugin()
322 : {
323 : if (GDALGetDriverByName(DRIVER_NAME) != nullptr)
324 : {
325 : return;
326 : }
327 : auto poDriver = new GDALPluginDriverProxy(PLUGIN_FILENAME);
328 : #ifdef PLUGIN_INSTALLATION_MESSAGE
329 : poDriver->SetMetadataItem(GDAL_DMD_PLUGIN_INSTALLATION_MESSAGE,
330 : PLUGIN_INSTALLATION_MESSAGE);
331 : #endif
332 : OGROpenFileGDBDriverSetCommonMetadata(poDriver);
333 : GetGDALDriverManager()->DeclareDeferredPluginDriver(poDriver);
334 : }
335 : #endif
|