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 49809 : GDALIdentifyEnum OGROpenFileGDBDriverIdentify(GDALOpenInfo *poOpenInfo,
25 : const char *&pszFilename)
26 : {
27 49809 : if (STARTS_WITH(pszFilename, "OpenFileGDB:"))
28 44 : return GDAL_IDENTIFY_TRUE;
29 :
30 : // First check if we have to do any work.
31 49765 : size_t nLen = strlen(pszFilename);
32 49765 : if (ENDS_WITH(pszFilename, nLen, ".gdb") ||
33 47422 : 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 1356 : if (STARTS_WITH(pszFilename, "/vsicurl/https://github.com/") ||
40 1356 : !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 486 : if (!(STARTS_WITH(pszFilename, "/vsicurl/") &&
46 0 : VSIStatL(
47 486 : CPLFormFilenameSafe(pszFilename, "a00000001", "gdbtable")
48 : .c_str(),
49 : &stat) == 0))
50 : {
51 486 : return GDAL_IDENTIFY_FALSE;
52 0 : }
53 : }
54 870 : 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 868 : return GDAL_IDENTIFY_TRUE;
87 : }
88 : /* We also accept zipped GDB */
89 48409 : else if (ENDS_WITH(pszFilename, nLen, ".gdb.zip") ||
90 48271 : ENDS_WITH(pszFilename, nLen, ".gdb.tar") ||
91 : /* Canvec GBs */
92 47296 : (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 48275 : 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 48233 : 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 48223 : 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 48221 : 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 48218 : return GDAL_IDENTIFY_FALSE;
141 : }
142 : }
143 :
144 49257 : static int OGROpenFileGDBDriverIdentify(GDALOpenInfo *poOpenInfo)
145 : {
146 49257 : const char *pszFilename = poOpenInfo->pszFilename;
147 98511 : return OGROpenFileGDBDriverIdentify(poOpenInfo, pszFilename);
148 : }
149 :
150 : /************************************************************************/
151 : /* OGROpenFileGDBDriverSetCommonMetadata() */
152 : /************************************************************************/
153 :
154 1385 : void OGROpenFileGDBDriverSetCommonMetadata(GDALDriver *poDriver)
155 : {
156 1385 : poDriver->SetDescription(DRIVER_NAME);
157 1385 : poDriver->SetMetadataItem(GDAL_DMD_LONGNAME,
158 1385 : "ESRI FileGeodatabase (using OpenFileGDB)");
159 1385 : poDriver->SetMetadataItem(GDAL_DMD_EXTENSION, "gdb");
160 1385 : poDriver->SetMetadataItem(GDAL_DMD_HELPTOPIC,
161 1385 : "drivers/vector/openfilegdb.html");
162 1385 : poDriver->SetMetadataItem(GDAL_DCAP_RASTER, "YES");
163 1385 : poDriver->SetMetadataItem(GDAL_DCAP_VECTOR, "YES");
164 1385 : poDriver->SetMetadataItem(GDAL_DCAP_CREATE_LAYER, "YES");
165 1385 : poDriver->SetMetadataItem(GDAL_DCAP_DELETE_LAYER, "YES");
166 1385 : poDriver->SetMetadataItem(GDAL_DCAP_CREATE_FIELD, "YES");
167 1385 : poDriver->SetMetadataItem(GDAL_DCAP_DELETE_FIELD, "YES");
168 1385 : poDriver->SetMetadataItem(GDAL_DCAP_VIRTUALIO, "YES");
169 1385 : poDriver->SetMetadataItem(GDAL_DCAP_CURVE_GEOMETRIES, "YES");
170 1385 : poDriver->SetMetadataItem(GDAL_DCAP_MEASURED_GEOMETRIES, "YES");
171 1385 : poDriver->SetMetadataItem(GDAL_DCAP_Z_GEOMETRIES, "YES");
172 1385 : poDriver->SetMetadataItem(GDAL_DMD_GEOMETRY_FLAGS,
173 : "EquatesMultiAndSingleLineStringDuringWrite "
174 1385 : "EquatesMultiAndSinglePolygonDuringWrite");
175 1385 : poDriver->SetMetadataItem(GDAL_DMD_CREATIONFIELDDATATYPES,
176 : "Integer Real String Date DateTime Binary "
177 1385 : "Integer64 Date Time");
178 1385 : poDriver->SetMetadataItem(GDAL_DMD_CREATIONFIELDDATASUBTYPES,
179 1385 : "Int16 Float32");
180 1385 : poDriver->SetMetadataItem(GDAL_DMD_CREATION_FIELD_DEFN_FLAGS,
181 : "Nullable Default "
182 1385 : "AlternativeName Domain");
183 1385 : poDriver->SetMetadataItem(
184 : GDAL_DMD_ALTER_FIELD_DEFN_FLAGS,
185 1385 : "Name Type Nullable Default Domain AlternativeName");
186 : // see https://support.esri.com/en/technical-article/000010906
187 1385 : 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 1385 : "UPDATE VALUES WHERE");
192 1385 : poDriver->SetMetadataItem(GDAL_DCAP_NOTNULL_FIELDS, "YES");
193 1385 : poDriver->SetMetadataItem(GDAL_DCAP_DEFAULT_FIELDS, "YES");
194 1385 : poDriver->SetMetadataItem(GDAL_DCAP_NOTNULL_GEOMFIELDS, "YES");
195 1385 : poDriver->SetMetadataItem(GDAL_DCAP_MULTIPLE_VECTOR_LAYERS, "YES");
196 1385 : poDriver->SetMetadataItem(GDAL_DCAP_FIELD_DOMAINS, "YES");
197 1385 : poDriver->SetMetadataItem(GDAL_DCAP_RENAME_LAYERS, "YES");
198 :
199 1385 : poDriver->SetMetadataItem(GDAL_DCAP_RELATIONSHIPS, "YES");
200 1385 : poDriver->SetMetadataItem(GDAL_DCAP_CREATE_RELATIONSHIP, "YES");
201 1385 : poDriver->SetMetadataItem(GDAL_DCAP_DELETE_RELATIONSHIP, "YES");
202 1385 : poDriver->SetMetadataItem(GDAL_DCAP_UPDATE_RELATIONSHIP, "YES");
203 1385 : poDriver->SetMetadataItem(GDAL_DMD_RELATIONSHIP_FLAGS,
204 : "OneToOne OneToMany ManyToMany Composite "
205 1385 : "Association ForwardPathLabel BackwardPathLabel");
206 1385 : poDriver->SetMetadataItem(GDAL_DMD_RELATIONSHIP_RELATED_TABLE_TYPES,
207 1385 : "features media");
208 :
209 1385 : poDriver->SetMetadataItem(GDAL_DMD_SUPPORTED_SQL_DIALECTS, "OGRSQL SQLITE");
210 :
211 1385 : poDriver->SetMetadataItem(GDAL_DMD_CREATION_FIELD_DOMAIN_TYPES,
212 1385 : "Coded Range");
213 :
214 1385 : poDriver->SetMetadataItem(GDAL_DMD_ALTER_GEOM_FIELD_DEFN_FLAGS, "Name SRS");
215 1385 : poDriver->SetMetadataItem(GDAL_DCAP_HONOR_GEOM_COORDINATE_PRECISION, "YES");
216 :
217 1385 : 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 1385 : "</OpenOptionList>");
229 :
230 1385 : 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 1385 : "</LayerCreationOptionList>");
294 :
295 1385 : poDriver->SetMetadataItem(GDAL_DCAP_UPDATE, "YES");
296 1385 : 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 1385 : poDriver->pfnIdentify = OGROpenFileGDBDriverIdentify;
307 1385 : poDriver->SetMetadataItem(GDAL_DCAP_OPEN, "YES");
308 1385 : poDriver->SetMetadataItem(GDAL_DCAP_CREATE, "YES");
309 1385 : }
310 :
311 : /************************************************************************/
312 : /* DeclareDeferredOGROpenFileGDBPlugin() */
313 : /************************************************************************/
314 :
315 : #ifdef PLUGIN_FILENAME
316 : void DeclareDeferredOGROpenFileGDBPlugin()
317 : {
318 : if (GDALGetDriverByName(DRIVER_NAME) != nullptr)
319 : {
320 : return;
321 : }
322 : auto poDriver = new GDALPluginDriverProxy(PLUGIN_FILENAME);
323 : #ifdef PLUGIN_INSTALLATION_MESSAGE
324 : poDriver->SetMetadataItem(GDAL_DMD_PLUGIN_INSTALLATION_MESSAGE,
325 : PLUGIN_INSTALLATION_MESSAGE);
326 : #endif
327 : OGROpenFileGDBDriverSetCommonMetadata(poDriver);
328 : GetGDALDriverManager()->DeclareDeferredPluginDriver(poDriver);
329 : }
330 : #endif
|