1 /******************************************************************************
2  * $Id: FGdbDriver.cpp 29180 2015-05-10 15:07:48Z rouault $
3  *
4  * Project:  OpenGIS Simple Features Reference Implementation
5  * Purpose:  Implements FileGDB OGR driver.
6  * Author:   Ragi Yaser Burhum, ragi@burhum.com
7  *           Paul Ramsey, pramsey at cleverelephant.ca
8  *
9  ******************************************************************************
10  * Copyright (c) 2010, Ragi Yaser Burhum
11  * Copyright (c) 2011, Paul Ramsey <pramsey at cleverelephant.ca>
12  * Copyright (c) 2011-2013, Even Rouault <even dot rouault at mines-paris dot org>
13  *
14  * Permission is hereby granted, free of charge, to any person obtaining a
15  * copy of this software and associated documentation files (the "Software"),
16  * to deal in the Software without restriction, including without limitation
17  * the rights to use, copy, modify, merge, publish, distribute, sublicense,
18  * and/or sell copies of the Software, and to permit persons to whom the
19  * Software is furnished to do so, subject to the following conditions:
20  *
21  * The above copyright notice and this permission notice shall be included
22  * in all copies or substantial portions of the Software.
23  *
24  * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS
25  * OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
26  * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL
27  * THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
28  * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
29  * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
30  * DEALINGS IN THE SOFTWARE.
31  ****************************************************************************/
32 
33 #include "ogr_fgdb.h"
34 #include "cpl_conv.h"
35 #include "FGdbUtils.h"
36 #include "cpl_multiproc.h"
37 #include "ogrmutexeddatasource.h"
38 
39 CPL_CVSID("$Id: FGdbDriver.cpp 29180 2015-05-10 15:07:48Z rouault $");
40 
41 extern "C" void RegisterOGRFileGDB();
42 
43 /************************************************************************/
44 /*                            FGdbDriver()                              */
45 /************************************************************************/
FGdbDriver()46 FGdbDriver::FGdbDriver(): OGRSFDriver(), hMutex(NULL)
47 {
48 }
49 
50 /************************************************************************/
51 /*                            ~FGdbDriver()                             */
52 /************************************************************************/
~FGdbDriver()53 FGdbDriver::~FGdbDriver()
54 
55 {
56     if( oMapConnections.size() != 0 )
57         CPLDebug("FileGDB", "Remaining %d connections. Bug?",
58                  (int)oMapConnections.size());
59     if( hMutex != NULL )
60         CPLDestroyMutex(hMutex);
61     hMutex = NULL;
62 }
63 
64 
65 /************************************************************************/
66 /*                              GetName()                               */
67 /************************************************************************/
68 
GetName()69 const char *FGdbDriver::GetName()
70 
71 {
72     return "FileGDB";
73 }
74 
75 /************************************************************************/
76 /*                                Open()                                */
77 /************************************************************************/
78 
Open(const char * pszFilename,int bUpdate)79 OGRDataSource *FGdbDriver::Open( const char* pszFilename, int bUpdate )
80 
81 {
82     // First check if we have to do any work.
83     int nLen = strlen(pszFilename);
84     if(! ((nLen >= 4 && EQUAL(pszFilename + nLen - 4, ".gdb")) ||
85           (nLen >= 5 && EQUAL(pszFilename + nLen - 5, ".gdb/"))) )
86         return NULL;
87 
88     long hr;
89 
90     /* Check that the filename is really a directory, to avoid confusion with */
91     /* Garmin MapSource - gdb format which can be a problem when the FileGDB */
92     /* driver is loaded as a plugin, and loaded before the GPSBabel driver */
93     /* (http://trac.osgeo.org/osgeo4w/ticket/245) */
94     VSIStatBuf stat;
95     if( CPLStat( pszFilename, &stat ) != 0 || !VSI_ISDIR(stat.st_mode) )
96     {
97         return NULL;
98     }
99 
100     CPLMutexHolderD(&hMutex);
101     Geodatabase* pGeoDatabase = NULL;
102 
103     FGdbDatabaseConnection* pConnection = oMapConnections[pszFilename];
104     if( pConnection != NULL )
105     {
106         pGeoDatabase = pConnection->m_pGeodatabase;
107         pConnection->m_nRefCount ++;
108         CPLDebug("FileGDB", "ref_count of %s = %d now", pszFilename,
109                  pConnection->m_nRefCount);
110     }
111     else
112     {
113         pGeoDatabase = new Geodatabase;
114         hr = ::OpenGeodatabase(StringToWString(pszFilename), *pGeoDatabase);
115 
116         if (FAILED(hr) || pGeoDatabase == NULL)
117         {
118             delete pGeoDatabase;
119 
120             if( OGRGetDriverByName("OpenFileGDB") != NULL && bUpdate == FALSE )
121             {
122                 std::wstring fgdb_error_desc_w;
123                 std::string fgdb_error_desc("Unknown error");
124                 fgdbError er;
125                 er = FileGDBAPI::ErrorInfo::GetErrorDescription(hr, fgdb_error_desc_w);
126                 if ( er == S_OK )
127                 {
128                     fgdb_error_desc = WStringToString(fgdb_error_desc_w);
129                 }
130                 CPLDebug("FileGDB", "Cannot open %s with FileGDB driver: %s. Failing silently so OpenFileGDB can be tried",
131                          pszFilename,
132                          fgdb_error_desc.c_str());
133             }
134             else
135             {
136                 GDBErr(hr, "Failed to open Geodatabase");
137             }
138             oMapConnections.erase(pszFilename);
139             return NULL;
140         }
141 
142         CPLDebug("FileGDB", "Really opening %s", pszFilename);
143         pConnection = new FGdbDatabaseConnection(pGeoDatabase);
144         oMapConnections[pszFilename] = pConnection;
145     }
146 
147     FGdbDataSource* pDS;
148 
149     pDS = new FGdbDataSource(this, pConnection);
150 
151     if(!pDS->Open( pszFilename, bUpdate ) )
152     {
153         delete pDS;
154         return NULL;
155     }
156     else
157     {
158         OGRMutexedDataSource* poMutexedDS =
159                 new OGRMutexedDataSource(pDS, TRUE, hMutex, TRUE);
160         if( bUpdate )
161             return OGRCreateEmulatedTransactionDataSourceWrapper(poMutexedDS, this, TRUE, FALSE);
162         else
163             return poMutexedDS;
164     }
165 }
166 
167 /***********************************************************************/
168 /*                     CreateDataSource()                              */
169 /***********************************************************************/
170 
CreateDataSource(const char * conn,char ** papszOptions)171 OGRDataSource* FGdbDriver::CreateDataSource( const char * conn,
172                                            char **papszOptions)
173 {
174     long hr;
175     Geodatabase *pGeodatabase;
176     std::wstring wconn = StringToWString(conn);
177     int bUpdate = TRUE; // If we're creating, we must be writing.
178     VSIStatBuf stat;
179 
180     CPLMutexHolderD(&hMutex);
181 
182     /* We don't support options yet, so warn if they send us some */
183     if ( papszOptions )
184     {
185         /* TODO: warning, ignoring options */
186     }
187 
188     /* Only accept names of form "filename.gdb" and */
189     /* also .gdb.zip to be able to return FGDB with MapServer OGR output (#4199) */
190     const char* pszExt = CPLGetExtension(conn);
191     if ( !(EQUAL(pszExt,"gdb") || EQUAL(pszExt, "zip")) )
192     {
193         CPLError( CE_Failure, CPLE_AppDefined,
194                   "FGDB data source name must use 'gdb' extension.\n" );
195         return NULL;
196     }
197 
198     /* Don't try to create on top of something already there */
199     if( CPLStat( conn, &stat ) == 0 )
200     {
201         CPLError( CE_Failure, CPLE_AppDefined,
202                   "%s already exists.\n", conn );
203         return NULL;
204     }
205 
206     /* Try to create the geodatabase */
207     pGeodatabase = new Geodatabase; // Create on heap so we can store it in the Datasource
208     hr = CreateGeodatabase(wconn, *pGeodatabase);
209 
210     /* Handle creation errors */
211     if ( S_OK != hr )
212     {
213         const char *errstr = "Error creating geodatabase (%s).\n";
214         if ( hr == -2147220653 )
215             errstr = "File already exists (%s).\n";
216         delete pGeodatabase;
217         CPLError( CE_Failure, CPLE_AppDefined, errstr, conn );
218         return NULL;
219     }
220 
221     FGdbDatabaseConnection* pConnection = new FGdbDatabaseConnection(pGeodatabase);
222     oMapConnections[conn] = pConnection;
223 
224     /* Ready to embed the Geodatabase in an OGR Datasource */
225     FGdbDataSource* pDS = new FGdbDataSource(this, pConnection);
226     if ( ! pDS->Open(conn, bUpdate) )
227     {
228         delete pDS;
229         return NULL;
230     }
231     else
232         return OGRCreateEmulatedTransactionDataSourceWrapper(
233             new OGRMutexedDataSource(pDS, TRUE, hMutex, TRUE), this,
234             TRUE, FALSE);
235 }
236 
237 /************************************************************************/
238 /*                           StartTransaction()                         */
239 /************************************************************************/
240 
StartTransaction(OGRDataSource * & poDSInOut,int & bOutHasReopenedDS)241 OGRErr FGdbDriver::StartTransaction(OGRDataSource*& poDSInOut, int& bOutHasReopenedDS)
242 {
243     CPLMutexHolderOptionalLockD(hMutex);
244 
245     bOutHasReopenedDS = FALSE;
246 
247     OGRMutexedDataSource* poMutexedDS = (OGRMutexedDataSource*)poDSInOut;
248     FGdbDataSource* poDS = (FGdbDataSource* )poMutexedDS->GetBaseDataSource();
249     if( !poDS->GetUpdate() )
250         return OGRERR_FAILURE;
251     FGdbDatabaseConnection* pConnection = poDS->GetConnection();
252     if( pConnection->GetRefCount() != 1 )
253     {
254         CPLError(CE_Failure, CPLE_AppDefined,
255                  "Cannot start transaction as database is opened in another connection");
256         return OGRERR_FAILURE;
257     }
258     if( pConnection->IsLocked() )
259     {
260         CPLError(CE_Failure, CPLE_AppDefined,
261                  "Transaction is already in progress");
262         return OGRERR_FAILURE;
263     }
264 
265     bOutHasReopenedDS = TRUE;
266 
267     CPLString osName(poMutexedDS->GetName());
268     if( osName[osName.size()-1] == '/' || osName[osName.size()-1] == '\\' )
269         osName.resize(osName.size()-1);
270 
271     pConnection->m_nRefCount ++;
272     delete poDSInOut;
273     poDSInOut = NULL;
274     poMutexedDS = NULL;
275     poDS = NULL;
276 
277     ::CloseGeodatabase(*(pConnection->m_pGeodatabase));
278     delete pConnection->m_pGeodatabase;
279     pConnection->m_pGeodatabase = NULL;
280 
281     CPLString osEditedName(osName);
282     osEditedName += ".ogredited";
283 
284     CPLPushErrorHandler(CPLQuietErrorHandler);
285     CPLUnlinkTree(osEditedName);
286     CPLPopErrorHandler();
287 
288     OGRErr eErr = OGRERR_NONE;
289     CPLString osDatabaseToReopen;
290     if( EQUAL(CPLGetConfigOption("FGDB_SIMUL_FAIL", ""), "CASE1") ||
291         CPLCopyTree( osEditedName, osName ) != 0 )
292     {
293         CPLError(CE_Failure, CPLE_AppDefined,
294                  "Cannot backup geodatabase");
295         eErr = OGRERR_FAILURE;
296         osDatabaseToReopen = osName;
297     }
298     else
299         osDatabaseToReopen = osEditedName;
300 
301     pConnection->m_pGeodatabase = new Geodatabase;
302     long hr = ::OpenGeodatabase(StringToWString(osDatabaseToReopen), *(pConnection->m_pGeodatabase));
303     if( EQUAL(CPLGetConfigOption("FGDB_SIMUL_FAIL", ""), "CASE2") || FAILED(hr))
304     {
305         delete pConnection->m_pGeodatabase;
306         pConnection->m_pGeodatabase = NULL;
307         Release(osName);
308         GDBErr(hr, CPLSPrintf("Failed to open %s. Dataset should be closed",
309                               osDatabaseToReopen.c_str()));
310 
311         return OGRERR_FAILURE;
312     }
313 
314     FGdbDataSource* pDS = new FGdbDataSource(this, pConnection);
315     pDS->Open(osName, TRUE);
316     poDSInOut = new OGRMutexedDataSource(pDS, TRUE, hMutex, TRUE);
317 
318     if( eErr == OGRERR_NONE )
319         pConnection->SetLocked(TRUE);
320     return eErr;
321 }
322 
323 /************************************************************************/
324 /*                           CommitTransaction()                        */
325 /************************************************************************/
326 
CommitTransaction(OGRDataSource * & poDSInOut,int & bOutHasReopenedDS)327 OGRErr FGdbDriver::CommitTransaction(OGRDataSource*& poDSInOut, int& bOutHasReopenedDS)
328 {
329     CPLMutexHolderOptionalLockD(hMutex);
330 
331     bOutHasReopenedDS = FALSE;
332 
333 
334     OGRMutexedDataSource* poMutexedDS = (OGRMutexedDataSource*)poDSInOut;
335     FGdbDataSource* poDS = (FGdbDataSource* )poMutexedDS->GetBaseDataSource();
336     FGdbDatabaseConnection* pConnection = poDS->GetConnection();
337     if( !pConnection->IsLocked() )
338     {
339         CPLError(CE_Failure, CPLE_NotSupported,
340                  "No transaction in progress");
341         return OGRERR_FAILURE;
342     }
343 
344     bOutHasReopenedDS = TRUE;
345 
346     CPLString osName(poMutexedDS->GetName());
347     if( osName[osName.size()-1] == '/' || osName[osName.size()-1] == '\\' )
348         osName.resize(osName.size()-1);
349 
350     pConnection->m_nRefCount ++;
351     delete poDSInOut;
352     poDSInOut = NULL;
353     poMutexedDS = NULL;
354     poDS = NULL;
355 
356     ::CloseGeodatabase(*(pConnection->m_pGeodatabase));
357     delete pConnection->m_pGeodatabase;
358     pConnection->m_pGeodatabase = NULL;
359 
360     CPLString osEditedName(osName);
361     osEditedName += ".ogredited";
362     CPLString osTmpName(osName);
363     osTmpName += ".ogrtmp";
364 
365     /* Install the backup copy as the main database in 3 steps : */
366     /* first rename the main directory  in .tmp */
367     /* then rename the edited copy under regular name */
368     /* and finally dispose the .tmp directory */
369     /* That way there's no risk definitely losing data */
370     if( EQUAL(CPLGetConfigOption("FGDB_SIMUL_FAIL", ""), "CASE1") ||
371         VSIRename(osName, osTmpName) != 0 )
372     {
373         CPLError(CE_Failure, CPLE_AppDefined,
374                  "Cannot rename %s to %s. Edited database during transaction is in %s"
375                  "Dataset should be closed",
376                  osName.c_str(), osTmpName.c_str(), osEditedName.c_str());
377         pConnection->SetLocked(FALSE);
378         Release(osName);
379         return OGRERR_FAILURE;
380     }
381 
382     if( EQUAL(CPLGetConfigOption("FGDB_SIMUL_FAIL", ""), "CASE2") ||
383         VSIRename(osEditedName, osName) != 0 )
384     {
385         CPLError(CE_Failure, CPLE_AppDefined,
386                  "Cannot rename %s to %s. The original geodatabase is in '%s'. "
387                  "Dataset should be closed",
388                  osEditedName.c_str(), osName.c_str(), osTmpName.c_str());
389         pConnection->SetLocked(FALSE);
390         Release(osName);
391         return OGRERR_FAILURE;
392     }
393 
394     if( EQUAL(CPLGetConfigOption("FGDB_SIMUL_FAIL", ""), "CASE3") ||
395         CPLUnlinkTree(osTmpName) != 0 )
396     {
397         CPLError(CE_Warning, CPLE_AppDefined,
398                  "Cannot remove %s. Manual cleanup required", osTmpName.c_str());
399     }
400 
401     pConnection->m_pGeodatabase = new Geodatabase;
402     long hr = ::OpenGeodatabase(StringToWString(osName), *(pConnection->m_pGeodatabase));
403     if( EQUAL(CPLGetConfigOption("FGDB_SIMUL_FAIL", ""), "CASE4") || FAILED(hr))
404     {
405         delete pConnection->m_pGeodatabase;
406         pConnection->m_pGeodatabase = NULL;
407         pConnection->SetLocked(FALSE);
408         Release(osName);
409         GDBErr(hr, "Failed to re-open Geodatabase. Dataset should be closed");
410         return OGRERR_FAILURE;
411     }
412 
413     FGdbDataSource* pDS = new FGdbDataSource(this, pConnection);
414     pDS->Open(osName, TRUE);
415     poDSInOut = new OGRMutexedDataSource(pDS, TRUE, hMutex, TRUE);
416 
417     pConnection->SetLocked(FALSE);
418 
419     return OGRERR_NONE;
420 }
421 
422 /************************************************************************/
423 /*                           RollbackTransaction()                      */
424 /************************************************************************/
425 
RollbackTransaction(OGRDataSource * & poDSInOut,int & bOutHasReopenedDS)426 OGRErr FGdbDriver::RollbackTransaction(OGRDataSource*& poDSInOut, int& bOutHasReopenedDS)
427 {
428     CPLMutexHolderOptionalLockD(hMutex);
429 
430     bOutHasReopenedDS = FALSE;
431 
432     OGRMutexedDataSource* poMutexedDS = (OGRMutexedDataSource*)poDSInOut;
433     FGdbDataSource* poDS = (FGdbDataSource* )poMutexedDS->GetBaseDataSource();
434     FGdbDatabaseConnection* pConnection = poDS->GetConnection();
435     if( !pConnection->IsLocked() )
436     {
437         CPLError(CE_Failure, CPLE_NotSupported,
438                  "No transaction in progress");
439         return OGRERR_FAILURE;
440     }
441 
442     bOutHasReopenedDS = TRUE;
443 
444     CPLString osName(poMutexedDS->GetName());
445     if( osName[osName.size()-1] == '/' || osName[osName.size()-1] == '\\' )
446         osName.resize(osName.size()-1);
447 
448     pConnection->m_nRefCount ++;
449     delete poDSInOut;
450     poDSInOut = NULL;
451     poMutexedDS = NULL;
452     poDS = NULL;
453 
454     ::CloseGeodatabase(*(pConnection->m_pGeodatabase));
455     delete pConnection->m_pGeodatabase;
456     pConnection->m_pGeodatabase = NULL;
457 
458     CPLString osEditedName(osName);
459     osEditedName += ".ogredited";
460 
461     OGRErr eErr = OGRERR_NONE;
462     if( EQUAL(CPLGetConfigOption("FGDB_SIMUL_FAIL", ""), "CASE1") ||
463         CPLUnlinkTree(osEditedName) != 0 )
464     {
465         CPLError(CE_Warning, CPLE_AppDefined,
466                  "Cannot remove %s. Manual cleanup required", osEditedName.c_str());
467         eErr = OGRERR_FAILURE;
468     }
469 
470     pConnection->m_pGeodatabase = new Geodatabase;
471     long hr = ::OpenGeodatabase(StringToWString(osName), *(pConnection->m_pGeodatabase));
472     if (EQUAL(CPLGetConfigOption("FGDB_SIMUL_FAIL", ""), "CASE2") ||
473         FAILED(hr))
474     {
475         delete pConnection->m_pGeodatabase;
476         pConnection->m_pGeodatabase = NULL;
477         pConnection->SetLocked(FALSE);
478         Release(osName);
479         GDBErr(hr, "Failed to re-open Geodatabase. Dataset should be closed");
480         return OGRERR_FAILURE;
481     }
482 
483     FGdbDataSource* pDS = new FGdbDataSource(this, pConnection);
484     pDS->Open(osName, TRUE);
485     poDSInOut = new OGRMutexedDataSource(pDS, TRUE, hMutex, TRUE);
486 
487     pConnection->SetLocked(FALSE);
488 
489     return eErr;
490 }
491 
492 /***********************************************************************/
493 /*                            Release()                                */
494 /***********************************************************************/
495 
Release(const char * pszName)496 void FGdbDriver::Release(const char* pszName)
497 {
498     CPLMutexHolderOptionalLockD(hMutex);
499 
500     FGdbDatabaseConnection* pConnection = oMapConnections[pszName];
501     if( pConnection != NULL )
502     {
503         pConnection->m_nRefCount --;
504         CPLDebug("FileGDB", "ref_count of %s = %d now", pszName,
505                  pConnection->m_nRefCount);
506         if( pConnection->m_nRefCount == 0 )
507         {
508             if( pConnection->m_pGeodatabase != NULL )
509             {
510                 CPLDebug("FileGDB", "Really closing %s now", pszName);
511                 ::CloseGeodatabase(*(pConnection->m_pGeodatabase));
512                 delete pConnection->m_pGeodatabase;
513                 pConnection->m_pGeodatabase = NULL;
514             }
515             delete pConnection;
516             oMapConnections.erase(pszName);
517         }
518     }
519 }
520 
521 /***********************************************************************/
522 /*                         TestCapability()                            */
523 /***********************************************************************/
524 
TestCapability(const char * pszCap)525 int FGdbDriver::TestCapability( const char * pszCap )
526 {
527     if (EQUAL(pszCap, ODrCCreateDataSource) )
528         return TRUE;
529 
530     else if (EQUAL(pszCap, ODrCDeleteDataSource) )
531         return TRUE;
532 
533     return FALSE;
534 }
535 /************************************************************************/
536 /*                          DeleteDataSource()                          */
537 /************************************************************************/
538 
DeleteDataSource(const char * pszDataSource)539 OGRErr FGdbDriver::DeleteDataSource( const char *pszDataSource )
540 {
541     CPLMutexHolderD(&hMutex);
542 
543     std::wstring wstr = StringToWString(pszDataSource);
544 
545     long hr;
546 
547     if (S_OK != (hr = ::DeleteGeodatabase(wstr)))
548     {
549         GDBErr(hr, "Failed to delete Geodatabase");
550         return OGRERR_FAILURE;
551     }
552 
553     return OGRERR_NONE;
554 }
555 
556 /***********************************************************************/
557 /*                       RegisterOGRFileGDB()                          */
558 /***********************************************************************/
559 
RegisterOGRFileGDB()560 void RegisterOGRFileGDB()
561 
562 {
563     if (! GDAL_CHECK_VERSION("OGR FGDB"))
564         return;
565     OGRSFDriver* poDriver = new FGdbDriver;
566     poDriver->SetMetadataItem( GDAL_DMD_LONGNAME,
567                                 "ESRI FileGDB" );
568     poDriver->SetMetadataItem( GDAL_DMD_EXTENSION, "gdb" );
569     poDriver->SetMetadataItem( GDAL_DMD_HELPTOPIC,
570                                 "drv_filegdb.html" );
571 
572     poDriver->SetMetadataItem( GDAL_DMD_CREATIONOPTIONLIST, "<CreationOptionList/>" );
573 
574     poDriver->SetMetadataItem( GDAL_DS_LAYER_CREATIONOPTIONLIST,
575 "<LayerCreationOptionList>"
576 "  <Option name='FEATURE_DATASET' type='string' description='FeatureDataset folder into to put the new layer'/>"
577 "  <Option name='GEOMETRY_NAME' type='string' description='Name of geometry column' default='SHAPE'/>"
578 "  <Option name='GEOMETRY_NULLABLE' type='boolean' description='Whether the values of the geometry column can be NULL' default='YES'/>"
579 "  <Option name='FID' type='string' description='Name of OID column' default='OBJECTID' deprecated_alias='OID_NAME'/>"
580 "  <Option name='XYTOLERANCE' type='float' description='Snapping tolerance, used for advanced ArcGIS features like network and topology rules, on 2D coordinates, in the units of the CRS'/>"
581 "  <Option name='ZTOLERANCE' type='float' description='Snapping tolerance, used for advanced ArcGIS features like network and topology rules, on Z coordinates, in the units of the CRS'/>"
582 "  <Option name='XORIGIN' type='float' description='X origin of the coordinate precision grid'/>"
583 "  <Option name='YORIGIN' type='float' description='Y origin of the coordinate precision grid'/>"
584 "  <Option name='ZORIGIN' type='float' description='Z origin of the coordinate precision grid'/>"
585 "  <Option name='XYSCALE' type='float' description='X,Y scale of the coordinate precision grid'/>"
586 "  <Option name='ZSCALE' type='float' description='Z scale of the coordinate precision grid'/>"
587 "  <Option name='XML_DEFINITION' type='string' description='XML definition to create the new table. The root node of such a XML definition must be a <esri:DataElement> element conformant to FileGDBAPI.xsd'/>"
588 "  <Option name='CREATE_MULTIPATCH' type='boolean' description='Whether to write geometries of layers of type MultiPolygon as MultiPatch' default='NO'/>"
589 "  <Option name='COLUMN_TYPES' type='string' description='A list of strings of format field_name=fgdb_filed_type (separated by comma) to force the FileGDB column type of fields to be created'/>"
590 "  <Option name='CONFIGURATION_KEYWORD' type='string-select' description='Customize how data is stored. By default text in UTF-8 and data up to 1TB'>"
591 "    <Value>DEFAULTS</Value>"
592 "    <Value>TEXT_UTF16</Value>"
593 "    <Value>MAX_FILE_SIZE_4GB</Value>"
594 "    <Value>MAX_FILE_SIZE_256TB</Value>"
595 "    <Value>GEOMETRY_OUTOFLINE</Value>"
596 "    <Value>BLOB_OUTOFLINE</Value>"
597 "    <Value>GEOMETRY_AND_BLOB_OUTOFLINE</Value>"
598 "  </Option>"
599 "</LayerCreationOptionList>");
600 
601     poDriver->SetMetadataItem( GDAL_DMD_CREATIONFIELDDATATYPES, "Integer Real String Date DateTime Binary" );
602     poDriver->SetMetadataItem( GDAL_DCAP_NOTNULL_FIELDS, "YES" );
603     poDriver->SetMetadataItem( GDAL_DCAP_DEFAULT_FIELDS, "YES" );
604     poDriver->SetMetadataItem( GDAL_DCAP_NOTNULL_GEOMFIELDS, "YES" );
605 
606     OGRSFDriverRegistrar::GetRegistrar()->RegisterDriver(poDriver);
607 }
608 
609