1 
2 /******************************************************************************
3  *
4  * Project:  GDAL
5  * Purpose:  GDALJP2Metadata - Read GeoTIFF and/or GML georef info.
6  * Author:   Frank Warmerdam, warmerdam@pobox.com
7  *           Even Rouault <even dot rouault at spatialys dot com>
8  *
9  ******************************************************************************
10  * Copyright (c) 2005, Frank Warmerdam <warmerdam@pobox.com>
11  * Copyright (c) 2010-2015, Even Rouault <even dot rouault at spatialys dot com>
12  * Copyright (c) 2015, European Union Satellite Centre
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 "cpl_port.h"
34 #include "gdaljp2metadata.h"
35 #include "gdaljp2metadatagenerator.h"
36 
37 #include <cmath>
38 #include <cstddef>
39 #include <cstdlib>
40 #include <cstring>
41 #if HAVE_FCNTL_H
42 #  include <fcntl.h>
43 #endif
44 
45 #include <algorithm>
46 #include <memory>
47 #include <set>
48 #include <string>
49 #include <vector>
50 
51 #include "cpl_error.h"
52 #include "cpl_string.h"
53 #include "cpl_minixml.h"
54 #include "gdaljp2metadatagenerator.h"
55 #include "gt_wkt_srs_for_gdal.h"
56 #include "ogr_api.h"
57 #include "ogr_core.h"
58 #include "ogr_geometry.h"
59 #include "ogr_spatialref.h"
60 #include "ogrgeojsonreader.h"
61 
62 /*! @cond Doxygen_Suppress */
63 
64 CPL_CVSID("$Id: gdaljp2metadata.cpp 43089a69cc6e35392c8be6316aa931a7d3c59def 2021-06-08 23:43:42 +0200 Even Rouault $")
65 
66 static const unsigned char msi_uuid2[16] = {
67     0xb1,0x4b,0xf8,0xbd,0x08,0x3d,0x4b,0x43,
68     0xa5,0xae,0x8c,0xd7,0xd5,0xa6,0xce,0x03 };
69 
70 static const unsigned char msig_uuid[16] = {
71     0x96,0xA9,0xF1,0xF1,0xDC,0x98,0x40,0x2D,
72     0xA7,0xAE,0xD6,0x8E,0x34,0x45,0x18,0x09 };
73 
74 static const unsigned char xmp_uuid[16] = {
75     0xBE,0x7A,0xCF,0xCB,0x97,0xA9,0x42,0xE8,
76     0x9C,0x71,0x99,0x94,0x91,0xE3,0xAF,0xAC };
77 
78 struct _GDALJP2GeoTIFFBox
79 {
80     int    nGeoTIFFSize;
81     GByte  *pabyGeoTIFFData;
82 };
83 
84 constexpr int MAX_JP2GEOTIFF_BOXES = 2;
85 
86 /************************************************************************/
87 /*                          GDALJP2Metadata()                           */
88 /************************************************************************/
89 
GDALJP2Metadata()90 GDALJP2Metadata::GDALJP2Metadata() :
91     nGeoTIFFBoxesCount(0),
92     pasGeoTIFFBoxes(nullptr),
93     nMSIGSize(0),
94     pabyMSIGData(nullptr),
95     papszGMLMetadata(nullptr),
96     bHaveGeoTransform(false),
97     adfGeoTransform{0.0, 1.0, 0.0, 0.0, 0.0, 1.0},
98     bPixelIsPoint(false),
99     pszProjection(nullptr),
100     nGCPCount(0),
101     pasGCPList(nullptr),
102     papszRPCMD(nullptr),
103     papszMetadata(nullptr),
104     pszXMPMetadata(nullptr),
105     pszGDALMultiDomainMetadata(nullptr),
106     pszXMLIPR(nullptr)
107 {
108 }
109 
110 /************************************************************************/
111 /*                          ~GDALJP2Metadata()                          */
112 /************************************************************************/
113 
~GDALJP2Metadata()114 GDALJP2Metadata::~GDALJP2Metadata()
115 
116 {
117     CPLFree( pszProjection );
118     if( nGCPCount > 0 )
119     {
120         GDALDeinitGCPs( nGCPCount, pasGCPList );
121         CPLFree( pasGCPList );
122     }
123     CSLDestroy(papszRPCMD);
124 
125     for( int i = 0; i < nGeoTIFFBoxesCount; ++i )
126     {
127         CPLFree( pasGeoTIFFBoxes[i].pabyGeoTIFFData );
128     }
129     CPLFree( pasGeoTIFFBoxes );
130     CPLFree( pabyMSIGData );
131     CSLDestroy( papszGMLMetadata );
132     CSLDestroy( papszMetadata );
133     CPLFree( pszXMPMetadata );
134     CPLFree( pszGDALMultiDomainMetadata );
135     CPLFree( pszXMLIPR );
136 }
137 
138 /************************************************************************/
139 /*                            ReadAndParse()                            */
140 /*                                                                      */
141 /*      Read a JP2 file and try to collect georeferencing               */
142 /*      information from the various available forms.  Returns TRUE     */
143 /*      if anything useful is found.                                    */
144 /************************************************************************/
145 
ReadAndParse(const char * pszFilename,int nGEOJP2Index,int nGMLJP2Index,int nMSIGIndex,int nWorldFileIndex,int * pnIndexUsed)146 int GDALJP2Metadata::ReadAndParse( const char *pszFilename, int nGEOJP2Index,
147                                    int nGMLJP2Index, int nMSIGIndex,
148                                    int nWorldFileIndex, int *pnIndexUsed  )
149 
150 {
151     VSILFILE *fpLL = VSIFOpenL( pszFilename, "rb" );
152     if( fpLL == nullptr )
153     {
154         CPLDebug( "GDALJP2Metadata", "Could not even open %s.",
155                   pszFilename );
156 
157         return FALSE;
158     }
159 
160     int nIndexUsed = -1;
161     bool bRet = CPL_TO_BOOL(ReadAndParse( fpLL, nGEOJP2Index, nGMLJP2Index,
162                                           nMSIGIndex, &nIndexUsed ));
163     CPL_IGNORE_RET_VAL(VSIFCloseL( fpLL ));
164 
165 /* -------------------------------------------------------------------- */
166 /*      If we still don't have a geotransform, look for a world         */
167 /*      file.                                                           */
168 /* -------------------------------------------------------------------- */
169     if( nWorldFileIndex >= 0 &&
170         ((bHaveGeoTransform && nWorldFileIndex < nIndexUsed) ||
171          !bHaveGeoTransform) )
172     {
173         bHaveGeoTransform = CPL_TO_BOOL(
174             GDALReadWorldFile( pszFilename, nullptr, adfGeoTransform )
175             || GDALReadWorldFile( pszFilename, ".wld", adfGeoTransform ) );
176         bRet |= bHaveGeoTransform;
177     }
178 
179     if( pnIndexUsed )
180         *pnIndexUsed = nIndexUsed;
181 
182     return bRet;
183 }
184 
ReadAndParse(VSILFILE * fpLL,int nGEOJP2Index,int nGMLJP2Index,int nMSIGIndex,int * pnIndexUsed)185 int GDALJP2Metadata::ReadAndParse( VSILFILE *fpLL, int nGEOJP2Index,
186                                    int nGMLJP2Index, int nMSIGIndex,
187                                    int *pnIndexUsed )
188 
189 {
190     ReadBoxes( fpLL );
191 
192 /* -------------------------------------------------------------------- */
193 /*      Try JP2GeoTIFF, GML and finally MSIG in specified order.        */
194 /* -------------------------------------------------------------------- */
195     std::set<int> aoSetPriorities;
196     if( nGEOJP2Index >= 0 ) aoSetPriorities.insert(nGEOJP2Index);
197     if( nGMLJP2Index >= 0 ) aoSetPriorities.insert(nGMLJP2Index);
198     if( nMSIGIndex >= 0 ) aoSetPriorities.insert(nMSIGIndex);
199     std::set<int>::iterator oIter = aoSetPriorities.begin();
200     for( ; oIter != aoSetPriorities.end(); ++oIter )
201     {
202         int nIndex = *oIter;
203         if( (nIndex == nGEOJP2Index && ParseJP2GeoTIFF()) ||
204             (nIndex == nGMLJP2Index && ParseGMLCoverageDesc()) ||
205             (nIndex == nMSIGIndex && ParseMSIG() ) )
206         {
207             if( pnIndexUsed )
208                 *pnIndexUsed = nIndex;
209             break;
210         }
211     }
212 
213 /* -------------------------------------------------------------------- */
214 /*      Return success either either of projection or geotransform      */
215 /*      or gcps.                                                        */
216 /* -------------------------------------------------------------------- */
217     return bHaveGeoTransform
218         || nGCPCount > 0
219         || (pszProjection != nullptr && strlen(pszProjection) > 0)
220         || papszRPCMD != nullptr;
221 }
222 
223 /************************************************************************/
224 /*                           CollectGMLData()                           */
225 /*                                                                      */
226 /*      Read all the asoc boxes after this node, and store the          */
227 /*      contain xml documents along with the name from the label.       */
228 /************************************************************************/
229 
CollectGMLData(GDALJP2Box * poGMLData)230 void GDALJP2Metadata::CollectGMLData( GDALJP2Box *poGMLData )
231 
232 {
233     GDALJP2Box oChildBox( poGMLData->GetFILE() );
234 
235     if( !oChildBox.ReadFirstChild( poGMLData ) )
236         return;
237 
238     while( strlen(oChildBox.GetType()) > 0 )
239     {
240         if( EQUAL(oChildBox.GetType(),"asoc") )
241         {
242             GDALJP2Box oSubChildBox( oChildBox.GetFILE() );
243 
244             if( !oSubChildBox.ReadFirstChild( &oChildBox ) )
245                 break;
246 
247             char *pszLabel = nullptr;
248             char *pszXML = nullptr;
249 
250             while( strlen(oSubChildBox.GetType()) > 0 )
251             {
252                 if( EQUAL(oSubChildBox.GetType(),"lbl ") )
253                     pszLabel = reinterpret_cast<char *>(oSubChildBox.ReadBoxData());
254                 else if( EQUAL(oSubChildBox.GetType(),"xml ") )
255                 {
256                     pszXML =
257                         reinterpret_cast<char *>( oSubChildBox.ReadBoxData() );
258                     GIntBig nXMLLength = oSubChildBox.GetDataLength();
259 
260                     // Some GML data contains \0 instead of \n.
261                     // See http://trac.osgeo.org/gdal/ticket/5760
262                     // TODO(schwehr): Explain the numbers in the next line.
263                     if( pszXML != nullptr && nXMLLength < 100 * 1024 * 1024 )
264                     {
265                         // coverity[tainted_data].
266                         for( GIntBig i = nXMLLength - 1; i >= 0; --i )
267                         {
268                             if( pszXML[i] == '\0' )
269                                 --nXMLLength;
270                             else
271                                 break;
272                         }
273                         // coverity[tainted_data]
274                         GIntBig i = 0;  // Used after for.
275                         for( ; i < nXMLLength; ++i )
276                         {
277                             if( pszXML[i] == '\0' )
278                                 break;
279                         }
280                         if( i < nXMLLength )
281                         {
282                             CPLPushErrorHandler(CPLQuietErrorHandler);
283                             CPLXMLTreeCloser psNode(CPLParseXMLString(pszXML));
284                             CPLPopErrorHandler();
285                             if( psNode == nullptr )
286                             {
287                                 CPLDebug(
288                                     "GMLJP2",
289                                     "GMLJP2 data contains nul characters "
290                                     "inside content. Replacing them by \\n");
291                                 // coverity[tainted_data]
292                                 for( GIntBig j = 0; j < nXMLLength; ++j )
293                                 {
294                                     if( pszXML[j] == '\0' )
295                                         pszXML[j] = '\n';
296                                 }
297                             }
298                         }
299                     }
300                 }
301 
302                 if( !oSubChildBox.ReadNextChild( &oChildBox ) )
303                     break;
304             }
305 
306             if( pszLabel != nullptr && pszXML != nullptr )
307             {
308                 papszGMLMetadata = CSLSetNameValue( papszGMLMetadata,
309                                                     pszLabel, pszXML );
310 
311                 if( strcmp(pszLabel, "gml.root-instance") == 0 &&
312                     pszGDALMultiDomainMetadata == nullptr &&
313                     strstr(pszXML, "GDALMultiDomainMetadata") != nullptr )
314                 {
315                     CPLXMLTreeCloser psTree(CPLParseXMLString(pszXML));
316                     if( psTree != nullptr )
317                     {
318                         CPLXMLNode* psGDALMDMD =
319                             CPLSearchXMLNode(psTree.get(), "GDALMultiDomainMetadata");
320                         if( psGDALMDMD )
321                             pszGDALMultiDomainMetadata =
322                                 CPLSerializeXMLTree(psGDALMDMD);
323                     }
324                 }
325             }
326 
327             CPLFree( pszLabel );
328             CPLFree( pszXML );
329         }
330 
331         if( !oChildBox.ReadNextChild( poGMLData ) )
332             break;
333     }
334 }
335 
336 /************************************************************************/
337 /*                             ReadBoxes()                              */
338 /************************************************************************/
339 
ReadBoxes(VSILFILE * fpVSIL)340 int GDALJP2Metadata::ReadBoxes( VSILFILE *fpVSIL )
341 
342 {
343     GDALJP2Box oBox( fpVSIL );
344 
345     if (!oBox.ReadFirst())
346         return FALSE;
347 
348     int iBox = 0;
349     while( strlen(oBox.GetType()) > 0 )
350     {
351 #ifdef DEBUG
352         if (CPLTestBool(CPLGetConfigOption("DUMP_JP2_BOXES", "NO")))
353             oBox.DumpReadable(stderr);
354 #endif
355 
356 /* -------------------------------------------------------------------- */
357 /*      Collect geotiff box.                                            */
358 /* -------------------------------------------------------------------- */
359         if( EQUAL(oBox.GetType(),"uuid")
360             && memcmp( oBox.GetUUID(), msi_uuid2, 16 ) == 0 )
361         {
362             // Erdas JPEG2000 files sometimes contain 2 GeoTIFF UUID boxes. One
363             // that is correct, another one that does not contain correct
364             // georeferencing. Fetch at most 2 of them for later analysis.
365             if( nGeoTIFFBoxesCount == MAX_JP2GEOTIFF_BOXES )
366             {
367                 CPLDebug( "GDALJP2",
368                           "Too many UUID GeoTIFF boxes. Ignoring this one" );
369             }
370             else
371             {
372                 const int nGeoTIFFSize =
373                     static_cast<int>( oBox.GetDataLength() );
374                 GByte* pabyGeoTIFFData = oBox.ReadBoxData();
375                 if( pabyGeoTIFFData == nullptr )
376                 {
377                     CPLDebug( "GDALJP2",
378                               "Cannot read data for UUID GeoTIFF box" );
379                 }
380                 else
381                 {
382                     pasGeoTIFFBoxes = static_cast<GDALJP2GeoTIFFBox *>(
383                         CPLRealloc(
384                             pasGeoTIFFBoxes,
385                             sizeof(GDALJP2GeoTIFFBox) *
386                                 (nGeoTIFFBoxesCount + 1) ) );
387                     pasGeoTIFFBoxes[nGeoTIFFBoxesCount].nGeoTIFFSize =
388                         nGeoTIFFSize;
389                     pasGeoTIFFBoxes[nGeoTIFFBoxesCount].pabyGeoTIFFData =
390                         pabyGeoTIFFData;
391                     ++nGeoTIFFBoxesCount;
392                 }
393             }
394         }
395 
396 /* -------------------------------------------------------------------- */
397 /*      Collect MSIG box.                                               */
398 /* -------------------------------------------------------------------- */
399         if( EQUAL(oBox.GetType(),"uuid")
400             && memcmp( oBox.GetUUID(), msig_uuid, 16 ) == 0 )
401         {
402             if( nMSIGSize == 0 )
403             {
404                 nMSIGSize = static_cast<int>( oBox.GetDataLength() );
405                 pabyMSIGData = oBox.ReadBoxData();
406 
407                 if( nMSIGSize < 70
408                     || pabyMSIGData == nullptr
409                     || memcmp( pabyMSIGData, "MSIG/", 5 ) != 0 )
410                 {
411                     CPLFree( pabyMSIGData );
412                     pabyMSIGData = nullptr;
413                     nMSIGSize = 0;
414                 }
415             }
416             else
417             {
418                 CPLDebug("GDALJP2", "Too many UUID MSIG boxes. Ignoring this one");
419             }
420         }
421 
422 /* -------------------------------------------------------------------- */
423 /*      Collect XMP box.                                                */
424 /* -------------------------------------------------------------------- */
425         if( EQUAL(oBox.GetType(),"uuid")
426             && memcmp( oBox.GetUUID(), xmp_uuid, 16 ) == 0 )
427         {
428             if( pszXMPMetadata == nullptr )
429             {
430                 pszXMPMetadata = reinterpret_cast<char *>(oBox.ReadBoxData());
431             }
432             else
433             {
434                 CPLDebug("GDALJP2", "Too many UUID XMP boxes. Ignoring this one");
435             }
436         }
437 
438 /* -------------------------------------------------------------------- */
439 /*      Process asoc box looking for Labelled GML data.                 */
440 /* -------------------------------------------------------------------- */
441         if( EQUAL(oBox.GetType(),"asoc") )
442         {
443             GDALJP2Box oSubBox( fpVSIL );
444 
445             if( oSubBox.ReadFirstChild( &oBox ) &&
446                 EQUAL(oSubBox.GetType(),"lbl ") )
447             {
448                 char *pszLabel = reinterpret_cast<char *>(oSubBox.ReadBoxData());
449                 if( pszLabel != nullptr && EQUAL(pszLabel,"gml.data") )
450                 {
451                     CollectGMLData( &oBox );
452                 }
453                 CPLFree( pszLabel );
454             }
455         }
456 
457 /* -------------------------------------------------------------------- */
458 /*      Process simple xml boxes.                                       */
459 /* -------------------------------------------------------------------- */
460         if( EQUAL(oBox.GetType(),"xml ") )
461         {
462             CPLString osBoxName;
463 
464             char *pszXML = reinterpret_cast<char *>(oBox.ReadBoxData());
465             if( pszXML != nullptr &&
466                 STARTS_WITH(pszXML, "<GDALMultiDomainMetadata>") )
467             {
468                 if( pszGDALMultiDomainMetadata == nullptr )
469                 {
470                     pszGDALMultiDomainMetadata = pszXML;
471                     pszXML = nullptr;
472                 }
473                 else
474                 {
475                     CPLDebug(
476                         "GDALJP2",
477                         "Too many GDAL metadata boxes. Ignoring this one");
478                 }
479             }
480             else if( pszXML != nullptr )
481             {
482                 osBoxName.Printf( "BOX_%d", iBox++ );
483 
484                 papszGMLMetadata = CSLSetNameValue( papszGMLMetadata,
485                                                     osBoxName, pszXML );
486             }
487             CPLFree( pszXML );
488         }
489 
490 /* -------------------------------------------------------------------- */
491 /*      Check for a resd box in jp2h.                                   */
492 /* -------------------------------------------------------------------- */
493         if( EQUAL(oBox.GetType(),"jp2h") )
494         {
495             GDALJP2Box oSubBox( fpVSIL );
496 
497             for( oSubBox.ReadFirstChild( &oBox );
498                  strlen(oSubBox.GetType()) > 0;
499                  oSubBox.ReadNextChild( &oBox ) )
500             {
501                 if( EQUAL(oSubBox.GetType(),"res ") )
502                 {
503                     GDALJP2Box oResBox( fpVSIL );
504 
505                     oResBox.ReadFirstChild( &oSubBox );
506 
507                     // We will use either the resd or resc box, which ever
508                     // happens to be first.  Should we prefer resd?
509                     unsigned char *pabyResData = nullptr;
510                     if( oResBox.GetDataLength() == 10 &&
511                         (pabyResData = oResBox.ReadBoxData()) != nullptr )
512                     {
513                         int nVertNum, nVertDen, nVertExp;
514                         int nHorzNum, nHorzDen, nHorzExp;
515 
516                         nVertNum = pabyResData[0] * 256 + pabyResData[1];
517                         nVertDen = pabyResData[2] * 256 + pabyResData[3];
518                         nHorzNum = pabyResData[4] * 256 + pabyResData[5];
519                         nHorzDen = pabyResData[6] * 256 + pabyResData[7];
520                         nVertExp = pabyResData[8];
521                         nHorzExp = pabyResData[9];
522 
523                         // compute in pixels/cm
524                         const double dfVertRes =
525                             (nVertNum / static_cast<double>(nVertDen)) *
526                             pow(10.0, nVertExp) / 100;
527                         const double dfHorzRes =
528                             (nHorzNum / static_cast<double>(nHorzDen)) *
529                             pow(10.0,nHorzExp)/100;
530                         CPLString osFormatter;
531 
532                         papszMetadata = CSLSetNameValue(
533                             papszMetadata,
534                             "TIFFTAG_XRESOLUTION",
535                             osFormatter.Printf("%g",dfHorzRes) );
536 
537                         papszMetadata = CSLSetNameValue(
538                             papszMetadata,
539                             "TIFFTAG_YRESOLUTION",
540                             osFormatter.Printf("%g",dfVertRes) );
541                         papszMetadata = CSLSetNameValue(
542                             papszMetadata,
543                             "TIFFTAG_RESOLUTIONUNIT",
544                             "3 (pixels/cm)" );
545 
546                         CPLFree( pabyResData );
547                     }
548                 }
549             }
550         }
551 
552 /* -------------------------------------------------------------------- */
553 /*      Collect IPR box.                                                */
554 /* -------------------------------------------------------------------- */
555         if( EQUAL(oBox.GetType(),"jp2i") )
556         {
557             if( pszXMLIPR == nullptr )
558             {
559                 pszXMLIPR = reinterpret_cast<char*>(oBox.ReadBoxData());
560                 CPLXMLTreeCloser psNode(CPLParseXMLString(pszXMLIPR));
561                 if( psNode == nullptr )
562                 {
563                     CPLFree(pszXMLIPR);
564                     pszXMLIPR = nullptr;
565                 }
566             }
567             else
568             {
569                 CPLDebug("GDALJP2", "Too many IPR boxes. Ignoring this one");
570             }
571         }
572 
573         if (!oBox.ReadNext())
574             break;
575     }
576 
577     return TRUE;
578 }
579 
580 /************************************************************************/
581 /*                          ParseJP2GeoTIFF()                           */
582 /************************************************************************/
583 
ParseJP2GeoTIFF()584 int GDALJP2Metadata::ParseJP2GeoTIFF()
585 
586 {
587     if(! CPLTestBool(CPLGetConfigOption("GDAL_USE_GEOJP2", "TRUE")) )
588         return FALSE;
589 
590     bool abValidProjInfo[MAX_JP2GEOTIFF_BOXES] = { false };
591     char* apszProjection[MAX_JP2GEOTIFF_BOXES] = { nullptr };
592     double aadfGeoTransform[MAX_JP2GEOTIFF_BOXES][6];
593     int anGCPCount[MAX_JP2GEOTIFF_BOXES] = { 0 };
594     GDAL_GCP    *apasGCPList[MAX_JP2GEOTIFF_BOXES] = { nullptr };
595     int abPixelIsPoint[MAX_JP2GEOTIFF_BOXES] = { 0 };
596     char** apapszRPCMD[MAX_JP2GEOTIFF_BOXES] = { nullptr };
597 
598     const int nMax = std::min(nGeoTIFFBoxesCount, MAX_JP2GEOTIFF_BOXES);
599     for( int i = 0; i < nMax; ++i )
600     {
601     /* -------------------------------------------------------------------- */
602     /*      Convert raw data into projection and geotransform.              */
603     /* -------------------------------------------------------------------- */
604         aadfGeoTransform[i][0] = 0;
605         aadfGeoTransform[i][1] = 1;
606         aadfGeoTransform[i][2] = 0;
607         aadfGeoTransform[i][3] = 0;
608         aadfGeoTransform[i][4] = 0;
609         aadfGeoTransform[i][5] = 1;
610         if( GTIFWktFromMemBufEx( pasGeoTIFFBoxes[i].nGeoTIFFSize,
611                                pasGeoTIFFBoxes[i].pabyGeoTIFFData,
612                                &apszProjection[i], aadfGeoTransform[i],
613                                &anGCPCount[i], &apasGCPList[i],
614                                &abPixelIsPoint[i], &apapszRPCMD[i] ) == CE_None )
615         {
616             if( apszProjection[i] != nullptr && strlen(apszProjection[i]) != 0 )
617                 abValidProjInfo[i] = true;
618         }
619     }
620 
621     // Detect which box is the better one.
622     int iBestIndex = -1;
623     for( int i = 0; i < nMax; ++i )
624     {
625         if( abValidProjInfo[i] && iBestIndex < 0 )
626         {
627             iBestIndex = i;
628         }
629         else if( abValidProjInfo[i] && apszProjection[i] != nullptr )
630         {
631             // Anything else than a LOCAL_CS will probably be better.
632             if( STARTS_WITH_CI(apszProjection[iBestIndex], "LOCAL_CS") )
633                 iBestIndex = i;
634         }
635     }
636 
637     if( iBestIndex < 0 )
638     {
639         for( int i = 0; i < nMax; ++i )
640         {
641             if( aadfGeoTransform[i][0] != 0
642                 || aadfGeoTransform[i][1] != 1
643                 || aadfGeoTransform[i][2] != 0
644                 || aadfGeoTransform[i][3] != 0
645                 || aadfGeoTransform[i][4] != 0
646                 || aadfGeoTransform[i][5] != 1
647                 || anGCPCount[i] > 0
648                 || apapszRPCMD[i] != nullptr )
649             {
650                 iBestIndex = i;
651             }
652         }
653     }
654 
655     if( iBestIndex >= 0 )
656     {
657         pszProjection = apszProjection[iBestIndex];
658         memcpy(adfGeoTransform, aadfGeoTransform[iBestIndex], 6 * sizeof(double));
659         nGCPCount = anGCPCount[iBestIndex];
660         pasGCPList = apasGCPList[iBestIndex];
661         bPixelIsPoint = CPL_TO_BOOL(abPixelIsPoint[iBestIndex]);
662         papszRPCMD = apapszRPCMD[iBestIndex];
663 
664         if( adfGeoTransform[0] != 0
665             || adfGeoTransform[1] != 1
666             || adfGeoTransform[2] != 0
667             || adfGeoTransform[3] != 0
668             || adfGeoTransform[4] != 0
669             || adfGeoTransform[5] != 1 )
670             bHaveGeoTransform = true;
671 
672         if( pszProjection )
673             CPLDebug( "GDALJP2Metadata",
674                 "Got projection from GeoJP2 (geotiff) box (%d): %s",
675                 iBestIndex, pszProjection );
676     }
677 
678     // Cleanup unused boxes.
679     for( int i = 0; i < nMax; ++i )
680     {
681         if( i != iBestIndex )
682         {
683             CPLFree( apszProjection[i] );
684             if( anGCPCount[i] > 0 )
685             {
686                 GDALDeinitGCPs( anGCPCount[i], apasGCPList[i] );
687                 CPLFree( apasGCPList[i] );
688             }
689             CSLDestroy( apapszRPCMD[i] );
690         }
691     }
692 
693     return iBestIndex >= 0;
694 }
695 
696 /************************************************************************/
697 /*                             ParseMSIG()                              */
698 /************************************************************************/
699 
ParseMSIG()700 int GDALJP2Metadata::ParseMSIG()
701 
702 {
703     if( nMSIGSize < 70 )
704         return FALSE;
705 
706 /* -------------------------------------------------------------------- */
707 /*      Try and extract worldfile parameters and adjust.                */
708 /* -------------------------------------------------------------------- */
709     memcpy( adfGeoTransform + 0, pabyMSIGData + 22 + 8 * 4, 8 );
710     memcpy( adfGeoTransform + 1, pabyMSIGData + 22 + 8 * 0, 8 );
711     memcpy( adfGeoTransform + 2, pabyMSIGData + 22 + 8 * 2, 8 );
712     memcpy( adfGeoTransform + 3, pabyMSIGData + 22 + 8 * 5, 8 );
713     memcpy( adfGeoTransform + 4, pabyMSIGData + 22 + 8 * 1, 8 );
714     memcpy( adfGeoTransform + 5, pabyMSIGData + 22 + 8 * 3, 8 );
715 
716     // data is in LSB (little endian) order in file.
717     CPL_LSBPTR64( adfGeoTransform + 0 );
718     CPL_LSBPTR64( adfGeoTransform + 1 );
719     CPL_LSBPTR64( adfGeoTransform + 2 );
720     CPL_LSBPTR64( adfGeoTransform + 3 );
721     CPL_LSBPTR64( adfGeoTransform + 4 );
722     CPL_LSBPTR64( adfGeoTransform + 5 );
723 
724     // correct for center of pixel vs. top left of pixel
725     adfGeoTransform[0] -= 0.5 * adfGeoTransform[1];
726     adfGeoTransform[0] -= 0.5 * adfGeoTransform[2];
727     adfGeoTransform[3] -= 0.5 * adfGeoTransform[4];
728     adfGeoTransform[3] -= 0.5 * adfGeoTransform[5];
729 
730     bHaveGeoTransform = true;
731 
732     return TRUE;
733 }
734 
735 /************************************************************************/
736 /*                         GetDictionaryItem()                          */
737 /************************************************************************/
738 
739 static CPLXMLNode *
GetDictionaryItem(char ** papszGMLMetadata,const char * pszURN)740 GetDictionaryItem( char **papszGMLMetadata, const char *pszURN )
741 
742 {
743     char *pszLabel = nullptr;
744 
745     if( STARTS_WITH_CI(pszURN, "urn:jp2k:xml:") )
746         pszLabel = CPLStrdup( pszURN + 13 );
747     else if( STARTS_WITH_CI(pszURN, "urn:ogc:tc:gmljp2:xml:") )
748         pszLabel = CPLStrdup( pszURN + 22 );
749     else if( STARTS_WITH_CI(pszURN, "gmljp2://xml/") )
750         pszLabel = CPLStrdup( pszURN + 13 );
751     else
752         pszLabel = CPLStrdup( pszURN );
753 
754 /* -------------------------------------------------------------------- */
755 /*      Split out label and fragment id.                                */
756 /* -------------------------------------------------------------------- */
757     const char *pszFragmentId = nullptr;
758 
759     {
760         int i = 0;  // Used after for.
761         for( ; pszLabel[i] != '#'; ++i )
762         {
763             if( pszLabel[i] == '\0' )
764             {
765                 CPLFree(pszLabel);
766                 return nullptr;
767             }
768         }
769 
770         pszFragmentId = pszLabel + i + 1;
771         pszLabel[i] = '\0';
772     }
773 
774 /* -------------------------------------------------------------------- */
775 /*      Can we find an XML box with the desired label?                  */
776 /* -------------------------------------------------------------------- */
777     const char *pszDictionary =
778         CSLFetchNameValue( papszGMLMetadata, pszLabel );
779 
780     if( pszDictionary == nullptr )
781     {
782         CPLFree(pszLabel);
783         return nullptr;
784     }
785 
786 /* -------------------------------------------------------------------- */
787 /*      Try and parse the dictionary.                                   */
788 /* -------------------------------------------------------------------- */
789     CPLXMLTreeCloser psDictTree(CPLParseXMLString( pszDictionary ));
790 
791     if( psDictTree == nullptr )
792     {
793         CPLFree(pszLabel);
794         return nullptr;
795     }
796 
797     CPLStripXMLNamespace( psDictTree.get(), nullptr, TRUE );
798 
799     CPLXMLNode *psDictRoot = CPLSearchXMLNode( psDictTree.get(), "=Dictionary" );
800 
801     if( psDictRoot == nullptr )
802     {
803         CPLFree(pszLabel);
804         return nullptr;
805     }
806 
807 /* -------------------------------------------------------------------- */
808 /*      Search for matching id.                                         */
809 /* -------------------------------------------------------------------- */
810     CPLXMLNode *psEntry, *psHit = nullptr;
811     for( psEntry = psDictRoot->psChild;
812          psEntry != nullptr && psHit == nullptr;
813          psEntry = psEntry->psNext )
814     {
815         const char *pszId;
816 
817         if( psEntry->eType != CXT_Element )
818             continue;
819 
820         if( !EQUAL(psEntry->pszValue,"dictionaryEntry") )
821             continue;
822 
823         if( psEntry->psChild == nullptr )
824             continue;
825 
826         pszId = CPLGetXMLValue( psEntry->psChild, "id", "" );
827 
828         if( EQUAL(pszId, pszFragmentId) )
829             psHit = CPLCloneXMLTree( psEntry->psChild );
830     }
831 
832 /* -------------------------------------------------------------------- */
833 /*      Cleanup                                                         */
834 /* -------------------------------------------------------------------- */
835     CPLFree( pszLabel );
836 
837     return psHit;
838 }
839 
840 /************************************************************************/
841 /*                            GMLSRSLookup()                            */
842 /*                                                                      */
843 /*      Lookup an SRS in a dictionary inside this file.  We will get    */
844 /*      something like:                                                 */
845 /*        urn:jp2k:xml:CRSDictionary.xml#crs1112                        */
846 /*                                                                      */
847 /*      We need to split the filename from the fragment id, and         */
848 /*      lookup the fragment in the file if we can find it our           */
849 /*      list of labelled xml boxes.                                     */
850 /************************************************************************/
851 
GMLSRSLookup(const char * pszURN)852 int GDALJP2Metadata::GMLSRSLookup( const char *pszURN )
853 
854 {
855     CPLXMLTreeCloser psDictEntry(GetDictionaryItem( papszGMLMetadata, pszURN ));
856 
857     if( psDictEntry == nullptr )
858         return FALSE;
859 
860 /* -------------------------------------------------------------------- */
861 /*      Reserialize this fragment.                                      */
862 /* -------------------------------------------------------------------- */
863     char *pszDictEntryXML = CPLSerializeXMLTree( psDictEntry.get() );
864     psDictEntry.reset();
865 
866 /* -------------------------------------------------------------------- */
867 /*      Try to convert into an OGRSpatialReference.                     */
868 /* -------------------------------------------------------------------- */
869     OGRSpatialReference oSRS;
870     bool bSuccess = false;
871 
872     if( oSRS.importFromXML( pszDictEntryXML ) == OGRERR_NONE )
873     {
874         CPLFree( pszProjection );
875         pszProjection = nullptr;
876 
877         oSRS.exportToWkt( &pszProjection );
878         bSuccess = true;
879     }
880 
881     CPLFree( pszDictEntryXML );
882 
883     return bSuccess;
884 }
885 
886 /************************************************************************/
887 /*                        ParseGMLCoverageDesc()                        */
888 /************************************************************************/
889 
ParseGMLCoverageDesc()890 int GDALJP2Metadata::ParseGMLCoverageDesc()
891 
892 {
893     if(! CPLTestBool(CPLGetConfigOption("GDAL_USE_GMLJP2", "TRUE")) )
894         return FALSE;
895 
896 /* -------------------------------------------------------------------- */
897 /*      Do we have an XML doc that is apparently a coverage             */
898 /*      description?                                                    */
899 /* -------------------------------------------------------------------- */
900     const char *pszCoverage = CSLFetchNameValue( papszGMLMetadata,
901                                                  "gml.root-instance" );
902 
903     if( pszCoverage == nullptr )
904         return FALSE;
905 
906     CPLDebug( "GDALJP2Metadata", "Found GML Box:\n%s", pszCoverage );
907 
908 /* -------------------------------------------------------------------- */
909 /*      Try parsing the XML.  Wipe any namespace prefixes.              */
910 /* -------------------------------------------------------------------- */
911     CPLXMLTreeCloser psXML(CPLParseXMLString( pszCoverage ));
912 
913     if( psXML == nullptr )
914         return FALSE;
915 
916     CPLStripXMLNamespace( psXML.get(), nullptr, TRUE );
917 
918 /* -------------------------------------------------------------------- */
919 /*      Isolate RectifiedGrid.  Eventually we will need to support      */
920 /*      other georeferencing objects.                                   */
921 /* -------------------------------------------------------------------- */
922     CPLXMLNode *psRG = CPLSearchXMLNode( psXML.get(), "=RectifiedGrid" );
923     CPLXMLNode *psOriginPoint = nullptr;
924     const char *pszOffset1 = nullptr;
925     const char *pszOffset2 = nullptr;
926 
927     if( psRG != nullptr )
928     {
929         psOriginPoint = CPLGetXMLNode( psRG, "origin.Point" );
930 
931         CPLXMLNode *psOffset1 = CPLGetXMLNode( psRG, "offsetVector" );
932         if( psOffset1 != nullptr )
933         {
934             pszOffset1 = CPLGetXMLValue( psOffset1, "", nullptr );
935             pszOffset2 = CPLGetXMLValue( psOffset1->psNext, "=offsetVector",
936                                          nullptr );
937         }
938     }
939 
940 /* -------------------------------------------------------------------- */
941 /*      If we are missing any of the origin or 2 offsets then give up.  */
942 /* -------------------------------------------------------------------- */
943     if( psOriginPoint == nullptr || pszOffset1 == nullptr || pszOffset2 == nullptr )
944     {
945         return FALSE;
946     }
947 
948 /* -------------------------------------------------------------------- */
949 /*      Extract origin location.                                        */
950 /* -------------------------------------------------------------------- */
951     OGRPoint *poOriginGeometry = nullptr;
952 
953     OGRGeometry* poGeom = reinterpret_cast<OGRGeometry*>(
954         OGR_G_CreateFromGMLTree( psOriginPoint ));
955 
956     if( poGeom != nullptr
957         && wkbFlatten(poGeom->getGeometryType()) == wkbPoint )
958     {
959         poOriginGeometry = poGeom->toPoint();
960     }
961     else
962     {
963         delete poGeom;
964     }
965 
966     // SRS?
967     const char* pszSRSName = CPLGetXMLValue( psOriginPoint, "srsName", nullptr );
968 
969 /* -------------------------------------------------------------------- */
970 /*      Extract offset(s)                                               */
971 /* -------------------------------------------------------------------- */
972     bool bSuccess = false;
973 
974     char** papszOffset1Tokens =
975         CSLTokenizeStringComplex( pszOffset1, " ,", FALSE, FALSE );
976     char** papszOffset2Tokens =
977         CSLTokenizeStringComplex( pszOffset2, " ,", FALSE, FALSE );
978 
979     if( CSLCount(papszOffset1Tokens) >= 2
980         && CSLCount(papszOffset2Tokens) >= 2
981         && poOriginGeometry != nullptr )
982     {
983         adfGeoTransform[0] = poOriginGeometry->getX();
984         adfGeoTransform[1] = CPLAtof(papszOffset1Tokens[0]);
985         adfGeoTransform[2] = CPLAtof(papszOffset2Tokens[0]);
986         adfGeoTransform[3] = poOriginGeometry->getY();
987         adfGeoTransform[4] = CPLAtof(papszOffset1Tokens[1]);
988         adfGeoTransform[5] = CPLAtof(papszOffset2Tokens[1]);
989 
990         // offset from center of pixel.
991         adfGeoTransform[0] -= adfGeoTransform[1]*0.5;
992         adfGeoTransform[0] -= adfGeoTransform[2]*0.5;
993         adfGeoTransform[3] -= adfGeoTransform[4]*0.5;
994         adfGeoTransform[3] -= adfGeoTransform[5]*0.5;
995 
996         bSuccess = true;
997         bHaveGeoTransform = true;
998     }
999 
1000     CSLDestroy( papszOffset1Tokens );
1001     CSLDestroy( papszOffset2Tokens );
1002 
1003     if( poOriginGeometry != nullptr )
1004         delete poOriginGeometry;
1005 
1006 /* -------------------------------------------------------------------- */
1007 /*      If we still don't have an srsName, check for it on the          */
1008 /*      boundedBy Envelope.  Some products                              */
1009 /*      (i.e. EuropeRasterTile23.jpx) use this as the only srsName      */
1010 /*      delivery vehicle.                                               */
1011 /* -------------------------------------------------------------------- */
1012     if( pszSRSName == nullptr )
1013     {
1014         pszSRSName =
1015             CPLGetXMLValue( psXML.get(),
1016                             "=FeatureCollection.boundedBy.Envelope.srsName",
1017                             nullptr );
1018     }
1019 /* -------------------------------------------------------------------- */
1020 /*      Examples of DGIWG_Profile_of_JPEG2000_for_Georeference_Imagery.pdf */
1021 /*      have srsName only on RectifiedGrid element.                     */
1022 /* -------------------------------------------------------------------- */
1023     if( psRG != nullptr && pszSRSName == nullptr )
1024     {
1025         pszSRSName = CPLGetXMLValue( psRG,  "srsName", nullptr );
1026     }
1027 
1028 /* -------------------------------------------------------------------- */
1029 /*      If we have gotten a geotransform, then try to interpret the     */
1030 /*      srsName.                                                        */
1031 /* -------------------------------------------------------------------- */
1032     bool bNeedAxisFlip = false;
1033 
1034     OGRSpatialReference oSRS;
1035     if( bSuccess && pszSRSName != nullptr
1036         && (pszProjection == nullptr || strlen(pszProjection) == 0) )
1037     {
1038         if( STARTS_WITH_CI(pszSRSName, "epsg:") )
1039         {
1040             if( oSRS.SetFromUserInput( pszSRSName ) == OGRERR_NONE )
1041                 oSRS.exportToWkt( &pszProjection );
1042         }
1043         else if( (STARTS_WITH_CI(pszSRSName, "urn:")
1044                  && strstr(pszSRSName,":def:") != nullptr
1045                  && oSRS.importFromURN(pszSRSName) == OGRERR_NONE) ||
1046                  /* GMLJP2 v2.0 uses CRS URL instead of URN */
1047                  /* See e.g. http://schemas.opengis.net/gmljp2/2.0/examples/minimalInstance.xml */
1048                  (STARTS_WITH_CI(pszSRSName, "http://www.opengis.net/def/crs/")                  && oSRS.importFromCRSURL(pszSRSName) == OGRERR_NONE) )
1049         {
1050             oSRS.exportToWkt( &pszProjection );
1051 
1052             // Per #2131
1053             if( oSRS.EPSGTreatsAsLatLong() || oSRS.EPSGTreatsAsNorthingEasting() )
1054             {
1055                 CPLDebug( "GMLJP2", "Request axis flip for SRS=%s",
1056                           pszSRSName );
1057                 bNeedAxisFlip = true;
1058             }
1059         }
1060         else if( !GMLSRSLookup( pszSRSName ) )
1061         {
1062             CPLDebug( "GDALJP2Metadata",
1063                       "Unable to evaluate SRSName=%s",
1064                       pszSRSName );
1065         }
1066     }
1067 
1068     if( pszProjection )
1069         CPLDebug( "GDALJP2Metadata",
1070                   "Got projection from GML box: %s",
1071                  pszProjection );
1072 
1073 /* -------------------------------------------------------------------- */
1074 /*      Do we need to flip the axes?                                    */
1075 /* -------------------------------------------------------------------- */
1076     if( bNeedAxisFlip
1077         && CPLTestBool( CPLGetConfigOption( "GDAL_IGNORE_AXIS_ORIENTATION",
1078                                                "FALSE" ) ) )
1079     {
1080         bNeedAxisFlip = false;
1081         CPLDebug( "GMLJP2", "Suppressed axis flipping based on GDAL_IGNORE_AXIS_ORIENTATION." );
1082     }
1083 
1084     /* Some Pleiades files have explicit <gml:axisName>Easting</gml:axisName> */
1085     /* <gml:axisName>Northing</gml:axisName> to override default EPSG order */
1086     if( bNeedAxisFlip && psRG != nullptr )
1087     {
1088         int nAxisCount = 0;
1089         bool bFirstAxisIsEastOrLong = false;
1090         bool bSecondAxisIsNorthOrLat = false;
1091         for(CPLXMLNode* psIter = psRG->psChild; psIter != nullptr; psIter = psIter->psNext )
1092         {
1093             if( psIter->eType == CXT_Element && strcmp(psIter->pszValue, "axisName") == 0 &&
1094                 psIter->psChild != nullptr && psIter->psChild->eType == CXT_Text )
1095             {
1096                 if( nAxisCount == 0 &&
1097                     (STARTS_WITH_CI(psIter->psChild->pszValue, "EAST") ||
1098                      STARTS_WITH_CI(psIter->psChild->pszValue, "LONG") ) )
1099                 {
1100                     bFirstAxisIsEastOrLong = true;
1101                 }
1102                 else if( nAxisCount == 1 &&
1103                          (STARTS_WITH_CI(psIter->psChild->pszValue, "NORTH") ||
1104                           STARTS_WITH_CI(psIter->psChild->pszValue, "LAT")) )
1105                 {
1106                     bSecondAxisIsNorthOrLat = true;
1107                 }
1108                 ++nAxisCount;
1109             }
1110         }
1111         if( bFirstAxisIsEastOrLong && bSecondAxisIsNorthOrLat )
1112         {
1113             CPLDebug(
1114                 "GMLJP2",
1115                 "Disable axis flip because of explicit axisName disabling it" );
1116             bNeedAxisFlip = false;
1117         }
1118     }
1119 
1120     psXML.reset();
1121     psRG = nullptr;
1122 
1123     if( bNeedAxisFlip )
1124     {
1125         double dfTemp;
1126 
1127         CPLDebug( "GMLJP2",
1128                   "Flipping axis orientation in GMLJP2 coverage description." );
1129 
1130         dfTemp = adfGeoTransform[0];
1131         adfGeoTransform[0] = adfGeoTransform[3];
1132         adfGeoTransform[3] = dfTemp;
1133 
1134         int swapWith1Index = 4;
1135         int swapWith2Index = 5;
1136 
1137         /* Look if we have GDAL_JP2K_ALT_OFFSETVECTOR_ORDER=TRUE as a XML comment */
1138         int bHasAltOffsetVectorOrderComment =
1139             strstr(pszCoverage, "GDAL_JP2K_ALT_OFFSETVECTOR_ORDER=TRUE") != nullptr;
1140 
1141         if( bHasAltOffsetVectorOrderComment ||
1142             CPLTestBool( CPLGetConfigOption( "GDAL_JP2K_ALT_OFFSETVECTOR_ORDER",
1143                                                 "FALSE" ) ) )
1144         {
1145             swapWith1Index = 5;
1146             swapWith2Index = 4;
1147             CPLDebug( "GMLJP2", "Choosing alternate GML \"<offsetVector>\" order based on "
1148                 "GDAL_JP2K_ALT_OFFSETVECTOR_ORDER." );
1149         }
1150 
1151         dfTemp = adfGeoTransform[1];
1152         adfGeoTransform[1] = adfGeoTransform[swapWith1Index];
1153         adfGeoTransform[swapWith1Index] = dfTemp;
1154 
1155         dfTemp = adfGeoTransform[2];
1156         adfGeoTransform[2] = adfGeoTransform[swapWith2Index];
1157         adfGeoTransform[swapWith2Index] = dfTemp;
1158 
1159         /* Found in autotest/gdrivers/data/ll.jp2 */
1160         if( adfGeoTransform[1] == 0.0 && adfGeoTransform[2] < 0.0 &&
1161             adfGeoTransform[4] > 0.0 && adfGeoTransform[5] == 0.0 )
1162         {
1163             CPLError(CE_Warning, CPLE_AppDefined,
1164                      "It is likely that the axis order of the GMLJP2 box is not "
1165                      "consistent with the EPSG order and that the resulting georeferencing "
1166                      "will be incorrect. Try setting GDAL_IGNORE_AXIS_ORIENTATION=TRUE if it is the case");
1167         }
1168     }
1169 
1170     return pszProjection != nullptr && bSuccess;
1171 }
1172 
1173 /************************************************************************/
1174 /*                           SetProjection()                            */
1175 /************************************************************************/
1176 
SetProjection(const char * pszWKT)1177 void GDALJP2Metadata::SetProjection( const char *pszWKT )
1178 
1179 {
1180     CPLFree( pszProjection );
1181     pszProjection = CPLStrdup(pszWKT);
1182 }
1183 
1184 /************************************************************************/
1185 /*                              SetGCPs()                               */
1186 /************************************************************************/
1187 
SetGCPs(int nCount,const GDAL_GCP * pasGCPsIn)1188 void GDALJP2Metadata::SetGCPs( int nCount, const GDAL_GCP *pasGCPsIn )
1189 
1190 {
1191     if( nGCPCount > 0 )
1192     {
1193         GDALDeinitGCPs( nGCPCount, pasGCPList );
1194         CPLFree( pasGCPList );
1195     }
1196 
1197     nGCPCount = nCount;
1198     pasGCPList = GDALDuplicateGCPs(nGCPCount, pasGCPsIn);
1199 }
1200 
1201 /************************************************************************/
1202 /*                          SetGeoTransform()                           */
1203 /************************************************************************/
1204 
SetGeoTransform(double * padfGT)1205 void GDALJP2Metadata::SetGeoTransform( double *padfGT )
1206 
1207 {
1208     memcpy( adfGeoTransform, padfGT, sizeof(double) * 6 );
1209 }
1210 
1211 /************************************************************************/
1212 /*                             SetRPCMD()                               */
1213 /************************************************************************/
1214 
SetRPCMD(char ** papszRPCMDIn)1215 void GDALJP2Metadata::SetRPCMD( char** papszRPCMDIn )
1216 
1217 {
1218     CSLDestroy( papszRPCMD );
1219     papszRPCMD = CSLDuplicate(papszRPCMDIn);
1220 }
1221 
1222 /************************************************************************/
1223 /*                          CreateJP2GeoTIFF()                          */
1224 /************************************************************************/
1225 
CreateJP2GeoTIFF()1226 GDALJP2Box *GDALJP2Metadata::CreateJP2GeoTIFF()
1227 
1228 {
1229 /* -------------------------------------------------------------------- */
1230 /*      Prepare the memory buffer containing the degenerate GeoTIFF     */
1231 /*      file.                                                           */
1232 /* -------------------------------------------------------------------- */
1233     int         nGTBufSize = 0;
1234     unsigned char *pabyGTBuf = nullptr;
1235 
1236     if( GTIFMemBufFromWktEx( pszProjection, adfGeoTransform,
1237                              nGCPCount, pasGCPList,
1238                              &nGTBufSize, &pabyGTBuf, bPixelIsPoint,
1239                              papszRPCMD ) != CE_None )
1240         return nullptr;
1241 
1242     if( nGTBufSize == 0 )
1243         return nullptr;
1244 
1245 /* -------------------------------------------------------------------- */
1246 /*      Write to a box on the JP2 file.                                 */
1247 /* -------------------------------------------------------------------- */
1248     GDALJP2Box *poBox;
1249 
1250     poBox = GDALJP2Box::CreateUUIDBox( msi_uuid2, nGTBufSize, pabyGTBuf );
1251 
1252     CPLFree( pabyGTBuf );
1253 
1254     return poBox;
1255 }
1256 
1257 /************************************************************************/
1258 /*                     GetGMLJP2GeoreferencingInfo()                    */
1259 /************************************************************************/
1260 
GetGMLJP2GeoreferencingInfo(int & nEPSGCode,double adfOrigin[2],double adfXVector[2],double adfYVector[2],const char * & pszComment,CPLString & osDictBox,int & bNeedAxisFlip)1261 int GDALJP2Metadata::GetGMLJP2GeoreferencingInfo( int& nEPSGCode,
1262                                                   double adfOrigin[2],
1263                                                   double adfXVector[2],
1264                                                   double adfYVector[2],
1265                                                   const char*& pszComment,
1266                                                   CPLString& osDictBox,
1267                                                   int& bNeedAxisFlip )
1268 {
1269 
1270 /* -------------------------------------------------------------------- */
1271 /*      Try do determine a PCS or GCS code we can use.                  */
1272 /* -------------------------------------------------------------------- */
1273     OGRSpatialReference oSRS;
1274     nEPSGCode = 0;
1275     bNeedAxisFlip = FALSE;
1276 
1277     if( oSRS.importFromWkt( pszProjection ) != OGRERR_NONE )
1278         return FALSE;
1279 
1280     if( oSRS.IsProjected() )
1281     {
1282         const char *pszAuthName = oSRS.GetAuthorityName( "PROJCS" );
1283 
1284         if( pszAuthName != nullptr && EQUAL(pszAuthName,"epsg") )
1285         {
1286             nEPSGCode = atoi(oSRS.GetAuthorityCode( "PROJCS" ));
1287         }
1288     }
1289     else if( oSRS.IsGeographic() )
1290     {
1291         const char *pszAuthName = oSRS.GetAuthorityName( "GEOGCS" );
1292 
1293         if( pszAuthName != nullptr && EQUAL(pszAuthName,"epsg") )
1294         {
1295             nEPSGCode = atoi(oSRS.GetAuthorityCode( "GEOGCS" ));
1296         }
1297     }
1298 
1299     // Save error state as importFromEPSGA() will call CPLReset()
1300     CPLErrorNum errNo = CPLGetLastErrorNo();
1301     CPLErr eErr = CPLGetLastErrorType();
1302     CPLString osLastErrorMsg = CPLGetLastErrorMsg();
1303 
1304     // Determine if we need to flip axis. Reimport from EPSG and make
1305     // sure not to strip axis definitions to determine the axis order.
1306     if( nEPSGCode != 0 && oSRS.importFromEPSGA(nEPSGCode) == OGRERR_NONE )
1307     {
1308         if( oSRS.EPSGTreatsAsLatLong() || oSRS.EPSGTreatsAsNorthingEasting() )
1309         {
1310             bNeedAxisFlip = TRUE;
1311         }
1312     }
1313 
1314     // Restore error state
1315     CPLErrorSetState( eErr, errNo, osLastErrorMsg);
1316 
1317 /* -------------------------------------------------------------------- */
1318 /*      Prepare coverage origin and offset vectors.  Take axis          */
1319 /*      order into account if needed.                                   */
1320 /* -------------------------------------------------------------------- */
1321     adfOrigin[0] = adfGeoTransform[0] + adfGeoTransform[1] * 0.5
1322         + adfGeoTransform[4] * 0.5;
1323     adfOrigin[1] = adfGeoTransform[3] + adfGeoTransform[2] * 0.5
1324         + adfGeoTransform[5] * 0.5;
1325     adfXVector[0] = adfGeoTransform[1];
1326     adfXVector[1] = adfGeoTransform[2];
1327 
1328     adfYVector[0] = adfGeoTransform[4];
1329     adfYVector[1] = adfGeoTransform[5];
1330 
1331     if( bNeedAxisFlip
1332         && CPLTestBool( CPLGetConfigOption( "GDAL_IGNORE_AXIS_ORIENTATION",
1333                                                "FALSE" ) ) )
1334     {
1335         bNeedAxisFlip = FALSE;
1336         CPLDebug( "GMLJP2", "Suppressed axis flipping on write based on GDAL_IGNORE_AXIS_ORIENTATION." );
1337     }
1338 
1339     pszComment = "";
1340     if( bNeedAxisFlip )
1341     {
1342         double dfTemp;
1343 
1344         CPLDebug( "GMLJP2", "Flipping GML coverage axis order." );
1345 
1346         dfTemp = adfOrigin[0];
1347         adfOrigin[0] = adfOrigin[1];
1348         adfOrigin[1] = dfTemp;
1349 
1350         if( CPLTestBool( CPLGetConfigOption( "GDAL_JP2K_ALT_OFFSETVECTOR_ORDER",
1351                                                 "FALSE" ) ) )
1352         {
1353             CPLDebug( "GMLJP2", "Choosing alternate GML \"<offsetVector>\" order based on "
1354                 "GDAL_JP2K_ALT_OFFSETVECTOR_ORDER." );
1355 
1356             /* In this case the swapping is done in an "X" pattern */
1357             dfTemp = adfXVector[0];
1358             adfXVector[0] = adfYVector[1];
1359             adfYVector[1] = dfTemp;
1360 
1361             dfTemp = adfYVector[0];
1362             adfYVector[0] = adfXVector[1];
1363             adfXVector[1] = dfTemp;
1364 
1365             /* We add this as an XML comment so that we know we must do OffsetVector flipping on reading */
1366             pszComment = "              <!-- GDAL_JP2K_ALT_OFFSETVECTOR_ORDER=TRUE: First "
1367                          "value of offset is latitude/northing component of the "
1368                          "latitude/northing axis. -->\n";
1369         }
1370         else
1371         {
1372             dfTemp = adfXVector[0];
1373             adfXVector[0] = adfXVector[1];
1374             adfXVector[1] = dfTemp;
1375 
1376             dfTemp = adfYVector[0];
1377             adfYVector[0] = adfYVector[1];
1378             adfYVector[1] = dfTemp;
1379         }
1380     }
1381 
1382 /* -------------------------------------------------------------------- */
1383 /*      If we need a user defined CRSDictionary entry, prepare it       */
1384 /*      here.                                                           */
1385 /* -------------------------------------------------------------------- */
1386     if( nEPSGCode == 0 )
1387     {
1388         char *pszGMLDef = nullptr;
1389 
1390         if( oSRS.exportToXML( &pszGMLDef, nullptr ) == OGRERR_NONE )
1391         {
1392             char* pszWKT = nullptr;
1393             oSRS.exportToWkt(&pszWKT);
1394             char* pszXMLEscapedWKT = CPLEscapeString(pszWKT, -1, CPLES_XML);
1395             CPLFree(pszWKT);
1396             osDictBox.Printf(
1397 "<gml:Dictionary gml:id=\"CRSU1\" \n"
1398 "        xmlns:gml=\"http://www.opengis.net/gml\"\n"
1399 "        xmlns:xlink=\"http://www.w3.org/1999/xlink\"\n"
1400 "        xmlns:xsi=\"http://www.w3.org/2001/XMLSchema-instance\"\n"
1401 "        xsi:schemaLocation=\"http://www.opengis.net/gml "
1402 "http://schemas.opengis.net/gml/3.1.1/base/gml.xsd\">\n"
1403 "  <gml:description>Dictionary for custom SRS %s</gml:description>\n"
1404 "  <gml:name>Dictionary for custom SRS</gml:name>\n"
1405 "  <gml:dictionaryEntry>\n"
1406 "%s\n"
1407 "  </gml:dictionaryEntry>\n"
1408 "</gml:Dictionary>\n",
1409                      pszXMLEscapedWKT, pszGMLDef );
1410             CPLFree(pszXMLEscapedWKT);
1411         }
1412         CPLFree( pszGMLDef );
1413     }
1414 
1415     return TRUE;
1416 }
1417 
1418 /************************************************************************/
1419 /*                          CreateGMLJP2()                              */
1420 /************************************************************************/
1421 
CreateGMLJP2(int nXSize,int nYSize)1422 GDALJP2Box *GDALJP2Metadata::CreateGMLJP2( int nXSize, int nYSize )
1423 
1424 {
1425 /* -------------------------------------------------------------------- */
1426 /*      This is a backdoor to let us embed a literal gmljp2 chunk       */
1427 /*      supplied by the user as an external file.  This is mostly       */
1428 /*      for preparing test files with exotic contents.                  */
1429 /* -------------------------------------------------------------------- */
1430     if( CPLGetConfigOption( "GMLJP2OVERRIDE", nullptr ) != nullptr )
1431     {
1432         VSILFILE *fp = VSIFOpenL( CPLGetConfigOption( "GMLJP2OVERRIDE",""), "r" );
1433         char *pszGML = nullptr;
1434 
1435         if( fp == nullptr )
1436         {
1437             CPLError( CE_Failure, CPLE_AppDefined,
1438                       "Unable to open GMLJP2OVERRIDE file." );
1439             return nullptr;
1440         }
1441 
1442         CPL_IGNORE_RET_VAL(VSIFSeekL( fp, 0, SEEK_END ));
1443         const int nLength = static_cast<int>( VSIFTellL( fp ) );
1444         pszGML = static_cast<char *>(CPLCalloc(1,nLength+1));
1445         CPL_IGNORE_RET_VAL(VSIFSeekL( fp, 0, SEEK_SET ));
1446         CPL_IGNORE_RET_VAL(VSIFReadL( pszGML, 1, nLength, fp ));
1447         CPL_IGNORE_RET_VAL(VSIFCloseL( fp ));
1448 
1449         GDALJP2Box *apoGMLBoxes[2];
1450 
1451         apoGMLBoxes[0] = GDALJP2Box::CreateLblBox( "gml.data" );
1452         apoGMLBoxes[1] =
1453             GDALJP2Box::CreateLabelledXMLAssoc( "gml.root-instance",
1454                                                 pszGML );
1455 
1456         GDALJP2Box *poGMLData = GDALJP2Box::CreateAsocBox( 2, apoGMLBoxes);
1457 
1458         delete apoGMLBoxes[0];
1459         delete apoGMLBoxes[1];
1460 
1461         CPLFree( pszGML );
1462 
1463         return poGMLData;
1464     }
1465 
1466     int nEPSGCode;
1467     double adfOrigin[2];
1468     double adfXVector[2];
1469     double adfYVector[2];
1470     const char* pszComment = "";
1471     CPLString osDictBox;
1472     int bNeedAxisFlip = FALSE;
1473     if( !GetGMLJP2GeoreferencingInfo( nEPSGCode, adfOrigin,
1474                                       adfXVector, adfYVector,
1475                                       pszComment, osDictBox, bNeedAxisFlip ) )
1476     {
1477         return nullptr;
1478     }
1479 
1480     char szSRSName[100];
1481     if( nEPSGCode != 0 )
1482         snprintf( szSRSName, sizeof(szSRSName), "urn:ogc:def:crs:EPSG::%d", nEPSGCode );
1483     else
1484         snprintf( szSRSName, sizeof(szSRSName), "%s",
1485                 "gmljp2://xml/CRSDictionary.gml#ogrcrs1" );
1486 
1487     // Compute bounding box
1488     double dfX1 = adfGeoTransform[0];
1489     double dfX2 = adfGeoTransform[0] + nXSize * adfGeoTransform[1];
1490     double dfX3 = adfGeoTransform[0] +                               nYSize * adfGeoTransform[2];
1491     double dfX4 = adfGeoTransform[0] + nXSize * adfGeoTransform[1] + nYSize * adfGeoTransform[2];
1492     double dfY1 = adfGeoTransform[3];
1493     double dfY2 = adfGeoTransform[3] + nXSize * adfGeoTransform[4];
1494     double dfY3 = adfGeoTransform[3] +                               nYSize * adfGeoTransform[5];
1495     double dfY4 = adfGeoTransform[3] + nXSize * adfGeoTransform[4] + nYSize * adfGeoTransform[5];
1496     double dfLCX = std::min(std::min(dfX1, dfX2), std::min(dfX3, dfX4));
1497     double dfLCY = std::min(std::min(dfY1, dfY2), std::min(dfY3, dfY4));
1498     double dfUCX = std::max(std::max(dfX1, dfX2), std::max(dfX3, dfX4));
1499     double dfUCY = std::max(std::max(dfY1, dfY2), std::max(dfY3, dfY4));
1500     if( bNeedAxisFlip )
1501     {
1502         double dfTmp = dfLCX;
1503         dfLCX = dfLCY;
1504         dfLCY = dfTmp;
1505 
1506         dfTmp = dfUCX;
1507         dfUCX = dfUCY;
1508         dfUCY = dfTmp;
1509     }
1510 
1511 /* -------------------------------------------------------------------- */
1512 /*      For now we hardcode for a minimal instance format.              */
1513 /* -------------------------------------------------------------------- */
1514     CPLString osDoc;
1515 
1516     osDoc.Printf(
1517 "<gml:FeatureCollection\n"
1518 "   xmlns:gml=\"http://www.opengis.net/gml\"\n"
1519 "   xmlns:xsi=\"http://www.w3.org/2001/XMLSchema-instance\"\n"
1520 "   xsi:schemaLocation=\"http://www.opengis.net/gml http://schemas.opengis.net/gml/3.1.1/profiles/gmlJP2Profile/1.0.0/gmlJP2Profile.xsd\">\n"
1521 "  <gml:boundedBy>\n"
1522 "    <gml:Envelope srsName=\"%s\">\n"
1523 "      <gml:lowerCorner>%.15g %.15g</gml:lowerCorner>\n"
1524 "      <gml:upperCorner>%.15g %.15g</gml:upperCorner>\n"
1525 "    </gml:Envelope>\n"
1526 "  </gml:boundedBy>\n"
1527 "  <gml:featureMember>\n"
1528 "    <gml:FeatureCollection>\n"
1529 "      <gml:featureMember>\n"
1530 "        <gml:RectifiedGridCoverage dimension=\"2\" gml:id=\"RGC0001\">\n"
1531 "          <gml:rectifiedGridDomain>\n"
1532 "            <gml:RectifiedGrid dimension=\"2\">\n"
1533 "              <gml:limits>\n"
1534 "                <gml:GridEnvelope>\n"
1535 "                  <gml:low>0 0</gml:low>\n"
1536 "                  <gml:high>%d %d</gml:high>\n"
1537 "                </gml:GridEnvelope>\n"
1538 "              </gml:limits>\n"
1539 "              <gml:axisName>x</gml:axisName>\n"
1540 "              <gml:axisName>y</gml:axisName>\n"
1541 "              <gml:origin>\n"
1542 "                <gml:Point gml:id=\"P0001\" srsName=\"%s\">\n"
1543 "                  <gml:pos>%.15g %.15g</gml:pos>\n"
1544 "                </gml:Point>\n"
1545 "              </gml:origin>\n"
1546 "%s"
1547 "              <gml:offsetVector srsName=\"%s\">%.15g %.15g</gml:offsetVector>\n"
1548 "              <gml:offsetVector srsName=\"%s\">%.15g %.15g</gml:offsetVector>\n"
1549 "            </gml:RectifiedGrid>\n"
1550 "          </gml:rectifiedGridDomain>\n"
1551 "          <gml:rangeSet>\n"
1552 "            <gml:File>\n"
1553 "              <gml:rangeParameters/>\n"
1554 "              <gml:fileName>gmljp2://codestream/0</gml:fileName>\n"
1555 "              <gml:fileStructure>Record Interleaved</gml:fileStructure>\n"
1556 "            </gml:File>\n"
1557 "          </gml:rangeSet>\n"
1558 "        </gml:RectifiedGridCoverage>\n"
1559 "      </gml:featureMember>\n"
1560 "    </gml:FeatureCollection>\n"
1561 "  </gml:featureMember>\n"
1562 "</gml:FeatureCollection>\n",
1563              szSRSName, dfLCX, dfLCY, dfUCX, dfUCY,
1564              nXSize-1, nYSize-1, szSRSName, adfOrigin[0], adfOrigin[1],
1565              pszComment,
1566              szSRSName, adfXVector[0], adfXVector[1],
1567              szSRSName, adfYVector[0], adfYVector[1] );
1568 
1569 /* -------------------------------------------------------------------- */
1570 /*      Setup the gml.data label.                                       */
1571 /* -------------------------------------------------------------------- */
1572     GDALJP2Box *apoGMLBoxes[5];
1573     int nGMLBoxes = 0;
1574 
1575     apoGMLBoxes[nGMLBoxes++] = GDALJP2Box::CreateLblBox( "gml.data" );
1576 
1577 /* -------------------------------------------------------------------- */
1578 /*      Setup gml.root-instance.                                        */
1579 /* -------------------------------------------------------------------- */
1580     apoGMLBoxes[nGMLBoxes++] =
1581         GDALJP2Box::CreateLabelledXMLAssoc( "gml.root-instance", osDoc );
1582 
1583 /* -------------------------------------------------------------------- */
1584 /*      Add optional dictionary.                                        */
1585 /* -------------------------------------------------------------------- */
1586     if( !osDictBox.empty() )
1587         apoGMLBoxes[nGMLBoxes++] =
1588             GDALJP2Box::CreateLabelledXMLAssoc( "CRSDictionary.gml",
1589                                                 osDictBox );
1590 
1591 /* -------------------------------------------------------------------- */
1592 /*      Bundle gml.data boxes into an association.                      */
1593 /* -------------------------------------------------------------------- */
1594     GDALJP2Box *poGMLData = GDALJP2Box::CreateAsocBox( nGMLBoxes, apoGMLBoxes);
1595 
1596 /* -------------------------------------------------------------------- */
1597 /*      Cleanup working boxes.                                          */
1598 /* -------------------------------------------------------------------- */
1599     while( nGMLBoxes > 0 )
1600         delete apoGMLBoxes[--nGMLBoxes];
1601 
1602     return poGMLData;
1603 }
1604 
1605 /************************************************************************/
1606 /*                      GDALGMLJP2GetXMLRoot()                          */
1607 /************************************************************************/
1608 
GDALGMLJP2GetXMLRoot(CPLXMLNode * psNode)1609 static CPLXMLNode* GDALGMLJP2GetXMLRoot(CPLXMLNode* psNode)
1610 {
1611     for( ; psNode != nullptr; psNode = psNode->psNext )
1612     {
1613         if( psNode->eType == CXT_Element && psNode->pszValue[0] != '?' )
1614             return psNode;
1615     }
1616     return nullptr;
1617 }
1618 
1619 /************************************************************************/
1620 /*            GDALGMLJP2PatchFeatureCollectionSubstitutionGroup()       */
1621 /************************************************************************/
1622 
GDALGMLJP2PatchFeatureCollectionSubstitutionGroup(CPLXMLNode * psRoot)1623 static void GDALGMLJP2PatchFeatureCollectionSubstitutionGroup(CPLXMLNode* psRoot)
1624 {
1625     /* GML 3.2 SF profile recommends the feature collection type to derive */
1626     /* from gml:AbstractGML to prevent it to be included in another feature */
1627     /* collection, but this is what we want to do. So patch that... */
1628 
1629     /* <xs:element name="FeatureCollection" type="ogr:FeatureCollectionType" substitutionGroup="gml:AbstractGML"/> */
1630     /* --> */
1631     /* <xs:element name="FeatureCollection" type="ogr:FeatureCollectionType" substitutionGroup="gml:AbstractFeature"/> */
1632     if( psRoot->eType == CXT_Element &&
1633         (strcmp(psRoot->pszValue, "schema") == 0 || strcmp(psRoot->pszValue, "xs:schema") == 0) )
1634     {
1635         for(CPLXMLNode* psIter = psRoot->psChild; psIter != nullptr; psIter = psIter->psNext)
1636         {
1637             if( psIter->eType == CXT_Element &&
1638                 (strcmp(psIter->pszValue, "element") == 0 || strcmp(psIter->pszValue, "xs:element") == 0) &&
1639                 strcmp(CPLGetXMLValue(psIter, "name", ""), "FeatureCollection") == 0 &&
1640                 strcmp(CPLGetXMLValue(psIter, "substitutionGroup", ""), "gml:AbstractGML") == 0 )
1641             {
1642                 CPLDebug("GMLJP2", R"(Patching substitutionGroup="gml:AbstractGML" to "gml:AbstractFeature")");
1643                 CPLSetXMLValue( psIter, "#substitutionGroup", "gml:AbstractFeature" );
1644                 break;
1645             }
1646         }
1647     }
1648 }
1649 
1650 /************************************************************************/
1651 /*                          CreateGMLJP2V2()                            */
1652 /************************************************************************/
1653 
1654 class GMLJP2V2GMLFileDesc
1655 {
1656     public:
1657         CPLString osFile{};
1658         CPLString osRemoteResource{};
1659         CPLString osNamespace{};
1660         CPLString osNamespacePrefix{};
1661         CPLString osSchemaLocation{};
1662         int       bInline = true;
1663         int       bParentCoverageCollection = true;
1664 };
1665 
1666 class GMLJP2V2AnnotationDesc
1667 {
1668     public:
1669         CPLString osFile{};
1670 };
1671 
1672 class GMLJP2V2MetadataDesc
1673 {
1674     public:
1675         CPLString osFile{};
1676         CPLString osContent{};
1677         CPLString osTemplateFile{};
1678         CPLString osSourceFile{};
1679         int       bGDALMetadata = false;
1680         int       bParentCoverageCollection = true;
1681 };
1682 
1683 class GMLJP2V2StyleDesc
1684 {
1685     public:
1686         CPLString osFile{};
1687         int       bParentCoverageCollection = true;
1688 };
1689 
1690 class GMLJP2V2ExtensionDesc
1691 {
1692     public:
1693         CPLString osFile{};
1694         int       bParentCoverageCollection = true;
1695 };
1696 
1697 class GMLJP2V2BoxDesc
1698 {
1699     public:
1700         CPLString osFile{};
1701         CPLString osLabel{};
1702 };
1703 
CreateGMLJP2V2(int nXSize,int nYSize,const char * pszDefFilename,GDALDataset * poSrcDS)1704 GDALJP2Box *GDALJP2Metadata::CreateGMLJP2V2( int nXSize, int nYSize,
1705                                              const char* pszDefFilename,
1706                                              GDALDataset* poSrcDS )
1707 
1708 {
1709     CPLString osRootGMLId = "ID_GMLJP2_0";
1710     CPLString osGridCoverage;
1711     CPLString osGridCoverageFile;
1712     CPLString osCoverageRangeTypeXML;
1713     bool bCRSURL = true;
1714     std::vector<GMLJP2V2MetadataDesc> aoMetadata;
1715     std::vector<GMLJP2V2AnnotationDesc> aoAnnotations;
1716     std::vector<GMLJP2V2GMLFileDesc> aoGMLFiles;
1717     std::vector<GMLJP2V2StyleDesc> aoStyles;
1718     std::vector<GMLJP2V2ExtensionDesc> aoExtensions;
1719     std::vector<GMLJP2V2BoxDesc> aoBoxes;
1720 
1721 /* -------------------------------------------------------------------- */
1722 /*      Parse definition file.                                          */
1723 /* -------------------------------------------------------------------- */
1724     if( pszDefFilename && !EQUAL(pszDefFilename, "YES") && !EQUAL(pszDefFilename, "TRUE") )
1725     {
1726         GByte* pabyContent = nullptr;
1727         if( pszDefFilename[0] != '{' )
1728         {
1729             if( !VSIIngestFile( nullptr, pszDefFilename, &pabyContent, nullptr, -1 ) )
1730                 return nullptr;
1731         }
1732 
1733 /*
1734 {
1735     "#doc" : "Unless otherwise specified, all elements are optional",
1736 
1737     "#root_instance_doc": "Describe content of the GMLJP2CoverageCollection",
1738     "root_instance": {
1739         "#gml_id_doc": "Specify GMLJP2CoverageCollection id here. Default is ID_GMLJP2_0",
1740         "gml_id": "some_gml_id",
1741 
1742         "#grid_coverage_file_doc": [
1743             "External XML file, whose root might be a GMLJP2GridCoverage, ",
1744             "GMLJP2RectifiedGridCoverage or a GMLJP2ReferenceableGridCoverage",
1745             "If not specified, GDAL will auto-generate a GMLJP2RectifiedGridCoverage" ],
1746         "grid_coverage_file": "gmljp2gridcoverage.xml",
1747 
1748         "#grid_coverage_range_type_field_predefined_name_doc": [
1749             "One of Color, Elevation_meter or Panchromatic ",
1750             "to fill gmlcov:rangeType/swe:DataRecord/swe:field",
1751             "Only used if grid_coverage_file is not defined.",
1752             "Exclusive with grid_coverage_range_type_file" ],
1753         "grid_coverage_range_type_field_predefined_name": "Color",
1754 
1755         "#grid_coverage_range_type_file_doc": [
1756             "File that is XML content to put under gml:RectifiedGrid/gmlcov:rangeType",
1757             "Only used if grid_coverage_file is not defined.",
1758             "Exclusive with grid_coverage_range_type_field_predefined_name" ],
1759         "grid_coverage_range_type_file": "grid_coverage_range_type.xml",
1760 
1761         "#crs_url_doc": [
1762             "true for http://www.opengis.net/def/crs/EPSG/0/XXXX CRS URL.",
1763             "If false, use CRS URN. Default value is true" ],
1764         "crs_url": true,
1765 
1766         "#metadata_doc": [ "An array of metadata items. Can be either strings, with ",
1767                            "a filename or directly inline XML content, or either ",
1768                            "a more complete description." ],
1769         "metadata": [
1770 
1771             "dcmetadata.xml",
1772 
1773             {
1774                 "#file_doc": "Can use relative or absolute paths. Exclusive of content, gdal_metadata and generated_metadata.",
1775                 "file": "dcmetadata.xml",
1776 
1777                 "#gdal_metadata_doc": "Whether to serialize GDAL metadata as GDALMultiDomainMetadata",
1778                 "gdal_metadata": false,
1779 
1780                 "#dynamic_metadata_doc":
1781                     [ "The metadata file will be generated from a template and a source file.",
1782                       "The template is a valid GMLJP2 metadata XML tree with placeholders like",
1783                       "{{{XPATH(some_xpath_expression)}}}",
1784                       "that are evaluated from the source XML file. Typical use case",
1785                       "is to generate a gmljp2:eopMetadata from the XML metadata",
1786                       "provided by the image provider in their own particular format." ],
1787                 "dynamic_metadata" :
1788                 {
1789                     "template": "my_template.xml",
1790                     "source": "my_source.xml"
1791                 },
1792 
1793                 "#content": "Exclusive of file. Inline XML metadata content",
1794                 "content": "<gmljp2:metadata>Some simple textual metadata</gmljp2:metadata>",
1795 
1796                 "#parent_node": ["Where to put the metadata.",
1797                                  "Under CoverageCollection (default) or GridCoverage" ],
1798                 "parent_node": "CoverageCollection"
1799             }
1800         ],
1801 
1802         "#annotations_doc": [ "An array of filenames, either directly KML files",
1803                               "or other vector files recognized by GDAL that ",
1804                               "will be translated on-the-fly as KML" ],
1805         "annotations": [
1806             "my.kml"
1807         ],
1808 
1809         "#gml_filelist_doc" :[
1810             "An array of GML files. Can be either GML filenames, ",
1811             "or a more complete description" ],
1812         "gml_filelist": [
1813 
1814             "my.gml",
1815 
1816             {
1817                 "#file_doc": "Can use relative or absolute paths. Exclusive of remote_resource",
1818                 "file": "converted/test_0.gml",
1819 
1820                 "#remote_resource_doc": "URL of a feature collection that must be referenced through a xlink:href",
1821                 "remote_resource": "http://svn.osgeo.org/gdal/trunk/autotest/ogr/data/expected_gml_gml32.gml",
1822 
1823                 "#namespace_doc": ["The namespace in schemaLocation for which to substitute",
1824                                   "its original schemaLocation with the one provided below.",
1825                                   "Ignored for a remote_resource"],
1826                 "namespace": "http://example.com",
1827 
1828                 "#schema_location_doc": ["Value of the substituted schemaLocation. ",
1829                                          "Typically a schema box label (link)",
1830                                          "Ignored for a remote_resource"],
1831                 "schema_location": "gmljp2://xml/schema_0.xsd",
1832 
1833                 "#inline_doc": [
1834                     "Whether to inline the content, or put it in a separate xml box. Default is true",
1835                     "Ignored for a remote_resource." ],
1836                 "inline": true,
1837 
1838                 "#parent_node": ["Where to put the FeatureCollection.",
1839                                  "Under CoverageCollection (default) or GridCoverage" ],
1840                 "parent_node": "CoverageCollection"
1841             }
1842         ],
1843 
1844         "#styles_doc": [ "An array of styles. For example SLD files" ],
1845         "styles" : [
1846             {
1847                 "#file_doc": "Can use relative or absolute paths.",
1848                 "file": "my.sld",
1849 
1850                 "#parent_node": ["Where to put the FeatureCollection.",
1851                                  "Under CoverageCollection (default) or GridCoverage" ],
1852                 "parent_node": "CoverageCollection"
1853             }
1854         ],
1855 
1856         "#extensions_doc": [ "An array of extensions." ],
1857         "extensions" : [
1858             {
1859                 "#file_doc": "Can use relative or absolute paths.",
1860                 "file": "my.xml",
1861 
1862                 "#parent_node": ["Where to put the FeatureCollection.",
1863                                  "Under CoverageCollection (default) or GridCoverage" ],
1864                 "parent_node": "CoverageCollection"
1865             }
1866         ]
1867     },
1868 
1869     "#boxes_doc": "An array to describe the content of XML asoc boxes",
1870     "boxes": [
1871         {
1872             "#file_doc": "can use relative or absolute paths. Required",
1873             "file": "converted/test_0.xsd",
1874 
1875             "#label_doc": ["the label of the XML box. If not specified, will be the ",
1876                           "filename without the directory part." ],
1877             "label": "schema_0.xsd"
1878         }
1879     ]
1880 }
1881 */
1882 
1883         json_tokener* jstok = json_tokener_new();
1884         json_object* poObj = json_tokener_parse_ex(jstok, pabyContent ?
1885             reinterpret_cast<const char*>(pabyContent) : pszDefFilename, -1);
1886         CPLFree(pabyContent);
1887         if( jstok->err != json_tokener_success)
1888         {
1889             CPLError( CE_Failure, CPLE_AppDefined,
1890                         "JSON parsing error: %s (at offset %d)",
1891                         json_tokener_error_desc(jstok->err), jstok->char_offset);
1892             json_tokener_free(jstok);
1893             return nullptr;
1894         }
1895         json_tokener_free(jstok);
1896 
1897         json_object* poRootInstance = CPL_json_object_object_get(poObj, "root_instance");
1898         if( poRootInstance && json_object_get_type(poRootInstance) == json_type_object )
1899         {
1900             json_object* poGMLId = CPL_json_object_object_get(poRootInstance, "gml_id");
1901             if( poGMLId && json_object_get_type(poGMLId) == json_type_string )
1902                 osRootGMLId = json_object_get_string(poGMLId);
1903 
1904             json_object* poGridCoverageFile = CPL_json_object_object_get(poRootInstance, "grid_coverage_file");
1905             if( poGridCoverageFile && json_object_get_type(poGridCoverageFile) == json_type_string )
1906                 osGridCoverageFile = json_object_get_string(poGridCoverageFile);
1907 
1908             json_object* poGCRTFPN =
1909                 CPL_json_object_object_get(poRootInstance, "grid_coverage_range_type_field_predefined_name");
1910             if( poGCRTFPN && json_object_get_type(poGCRTFPN) == json_type_string )
1911             {
1912                 CPLString osPredefinedName( json_object_get_string(poGCRTFPN) );
1913                 if( EQUAL(osPredefinedName, "Color") )
1914                 {
1915                     osCoverageRangeTypeXML =
1916     "<swe:DataRecord>"
1917         "<swe:field name=\"Color\">"
1918             "<swe:Quantity definition=\"http://www.opengis.net/def/ogc-eo/opt/SpectralMode/Color\">"
1919                 "<swe:description>Color image</swe:description>"
1920                 "<swe:uom code=\"unity\"/>"
1921             "</swe:Quantity>"
1922         "</swe:field>"
1923     "</swe:DataRecord>";
1924                 }
1925                 else if( EQUAL(osPredefinedName, "Elevation_meter") )
1926                 {
1927                     osCoverageRangeTypeXML =
1928     "<swe:DataRecord>"
1929         "<swe:field name=\"Elevation\">"
1930             "<swe:Quantity definition=\"http://inspire.ec.europa.eu/enumeration/ElevationPropertyTypeValue/height\" "
1931                           "referenceFrame=\"http://www.opengis.net/def/crs/EPSG/0/5714\">"
1932                 "<swe:description>Elevation above sea level</swe:description>"
1933                 "<swe:uom code=\"m\"/>"
1934             "</swe:Quantity>"
1935         "</swe:field>"
1936     "</swe:DataRecord>";
1937                 }
1938                 else if( EQUAL(osPredefinedName, "Panchromatic") )
1939                 {
1940                     osCoverageRangeTypeXML =
1941     "<swe:DataRecord>"
1942         "<swe:field name=\"Panchromatic\">"
1943             "<swe:Quantity definition=\"http://www.opengis.net/def/ogc-eo/opt/SpectralMode/Panchromatic\">"
1944                 "<swe:description>Panchromatic Channel</swe:description>"
1945                 "<swe:uom code=\"unity\"/>"
1946             "</swe:Quantity>"
1947         "</swe:field>"
1948     "</swe:DataRecord>";
1949                 }
1950                 else
1951                 {
1952                     CPLError(CE_Warning, CPLE_AppDefined,
1953                              "Unrecognized value for grid_coverage_range_type_field_predefined_name");
1954                 }
1955             }
1956             else
1957             {
1958                 json_object* poGCRTFile =
1959                     CPL_json_object_object_get(poRootInstance, "grid_coverage_range_type_file");
1960                 if( poGCRTFile && json_object_get_type(poGCRTFile) == json_type_string )
1961                 {
1962                     CPLXMLTreeCloser psTmp(CPLParseXMLFile(json_object_get_string(poGCRTFile)));
1963                     if( psTmp != nullptr )
1964                     {
1965                         CPLXMLNode* psTmpRoot = GDALGMLJP2GetXMLRoot(psTmp.get());
1966                         if( psTmpRoot )
1967                         {
1968                             char* pszTmp = CPLSerializeXMLTree(psTmpRoot);
1969                             osCoverageRangeTypeXML = pszTmp;
1970                             CPLFree(pszTmp);
1971                         }
1972                     }
1973                 }
1974             }
1975 
1976             json_object* poCRSURL = CPL_json_object_object_get(poRootInstance, "crs_url");
1977             if( poCRSURL && json_object_get_type(poCRSURL) == json_type_boolean )
1978                 bCRSURL = CPL_TO_BOOL(json_object_get_boolean(poCRSURL));
1979 
1980             json_object* poMetadatas = CPL_json_object_object_get(poRootInstance, "metadata");
1981             if( poMetadatas && json_object_get_type(poMetadatas) == json_type_array )
1982             {
1983                 auto nLength = json_object_array_length(poMetadatas);
1984                 for( decltype(nLength) i = 0; i < nLength; ++i )
1985                 {
1986                     json_object* poMetadata =
1987                         json_object_array_get_idx(poMetadatas, i);
1988                     if( poMetadata &&
1989                         json_object_get_type(poMetadata) == json_type_string )
1990                     {
1991                         GMLJP2V2MetadataDesc oDesc;
1992                         const char* pszStr = json_object_get_string(poMetadata);
1993                         if( pszStr[0] == '<' )
1994                             oDesc.osContent = pszStr;
1995                         else
1996                             oDesc.osFile = pszStr;
1997                         aoMetadata.push_back(oDesc);
1998                     }
1999                     else if ( poMetadata && json_object_get_type(poMetadata) == json_type_object )
2000                     {
2001                         const char* pszFile = nullptr;
2002                         json_object* poFile = CPL_json_object_object_get(poMetadata, "file");
2003                         if( poFile && json_object_get_type(poFile) == json_type_string )
2004                             pszFile = json_object_get_string(poFile);
2005 
2006                         const char* pszContent = nullptr;
2007                         json_object* poContent = CPL_json_object_object_get(poMetadata, "content");
2008                         if( poContent && json_object_get_type(poContent) == json_type_string )
2009                             pszContent = json_object_get_string(poContent);
2010 
2011                         const char* pszTemplate = nullptr;
2012                         const char* pszSource = nullptr;
2013                         json_object* poDynamicMetadata = CPL_json_object_object_get(poMetadata, "dynamic_metadata");
2014                         if( poDynamicMetadata && json_object_get_type(poDynamicMetadata) == json_type_object )
2015                         {
2016 #ifdef HAVE_LIBXML2
2017                             if( CPLTestBool(CPLGetConfigOption("GDAL_DEBUG_PROCESS_DYNAMIC_METADATA", "YES")) )
2018                             {
2019                                 json_object* poTemplate = CPL_json_object_object_get(poDynamicMetadata, "template");
2020                                 if( poTemplate && json_object_get_type(poTemplate) == json_type_string )
2021                                     pszTemplate = json_object_get_string(poTemplate);
2022 
2023                                 json_object* poSource = CPL_json_object_object_get(poDynamicMetadata, "source");
2024                                 if( poSource && json_object_get_type(poSource) == json_type_string )
2025                                     pszSource = json_object_get_string(poSource);
2026                             }
2027                             else
2028 #endif
2029                             {
2030                             CPLError(CE_Warning, CPLE_NotSupported,
2031                                      "dynamic_metadata not supported since libxml2 is not available");
2032                             }
2033                         }
2034 
2035                         bool bGDALMetadata = false;
2036                         json_object* poGDALMetadata = CPL_json_object_object_get(poMetadata, "gdal_metadata");
2037                         if( poGDALMetadata && json_object_get_type(poGDALMetadata) == json_type_boolean )
2038                             bGDALMetadata = CPL_TO_BOOL(
2039                                 json_object_get_boolean(poGDALMetadata));
2040 
2041                         if( pszFile != nullptr || pszContent != nullptr ||
2042                             (pszTemplate != nullptr && pszSource != nullptr) ||
2043                             bGDALMetadata )
2044                         {
2045                             GMLJP2V2MetadataDesc oDesc;
2046                             if( pszFile )
2047                                 oDesc.osFile = pszFile;
2048                             if( pszContent )
2049                                 oDesc.osContent = pszContent;
2050                             if( pszTemplate )
2051                                 oDesc.osTemplateFile = pszTemplate;
2052                             if( pszSource )
2053                                 oDesc.osSourceFile = pszSource;
2054                             oDesc.bGDALMetadata = bGDALMetadata;
2055 
2056                             json_object* poLocation = CPL_json_object_object_get(poMetadata, "parent_node");
2057                             if( poLocation && json_object_get_type(poLocation) == json_type_string )
2058                             {
2059                                 const char* pszLocation = json_object_get_string(poLocation);
2060                                 if( EQUAL(pszLocation, "CoverageCollection") )
2061                                     oDesc.bParentCoverageCollection  = TRUE;
2062                                 else if( EQUAL(pszLocation, "GridCoverage") )
2063                                     oDesc.bParentCoverageCollection = FALSE;
2064                                 else
2065                                     CPLError(CE_Warning, CPLE_NotSupported,
2066                                              "metadata[].parent_node should be CoverageCollection or GridCoverage");
2067                             }
2068 
2069                             aoMetadata.push_back(oDesc);
2070                         }
2071                     }
2072                 }
2073             }
2074 
2075             json_object* poAnnotations = CPL_json_object_object_get(poRootInstance, "annotations");
2076             if( poAnnotations && json_object_get_type(poAnnotations) == json_type_array )
2077             {
2078                 auto nLength = json_object_array_length(poAnnotations);
2079                 for( decltype(nLength) i = 0; i < nLength; ++i )
2080                 {
2081                     json_object* poAnnotation = json_object_array_get_idx(poAnnotations, i);
2082                     if( poAnnotation && json_object_get_type(poAnnotation) == json_type_string )
2083                     {
2084                         GMLJP2V2AnnotationDesc oDesc;
2085                         oDesc.osFile = json_object_get_string(poAnnotation);
2086                         aoAnnotations.push_back(oDesc);
2087                     }
2088                 }
2089             }
2090 
2091             json_object* poGMLFileList =
2092                 CPL_json_object_object_get(poRootInstance, "gml_filelist");
2093             if( poGMLFileList &&
2094                 json_object_get_type(poGMLFileList) == json_type_array )
2095             {
2096                 auto nLength = json_object_array_length(poGMLFileList);
2097                 for( decltype(nLength) i = 0; i < nLength; ++i )
2098                 {
2099                     json_object* poGMLFile =
2100                         json_object_array_get_idx(poGMLFileList, i);
2101                     if( poGMLFile &&
2102                         json_object_get_type(poGMLFile) == json_type_object )
2103                     {
2104                         const char* pszFile = nullptr;
2105                         json_object* poFile = CPL_json_object_object_get(poGMLFile, "file");
2106                         if( poFile && json_object_get_type(poFile) == json_type_string )
2107                             pszFile = json_object_get_string(poFile);
2108 
2109                         const char* pszRemoteResource = nullptr;
2110                         json_object* poRemoteResource = CPL_json_object_object_get(poGMLFile, "remote_resource");
2111                         if( poRemoteResource && json_object_get_type(poRemoteResource) == json_type_string )
2112                             pszRemoteResource = json_object_get_string(poRemoteResource);
2113 
2114                         if( pszFile || pszRemoteResource )
2115                         {
2116                             GMLJP2V2GMLFileDesc oDesc;
2117                             if( pszFile )
2118                                 oDesc.osFile = pszFile;
2119                             else if( pszRemoteResource )
2120                                 oDesc.osRemoteResource = pszRemoteResource;
2121 
2122                             json_object* poNamespacePrefix = CPL_json_object_object_get(poGMLFile, "namespace_prefix");
2123                             if( poNamespacePrefix && json_object_get_type(poNamespacePrefix) == json_type_string )
2124                                 oDesc.osNamespacePrefix = json_object_get_string(poNamespacePrefix);
2125 
2126                             json_object* poNamespace = CPL_json_object_object_get(poGMLFile, "namespace");
2127                             if( poNamespace && json_object_get_type(poNamespace) == json_type_string )
2128                                 oDesc.osNamespace = json_object_get_string(poNamespace);
2129 
2130                             json_object* poSchemaLocation = CPL_json_object_object_get(poGMLFile, "schema_location");
2131                             if( poSchemaLocation && json_object_get_type(poSchemaLocation) == json_type_string )
2132                                 oDesc.osSchemaLocation = json_object_get_string(poSchemaLocation);
2133 
2134                             json_object* poInline = CPL_json_object_object_get(poGMLFile, "inline");
2135                             if( poInline && json_object_get_type(poInline) == json_type_boolean )
2136                                 oDesc.bInline = json_object_get_boolean(poInline);
2137 
2138                             json_object* poLocation = CPL_json_object_object_get(poGMLFile, "parent_node");
2139                             if( poLocation && json_object_get_type(poLocation) == json_type_string )
2140                             {
2141                                 const char* pszLocation = json_object_get_string(poLocation);
2142                                 if( EQUAL(pszLocation, "CoverageCollection") )
2143                                     oDesc.bParentCoverageCollection  = TRUE;
2144                                 else if( EQUAL(pszLocation, "GridCoverage") )
2145                                     oDesc.bParentCoverageCollection = FALSE;
2146                                 else
2147                                     CPLError(CE_Warning, CPLE_NotSupported,
2148                                              "gml_filelist[].parent_node should be CoverageCollection or GridCoverage");
2149                             }
2150 
2151                             aoGMLFiles.push_back(oDesc);
2152                         }
2153                     }
2154                     else if( poGMLFile && json_object_get_type(poGMLFile) == json_type_string )
2155                     {
2156                         GMLJP2V2GMLFileDesc oDesc;
2157                         oDesc.osFile = json_object_get_string(poGMLFile);
2158                         aoGMLFiles.push_back(oDesc);
2159                     }
2160                 }
2161             }
2162 
2163             json_object* poStyles = CPL_json_object_object_get(poRootInstance, "styles");
2164             if( poStyles && json_object_get_type(poStyles) == json_type_array )
2165             {
2166                 auto nLength = json_object_array_length(poStyles);
2167                 for( decltype(nLength) i = 0; i < nLength; ++i )
2168                 {
2169                     json_object* poStyle = json_object_array_get_idx(poStyles, i);
2170                     if( poStyle && json_object_get_type(poStyle) == json_type_object )
2171                     {
2172                         const char* pszFile = nullptr;
2173                         json_object* poFile = CPL_json_object_object_get(poStyle, "file");
2174                         if( poFile && json_object_get_type(poFile) == json_type_string )
2175                             pszFile = json_object_get_string(poFile);
2176 
2177                         if( pszFile )
2178                         {
2179                             GMLJP2V2StyleDesc oDesc;
2180                             oDesc.osFile = pszFile;
2181 
2182                             json_object* poLocation = CPL_json_object_object_get(poStyle, "parent_node");
2183                             if( poLocation && json_object_get_type(poLocation) == json_type_string )
2184                             {
2185                                 const char* pszLocation = json_object_get_string(poLocation);
2186                                 if( EQUAL(pszLocation, "CoverageCollection") )
2187                                     oDesc.bParentCoverageCollection  = TRUE;
2188                                 else if( EQUAL(pszLocation, "GridCoverage") )
2189                                     oDesc.bParentCoverageCollection = FALSE;
2190                                 else
2191                                     CPLError(CE_Warning, CPLE_NotSupported,
2192                                              "styles[].parent_node should be CoverageCollection or GridCoverage");
2193                             }
2194 
2195                             aoStyles.push_back(oDesc);
2196                         }
2197                     }
2198                     else if( poStyle && json_object_get_type(poStyle) == json_type_string )
2199                     {
2200                         GMLJP2V2StyleDesc oDesc;
2201                         oDesc.osFile = json_object_get_string(poStyle);
2202                         aoStyles.push_back(oDesc);
2203                     }
2204                 }
2205             }
2206 
2207             json_object* poExtensions = CPL_json_object_object_get(poRootInstance, "extensions");
2208             if( poExtensions && json_object_get_type(poExtensions) == json_type_array )
2209             {
2210                 auto nLength = json_object_array_length(poExtensions);
2211                 for( decltype(nLength) i = 0; i < nLength; ++i )
2212                 {
2213                     json_object* poExtension = json_object_array_get_idx(poExtensions, i);
2214                     if( poExtension && json_object_get_type(poExtension) == json_type_object )
2215                     {
2216                         const char* pszFile = nullptr;
2217                         json_object* poFile = CPL_json_object_object_get(poExtension, "file");
2218                         if( poFile && json_object_get_type(poFile) == json_type_string )
2219                             pszFile = json_object_get_string(poFile);
2220 
2221                         if( pszFile )
2222                         {
2223                             GMLJP2V2ExtensionDesc oDesc;
2224                             oDesc.osFile = pszFile;
2225 
2226                             json_object* poLocation = CPL_json_object_object_get(poExtension, "parent_node");
2227                             if( poLocation && json_object_get_type(poLocation) == json_type_string )
2228                             {
2229                                 const char* pszLocation = json_object_get_string(poLocation);
2230                                 if( EQUAL(pszLocation, "CoverageCollection") )
2231                                     oDesc.bParentCoverageCollection  = TRUE;
2232                                 else if( EQUAL(pszLocation, "GridCoverage") )
2233                                     oDesc.bParentCoverageCollection = FALSE;
2234                                 else
2235                                     CPLError(CE_Warning, CPLE_NotSupported,
2236                                              "extensions[].parent_node should be CoverageCollection or GridCoverage");
2237                             }
2238 
2239                             aoExtensions.push_back(oDesc);
2240                         }
2241                     }
2242                     else if( poExtension && json_object_get_type(poExtension) == json_type_string )
2243                     {
2244                         GMLJP2V2ExtensionDesc oDesc;
2245                         oDesc.osFile = json_object_get_string(poExtension);
2246                         aoExtensions.push_back(oDesc);
2247                     }
2248                 }
2249             }
2250         }
2251 
2252         json_object* poBoxes = CPL_json_object_object_get(poObj, "boxes");
2253         if( poBoxes && json_object_get_type(poBoxes) == json_type_array )
2254         {
2255             auto nLength = json_object_array_length(poBoxes);
2256             for( decltype(nLength) i = 0; i < nLength; ++i )
2257             {
2258                 json_object* poBox = json_object_array_get_idx(poBoxes, i);
2259                 if( poBox && json_object_get_type(poBox) == json_type_object )
2260                 {
2261                     json_object* poFile = CPL_json_object_object_get(poBox, "file");
2262                     if( poFile &&
2263                         json_object_get_type(poFile) == json_type_string )
2264                     {
2265                         GMLJP2V2BoxDesc oDesc;
2266                         oDesc.osFile = json_object_get_string(poFile);
2267 
2268                         json_object* poLabel =
2269                             CPL_json_object_object_get(poBox, "label");
2270                         if( poLabel &&
2271                             json_object_get_type(poLabel) == json_type_string )
2272                             oDesc.osLabel = json_object_get_string(poLabel);
2273                         else
2274                             oDesc.osLabel = CPLGetFilename(oDesc.osFile);
2275 
2276                         aoBoxes.push_back(oDesc);
2277                     }
2278                 }
2279                 else if( poBox &&
2280                          json_object_get_type(poBox) == json_type_string )
2281                 {
2282                     GMLJP2V2BoxDesc oDesc;
2283                     oDesc.osFile = json_object_get_string(poBox);
2284                     oDesc.osLabel = CPLGetFilename(oDesc.osFile);
2285                     aoBoxes.push_back(oDesc);
2286                 }
2287             }
2288         }
2289 
2290         json_object_put(poObj);
2291 
2292         // Check that if a GML file points to an internal schemaLocation,
2293         // the matching box really exists.
2294         for( const auto& oGMLFile: aoGMLFiles )
2295         {
2296             if( !oGMLFile.osSchemaLocation.empty() &&
2297                 STARTS_WITH(oGMLFile.osSchemaLocation, "gmljp2://xml/") )
2298             {
2299                 const char* pszLookedLabel =
2300                     oGMLFile.osSchemaLocation.c_str() +
2301                     strlen("gmljp2://xml/");
2302                 bool bFound = false;
2303                 for( int j = 0;
2304                      !bFound && j < static_cast<int>(aoBoxes.size());
2305                      ++j )
2306                     bFound = (strcmp(pszLookedLabel, aoBoxes[j].osLabel) == 0);
2307                 if( !bFound )
2308                 {
2309                     CPLError(
2310                         CE_Warning, CPLE_AppDefined,
2311                         "GML file %s has a schema_location=%s, "
2312                         "but no box with label %s is defined",
2313                         oGMLFile.osFile.c_str(),
2314                         oGMLFile.osSchemaLocation.c_str(),
2315                         pszLookedLabel);
2316                 }
2317             }
2318         }
2319 
2320         // Read custom grid coverage file.
2321         if( !osGridCoverageFile.empty() )
2322         {
2323             CPLXMLTreeCloser psTmp(CPLParseXMLFile(osGridCoverageFile));
2324             if( psTmp == nullptr )
2325                 return nullptr;
2326             CPLXMLNode* psTmpRoot = GDALGMLJP2GetXMLRoot(psTmp.get());
2327             if( psTmpRoot )
2328             {
2329                 char* pszTmp = CPLSerializeXMLTree(psTmpRoot);
2330                 osGridCoverage = pszTmp;
2331                 CPLFree(pszTmp);
2332             }
2333         }
2334     }
2335 
2336     CPLString osDictBox;
2337     CPLString osDoc;
2338 
2339     if( osGridCoverage.empty() )
2340     {
2341 /* -------------------------------------------------------------------- */
2342 /*      Prepare GMLJP2RectifiedGridCoverage                             */
2343 /* -------------------------------------------------------------------- */
2344         int nEPSGCode = 0;
2345         double adfOrigin[2];
2346         double adfXVector[2];
2347         double adfYVector[2];
2348         const char* pszComment = "";
2349         int bNeedAxisFlip = FALSE;
2350         if( !GetGMLJP2GeoreferencingInfo( nEPSGCode, adfOrigin,
2351                                         adfXVector, adfYVector,
2352                                         pszComment, osDictBox, bNeedAxisFlip ) )
2353         {
2354             return nullptr;
2355         }
2356 
2357         char szSRSName[100] = {0};
2358         if( nEPSGCode != 0 )
2359         {
2360             if( bCRSURL )
2361                 snprintf( szSRSName, sizeof(szSRSName),
2362                           "http://www.opengis.net/def/crs/EPSG/0/%d", nEPSGCode );
2363             else
2364                 snprintf( szSRSName, sizeof(szSRSName),
2365                           "urn:ogc:def:crs:EPSG::%d", nEPSGCode );
2366         }
2367         else
2368             snprintf( szSRSName, sizeof(szSRSName), "%s",
2369                     "gmljp2://xml/CRSDictionary.gml#ogrcrs1" );
2370 
2371 
2372         // Compute bounding box
2373         double dfX1 = adfGeoTransform[0];
2374         double dfX2 = adfGeoTransform[0] + nXSize * adfGeoTransform[1];
2375         double dfX3 = adfGeoTransform[0] +                               nYSize * adfGeoTransform[2];
2376         double dfX4 = adfGeoTransform[0] + nXSize * adfGeoTransform[1] + nYSize * adfGeoTransform[2];
2377         double dfY1 = adfGeoTransform[3];
2378         double dfY2 = adfGeoTransform[3] + nXSize * adfGeoTransform[4];
2379         double dfY3 = adfGeoTransform[3] +                               nYSize * adfGeoTransform[5];
2380         double dfY4 = adfGeoTransform[3] + nXSize * adfGeoTransform[4] + nYSize * adfGeoTransform[5];
2381         double dfLCX = std::min(std::min(dfX1, dfX2), std::min(dfX3, dfX4));
2382         double dfLCY = std::min(std::min(dfY1, dfY2), std::min(dfY3, dfY4));
2383         double dfUCX = std::max(std::max(dfX1, dfX2), std::max(dfX3, dfX4));
2384         double dfUCY = std::max(std::max(dfY1, dfY2), std::max(dfY3, dfY4));
2385         if( bNeedAxisFlip )
2386         {
2387             std::swap(dfLCX, dfLCY);
2388             std::swap(dfUCX, dfUCY);
2389         }
2390 
2391         osGridCoverage.Printf(
2392 "   <gmljp2:GMLJP2RectifiedGridCoverage gml:id=\"RGC_1_%s\">\n"
2393 "     <gml:boundedBy>\n"
2394 "       <gml:Envelope srsDimension=\"2\" srsName=\"%s\">\n"
2395 "         <gml:lowerCorner>%.15g %.15g</gml:lowerCorner>\n"
2396 "         <gml:upperCorner>%.15g %.15g</gml:upperCorner>\n"
2397 "       </gml:Envelope>\n"
2398 "     </gml:boundedBy>\n"
2399 "     <gml:domainSet>\n"
2400 "      <gml:RectifiedGrid gml:id=\"RGC_1_GRID_%s\" dimension=\"2\" srsName=\"%s\">\n"
2401 "       <gml:limits>\n"
2402 "         <gml:GridEnvelope>\n"
2403 "           <gml:low>0 0</gml:low>\n"
2404 "           <gml:high>%d %d</gml:high>\n"
2405 "         </gml:GridEnvelope>\n"
2406 "       </gml:limits>\n"
2407 "       <gml:axisName>x</gml:axisName>\n"
2408 "       <gml:axisName>y</gml:axisName>\n"
2409 "       <gml:origin>\n"
2410 "         <gml:Point gml:id=\"P0001\" srsName=\"%s\">\n"
2411 "           <gml:pos>%.15g %.15g</gml:pos>\n"
2412 "         </gml:Point>\n"
2413 "       </gml:origin>\n"
2414 "%s"
2415 "       <gml:offsetVector srsName=\"%s\">%.15g %.15g</gml:offsetVector>\n"
2416 "       <gml:offsetVector srsName=\"%s\">%.15g %.15g</gml:offsetVector>\n"
2417 "      </gml:RectifiedGrid>\n"
2418 "     </gml:domainSet>\n"
2419 "     <gml:rangeSet>\n"
2420 "      <gml:File>\n"
2421 "        <gml:rangeParameters/>\n"
2422 "        <gml:fileName>gmljp2://codestream/0</gml:fileName>\n"
2423 "        <gml:fileStructure>inapplicable</gml:fileStructure>\n"
2424 "      </gml:File>\n"
2425 "     </gml:rangeSet>\n"
2426 "     <gmlcov:rangeType>%s</gmlcov:rangeType>\n"
2427 "   </gmljp2:GMLJP2RectifiedGridCoverage>\n",
2428             osRootGMLId.c_str(),
2429             szSRSName,
2430             dfLCX, dfLCY,
2431             dfUCX, dfUCY,
2432             osRootGMLId.c_str(),
2433             szSRSName,
2434             nXSize-1, nYSize-1, szSRSName, adfOrigin[0], adfOrigin[1],
2435             pszComment,
2436             szSRSName, adfXVector[0], adfXVector[1],
2437             szSRSName, adfYVector[0], adfYVector[1],
2438             osCoverageRangeTypeXML.c_str() );
2439     }
2440 
2441 /* -------------------------------------------------------------------- */
2442 /*      Main node.                                                      */
2443 /* -------------------------------------------------------------------- */
2444 
2445     // Per http://docs.opengeospatial.org/is/08-085r5/08-085r5.html#requirement_11
2446     osDoc.Printf(
2447 //"<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n"
2448 "<gmljp2:GMLJP2CoverageCollection gml:id=\"%s\"\n"
2449 "     xmlns:gml=\"http://www.opengis.net/gml/3.2\"\n"
2450 "     xmlns:gmlcov=\"http://www.opengis.net/gmlcov/1.0\"\n"
2451 "     xmlns:gmljp2=\"http://www.opengis.net/gmljp2/2.0\"\n"
2452 "     xmlns:swe=\"http://www.opengis.net/swe/2.0\"\n"
2453 "     xmlns:xsi=\"http://www.w3.org/2001/XMLSchema-instance\"\n"
2454 "     xsi:schemaLocation=\"http://www.opengis.net/gmljp2/2.0 http://schemas.opengis.net/gmljp2/2.0/gmljp2.xsd\">\n"
2455 "  <gml:domainSet nilReason=\"inapplicable\"/>\n"
2456 "  <gml:rangeSet>\n"
2457 "    <gml:DataBlock>\n"
2458 "       <gml:rangeParameters nilReason=\"inapplicable\"/>\n"
2459 "       <gml:doubleOrNilReasonTupleList>inapplicable</gml:doubleOrNilReasonTupleList>\n"
2460 "     </gml:DataBlock>\n"
2461 "  </gml:rangeSet>\n"
2462 "  <gmlcov:rangeType>\n"
2463 "    <swe:DataRecord>\n"
2464 "      <swe:field name=\"Collection\"> </swe:field>\n"
2465 "    </swe:DataRecord>\n"
2466 "  </gmlcov:rangeType>\n"
2467 "  <gmljp2:featureMember>\n"
2468 "%s"
2469 "  </gmljp2:featureMember>\n"
2470 "</gmljp2:GMLJP2CoverageCollection>\n",
2471                  osRootGMLId.c_str(),
2472                  osGridCoverage.c_str() );
2473 
2474 /* -------------------------------------------------------------------- */
2475 /*      Process metadata, annotations and features collections.         */
2476 /* -------------------------------------------------------------------- */
2477     std::vector<CPLString> aosTmpFiles;
2478     if( !aoMetadata.empty() || !aoAnnotations.empty() || !aoGMLFiles.empty() ||
2479         !aoStyles.empty() || !aoExtensions.empty() )
2480     {
2481         CPLXMLTreeCloser psRoot(CPLParseXMLString(osDoc));
2482         CPLAssert(psRoot);
2483         CPLXMLNode* psGMLJP2CoverageCollection = GDALGMLJP2GetXMLRoot(psRoot.get());
2484         CPLAssert(psGMLJP2CoverageCollection);
2485 
2486         for( const auto& oMetadata: aoMetadata )
2487         {
2488             CPLXMLTreeCloser psMetadata(nullptr);
2489             if( !oMetadata.osFile.empty() )
2490                 psMetadata = CPLXMLTreeCloser(CPLParseXMLFile(oMetadata.osFile));
2491             else if( !oMetadata.osContent.empty() )
2492                 psMetadata = CPLXMLTreeCloser(CPLParseXMLString(oMetadata.osContent));
2493             else if( oMetadata.bGDALMetadata )
2494             {
2495                 psMetadata = CPLXMLTreeCloser(CreateGDALMultiDomainMetadataXML(poSrcDS, TRUE));
2496                 if( psMetadata )
2497                 {
2498                     CPLSetXMLValue(psMetadata.get(), "#xmlns", "http://gdal.org");
2499                     CPLXMLNode* psNewMetadata =
2500                         CPLCreateXMLNode(nullptr, CXT_Element, "gmljp2:metadata");
2501                     CPLAddXMLChild(psNewMetadata, psMetadata.release());
2502                     psMetadata = CPLXMLTreeCloser(psNewMetadata);
2503                 }
2504             }
2505             else
2506                 psMetadata = CPLXMLTreeCloser(
2507                     GDALGMLJP2GenerateMetadata(oMetadata.osTemplateFile,
2508                                                oMetadata.osSourceFile));
2509             if( psMetadata == nullptr )
2510                 continue;
2511             CPLXMLNode* psMetadataRoot = GDALGMLJP2GetXMLRoot(psMetadata.get());
2512             if( psMetadataRoot )
2513             {
2514                 if( strcmp(psMetadataRoot->pszValue,
2515                            "eop:EarthObservation") == 0 )
2516                 {
2517                     CPLXMLNode* psNewMetadata = CPLCreateXMLNode(nullptr, CXT_Element, "gmljp2:eopMetadata");
2518                     CPLAddXMLChild(psNewMetadata, CPLCloneXMLTree(psMetadataRoot));
2519                     psMetadataRoot = psNewMetadata;
2520                     psMetadata = CPLXMLTreeCloser(psNewMetadata);
2521                 }
2522                 if( strcmp(psMetadataRoot->pszValue, "gmljp2:isoMetadata") != 0 &&
2523                     strcmp(psMetadataRoot->pszValue, "gmljp2:eopMetadata") != 0 &&
2524                     strcmp(psMetadataRoot->pszValue, "gmljp2:dcMetadata") != 0 &&
2525                     strcmp(psMetadataRoot->pszValue, "gmljp2:metadata") != 0 )
2526                 {
2527                     CPLError(CE_Warning, CPLE_AppDefined,
2528                              "The metadata root node should be one of gmljp2:isoMetadata, "
2529                              "gmljp2:eopMetadata, gmljp2:dcMetadata or gmljp2:metadata");
2530                 }
2531                 else if( oMetadata.bParentCoverageCollection )
2532                 {
2533                     /* Insert the gmlcov:metadata link as the next sibling of */
2534                     /* GMLJP2CoverageCollection.rangeType */
2535                     CPLXMLNode* psRangeType =
2536                         CPLGetXMLNode(psGMLJP2CoverageCollection, "gmlcov:rangeType");
2537                     CPLAssert(psRangeType);
2538                     CPLXMLNode* psNodeAfterWhichToInsert = psRangeType;
2539                     CPLXMLNode* psNext = psNodeAfterWhichToInsert->psNext;
2540                     while( psNext != nullptr && psNext->eType == CXT_Element &&
2541                            strcmp(psNext->pszValue, "gmlcov:metadata") == 0 )
2542                     {
2543                         psNodeAfterWhichToInsert = psNext;
2544                         psNext = psNext->psNext;
2545                     }
2546                     psNodeAfterWhichToInsert->psNext = nullptr;
2547                     CPLXMLNode* psGMLCovMetadata = CPLCreateXMLNode(
2548                         psGMLJP2CoverageCollection, CXT_Element, "gmlcov:metadata" );
2549                     psGMLCovMetadata->psNext = psNext;
2550                     CPLXMLNode* psGMLJP2Metadata = CPLCreateXMLNode(
2551                         psGMLCovMetadata, CXT_Element, "gmljp2:Metadata" );
2552                     CPLAddXMLChild( psGMLJP2Metadata, CPLCloneXMLTree(psMetadataRoot) );
2553                 }
2554                 else
2555                 {
2556                     /* Insert the gmlcov:metadata link as the last child of */
2557                     /* GMLJP2RectifiedGridCoverage typically */
2558                     CPLXMLNode* psFeatureMemberOfGridCoverage =
2559                         CPLGetXMLNode(psGMLJP2CoverageCollection, "gmljp2:featureMember");
2560                     CPLAssert(psFeatureMemberOfGridCoverage);
2561                     CPLXMLNode* psGridCoverage = psFeatureMemberOfGridCoverage->psChild;
2562                     CPLAssert(psGridCoverage);
2563                     CPLXMLNode* psGMLCovMetadata = CPLCreateXMLNode(
2564                         psGridCoverage, CXT_Element, "gmlcov:metadata" );
2565                     CPLXMLNode* psGMLJP2Metadata = CPLCreateXMLNode(
2566                         psGMLCovMetadata, CXT_Element, "gmljp2:Metadata" );
2567                     CPLAddXMLChild( psGMLJP2Metadata, CPLCloneXMLTree(psMetadataRoot) );
2568                 }
2569             }
2570         }
2571 
2572         bool bRootHasXLink = false;
2573 
2574         // Examples of inline or reference feature collections can be found
2575         // in http://schemas.opengis.net/gmljp2/2.0/examples/gmljp2.xml
2576         for( int i = 0; i < static_cast<int>(aoGMLFiles.size()); ++i )
2577         {
2578             // Is the file already a GML file?
2579             CPLXMLTreeCloser psGMLFile(nullptr);
2580             if( !aoGMLFiles[i].osFile.empty() )
2581             {
2582                 if( EQUAL(CPLGetExtension(aoGMLFiles[i].osFile), "gml") ||
2583                     EQUAL(CPLGetExtension(aoGMLFiles[i].osFile), "xml") )
2584                 {
2585                     psGMLFile = CPLXMLTreeCloser(CPLParseXMLFile(aoGMLFiles[i].osFile));
2586                 }
2587                 GDALDriverH hDrv = nullptr;
2588                 if( psGMLFile == nullptr )
2589                 {
2590                     hDrv = GDALIdentifyDriver(aoGMLFiles[i].osFile, nullptr);
2591                     if( hDrv == nullptr )
2592                     {
2593                         CPLError(CE_Failure, CPLE_AppDefined,
2594                                  "%s is no a GDAL recognized file",
2595                                  aoGMLFiles[i].osFile.c_str());
2596                         continue;
2597                     }
2598                 }
2599                 GDALDriverH hGMLDrv = GDALGetDriverByName("GML");
2600                 if( psGMLFile == nullptr && hDrv == hGMLDrv )
2601                 {
2602                     // Yes, parse it
2603                     psGMLFile = CPLXMLTreeCloser(CPLParseXMLFile(aoGMLFiles[i].osFile));
2604                 }
2605                 else if( psGMLFile == nullptr )
2606                 {
2607                     if( hGMLDrv == nullptr )
2608                     {
2609                         CPLError(CE_Failure, CPLE_AppDefined,
2610                                 "Cannot translate %s to GML",
2611                                 aoGMLFiles[i].osFile.c_str());
2612                         continue;
2613                     }
2614 
2615                     // On-the-fly translation to GML 3.2
2616                     GDALDatasetH hSrcDS = GDALOpenEx(aoGMLFiles[i].osFile, 0, nullptr, nullptr, nullptr);
2617                     if( hSrcDS )
2618                     {
2619                         CPLString osTmpFile = CPLSPrintf("/vsimem/gmljp2/%p/%d/%s.gml",
2620                                                         this,
2621                                                         i,
2622                                                         CPLGetBasename(aoGMLFiles[i].osFile));
2623                         char ** papszOptions = nullptr;
2624                         papszOptions = CSLSetNameValue(papszOptions,
2625                                                        "FORMAT", "GML3.2");
2626                         papszOptions = CSLSetNameValue(papszOptions,
2627                                 "SRSNAME_FORMAT",
2628                                 (bCRSURL) ? "OGC_URL" : "OGC_URN");
2629                         if( aoGMLFiles.size() > 1 ||
2630                             !aoGMLFiles[i].osNamespace.empty() ||
2631                             !aoGMLFiles[i].osNamespacePrefix.empty() )
2632                         {
2633                             papszOptions = CSLSetNameValue(papszOptions,
2634                                 "PREFIX",
2635                                     aoGMLFiles[i].osNamespacePrefix.empty() ?
2636                                         CPLSPrintf("ogr%d", i) :
2637                                         aoGMLFiles[i].osNamespacePrefix.c_str());
2638                             papszOptions = CSLSetNameValue(papszOptions,
2639                                 "TARGET_NAMESPACE",
2640                                 aoGMLFiles[i].osNamespace.empty() ?
2641                                     CPLSPrintf("http://ogr.maptools.org/%d", i) :
2642                                     aoGMLFiles[i].osNamespace.c_str());
2643                         }
2644                         GDALDatasetH hDS = GDALCreateCopy(
2645                                     hGMLDrv, osTmpFile, hSrcDS,
2646                                     FALSE,
2647                                     papszOptions, nullptr, nullptr);
2648                         CSLDestroy(papszOptions);
2649                         if( hDS )
2650                         {
2651                             GDALClose(hDS);
2652                             psGMLFile = CPLXMLTreeCloser(CPLParseXMLFile(osTmpFile));
2653                             aoGMLFiles[i].osFile = osTmpFile;
2654                             VSIUnlink(osTmpFile);
2655                             aosTmpFiles.emplace_back(CPLResetExtension(osTmpFile, "xsd"));
2656                         }
2657                         else
2658                         {
2659                             CPLError(CE_Failure, CPLE_AppDefined,
2660                                     "Conversion of %s to GML failed",
2661                                     aoGMLFiles[i].osFile.c_str());
2662                         }
2663                     }
2664                     GDALClose(hSrcDS);
2665                 }
2666                 if( psGMLFile == nullptr )
2667                     continue;
2668             }
2669 
2670             CPLXMLNode* psGMLFileRoot = psGMLFile ? GDALGMLJP2GetXMLRoot(psGMLFile.get()) : nullptr;
2671             if( psGMLFileRoot || !aoGMLFiles[i].osRemoteResource.empty() )
2672             {
2673                 CPLXMLNode *node_f;
2674                 if( aoGMLFiles[i].bParentCoverageCollection )
2675                 {
2676                     // Insert in gmljp2:featureMember.gmljp2:GMLJP2Features.gmljp2:feature
2677                     CPLXMLNode *node_fm = CPLCreateXMLNode(
2678                             psGMLJP2CoverageCollection, CXT_Element, "gmljp2:featureMember" );
2679 
2680                     CPLXMLNode *node_gf = CPLCreateXMLNode(
2681                             node_fm, CXT_Element, "gmljp2:GMLJP2Features" );
2682 
2683                     CPLSetXMLValue(node_gf, "#gml:id", CPLSPrintf("%s_GMLJP2Features_%d",
2684                                                                     osRootGMLId.c_str(),
2685                                                                     i));
2686 
2687                     node_f = CPLCreateXMLNode( node_gf, CXT_Element, "gmljp2:feature"  );
2688                 }
2689                 else
2690                 {
2691                     CPLXMLNode* psFeatureMemberOfGridCoverage =
2692                         CPLGetXMLNode(psGMLJP2CoverageCollection, "gmljp2:featureMember");
2693                     CPLAssert(psFeatureMemberOfGridCoverage);
2694                     CPLXMLNode* psGridCoverage = psFeatureMemberOfGridCoverage->psChild;
2695                     CPLAssert(psGridCoverage);
2696                     node_f = CPLCreateXMLNode( psGridCoverage, CXT_Element, "gmljp2:feature"  );
2697                 }
2698 
2699                 if( !aoGMLFiles[i].bInline || !aoGMLFiles[i].osRemoteResource.empty() )
2700                 {
2701                     if( !bRootHasXLink )
2702                     {
2703                         bRootHasXLink = true;
2704                         CPLSetXMLValue(psGMLJP2CoverageCollection, "#xmlns:xlink",
2705                                        "http://www.w3.org/1999/xlink");
2706                     }
2707                 }
2708 
2709                 if( !aoGMLFiles[i].osRemoteResource.empty() )
2710                 {
2711                     CPLSetXMLValue(node_f, "#xlink:href",
2712                                    aoGMLFiles[i].osRemoteResource.c_str());
2713                     continue;
2714                 }
2715 
2716                 CPLString osTmpFile;
2717                 if( !aoGMLFiles[i].bInline || !aoGMLFiles[i].osRemoteResource.empty() )
2718                 {
2719                     osTmpFile = CPLSPrintf("/vsimem/gmljp2/%p/%d/%s.gml",
2720                                            this,
2721                                            i,
2722                                            CPLGetBasename(aoGMLFiles[i].osFile));
2723                     aosTmpFiles.push_back(osTmpFile);
2724 
2725                     GMLJP2V2BoxDesc oDesc;
2726                     oDesc.osFile = osTmpFile;
2727                     oDesc.osLabel = CPLGetFilename(oDesc.osFile);
2728                     aoBoxes.push_back(oDesc);
2729 
2730                     CPLSetXMLValue(node_f, "#xlink:href",
2731                             CPLSPrintf("gmljp2://xml/%s", oDesc.osLabel.c_str()));
2732                 }
2733 
2734                 if( CPLGetXMLNode(psGMLFileRoot, "xmlns") == nullptr &&
2735                     CPLGetXMLNode(psGMLFileRoot, "xmlns:gml") == nullptr )
2736                 {
2737                     CPLSetXMLValue(psGMLFileRoot, "#xmlns",
2738                                    "http://www.opengis.net/gml/3.2");
2739                 }
2740 
2741                 // modify the gml id making it unique for this document
2742                 CPLXMLNode* psGMLFileGMLId =
2743                     CPLGetXMLNode(psGMLFileRoot, "gml:id");
2744                 if( psGMLFileGMLId && psGMLFileGMLId->eType == CXT_Attribute )
2745                     CPLSetXMLValue( psGMLFileGMLId, "",
2746                                     CPLSPrintf("%s_%d_%s",
2747                                                 osRootGMLId.c_str(), i,
2748                                                 psGMLFileGMLId->psChild->pszValue) );
2749                 psGMLFileGMLId = nullptr;
2750                 //PrefixAllGMLIds(psGMLFileRoot, CPLSPrintf("%s_%d_", osRootGMLId.c_str(), i));
2751 
2752                 // replace schema location
2753                 CPLXMLNode* psSchemaLocation =
2754                     CPLGetXMLNode(psGMLFileRoot, "xsi:schemaLocation");
2755                 if( psSchemaLocation && psSchemaLocation->eType == CXT_Attribute )
2756                 {
2757                     char **papszTokens = CSLTokenizeString2(
2758                         psSchemaLocation->psChild->pszValue, " \t\n",
2759                         CSLT_HONOURSTRINGS | CSLT_STRIPLEADSPACES | CSLT_STRIPENDSPACES);
2760                     CPLString osSchemaLocation;
2761 
2762                     if( CSLCount(papszTokens) == 2 &&
2763                         aoGMLFiles[i].osNamespace.empty() &&
2764                         !aoGMLFiles[i].osSchemaLocation.empty() )
2765                     {
2766                         osSchemaLocation += papszTokens[0];
2767                         osSchemaLocation += " ";
2768                         osSchemaLocation += aoGMLFiles[i].osSchemaLocation;
2769                     }
2770 
2771                     else if( CSLCount(papszTokens) == 2 &&
2772                                 (aoGMLFiles[i].osNamespace.empty() ||
2773                                 strcmp(papszTokens[0], aoGMLFiles[i].osNamespace) == 0) &&
2774                                 aoGMLFiles[i].osSchemaLocation.empty() )
2775                     {
2776                         VSIStatBufL sStat;
2777                         CPLString osXSD;
2778                         if( CSLCount(papszTokens) == 2 &&
2779                             !CPLIsFilenameRelative(papszTokens[1]) &&
2780                             VSIStatL(papszTokens[1], &sStat) == 0 )
2781                         {
2782                             osXSD = papszTokens[1];
2783                         }
2784                         else if( CSLCount(papszTokens) == 2 &&
2785                                 CPLIsFilenameRelative(papszTokens[1]) &&
2786                                 VSIStatL(CPLFormFilename(CPLGetDirname(aoGMLFiles[i].osFile),
2787                                                         papszTokens[1], nullptr),
2788                                         &sStat) == 0 )
2789                         {
2790                             osXSD = CPLFormFilename(CPLGetDirname(aoGMLFiles[i].osFile),
2791                                                         papszTokens[1], nullptr);
2792                         }
2793                         if( !osXSD.empty() )
2794                         {
2795                             GMLJP2V2BoxDesc oDesc;
2796                             oDesc.osFile = osXSD;
2797                             oDesc.osLabel = CPLGetFilename(oDesc.osFile);
2798                             osSchemaLocation += papszTokens[0];
2799                             osSchemaLocation += " ";
2800                             osSchemaLocation += "gmljp2://xml/";
2801                             osSchemaLocation += oDesc.osLabel;
2802                             int j = 0;  // Used after for.
2803                             for( ; j < static_cast<int>(aoBoxes.size()); ++j )
2804                             {
2805                                 if( aoBoxes[j].osLabel == oDesc.osLabel )
2806                                     break;
2807                             }
2808                             if( j == static_cast<int>(aoBoxes.size()) )
2809                                 aoBoxes.push_back(oDesc);
2810                         }
2811                     }
2812 
2813                     else if( (CSLCount(papszTokens) % 2) == 0 )
2814                     {
2815                         for( char** papszIter = papszTokens;
2816                              *papszIter;
2817                              papszIter += 2 )
2818                         {
2819                             if( !osSchemaLocation.empty() )
2820                                 osSchemaLocation += " ";
2821                             if( !aoGMLFiles[i].osNamespace.empty() &&
2822                                 !aoGMLFiles[i].osSchemaLocation.empty() &&
2823                                 strcmp(papszIter[0],
2824                                        aoGMLFiles[i].osNamespace) == 0 )
2825                             {
2826                                 osSchemaLocation += papszIter[0];
2827                                 osSchemaLocation += " ";
2828                                 osSchemaLocation +=
2829                                     aoGMLFiles[i].osSchemaLocation;
2830                             }
2831                             else
2832                             {
2833                                 osSchemaLocation += papszIter[0];
2834                                 osSchemaLocation += " ";
2835                                 osSchemaLocation += papszIter[1];
2836                             }
2837                         }
2838                     }
2839                     CSLDestroy(papszTokens);
2840                     CPLSetXMLValue( psSchemaLocation, "", osSchemaLocation);
2841                 }
2842 
2843                 if( aoGMLFiles[i].bInline )
2844                     CPLAddXMLChild(node_f, CPLCloneXMLTree(psGMLFileRoot));
2845                 else
2846                     CPLSerializeXMLTreeToFile( psGMLFile.get(), osTmpFile );
2847             }
2848         }
2849 
2850         // c.f.
2851         // http://schemas.opengis.net/gmljp2/2.0/examples/gmljp2_annotation.xml
2852         for( int i = 0; i < static_cast<int>(aoAnnotations.size()); ++i )
2853         {
2854             // Is the file already a KML file?
2855             CPLXMLTreeCloser psKMLFile(nullptr);
2856             if( EQUAL(CPLGetExtension(aoAnnotations[i].osFile), "kml") )
2857                  psKMLFile = CPLXMLTreeCloser(CPLParseXMLFile(aoAnnotations[i].osFile));
2858             GDALDriverH hDrv = nullptr;
2859             if( psKMLFile == nullptr )
2860             {
2861                 hDrv = GDALIdentifyDriver(aoAnnotations[i].osFile, nullptr);
2862                 if( hDrv == nullptr )
2863                 {
2864                     CPLError(CE_Failure, CPLE_AppDefined, "%s is no a GDAL recognized file",
2865                              aoAnnotations[i].osFile.c_str());
2866                     continue;
2867                 }
2868             }
2869             GDALDriverH hKMLDrv = GDALGetDriverByName("KML");
2870             GDALDriverH hLIBKMLDrv = GDALGetDriverByName("LIBKML");
2871             if( psKMLFile == nullptr && (hDrv == hKMLDrv || hDrv == hLIBKMLDrv) )
2872             {
2873                 // Yes, parse it
2874                 psKMLFile = CPLXMLTreeCloser(CPLParseXMLFile(aoAnnotations[i].osFile));
2875             }
2876             else if( psKMLFile == nullptr )
2877             {
2878                 if( hKMLDrv == nullptr && hLIBKMLDrv == nullptr )
2879                 {
2880                     CPLError(CE_Failure, CPLE_AppDefined, "Cannot translate %s to KML",
2881                              aoAnnotations[i].osFile.c_str());
2882                     continue;
2883                 }
2884 
2885                 // On-the-fly translation to KML
2886                 GDALDatasetH hSrcDS = GDALOpenEx(aoAnnotations[i].osFile, 0, nullptr, nullptr, nullptr);
2887                 if( hSrcDS )
2888                 {
2889                     CPLString osTmpFile = CPLSPrintf("/vsimem/gmljp2/%p/%d/%s.kml",
2890                                                      this,
2891                                                      i,
2892                                                      CPLGetBasename(aoAnnotations[i].osFile));
2893                     char** papszOptions = nullptr;
2894                     if( aoAnnotations.size() > 1 )
2895                     {
2896                         papszOptions = CSLSetNameValue(
2897                             papszOptions, "DOCUMENT_ID",
2898                             CPLSPrintf("root_doc_%d", i));
2899                     }
2900                     GDALDatasetH hDS = GDALCreateCopy(hLIBKMLDrv ? hLIBKMLDrv : hKMLDrv,
2901                                                       osTmpFile, hSrcDS,
2902                                                       FALSE, papszOptions, nullptr, nullptr);
2903                     CSLDestroy(papszOptions);
2904                     if( hDS )
2905                     {
2906                         GDALClose(hDS);
2907                         psKMLFile = CPLXMLTreeCloser(CPLParseXMLFile(osTmpFile));
2908                         aoAnnotations[i].osFile = osTmpFile;
2909                         VSIUnlink(osTmpFile);
2910                     }
2911                     else
2912                     {
2913                         CPLError(CE_Failure, CPLE_AppDefined, "Conversion of %s to KML failed",
2914                                   aoAnnotations[i].osFile.c_str());
2915                     }
2916                 }
2917                 GDALClose(hSrcDS);
2918             }
2919             if( psKMLFile == nullptr )
2920                 continue;
2921 
2922             CPLXMLNode* psKMLFileRoot = GDALGMLJP2GetXMLRoot(psKMLFile.get());
2923             if( psKMLFileRoot )
2924             {
2925                 CPLXMLNode* psFeatureMemberOfGridCoverage =
2926                     CPLGetXMLNode(psGMLJP2CoverageCollection, "gmljp2:featureMember");
2927                 CPLAssert(psFeatureMemberOfGridCoverage);
2928                 CPLXMLNode* psGridCoverage = psFeatureMemberOfGridCoverage->psChild;
2929                 CPLAssert(psGridCoverage);
2930                 CPLXMLNode *psAnnotation = CPLCreateXMLNode(
2931                             psGridCoverage, CXT_Element, "gmljp2:annotation" );
2932 
2933                 /* Add a xsi:schemaLocation if not already present */
2934                 if( psKMLFileRoot->eType == CXT_Element &&
2935                     strcmp(psKMLFileRoot->pszValue, "kml") == 0 &&
2936                     CPLGetXMLNode(psKMLFileRoot, "xsi:schemaLocation") == nullptr &&
2937                     strcmp(CPLGetXMLValue(psKMLFileRoot, "xmlns", ""),
2938                            "http://www.opengis.net/kml/2.2") == 0  )
2939                 {
2940                     CPLSetXMLValue(psKMLFileRoot, "#xsi:schemaLocation",
2941                                    "http://www.opengis.net/kml/2.2 http://schemas.opengis.net/kml/2.2.0/ogckml22.xsd");
2942                 }
2943 
2944                 CPLAddXMLChild(psAnnotation, CPLCloneXMLTree(psKMLFileRoot));
2945             }
2946         }
2947 
2948         // Add styles.
2949         for( const auto& oStyle: aoStyles )
2950         {
2951             CPLXMLTreeCloser psStyle(CPLParseXMLFile(oStyle.osFile));
2952             if( psStyle == nullptr )
2953                 continue;
2954 
2955             CPLXMLNode* psStyleRoot = GDALGMLJP2GetXMLRoot(psStyle.get());
2956             if( psStyleRoot )
2957             {
2958                 CPLXMLNode *psGMLJP2Style = nullptr;
2959                 if( oStyle.bParentCoverageCollection )
2960                 {
2961                     psGMLJP2Style =
2962                         CPLCreateXMLNode( psGMLJP2CoverageCollection,
2963                                           CXT_Element, "gmljp2:style" );
2964                 }
2965                 else
2966                 {
2967                     CPLXMLNode* psFeatureMemberOfGridCoverage =
2968                         CPLGetXMLNode(psGMLJP2CoverageCollection,
2969                                       "gmljp2:featureMember");
2970                     CPLAssert(psFeatureMemberOfGridCoverage);
2971                     CPLXMLNode* psGridCoverage =
2972                         psFeatureMemberOfGridCoverage->psChild;
2973                     CPLAssert(psGridCoverage);
2974                     psGMLJP2Style =
2975                         CPLCreateXMLNode( psGridCoverage,
2976                                           CXT_Element, "gmljp2:style" );
2977                 }
2978 
2979                 // Add dummy namespace for validation purposes if needed
2980                 if( strchr(psStyleRoot->pszValue, ':') == nullptr &&
2981                     CPLGetXMLValue(psStyleRoot, "xmlns", nullptr) == nullptr )
2982                 {
2983                     CPLSetXMLValue(psStyleRoot, "#xmlns",
2984                                    "http://undefined_namespace");
2985                 }
2986 
2987                 CPLAddXMLChild( psGMLJP2Style, CPLCloneXMLTree(psStyleRoot) );
2988             }
2989         }
2990 
2991         // Add extensions.
2992         for( const auto& oExt: aoExtensions )
2993         {
2994             CPLXMLTreeCloser psExtension(CPLParseXMLFile(oExt.osFile));
2995             if( psExtension == nullptr )
2996                 continue;
2997 
2998             CPLXMLNode* psExtensionRoot = GDALGMLJP2GetXMLRoot(psExtension.get());
2999             if( psExtensionRoot )
3000             {
3001                 CPLXMLNode *psGMLJP2Extension;
3002                 if( oExt.bParentCoverageCollection )
3003                 {
3004                     psGMLJP2Extension =
3005                         CPLCreateXMLNode( psGMLJP2CoverageCollection,
3006                                           CXT_Element, "gmljp2:extension" );
3007                 }
3008                 else
3009                 {
3010                     CPLXMLNode* psFeatureMemberOfGridCoverage =
3011                         CPLGetXMLNode(psGMLJP2CoverageCollection,
3012                                       "gmljp2:featureMember");
3013                     CPLAssert(psFeatureMemberOfGridCoverage);
3014                     CPLXMLNode* psGridCoverage =
3015                         psFeatureMemberOfGridCoverage->psChild;
3016                     CPLAssert(psGridCoverage);
3017                     psGMLJP2Extension =
3018                         CPLCreateXMLNode( psGridCoverage,
3019                                           CXT_Element, "gmljp2:extension" );
3020                 }
3021 
3022                 // Add dummy namespace for validation purposes if needed
3023                 if( strchr(psExtensionRoot->pszValue, ':') == nullptr &&
3024                     CPLGetXMLValue(psExtensionRoot, "xmlns", nullptr) == nullptr )
3025                 {
3026                     CPLSetXMLValue(psExtensionRoot, "#xmlns",
3027                                    "http://undefined_namespace");
3028                 }
3029 
3030                 CPLAddXMLChild( psGMLJP2Extension,
3031                                 CPLCloneXMLTree(psExtensionRoot) );
3032             }
3033         }
3034 
3035         char* pszRoot = CPLSerializeXMLTree(psRoot.get());
3036         psRoot.reset();
3037         osDoc = pszRoot;
3038         CPLFree(pszRoot);
3039         pszRoot = nullptr;
3040     }
3041 
3042 /* -------------------------------------------------------------------- */
3043 /*      Setup the gml.data label.                                       */
3044 /* -------------------------------------------------------------------- */
3045     std::vector<GDALJP2Box *> apoGMLBoxes;
3046 
3047     apoGMLBoxes.push_back(GDALJP2Box::CreateLblBox( "gml.data" ));
3048 
3049 /* -------------------------------------------------------------------- */
3050 /*      Setup gml.root-instance.                                        */
3051 /* -------------------------------------------------------------------- */
3052     apoGMLBoxes.push_back(
3053         GDALJP2Box::CreateLabelledXMLAssoc( "gml.root-instance", osDoc ));
3054 
3055 /* -------------------------------------------------------------------- */
3056 /*      Add optional dictionary.                                        */
3057 /* -------------------------------------------------------------------- */
3058     if( !osDictBox.empty() )
3059         apoGMLBoxes.push_back(
3060             GDALJP2Box::CreateLabelledXMLAssoc( "CRSDictionary.gml",
3061                                                 osDictBox ) );
3062 
3063 /* -------------------------------------------------------------------- */
3064 /*      Additional user specified boxes.                                */
3065 /* -------------------------------------------------------------------- */
3066     for( auto& oBox: aoBoxes )
3067     {
3068         GByte* pabyContent = nullptr;
3069         if( VSIIngestFile( nullptr, oBox.osFile, &pabyContent, nullptr, -1 ) )
3070         {
3071             CPLXMLTreeCloser psNode(CPLParseXMLString(
3072                                 reinterpret_cast<const char*>(pabyContent)));
3073             CPLFree(pabyContent);
3074             pabyContent = nullptr;
3075             if( psNode.get() )
3076             {
3077                 CPLXMLNode* psRoot = GDALGMLJP2GetXMLRoot(psNode.get());
3078                 if( psRoot )
3079                 {
3080                     GDALGMLJP2PatchFeatureCollectionSubstitutionGroup(psRoot);
3081                     pabyContent = reinterpret_cast<GByte*>(CPLSerializeXMLTree(psRoot));
3082                     apoGMLBoxes.push_back(
3083                         GDALJP2Box::CreateLabelledXMLAssoc( oBox.osLabel,
3084                                 reinterpret_cast<const char*>(pabyContent)) );
3085                 }
3086             }
3087         }
3088         CPLFree(pabyContent);
3089     }
3090 
3091 /* -------------------------------------------------------------------- */
3092 /*      Bundle gml.data boxes into an association.                      */
3093 /* -------------------------------------------------------------------- */
3094     GDALJP2Box *poGMLData =
3095         GDALJP2Box::CreateAsocBox( static_cast<int>(apoGMLBoxes.size()),
3096                                    &apoGMLBoxes[0] );
3097 
3098 /* -------------------------------------------------------------------- */
3099 /*      Cleanup working boxes.                                          */
3100 /* -------------------------------------------------------------------- */
3101     for( auto& poGMLBox: apoGMLBoxes )
3102         delete poGMLBox;
3103 
3104     for( const auto& osTmpFile: aosTmpFiles )
3105     {
3106         VSIUnlink(osTmpFile);
3107     }
3108 
3109     return poGMLData;
3110 }
3111 
3112 /************************************************************************/
3113 /*                 CreateGDALMultiDomainMetadataXML()                   */
3114 /************************************************************************/
3115 
CreateGDALMultiDomainMetadataXML(GDALDataset * poSrcDS,int bMainMDDomainOnly)3116 CPLXMLNode* GDALJP2Metadata::CreateGDALMultiDomainMetadataXML(
3117     GDALDataset* poSrcDS, int bMainMDDomainOnly )
3118 {
3119     GDALMultiDomainMetadata oLocalMDMD;
3120     char** papszSrcMD = CSLDuplicate(poSrcDS->GetMetadata());
3121     /* Remove useless metadata */
3122     papszSrcMD = CSLSetNameValue(papszSrcMD, GDALMD_AREA_OR_POINT, nullptr);
3123     papszSrcMD = CSLSetNameValue(papszSrcMD, "TIFFTAG_RESOLUTIONUNIT", nullptr);
3124     papszSrcMD = CSLSetNameValue(papszSrcMD, "TIFFTAG_XREpsMasterXMLNodeSOLUTION", nullptr);
3125     papszSrcMD = CSLSetNameValue(papszSrcMD, "TIFFTAG_YRESOLUTION", nullptr);
3126     papszSrcMD = CSLSetNameValue(papszSrcMD, "Corder", nullptr); /* from JP2KAK */
3127     if( poSrcDS->GetDriver() != nullptr &&
3128         EQUAL(poSrcDS->GetDriver()->GetDescription(), "JP2ECW") )
3129     {
3130         papszSrcMD = CSLSetNameValue(papszSrcMD, "COMPRESSION_RATE_TARGET", nullptr);
3131         papszSrcMD = CSLSetNameValue(papszSrcMD, "COLORSPACE", nullptr);
3132         papszSrcMD = CSLSetNameValue(papszSrcMD, "VERSION", nullptr);
3133     }
3134 
3135     bool bHasMD = false;
3136     if( papszSrcMD && *papszSrcMD )
3137     {
3138         bHasMD = true;
3139         oLocalMDMD.SetMetadata(papszSrcMD);
3140     }
3141     CSLDestroy(papszSrcMD);
3142 
3143     if( !bMainMDDomainOnly )
3144     {
3145         char** papszMDList = poSrcDS->GetMetadataDomainList();
3146         for( char** papszMDListIter = papszMDList;
3147              papszMDListIter && *papszMDListIter;
3148              ++papszMDListIter )
3149         {
3150             if( !EQUAL(*papszMDListIter, "") &&
3151                 !EQUAL(*papszMDListIter, "IMAGE_STRUCTURE") &&
3152                 !EQUAL(*papszMDListIter, "DERIVED_SUBDATASETS") &&
3153                 !EQUAL(*papszMDListIter, "JPEG2000") &&
3154                 !STARTS_WITH_CI(*papszMDListIter, "xml:BOX_") &&
3155                 !EQUAL(*papszMDListIter, "xml:gml.root-instance") &&
3156                 !EQUAL(*papszMDListIter, "xml:XMP") &&
3157                 !EQUAL(*papszMDListIter, "xml:IPR") )
3158             {
3159                 papszSrcMD = poSrcDS->GetMetadata(*papszMDListIter);
3160                 if( papszSrcMD && *papszSrcMD )
3161                 {
3162                     bHasMD = true;
3163                     oLocalMDMD.SetMetadata(papszSrcMD, *papszMDListIter);
3164                 }
3165             }
3166         }
3167         CSLDestroy(papszMDList);
3168     }
3169 
3170     CPLXMLNode* psMasterXMLNode = nullptr;
3171     if( bHasMD )
3172     {
3173         CPLXMLNode* psXMLNode = oLocalMDMD.Serialize();
3174         psMasterXMLNode = CPLCreateXMLNode( nullptr, CXT_Element,
3175                                                     "GDALMultiDomainMetadata" );
3176         psMasterXMLNode->psChild = psXMLNode;
3177     }
3178     return psMasterXMLNode;
3179 }
3180 
3181 /************************************************************************/
3182 /*                CreateGDALMultiDomainMetadataXMLBox()                 */
3183 /************************************************************************/
3184 
CreateGDALMultiDomainMetadataXMLBox(GDALDataset * poSrcDS,int bMainMDDomainOnly)3185 GDALJP2Box *GDALJP2Metadata::CreateGDALMultiDomainMetadataXMLBox(
3186                                        GDALDataset* poSrcDS,
3187                                        int bMainMDDomainOnly )
3188 {
3189     CPLXMLTreeCloser psMasterXMLNode(CreateGDALMultiDomainMetadataXML(
3190                                        poSrcDS, bMainMDDomainOnly ));
3191     if( psMasterXMLNode == nullptr )
3192         return nullptr;
3193     char* pszXML = CPLSerializeXMLTree(psMasterXMLNode.get());
3194     psMasterXMLNode.reset();
3195 
3196     GDALJP2Box* poBox = new GDALJP2Box();
3197     poBox->SetType("xml ");
3198     poBox->SetWritableData(static_cast<int>(strlen(pszXML) + 1),
3199                            reinterpret_cast<const GByte*>(pszXML));
3200     CPLFree(pszXML);
3201 
3202     return poBox;
3203 }
3204 
3205 /************************************************************************/
3206 /*                         WriteXMLBoxes()                              */
3207 /************************************************************************/
3208 
CreateXMLBoxes(GDALDataset * poSrcDS,int * pnBoxes)3209 GDALJP2Box** GDALJP2Metadata::CreateXMLBoxes( GDALDataset* poSrcDS,
3210                                               int* pnBoxes )
3211 {
3212     GDALJP2Box** papoBoxes = nullptr;
3213     *pnBoxes = 0;
3214     char** papszMDList = poSrcDS->GetMetadataDomainList();
3215     for( char** papszMDListIter = papszMDList;
3216          papszMDListIter && *papszMDListIter;
3217          ++papszMDListIter )
3218     {
3219         /* Write metadata that look like originating from JP2 XML boxes */
3220         /* as a standalone JP2 XML box */
3221         if( STARTS_WITH_CI(*papszMDListIter, "xml:BOX_") )
3222         {
3223             char** papszSrcMD = poSrcDS->GetMetadata(*papszMDListIter);
3224             if( papszSrcMD && *papszSrcMD )
3225             {
3226                 GDALJP2Box* poBox = new GDALJP2Box();
3227                 poBox->SetType("xml ");
3228                 poBox->SetWritableData(static_cast<int>(strlen(*papszSrcMD) + 1),
3229                                        reinterpret_cast<const GByte*>(*papszSrcMD));
3230                 papoBoxes = static_cast<GDALJP2Box**>(CPLRealloc(papoBoxes,
3231                                         sizeof(GDALJP2Box*) * (*pnBoxes + 1)));
3232                 papoBoxes[(*pnBoxes)++] = poBox;
3233             }
3234         }
3235     }
3236     CSLDestroy(papszMDList);
3237     return papoBoxes;
3238 }
3239 
3240 /************************************************************************/
3241 /*                          CreateXMPBox()                              */
3242 /************************************************************************/
3243 
CreateXMPBox(GDALDataset * poSrcDS)3244 GDALJP2Box *GDALJP2Metadata::CreateXMPBox ( GDALDataset* poSrcDS )
3245 {
3246     char** papszSrcMD = poSrcDS->GetMetadata("xml:XMP");
3247     GDALJP2Box* poBox = nullptr;
3248     if( papszSrcMD && * papszSrcMD )
3249     {
3250         poBox = GDALJP2Box::CreateUUIDBox(xmp_uuid,
3251                                           static_cast<int>(strlen(*papszSrcMD) + 1),
3252                                           reinterpret_cast<const GByte*>(*papszSrcMD));
3253     }
3254     return poBox;
3255 }
3256 
3257 /************************************************************************/
3258 /*                          CreateIPRBox()                              */
3259 /************************************************************************/
3260 
CreateIPRBox(GDALDataset * poSrcDS)3261 GDALJP2Box *GDALJP2Metadata::CreateIPRBox ( GDALDataset* poSrcDS )
3262 {
3263     char** papszSrcMD = poSrcDS->GetMetadata("xml:IPR");
3264     GDALJP2Box* poBox = nullptr;
3265     if( papszSrcMD && * papszSrcMD )
3266     {
3267         poBox = new GDALJP2Box();
3268         poBox->SetType("jp2i");
3269         poBox->SetWritableData(static_cast<int>(strlen(*papszSrcMD) + 1),
3270                                reinterpret_cast<const GByte*>(*papszSrcMD));
3271     }
3272     return poBox;
3273 }
3274 
3275 /************************************************************************/
3276 /*                           IsUUID_MSI()                              */
3277 /************************************************************************/
3278 
IsUUID_MSI(const GByte * abyUUID)3279 int GDALJP2Metadata::IsUUID_MSI(const GByte *abyUUID)
3280 {
3281     return memcmp(abyUUID, msi_uuid2, 16) == 0;
3282 }
3283 
3284 /************************************************************************/
3285 /*                           IsUUID_XMP()                               */
3286 /************************************************************************/
3287 
IsUUID_XMP(const GByte * abyUUID)3288 int GDALJP2Metadata::IsUUID_XMP(const GByte *abyUUID)
3289 {
3290     return memcmp(abyUUID, xmp_uuid, 16) == 0;
3291 }
3292 
3293 /*! @endcond */
3294