1 /******************************************************************************
2  *
3  * Project:  GDAL
4  * Purpose:  GDALGeorefPamDataset with helper to read georeferencing and other
5  *           metadata from JP2Boxes
6  * Author:   Even Rouault <even dot rouault at spatialys.com>
7  *
8  ******************************************************************************
9  * Copyright (c) 2002, Frank Warmerdam <warmerdam@pobox.com>
10  * Copyright (c) 2013, Even Rouault <even dot rouault at spatialys.com>
11  *
12  * Permission is hereby granted, free of charge, to any person obtaining a
13  * copy of this software and associated documentation files (the "Software"),
14  * to deal in the Software without restriction, including without limitation
15  * the rights to use, copy, modify, merge, publish, distribute, sublicense,
16  * and/or sell copies of the Software, and to permit persons to whom the
17  * Software is furnished to do so, subject to the following conditions:
18  *
19  * The above copyright notice and this permission notice shall be included
20  * in all copies or substantial portions of the Software.
21  *
22  * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS
23  * OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
24  * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL
25  * THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
26  * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
27  * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
28  * DEALINGS IN THE SOFTWARE.
29  ****************************************************************************/
30 
31 #include "cpl_port.h"
32 #include "gdaljp2abstractdataset.h"
33 
34 #include <cstring>
35 
36 #include <string>
37 
38 #include "cpl_conv.h"
39 #include "cpl_error.h"
40 #include "cpl_minixml.h"
41 #include "cpl_string.h"
42 #include "cpl_vsi.h"
43 #include "gdal.h"
44 #include "gdal_mdreader.h"
45 #include "gdal_priv.h"
46 #include "gdaljp2metadata.h"
47 #include "ogrsf_frmts.h"
48 
49 /*! @cond Doxygen_Suppress */
50 
51 /************************************************************************/
52 /*                     GDALJP2AbstractDataset()                         */
53 /************************************************************************/
54 
55 GDALJP2AbstractDataset::GDALJP2AbstractDataset() = default;
56 
57 /************************************************************************/
58 /*                     ~GDALJP2AbstractDataset()                        */
59 /************************************************************************/
60 
~GDALJP2AbstractDataset()61 GDALJP2AbstractDataset::~GDALJP2AbstractDataset()
62 {
63     CPLFree(pszWldFilename);
64     GDALJP2AbstractDataset::CloseDependentDatasets();
65     CSLDestroy(papszMetadataFiles);
66 }
67 
68 /************************************************************************/
69 /*                      CloseDependentDatasets()                        */
70 /************************************************************************/
71 
CloseDependentDatasets()72 int GDALJP2AbstractDataset::CloseDependentDatasets()
73 {
74     const bool bRet =
75         CPL_TO_BOOL( GDALGeorefPamDataset::CloseDependentDatasets() );
76     if( poMemDS == nullptr )
77       return bRet;
78 
79     GDALClose(poMemDS);
80     poMemDS = nullptr;
81     return true;
82 }
83 
84 /************************************************************************/
85 /*                          LoadJP2Metadata()                           */
86 /************************************************************************/
87 
LoadJP2Metadata(GDALOpenInfo * poOpenInfo,const char * pszOverrideFilenameIn)88 void GDALJP2AbstractDataset::LoadJP2Metadata(
89     GDALOpenInfo* poOpenInfo, const char* pszOverrideFilenameIn )
90 {
91     const char* pszOverrideFilename = pszOverrideFilenameIn;
92     if( pszOverrideFilename == nullptr )
93         pszOverrideFilename = poOpenInfo->pszFilename;
94 
95 /* -------------------------------------------------------------------- */
96 /*      Identify authorized georeferencing sources                      */
97 /* -------------------------------------------------------------------- */
98     const char* pszGeorefSourcesOption =
99         CSLFetchNameValue( poOpenInfo->papszOpenOptions, "GEOREF_SOURCES");
100     bool bGeorefSourcesConfigOption = pszGeorefSourcesOption != nullptr;
101     CPLString osGeorefSources = (pszGeorefSourcesOption) ?
102         pszGeorefSourcesOption :
103         CPLGetConfigOption("GDAL_GEOREF_SOURCES", "PAM,INTERNAL,WORLDFILE");
104     size_t nInternalIdx = osGeorefSources.ifind("INTERNAL");
105     // coverity[tainted_data]
106     if( nInternalIdx != std::string::npos &&
107         (nInternalIdx == 0 || osGeorefSources[nInternalIdx-1] == ',') &&
108         (nInternalIdx + strlen("INTERNAL") == osGeorefSources.size() ||
109          osGeorefSources[nInternalIdx+strlen("INTERNAL")] == ',') )
110     {
111         osGeorefSources.replace( nInternalIdx, strlen("INTERNAL"),
112                                  "GEOJP2,GMLJP2,MSIG" );
113     }
114     char** papszTokens = CSLTokenizeString2(osGeorefSources, ",", 0);
115     m_bGotPAMGeorefSrcIndex = true;
116     m_nPAMGeorefSrcIndex = CSLFindString(papszTokens, "PAM");
117     const int nGEOJP2Index = CSLFindString(papszTokens, "GEOJP2");
118     const int nGMLJP2Index = CSLFindString(papszTokens, "GMLJP2");
119     const int nMSIGIndex = CSLFindString(papszTokens, "MSIG");
120     m_nWORLDFILEIndex = CSLFindString(papszTokens, "WORLDFILE");
121 
122     if( bGeorefSourcesConfigOption )
123     {
124         for(char** papszIter = papszTokens; *papszIter; ++papszIter )
125         {
126             if( !EQUAL(*papszIter, "PAM") &&
127                 !EQUAL(*papszIter, "GEOJP2") &&
128                 !EQUAL(*papszIter, "GMLJP2") &&
129                 !EQUAL(*papszIter, "MSIG") &&
130                 !EQUAL(*papszIter, "WORLDFILE") &&
131                 !EQUAL(*papszIter, "NONE") )
132             {
133                 CPLError(CE_Warning, CPLE_AppDefined,
134                          "Unhandled value %s in GEOREF_SOURCES", *papszIter);
135             }
136         }
137     }
138     CSLDestroy(papszTokens);
139 
140 /* -------------------------------------------------------------------- */
141 /*      Check for georeferencing information.                           */
142 /* -------------------------------------------------------------------- */
143     GDALJP2Metadata oJP2Geo;
144     int nIndexUsed = -1;
145     if( ((poOpenInfo->fpL != nullptr && pszOverrideFilenameIn == nullptr &&
146          oJP2Geo.ReadAndParse(poOpenInfo->fpL, nGEOJP2Index, nGMLJP2Index,
147                               nMSIGIndex, &nIndexUsed) ) ||
148         (!(poOpenInfo->fpL != nullptr && pszOverrideFilenameIn == nullptr) &&
149          oJP2Geo.ReadAndParse( pszOverrideFilename, nGEOJP2Index, nGMLJP2Index,
150                                nMSIGIndex, m_nWORLDFILEIndex, &nIndexUsed ))) &&
151         (nGMLJP2Index >= 0 || nGEOJP2Index >= 0 || nMSIGIndex >= 0 ||
152          m_nWORLDFILEIndex >= 0) )
153     {
154         CPLFree(pszProjection);
155         pszProjection = CPLStrdup(oJP2Geo.pszProjection);
156         if( strlen(pszProjection) > 0 )
157             m_nProjectionGeorefSrcIndex = nIndexUsed;
158         bGeoTransformValid = CPL_TO_BOOL( oJP2Geo.bHaveGeoTransform );
159         if( bGeoTransformValid )
160             m_nGeoTransformGeorefSrcIndex = nIndexUsed;
161         memcpy( adfGeoTransform, oJP2Geo.adfGeoTransform,
162                 sizeof(double) * 6 );
163         nGCPCount = oJP2Geo.nGCPCount;
164         if( nGCPCount )
165             m_nGCPGeorefSrcIndex = nIndexUsed;
166         pasGCPList =
167             GDALDuplicateGCPs( oJP2Geo.nGCPCount, oJP2Geo.pasGCPList );
168 
169         if( oJP2Geo.bPixelIsPoint )
170         {
171             m_bPixelIsPoint = true;
172             m_nPixelIsPointGeorefSrcIndex = nIndexUsed;
173         }
174         if( oJP2Geo.papszRPCMD )
175         {
176             m_papszRPC = CSLDuplicate( oJP2Geo.papszRPCMD );
177             m_nRPCGeorefSrcIndex = nIndexUsed;
178         }
179     }
180 
181 /* -------------------------------------------------------------------- */
182 /*      Report XML UUID box in a dedicated metadata domain              */
183 /* -------------------------------------------------------------------- */
184     if( oJP2Geo.pszXMPMetadata )
185     {
186         char *apszMDList[2] = { oJP2Geo.pszXMPMetadata, nullptr };
187         GDALDataset::SetMetadata(apszMDList, "xml:XMP");
188     }
189 
190 /* -------------------------------------------------------------------- */
191 /*      Do we have any XML boxes we would like to treat as special      */
192 /*      domain metadata? (Note: the GDAL multidomain metadata XML box   */
193 /*      has been excluded and is dealt a few lines below.               */
194 /* -------------------------------------------------------------------- */
195 
196     for( int iBox = 0;
197          oJP2Geo.papszGMLMetadata
198              && oJP2Geo.papszGMLMetadata[iBox] != nullptr;
199          ++iBox )
200     {
201         char *pszName = nullptr;
202         const char *pszXML =
203             CPLParseNameValue( oJP2Geo.papszGMLMetadata[iBox],
204                                 &pszName );
205         CPLString osDomain;
206         osDomain.Printf( "xml:%s", pszName );
207         char *apszMDList[2] = { const_cast<char *>(pszXML), nullptr };
208 
209         GDALDataset::SetMetadata( apszMDList, osDomain );
210 
211         CPLFree( pszName );
212     }
213 
214 /* -------------------------------------------------------------------- */
215 /*      Do we have GDAL metadata?                                       */
216 /* -------------------------------------------------------------------- */
217     if( oJP2Geo.pszGDALMultiDomainMetadata != nullptr )
218     {
219         CPLErr eLastErr = CPLGetLastErrorType();
220         int nLastErrNo = CPLGetLastErrorNo();
221         CPLString osLastErrorMsg = CPLGetLastErrorMsg();
222         CPLXMLNode* psXMLNode =
223             CPLParseXMLString(oJP2Geo.pszGDALMultiDomainMetadata);
224         if( CPLGetLastErrorType() == CE_None && eLastErr != CE_None )
225             CPLErrorSetState( eLastErr, nLastErrNo, osLastErrorMsg.c_str() );
226 
227         if( psXMLNode )
228         {
229             GDALMultiDomainMetadata oLocalMDMD;
230             oLocalMDMD.XMLInit(psXMLNode, FALSE);
231             char** papszDomainList = oLocalMDMD.GetDomainList();
232             char** papszIter = papszDomainList;
233             GDALDataset::SetMetadata(oLocalMDMD.GetMetadata());
234             while( papszIter && *papszIter )
235             {
236                 if( !EQUAL(*papszIter, "") &&
237                     !EQUAL(*papszIter, "IMAGE_STRUCTURE") )
238                 {
239                     if( GDALDataset::GetMetadata(*papszIter) != nullptr )
240                     {
241                         CPLDebug(
242                             "GDALJP2",
243                             "GDAL metadata overrides metadata in %s domain "
244                             "over metadata read from other boxes",
245                             *papszIter );
246                     }
247                     GDALDataset::SetMetadata(
248                         oLocalMDMD.GetMetadata(*papszIter), *papszIter );
249                 }
250                 ++papszIter;
251             }
252             CPLDestroyXMLNode(psXMLNode);
253         }
254         else
255         {
256             CPLErrorReset();
257         }
258     }
259 
260 /* -------------------------------------------------------------------- */
261 /*      Do we have other misc metadata (from resd box for now) ?        */
262 /* -------------------------------------------------------------------- */
263     if( oJP2Geo.papszMetadata != nullptr )
264     {
265         char **papszMD = CSLDuplicate(GDALDataset::GetMetadata());
266 
267         papszMD = CSLMerge( papszMD, oJP2Geo.papszMetadata );
268         GDALDataset::SetMetadata( papszMD );
269 
270         CSLDestroy( papszMD );
271     }
272 
273 /* -------------------------------------------------------------------- */
274 /*      Do we have XML IPR ?                                            */
275 /* -------------------------------------------------------------------- */
276     if( oJP2Geo.pszXMLIPR != nullptr )
277     {
278         char* apszMD[2] = { oJP2Geo.pszXMLIPR, nullptr };
279         GDALDataset::SetMetadata( apszMD, "xml:IPR" );
280     }
281 
282 /* -------------------------------------------------------------------- */
283 /*      Check for world file.                                           */
284 /* -------------------------------------------------------------------- */
285     if( m_nWORLDFILEIndex >= 0 &&
286         ((bGeoTransformValid && m_nWORLDFILEIndex <
287                     m_nGeoTransformGeorefSrcIndex) || !bGeoTransformValid) )
288     {
289         bGeoTransformValid |=
290             GDALReadWorldFile2( pszOverrideFilename, nullptr,
291                                 adfGeoTransform,
292                                 poOpenInfo->GetSiblingFiles(), &pszWldFilename )
293             || GDALReadWorldFile2( pszOverrideFilename, ".wld",
294                                    adfGeoTransform,
295                                    poOpenInfo->GetSiblingFiles(),
296                                    &pszWldFilename );
297         if( bGeoTransformValid )
298         {
299             m_nGeoTransformGeorefSrcIndex = m_nWORLDFILEIndex;
300             m_bPixelIsPoint = false;
301             m_nPixelIsPointGeorefSrcIndex = -1;
302         }
303     }
304 
305     GDALMDReaderManager mdreadermanager;
306     GDALMDReaderBase* mdreader =
307         mdreadermanager.GetReader(poOpenInfo->pszFilename,
308                                   poOpenInfo->GetSiblingFiles(), MDR_ANY);
309     if(nullptr != mdreader)
310     {
311         mdreader->FillMetadata(&(oMDMD));
312         papszMetadataFiles = mdreader->GetMetadataFiles();
313     }
314 }
315 
316 /************************************************************************/
317 /*                            GetFileList()                             */
318 /************************************************************************/
319 
GetFileList()320 char **GDALJP2AbstractDataset::GetFileList()
321 
322 {
323     char **papszFileList = GDALGeorefPamDataset::GetFileList();
324 
325     if( pszWldFilename != nullptr &&
326         m_nGeoTransformGeorefSrcIndex == m_nWORLDFILEIndex &&
327         GDALCanReliablyUseSiblingFileList(pszWldFilename) &&
328         CSLFindString( papszFileList, pszWldFilename ) == -1 )
329     {
330         double l_adfGeoTransform[6];
331         GetGeoTransform(l_adfGeoTransform);
332         if( m_nGeoTransformGeorefSrcIndex == m_nWORLDFILEIndex )
333         {
334             papszFileList = CSLAddString( papszFileList, pszWldFilename );
335         }
336     }
337     if( papszMetadataFiles != nullptr )
338     {
339         for( int i = 0; papszMetadataFiles[i] != nullptr; ++i )
340         {
341             papszFileList =
342                 CSLAddString( papszFileList, papszMetadataFiles[i] );
343         }
344     }
345     return papszFileList;
346 }
347 
348 /************************************************************************/
349 /*                        LoadVectorLayers()                            */
350 /************************************************************************/
351 
LoadVectorLayers(int bOpenRemoteResources)352 void GDALJP2AbstractDataset::LoadVectorLayers( int bOpenRemoteResources )
353 {
354     char** papszGMLJP2 = GetMetadata("xml:gml.root-instance");
355     if( papszGMLJP2 == nullptr )
356         return;
357     GDALDriver * const poMemDriver =
358         static_cast<GDALDriver *>(GDALGetDriverByName("Memory"));
359     if( poMemDriver == nullptr )
360         return;
361 
362     CPLErr eLastErr = CPLGetLastErrorType();
363     int nLastErrNo = CPLGetLastErrorNo();
364     CPLString osLastErrorMsg = CPLGetLastErrorMsg();
365     CPLXMLNode* const psRoot = CPLParseXMLString(papszGMLJP2[0]);
366     if( CPLGetLastErrorType() == CE_None && eLastErr != CE_None )
367         CPLErrorSetState( eLastErr, nLastErrNo, osLastErrorMsg.c_str() );
368 
369     if( psRoot == nullptr )
370         return;
371     CPLXMLNode* const psCC =
372         CPLGetXMLNode(psRoot, "=gmljp2:GMLJP2CoverageCollection");
373     if( psCC == nullptr )
374     {
375         CPLDestroyXMLNode(psRoot);
376         return;
377     }
378 
379     // Find feature collections.
380     int nLayersAtCC = 0;
381     int nLayersAtGC = 0;
382     // CPLXMLNode* psCCChildIter = psCC->psChild;
383     for( CPLXMLNode* psCCChildIter = psCC->psChild;
384          psCCChildIter != nullptr;
385          psCCChildIter = psCCChildIter->psNext )
386     {
387         if( psCCChildIter->eType != CXT_Element ||
388             strcmp(psCCChildIter->pszValue, "gmljp2:featureMember") != 0 ||
389             psCCChildIter->psChild == nullptr ||
390             psCCChildIter->psChild->eType != CXT_Element )
391             continue;
392 
393         CPLXMLNode * const psGCorGMLJP2Features = psCCChildIter->psChild;
394         bool bIsGC =
395             strstr(psGCorGMLJP2Features->pszValue, "GridCoverage") != nullptr;
396 
397         for( CPLXMLNode *psGCorGMLJP2FeaturesChildIter =
398                  psGCorGMLJP2Features->psChild;
399              psGCorGMLJP2FeaturesChildIter != nullptr;
400              psGCorGMLJP2FeaturesChildIter =
401                  psGCorGMLJP2FeaturesChildIter->psNext )
402         {
403             if( psGCorGMLJP2FeaturesChildIter->eType != CXT_Element ||
404                 strcmp(psGCorGMLJP2FeaturesChildIter->pszValue,
405                        "gmljp2:feature") != 0 ||
406                 psGCorGMLJP2FeaturesChildIter->psChild == nullptr )
407                 continue;
408 
409             CPLXMLNode* psFC = nullptr;
410             bool bFreeFC = false;
411 
412             CPLXMLNode * const psChild = psGCorGMLJP2FeaturesChildIter->psChild;
413             if( psChild->eType == CXT_Attribute &&
414                 strcmp(psChild->pszValue, "xlink:href") == 0 &&
415                 STARTS_WITH(psChild->psChild->pszValue, "gmljp2://xml/") )
416             {
417                 const char * const pszBoxName =
418                     psChild->psChild->pszValue + strlen("gmljp2://xml/");
419                 char** papszBoxData =
420                     GetMetadata(CPLSPrintf("xml:%s", pszBoxName));
421                 if( papszBoxData != nullptr )
422                 {
423                     psFC = CPLParseXMLString(papszBoxData[0]);
424                     bFreeFC = true;
425                 }
426                 else
427                 {
428                     CPLDebug(
429                         "GMLJP2",
430                         "gmljp2:feature references %s, "
431                         "but no corresponding box found",
432                         psChild->psChild->pszValue);
433                 }
434             }
435 
436             CPLString osGMLTmpFile;
437             if( psChild->eType == CXT_Attribute &&
438                 strcmp(psChild->pszValue, "xlink:href") == 0 &&
439                 (STARTS_WITH(psChild->psChild->pszValue, "http://") ||
440                  STARTS_WITH(psChild->psChild->pszValue, "https://")) )
441             {
442                 if( !bOpenRemoteResources )
443                     CPLDebug(
444                         "GMLJP2",
445                         "Remote feature collection %s mentioned in GMLJP2 box",
446                         psChild->psChild->pszValue);
447                 else
448                     osGMLTmpFile =
449                         "/vsicurl/" + CPLString(psChild->psChild->pszValue);
450             }
451             else if( psChild->eType == CXT_Element &&
452                      strstr(psChild->pszValue, "FeatureCollection") != nullptr )
453             {
454                 psFC = psChild;
455             }
456 
457             if( psFC == nullptr && osGMLTmpFile.empty() )
458             {
459                 continue;
460             }
461 
462             if( psFC != nullptr )
463             {
464                 osGMLTmpFile = CPLSPrintf("/vsimem/gmljp2/%p/my.gml", this);
465                 // Create temporary .gml file.
466                 CPLSerializeXMLTreeToFile(psFC, osGMLTmpFile);
467             }
468 
469             CPLDebug("GMLJP2", "Found a FeatureCollection at %s level",
470                      bIsGC ? "GridCoverage" : "CoverageCollection");
471 
472             CPLString osXSDTmpFile;
473 
474             if( psFC )
475             {
476                 // Try to localize its .xsd schema in a GMLJP2 auxiliary box
477                 const char * const pszSchemaLocation =
478                     CPLGetXMLValue(psFC, "xsi:schemaLocation", nullptr);
479                 if( pszSchemaLocation )
480                 {
481                     char **papszTokens = CSLTokenizeString2(
482                             pszSchemaLocation, " \t\n",
483                             CSLT_HONOURSTRINGS | CSLT_STRIPLEADSPACES |
484                             CSLT_STRIPENDSPACES );
485 
486                     if( (CSLCount(papszTokens) % 2) == 0 )
487                     {
488                         for( char** papszIter = papszTokens;
489                              *papszIter != nullptr;
490                              papszIter += 2 )
491                         {
492                             if( STARTS_WITH(papszIter[1], "gmljp2://xml/") )
493                             {
494                                 const char* pszBoxName =
495                                     papszIter[1] + strlen("gmljp2://xml/");
496                                 char** papszBoxData =
497                                     GetMetadata(CPLSPrintf("xml:%s",
498                                                            pszBoxName));
499                                 if( papszBoxData != nullptr )
500                                 {
501                                     osXSDTmpFile =
502                                         CPLSPrintf("/vsimem/gmljp2/%p/my.xsd",
503                                                    this);
504                                     CPL_IGNORE_RET_VAL(VSIFCloseL(
505                                         VSIFileFromMemBuffer(
506                                             osXSDTmpFile,
507                                             reinterpret_cast<GByte *>(
508                                                 papszBoxData[0]),
509                                             strlen(papszBoxData[0]),
510                                             FALSE)));
511                                 }
512                                 else
513                                 {
514                                     CPLDebug(
515                                         "GMLJP2",
516                                         "Feature collection references %s, "
517                                         "but no corresponding box found",
518                                         papszIter[1] );
519                                 }
520                                 break;
521                             }
522                         }
523                     }
524                     CSLDestroy(papszTokens);
525                 }
526                 if( bFreeFC )
527                 {
528                     CPLDestroyXMLNode(psFC);
529                     psFC = nullptr;
530                 }
531             }
532 
533             GDALDriverH hDrv = GDALIdentifyDriver(osGMLTmpFile, nullptr);
534             GDALDriverH hGMLDrv = GDALGetDriverByName("GML");
535             if( hDrv != nullptr && hDrv == hGMLDrv )
536             {
537                 char* apszOpenOptions[2] = {
538                     const_cast<char *>( "FORCE_SRS_DETECTION=YES" ), nullptr };
539                 GDALDatasetUniquePtr poTmpDS(GDALDataset::Open(
540                                 osGMLTmpFile, GDAL_OF_VECTOR, nullptr,
541                                 apszOpenOptions, nullptr ));
542                 if( poTmpDS )
543                 {
544                     int nLayers = poTmpDS->GetLayerCount();
545                     for( int i = 0; i < nLayers; ++i )
546                     {
547                         if( poMemDS == nullptr )
548                             poMemDS =
549                                 poMemDriver->Create("", 0, 0, 0,
550                                                     GDT_Unknown, nullptr);
551                         OGRLayer* poSrcLyr = poTmpDS->GetLayer(i);
552                         const char* const pszLayerName = bIsGC ?
553                             CPLSPrintf("FC_GridCoverage_%d_%s",
554                                        ++nLayersAtGC, poSrcLyr->GetName()) :
555                             CPLSPrintf("FC_CoverageCollection_%d_%s",
556                                        ++nLayersAtCC, poSrcLyr->GetName());
557                         poMemDS->CopyLayer(poSrcLyr, pszLayerName, nullptr);
558                     }
559 
560                     // If there was no schema, a .gfs might have been generated.
561                     VSIUnlink(CPLSPrintf("/vsimem/gmljp2/%p/my.gfs", this));
562                 }
563             }
564             else
565             {
566                 CPLDebug(
567                     "GMLJP2",
568                     "No GML driver found to read feature collection" );
569             }
570 
571             if( !STARTS_WITH(osGMLTmpFile, "/vsicurl/") )
572                 VSIUnlink(osGMLTmpFile);
573             if( !osXSDTmpFile.empty() )
574                 VSIUnlink(osXSDTmpFile);
575         }
576     }
577 
578     // Find annotations
579     int nAnnotations = 0;
580     for( CPLXMLNode* psCCChildIter = psCC->psChild;
581          psCCChildIter != nullptr;
582          psCCChildIter = psCCChildIter->psNext )
583     {
584         if( psCCChildIter->eType != CXT_Element ||
585             strcmp(psCCChildIter->pszValue, "gmljp2:featureMember") != 0 ||
586             psCCChildIter->psChild == nullptr ||
587             psCCChildIter->psChild->eType != CXT_Element )
588             continue;
589         CPLXMLNode * const psGCorGMLJP2Features = psCCChildIter->psChild;
590         bool bIsGC =
591             strstr(psGCorGMLJP2Features->pszValue, "GridCoverage") != nullptr;
592         if( !bIsGC )
593             continue;
594         for( CPLXMLNode* psGCorGMLJP2FeaturesChildIter =
595                  psGCorGMLJP2Features->psChild;
596              psGCorGMLJP2FeaturesChildIter != nullptr;
597              psGCorGMLJP2FeaturesChildIter =
598                  psGCorGMLJP2FeaturesChildIter->psNext )
599         {
600             if( psGCorGMLJP2FeaturesChildIter->eType != CXT_Element ||
601                 strcmp(psGCorGMLJP2FeaturesChildIter->pszValue,
602                        "gmljp2:annotation") != 0 ||
603                 psGCorGMLJP2FeaturesChildIter->psChild == nullptr ||
604                 psGCorGMLJP2FeaturesChildIter->psChild->eType != CXT_Element ||
605                 strstr(psGCorGMLJP2FeaturesChildIter->psChild->pszValue,
606                        "kml") == nullptr )
607                 continue;
608 
609             CPLDebug("GMLJP2", "Found a KML annotation");
610 
611             // Create temporary .kml file.
612             CPLXMLNode* const psKML = psGCorGMLJP2FeaturesChildIter->psChild;
613             CPLString osKMLTmpFile(
614                 CPLSPrintf("/vsimem/gmljp2/%p/my.kml", this) );
615             CPLSerializeXMLTreeToFile(psKML, osKMLTmpFile);
616 
617             GDALDatasetUniquePtr poTmpDS(GDALDataset::Open(
618                 osKMLTmpFile, GDAL_OF_VECTOR, nullptr, nullptr, nullptr ));
619             if( poTmpDS )
620             {
621                 int nLayers = poTmpDS->GetLayerCount();
622                 for( int i = 0; i < nLayers; ++i )
623                 {
624                     if( poMemDS == nullptr )
625                         poMemDS =
626                             poMemDriver->Create("", 0, 0, 0, GDT_Unknown, nullptr);
627                     OGRLayer* const poSrcLyr = poTmpDS->GetLayer(i);
628                     const char* pszLayerName =
629                         CPLSPrintf("Annotation_%d_%s",
630                                    ++nAnnotations, poSrcLyr->GetName());
631                     poMemDS->CopyLayer(poSrcLyr, pszLayerName, nullptr);
632                 }
633             }
634             else
635             {
636                 CPLDebug(
637                     "GMLJP2", "No KML/LIBKML driver found to read annotation" );
638             }
639 
640             VSIUnlink(osKMLTmpFile);
641         }
642     }
643 
644     CPLDestroyXMLNode(psRoot);
645 }
646 
647 /************************************************************************/
648 /*                           GetLayerCount()                            */
649 /************************************************************************/
650 
GetLayerCount()651 int GDALJP2AbstractDataset::GetLayerCount()
652 {
653     return poMemDS != nullptr ? poMemDS->GetLayerCount() : 0;
654 }
655 
656 /************************************************************************/
657 /*                             GetLayer()                               */
658 /************************************************************************/
659 
GetLayer(int i)660 OGRLayer* GDALJP2AbstractDataset::GetLayer( int i )
661 {
662     return poMemDS != nullptr ? poMemDS->GetLayer(i) : nullptr;
663 }
664 
665 /*! @endcond */
666