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 52198 : GDALIdentifyEnum OGROpenFileGDBDriverIdentify(GDALOpenInfo *poOpenInfo,
25 : const char *&pszFilename)
26 : {
27 52198 : if (STARTS_WITH(pszFilename, "OpenFileGDB:"))
28 44 : return GDAL_IDENTIFY_TRUE;
29 :
30 : // First check if we have to do any work.
31 52154 : size_t nLen = strlen(pszFilename);
32 52154 : if (ENDS_WITH(pszFilename, nLen, ".gdb") ||
33 49862 : 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 1406 : if (STARTS_WITH(pszFilename, "/vsicurl/https://github.com/") ||
40 1406 : !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 488 : if (!(STARTS_WITH(pszFilename, "/vsicurl/") &&
46 0 : VSIStatL(
47 488 : CPLFormFilenameSafe(pszFilename, "a00000001", "gdbtable")
48 : .c_str(),
49 : &stat) == 0))
50 : {
51 488 : return GDAL_IDENTIFY_FALSE;
52 0 : }
53 : }
54 918 : 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 916 : return GDAL_IDENTIFY_TRUE;
87 : }
88 : /* We also accept zipped GDB */
89 50748 : else if (ENDS_WITH(pszFilename, nLen, ".gdb.zip") ||
90 50599 : ENDS_WITH(pszFilename, nLen, ".gdb.tar") ||
91 : /* Canvec GBs */
92 49721 : (ENDS_WITH(pszFilename, nLen, ".zip") &&
93 12 : (strstr(pszFilename, "_gdb") != nullptr ||
94 12 : strstr(pszFilename, "_GDB") != nullptr)))
95 : {
96 134 : return GDAL_IDENTIFY_TRUE;
97 : }
98 : /* We also accept tables themselves */
99 50614 : else if (ENDS_WITH(pszFilename, nLen, ".gdbtable"))
100 : {
101 112 : return GDAL_IDENTIFY_TRUE;
102 : }
103 :
104 : #ifdef DEBUG
105 : /* For AFL, so that .cur_input is detected as the archive filename */
106 50502 : 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 50498 : else if (EQUAL(pszFilename, "."))
115 : {
116 3 : GDALIdentifyEnum eRet = GDAL_IDENTIFY_FALSE;
117 3 : char *pszCurrentDir = CPLGetCurrentDir();
118 3 : if (pszCurrentDir)
119 : {
120 3 : const char *pszTmp = pszCurrentDir;
121 3 : eRet = OGROpenFileGDBDriverIdentify(poOpenInfo, pszTmp);
122 3 : CPLFree(pszCurrentDir);
123 : }
124 3 : return eRet;
125 : }
126 :
127 : else
128 : {
129 50495 : return GDAL_IDENTIFY_FALSE;
130 : }
131 : }
132 :
133 51595 : static int OGROpenFileGDBDriverIdentify(GDALOpenInfo *poOpenInfo)
134 : {
135 51595 : const char *pszFilename = poOpenInfo->pszFilename;
136 103185 : return OGROpenFileGDBDriverIdentify(poOpenInfo, pszFilename);
137 : }
138 :
139 : /************************************************************************/
140 : /* OGROpenFileGDBDriverSetCommonMetadata() */
141 : /************************************************************************/
142 :
143 1381 : void OGROpenFileGDBDriverSetCommonMetadata(GDALDriver *poDriver)
144 : {
145 1381 : poDriver->SetDescription(DRIVER_NAME);
146 1381 : poDriver->SetMetadataItem(GDAL_DMD_LONGNAME,
147 1381 : "ESRI FileGeodatabase (using OpenFileGDB)");
148 1381 : poDriver->SetMetadataItem(GDAL_DMD_EXTENSION, "gdb");
149 1381 : poDriver->SetMetadataItem(GDAL_DMD_HELPTOPIC,
150 1381 : "drivers/vector/openfilegdb.html");
151 1381 : poDriver->SetMetadataItem(GDAL_DCAP_RASTER, "YES");
152 1381 : poDriver->SetMetadataItem(GDAL_DCAP_VECTOR, "YES");
153 1381 : poDriver->SetMetadataItem(GDAL_DCAP_CREATE_LAYER, "YES");
154 1381 : poDriver->SetMetadataItem(GDAL_DCAP_DELETE_LAYER, "YES");
155 1381 : poDriver->SetMetadataItem(GDAL_DCAP_CREATE_FIELD, "YES");
156 1381 : poDriver->SetMetadataItem(GDAL_DCAP_DELETE_FIELD, "YES");
157 1381 : poDriver->SetMetadataItem(GDAL_DCAP_VIRTUALIO, "YES");
158 1381 : poDriver->SetMetadataItem(GDAL_DCAP_CURVE_GEOMETRIES, "YES");
159 1381 : poDriver->SetMetadataItem(GDAL_DCAP_MEASURED_GEOMETRIES, "YES");
160 1381 : poDriver->SetMetadataItem(GDAL_DCAP_Z_GEOMETRIES, "YES");
161 1381 : poDriver->SetMetadataItem(GDAL_DMD_GEOMETRY_FLAGS,
162 : "EquatesMultiAndSingleLineStringDuringWrite "
163 1381 : "EquatesMultiAndSinglePolygonDuringWrite");
164 1381 : poDriver->SetMetadataItem(GDAL_DMD_CREATIONFIELDDATATYPES,
165 : "Integer Real String Date DateTime Binary "
166 1381 : "Integer64 Date Time");
167 1381 : poDriver->SetMetadataItem(GDAL_DMD_CREATIONFIELDDATASUBTYPES,
168 1381 : "Int16 Float32");
169 1381 : poDriver->SetMetadataItem(GDAL_DMD_CREATION_FIELD_DEFN_FLAGS,
170 : "Nullable Default "
171 1381 : "AlternativeName Domain");
172 1381 : poDriver->SetMetadataItem(
173 : GDAL_DMD_ALTER_FIELD_DEFN_FLAGS,
174 1381 : "Name Type Nullable Default Domain AlternativeName");
175 : // see https://support.esri.com/en/technical-article/000010906
176 1381 : poDriver->SetMetadataItem(
177 : GDAL_DMD_ILLEGAL_FIELD_NAMES,
178 : "ADD ALTER AND BETWEEN BY COLUMN CREATE DELETE DROP EXISTS FOR FROM "
179 : "GROUP IN INSERT INTO IS LIKE NOT NULL OR ORDER SELECT SET TABLE "
180 1381 : "UPDATE VALUES WHERE");
181 1381 : poDriver->SetMetadataItem(GDAL_DCAP_NOTNULL_FIELDS, "YES");
182 1381 : poDriver->SetMetadataItem(GDAL_DCAP_DEFAULT_FIELDS, "YES");
183 1381 : poDriver->SetMetadataItem(GDAL_DCAP_NOTNULL_GEOMFIELDS, "YES");
184 1381 : poDriver->SetMetadataItem(GDAL_DCAP_MULTIPLE_VECTOR_LAYERS, "YES");
185 1381 : poDriver->SetMetadataItem(GDAL_DCAP_FIELD_DOMAINS, "YES");
186 1381 : poDriver->SetMetadataItem(GDAL_DCAP_RENAME_LAYERS, "YES");
187 :
188 1381 : poDriver->SetMetadataItem(GDAL_DCAP_RELATIONSHIPS, "YES");
189 1381 : poDriver->SetMetadataItem(GDAL_DCAP_CREATE_RELATIONSHIP, "YES");
190 1381 : poDriver->SetMetadataItem(GDAL_DCAP_DELETE_RELATIONSHIP, "YES");
191 1381 : poDriver->SetMetadataItem(GDAL_DCAP_UPDATE_RELATIONSHIP, "YES");
192 1381 : poDriver->SetMetadataItem(GDAL_DMD_RELATIONSHIP_FLAGS,
193 : "OneToOne OneToMany ManyToMany Composite "
194 1381 : "Association ForwardPathLabel BackwardPathLabel");
195 1381 : poDriver->SetMetadataItem(GDAL_DMD_RELATIONSHIP_RELATED_TABLE_TYPES,
196 1381 : "features media");
197 :
198 1381 : poDriver->SetMetadataItem(GDAL_DMD_SUPPORTED_SQL_DIALECTS, "OGRSQL SQLITE");
199 :
200 1381 : poDriver->SetMetadataItem(GDAL_DMD_CREATION_FIELD_DOMAIN_TYPES,
201 1381 : "Coded Range");
202 :
203 1381 : poDriver->SetMetadataItem(GDAL_DMD_ALTER_GEOM_FIELD_DEFN_FLAGS, "Name SRS");
204 1381 : poDriver->SetMetadataItem(GDAL_DCAP_HONOR_GEOM_COORDINATE_PRECISION, "YES");
205 :
206 1381 : poDriver->SetMetadataItem(
207 : GDAL_DMD_OPENOPTIONLIST,
208 : "<OpenOptionList>"
209 : " <Option name='LIST_ALL_TABLES' type='string-select' scope='vector' "
210 : "description='Whether all tables, including system and internal tables "
211 : "(such as GDB_* tables) should be listed' default='NO'>"
212 : " <Value>YES</Value>"
213 : " <Value>NO</Value>"
214 : " </Option>"
215 : " <Option name='NODATA_OR_MASK' type='string' scope='raster' "
216 : "description='AUTO, MASK, NONE or numeric nodata value'/>"
217 1381 : "</OpenOptionList>");
218 :
219 1381 : poDriver->SetMetadataItem(
220 : GDAL_DS_LAYER_CREATIONOPTIONLIST,
221 : "<LayerCreationOptionList>"
222 : " <Option name='TARGET_ARCGIS_VERSION' type='string-select' "
223 : "default='ALL'>"
224 : " <Value>ALL</Value>"
225 : " <Value>ARCGIS_PRO_3_2_OR_LATER</Value>"
226 : " </Option>"
227 : " <Option name='FEATURE_DATASET' type='string' "
228 : "description='FeatureDataset folder into which to put the new layer'/>"
229 : " <Option name='LAYER_ALIAS' type='string' description='Alias of "
230 : "layer name'/>"
231 : " <Option name='GEOMETRY_NAME' type='string' description='Name of "
232 : "geometry column' default='SHAPE'/>"
233 : " <Option name='GEOMETRY_NULLABLE' type='boolean' "
234 : "description='Whether the values of the geometry column can be NULL' "
235 : "default='YES'/>"
236 : " <Option name='FID' type='string' description='Name of OID column' "
237 : "default='OBJECTID'/>"
238 : " <Option name='XYTOLERANCE' type='float' description='Snapping "
239 : "tolerance, used for advanced ArcGIS features like network and "
240 : "topology rules, on 2D coordinates, in the units of the CRS'/>"
241 : " <Option name='ZTOLERANCE' type='float' description='Snapping "
242 : "tolerance, used for advanced ArcGIS features like network and "
243 : "topology rules, on Z coordinates, in the units of the CRS'/>"
244 : " <Option name='MTOLERANCE' type='float' description='Snapping "
245 : "tolerance, used for advanced ArcGIS features like network and "
246 : "topology rules, on M coordinates'/>"
247 : " <Option name='XORIGIN' type='float' description='X origin of the "
248 : "coordinate precision grid'/>"
249 : " <Option name='YORIGIN' type='float' description='Y origin of the "
250 : "coordinate precision grid'/>"
251 : " <Option name='ZORIGIN' type='float' description='Z origin of the "
252 : "coordinate precision grid'/>"
253 : " <Option name='MORIGIN' type='float' description='M origin of the "
254 : "coordinate precision grid'/>"
255 : " <Option name='XYSCALE' type='float' description='X,Y scale of the "
256 : "coordinate precision grid'/>"
257 : " <Option name='ZSCALE' type='float' description='Z scale of the "
258 : "coordinate precision grid'/>"
259 : " <Option name='MSCALE' type='float' description='M scale of the "
260 : "coordinate precision grid'/>"
261 : " <Option name='COLUMN_TYPES' type='string' description='A list of "
262 : "strings of format field_name=fgdb_field_type (separated by comma) to "
263 : "force the FileGDB column type of fields to be created'/>"
264 : " <Option name='DOCUMENTATION' type='string' description='XML "
265 : "documentation'/>"
266 : " <Option name='CONFIGURATION_KEYWORD' type='string-select' "
267 : "description='Customize how data is stored. By default text in UTF-8 "
268 : "and data up to 1TB' default='DEFAULTS'>"
269 : " <Value>DEFAULTS</Value>"
270 : " <Value>MAX_FILE_SIZE_4GB</Value>"
271 : " <Value>MAX_FILE_SIZE_256TB</Value>"
272 : " <Value>TEXT_UTF16</Value>"
273 : " </Option>"
274 : " <Option name='TIME_IN_UTC' type='boolean' description='Whether "
275 : "datetime fields should be considered to be in UTC' default='NO'/>"
276 : " <Option name='CREATE_SHAPE_AREA_AND_LENGTH_FIELDS' type='boolean' "
277 : "description='Whether to create special Shape_Length and Shape_Area "
278 : "fields' default='NO'/>"
279 1381 : "</LayerCreationOptionList>");
280 :
281 : // Setting to another value than the default one doesn't really work
282 : // with the SDK
283 : // Option name='AREA_FIELD_NAME' type='string' description='Name of
284 : // the column that contains the geometry area' default='Shape_Area'
285 : // Option name='length_field_name' type='string' description='Name of
286 : // the column that contains the geometry length'
287 : // default='Shape_Length'
288 :
289 1381 : poDriver->pfnIdentify = OGROpenFileGDBDriverIdentify;
290 1381 : poDriver->SetMetadataItem(GDAL_DCAP_OPEN, "YES");
291 1381 : poDriver->SetMetadataItem(GDAL_DCAP_CREATE, "YES");
292 1381 : }
293 :
294 : /************************************************************************/
295 : /* DeclareDeferredOGROpenFileGDBPlugin() */
296 : /************************************************************************/
297 :
298 : #ifdef PLUGIN_FILENAME
299 : void DeclareDeferredOGROpenFileGDBPlugin()
300 : {
301 : if (GDALGetDriverByName(DRIVER_NAME) != nullptr)
302 : {
303 : return;
304 : }
305 : auto poDriver = new GDALPluginDriverProxy(PLUGIN_FILENAME);
306 : #ifdef PLUGIN_INSTALLATION_MESSAGE
307 : poDriver->SetMetadataItem(GDAL_DMD_PLUGIN_INSTALLATION_MESSAGE,
308 : PLUGIN_INSTALLATION_MESSAGE);
309 : #endif
310 : OGROpenFileGDBDriverSetCommonMetadata(poDriver);
311 : GetGDALDriverManager()->DeclareDeferredPluginDriver(poDriver);
312 : }
313 : #endif
|