LCOV - code coverage report
Current view: top level - gcore - gdaljp2metadata.cpp (source / functions) Hit Total Coverage
Test: gdal_filtered.info Lines: 1280 1394 91.8 %
Date: 2025-09-10 17:48:50 Functions: 30 30 100.0 %

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

Generated by: LCOV version 1.14