1 /*******************************************************************************
2  *  Project: NextGIS Web Driver
3  *  Purpose: Implements NextGIS Web Driver
4  *  Author: Dmitry Baryshnikov, dmitry.baryshnikov@nextgis.com
5  *  Language: C++
6  *******************************************************************************
7  *  The MIT License (MIT)
8  *
9  *  Copyright (c) 2018-2020, NextGIS <info@nextgis.com>
10  *
11  *  Permission is hereby granted, free of charge, to any person obtaining a copy
12  *  of this software and associated documentation files (the "Software"), to deal
13  *  in the Software without restriction, including without limitation the rights
14  *  to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
15  *  copies of the Software, and to permit persons to whom the Software is
16  *  furnished to do so, subject to the following conditions:
17  *
18  *  The above copyright notice and this permission notice shall be included in all
19  *  copies or substantial portions of the Software.
20  *
21  *  THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
22  *  IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
23  *  FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
24  *  AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
25  *  LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
26  *  OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
27  *  SOFTWARE.
28  *******************************************************************************/
29 
30 #include "ogr_ngw.h"
31 
32 /*
33  * GetHeaders()
34  */
GetHeaders(const std::string & osUserPwdIn="")35 static char **GetHeaders(const std::string &osUserPwdIn = "")
36 {
37     char **papszOptions = nullptr;
38     papszOptions = CSLAddString(papszOptions, "HEADERS=Accept: */*");
39     std::string osUserPwd;
40     if( osUserPwdIn.empty() )
41     {
42         osUserPwd = CPLGetConfigOption("NGW_USERPWD", "");
43     }
44     else
45     {
46         osUserPwd = osUserPwdIn;
47     }
48 
49     if( !osUserPwd.empty() )
50     {
51         papszOptions = CSLAddString(papszOptions, "HTTPAUTH=BASIC");
52         std::string osUserPwdOption("USERPWD=");
53         osUserPwdOption += osUserPwd;
54         papszOptions = CSLAddString(papszOptions, osUserPwdOption.c_str());
55     }
56     return papszOptions;
57 }
58 
59 /*
60  * OGRNGWDriverIdentify()
61  */
62 
OGRNGWDriverIdentify(GDALOpenInfo * poOpenInfo)63 static int OGRNGWDriverIdentify( GDALOpenInfo *poOpenInfo )
64 {
65     return STARTS_WITH_CI( poOpenInfo->pszFilename, "NGW:" );
66 }
67 
68 /*
69  * OGRNGWDriverOpen()
70  */
71 
OGRNGWDriverOpen(GDALOpenInfo * poOpenInfo)72 static GDALDataset *OGRNGWDriverOpen( GDALOpenInfo *poOpenInfo )
73 {
74     if( OGRNGWDriverIdentify( poOpenInfo ) == 0 )
75     {
76         return nullptr;
77     }
78 
79     OGRNGWDataset *poDS = new OGRNGWDataset();
80     if( !poDS->Open( poOpenInfo->pszFilename, poOpenInfo->papszOpenOptions,
81                      poOpenInfo->eAccess == GA_Update, poOpenInfo->nOpenFlags ) )
82     {
83         delete poDS;
84         poDS = nullptr;
85     }
86 
87     return poDS;
88 }
89 
90 /*
91  * OGRNGWDriverCreate()
92  *
93  * Add new datasource name at the end of URL:
94  * NGW:http://some.nextgis.com/resource/0/new_name
95  * NGW:http://some.nextgis.com:8000/test/resource/0/new_name
96  */
97 
OGRNGWDriverCreate(const char * pszName,CPL_UNUSED int nBands,CPL_UNUSED int nXSize,CPL_UNUSED int nYSize,CPL_UNUSED GDALDataType eDT,char ** papszOptions)98 static GDALDataset *OGRNGWDriverCreate( const char *pszName,
99                                             CPL_UNUSED int nBands,
100                                             CPL_UNUSED int nXSize,
101                                             CPL_UNUSED int nYSize,
102                                             CPL_UNUSED GDALDataType eDT,
103                                             char **papszOptions )
104 
105 {
106     NGWAPI::Uri stUri = NGWAPI::ParseUri(pszName);
107     CPLErrorReset();
108     if( stUri.osPrefix != "NGW" )
109     {
110         CPLError(CE_Failure, CPLE_NotSupported, "Unsupported name %s", pszName);
111         return nullptr;
112     }
113 
114     CPLDebug("NGW", "Parse uri result. URL: %s, ID: %s, New name: %s",
115         stUri.osAddress.c_str(), stUri.osResourceId.c_str(),
116         stUri.osNewResourceName.c_str());
117 
118     std::string osKey = CSLFetchNameValueDef( papszOptions, "KEY", "");
119     std::string osDesc = CSLFetchNameValueDef( papszOptions, "DESCRIPTION", "");
120     std::string osUserPwd = CSLFetchNameValueDef( papszOptions, "USERPWD",
121         CPLGetConfigOption("NGW_USERPWD", "") );
122 
123     CPLJSONObject oPayload;
124     CPLJSONObject oResource( "resource", oPayload );
125     oResource.Add( "cls", "resource_group" );
126     oResource.Add( "display_name", stUri.osNewResourceName );
127     if( !osKey.empty() )
128     {
129         oResource.Add( "keyname", osKey );
130     }
131 
132     if( !osDesc.empty() )
133     {
134         oResource.Add( "description", osDesc );
135     }
136 
137     CPLJSONObject oParent( "parent", oResource );
138     oParent.Add( "id", atoi(stUri.osResourceId.c_str()) );
139 
140     std::string osNewResourceId = NGWAPI::CreateResource( stUri.osAddress,
141         oPayload.Format(CPLJSONObject::PrettyFormat::Plain), GetHeaders(osUserPwd) );
142     if( osNewResourceId == "-1" )
143     {
144         return nullptr;
145     }
146 
147     OGRNGWDataset *poDS = new OGRNGWDataset();
148 
149     if( !poDS->Open( stUri.osAddress, osNewResourceId, papszOptions, true, GDAL_OF_RASTER | GDAL_OF_VECTOR  ) ) // TODO: GDAL_OF_GNM
150     {
151         delete poDS;
152         poDS = nullptr;
153     }
154 
155     return poDS;
156 }
157 
158 /*
159  * OGRNGWDriverDelete()
160  */
OGRNGWDriverDelete(const char * pszName)161 static CPLErr OGRNGWDriverDelete( const char *pszName )
162 {
163     NGWAPI::Uri stUri = NGWAPI::ParseUri(pszName);
164     CPLErrorReset();
165     if( !stUri.osNewResourceName.empty() )
166     {
167         CPLError(CE_Warning, CPLE_NotSupported, "Cannot delete new resource with name %s", pszName);
168         return CE_Failure;
169     }
170 
171     if( stUri.osPrefix != "NGW" )
172     {
173         CPLError(CE_Failure, CPLE_NotSupported, "Unsupported name %s", pszName);
174         return CE_Failure;
175     }
176 
177     if( stUri.osResourceId == "0" )
178     {
179         CPLError(CE_Failure, CPLE_NotSupported, "Cannot delete resource 0");
180         return CE_Failure;
181     }
182 
183     char **papszOptions = GetHeaders();
184     // NGWAPI::Permissions stPermissions = NGWAPI::CheckPermissions(stUri.osAddress,
185     //     stUri.osResourceId, papszOptions, true);
186     // if( stPermissions.bResourceCanDelete )
187     // {
188         return NGWAPI::DeleteResource(stUri.osAddress, stUri.osResourceId,
189             papszOptions) ? CE_None : CE_Failure;
190     // }
191     // CPLError(CE_Failure, CPLE_AppDefined, "Operation not permitted.");
192     // return CE_Failure;
193 }
194 
195 /*
196  * OGRNGWDriverRename()
197  */
OGRNGWDriverRename(const char * pszNewName,const char * pszOldName)198 static CPLErr OGRNGWDriverRename( const char *pszNewName, const char *pszOldName )
199 {
200     NGWAPI::Uri stUri = NGWAPI::ParseUri(pszOldName);
201     CPLErrorReset();
202     if( stUri.osPrefix != "NGW" )
203     {
204         CPLError(CE_Failure, CPLE_NotSupported, "Unsupported name %s", pszOldName);
205         return CE_Failure;
206     }
207     CPLDebug("NGW", "Parse uri result. URL: %s, ID: %s, New name: %s",
208         stUri.osAddress.c_str(), stUri.osResourceId.c_str(), pszNewName);
209     char **papszOptions = GetHeaders();
210     // NGWAPI::Permissions stPermissions = NGWAPI::CheckPermissions(stUri.osAddress,
211     //     stUri.osResourceId, papszOptions, true);
212     // if( stPermissions.bResourceCanUpdate )
213     // {
214         return NGWAPI::RenameResource(stUri.osAddress, stUri.osResourceId,
215             pszNewName, papszOptions) ? CE_None : CE_Failure;
216     // }
217     // CPLError(CE_Failure, CPLE_AppDefined, "Operation not permitted.");
218     // return CE_Failure;
219 }
220 
221 /*
222  * OGRNGWDriverCreateCopy()
223  */
OGRNGWDriverCreateCopy(const char * pszFilename,GDALDataset * poSrcDS,int bStrict,char ** papszOptions,GDALProgressFunc pfnProgress,void * pProgressData)224 static GDALDataset *OGRNGWDriverCreateCopy( const char *pszFilename,
225     GDALDataset *poSrcDS, int bStrict, char **papszOptions,
226     GDALProgressFunc pfnProgress, void *pProgressData )
227 {
228     // Check destination dataset,
229     NGWAPI::Uri stUri = NGWAPI::ParseUri(pszFilename);
230     CPLErrorReset();
231     if( stUri.osPrefix != "NGW" )
232     {
233         CPLError(CE_Failure, CPLE_NotSupported, "Unsupported name %s", pszFilename);
234         return nullptr;
235     }
236 
237     // NGW v3.1 supported different raster types: 1 band and 16/32 bit, RGB/RGBA
238     // rasters and etc.
239     // For RGB/RGBA rasters we can create default raster_style.
240     // For other types - qml style file path is mandatory.
241     std::string osQMLPath = CSLFetchNameValueDef( papszOptions, "RASTER_QML_PATH", "");
242 
243     // Check bands count.
244     const int nBands = poSrcDS->GetRasterCount();
245     if( nBands < 3 || nBands > 4 )
246     {
247         if( osQMLPath.empty() ) {
248             CPLError( CE_Failure, CPLE_NotSupported,
249                 "Default NGW raster style supports only 3 (RGB) or 4 (RGBA). "
250                 "Raster has %d bands. You must provide QML file with raster style.",
251                 nBands );
252             return nullptr;
253         }
254     }
255 
256     // Check band data type.
257     if( poSrcDS->GetRasterBand(1)->GetRasterDataType() != GDT_Byte )
258     {
259         if( osQMLPath.empty() ) {
260             CPLError( CE_Failure, CPLE_NotSupported,
261                 "Default NGW raster style supports only 8 bit byte bands. "
262                 "Raster has data type %s. You must provide QML file with raster style.",
263                 GDALGetDataTypeName( poSrcDS->GetRasterBand(1)->GetRasterDataType()) );
264             return nullptr;
265         }
266     }
267 
268     bool bCloseDS = false;
269     std::string osFilename;
270 
271     // Check if source GDALDataset is tiff.
272     if( EQUAL(poSrcDS->GetDriverName(), "GTiff") == FALSE )
273     {
274         GDALDriver *poDriver = GetGDALDriverManager()->GetDriverByName("GTiff");
275         // Compress to minimize network transfer.
276         const char* apszOptions[] = { "COMPRESS=LZW", "NUM_THREADS=ALL_CPUS", nullptr };
277         std::string osTempFilename = CPLGenerateTempFilename("ngw_tmp");
278         osTempFilename += ".tif";
279         GDALDataset *poTmpDS = poDriver->CreateCopy( osTempFilename.c_str(),
280             poSrcDS, bStrict, const_cast<char**>(apszOptions), pfnProgress,
281             pProgressData);
282 
283         if( poTmpDS != nullptr )
284         {
285             bCloseDS = true;
286             osFilename = osTempFilename;
287             poSrcDS = poTmpDS;
288         }
289         else
290         {
291             CPLError( CE_Failure, CPLE_NotSupported,
292                 "NGW driver doesn't support %s source raster.",
293                 poSrcDS->GetDriverName() );
294             return nullptr;
295         }
296     }
297 
298     if( osFilename.empty() )
299     {
300         // Check if source tiff is local file.
301         CPLStringList oaFiles( poSrcDS->GetFileList() );
302         for( int i = 0; i < oaFiles.size(); ++i )
303         {
304             // Check extension tif
305             const char *pszExt = CPLGetExtension(oaFiles[i]);
306             if( pszExt && EQUALN(pszExt, "tif", 3) )
307             {
308                 osFilename = oaFiles[i];
309                 break;
310             }
311         }
312     }
313 
314     if( bCloseDS )
315     {
316         GDALClose( (GDALDatasetH) poSrcDS );
317     }
318 
319     std::string osKey = CSLFetchNameValueDef( papszOptions, "KEY", "");
320     std::string osDesc = CSLFetchNameValueDef( papszOptions, "DESCRIPTION", "");
321     std::string osUserPwd = CSLFetchNameValueDef( papszOptions, "USERPWD",
322         CPLGetConfigOption("NGW_USERPWD", ""));
323     std::string osStyleName = CSLFetchNameValueDef( papszOptions, "RASTER_STYLE_NAME", "");
324 
325     // Send file
326     char **papszHTTPOptions = GetHeaders(osUserPwd);
327     CPLJSONObject oFileJson = NGWAPI::UploadFile(stUri.osAddress, osFilename,
328         papszHTTPOptions, pfnProgress, pProgressData);
329 
330     if( bCloseDS ) // Delete temp tiff file.
331     {
332         VSIUnlink(osFilename.c_str());
333     }
334 
335     if( !oFileJson.IsValid() )
336     {
337         return nullptr;
338     }
339 
340     CPLJSONArray oUploadMeta = oFileJson.GetArray("upload_meta");
341     if( !oUploadMeta.IsValid() || oUploadMeta.Size() == 0 )
342     {
343         CPLError(CE_Failure, CPLE_AppDefined, "Get unexpected response: %s.",
344             oFileJson.Format(CPLJSONObject::PrettyFormat::Plain).c_str());
345         return nullptr;
346     }
347 
348     // Create raster layer
349     // Create payload
350     CPLJSONObject oPayloadRaster;
351     CPLJSONObject oResource( "resource", oPayloadRaster );
352     oResource.Add( "cls", "raster_layer" );
353     oResource.Add( "display_name", stUri.osNewResourceName );
354     if( !osKey.empty() )
355     {
356         oResource.Add( "keyname", osKey );
357     }
358 
359     if( !osDesc.empty() )
360     {
361         oResource.Add( "description", osDesc );
362     }
363 
364     CPLJSONObject oParent( "parent", oResource );
365     oParent.Add( "id", atoi(stUri.osResourceId.c_str()) );
366 
367     CPLJSONObject oRasterLayer( "raster_layer", oPayloadRaster );
368     oRasterLayer.Add( "source", oUploadMeta[0] );
369 
370     CPLJSONObject oSrs("srs", oRasterLayer);
371     oSrs.Add( "id", 3857 ); // Now only Web Mercator supported.
372 
373     papszHTTPOptions = GetHeaders(osUserPwd);
374     std::string osNewResourceId = NGWAPI::CreateResource( stUri.osAddress,
375         oPayloadRaster.Format(CPLJSONObject::PrettyFormat::Plain), papszHTTPOptions );
376     if( osNewResourceId == "-1" )
377     {
378         return nullptr;
379     }
380 
381     // Create raster style
382     CPLJSONObject oPayloadRasterStyle;
383     CPLJSONObject oResourceStyle( "resource", oPayloadRasterStyle );
384     if( osQMLPath.empty() )
385     {
386         oResourceStyle.Add( "cls", "raster_style" );
387     }
388     else
389     {
390         oResourceStyle.Add( "cls", "qgis_raster_style" );
391 
392         // Upload QML file
393         papszHTTPOptions = GetHeaders(osUserPwd);
394         oFileJson = NGWAPI::UploadFile(stUri.osAddress, osQMLPath,
395             papszHTTPOptions, pfnProgress, pProgressData);
396         oUploadMeta = oFileJson.GetArray("upload_meta");
397         if( !oUploadMeta.IsValid() || oUploadMeta.Size() == 0 )
398         {
399             CPLError(CE_Failure, CPLE_AppDefined, "Get unexpected response: %s.",
400                 oFileJson.Format(CPLJSONObject::PrettyFormat::Plain).c_str());
401             return nullptr;
402         }
403         CPLJSONObject oQGISRasterStyle( "qgis_raster_style", oPayloadRasterStyle );
404         oQGISRasterStyle.Add( "file_upload", oUploadMeta[0]);
405     }
406 
407     if( osStyleName.empty() )
408     {
409         osStyleName = stUri.osNewResourceName;
410     }
411     oResourceStyle.Add( "display_name", osStyleName );
412     CPLJSONObject oParentRaster( "parent", oResourceStyle );
413     oParentRaster.Add( "id", atoi(osNewResourceId.c_str()) );
414 
415     papszHTTPOptions = GetHeaders(osUserPwd);
416     osNewResourceId = NGWAPI::CreateResource( stUri.osAddress,
417         oPayloadRasterStyle.Format(CPLJSONObject::PrettyFormat::Plain), papszHTTPOptions );
418     if( osNewResourceId == "-1" )
419     {
420         return nullptr;
421     }
422 
423     OGRNGWDataset *poDS = new OGRNGWDataset();
424 
425     if( !poDS->Open( stUri.osAddress, osNewResourceId, papszOptions, true, GDAL_OF_RASTER ) )
426     {
427         delete poDS;
428         poDS = nullptr;
429     }
430 
431     return poDS;
432 }
433 
434 /*
435  * RegisterOGRNGW()
436  */
437 
RegisterOGRNGW()438 void RegisterOGRNGW()
439 {
440     if( GDALGetDriverByName( "NGW" ) != nullptr )
441     {
442         return;
443     }
444 
445     GDALDriver *poDriver = new GDALDriver();
446 
447     poDriver->SetDescription( "NGW" );
448     poDriver->SetMetadataItem( GDAL_DMD_LONGNAME, "NextGIS Web" );
449     poDriver->SetMetadataItem( GDAL_DCAP_RASTER, "YES" );
450     poDriver->SetMetadataItem( GDAL_DCAP_VECTOR, "YES" );
451     poDriver->SetMetadataItem( GDAL_DMD_SUBDATASETS, "YES" );
452     poDriver->SetMetadataItem( GDAL_DMD_HELPTOPIC, "drivers/vector/ngw.html" );
453     poDriver->SetMetadataItem( GDAL_DMD_CONNECTION_PREFIX, "NGW:" );
454 
455     poDriver->SetMetadataItem( GDAL_DMD_CREATIONDATATYPES, "Byte" );
456     poDriver->SetMetadataItem( GDAL_DCAP_CREATECOPY, "YES" );
457 
458     poDriver->SetMetadataItem( GDAL_DMD_OPENOPTIONLIST,
459         "<OpenOptionList>"
460         "   <Option name='USERPWD' scope='raster,vector' type='string' description='Username and password, separated by colon'/>"
461         "   <Option name='PAGE_SIZE' scope='vector' type='integer' description='Limit feature count while fetching from server. Default value is -1 - no limit' default='-1'/>"
462         "   <Option name='BATCH_SIZE' scope='vector' type='integer' description='Size of feature insert and update operations cache before send to server. If batch size is -1 batch mode is disabled' default='-1'/>"
463         "   <Option name='NATIVE_DATA' scope='vector' type='boolean' description='Whether to store the native Json representation of extensions key. If EXTENSIONS not set or empty, NATIVE_DATA defaults to NO' default='NO'/>"
464         "   <Option name='CACHE_EXPIRES' scope='raster' type='integer' description='Time in seconds cached files will stay valid. If cached file expires it is deleted when maximum size of cache is reached. Also expired file can be overwritten by the new one from web' default='604800'/>"
465         "   <Option name='CACHE_MAX_SIZE' scope='raster' type='integer' description='The cache maximum size in bytes. If cache reached maximum size, expired cached files will be deleted' default='67108864'/>"
466         "   <Option name='JSON_DEPTH' scope='raster,vector' type='integer' description='The depth of json response that can be parsed. If depth is greater than this value, parse error occurs' default='32'/>"
467         "   <Option name='EXTENSIONS' scope='vector' type='string' description='Comma separated extensions list. Available are description and attachment' default=''/>"
468         "</OpenOptionList>"
469     );
470 
471     poDriver->SetMetadataItem( GDAL_DMD_CREATIONOPTIONLIST,
472         "<CreationOptionList>"
473         "   <Option name='KEY' scope='raster,vector' type='string' description='Key value. Must be unique in whole NextGIS Web instance'/>"
474         "   <Option name='DESCRIPTION' scope='raster,vector' type='string' description='Resource description'/>"
475         "   <Option name='RASTER_STYLE_NAME' scope='raster' type='string' description='Raster layer style name'/>"
476         "   <Option name='USERPWD' scope='raster,vector' type='string' description='Username and password, separated by colon'/>"
477         "   <Option name='PAGE_SIZE' scope='vector' type='integer' description='Limit feature count while fetching from server. Default value is -1 - no limit' default='-1'/>"
478         "   <Option name='BATCH_SIZE' scope='vector' type='integer' description='Size of feature insert and update operations cache before send to server. If batch size is -1 batch mode is disabled' default='-1'/>"
479         "   <Option name='NATIVE_DATA' scope='vector' type='boolean' description='Whether to store the native Json representation of extensions key. If EXTENSIONS not set or empty, NATIVE_DATA defaults to NO' default='NO'/>"
480         "   <Option name='CACHE_EXPIRES' scope='raster' type='integer' description='Time in seconds cached files will stay valid. If cached file expires it is deleted when maximum size of cache is reached. Also expired file can be overwritten by the new one from web' default='604800'/>"
481         "   <Option name='CACHE_MAX_SIZE' scope='raster' type='integer' description='The cache maximum size in bytes. If cache reached maximum size, expired cached files will be deleted' default='67108864'/>"
482         "   <Option name='JSON_DEPTH' scope='raster,vector' type='integer' description='The depth of json response that can be parsed. If depth is greater than this value, parse error occurs' default='32'/>"
483         "   <Option name='RASTER_QML_PATH' scope='raster' type='string' description='Raster QMS style path'/>"
484         "   <Option name='EXTENSIONS' scope='vector' type='string' description='Comma separated extensions list. Available are description and attachment' default=''/>"
485         "</CreationOptionList>"
486     );
487 
488     poDriver->SetMetadataItem( GDAL_DS_LAYER_CREATIONOPTIONLIST,
489         "<LayerCreationOptionList>"
490         "   <Option name='OVERWRITE' type='boolean' description='Whether to overwrite an existing table with the layer name to be created' default='NO'/>"
491         "   <Option name='KEY' type='string' description='Key value. Must be unique in whole NextGIS Web instance'/>"
492         "   <Option name='DESCRIPTION' type='string' description='Resource description'/>"
493         "</LayerCreationOptionList>"
494     );
495 
496     poDriver->SetMetadataItem( GDAL_DMD_CREATIONFIELDDATATYPES, "Integer Integer64 Real String Date DateTime Time" );
497     poDriver->SetMetadataItem( GDAL_DCAP_NOTNULL_FIELDS, "NO" );
498     poDriver->SetMetadataItem( GDAL_DCAP_DEFAULT_FIELDS, "NO" );
499     poDriver->SetMetadataItem( GDAL_DCAP_NOTNULL_GEOMFIELDS, "YES" );
500 
501     poDriver->pfnOpen = OGRNGWDriverOpen;
502     poDriver->pfnIdentify = OGRNGWDriverIdentify;
503     poDriver->pfnCreate = OGRNGWDriverCreate;
504     poDriver->pfnCreateCopy = OGRNGWDriverCreateCopy;
505     poDriver->pfnDelete = OGRNGWDriverDelete;
506     poDriver->pfnRename = OGRNGWDriverRename;
507 
508     GetGDALDriverManager()->RegisterDriver( poDriver );
509 }
510