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