1 /******************************************************************************
2 * Project: geography network utility
3 * Purpose: Analyse GNM networks
4 * Authors: Mikhail Gusev, gusevmihs at gmail dot com
5 * Dmitry Baryshnikov, polimax@mail.ru
6 *
7 ******************************************************************************
8 * Copyright (C) 2014 Mikhail Gusev
9 * Copyright (c) 2014-2015, NextGIS <info@nextgis.com>
10 *
11 * Permission is hereby granted, free of charge, to any person obtaining a
12 * copy of this software and associated documentation files (the "Software"),
13 * to deal in the Software without restriction, including without limitation
14 * the rights to use, copy, modify, merge, publish, distribute, sublicense,
15 * and/or sell copies of the Software, and to permit persons to whom the
16 * Software is furnished to do so, subject to the following conditions:
17 *
18 * The above copyright notice and this permission notice shall be included
19 * in all copies or substantial portions of the Software.
20 *
21 * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS
22 * OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
23 * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL
24 * THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
25 * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
26 * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
27 * DEALINGS IN THE SOFTWARE.
28 ****************************************************************************/
29
30 #include "commonutils.h"
31 #include "gdal_version.h"
32 #include "gnm.h"
33 #include "gnm_priv.h"
34 #include "ogr_p.h"
35
36 CPL_CVSID("$Id: gnmanalyse.cpp 2dd182b498f0381aa3bc200928681dfbcf2ecfa6 2020-02-04 10:22:31 +0100 Enrico Weigelt, metux IT consult $")
37
38 enum operation
39 {
40 op_unknown = 0, /** no operation */
41 op_dijkstra, /** create shortest path using Dijkstra algorithm */
42 op_kpaths, /** create k shortest paths using Yens algorithm */
43 op_resource /** create resource distribution layer */
44 };
45
46 /************************************************************************/
47 /* Usage() */
48 /************************************************************************/
Usage(const char * pszAdditionalMsg,int bShort=TRUE)49 static void Usage(const char* pszAdditionalMsg, int bShort = TRUE)
50 {
51 printf("Usage: gnmanalyse [--help][-q][-quiet][--long-usage]\n"
52 " [dijkstra start_gfid end_gfid [[-alo NAME=VALUE] ...]]\n"
53 " [kpaths start_gfid end_gfid k [[-alo NAME=VALUE] ...]]\n"
54 " [resource [[-alo NAME=VALUE] ...]]\n"
55 " [-ds ds_name][-f ds_format][-l layer_name]\n"
56 " [[-dsco NAME=VALUE] ...][-lco NAME=VALUE]\n"
57 " gnm_name\n");
58
59 if (bShort)
60 {
61 printf("\nNote: gnmanalyse --long-usage for full help.\n");
62 if (pszAdditionalMsg)
63 fprintf(stderr, "\nFAILURE: %s\n", pszAdditionalMsg);
64 exit(1);
65 }
66
67 printf("\n dijkstra start_gfid end_gfid: calculates the best path between two points using Dijkstra algorithm from start_gfid point to end_gfid point\n"
68 " kpaths start_gfid end_gfid k: calculates k (up to 10) best paths between two points using Yen\'s algorithm (which internally uses Dijkstra algorithm for single path calculating) from start_gfid point to end_gfid point\n"
69 " resource: calculates the \"resource distribution\". The connected components search is performed using breadth-first search and starting from that features which are marked by rules as \'EMITTERS\'\n"
70 " -ds ds_name: the name&path of the dataset to save the layer with resulting paths. Not need to be existed dataset\n"
71 " -f ds_format: define this to set the format of newly created dataset\n"
72 " -l layer_name: the name of the resulting layer. If the layer exists already - it will be rewritten. For K shortest paths several layers are created in format layer_nameN, where N - is number of the path (0 - is the most shortest one)\n"
73 " -dsco NAME=VALUE: Dataset creation option (format specific)\n"
74 " -lco NAME=VALUE: Layer creation option (format specific)\n"
75 " -alo NAME=VALUE: Algorithm option (format specific)\n"
76 " gnm_name: the network to work with (path and name)\n"
77 );
78
79 if (pszAdditionalMsg)
80 fprintf(stderr, "\nFAILURE: %s\n", pszAdditionalMsg);
81
82 exit(1);
83 }
84
Usage(int bShort=TRUE)85 static void Usage(int bShort = TRUE)
86 {
87 Usage(nullptr, bShort);
88 }
89
90 /************************************************************************/
91 /* GetLayerAndOverwriteIfNecessary() */
92 /************************************************************************/
93
GetLayerAndOverwriteIfNecessary(GDALDataset * poDstDS,const char * pszNewLayerName,int bOverwrite,int * pbErrorOccurred)94 static OGRLayer* GetLayerAndOverwriteIfNecessary(GDALDataset *poDstDS,
95 const char* pszNewLayerName,
96 int bOverwrite,
97 int* pbErrorOccurred)
98 {
99 if( pbErrorOccurred )
100 *pbErrorOccurred = FALSE;
101
102 /* GetLayerByName() can instantiate layers that would have been */
103 /* 'hidden' otherwise, for example, non-spatial tables in a */
104 /* PostGIS-enabled database, so this apparently useless command is */
105 /* not useless... (#4012) */
106 CPLPushErrorHandler(CPLQuietErrorHandler);
107 OGRLayer* poDstLayer = poDstDS->GetLayerByName(pszNewLayerName);
108 CPLPopErrorHandler();
109 CPLErrorReset();
110
111 int iLayer = -1;
112 if (poDstLayer != nullptr)
113 {
114 int nLayerCount = poDstDS->GetLayerCount();
115 for( iLayer = 0; iLayer < nLayerCount; iLayer++ )
116 {
117 OGRLayer *poLayer = poDstDS->GetLayer(iLayer);
118 if (poLayer == poDstLayer)
119 break;
120 }
121
122 if (iLayer == nLayerCount)
123 /* should not happen with an ideal driver */
124 poDstLayer = nullptr;
125 }
126
127 /* -------------------------------------------------------------------- */
128 /* If the user requested overwrite, and we have the layer in */
129 /* question we need to delete it now so it will get recreated */
130 /* (overwritten). */
131 /* -------------------------------------------------------------------- */
132 if( poDstLayer != nullptr && bOverwrite )
133 {
134 if( poDstDS->DeleteLayer( iLayer ) != OGRERR_NONE )
135 {
136 fprintf( stderr,
137 "DeleteLayer() failed when overwrite requested.\n" );
138 if( pbErrorOccurred )
139 *pbErrorOccurred = TRUE;
140 }
141 poDstLayer = nullptr;
142 }
143
144 return poDstLayer;
145 }
146
147 /************************************************************************/
148 /* CreateAndFillOutputDataset */
149 /************************************************************************/
CreateAndFillOutputDataset(OGRLayer * poSrcLayer,const char * pszDestDataSource,const char * pszFormat,const char * pszLayer,char ** papszDSCO,char ** papszLCO,int bQuiet)150 static OGRErr CreateAndFillOutputDataset(OGRLayer* poSrcLayer,
151 const char* pszDestDataSource,
152 const char* pszFormat,
153 const char* pszLayer,
154 char** papszDSCO,
155 char** papszLCO,
156 int bQuiet)
157 {
158 GDALDriver *poDriver = GetGDALDriverManager()->GetDriverByName(pszFormat);
159 if( poDriver == nullptr )
160 {
161 fprintf( stderr, "%s driver not available\n", pszFormat );
162 return OGRERR_FAILURE;
163 }
164
165 if( !CPLTestBool(
166 CSLFetchNameValueDef(poDriver->GetMetadata(), GDAL_DCAP_CREATE,
167 "FALSE") ) )
168 {
169 fprintf( stderr, "%s driver does not support data source creation.\n",
170 pszFormat );
171 return OGRERR_FAILURE;
172 }
173
174 GDALDataset* poODS = poDriver->Create( pszDestDataSource, 0, 0, 0,
175 GDT_Unknown, papszDSCO );
176 if( poODS == nullptr )
177 {
178 fprintf( stderr, "%s driver failed to create %s\n",
179 pszFormat, pszDestDataSource );
180 return OGRERR_FAILURE;
181 }
182
183 if(nullptr == pszLayer)
184 pszLayer = poSrcLayer->GetName();
185 int nError;
186 GetLayerAndOverwriteIfNecessary(poODS, pszLayer, TRUE, &nError);
187 if(nError == TRUE)
188 {
189 return OGRERR_FAILURE;
190 }
191
192 // create layer
193 OGRLayer * poLayer = poODS->CopyLayer(poSrcLayer, pszLayer, papszLCO);
194 if (nullptr == poLayer)
195 {
196 fprintf(stderr, "\nFAILURE: Can not copy path to %s\n",
197 pszDestDataSource);
198 GDALClose(poODS);
199
200 return OGRERR_FAILURE;
201 }
202
203 if (bQuiet == FALSE)
204 {
205 printf("\nPath successfully copied and added to the network at %s\n",
206 pszDestDataSource);
207 }
208
209 GDALClose(poODS);
210
211 return OGRERR_NONE;
212 }
213
214 /************************************************************************/
215 /* ReportOnLayer() */
216 /************************************************************************/
217
ReportOnLayer(OGRLayer * poLayer,int bVerbose)218 static void ReportOnLayer( OGRLayer * poLayer, int bVerbose )
219
220 {
221 OGRFeatureDefn *poDefn = poLayer->GetLayerDefn();
222
223 /* -------------------------------------------------------------------- */
224 /* Report various overall information. */
225 /* -------------------------------------------------------------------- */
226 printf( "\n" );
227
228 printf( "Layer name: %s\n", poLayer->GetName() );
229
230 if( bVerbose )
231 {
232 int nGeomFieldCount =
233 poLayer->GetLayerDefn()->GetGeomFieldCount();
234 if( nGeomFieldCount > 1 )
235 {
236 for(int iGeom = 0;iGeom < nGeomFieldCount; iGeom ++ )
237 {
238 OGRGeomFieldDefn* poGFldDefn =
239 poLayer->GetLayerDefn()->GetGeomFieldDefn(iGeom);
240 printf( "Geometry (%s): %s\n", poGFldDefn->GetNameRef(),
241 OGRGeometryTypeToName( poGFldDefn->GetType() ) );
242 }
243 }
244 else
245 {
246 printf( "Geometry: %s\n",
247 OGRGeometryTypeToName( poLayer->GetGeomType() ) );
248 }
249
250 printf( "Feature Count: " CPL_FRMT_GIB "\n", poLayer->GetFeatureCount() );
251
252 OGREnvelope oExt;
253 if( nGeomFieldCount > 1 )
254 {
255 for(int iGeom = 0;iGeom < nGeomFieldCount; iGeom ++ )
256 {
257 if (poLayer->GetExtent(iGeom, &oExt, TRUE) == OGRERR_NONE)
258 {
259 OGRGeomFieldDefn* poGFldDefn =
260 poLayer->GetLayerDefn()->GetGeomFieldDefn(iGeom);
261 CPLprintf("Extent (%s): (%f, %f) - (%f, %f)\n",
262 poGFldDefn->GetNameRef(),
263 oExt.MinX, oExt.MinY, oExt.MaxX, oExt.MaxY);
264 }
265 }
266 }
267 else if ( poLayer->GetExtent(&oExt, TRUE) == OGRERR_NONE)
268 {
269 CPLprintf("Extent: (%f, %f) - (%f, %f)\n",
270 oExt.MinX, oExt.MinY, oExt.MaxX, oExt.MaxY);
271 }
272
273 char *pszWKT;
274
275 if( nGeomFieldCount > 1 )
276 {
277 for(int iGeom = 0;iGeom < nGeomFieldCount; iGeom ++ )
278 {
279 OGRGeomFieldDefn* poGFldDefn =
280 poLayer->GetLayerDefn()->GetGeomFieldDefn(iGeom);
281 OGRSpatialReference* poSRS = poGFldDefn->GetSpatialRef();
282 if( poSRS == nullptr )
283 pszWKT = CPLStrdup( "(unknown)" );
284 else
285 {
286 poSRS->exportToPrettyWkt( &pszWKT );
287 }
288
289 printf( "SRS WKT (%s):\n%s\n",
290 poGFldDefn->GetNameRef(), pszWKT );
291 CPLFree( pszWKT );
292 }
293 }
294 else
295 {
296 if( poLayer->GetSpatialRef() == nullptr )
297 pszWKT = CPLStrdup( "(unknown)" );
298 else
299 {
300 poLayer->GetSpatialRef()->exportToPrettyWkt( &pszWKT );
301 }
302
303 printf( "Layer SRS WKT:\n%s\n", pszWKT );
304 CPLFree( pszWKT );
305 }
306
307 if( strlen(poLayer->GetFIDColumn()) > 0 )
308 printf( "FID Column = %s\n",
309 poLayer->GetFIDColumn() );
310
311 for(int iGeom = 0;iGeom < nGeomFieldCount; iGeom ++ )
312 {
313 OGRGeomFieldDefn* poGFldDefn =
314 poLayer->GetLayerDefn()->GetGeomFieldDefn(iGeom);
315 if( nGeomFieldCount == 1 &&
316 EQUAL(poGFldDefn->GetNameRef(), "") && poGFldDefn->IsNullable() )
317 break;
318 printf( "Geometry Column ");
319 if( nGeomFieldCount > 1 )
320 printf("%d ", iGeom + 1);
321 if( !poGFldDefn->IsNullable() )
322 printf("NOT NULL ");
323 printf("= %s\n", poGFldDefn->GetNameRef() );
324 }
325
326 for( int iAttr = 0; iAttr < poDefn->GetFieldCount(); iAttr++ )
327 {
328 OGRFieldDefn *poField = poDefn->GetFieldDefn( iAttr );
329 const char* pszType = (poField->GetSubType() != OFSTNone) ?
330 CPLSPrintf("%s(%s)",
331 poField->GetFieldTypeName( poField->GetType() ),
332 poField->GetFieldSubTypeName(poField->GetSubType())) :
333 poField->GetFieldTypeName( poField->GetType() );
334 printf( "%s: %s (%d.%d)",
335 poField->GetNameRef(),
336 pszType,
337 poField->GetWidth(),
338 poField->GetPrecision() );
339 if( !poField->IsNullable() )
340 printf(" NOT NULL");
341 if( poField->GetDefault() != nullptr )
342 printf(" DEFAULT %s", poField->GetDefault() );
343 printf( "\n" );
344 }
345 }
346
347 /* -------------------------------------------------------------------- */
348 /* Read, and dump features. */
349 /* -------------------------------------------------------------------- */
350 OGRFeature *poFeature = nullptr;
351 while( (poFeature = poLayer->GetNextFeature()) != nullptr )
352 {
353 poFeature->DumpReadable( nullptr );
354 OGRFeature::DestroyFeature( poFeature );
355 }
356 }
357
358 /************************************************************************/
359 /* main() */
360 /************************************************************************/
361
362 #define CHECK_HAS_ENOUGH_ADDITIONAL_ARGS(nExtraArg) \
363 do { if (iArg + nExtraArg >= nArgc) \
364 Usage(CPLSPrintf("%s option requires %d argument(s)", papszArgv[iArg], \
365 nExtraArg)); \
366 } while( false )
367
MAIN_START(nArgc,papszArgv)368 MAIN_START(nArgc, papszArgv)
369
370 {
371 int bQuiet = FALSE;
372
373 const char *pszDataSource = nullptr;
374
375 GNMGFID nFromFID = -1;
376 GNMGFID nToFID = -1;
377 int nK = 1;
378 const char *pszDataset = nullptr;
379 const char *pszFormat = "ESRI Shapefile";
380 const char *pszLayer = nullptr;
381 GNMNetwork *poDS = nullptr;
382 OGRLayer* poResultLayer = nullptr;
383 char **papszDSCO = nullptr, **papszLCO = nullptr, **papszALO = nullptr;
384
385 operation stOper = op_unknown;
386
387 int nRet = 0;
388
389 // Check strict compilation and runtime library version as we use C++ API
390 if (! GDAL_CHECK_VERSION(papszArgv[0]))
391 exit(1);
392
393 EarlySetConfigOptions(nArgc, papszArgv);
394
395 /* -------------------------------------------------------------------- */
396 /* Register format(s). */
397 /* -------------------------------------------------------------------- */
398 GDALAllRegister();
399
400 /* -------------------------------------------------------------------- */
401 /* Processing command line arguments. */
402 /* -------------------------------------------------------------------- */
403 nArgc = GDALGeneralCmdLineProcessor( nArgc, &papszArgv, GDAL_OF_GNM );
404
405 if( nArgc < 1 )
406 {
407 exit( -nArgc );
408 }
409
410 for( int iArg = 1; iArg < nArgc; iArg++ )
411 {
412 if( EQUAL(papszArgv[1], "--utility_version") )
413 {
414 printf("%s was compiled against GDAL %s and is running against GDAL %s\n",
415 papszArgv[0], GDAL_RELEASE_NAME, GDALVersionInfo("RELEASE_NAME"));
416 CSLDestroy( papszArgv );
417 return 0;
418 }
419
420 else if( EQUAL(papszArgv[iArg],"--help") )
421 {
422 Usage();
423 }
424
425 else if ( EQUAL(papszArgv[iArg], "--long-usage") )
426 {
427 Usage(FALSE);
428 }
429
430 else if( EQUAL(papszArgv[iArg],"-q") || EQUAL(papszArgv[iArg],"-quiet") )
431 {
432 bQuiet = TRUE;
433 }
434
435 else if( EQUAL(papszArgv[iArg],"dijkstra") )
436 {
437 CHECK_HAS_ENOUGH_ADDITIONAL_ARGS(2);
438 stOper = op_dijkstra;
439 nFromFID = atoi(papszArgv[++iArg]);
440 nToFID = atoi(papszArgv[++iArg]);
441 }
442
443 else if( EQUAL(papszArgv[iArg],"kpaths") )
444 {
445 CHECK_HAS_ENOUGH_ADDITIONAL_ARGS(3);
446 stOper = op_kpaths;
447 nFromFID = atoi(papszArgv[++iArg]);
448 nToFID = atoi(papszArgv[++iArg]);
449 nK = atoi(papszArgv[++iArg]);
450 }
451
452 else if( EQUAL(papszArgv[iArg],"resource") )
453 {
454 stOper = op_resource;
455 }
456
457 else if( EQUAL(papszArgv[iArg],"-ds") )
458 {
459 CHECK_HAS_ENOUGH_ADDITIONAL_ARGS(1);
460 pszDataset = papszArgv[++iArg];
461 }
462
463 else if( (EQUAL(papszArgv[iArg],"-f") || EQUAL(papszArgv[iArg],"-of")) )
464 {
465 CHECK_HAS_ENOUGH_ADDITIONAL_ARGS(1);
466 pszFormat = papszArgv[++iArg];
467 }
468
469 else if( EQUAL(papszArgv[iArg],"-l") )
470 {
471 CHECK_HAS_ENOUGH_ADDITIONAL_ARGS(1);
472 pszLayer = papszArgv[++iArg];
473 }
474 else if( EQUAL(papszArgv[iArg],"-dsco") )
475 {
476 CHECK_HAS_ENOUGH_ADDITIONAL_ARGS(1);
477 papszDSCO = CSLAddString(papszDSCO, papszArgv[++iArg] );
478 }
479 else if( EQUAL(papszArgv[iArg],"-lco") )
480 {
481 CHECK_HAS_ENOUGH_ADDITIONAL_ARGS(1);
482 papszLCO = CSLAddString(papszLCO, papszArgv[++iArg] );
483 }
484 else if( EQUAL(papszArgv[iArg],"-alo") )
485 {
486 CHECK_HAS_ENOUGH_ADDITIONAL_ARGS(1);
487 papszALO = CSLAddString(papszALO, papszArgv[++iArg] );
488 }
489 else if( papszArgv[iArg][0] == '-' )
490 {
491 Usage(CPLSPrintf("Unknown option name '%s'", papszArgv[iArg]));
492 }
493
494 else if( pszDataSource == nullptr )
495 pszDataSource = papszArgv[iArg];
496 }
497
498 // do the work ////////////////////////////////////////////////////////////////
499
500 if(stOper == op_dijkstra)
501 {
502 if(pszDataSource == nullptr)
503 Usage("No network dataset provided");
504
505 if(nFromFID == -1 || nToFID == -1)
506 Usage("Invalid input from or to identificators");
507
508 // open
509 poDS = cpl::down_cast<GNMNetwork*>(static_cast<GDALDataset*>(GDALOpenEx( pszDataSource,
510 GDAL_OF_UPDATE | GDAL_OF_GNM, nullptr, nullptr, nullptr )));
511 if(nullptr == poDS)
512 {
513 fprintf( stderr, "\nFailed to open network at %s\n", pszDataSource);
514 nRet = 1;
515 goto exit;
516 }
517
518 poResultLayer = poDS->GetPath(nFromFID, nToFID, GATDijkstraShortestPath,
519 papszALO);
520 if(nullptr == pszDataset)
521 {
522 ReportOnLayer(poResultLayer, bQuiet == FALSE);
523 }
524 else
525 {
526 if(CreateAndFillOutputDataset(poResultLayer, pszDataset, pszFormat,
527 pszLayer, papszDSCO, papszLCO, bQuiet)
528 != OGRERR_NONE)
529 {
530 nRet = 1;
531 goto exit;
532 }
533 }
534 }
535 else if(stOper == op_kpaths)
536 {
537 if(pszDataSource == nullptr)
538 Usage("No network dataset provided");
539
540 if(nFromFID == -1 || nToFID == -1)
541 Usage("Invalid input from or to identificators");
542
543 // open
544 poDS = cpl::down_cast<GNMNetwork*>(static_cast<GDALDataset*>(GDALOpenEx( pszDataSource,
545 GDAL_OF_UPDATE | GDAL_OF_GNM, nullptr, nullptr, nullptr )));
546 if(nullptr == poDS)
547 {
548 fprintf( stderr, "\nFailed to open network at %s\n", pszDataSource);
549 nRet = 1;
550 goto exit;
551 }
552
553 if(CSLFindName(papszALO, GNM_MD_NUM_PATHS) == -1)
554 {
555 CPLDebug("GNM", "No K in options, add %d value", nK);
556 papszALO = CSLAddNameValue(papszALO, GNM_MD_NUM_PATHS,
557 CPLSPrintf("%d", nK));
558 }
559
560 poResultLayer = poDS->GetPath(nFromFID, nToFID, GATKShortestPath,
561 papszALO);
562
563 if(nullptr == pszDataset)
564 {
565 ReportOnLayer(poResultLayer, bQuiet == FALSE);
566 }
567 else
568 {
569 if(CreateAndFillOutputDataset(poResultLayer, pszDataset, pszFormat,
570 pszLayer, papszDSCO, papszLCO, bQuiet)
571 != OGRERR_NONE)
572 {
573 nRet = 1;
574 goto exit;
575 }
576 }
577 }
578 else if(stOper == op_resource)
579 {
580 if(pszDataSource == nullptr)
581 Usage("No network dataset provided");
582
583 // open
584 poDS = cpl::down_cast<GNMNetwork*>(static_cast<GDALDataset*>(GDALOpenEx( pszDataSource,
585 GDAL_OF_UPDATE | GDAL_OF_GNM, nullptr, nullptr, nullptr )));
586 if(nullptr == poDS)
587 {
588 fprintf( stderr, "\nFailed to open network at %s\n", pszDataSource);
589 nRet = 1;
590 goto exit;
591 }
592
593 poResultLayer = poDS->GetPath(nFromFID, nToFID, GATConnectedComponents,
594 papszALO);
595
596 if(nullptr == pszDataset)
597 {
598 ReportOnLayer(poResultLayer, bQuiet == FALSE);
599 }
600 else
601 {
602 if(CreateAndFillOutputDataset(poResultLayer, pszDataset, pszFormat,
603 pszLayer, papszDSCO, papszLCO, bQuiet)
604 != OGRERR_NONE)
605 {
606 nRet = 1;
607 goto exit;
608 }
609 }
610 }
611 else
612 {
613 printf("\nNeed an operation. See help what you can do with gnmanalyse:\n");
614 Usage();
615 }
616
617 exit:
618 CSLDestroy(papszDSCO);
619 CSLDestroy(papszLCO);
620 CSLDestroy(papszALO);
621 CSLDestroy( papszArgv );
622
623 if(poResultLayer != nullptr)
624 poDS->ReleaseResultSet(poResultLayer);
625
626 if( poDS != nullptr )
627 GDALClose(poDS);
628
629 GDALDestroyDriverManager();
630
631 return nRet;
632 }
633 MAIN_END
634