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