• Home
  • Features
  • Pricing
  • Docs
  • Announcements
  • Sign In

OSGeo / gdal / 15992523769

01 Jul 2025 07:16AM UTC coverage: 71.09% (-0.004%) from 71.094%
15992523769

Pull #12682

github

web-flow
Merge 1383a7df3 into f6ed9706f
Pull Request #12682: Bump msys2/setup-msys2 from 2.27.0 to 2.28.0

574092 of 807557 relevant lines covered (71.09%)

250020.23 hits per line

Source File
Press 'n' to go to next uncovered line, 'b' for previous

91.82
/gcore/gdaljp2metadata.cpp
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
#if HAVE_FCNTL_H
26
#include <fcntl.h>
27
#endif
28

29
#include <algorithm>
30
#include <array>
31
#include <memory>
32
#include <set>
33
#include <string>
34
#include <vector>
35

36
#include "cpl_error.h"
37
#include "cpl_string.h"
38
#include "cpl_minixml.h"
39
#include "gdaljp2metadatagenerator.h"
40
#ifdef HAVE_TIFF
41
#include "gt_wkt_srs_for_gdal.h"
42
#endif
43
#include "ogr_api.h"
44
#include "ogr_core.h"
45
#include "ogr_geometry.h"
46
#include "ogr_spatialref.h"
47
#include "ogrlibjsonutils.h"
48

49
/*! @cond Doxygen_Suppress */
50

51
static const unsigned char msi_uuid2[16] = {0xb1, 0x4b, 0xf8, 0xbd, 0x08, 0x3d,
52
                                            0x4b, 0x43, 0xa5, 0xae, 0x8c, 0xd7,
53
                                            0xd5, 0xa6, 0xce, 0x03};
54

55
static const unsigned char msig_uuid[16] = {0x96, 0xA9, 0xF1, 0xF1, 0xDC, 0x98,
56
                                            0x40, 0x2D, 0xA7, 0xAE, 0xD6, 0x8E,
57
                                            0x34, 0x45, 0x18, 0x09};
58

59
static const unsigned char xmp_uuid[16] = {0xBE, 0x7A, 0xCF, 0xCB, 0x97, 0xA9,
60
                                           0x42, 0xE8, 0x9C, 0x71, 0x99, 0x94,
61
                                           0x91, 0xE3, 0xAF, 0xAC};
62

63
struct _GDALJP2GeoTIFFBox
64
{
65
    int nGeoTIFFSize;
66
    GByte *pabyGeoTIFFData;
67
};
68

69
constexpr int MAX_JP2GEOTIFF_BOXES = 2;
70

71
/************************************************************************/
72
/*                          GDALJP2Metadata()                           */
73
/************************************************************************/
74

75
GDALJP2Metadata::GDALJP2Metadata()
1,530✔
76
    : nGeoTIFFBoxesCount(0), pasGeoTIFFBoxes(nullptr), nMSIGSize(0),
77
      pabyMSIGData(nullptr), papszGMLMetadata(nullptr), bPixelIsPoint(false),
78
      nGCPCount(0), pasGCPList(nullptr), papszRPCMD(nullptr),
79
      papszMetadata(nullptr), pszXMPMetadata(nullptr),
80
      pszGDALMultiDomainMetadata(nullptr), pszXMLIPR(nullptr)
1,530✔
81
{
82
}
1,530✔
83

84
/************************************************************************/
85
/*                          ~GDALJP2Metadata()                          */
86
/************************************************************************/
87

88
GDALJP2Metadata::~GDALJP2Metadata()
1,530✔
89

90
{
91
    if (nGCPCount > 0)
1,530✔
92
    {
93
        GDALDeinitGCPs(nGCPCount, pasGCPList);
18✔
94
        CPLFree(pasGCPList);
18✔
95
    }
96
    CSLDestroy(papszRPCMD);
1,530✔
97

98
    for (int i = 0; i < nGeoTIFFBoxesCount; ++i)
2,133✔
99
    {
100
        CPLFree(pasGeoTIFFBoxes[i].pabyGeoTIFFData);
603✔
101
    }
102
    CPLFree(pasGeoTIFFBoxes);
1,530✔
103
    CPLFree(pabyMSIGData);
1,530✔
104
    CSLDestroy(papszGMLMetadata);
1,530✔
105
    CSLDestroy(papszMetadata);
1,530✔
106
    CPLFree(pszXMPMetadata);
1,530✔
107
    CPLFree(pszGDALMultiDomainMetadata);
1,530✔
108
    CPLFree(pszXMLIPR);
1,530✔
109
}
1,530✔
110

111
/************************************************************************/
112
/*                            ReadAndParse()                            */
113
/*                                                                      */
114
/*      Read a JP2 file and try to collect georeferencing               */
115
/*      information from the various available forms.  Returns TRUE     */
116
/*      if anything useful is found.                                    */
117
/************************************************************************/
118

119
int GDALJP2Metadata::ReadAndParse(const char *pszFilename, int nGEOJP2Index,
89✔
120
                                  int nGMLJP2Index, int nMSIGIndex,
121
                                  int nWorldFileIndex, int *pnIndexUsed)
122

123
{
124
    VSILFILE *fpLL = VSIFOpenL(pszFilename, "rb");
89✔
125
    if (fpLL == nullptr)
89✔
126
    {
127
        CPLDebug("GDALJP2Metadata", "Could not even open %s.", pszFilename);
×
128

129
        return FALSE;
×
130
    }
131

132
    int nIndexUsed = -1;
89✔
133
    bool bRet = CPL_TO_BOOL(ReadAndParse(fpLL, nGEOJP2Index, nGMLJP2Index,
89✔
134
                                         nMSIGIndex, &nIndexUsed));
135
    CPL_IGNORE_RET_VAL(VSIFCloseL(fpLL));
89✔
136

137
    /* -------------------------------------------------------------------- */
138
    /*      If we still don't have a geotransform, look for a world         */
139
    /*      file.                                                           */
140
    /* -------------------------------------------------------------------- */
141
    if (nWorldFileIndex >= 0 &&
89✔
142
        ((m_bHaveGeoTransform && nWorldFileIndex < nIndexUsed) ||
89✔
143
         !m_bHaveGeoTransform))
89✔
144
    {
145
        m_bHaveGeoTransform =
54✔
146
            CPL_TO_BOOL(GDALReadWorldFile(pszFilename, nullptr, m_gt.data()) ||
107✔
147
                        GDALReadWorldFile(pszFilename, ".wld", m_gt.data()));
53✔
148
        bRet |= m_bHaveGeoTransform;
54✔
149
    }
150

151
    if (pnIndexUsed)
89✔
152
        *pnIndexUsed = nIndexUsed;
89✔
153

154
    return bRet;
89✔
155
}
156

157
int GDALJP2Metadata::ReadAndParse(VSILFILE *fpLL, int nGEOJP2Index,
1,185✔
158
                                  int nGMLJP2Index, int nMSIGIndex,
159
                                  int *pnIndexUsed)
160

161
{
162
    ReadBoxes(fpLL);
1,185✔
163

164
    /* -------------------------------------------------------------------- */
165
    /*      Try JP2GeoTIFF, GML and finally MSIG in specified order.        */
166
    /* -------------------------------------------------------------------- */
167
    std::set<int> aoSetPriorities;
1,185✔
168
    if (nGEOJP2Index >= 0)
1,185✔
169
        aoSetPriorities.insert(nGEOJP2Index);
1,171✔
170
    if (nGMLJP2Index >= 0)
1,185✔
171
        aoSetPriorities.insert(nGMLJP2Index);
1,171✔
172
    if (nMSIGIndex >= 0)
1,185✔
173
        aoSetPriorities.insert(nMSIGIndex);
1,169✔
174
    for (const int nIndex : aoSetPriorities)
2,895✔
175
    {
176
        if ((nIndex == nGEOJP2Index && ParseJP2GeoTIFF()) ||
1,169✔
177
            (nIndex == nGMLJP2Index && ParseGMLCoverageDesc()) ||
5,204✔
178
            (nIndex == nMSIGIndex && ParseMSIG()))
1,710✔
179
        {
180
            if (pnIndexUsed)
615✔
181
                *pnIndexUsed = nIndex;
615✔
182
            break;
615✔
183
        }
184
    }
185

186
    /* -------------------------------------------------------------------- */
187
    /*      Return success either either of projection or geotransform      */
188
    /*      or gcps.                                                        */
189
    /* -------------------------------------------------------------------- */
190
    return m_bHaveGeoTransform || nGCPCount > 0 || !m_oSRS.IsEmpty() ||
1,754✔
191
           papszRPCMD != nullptr;
2,939✔
192
}
193

194
/************************************************************************/
195
/*                           CollectGMLData()                           */
196
/*                                                                      */
197
/*      Read all the asoc boxes after this node, and store the          */
198
/*      contain xml documents along with the name from the label.       */
199
/************************************************************************/
200

201
void GDALJP2Metadata::CollectGMLData(GDALJP2Box *poGMLData)
225✔
202

203
{
204
    GDALJP2Box oChildBox(poGMLData->GetFILE());
225✔
205

206
    if (!oChildBox.ReadFirstChild(poGMLData))
225✔
207
        return;
×
208

209
    while (strlen(oChildBox.GetType()) > 0)
505✔
210
    {
211
        if (EQUAL(oChildBox.GetType(), "asoc"))
505✔
212
        {
213
            GDALJP2Box oSubChildBox(oChildBox.GetFILE());
280✔
214

215
            if (!oSubChildBox.ReadFirstChild(&oChildBox))
280✔
216
                break;
×
217

218
            char *pszLabel = nullptr;
280✔
219
            char *pszXML = nullptr;
280✔
220

221
            while (strlen(oSubChildBox.GetType()) > 0)
560✔
222
            {
223
                if (EQUAL(oSubChildBox.GetType(), "lbl "))
560✔
224
                    pszLabel =
225
                        reinterpret_cast<char *>(oSubChildBox.ReadBoxData());
280✔
226
                else if (EQUAL(oSubChildBox.GetType(), "xml "))
280✔
227
                {
228
                    pszXML =
229
                        reinterpret_cast<char *>(oSubChildBox.ReadBoxData());
280✔
230
                    GIntBig nXMLLength = oSubChildBox.GetDataLength();
280✔
231

232
                    // Some GML data contains \0 instead of \n.
233
                    // See http://trac.osgeo.org/gdal/ticket/5760
234
                    // TODO(schwehr): Explain the numbers in the next line.
235
                    if (pszXML != nullptr && nXMLLength > 0 &&
280✔
236
                        nXMLLength < 100 * 1024 * 1024)
237
                    {
238
                        for (GIntBig i = nXMLLength - 1; i >= 0; --i)
553✔
239
                        {
240
                            if (pszXML[i] == '\0')
553✔
241
                                --nXMLLength;
273✔
242
                            else
243
                                break;
280✔
244
                        }
245
                        GIntBig i = 0;  // Used after for.
280✔
246
                        for (; i < nXMLLength; ++i)
714,521✔
247
                        {
248
                            if (pszXML[i] == '\0')
714,242✔
249
                                break;
1✔
250
                        }
251
                        if (i < nXMLLength)
280✔
252
                        {
253
                            CPLPushErrorHandler(CPLQuietErrorHandler);
1✔
254
                            CPLXMLTreeCloser psNode(CPLParseXMLString(pszXML));
2✔
255
                            CPLPopErrorHandler();
1✔
256
                            if (psNode == nullptr)
1✔
257
                            {
258
                                CPLDebug(
1✔
259
                                    "GMLJP2",
260
                                    "GMLJP2 data contains nul characters "
261
                                    "inside content. Replacing them by \\n");
262
                                for (GIntBig j = 0; j < nXMLLength; ++j)
1,708✔
263
                                {
264
                                    if (pszXML[j] == '\0')
1,707✔
265
                                        pszXML[j] = '\n';
1✔
266
                                }
267
                            }
268
                        }
269
                    }
270
                }
271

272
                if (!oSubChildBox.ReadNextChild(&oChildBox))
560✔
273
                    break;
280✔
274
            }
275

276
            if (pszLabel != nullptr && pszXML != nullptr)
280✔
277
            {
278
                papszGMLMetadata =
280✔
279
                    CSLSetNameValue(papszGMLMetadata, pszLabel, pszXML);
280✔
280

281
                if (strcmp(pszLabel, "gml.root-instance") == 0 &&
280✔
282
                    pszGDALMultiDomainMetadata == nullptr &&
225✔
283
                    strstr(pszXML, "GDALMultiDomainMetadata") != nullptr)
219✔
284
                {
285
                    CPLXMLTreeCloser psTree(CPLParseXMLString(pszXML));
4✔
286
                    if (psTree != nullptr)
2✔
287
                    {
288
                        CPLXMLNode *psGDALMDMD = CPLSearchXMLNode(
2✔
289
                            psTree.get(), "GDALMultiDomainMetadata");
290
                        if (psGDALMDMD)
2✔
291
                            pszGDALMultiDomainMetadata =
2✔
292
                                CPLSerializeXMLTree(psGDALMDMD);
2✔
293
                    }
294
                }
295
            }
296

297
            CPLFree(pszLabel);
280✔
298
            CPLFree(pszXML);
280✔
299
        }
300

301
        if (!oChildBox.ReadNextChild(poGMLData))
505✔
302
            break;
225✔
303
    }
304
}
305

306
/************************************************************************/
307
/*                              ReadBox()                               */
308
/************************************************************************/
309

310
void GDALJP2Metadata::ReadBox(VSILFILE *fpVSIL, GDALJP2Box &oBox, int &iBox)
4,086✔
311
{
312
#ifdef DEBUG
313
    if (CPLTestBool(CPLGetConfigOption("DUMP_JP2_BOXES", "NO")))
4,086✔
314
        oBox.DumpReadable(stderr);
×
315
#endif
316

317
    /* -------------------------------------------------------------------- */
318
    /*      Collect geotiff box.                                            */
319
    /* -------------------------------------------------------------------- */
320
    if (EQUAL(oBox.GetType(), "uuid") &&
4,698✔
321
        memcmp(oBox.GetUUID(), msi_uuid2, 16) == 0)
612✔
322
    {
323
        // Erdas JPEG2000 files sometimes contain 2 GeoTIFF UUID boxes. One
324
        // that is correct, another one that does not contain correct
325
        // georeferencing. Fetch at most 2 of them for later analysis.
326
        if (nGeoTIFFBoxesCount == MAX_JP2GEOTIFF_BOXES)
603✔
327
        {
328
            CPLDebug("GDALJP2",
×
329
                     "Too many UUID GeoTIFF boxes. Ignoring this one");
330
        }
331
        else
332
        {
333
            const int nGeoTIFFSize = static_cast<int>(oBox.GetDataLength());
603✔
334
            GByte *pabyGeoTIFFData = oBox.ReadBoxData();
603✔
335
            if (pabyGeoTIFFData == nullptr)
603✔
336
            {
337
                CPLDebug("GDALJP2", "Cannot read data for UUID GeoTIFF box");
×
338
            }
339
            else
340
            {
341
                pasGeoTIFFBoxes = static_cast<GDALJP2GeoTIFFBox *>(
603✔
342
                    CPLRealloc(pasGeoTIFFBoxes, sizeof(GDALJP2GeoTIFFBox) *
1,206✔
343
                                                    (nGeoTIFFBoxesCount + 1)));
603✔
344
                pasGeoTIFFBoxes[nGeoTIFFBoxesCount].nGeoTIFFSize = nGeoTIFFSize;
603✔
345
                pasGeoTIFFBoxes[nGeoTIFFBoxesCount].pabyGeoTIFFData =
603✔
346
                    pabyGeoTIFFData;
347
                ++nGeoTIFFBoxesCount;
603✔
348
            }
349
        }
350
    }
351

352
    /* -------------------------------------------------------------------- */
353
    /*      Collect MSIG box.                                               */
354
    /* -------------------------------------------------------------------- */
355
    else if (EQUAL(oBox.GetType(), "uuid") &&
3,492✔
356
             memcmp(oBox.GetUUID(), msig_uuid, 16) == 0)
9✔
357
    {
358
        if (nMSIGSize == 0)
×
359
        {
360
            nMSIGSize = static_cast<int>(oBox.GetDataLength());
×
361
            pabyMSIGData = oBox.ReadBoxData();
×
362

363
            if (nMSIGSize < 70 || pabyMSIGData == nullptr ||
×
364
                memcmp(pabyMSIGData, "MSIG/", 5) != 0)
×
365
            {
366
                CPLFree(pabyMSIGData);
×
367
                pabyMSIGData = nullptr;
×
368
                nMSIGSize = 0;
×
369
            }
370
        }
371
        else
372
        {
373
            CPLDebug("GDALJP2", "Too many UUID MSIG boxes. Ignoring this one");
×
374
        }
375
    }
376

377
    /* -------------------------------------------------------------------- */
378
    /*      Collect XMP box.                                                */
379
    /* -------------------------------------------------------------------- */
380
    else if (EQUAL(oBox.GetType(), "uuid") &&
3,492✔
381
             memcmp(oBox.GetUUID(), xmp_uuid, 16) == 0)
9✔
382
    {
383
        if (pszXMPMetadata == nullptr)
9✔
384
        {
385
            pszXMPMetadata = reinterpret_cast<char *>(oBox.ReadBoxData());
9✔
386
        }
387
        else
388
        {
389
            CPLDebug("GDALJP2", "Too many UUID XMP boxes. Ignoring this one");
×
390
        }
391
    }
392

393
    /* -------------------------------------------------------------------- */
394
    /*      Process asoc box looking for Labelled GML data.                 */
395
    /* -------------------------------------------------------------------- */
396
    else if (EQUAL(oBox.GetType(), "asoc"))
3,474✔
397
    {
398
        GDALJP2Box oSubBox(fpVSIL);
450✔
399

400
        if (oSubBox.ReadFirstChild(&oBox) && EQUAL(oSubBox.GetType(), "lbl "))
225✔
401
        {
402
            char *pszLabel = reinterpret_cast<char *>(oSubBox.ReadBoxData());
225✔
403
            if (pszLabel != nullptr && EQUAL(pszLabel, "gml.data"))
225✔
404
            {
405
                CollectGMLData(&oBox);
225✔
406
            }
407
            CPLFree(pszLabel);
225✔
408
        }
409
    }
410

411
    /* -------------------------------------------------------------------- */
412
    /*      Process simple xml boxes.                                       */
413
    /* -------------------------------------------------------------------- */
414
    else if (EQUAL(oBox.GetType(), "xml "))
3,249✔
415
    {
416
        CPLString osBoxName;
58✔
417

418
        char *pszXML = reinterpret_cast<char *>(oBox.ReadBoxData());
29✔
419
        if (pszXML != nullptr &&
29✔
420
            STARTS_WITH(pszXML, "<GDALMultiDomainMetadata>"))
29✔
421
        {
422
            if (pszGDALMultiDomainMetadata == nullptr)
17✔
423
            {
424
                pszGDALMultiDomainMetadata = pszXML;
17✔
425
                pszXML = nullptr;
17✔
426
            }
427
            else
428
            {
429
                CPLDebug("GDALJP2",
×
430
                         "Too many GDAL metadata boxes. Ignoring this one");
431
            }
432
        }
433
        else if (pszXML != nullptr)
12✔
434
        {
435
            osBoxName.Printf("BOX_%d", iBox++);
12✔
436

437
            papszGMLMetadata =
12✔
438
                CSLSetNameValue(papszGMLMetadata, osBoxName, pszXML);
12✔
439
        }
440
        CPLFree(pszXML);
29✔
441
    }
442

443
    /* -------------------------------------------------------------------- */
444
    /*      Check for a resd box in jp2h.                                   */
445
    /* -------------------------------------------------------------------- */
446
    else if (EQUAL(oBox.GetType(), "jp2h"))
3,220✔
447
    {
448
        GDALJP2Box oSubBox(fpVSIL);
1,474✔
449

450
        for (oSubBox.ReadFirstChild(&oBox); strlen(oSubBox.GetType()) > 0;
2,375✔
451
             oSubBox.ReadNextChild(&oBox))
1,638✔
452
        {
453
            if (EQUAL(oSubBox.GetType(), "res "))
1,638✔
454
            {
455
                GDALJP2Box oResBox(fpVSIL);
32✔
456

457
                oResBox.ReadFirstChild(&oSubBox);
16✔
458

459
                // We will use either the resd or resc box, which ever
460
                // happens to be first.  Should we prefer resd?
461
                unsigned char *pabyResData = nullptr;
16✔
462
                if (oResBox.GetDataLength() == 10 &&
32✔
463
                    (pabyResData = oResBox.ReadBoxData()) != nullptr)
16✔
464
                {
465
                    int nVertNum, nVertDen, nVertExp;
466
                    int nHorzNum, nHorzDen, nHorzExp;
467

468
                    nVertNum = pabyResData[0] * 256 + pabyResData[1];
16✔
469
                    nVertDen = pabyResData[2] * 256 + pabyResData[3];
16✔
470
                    nHorzNum = pabyResData[4] * 256 + pabyResData[5];
16✔
471
                    nHorzDen = pabyResData[6] * 256 + pabyResData[7];
16✔
472
                    nVertExp = pabyResData[8];
16✔
473
                    nHorzExp = pabyResData[9];
16✔
474

475
                    // compute in pixels/cm
476
                    const double dfVertRes =
477
                        (nVertNum / static_cast<double>(nVertDen)) *
32✔
478
                        pow(10.0, nVertExp) / 100;
16✔
479
                    const double dfHorzRes =
480
                        (nHorzNum / static_cast<double>(nHorzDen)) *
32✔
481
                        pow(10.0, nHorzExp) / 100;
16✔
482
                    CPLString osFormatter;
32✔
483

484
                    papszMetadata =
16✔
485
                        CSLSetNameValue(papszMetadata, "TIFFTAG_XRESOLUTION",
16✔
486
                                        osFormatter.Printf("%g", dfHorzRes));
16✔
487

488
                    papszMetadata =
16✔
489
                        CSLSetNameValue(papszMetadata, "TIFFTAG_YRESOLUTION",
16✔
490
                                        osFormatter.Printf("%g", dfVertRes));
16✔
491
                    papszMetadata =
16✔
492
                        CSLSetNameValue(papszMetadata, "TIFFTAG_RESOLUTIONUNIT",
16✔
493
                                        "3 (pixels/cm)");
494

495
                    CPLFree(pabyResData);
16✔
496
                }
497
            }
498
        }
499
    }
500

501
    /* -------------------------------------------------------------------- */
502
    /*      Collect IPR box.                                                */
503
    /* -------------------------------------------------------------------- */
504
    else if (EQUAL(oBox.GetType(), "jp2i"))
2,483✔
505
    {
506
        if (pszXMLIPR == nullptr)
5✔
507
        {
508
            pszXMLIPR = reinterpret_cast<char *>(oBox.ReadBoxData());
5✔
509
            CPLXMLTreeCloser psNode(CPLParseXMLString(pszXMLIPR));
10✔
510
            if (psNode == nullptr)
5✔
511
            {
512
                CPLFree(pszXMLIPR);
×
513
                pszXMLIPR = nullptr;
×
514
            }
515
        }
516
        else
517
        {
518
            CPLDebug("GDALJP2", "Too many IPR boxes. Ignoring this one");
×
519
        }
520
    }
521

522
    /* -------------------------------------------------------------------- */
523
    /*      Process JUMBF super box                                         */
524
    /* -------------------------------------------------------------------- */
525
    else if (EQUAL(oBox.GetType(), "jumb"))
2,478✔
526
    {
527
        GDALJP2Box oSubBox(fpVSIL);
80✔
528

529
        for (oSubBox.ReadFirstChild(&oBox); strlen(oSubBox.GetType()) > 0;
120✔
530
             oSubBox.ReadNextChild(&oBox))
80✔
531
        {
532
            ReadBox(fpVSIL, oSubBox, iBox);
80✔
533
        }
534
    }
535
}
4,086✔
536

537
/************************************************************************/
538
/*                             ReadBoxes()                              */
539
/************************************************************************/
540

541
int GDALJP2Metadata::ReadBoxes(VSILFILE *fpVSIL)
1,185✔
542

543
{
544
    GDALJP2Box oBox(fpVSIL);
2,370✔
545

546
    if (!oBox.ReadFirst())
1,185✔
547
        return FALSE;
297✔
548

549
    int iBox = 0;
888✔
550
    while (strlen(oBox.GetType()) > 0)
4,155✔
551
    {
552
        ReadBox(fpVSIL, oBox, iBox);
4,006✔
553
        if (!oBox.ReadNext())
4,006✔
554
            break;
739✔
555
    }
556

557
    return TRUE;
888✔
558
}
559

560
/************************************************************************/
561
/*                          ParseJP2GeoTIFF()                           */
562
/************************************************************************/
563

564
int GDALJP2Metadata::ParseJP2GeoTIFF()
1,169✔
565

566
{
567
#ifdef HAVE_TIFF
568
    if (!CPLTestBool(CPLGetConfigOption("GDAL_USE_GEOJP2", "TRUE")))
1,169✔
569
        return FALSE;
11✔
570

571
    bool abValidProjInfo[MAX_JP2GEOTIFF_BOXES] = {false};
1,158✔
572
    OGRSpatialReferenceH ahSRS[MAX_JP2GEOTIFF_BOXES] = {nullptr};
1,158✔
573
    std::array<GDALGeoTransform, MAX_JP2GEOTIFF_BOXES> aGT{};
2,316✔
574
    int anGCPCount[MAX_JP2GEOTIFF_BOXES] = {0};
1,158✔
575
    GDAL_GCP *apasGCPList[MAX_JP2GEOTIFF_BOXES] = {nullptr};
1,158✔
576
    int abPixelIsPoint[MAX_JP2GEOTIFF_BOXES] = {0};
1,158✔
577
    char **apapszRPCMD[MAX_JP2GEOTIFF_BOXES] = {nullptr};
1,158✔
578

579
    const int nMax = std::min(nGeoTIFFBoxesCount, MAX_JP2GEOTIFF_BOXES);
1,158✔
580
    for (int i = 0; i < nMax; ++i)
1,738✔
581
    {
582
        /* --------------------------------------------------------------------
583
         */
584
        /*      Convert raw data into projection and geotransform. */
585
        /* --------------------------------------------------------------------
586
         */
587
        if (GTIFWktFromMemBufEx(pasGeoTIFFBoxes[i].nGeoTIFFSize,
580✔
588
                                pasGeoTIFFBoxes[i].pabyGeoTIFFData, &ahSRS[i],
580✔
589
                                aGT[i].data(), &anGCPCount[i], &apasGCPList[i],
580✔
590
                                &abPixelIsPoint[i], &apapszRPCMD[i]) == CE_None)
580✔
591
        {
592
            if (ahSRS[i] != nullptr)
579✔
593
                abValidProjInfo[i] = true;
579✔
594
        }
595
    }
596

597
    // Detect which box is the better one.
598
    int iBestIndex = -1;
1,158✔
599
    for (int i = 0; i < nMax; ++i)
1,738✔
600
    {
601
        if (abValidProjInfo[i] && iBestIndex < 0)
580✔
602
        {
603
            iBestIndex = i;
575✔
604
        }
605
        else if (abValidProjInfo[i] && ahSRS[i] != nullptr)
5✔
606
        {
607
            // Anything else than a LOCAL_CS will probably be better.
608
            if (OSRIsLocal(ahSRS[iBestIndex]))
4✔
609
                iBestIndex = i;
×
610
        }
611
    }
612

613
    if (iBestIndex < 0)
1,158✔
614
    {
615
        for (int i = 0; i < nMax; ++i)
584✔
616
        {
617
            if (aGT[i] != GDALGeoTransform() || anGCPCount[i] > 0 ||
1✔
618
                apapszRPCMD[i] != nullptr)
×
619
            {
620
                iBestIndex = i;
1✔
621
            }
622
        }
623
    }
624

625
    if (iBestIndex >= 0)
1,158✔
626
    {
627
        m_oSRS.Clear();
576✔
628
        if (ahSRS[iBestIndex])
576✔
629
            m_oSRS = *(OGRSpatialReference::FromHandle(ahSRS[iBestIndex]));
575✔
630
        m_oSRS.SetAxisMappingStrategy(OAMS_TRADITIONAL_GIS_ORDER);
576✔
631
        m_gt = aGT[iBestIndex];
576✔
632
        nGCPCount = anGCPCount[iBestIndex];
576✔
633
        pasGCPList = apasGCPList[iBestIndex];
576✔
634
        bPixelIsPoint = CPL_TO_BOOL(abPixelIsPoint[iBestIndex]);
576✔
635
        papszRPCMD = apapszRPCMD[iBestIndex];
576✔
636

637
        if (m_gt[0] != 0 || m_gt[1] != 1 || m_gt[2] != 0 || m_gt[3] != 0 ||
638✔
638
            m_gt[4] != 0 || m_gt[5] != 1)
638✔
639
            m_bHaveGeoTransform = true;
544✔
640

641
        if (ahSRS[iBestIndex])
576✔
642
        {
643
            char *pszWKT = nullptr;
575✔
644
            m_oSRS.exportToWkt(&pszWKT);
575✔
645
            CPLDebug("GDALJP2Metadata",
575✔
646
                     "Got projection from GeoJP2 (geotiff) box (%d): %s",
647
                     iBestIndex, pszWKT ? pszWKT : "(null)");
575✔
648
            CPLFree(pszWKT);
575✔
649
        }
650
    }
651

652
    // Cleanup unused boxes.
653
    for (int i = 0; i < nMax; ++i)
1,738✔
654
    {
655
        if (i != iBestIndex)
580✔
656
        {
657
            if (anGCPCount[i] > 0)
4✔
658
            {
659
                GDALDeinitGCPs(anGCPCount[i], apasGCPList[i]);
×
660
                CPLFree(apasGCPList[i]);
×
661
            }
662
            CSLDestroy(apapszRPCMD[i]);
4✔
663
        }
664
        OSRDestroySpatialReference(ahSRS[i]);
580✔
665
    }
666

667
    return iBestIndex >= 0;
1,158✔
668
#else
669
    return false;
670
#endif
671
}
672

673
/************************************************************************/
674
/*                             ParseMSIG()                              */
675
/************************************************************************/
676

677
int GDALJP2Metadata::ParseMSIG()
560✔
678

679
{
680
    if (nMSIGSize < 70)
560✔
681
        return FALSE;
560✔
682

683
    double adfGeoTransform[6];
684

685
    /* -------------------------------------------------------------------- */
686
    /*      Try and extract worldfile parameters and adjust.                */
687
    /* -------------------------------------------------------------------- */
688
    memcpy(adfGeoTransform + 0, pabyMSIGData + 22 + 8 * 4, 8);
×
689
    memcpy(adfGeoTransform + 1, pabyMSIGData + 22 + 8 * 0, 8);
×
690
    memcpy(adfGeoTransform + 2, pabyMSIGData + 22 + 8 * 2, 8);
×
691
    memcpy(adfGeoTransform + 3, pabyMSIGData + 22 + 8 * 5, 8);
×
692
    memcpy(adfGeoTransform + 4, pabyMSIGData + 22 + 8 * 1, 8);
×
693
    memcpy(adfGeoTransform + 5, pabyMSIGData + 22 + 8 * 3, 8);
×
694

695
    // data is in LSB (little endian) order in file.
696
    CPL_LSBPTR64(adfGeoTransform + 0);
×
697
    CPL_LSBPTR64(adfGeoTransform + 1);
×
698
    CPL_LSBPTR64(adfGeoTransform + 2);
×
699
    CPL_LSBPTR64(adfGeoTransform + 3);
×
700
    CPL_LSBPTR64(adfGeoTransform + 4);
×
701
    CPL_LSBPTR64(adfGeoTransform + 5);
×
702

703
    // correct for center of pixel vs. top left of pixel
704
    adfGeoTransform[0] -= 0.5 * adfGeoTransform[1];
×
705
    adfGeoTransform[0] -= 0.5 * adfGeoTransform[2];
×
706
    adfGeoTransform[3] -= 0.5 * adfGeoTransform[4];
×
707
    adfGeoTransform[3] -= 0.5 * adfGeoTransform[5];
×
708

709
    m_gt = GDALGeoTransform(adfGeoTransform);
×
710
    m_bHaveGeoTransform = true;
×
711

712
    return TRUE;
×
713
}
714

715
/************************************************************************/
716
/*                         GetDictionaryItem()                          */
717
/************************************************************************/
718

719
static CPLXMLNode *GetDictionaryItem(char **papszGMLMetadata,
1✔
720
                                     const char *pszURN)
721

722
{
723
    char *pszLabel = nullptr;
1✔
724

725
    if (STARTS_WITH_CI(pszURN, "urn:jp2k:xml:"))
1✔
726
        pszLabel = CPLStrdup(pszURN + 13);
×
727
    else if (STARTS_WITH_CI(pszURN, "urn:ogc:tc:gmljp2:xml:"))
1✔
728
        pszLabel = CPLStrdup(pszURN + 22);
×
729
    else if (STARTS_WITH_CI(pszURN, "gmljp2://xml/"))
1✔
730
        pszLabel = CPLStrdup(pszURN + 13);
1✔
731
    else
732
        pszLabel = CPLStrdup(pszURN);
×
733

734
    /* -------------------------------------------------------------------- */
735
    /*      Split out label and fragment id.                                */
736
    /* -------------------------------------------------------------------- */
737
    const char *pszFragmentId = nullptr;
1✔
738

739
    {
740
        int i = 0;  // Used after for.
1✔
741
        for (; pszLabel[i] != '#'; ++i)
18✔
742
        {
743
            if (pszLabel[i] == '\0')
17✔
744
            {
745
                CPLFree(pszLabel);
×
746
                return nullptr;
×
747
            }
748
        }
749

750
        pszFragmentId = pszLabel + i + 1;
1✔
751
        pszLabel[i] = '\0';
1✔
752
    }
753

754
    /* -------------------------------------------------------------------- */
755
    /*      Can we find an XML box with the desired label?                  */
756
    /* -------------------------------------------------------------------- */
757
    const char *pszDictionary = CSLFetchNameValue(papszGMLMetadata, pszLabel);
1✔
758

759
    if (pszDictionary == nullptr)
1✔
760
    {
761
        CPLFree(pszLabel);
×
762
        return nullptr;
×
763
    }
764

765
    /* -------------------------------------------------------------------- */
766
    /*      Try and parse the dictionary.                                   */
767
    /* -------------------------------------------------------------------- */
768
    CPLXMLTreeCloser psDictTree(CPLParseXMLString(pszDictionary));
2✔
769

770
    if (psDictTree == nullptr)
1✔
771
    {
772
        CPLFree(pszLabel);
×
773
        return nullptr;
×
774
    }
775

776
    CPLStripXMLNamespace(psDictTree.get(), nullptr, TRUE);
1✔
777

778
    CPLXMLNode *psDictRoot = CPLSearchXMLNode(psDictTree.get(), "=Dictionary");
1✔
779

780
    if (psDictRoot == nullptr)
1✔
781
    {
782
        CPLFree(pszLabel);
×
783
        return nullptr;
×
784
    }
785

786
    /* -------------------------------------------------------------------- */
787
    /*      Search for matching id.                                         */
788
    /* -------------------------------------------------------------------- */
789
    CPLXMLNode *psEntry, *psHit = nullptr;
1✔
790
    for (psEntry = psDictRoot->psChild; psEntry != nullptr && psHit == nullptr;
9✔
791
         psEntry = psEntry->psNext)
8✔
792
    {
793
        const char *pszId;
794

795
        if (psEntry->eType != CXT_Element)
8✔
796
            continue;
5✔
797

798
        if (!EQUAL(psEntry->pszValue, "dictionaryEntry"))
3✔
799
            continue;
2✔
800

801
        if (psEntry->psChild == nullptr)
1✔
802
            continue;
×
803

804
        pszId = CPLGetXMLValue(psEntry->psChild, "id", "");
1✔
805

806
        if (EQUAL(pszId, pszFragmentId))
1✔
807
            psHit = CPLCloneXMLTree(psEntry->psChild);
×
808
    }
809

810
    /* -------------------------------------------------------------------- */
811
    /*      Cleanup                                                         */
812
    /* -------------------------------------------------------------------- */
813
    CPLFree(pszLabel);
1✔
814

815
    return psHit;
1✔
816
}
817

818
/************************************************************************/
819
/*                            GMLSRSLookup()                            */
820
/*                                                                      */
821
/*      Lookup an SRS in a dictionary inside this file.  We will get    */
822
/*      something like:                                                 */
823
/*        urn:jp2k:xml:CRSDictionary.xml#crs1112                        */
824
/*                                                                      */
825
/*      We need to split the filename from the fragment id, and         */
826
/*      lookup the fragment in the file if we can find it our           */
827
/*      list of labelled xml boxes.                                     */
828
/************************************************************************/
829

830
int GDALJP2Metadata::GMLSRSLookup(const char *pszURN)
1✔
831

832
{
833
    CPLXMLTreeCloser psDictEntry(GetDictionaryItem(papszGMLMetadata, pszURN));
2✔
834

835
    if (psDictEntry == nullptr)
1✔
836
        return FALSE;
1✔
837

838
    /* -------------------------------------------------------------------- */
839
    /*      Reserialize this fragment.                                      */
840
    /* -------------------------------------------------------------------- */
841
    char *pszDictEntryXML = CPLSerializeXMLTree(psDictEntry.get());
×
842
    psDictEntry.reset();
×
843

844
    /* -------------------------------------------------------------------- */
845
    /*      Try to convert into an OGRSpatialReference.                     */
846
    /* -------------------------------------------------------------------- */
847
    OGRSpatialReference oSRS;
×
848
    bool bSuccess = false;
×
849

850
    if (oSRS.importFromXML(pszDictEntryXML) == OGRERR_NONE)
×
851
    {
852
        m_oSRS = std::move(oSRS);
×
853
        m_oSRS.SetAxisMappingStrategy(OAMS_TRADITIONAL_GIS_ORDER);
×
854
        bSuccess = true;
×
855
    }
856

857
    CPLFree(pszDictEntryXML);
×
858

859
    return bSuccess;
×
860
}
861

862
/************************************************************************/
863
/*                        ParseGMLCoverageDesc()                        */
864
/************************************************************************/
865

866
int GDALJP2Metadata::ParseGMLCoverageDesc()
596✔
867

868
{
869
    if (!CPLTestBool(CPLGetConfigOption("GDAL_USE_GMLJP2", "TRUE")))
596✔
870
        return FALSE;
×
871

872
    /* -------------------------------------------------------------------- */
873
    /*      Do we have an XML doc that is apparently a coverage             */
874
    /*      description?                                                    */
875
    /* -------------------------------------------------------------------- */
876
    const char *pszCoverage =
877
        CSLFetchNameValue(papszGMLMetadata, "gml.root-instance");
596✔
878

879
    if (pszCoverage == nullptr)
596✔
880
        return FALSE;
556✔
881

882
    CPLDebug("GDALJP2Metadata", "Found GML Box:\n%s", pszCoverage);
40✔
883

884
    /* -------------------------------------------------------------------- */
885
    /*      Try parsing the XML.  Wipe any namespace prefixes.              */
886
    /* -------------------------------------------------------------------- */
887
    CPLXMLTreeCloser psXML(CPLParseXMLString(pszCoverage));
80✔
888

889
    if (psXML == nullptr)
40✔
890
        return FALSE;
×
891

892
    CPLStripXMLNamespace(psXML.get(), nullptr, TRUE);
40✔
893

894
    /* -------------------------------------------------------------------- */
895
    /*      Isolate RectifiedGrid.  Eventually we will need to support      */
896
    /*      other georeferencing objects.                                   */
897
    /* -------------------------------------------------------------------- */
898
    CPLXMLNode *psRG = CPLSearchXMLNode(psXML.get(), "=RectifiedGrid");
40✔
899
    CPLXMLNode *psOriginPoint = nullptr;
40✔
900
    const char *pszOffset1 = nullptr;
40✔
901
    const char *pszOffset2 = nullptr;
40✔
902

903
    if (psRG != nullptr)
40✔
904
    {
905
        psOriginPoint = CPLGetXMLNode(psRG, "origin.Point");
40✔
906

907
        CPLXMLNode *psOffset1 = CPLGetXMLNode(psRG, "offsetVector");
40✔
908
        if (psOffset1 != nullptr)
40✔
909
        {
910
            pszOffset1 = CPLGetXMLValue(psOffset1, "", nullptr);
40✔
911
            pszOffset2 =
912
                CPLGetXMLValue(psOffset1->psNext, "=offsetVector", nullptr);
40✔
913
        }
914
    }
915

916
    /* -------------------------------------------------------------------- */
917
    /*      If we are missing any of the origin or 2 offsets then give up.  */
918
    /* -------------------------------------------------------------------- */
919
    if (psOriginPoint == nullptr || pszOffset1 == nullptr ||
40✔
920
        pszOffset2 == nullptr)
921
    {
922
        return FALSE;
×
923
    }
924

925
    /* -------------------------------------------------------------------- */
926
    /*      Extract origin location.                                        */
927
    /* -------------------------------------------------------------------- */
928
    OGRPoint *poOriginGeometry = nullptr;
40✔
929

930
    auto poGeom = std::unique_ptr<OGRGeometry>(
931
        OGRGeometry::FromHandle(OGR_G_CreateFromGMLTree(psOriginPoint)));
40✔
932

933
    if (poGeom != nullptr && wkbFlatten(poGeom->getGeometryType()) == wkbPoint)
40✔
934
    {
935
        poOriginGeometry = poGeom->toPoint();
40✔
936
    }
937

938
    // SRS?
939
    const char *pszSRSName = CPLGetXMLValue(psOriginPoint, "srsName", nullptr);
40✔
940

941
    /* -------------------------------------------------------------------- */
942
    /*      Extract offset(s)                                               */
943
    /* -------------------------------------------------------------------- */
944
    bool bSuccess = false;
40✔
945

946
    char **papszOffset1Tokens =
947
        CSLTokenizeStringComplex(pszOffset1, " ,", FALSE, FALSE);
40✔
948
    char **papszOffset2Tokens =
949
        CSLTokenizeStringComplex(pszOffset2, " ,", FALSE, FALSE);
40✔
950

951
    if (CSLCount(papszOffset1Tokens) >= 2 &&
40✔
952
        CSLCount(papszOffset2Tokens) >= 2 && poOriginGeometry != nullptr)
40✔
953
    {
954
        m_gt[0] = poOriginGeometry->getX();
40✔
955
        m_gt[1] = CPLAtof(papszOffset1Tokens[0]);
40✔
956
        m_gt[2] = CPLAtof(papszOffset2Tokens[0]);
40✔
957
        m_gt[3] = poOriginGeometry->getY();
40✔
958
        m_gt[4] = CPLAtof(papszOffset1Tokens[1]);
40✔
959
        m_gt[5] = CPLAtof(papszOffset2Tokens[1]);
40✔
960

961
        // offset from center of pixel.
962
        m_gt[0] -= m_gt[1] * 0.5;
40✔
963
        m_gt[0] -= m_gt[2] * 0.5;
40✔
964
        m_gt[3] -= m_gt[4] * 0.5;
40✔
965
        m_gt[3] -= m_gt[5] * 0.5;
40✔
966

967
        bSuccess = true;
40✔
968
        m_bHaveGeoTransform = true;
40✔
969
    }
970

971
    CSLDestroy(papszOffset1Tokens);
40✔
972
    CSLDestroy(papszOffset2Tokens);
40✔
973

974
    /* -------------------------------------------------------------------- */
975
    /*      If we still don't have an srsName, check for it on the          */
976
    /*      boundedBy Envelope.  Some products                              */
977
    /*      (i.e. EuropeRasterTile23.jpx) use this as the only srsName      */
978
    /*      delivery vehicle.                                               */
979
    /* -------------------------------------------------------------------- */
980
    if (pszSRSName == nullptr)
40✔
981
    {
982
        pszSRSName = CPLGetXMLValue(
6✔
983
            psXML.get(), "=FeatureCollection.boundedBy.Envelope.srsName",
6✔
984
            nullptr);
985
    }
986
    /* -------------------------------------------------------------------- */
987
    /*      Examples of DGIWG_Profile_of_JPEG2000_for_Georeference_Imagery.pdf
988
     */
989
    /*      have srsName only on RectifiedGrid element.                     */
990
    /* -------------------------------------------------------------------- */
991
    if (psRG != nullptr && pszSRSName == nullptr)
40✔
992
    {
993
        pszSRSName = CPLGetXMLValue(psRG, "srsName", nullptr);
2✔
994
    }
995

996
    /* -------------------------------------------------------------------- */
997
    /*      If we have gotten a geotransform, then try to interpret the     */
998
    /*      srsName.                                                        */
999
    /* -------------------------------------------------------------------- */
1000
    bool bNeedAxisFlip = false;
40✔
1001

1002
    if (bSuccess && pszSRSName != nullptr && m_oSRS.IsEmpty())
40✔
1003
    {
1004
        OGRSpatialReference oSRS;
80✔
1005
        if (STARTS_WITH_CI(pszSRSName, "epsg:"))
40✔
1006
        {
1007
            if (oSRS.SetFromUserInput(pszSRSName) == OGRERR_NONE)
×
1008
                m_oSRS = std::move(oSRS);
×
1009
        }
1010
        else if ((STARTS_WITH_CI(pszSRSName, "urn:") &&
115✔
1011
                  strstr(pszSRSName, ":def:") != nullptr &&
35✔
1012
                  oSRS.importFromURN(pszSRSName) == OGRERR_NONE) ||
80✔
1013
                 /* GMLJP2 v2.0 uses CRS URL instead of URN */
1014
                 /* See e.g.
1015
               http://schemas.opengis.net/gmljp2/2.0/examples/minimalInstance.xml
1016
             */
1017
                 (STARTS_WITH_CI(pszSRSName,
5✔
1018
                                 "http://www.opengis.net/def/crs/") &&
4✔
1019
                  oSRS.importFromCRSURL(pszSRSName) == OGRERR_NONE))
4✔
1020
        {
1021
            m_oSRS = std::move(oSRS);
39✔
1022

1023
            // Per #2131
1024
            if (m_oSRS.EPSGTreatsAsLatLong() ||
69✔
1025
                m_oSRS.EPSGTreatsAsNorthingEasting())
30✔
1026
            {
1027
                CPLDebug("GMLJP2", "Request axis flip for SRS=%s", pszSRSName);
10✔
1028
                bNeedAxisFlip = true;
10✔
1029
            }
1030
        }
1031
        else if (!GMLSRSLookup(pszSRSName))
1✔
1032
        {
1033
            CPLDebug("GDALJP2Metadata", "Unable to evaluate SRSName=%s",
1✔
1034
                     pszSRSName);
1035
        }
1036
    }
1037

1038
    m_oSRS.SetAxisMappingStrategy(OAMS_TRADITIONAL_GIS_ORDER);
40✔
1039
    if (!m_oSRS.IsEmpty())
40✔
1040
    {
1041
        char *pszWKT = nullptr;
39✔
1042
        m_oSRS.exportToWkt(&pszWKT);
39✔
1043
        CPLDebug("GDALJP2Metadata", "Got projection from GML box: %s",
39✔
1044
                 pszWKT ? pszWKT : "");
39✔
1045
        CPLFree(pszWKT);
39✔
1046
    }
1047

1048
    /* -------------------------------------------------------------------- */
1049
    /*      Do we need to flip the axes?                                    */
1050
    /* -------------------------------------------------------------------- */
1051
    if (bNeedAxisFlip && CPLTestBool(CPLGetConfigOption(
40✔
1052
                             "GDAL_IGNORE_AXIS_ORIENTATION", "FALSE")))
1053
    {
1054
        bNeedAxisFlip = false;
1✔
1055
        CPLDebug(
1✔
1056
            "GMLJP2",
1057
            "Suppressed axis flipping based on GDAL_IGNORE_AXIS_ORIENTATION.");
1058
    }
1059

1060
    /* Some Pleiades files have explicit <gml:axisName>Easting</gml:axisName> */
1061
    /* <gml:axisName>Northing</gml:axisName> to override default EPSG order */
1062
    if (bNeedAxisFlip && psRG != nullptr)
40✔
1063
    {
1064
        int nAxisCount = 0;
9✔
1065
        bool bFirstAxisIsEastOrLong = false;
9✔
1066
        bool bSecondAxisIsNorthOrLat = false;
9✔
1067
        for (CPLXMLNode *psIter = psRG->psChild; psIter != nullptr;
82✔
1068
             psIter = psIter->psNext)
73✔
1069
        {
1070
            if (psIter->eType == CXT_Element &&
73✔
1071
                strcmp(psIter->pszValue, "axisName") == 0 &&
52✔
1072
                psIter->psChild != nullptr &&
14✔
1073
                psIter->psChild->eType == CXT_Text)
14✔
1074
            {
1075
                if (nAxisCount == 0 &&
14✔
1076
                    (STARTS_WITH_CI(psIter->psChild->pszValue, "EAST") ||
7✔
1077
                     STARTS_WITH_CI(psIter->psChild->pszValue, "LONG")))
6✔
1078
                {
1079
                    bFirstAxisIsEastOrLong = true;
1✔
1080
                }
1081
                else if (nAxisCount == 1 &&
13✔
1082
                         (STARTS_WITH_CI(psIter->psChild->pszValue, "NORTH") ||
7✔
1083
                          STARTS_WITH_CI(psIter->psChild->pszValue, "LAT")))
6✔
1084
                {
1085
                    bSecondAxisIsNorthOrLat = true;
1✔
1086
                }
1087
                ++nAxisCount;
14✔
1088
            }
1089
        }
1090
        if (bFirstAxisIsEastOrLong && bSecondAxisIsNorthOrLat)
9✔
1091
        {
1092
            CPLDebug(
1✔
1093
                "GMLJP2",
1094
                "Disable axis flip because of explicit axisName disabling it");
1095
            bNeedAxisFlip = false;
1✔
1096
        }
1097
    }
1098

1099
    psXML.reset();
40✔
1100
    psRG = nullptr;
40✔
1101

1102
    if (bNeedAxisFlip)
40✔
1103
    {
1104
        CPLDebug("GMLJP2",
8✔
1105
                 "Flipping axis orientation in GMLJP2 coverage description.");
1106

1107
        std::swap(m_gt[0], m_gt[3]);
8✔
1108

1109
        int swapWith1Index = 4;
8✔
1110
        int swapWith2Index = 5;
8✔
1111

1112
        /* Look if we have GDAL_JP2K_ALT_OFFSETVECTOR_ORDER=TRUE as a XML
1113
         * comment */
1114
        int bHasAltOffsetVectorOrderComment =
8✔
1115
            strstr(pszCoverage, "GDAL_JP2K_ALT_OFFSETVECTOR_ORDER=TRUE") !=
8✔
1116
            nullptr;
1117

1118
        if (bHasAltOffsetVectorOrderComment ||
16✔
1119
            CPLTestBool(CPLGetConfigOption("GDAL_JP2K_ALT_OFFSETVECTOR_ORDER",
8✔
1120
                                           "FALSE")))
1121
        {
1122
            swapWith1Index = 5;
×
1123
            swapWith2Index = 4;
×
1124
            CPLDebug("GMLJP2",
×
1125
                     "Choosing alternate GML \"<offsetVector>\" order based on "
1126
                     "GDAL_JP2K_ALT_OFFSETVECTOR_ORDER.");
1127
        }
1128

1129
        std::swap(m_gt[1], m_gt[swapWith1Index]);
8✔
1130
        std::swap(m_gt[2], m_gt[swapWith2Index]);
8✔
1131

1132
        /* Found in autotest/gdrivers/data/ll.jp2 */
1133
        if (m_gt[1] == 0.0 && m_gt[2] < 0.0 && m_gt[4] > 0.0 && m_gt[5] == 0.0)
8✔
1134
        {
1135
            CPLError(
3✔
1136
                CE_Warning, CPLE_AppDefined,
1137
                "It is likely that the axis order of the GMLJP2 box is not "
1138
                "consistent with the EPSG order and that the resulting "
1139
                "georeferencing "
1140
                "will be incorrect. Try setting "
1141
                "GDAL_IGNORE_AXIS_ORIENTATION=TRUE if it is the case");
1142
        }
1143
    }
1144

1145
    return !m_oSRS.IsEmpty() && bSuccess;
40✔
1146
}
1147

1148
/************************************************************************/
1149
/*                         SetSpatialRef()                              */
1150
/************************************************************************/
1151

1152
void GDALJP2Metadata::SetSpatialRef(const OGRSpatialReference *poSRS)
138✔
1153

1154
{
1155
    m_oSRS.Clear();
138✔
1156
    if (poSRS)
138✔
1157
        m_oSRS = *poSRS;
126✔
1158
}
138✔
1159

1160
/************************************************************************/
1161
/*                              SetGCPs()                               */
1162
/************************************************************************/
1163

1164
void GDALJP2Metadata::SetGCPs(int nCount, const GDAL_GCP *pasGCPsIn)
41✔
1165

1166
{
1167
    if (nGCPCount > 0)
41✔
1168
    {
1169
        GDALDeinitGCPs(nGCPCount, pasGCPList);
×
1170
        CPLFree(pasGCPList);
×
1171
    }
1172

1173
    nGCPCount = nCount;
41✔
1174
    pasGCPList = GDALDuplicateGCPs(nGCPCount, pasGCPsIn);
41✔
1175
}
41✔
1176

1177
/************************************************************************/
1178
/*                          SetGeoTransform()                           */
1179
/************************************************************************/
1180

1181
void GDALJP2Metadata::SetGeoTransform(const GDALGeoTransform &gt)
241✔
1182

1183
{
1184
    m_bHaveGeoTransform = true;
241✔
1185
    m_gt = gt;
241✔
1186
}
241✔
1187

1188
/************************************************************************/
1189
/*                             SetRPCMD()                               */
1190
/************************************************************************/
1191

1192
void GDALJP2Metadata::SetRPCMD(char **papszRPCMDIn)
38✔
1193

1194
{
1195
    CSLDestroy(papszRPCMD);
38✔
1196
    papszRPCMD = CSLDuplicate(papszRPCMDIn);
38✔
1197
}
38✔
1198

1199
/************************************************************************/
1200
/*                          CreateJP2GeoTIFF()                          */
1201
/************************************************************************/
1202

1203
GDALJP2Box *GDALJP2Metadata::CreateJP2GeoTIFF()
225✔
1204

1205
{
1206
#ifdef HAVE_TIFF
1207
    /* -------------------------------------------------------------------- */
1208
    /*      Prepare the memory buffer containing the degenerate GeoTIFF     */
1209
    /*      file.                                                           */
1210
    /* -------------------------------------------------------------------- */
1211
    int nGTBufSize = 0;
225✔
1212
    unsigned char *pabyGTBuf = nullptr;
225✔
1213

1214
    if (GTIFMemBufFromSRS(OGRSpatialReference::ToHandle(&m_oSRS), m_gt.data(),
225✔
1215
                          nGCPCount, pasGCPList, &nGTBufSize, &pabyGTBuf,
225✔
1216
                          bPixelIsPoint, papszRPCMD) != CE_None)
450✔
1217
        return nullptr;
×
1218

1219
    if (nGTBufSize == 0)
225✔
1220
        return nullptr;
×
1221

1222
    /* -------------------------------------------------------------------- */
1223
    /*      Write to a box on the JP2 file.                                 */
1224
    /* -------------------------------------------------------------------- */
1225
    GDALJP2Box *poBox;
1226

1227
    poBox = GDALJP2Box::CreateUUIDBox(msi_uuid2, nGTBufSize, pabyGTBuf);
225✔
1228

1229
    CPLFree(pabyGTBuf);
225✔
1230

1231
    return poBox;
225✔
1232
#else
1233
    return nullptr;
1234
#endif
1235
}
1236

1237
/************************************************************************/
1238
/*                          IsSRSCompatible()                           */
1239
/************************************************************************/
1240

1241
/* Returns true if the SRS can be references through a EPSG code, or encoded
1242
 * as a GML SRS
1243
 */
1244
bool GDALJP2Metadata::IsSRSCompatible(const OGRSpatialReference *poSRS)
76✔
1245
{
1246
    const char *pszAuthName = poSRS->GetAuthorityName(nullptr);
76✔
1247
    const char *pszAuthCode = poSRS->GetAuthorityCode(nullptr);
76✔
1248

1249
    if (pszAuthName && pszAuthCode && EQUAL(pszAuthName, "epsg"))
76✔
1250
    {
1251
        if (atoi(pszAuthCode))
61✔
1252
            return true;
61✔
1253
    }
1254

1255
    CPLErrorStateBackuper oErrorStateBackuper(CPLQuietErrorHandler);
15✔
1256
    char *pszGMLDef = nullptr;
15✔
1257
    const bool bRet = (poSRS->exportToXML(&pszGMLDef, nullptr) == OGRERR_NONE);
15✔
1258
    CPLFree(pszGMLDef);
15✔
1259
    return bRet;
15✔
1260
}
1261

1262
/************************************************************************/
1263
/*                     GetGMLJP2GeoreferencingInfo()                    */
1264
/************************************************************************/
1265

1266
void GDALJP2Metadata::GetGMLJP2GeoreferencingInfo(
79✔
1267
    int &nEPSGCode, double adfOrigin[2], double adfXVector[2],
1268
    double adfYVector[2], const char *&pszComment, CPLString &osDictBox,
1269
    bool &bNeedAxisFlip)
1270
{
1271

1272
    /* -------------------------------------------------------------------- */
1273
    /*      Try do determine a PCS or GCS code we can use.                  */
1274
    /* -------------------------------------------------------------------- */
1275
    nEPSGCode = 0;
79✔
1276
    bNeedAxisFlip = false;
79✔
1277
    OGRSpatialReference oSRS(m_oSRS);
158✔
1278

1279
    const char *pszAuthName = oSRS.GetAuthorityName(nullptr);
79✔
1280
    const char *pszAuthCode = oSRS.GetAuthorityCode(nullptr);
79✔
1281

1282
    if (pszAuthName && pszAuthCode && EQUAL(pszAuthName, "epsg"))
79✔
1283
    {
1284
        nEPSGCode = atoi(pszAuthCode);
56✔
1285
    }
1286

1287
    {
1288
        CPLErrorStateBackuper oErrorStateBackuper;
158✔
1289
        // Determine if we need to flip axis. Reimport from EPSG and make
1290
        // sure not to strip axis definitions to determine the axis order.
1291
        if (nEPSGCode != 0 && oSRS.importFromEPSG(nEPSGCode) == OGRERR_NONE)
79✔
1292
        {
1293
            if (oSRS.EPSGTreatsAsLatLong() ||
106✔
1294
                oSRS.EPSGTreatsAsNorthingEasting())
50✔
1295
            {
1296
                bNeedAxisFlip = true;
6✔
1297
            }
1298
        }
1299
    }
1300

1301
    /* -------------------------------------------------------------------- */
1302
    /*      Prepare coverage origin and offset vectors.  Take axis          */
1303
    /*      order into account if needed.                                   */
1304
    /* -------------------------------------------------------------------- */
1305
    adfOrigin[0] = m_gt[0] + m_gt[1] * 0.5 + m_gt[4] * 0.5;
79✔
1306
    adfOrigin[1] = m_gt[3] + m_gt[2] * 0.5 + m_gt[5] * 0.5;
79✔
1307
    adfXVector[0] = m_gt[1];
79✔
1308
    adfXVector[1] = m_gt[2];
79✔
1309

1310
    adfYVector[0] = m_gt[4];
79✔
1311
    adfYVector[1] = m_gt[5];
79✔
1312

1313
    if (bNeedAxisFlip && CPLTestBool(CPLGetConfigOption(
79✔
1314
                             "GDAL_IGNORE_AXIS_ORIENTATION", "FALSE")))
1315
    {
1316
        bNeedAxisFlip = false;
×
1317
        CPLDebug("GMLJP2", "Suppressed axis flipping on write based on "
×
1318
                           "GDAL_IGNORE_AXIS_ORIENTATION.");
1319
    }
1320

1321
    pszComment = "";
79✔
1322
    if (bNeedAxisFlip)
79✔
1323
    {
1324
        CPLDebug("GMLJP2", "Flipping GML coverage axis order.");
6✔
1325

1326
        std::swap(adfOrigin[0], adfOrigin[1]);
6✔
1327

1328
        if (CPLTestBool(CPLGetConfigOption("GDAL_JP2K_ALT_OFFSETVECTOR_ORDER",
6✔
1329
                                           "FALSE")))
1330
        {
1331
            CPLDebug("GMLJP2",
×
1332
                     "Choosing alternate GML \"<offsetVector>\" order based on "
1333
                     "GDAL_JP2K_ALT_OFFSETVECTOR_ORDER.");
1334

1335
            /* In this case the swapping is done in an "X" pattern */
1336
            std::swap(adfXVector[0], adfYVector[1]);
×
1337
            std::swap(adfYVector[0], adfXVector[1]);
×
1338

1339
            /* We add this as an XML comment so that we know we must do
1340
             * OffsetVector flipping on reading */
1341
            pszComment =
×
1342
                "              <!-- GDAL_JP2K_ALT_OFFSETVECTOR_ORDER=TRUE: "
1343
                "First "
1344
                "value of offset is latitude/northing component of the "
1345
                "latitude/northing axis. -->\n";
1346
        }
1347
        else
1348
        {
1349
            std::swap(adfXVector[0], adfXVector[1]);
6✔
1350
            std::swap(adfYVector[0], adfYVector[1]);
6✔
1351
        }
1352
    }
1353

1354
    /* -------------------------------------------------------------------- */
1355
    /*      If we need a user defined CRSDictionary entry, prepare it       */
1356
    /*      here.                                                           */
1357
    /* -------------------------------------------------------------------- */
1358
    if (nEPSGCode == 0)
79✔
1359
    {
1360
        char *pszGMLDef = nullptr;
23✔
1361

1362
        CPLErrorStateBackuper oErrorStateBackuper;
46✔
1363
        if (oSRS.exportToXML(&pszGMLDef, nullptr) == OGRERR_NONE)
23✔
1364
        {
1365
            char *pszWKT = nullptr;
13✔
1366
            oSRS.exportToWkt(&pszWKT);
13✔
1367
            char *pszXMLEscapedWKT = CPLEscapeString(pszWKT, -1, CPLES_XML);
13✔
1368
            CPLFree(pszWKT);
13✔
1369
            osDictBox.Printf(
13✔
1370
                "<gml:Dictionary gml:id=\"CRSU1\" \n"
1371
                "        xmlns:gml=\"http://www.opengis.net/gml\"\n"
1372
                "        xmlns:xlink=\"http://www.w3.org/1999/xlink\"\n"
1373
                "        "
1374
                "xmlns:xsi=\"http://www.w3.org/2001/XMLSchema-instance\"\n"
1375
                "        xsi:schemaLocation=\"http://www.opengis.net/gml "
1376
                "http://schemas.opengis.net/gml/3.1.1/base/gml.xsd\">\n"
1377
                "  <gml:description>Dictionary for custom SRS "
1378
                "%s</gml:description>\n"
1379
                "  <gml:name>Dictionary for custom SRS</gml:name>\n"
1380
                "  <gml:dictionaryEntry>\n"
1381
                "%s\n"
1382
                "  </gml:dictionaryEntry>\n"
1383
                "</gml:Dictionary>\n",
1384
                pszXMLEscapedWKT, pszGMLDef);
13✔
1385
            CPLFree(pszXMLEscapedWKT);
13✔
1386
        }
1387
        CPLFree(pszGMLDef);
23✔
1388
    }
1389
}
79✔
1390

1391
/************************************************************************/
1392
/*                          CreateGMLJP2()                              */
1393
/************************************************************************/
1394

1395
GDALJP2Box *GDALJP2Metadata::CreateGMLJP2(int nXSize, int nYSize)
62✔
1396

1397
{
1398
    /* -------------------------------------------------------------------- */
1399
    /*      This is a backdoor to let us embed a literal gmljp2 chunk       */
1400
    /*      supplied by the user as an external file.  This is mostly       */
1401
    /*      for preparing test files with exotic contents.                  */
1402
    /* -------------------------------------------------------------------- */
1403
    if (CPLGetConfigOption("GMLJP2OVERRIDE", nullptr) != nullptr)
62✔
1404
    {
1405
        VSILFILE *fp = VSIFOpenL(CPLGetConfigOption("GMLJP2OVERRIDE", ""), "r");
7✔
1406
        char *pszGML = nullptr;
7✔
1407

1408
        if (fp == nullptr)
7✔
1409
        {
1410
            CPLError(CE_Failure, CPLE_AppDefined,
×
1411
                     "Unable to open GMLJP2OVERRIDE file.");
1412
            return nullptr;
×
1413
        }
1414

1415
        CPL_IGNORE_RET_VAL(VSIFSeekL(fp, 0, SEEK_END));
7✔
1416
        const int nLength = static_cast<int>(VSIFTellL(fp));
7✔
1417
        pszGML = static_cast<char *>(CPLCalloc(1, nLength + 1));
7✔
1418
        CPL_IGNORE_RET_VAL(VSIFSeekL(fp, 0, SEEK_SET));
7✔
1419
        CPL_IGNORE_RET_VAL(VSIFReadL(pszGML, 1, nLength, fp));
7✔
1420
        CPL_IGNORE_RET_VAL(VSIFCloseL(fp));
7✔
1421

1422
        GDALJP2Box *apoGMLBoxes[2];
1423

1424
        apoGMLBoxes[0] = GDALJP2Box::CreateLblBox("gml.data");
7✔
1425
        apoGMLBoxes[1] =
7✔
1426
            GDALJP2Box::CreateLabelledXMLAssoc("gml.root-instance", pszGML);
7✔
1427

1428
        GDALJP2Box *poGMLData = GDALJP2Box::CreateAsocBox(2, apoGMLBoxes);
7✔
1429

1430
        delete apoGMLBoxes[0];
7✔
1431
        delete apoGMLBoxes[1];
7✔
1432

1433
        CPLFree(pszGML);
7✔
1434

1435
        return poGMLData;
7✔
1436
    }
1437

1438
    int nEPSGCode;
1439
    double adfOrigin[2];
1440
    double adfXVector[2];
1441
    double adfYVector[2];
1442
    const char *pszComment = "";
55✔
1443
    CPLString osDictBox;
110✔
1444
    bool bNeedAxisFlip = false;
55✔
1445
    GetGMLJP2GeoreferencingInfo(nEPSGCode, adfOrigin, adfXVector, adfYVector,
55✔
1446
                                pszComment, osDictBox, bNeedAxisFlip);
1447

1448
    char szSRSName[100];
1449
    if (nEPSGCode != 0)
55✔
1450
        snprintf(szSRSName, sizeof(szSRSName), "urn:ogc:def:crs:EPSG::%d",
32✔
1451
                 nEPSGCode);
1452
    else
1453
        snprintf(szSRSName, sizeof(szSRSName), "%s",
23✔
1454
                 "gmljp2://xml/CRSDictionary.gml#ogrcrs1");
1455

1456
    // Compute bounding box
1457
    double dfX1 = m_gt[0];
55✔
1458
    double dfX2 = m_gt[0] + nXSize * m_gt[1];
55✔
1459
    double dfX3 = m_gt[0] + nYSize * m_gt[2];
55✔
1460
    double dfX4 = m_gt[0] + nXSize * m_gt[1] + nYSize * m_gt[2];
55✔
1461
    double dfY1 = m_gt[3];
55✔
1462
    double dfY2 = m_gt[3] + nXSize * m_gt[4];
55✔
1463
    double dfY3 = m_gt[3] + nYSize * m_gt[5];
55✔
1464
    double dfY4 = m_gt[3] + nXSize * m_gt[4] + nYSize * m_gt[5];
55✔
1465
    double dfLCX = std::min(std::min(dfX1, dfX2), std::min(dfX3, dfX4));
55✔
1466
    double dfLCY = std::min(std::min(dfY1, dfY2), std::min(dfY3, dfY4));
55✔
1467
    double dfUCX = std::max(std::max(dfX1, dfX2), std::max(dfX3, dfX4));
55✔
1468
    double dfUCY = std::max(std::max(dfY1, dfY2), std::max(dfY3, dfY4));
55✔
1469
    if (bNeedAxisFlip)
55✔
1470
    {
1471
        std::swap(dfLCX, dfLCY);
5✔
1472
        std::swap(dfUCX, dfUCY);
5✔
1473
    }
1474

1475
    /* -------------------------------------------------------------------- */
1476
    /*      For now we hardcode for a minimal instance format.              */
1477
    /* -------------------------------------------------------------------- */
1478
    CPLString osDoc;
55✔
1479

1480
    osDoc.Printf(
55✔
1481
        "<gml:FeatureCollection\n"
1482
        "   xmlns:gml=\"http://www.opengis.net/gml\"\n"
1483
        "   xmlns:xsi=\"http://www.w3.org/2001/XMLSchema-instance\"\n"
1484
        "   xsi:schemaLocation=\"http://www.opengis.net/gml "
1485
        "http://schemas.opengis.net/gml/3.1.1/profiles/gmlJP2Profile/1.0.0/"
1486
        "gmlJP2Profile.xsd\">\n"
1487
        "  <gml:boundedBy>\n"
1488
        "    <gml:Envelope srsName=\"%s\">\n"
1489
        "      <gml:lowerCorner>%.15g %.15g</gml:lowerCorner>\n"
1490
        "      <gml:upperCorner>%.15g %.15g</gml:upperCorner>\n"
1491
        "    </gml:Envelope>\n"
1492
        "  </gml:boundedBy>\n"
1493
        "  <gml:featureMember>\n"
1494
        "    <gml:FeatureCollection>\n"
1495
        "      <gml:featureMember>\n"
1496
        "        <gml:RectifiedGridCoverage dimension=\"2\" "
1497
        "gml:id=\"RGC0001\">\n"
1498
        "          <gml:rectifiedGridDomain>\n"
1499
        "            <gml:RectifiedGrid dimension=\"2\">\n"
1500
        "              <gml:limits>\n"
1501
        "                <gml:GridEnvelope>\n"
1502
        "                  <gml:low>0 0</gml:low>\n"
1503
        "                  <gml:high>%d %d</gml:high>\n"
1504
        "                </gml:GridEnvelope>\n"
1505
        "              </gml:limits>\n"
1506
        "              <gml:axisName>x</gml:axisName>\n"
1507
        "              <gml:axisName>y</gml:axisName>\n"
1508
        "              <gml:origin>\n"
1509
        "                <gml:Point gml:id=\"P0001\" srsName=\"%s\">\n"
1510
        "                  <gml:pos>%.15g %.15g</gml:pos>\n"
1511
        "                </gml:Point>\n"
1512
        "              </gml:origin>\n"
1513
        "%s"
1514
        "              <gml:offsetVector srsName=\"%s\">%.15g "
1515
        "%.15g</gml:offsetVector>\n"
1516
        "              <gml:offsetVector srsName=\"%s\">%.15g "
1517
        "%.15g</gml:offsetVector>\n"
1518
        "            </gml:RectifiedGrid>\n"
1519
        "          </gml:rectifiedGridDomain>\n"
1520
        "          <gml:rangeSet>\n"
1521
        "            <gml:File>\n"
1522
        "              <gml:rangeParameters/>\n"
1523
        "              <gml:fileName>gmljp2://codestream/0</gml:fileName>\n"
1524
        "              <gml:fileStructure>Record "
1525
        "Interleaved</gml:fileStructure>\n"
1526
        "            </gml:File>\n"
1527
        "          </gml:rangeSet>\n"
1528
        "        </gml:RectifiedGridCoverage>\n"
1529
        "      </gml:featureMember>\n"
1530
        "    </gml:FeatureCollection>\n"
1531
        "  </gml:featureMember>\n"
1532
        "</gml:FeatureCollection>\n",
1533
        szSRSName, dfLCX, dfLCY, dfUCX, dfUCY, nXSize - 1, nYSize - 1,
1534
        szSRSName, adfOrigin[0], adfOrigin[1], pszComment, szSRSName,
1535
        adfXVector[0], adfXVector[1], szSRSName, adfYVector[0], adfYVector[1]);
55✔
1536

1537
    /* -------------------------------------------------------------------- */
1538
    /*      Setup the gml.data label.                                       */
1539
    /* -------------------------------------------------------------------- */
1540
    GDALJP2Box *apoGMLBoxes[5];
1541
    int nGMLBoxes = 0;
55✔
1542

1543
    apoGMLBoxes[nGMLBoxes++] = GDALJP2Box::CreateLblBox("gml.data");
55✔
1544

1545
    /* -------------------------------------------------------------------- */
1546
    /*      Setup gml.root-instance.                                        */
1547
    /* -------------------------------------------------------------------- */
1548
    apoGMLBoxes[nGMLBoxes++] =
55✔
1549
        GDALJP2Box::CreateLabelledXMLAssoc("gml.root-instance", osDoc);
55✔
1550

1551
    /* -------------------------------------------------------------------- */
1552
    /*      Add optional dictionary.                                        */
1553
    /* -------------------------------------------------------------------- */
1554
    if (!osDictBox.empty())
55✔
1555
        apoGMLBoxes[nGMLBoxes++] =
13✔
1556
            GDALJP2Box::CreateLabelledXMLAssoc("CRSDictionary.gml", osDictBox);
13✔
1557

1558
    /* -------------------------------------------------------------------- */
1559
    /*      Bundle gml.data boxes into an association.                      */
1560
    /* -------------------------------------------------------------------- */
1561
    GDALJP2Box *poGMLData = GDALJP2Box::CreateAsocBox(nGMLBoxes, apoGMLBoxes);
55✔
1562

1563
    /* -------------------------------------------------------------------- */
1564
    /*      Cleanup working boxes.                                          */
1565
    /* -------------------------------------------------------------------- */
1566
    while (nGMLBoxes > 0)
178✔
1567
        delete apoGMLBoxes[--nGMLBoxes];
123✔
1568

1569
    return poGMLData;
55✔
1570
}
1571

1572
/************************************************************************/
1573
/*                      GDALGMLJP2GetXMLRoot()                          */
1574
/************************************************************************/
1575

1576
static CPLXMLNode *GDALGMLJP2GetXMLRoot(CPLXMLNode *psNode)
68✔
1577
{
1578
    for (; psNode != nullptr; psNode = psNode->psNext)
68✔
1579
    {
1580
        if (psNode->eType == CXT_Element && psNode->pszValue[0] != '?')
66✔
1581
            return psNode;
51✔
1582
    }
1583
    return nullptr;
2✔
1584
}
1585

1586
/************************************************************************/
1587
/*            GDALGMLJP2PatchFeatureCollectionSubstitutionGroup()       */
1588
/************************************************************************/
1589

1590
static void
1591
GDALGMLJP2PatchFeatureCollectionSubstitutionGroup(CPLXMLNode *psRoot)
8✔
1592
{
1593
    /* GML 3.2 SF profile recommends the feature collection type to derive */
1594
    /* from gml:AbstractGML to prevent it to be included in another feature */
1595
    /* collection, but this is what we want to do. So patch that... */
1596

1597
    /* <xs:element name="FeatureCollection" type="ogr:FeatureCollectionType"
1598
     * substitutionGroup="gml:AbstractGML"/> */
1599
    /* --> */
1600
    /* <xs:element name="FeatureCollection" type="ogr:FeatureCollectionType"
1601
     * substitutionGroup="gml:AbstractFeature"/> */
1602
    if (psRoot->eType == CXT_Element &&
8✔
1603
        (strcmp(psRoot->pszValue, "schema") == 0 ||
8✔
1604
         strcmp(psRoot->pszValue, "xs:schema") == 0))
8✔
1605
    {
1606
        for (CPLXMLNode *psIter = psRoot->psChild; psIter != nullptr;
52✔
1607
             psIter = psIter->psNext)
48✔
1608
        {
1609
            if (psIter->eType == CXT_Element &&
122✔
1610
                (strcmp(psIter->pszValue, "element") == 0 ||
22✔
1611
                 strcmp(psIter->pszValue, "xs:element") == 0) &&
22✔
1612
                strcmp(CPLGetXMLValue(psIter, "name", ""),
6✔
1613
                       "FeatureCollection") == 0 &&
72✔
1614
                strcmp(CPLGetXMLValue(psIter, "substitutionGroup", ""),
4✔
1615
                       "gml:AbstractGML") == 0)
1616
            {
1617
                CPLDebug(
2✔
1618
                    "GMLJP2",
1619
                    R"(Patching substitutionGroup="gml:AbstractGML" to "gml:AbstractFeature")");
1620
                CPLSetXMLValue(psIter, "#substitutionGroup",
2✔
1621
                               "gml:AbstractFeature");
1622
                break;
2✔
1623
            }
1624
        }
1625
    }
1626
}
8✔
1627

1628
/************************************************************************/
1629
/*                          CreateGMLJP2V2()                            */
1630
/************************************************************************/
1631

1632
class GMLJP2V2GMLFileDesc
1633
{
1634
  public:
1635
    CPLString osFile{};
1636
    CPLString osRemoteResource{};
1637
    CPLString osNamespace{};
1638
    CPLString osNamespacePrefix{};
1639
    CPLString osSchemaLocation{};
1640
    int bInline = true;
1641
    int bParentCoverageCollection = true;
1642
};
1643

1644
class GMLJP2V2AnnotationDesc
1645
{
1646
  public:
1647
    CPLString osFile{};
1648
};
1649

1650
class GMLJP2V2MetadataDesc
1651
{
1652
  public:
1653
    CPLString osFile{};
1654
    CPLString osContent{};
1655
    CPLString osTemplateFile{};
1656
    CPLString osSourceFile{};
1657
    int bGDALMetadata = false;
1658
    int bParentCoverageCollection = true;
1659
};
1660

1661
class GMLJP2V2StyleDesc
1662
{
1663
  public:
1664
    CPLString osFile{};
1665
    int bParentCoverageCollection = true;
1666
};
1667

1668
class GMLJP2V2ExtensionDesc
1669
{
1670
  public:
1671
    CPLString osFile{};
1672
    int bParentCoverageCollection = true;
1673
};
1674

1675
class GMLJP2V2BoxDesc
1676
{
1677
  public:
1678
    CPLString osFile{};
1679
    CPLString osLabel{};
1680
};
1681

1682
GDALJP2Box *GDALJP2Metadata::CreateGMLJP2V2(int nXSize, int nYSize,
28✔
1683
                                            const char *pszDefFilename,
1684
                                            GDALDataset *poSrcDS)
1685

1686
{
1687
    CPLString osRootGMLId = "ID_GMLJP2_0";
56✔
1688
    CPLString osGridCoverage;
56✔
1689
    CPLString osGridCoverageFile;
56✔
1690
    CPLString osCoverageRangeTypeXML;
56✔
1691
    bool bCRSURL = true;
28✔
1692
    std::vector<GMLJP2V2MetadataDesc> aoMetadata;
56✔
1693
    std::vector<GMLJP2V2AnnotationDesc> aoAnnotations;
56✔
1694
    std::vector<GMLJP2V2GMLFileDesc> aoGMLFiles;
56✔
1695
    std::vector<GMLJP2V2StyleDesc> aoStyles;
56✔
1696
    std::vector<GMLJP2V2ExtensionDesc> aoExtensions;
56✔
1697
    std::vector<GMLJP2V2BoxDesc> aoBoxes;
56✔
1698

1699
    /* -------------------------------------------------------------------- */
1700
    /*      Parse definition file.                                          */
1701
    /* -------------------------------------------------------------------- */
1702
    if (pszDefFilename && !EQUAL(pszDefFilename, "YES") &&
28✔
1703
        !EQUAL(pszDefFilename, "TRUE"))
26✔
1704
    {
1705
        GByte *pabyContent = nullptr;
26✔
1706
        if (pszDefFilename[0] != '{')
26✔
1707
        {
1708
            if (!VSIIngestFile(nullptr, pszDefFilename, &pabyContent, nullptr,
7✔
1709
                               -1))
1710
                return nullptr;
3✔
1711
        }
1712

1713
        /*
1714
        {
1715
            "#doc" : "Unless otherwise specified, all elements are optional",
1716

1717
            "#root_instance_doc": "Describe content of the
1718
        GMLJP2CoverageCollection", "root_instance": {
1719
                "#gml_id_doc": "Specify GMLJP2CoverageCollection id here.
1720
        Default is ID_GMLJP2_0", "gml_id": "some_gml_id",
1721

1722
                "#grid_coverage_file_doc": [
1723
                    "External XML file, whose root might be a
1724
        GMLJP2GridCoverage, ", "GMLJP2RectifiedGridCoverage or a
1725
        GMLJP2ReferenceableGridCoverage", "If not specified, GDAL will
1726
        auto-generate a GMLJP2RectifiedGridCoverage" ], "grid_coverage_file":
1727
        "gmljp2gridcoverage.xml",
1728

1729
                "#grid_coverage_range_type_field_predefined_name_doc": [
1730
                    "One of Color, Elevation_meter or Panchromatic ",
1731
                    "to fill gmlcov:rangeType/swe:DataRecord/swe:field",
1732
                    "Only used if grid_coverage_file is not defined.",
1733
                    "Exclusive with grid_coverage_range_type_file" ],
1734
                "grid_coverage_range_type_field_predefined_name": "Color",
1735

1736
                "#grid_coverage_range_type_file_doc": [
1737
                    "File that is XML content to put under
1738
        gml:RectifiedGrid/gmlcov:rangeType", "Only used if grid_coverage_file is
1739
        not defined.", "Exclusive with
1740
        grid_coverage_range_type_field_predefined_name" ],
1741
                "grid_coverage_range_type_file": "grid_coverage_range_type.xml",
1742

1743
                "#crs_url_doc": [
1744
                    "true for http://www.opengis.net/def/crs/EPSG/0/XXXX CRS
1745
        URL.", "If false, use CRS URN. Default value is true" ], "crs_url":
1746
        true,
1747

1748
                "#metadata_doc": [ "An array of metadata items. Can be either
1749
        strings, with ", "a filename or directly inline XML content, or either
1750
        ", "a more complete description." ], "metadata": [
1751

1752
                    "dcmetadata.xml",
1753

1754
                    {
1755
                        "#file_doc": "Can use relative or absolute paths.
1756
        Exclusive of content, gdal_metadata and generated_metadata.", "file":
1757
        "dcmetadata.xml",
1758

1759
                        "#gdal_metadata_doc": "Whether to serialize GDAL
1760
        metadata as GDALMultiDomainMetadata", "gdal_metadata": false,
1761

1762
                        "#dynamic_metadata_doc":
1763
                            [ "The metadata file will be generated from a
1764
        template and a source file.", "The template is a valid GMLJP2 metadata
1765
        XML tree with placeholders like",
1766
                              "{{{XPATH(some_xpath_expression)}}}",
1767
                              "that are evaluated from the source XML file.
1768
        Typical use case", "is to generate a gmljp2:eopMetadata from the XML
1769
        metadata", "provided by the image provider in their own particular
1770
        format." ], "dynamic_metadata" :
1771
                        {
1772
                            "template": "my_template.xml",
1773
                            "source": "my_source.xml"
1774
                        },
1775

1776
                        "#content": "Exclusive of file. Inline XML metadata
1777
        content", "content": "<gmljp2:metadata>Some simple textual
1778
        metadata</gmljp2:metadata>",
1779

1780
                        "#parent_node": ["Where to put the metadata.",
1781
                                         "Under CoverageCollection (default) or
1782
        GridCoverage" ], "parent_node": "CoverageCollection"
1783
                    }
1784
                ],
1785

1786
                "#annotations_doc": [ "An array of filenames, either directly
1787
        KML files", "or other vector files recognized by GDAL that ", "will be
1788
        translated on-the-fly as KML" ], "annotations": [ "my.kml"
1789
                ],
1790

1791
                "#gml_filelist_doc" :[
1792
                    "An array of GML files. Can be either GML filenames, ",
1793
                    "or a more complete description" ],
1794
                "gml_filelist": [
1795

1796
                    "my.gml",
1797

1798
                    {
1799
                        "#file_doc": "Can use relative or absolute paths.
1800
        Exclusive of remote_resource", "file": "converted/test_0.gml",
1801

1802
                        "#remote_resource_doc": "URL of a feature collection
1803
        that must be referenced through a xlink:href", "remote_resource":
1804
        "http://svn.osgeo.org/gdal/trunk/autotest/ogr/data/expected_gml_gml32.gml",
1805

1806
                        "#namespace_doc": ["The namespace in schemaLocation for
1807
        which to substitute", "its original schemaLocation with the one provided
1808
        below.", "Ignored for a remote_resource"], "namespace":
1809
        "http://example.com",
1810

1811
                        "#schema_location_doc": ["Value of the substituted
1812
        schemaLocation. ", "Typically a schema box label (link)", "Ignored for a
1813
        remote_resource"], "schema_location": "gmljp2://xml/schema_0.xsd",
1814

1815
                        "#inline_doc": [
1816
                            "Whether to inline the content, or put it in a
1817
        separate xml box. Default is true", "Ignored for a remote_resource." ],
1818
                        "inline": true,
1819

1820
                        "#parent_node": ["Where to put the FeatureCollection.",
1821
                                         "Under CoverageCollection (default) or
1822
        GridCoverage" ], "parent_node": "CoverageCollection"
1823
                    }
1824
                ],
1825

1826
                "#styles_doc": [ "An array of styles. For example SLD files" ],
1827
                "styles" : [
1828
                    {
1829
                        "#file_doc": "Can use relative or absolute paths.",
1830
                        "file": "my.sld",
1831

1832
                        "#parent_node": ["Where to put the FeatureCollection.",
1833
                                         "Under CoverageCollection (default) or
1834
        GridCoverage" ], "parent_node": "CoverageCollection"
1835
                    }
1836
                ],
1837

1838
                "#extensions_doc": [ "An array of extensions." ],
1839
                "extensions" : [
1840
                    {
1841
                        "#file_doc": "Can use relative or absolute paths.",
1842
                        "file": "my.xml",
1843

1844
                        "#parent_node": ["Where to put the FeatureCollection.",
1845
                                         "Under CoverageCollection (default) or
1846
        GridCoverage" ], "parent_node": "CoverageCollection"
1847
                    }
1848
                ]
1849
            },
1850

1851
            "#boxes_doc": "An array to describe the content of XML asoc boxes",
1852
            "boxes": [
1853
                {
1854
                    "#file_doc": "can use relative or absolute paths. Required",
1855
                    "file": "converted/test_0.xsd",
1856

1857
                    "#label_doc": ["the label of the XML box. If not specified,
1858
        will be the ", "filename without the directory part." ], "label":
1859
        "schema_0.xsd"
1860
                }
1861
            ]
1862
        }
1863
        */
1864

1865
        json_tokener *jstok = json_tokener_new();
25✔
1866
        json_object *poObj = json_tokener_parse_ex(
25✔
1867
            jstok,
1868
            pabyContent ? reinterpret_cast<const char *>(pabyContent)
1869
                        : pszDefFilename,
25✔
1870
            -1);
1871
        CPLFree(pabyContent);
25✔
1872
        if (jstok->err != json_tokener_success)
25✔
1873
        {
1874
            CPLError(CE_Failure, CPLE_AppDefined,
1✔
1875
                     "JSON parsing error: %s (at offset %d)",
1876
                     json_tokener_error_desc(jstok->err), jstok->char_offset);
1877
            json_tokener_free(jstok);
1✔
1878
            return nullptr;
1✔
1879
        }
1880
        json_tokener_free(jstok);
24✔
1881

1882
        json_object *poRootInstance =
1883
            CPL_json_object_object_get(poObj, "root_instance");
24✔
1884
        if (poRootInstance &&
47✔
1885
            json_object_get_type(poRootInstance) == json_type_object)
23✔
1886
        {
1887
            json_object *poGMLId =
1888
                CPL_json_object_object_get(poRootInstance, "gml_id");
23✔
1889
            if (poGMLId && json_object_get_type(poGMLId) == json_type_string)
23✔
1890
                osRootGMLId = json_object_get_string(poGMLId);
1✔
1891

1892
            json_object *poGridCoverageFile = CPL_json_object_object_get(
23✔
1893
                poRootInstance, "grid_coverage_file");
1894
            if (poGridCoverageFile &&
25✔
1895
                json_object_get_type(poGridCoverageFile) == json_type_string)
2✔
1896
                osGridCoverageFile = json_object_get_string(poGridCoverageFile);
2✔
1897

1898
            json_object *poGCRTFPN = CPL_json_object_object_get(
23✔
1899
                poRootInstance,
1900
                "grid_coverage_range_type_field_predefined_name");
1901
            if (poGCRTFPN &&
27✔
1902
                json_object_get_type(poGCRTFPN) == json_type_string)
4✔
1903
            {
1904
                CPLString osPredefinedName(json_object_get_string(poGCRTFPN));
8✔
1905
                if (EQUAL(osPredefinedName, "Color"))
4✔
1906
                {
1907
                    osCoverageRangeTypeXML =
1908
                        "<swe:DataRecord>"
1909
                        "<swe:field name=\"Color\">"
1910
                        "<swe:Quantity "
1911
                        "definition=\"http://www.opengis.net/def/ogc-eo/opt/"
1912
                        "SpectralMode/Color\">"
1913
                        "<swe:description>Color image</swe:description>"
1914
                        "<swe:uom code=\"unity\"/>"
1915
                        "</swe:Quantity>"
1916
                        "</swe:field>"
1917
                        "</swe:DataRecord>";
1✔
1918
                }
1919
                else if (EQUAL(osPredefinedName, "Elevation_meter"))
3✔
1920
                {
1921
                    osCoverageRangeTypeXML =
1922
                        "<swe:DataRecord>"
1923
                        "<swe:field name=\"Elevation\">"
1924
                        "<swe:Quantity "
1925
                        "definition=\"http://inspire.ec.europa.eu/enumeration/"
1926
                        "ElevationPropertyTypeValue/height\" "
1927
                        "referenceFrame=\"http://www.opengis.net/def/crs/EPSG/"
1928
                        "0/5714\">"
1929
                        "<swe:description>Elevation above sea "
1930
                        "level</swe:description>"
1931
                        "<swe:uom code=\"m\"/>"
1932
                        "</swe:Quantity>"
1933
                        "</swe:field>"
1934
                        "</swe:DataRecord>";
1✔
1935
                }
1936
                else if (EQUAL(osPredefinedName, "Panchromatic"))
2✔
1937
                {
1938
                    osCoverageRangeTypeXML =
1939
                        "<swe:DataRecord>"
1940
                        "<swe:field name=\"Panchromatic\">"
1941
                        "<swe:Quantity "
1942
                        "definition=\"http://www.opengis.net/def/ogc-eo/opt/"
1943
                        "SpectralMode/Panchromatic\">"
1944
                        "<swe:description>Panchromatic "
1945
                        "Channel</swe:description>"
1946
                        "<swe:uom code=\"unity\"/>"
1947
                        "</swe:Quantity>"
1948
                        "</swe:field>"
1949
                        "</swe:DataRecord>";
1✔
1950
                }
1951
                else
1952
                {
1953
                    CPLError(CE_Warning, CPLE_AppDefined,
1✔
1954
                             "Unrecognized value for "
1955
                             "grid_coverage_range_type_field_predefined_name");
1956
                }
1957
            }
1958
            else
1959
            {
1960
                json_object *poGCRTFile = CPL_json_object_object_get(
19✔
1961
                    poRootInstance, "grid_coverage_range_type_file");
1962
                if (poGCRTFile &&
21✔
1963
                    json_object_get_type(poGCRTFile) == json_type_string)
2✔
1964
                {
1965
                    CPLXMLTreeCloser psTmp(
1966
                        CPLParseXMLFile(json_object_get_string(poGCRTFile)));
4✔
1967
                    if (psTmp != nullptr)
2✔
1968
                    {
1969
                        CPLXMLNode *psTmpRoot =
1970
                            GDALGMLJP2GetXMLRoot(psTmp.get());
1✔
1971
                        if (psTmpRoot)
1✔
1972
                        {
1973
                            char *pszTmp = CPLSerializeXMLTree(psTmpRoot);
1✔
1974
                            osCoverageRangeTypeXML = pszTmp;
1✔
1975
                            CPLFree(pszTmp);
1✔
1976
                        }
1977
                    }
1978
                }
1979
            }
1980

1981
            json_object *poCRSURL =
1982
                CPL_json_object_object_get(poRootInstance, "crs_url");
23✔
1983
            if (poCRSURL && json_object_get_type(poCRSURL) == json_type_boolean)
23✔
1984
                bCRSURL = CPL_TO_BOOL(json_object_get_boolean(poCRSURL));
1✔
1985

1986
            json_object *poMetadatas =
1987
                CPL_json_object_object_get(poRootInstance, "metadata");
23✔
1988
            if (poMetadatas &&
37✔
1989
                json_object_get_type(poMetadatas) == json_type_array)
14✔
1990
            {
1991
                auto nLength = json_object_array_length(poMetadatas);
14✔
1992
                for (decltype(nLength) i = 0; i < nLength; ++i)
34✔
1993
                {
1994
                    json_object *poMetadata =
1995
                        json_object_array_get_idx(poMetadatas, i);
20✔
1996
                    if (poMetadata &&
40✔
1997
                        json_object_get_type(poMetadata) == json_type_string)
20✔
1998
                    {
1999
                        GMLJP2V2MetadataDesc oDesc;
8✔
2000
                        const char *pszStr = json_object_get_string(poMetadata);
4✔
2001
                        if (pszStr[0] == '<')
4✔
2002
                            oDesc.osContent = pszStr;
2✔
2003
                        else
2004
                            oDesc.osFile = pszStr;
2✔
2005
                        aoMetadata.push_back(std::move(oDesc));
4✔
2006
                    }
2007
                    else if (poMetadata && json_object_get_type(poMetadata) ==
16✔
2008
                                               json_type_object)
2009
                    {
2010
                        const char *pszFile = nullptr;
16✔
2011
                        json_object *poFile =
2012
                            CPL_json_object_object_get(poMetadata, "file");
16✔
2013
                        if (poFile &&
18✔
2014
                            json_object_get_type(poFile) == json_type_string)
2✔
2015
                            pszFile = json_object_get_string(poFile);
2✔
2016

2017
                        const char *pszContent = nullptr;
16✔
2018
                        json_object *poContent =
2019
                            CPL_json_object_object_get(poMetadata, "content");
16✔
2020
                        if (poContent &&
18✔
2021
                            json_object_get_type(poContent) == json_type_string)
2✔
2022
                            pszContent = json_object_get_string(poContent);
2✔
2023

2024
                        const char *pszTemplate = nullptr;
16✔
2025
                        const char *pszSource = nullptr;
16✔
2026
                        json_object *poDynamicMetadata =
2027
                            CPL_json_object_object_get(poMetadata,
16✔
2028
                                                       "dynamic_metadata");
2029
                        if (poDynamicMetadata &&
27✔
2030
                            json_object_get_type(poDynamicMetadata) ==
11✔
2031
                                json_type_object)
2032
                        {
2033
#ifdef HAVE_LIBXML2
2034
                            if (CPLTestBool(CPLGetConfigOption(
11✔
2035
                                    "GDAL_DEBUG_PROCESS_DYNAMIC_METADATA",
2036
                                    "YES")))
2037
                            {
2038
                                json_object *poTemplate =
2039
                                    CPL_json_object_object_get(
11✔
2040
                                        poDynamicMetadata, "template");
2041
                                if (poTemplate &&
22✔
2042
                                    json_object_get_type(poTemplate) ==
11✔
2043
                                        json_type_string)
2044
                                    pszTemplate =
2045
                                        json_object_get_string(poTemplate);
11✔
2046

2047
                                json_object *poSource =
2048
                                    CPL_json_object_object_get(
11✔
2049
                                        poDynamicMetadata, "source");
2050
                                if (poSource &&
22✔
2051
                                    json_object_get_type(poSource) ==
11✔
2052
                                        json_type_string)
2053
                                    pszSource =
2054
                                        json_object_get_string(poSource);
11✔
2055
                            }
2056
                            else
2057
#endif
2058
                            {
2059
                                CPLError(CE_Warning, CPLE_NotSupported,
×
2060
                                         "dynamic_metadata not supported since "
2061
                                         "libxml2 is not available");
2062
                            }
2063
                        }
2064

2065
                        bool bGDALMetadata = false;
16✔
2066
                        json_object *poGDALMetadata =
2067
                            CPL_json_object_object_get(poMetadata,
16✔
2068
                                                       "gdal_metadata");
2069
                        if (poGDALMetadata &&
17✔
2070
                            json_object_get_type(poGDALMetadata) ==
1✔
2071
                                json_type_boolean)
2072
                            bGDALMetadata = CPL_TO_BOOL(
1✔
2073
                                json_object_get_boolean(poGDALMetadata));
2074

2075
                        if (pszFile != nullptr || pszContent != nullptr ||
16✔
2076
                            (pszTemplate != nullptr && pszSource != nullptr) ||
12✔
2077
                            bGDALMetadata)
2078
                        {
2079
                            GMLJP2V2MetadataDesc oDesc;
32✔
2080
                            if (pszFile)
16✔
2081
                                oDesc.osFile = pszFile;
2✔
2082
                            if (pszContent)
16✔
2083
                                oDesc.osContent = pszContent;
2✔
2084
                            if (pszTemplate)
16✔
2085
                                oDesc.osTemplateFile = pszTemplate;
11✔
2086
                            if (pszSource)
16✔
2087
                                oDesc.osSourceFile = pszSource;
11✔
2088
                            oDesc.bGDALMetadata = bGDALMetadata;
16✔
2089

2090
                            json_object *poLocation =
2091
                                CPL_json_object_object_get(poMetadata,
16✔
2092
                                                           "parent_node");
2093
                            if (poLocation &&
20✔
2094
                                json_object_get_type(poLocation) ==
4✔
2095
                                    json_type_string)
2096
                            {
2097
                                const char *pszLocation =
2098
                                    json_object_get_string(poLocation);
4✔
2099
                                if (EQUAL(pszLocation, "CoverageCollection"))
4✔
2100
                                    oDesc.bParentCoverageCollection = TRUE;
2✔
2101
                                else if (EQUAL(pszLocation, "GridCoverage"))
2✔
2102
                                    oDesc.bParentCoverageCollection = FALSE;
1✔
2103
                                else
2104
                                    CPLError(
1✔
2105
                                        CE_Warning, CPLE_NotSupported,
2106
                                        "metadata[].parent_node should be "
2107
                                        "CoverageCollection or GridCoverage");
2108
                            }
2109

2110
                            aoMetadata.push_back(std::move(oDesc));
16✔
2111
                        }
2112
                    }
2113
                }
2114
            }
2115

2116
            json_object *poAnnotations =
2117
                CPL_json_object_object_get(poRootInstance, "annotations");
23✔
2118
            if (poAnnotations &&
25✔
2119
                json_object_get_type(poAnnotations) == json_type_array)
2✔
2120
            {
2121
                auto nLength = json_object_array_length(poAnnotations);
2✔
2122
                for (decltype(nLength) i = 0; i < nLength; ++i)
7✔
2123
                {
2124
                    json_object *poAnnotation =
2125
                        json_object_array_get_idx(poAnnotations, i);
5✔
2126
                    if (poAnnotation &&
10✔
2127
                        json_object_get_type(poAnnotation) == json_type_string)
5✔
2128
                    {
2129
                        GMLJP2V2AnnotationDesc oDesc;
10✔
2130
                        oDesc.osFile = json_object_get_string(poAnnotation);
5✔
2131
                        aoAnnotations.push_back(std::move(oDesc));
5✔
2132
                    }
2133
                }
2134
            }
2135

2136
            json_object *poGMLFileList =
2137
                CPL_json_object_object_get(poRootInstance, "gml_filelist");
23✔
2138
            if (poGMLFileList &&
27✔
2139
                json_object_get_type(poGMLFileList) == json_type_array)
4✔
2140
            {
2141
                auto nLength = json_object_array_length(poGMLFileList);
4✔
2142
                for (decltype(nLength) i = 0; i < nLength; ++i)
14✔
2143
                {
2144
                    json_object *poGMLFile =
2145
                        json_object_array_get_idx(poGMLFileList, i);
10✔
2146
                    if (poGMLFile &&
20✔
2147
                        json_object_get_type(poGMLFile) == json_type_object)
10✔
2148
                    {
2149
                        const char *pszFile = nullptr;
7✔
2150
                        json_object *poFile =
2151
                            CPL_json_object_object_get(poGMLFile, "file");
7✔
2152
                        if (poFile &&
13✔
2153
                            json_object_get_type(poFile) == json_type_string)
6✔
2154
                            pszFile = json_object_get_string(poFile);
6✔
2155

2156
                        const char *pszRemoteResource = nullptr;
7✔
2157
                        json_object *poRemoteResource =
2158
                            CPL_json_object_object_get(poGMLFile,
7✔
2159
                                                       "remote_resource");
2160
                        if (poRemoteResource &&
8✔
2161
                            json_object_get_type(poRemoteResource) ==
1✔
2162
                                json_type_string)
2163
                            pszRemoteResource =
2164
                                json_object_get_string(poRemoteResource);
1✔
2165

2166
                        if (pszFile || pszRemoteResource)
7✔
2167
                        {
2168
                            GMLJP2V2GMLFileDesc oDesc;
14✔
2169
                            if (pszFile)
7✔
2170
                                oDesc.osFile = pszFile;
6✔
2171
                            else if (pszRemoteResource)
1✔
2172
                                oDesc.osRemoteResource = pszRemoteResource;
1✔
2173

2174
                            json_object *poNamespacePrefix =
2175
                                CPL_json_object_object_get(poGMLFile,
7✔
2176
                                                           "namespace_prefix");
2177
                            if (poNamespacePrefix &&
7✔
2178
                                json_object_get_type(poNamespacePrefix) ==
×
2179
                                    json_type_string)
2180
                                oDesc.osNamespacePrefix =
2181
                                    json_object_get_string(poNamespacePrefix);
×
2182

2183
                            json_object *poNamespace =
2184
                                CPL_json_object_object_get(poGMLFile,
7✔
2185
                                                           "namespace");
2186
                            if (poNamespace &&
9✔
2187
                                json_object_get_type(poNamespace) ==
2✔
2188
                                    json_type_string)
2189
                                oDesc.osNamespace =
2190
                                    json_object_get_string(poNamespace);
2✔
2191

2192
                            json_object *poSchemaLocation =
2193
                                CPL_json_object_object_get(poGMLFile,
7✔
2194
                                                           "schema_location");
2195
                            if (poSchemaLocation &&
11✔
2196
                                json_object_get_type(poSchemaLocation) ==
4✔
2197
                                    json_type_string)
2198
                                oDesc.osSchemaLocation =
2199
                                    json_object_get_string(poSchemaLocation);
4✔
2200

2201
                            json_object *poInline =
2202
                                CPL_json_object_object_get(poGMLFile, "inline");
7✔
2203
                            if (poInline && json_object_get_type(poInline) ==
7✔
2204
                                                json_type_boolean)
2205
                                oDesc.bInline =
4✔
2206
                                    json_object_get_boolean(poInline);
4✔
2207

2208
                            json_object *poLocation =
2209
                                CPL_json_object_object_get(poGMLFile,
7✔
2210
                                                           "parent_node");
2211
                            if (poLocation &&
11✔
2212
                                json_object_get_type(poLocation) ==
4✔
2213
                                    json_type_string)
2214
                            {
2215
                                const char *pszLocation =
2216
                                    json_object_get_string(poLocation);
4✔
2217
                                if (EQUAL(pszLocation, "CoverageCollection"))
4✔
2218
                                    oDesc.bParentCoverageCollection = TRUE;
1✔
2219
                                else if (EQUAL(pszLocation, "GridCoverage"))
3✔
2220
                                    oDesc.bParentCoverageCollection = FALSE;
2✔
2221
                                else
2222
                                    CPLError(
1✔
2223
                                        CE_Warning, CPLE_NotSupported,
2224
                                        "gml_filelist[].parent_node should be "
2225
                                        "CoverageCollection or GridCoverage");
2226
                            }
2227

2228
                            aoGMLFiles.push_back(std::move(oDesc));
7✔
2229
                        }
2230
                    }
2231
                    else if (poGMLFile && json_object_get_type(poGMLFile) ==
3✔
2232
                                              json_type_string)
2233
                    {
2234
                        GMLJP2V2GMLFileDesc oDesc;
6✔
2235
                        oDesc.osFile = json_object_get_string(poGMLFile);
3✔
2236
                        aoGMLFiles.push_back(std::move(oDesc));
3✔
2237
                    }
2238
                }
2239
            }
2240

2241
            json_object *poStyles =
2242
                CPL_json_object_object_get(poRootInstance, "styles");
23✔
2243
            if (poStyles && json_object_get_type(poStyles) == json_type_array)
23✔
2244
            {
2245
                auto nLength = json_object_array_length(poStyles);
2✔
2246
                for (decltype(nLength) i = 0; i < nLength; ++i)
9✔
2247
                {
2248
                    json_object *poStyle =
2249
                        json_object_array_get_idx(poStyles, i);
7✔
2250
                    if (poStyle &&
14✔
2251
                        json_object_get_type(poStyle) == json_type_object)
7✔
2252
                    {
2253
                        const char *pszFile = nullptr;
4✔
2254
                        json_object *poFile =
2255
                            CPL_json_object_object_get(poStyle, "file");
4✔
2256
                        if (poFile &&
8✔
2257
                            json_object_get_type(poFile) == json_type_string)
4✔
2258
                            pszFile = json_object_get_string(poFile);
4✔
2259

2260
                        if (pszFile)
4✔
2261
                        {
2262
                            GMLJP2V2StyleDesc oDesc;
8✔
2263
                            oDesc.osFile = pszFile;
4✔
2264

2265
                            json_object *poLocation =
2266
                                CPL_json_object_object_get(poStyle,
4✔
2267
                                                           "parent_node");
2268
                            if (poLocation &&
7✔
2269
                                json_object_get_type(poLocation) ==
3✔
2270
                                    json_type_string)
2271
                            {
2272
                                const char *pszLocation =
2273
                                    json_object_get_string(poLocation);
3✔
2274
                                if (EQUAL(pszLocation, "CoverageCollection"))
3✔
2275
                                    oDesc.bParentCoverageCollection = TRUE;
1✔
2276
                                else if (EQUAL(pszLocation, "GridCoverage"))
2✔
2277
                                    oDesc.bParentCoverageCollection = FALSE;
1✔
2278
                                else
2279
                                    CPLError(
1✔
2280
                                        CE_Warning, CPLE_NotSupported,
2281
                                        "styles[].parent_node should be "
2282
                                        "CoverageCollection or GridCoverage");
2283
                            }
2284

2285
                            aoStyles.push_back(std::move(oDesc));
4✔
2286
                        }
2287
                    }
2288
                    else if (poStyle &&
6✔
2289
                             json_object_get_type(poStyle) == json_type_string)
3✔
2290
                    {
2291
                        GMLJP2V2StyleDesc oDesc;
6✔
2292
                        oDesc.osFile = json_object_get_string(poStyle);
3✔
2293
                        aoStyles.push_back(std::move(oDesc));
3✔
2294
                    }
2295
                }
2296
            }
2297

2298
            json_object *poExtensions =
2299
                CPL_json_object_object_get(poRootInstance, "extensions");
23✔
2300
            if (poExtensions &&
25✔
2301
                json_object_get_type(poExtensions) == json_type_array)
2✔
2302
            {
2303
                auto nLength = json_object_array_length(poExtensions);
2✔
2304
                for (decltype(nLength) i = 0; i < nLength; ++i)
9✔
2305
                {
2306
                    json_object *poExtension =
2307
                        json_object_array_get_idx(poExtensions, i);
7✔
2308
                    if (poExtension &&
14✔
2309
                        json_object_get_type(poExtension) == json_type_object)
7✔
2310
                    {
2311
                        const char *pszFile = nullptr;
4✔
2312
                        json_object *poFile =
2313
                            CPL_json_object_object_get(poExtension, "file");
4✔
2314
                        if (poFile &&
8✔
2315
                            json_object_get_type(poFile) == json_type_string)
4✔
2316
                            pszFile = json_object_get_string(poFile);
4✔
2317

2318
                        if (pszFile)
4✔
2319
                        {
2320
                            GMLJP2V2ExtensionDesc oDesc;
8✔
2321
                            oDesc.osFile = pszFile;
4✔
2322

2323
                            json_object *poLocation =
2324
                                CPL_json_object_object_get(poExtension,
4✔
2325
                                                           "parent_node");
2326
                            if (poLocation &&
7✔
2327
                                json_object_get_type(poLocation) ==
3✔
2328
                                    json_type_string)
2329
                            {
2330
                                const char *pszLocation =
2331
                                    json_object_get_string(poLocation);
3✔
2332
                                if (EQUAL(pszLocation, "CoverageCollection"))
3✔
2333
                                    oDesc.bParentCoverageCollection = TRUE;
1✔
2334
                                else if (EQUAL(pszLocation, "GridCoverage"))
2✔
2335
                                    oDesc.bParentCoverageCollection = FALSE;
1✔
2336
                                else
2337
                                    CPLError(
1✔
2338
                                        CE_Warning, CPLE_NotSupported,
2339
                                        "extensions[].parent_node should be "
2340
                                        "CoverageCollection or GridCoverage");
2341
                            }
2342

2343
                            aoExtensions.push_back(std::move(oDesc));
4✔
2344
                        }
2345
                    }
2346
                    else if (poExtension && json_object_get_type(poExtension) ==
3✔
2347
                                                json_type_string)
2348
                    {
2349
                        GMLJP2V2ExtensionDesc oDesc;
6✔
2350
                        oDesc.osFile = json_object_get_string(poExtension);
3✔
2351
                        aoExtensions.push_back(std::move(oDesc));
3✔
2352
                    }
2353
                }
2354
            }
2355
        }
2356

2357
        json_object *poBoxes = CPL_json_object_object_get(poObj, "boxes");
24✔
2358
        if (poBoxes && json_object_get_type(poBoxes) == json_type_array)
24✔
2359
        {
2360
            auto nLength = json_object_array_length(poBoxes);
2✔
2361
            for (decltype(nLength) i = 0; i < nLength; ++i)
6✔
2362
            {
2363
                json_object *poBox = json_object_array_get_idx(poBoxes, i);
4✔
2364
                if (poBox && json_object_get_type(poBox) == json_type_object)
4✔
2365
                {
2366
                    json_object *poFile =
2367
                        CPL_json_object_object_get(poBox, "file");
2✔
2368
                    if (poFile &&
4✔
2369
                        json_object_get_type(poFile) == json_type_string)
2✔
2370
                    {
2371
                        GMLJP2V2BoxDesc oDesc;
4✔
2372
                        oDesc.osFile = json_object_get_string(poFile);
2✔
2373

2374
                        json_object *poLabel =
2375
                            CPL_json_object_object_get(poBox, "label");
2✔
2376
                        if (poLabel &&
4✔
2377
                            json_object_get_type(poLabel) == json_type_string)
2✔
2378
                            oDesc.osLabel = json_object_get_string(poLabel);
2✔
2379
                        else
2380
                            oDesc.osLabel = CPLGetFilename(oDesc.osFile);
×
2381

2382
                        aoBoxes.push_back(std::move(oDesc));
2✔
2383
                    }
2384
                }
2385
                else if (poBox &&
4✔
2386
                         json_object_get_type(poBox) == json_type_string)
2✔
2387
                {
2388
                    GMLJP2V2BoxDesc oDesc;
4✔
2389
                    oDesc.osFile = json_object_get_string(poBox);
2✔
2390
                    oDesc.osLabel = CPLGetFilename(oDesc.osFile);
2✔
2391
                    aoBoxes.push_back(std::move(oDesc));
2✔
2392
                }
2393
            }
2394
        }
2395

2396
        json_object_put(poObj);
24✔
2397

2398
        // Check that if a GML file points to an internal schemaLocation,
2399
        // the matching box really exists.
2400
        for (const auto &oGMLFile : aoGMLFiles)
34✔
2401
        {
2402
            if (!oGMLFile.osSchemaLocation.empty() &&
14✔
2403
                STARTS_WITH(oGMLFile.osSchemaLocation, "gmljp2://xml/"))
4✔
2404
            {
2405
                const char *pszLookedLabel =
2406
                    oGMLFile.osSchemaLocation.c_str() + strlen("gmljp2://xml/");
4✔
2407
                bool bFound = false;
4✔
2408
                for (int j = 0; !bFound && j < static_cast<int>(aoBoxes.size());
9✔
2409
                     ++j)
2410
                    bFound = (strcmp(pszLookedLabel, aoBoxes[j].osLabel) == 0);
5✔
2411
                if (!bFound)
4✔
2412
                {
2413
                    CPLError(CE_Warning, CPLE_AppDefined,
1✔
2414
                             "GML file %s has a schema_location=%s, "
2415
                             "but no box with label %s is defined",
2416
                             oGMLFile.osFile.c_str(),
2417
                             oGMLFile.osSchemaLocation.c_str(), pszLookedLabel);
2418
                }
2419
            }
2420
        }
2421

2422
        // Read custom grid coverage file.
2423
        if (!osGridCoverageFile.empty())
24✔
2424
        {
2425
            CPLXMLTreeCloser psTmp(CPLParseXMLFile(osGridCoverageFile));
2✔
2426
            if (psTmp == nullptr)
2✔
2427
                return nullptr;
1✔
2428
            CPLXMLNode *psTmpRoot = GDALGMLJP2GetXMLRoot(psTmp.get());
1✔
2429
            if (psTmpRoot)
1✔
2430
            {
2431
                char *pszTmp = CPLSerializeXMLTree(psTmpRoot);
1✔
2432
                osGridCoverage = pszTmp;
1✔
2433
                CPLFree(pszTmp);
1✔
2434
            }
2435
        }
2436
    }
2437

2438
    CPLString osDictBox;
50✔
2439
    CPLString osDoc;
50✔
2440

2441
    if (osGridCoverage.empty())
25✔
2442
    {
2443
        /* --------------------------------------------------------------------
2444
         */
2445
        /*      Prepare GMLJP2RectifiedGridCoverage */
2446
        /* --------------------------------------------------------------------
2447
         */
2448
        int nEPSGCode = 0;
24✔
2449
        double adfOrigin[2];
2450
        double adfXVector[2];
2451
        double adfYVector[2];
2452
        const char *pszComment = "";
24✔
2453
        bool bNeedAxisFlip = false;
24✔
2454
        GetGMLJP2GeoreferencingInfo(nEPSGCode, adfOrigin, adfXVector,
24✔
2455
                                    adfYVector, pszComment, osDictBox,
2456
                                    bNeedAxisFlip);
2457

2458
        char szSRSName[100] = {0};
24✔
2459
        if (nEPSGCode != 0)
24✔
2460
        {
2461
            if (bCRSURL)
24✔
2462
                snprintf(szSRSName, sizeof(szSRSName),
23✔
2463
                         "http://www.opengis.net/def/crs/EPSG/0/%d", nEPSGCode);
2464
            else
2465
                snprintf(szSRSName, sizeof(szSRSName),
1✔
2466
                         "urn:ogc:def:crs:EPSG::%d", nEPSGCode);
2467
        }
2468
        else
2469
            snprintf(szSRSName, sizeof(szSRSName), "%s",
×
2470
                     "gmljp2://xml/CRSDictionary.gml#ogrcrs1");
2471

2472
        // Compute bounding box
2473
        double dfX1 = m_gt[0];
24✔
2474
        double dfX2 = m_gt[0] + nXSize * m_gt[1];
24✔
2475
        double dfX3 = m_gt[0] + nYSize * m_gt[2];
24✔
2476
        double dfX4 = m_gt[0] + nXSize * m_gt[1] + nYSize * m_gt[2];
24✔
2477
        double dfY1 = m_gt[3];
24✔
2478
        double dfY2 = m_gt[3] + nXSize * m_gt[4];
24✔
2479
        double dfY3 = m_gt[3] + nYSize * m_gt[5];
24✔
2480
        double dfY4 = m_gt[3] + nXSize * m_gt[4] + nYSize * m_gt[5];
24✔
2481
        double dfLCX = std::min(std::min(dfX1, dfX2), std::min(dfX3, dfX4));
24✔
2482
        double dfLCY = std::min(std::min(dfY1, dfY2), std::min(dfY3, dfY4));
24✔
2483
        double dfUCX = std::max(std::max(dfX1, dfX2), std::max(dfX3, dfX4));
24✔
2484
        double dfUCY = std::max(std::max(dfY1, dfY2), std::max(dfY3, dfY4));
24✔
2485
        if (bNeedAxisFlip)
24✔
2486
        {
2487
            std::swap(dfLCX, dfLCY);
1✔
2488
            std::swap(dfUCX, dfUCY);
1✔
2489
        }
2490

2491
        osGridCoverage.Printf(
48✔
2492
            "   <gmljp2:GMLJP2RectifiedGridCoverage gml:id=\"RGC_1_%s\">\n"
2493
            "     <gml:boundedBy>\n"
2494
            "       <gml:Envelope srsDimension=\"2\" srsName=\"%s\">\n"
2495
            "         <gml:lowerCorner>%.15g %.15g</gml:lowerCorner>\n"
2496
            "         <gml:upperCorner>%.15g %.15g</gml:upperCorner>\n"
2497
            "       </gml:Envelope>\n"
2498
            "     </gml:boundedBy>\n"
2499
            "     <gml:domainSet>\n"
2500
            "      <gml:RectifiedGrid gml:id=\"RGC_1_GRID_%s\" dimension=\"2\" "
2501
            "srsName=\"%s\">\n"
2502
            "       <gml:limits>\n"
2503
            "         <gml:GridEnvelope>\n"
2504
            "           <gml:low>0 0</gml:low>\n"
2505
            "           <gml:high>%d %d</gml:high>\n"
2506
            "         </gml:GridEnvelope>\n"
2507
            "       </gml:limits>\n"
2508
            "       <gml:axisName>x</gml:axisName>\n"
2509
            "       <gml:axisName>y</gml:axisName>\n"
2510
            "       <gml:origin>\n"
2511
            "         <gml:Point gml:id=\"P0001\" srsName=\"%s\">\n"
2512
            "           <gml:pos>%.15g %.15g</gml:pos>\n"
2513
            "         </gml:Point>\n"
2514
            "       </gml:origin>\n"
2515
            "%s"
2516
            "       <gml:offsetVector srsName=\"%s\">%.15g "
2517
            "%.15g</gml:offsetVector>\n"
2518
            "       <gml:offsetVector srsName=\"%s\">%.15g "
2519
            "%.15g</gml:offsetVector>\n"
2520
            "      </gml:RectifiedGrid>\n"
2521
            "     </gml:domainSet>\n"
2522
            "     <gml:rangeSet>\n"
2523
            "      <gml:File>\n"
2524
            "        <gml:rangeParameters/>\n"
2525
            "        <gml:fileName>gmljp2://codestream/0</gml:fileName>\n"
2526
            "        <gml:fileStructure>inapplicable</gml:fileStructure>\n"
2527
            "      </gml:File>\n"
2528
            "     </gml:rangeSet>\n"
2529
            "     <gmlcov:rangeType>%s</gmlcov:rangeType>\n"
2530
            "   </gmljp2:GMLJP2RectifiedGridCoverage>\n",
2531
            osRootGMLId.c_str(), szSRSName, dfLCX, dfLCY, dfUCX, dfUCY,
2532
            osRootGMLId.c_str(), szSRSName, nXSize - 1, nYSize - 1, szSRSName,
2533
            adfOrigin[0], adfOrigin[1], pszComment, szSRSName, adfXVector[0],
2534
            adfXVector[1], szSRSName, adfYVector[0], adfYVector[1],
2535
            osCoverageRangeTypeXML.c_str());
24✔
2536
    }
2537

2538
    /* -------------------------------------------------------------------- */
2539
    /*      Main node.                                                      */
2540
    /* -------------------------------------------------------------------- */
2541

2542
    // Per
2543
    // http://docs.opengeospatial.org/is/08-085r5/08-085r5.html#requirement_11
2544
    osDoc.Printf(
2545
        //"<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n"
2546
        "<gmljp2:GMLJP2CoverageCollection gml:id=\"%s\"\n"
2547
        "     xmlns:gml=\"http://www.opengis.net/gml/3.2\"\n"
2548
        "     xmlns:gmlcov=\"http://www.opengis.net/gmlcov/1.0\"\n"
2549
        "     xmlns:gmljp2=\"http://www.opengis.net/gmljp2/2.0\"\n"
2550
        "     xmlns:swe=\"http://www.opengis.net/swe/2.0\"\n"
2551
        "     xmlns:xsi=\"http://www.w3.org/2001/XMLSchema-instance\"\n"
2552
        "     xsi:schemaLocation=\"http://www.opengis.net/gmljp2/2.0 "
2553
        "http://schemas.opengis.net/gmljp2/2.0/gmljp2.xsd\">\n"
2554
        "  <gml:domainSet nilReason=\"inapplicable\"/>\n"
2555
        "  <gml:rangeSet>\n"
2556
        "    <gml:DataBlock>\n"
2557
        "       <gml:rangeParameters nilReason=\"inapplicable\"/>\n"
2558
        "       "
2559
        "<gml:doubleOrNilReasonTupleList>inapplicable</"
2560
        "gml:doubleOrNilReasonTupleList>\n"
2561
        "     </gml:DataBlock>\n"
2562
        "  </gml:rangeSet>\n"
2563
        "  <gmlcov:rangeType>\n"
2564
        "    <swe:DataRecord>\n"
2565
        "      <swe:field name=\"Collection\"> </swe:field>\n"
2566
        "    </swe:DataRecord>\n"
2567
        "  </gmlcov:rangeType>\n"
2568
        "  <gmljp2:featureMember>\n"
2569
        "%s"
2570
        "  </gmljp2:featureMember>\n"
2571
        "</gmljp2:GMLJP2CoverageCollection>\n",
2572
        osRootGMLId.c_str(), osGridCoverage.c_str());
25✔
2573

2574
    const std::string osTmpDir = VSIMemGenerateHiddenFilename("gmljp2");
50✔
2575

2576
    /* -------------------------------------------------------------------- */
2577
    /*      Process metadata, annotations and features collections.         */
2578
    /* -------------------------------------------------------------------- */
2579
    if (!aoMetadata.empty() || !aoAnnotations.empty() || !aoGMLFiles.empty() ||
36✔
2580
        !aoStyles.empty() || !aoExtensions.empty())
36✔
2581
    {
2582
        CPLXMLTreeCloser psRoot(CPLParseXMLString(osDoc));
16✔
2583
        CPLAssert(psRoot);
16✔
2584
        CPLXMLNode *psGMLJP2CoverageCollection =
2585
            GDALGMLJP2GetXMLRoot(psRoot.get());
16✔
2586
        CPLAssert(psGMLJP2CoverageCollection);
16✔
2587

2588
        for (const auto &oMetadata : aoMetadata)
36✔
2589
        {
2590
            CPLXMLTreeCloser psMetadata(nullptr);
20✔
2591
            if (!oMetadata.osFile.empty())
20✔
2592
                psMetadata =
2593
                    CPLXMLTreeCloser(CPLParseXMLFile(oMetadata.osFile));
4✔
2594
            else if (!oMetadata.osContent.empty())
16✔
2595
                psMetadata =
2596
                    CPLXMLTreeCloser(CPLParseXMLString(oMetadata.osContent));
4✔
2597
            else if (oMetadata.bGDALMetadata)
12✔
2598
            {
2599
                psMetadata = CPLXMLTreeCloser(
2✔
2600
                    CreateGDALMultiDomainMetadataXML(poSrcDS, TRUE));
1✔
2601
                if (psMetadata)
1✔
2602
                {
2603
                    CPLSetXMLValue(psMetadata.get(), "#xmlns",
1✔
2604
                                   "http://gdal.org");
2605
                    CPLXMLNode *psNewMetadata = CPLCreateXMLNode(
1✔
2606
                        nullptr, CXT_Element, "gmljp2:metadata");
2607
                    CPLAddXMLChild(psNewMetadata, psMetadata.release());
1✔
2608
                    psMetadata = CPLXMLTreeCloser(psNewMetadata);
1✔
2609
                }
2610
            }
2611
            else
2612
                psMetadata = CPLXMLTreeCloser(GDALGMLJP2GenerateMetadata(
22✔
2613
                    oMetadata.osTemplateFile, oMetadata.osSourceFile));
22✔
2614
            if (psMetadata == nullptr)
20✔
2615
                continue;
11✔
2616
            CPLXMLNode *psMetadataRoot = GDALGMLJP2GetXMLRoot(psMetadata.get());
9✔
2617
            if (psMetadataRoot)
9✔
2618
            {
2619
                if (strcmp(psMetadataRoot->pszValue, "eop:EarthObservation") ==
9✔
2620
                    0)
2621
                {
2622
                    CPLXMLNode *psNewMetadata = CPLCreateXMLNode(
×
2623
                        nullptr, CXT_Element, "gmljp2:eopMetadata");
2624
                    CPLAddXMLChild(psNewMetadata,
×
2625
                                   CPLCloneXMLTree(psMetadataRoot));
2626
                    psMetadataRoot = psNewMetadata;
×
2627
                    psMetadata.reset(psNewMetadata);
×
2628
                }
2629
                if (strcmp(psMetadataRoot->pszValue, "gmljp2:isoMetadata") !=
9✔
2630
                        0 &&
9✔
2631
                    strcmp(psMetadataRoot->pszValue, "gmljp2:eopMetadata") !=
9✔
2632
                        0 &&
8✔
2633
                    strcmp(psMetadataRoot->pszValue, "gmljp2:dcMetadata") !=
8✔
2634
                        0 &&
6✔
2635
                    strcmp(psMetadataRoot->pszValue, "gmljp2:metadata") != 0)
6✔
2636
                {
2637
                    CPLError(CE_Warning, CPLE_AppDefined,
1✔
2638
                             "The metadata root node should be one of "
2639
                             "gmljp2:isoMetadata, "
2640
                             "gmljp2:eopMetadata, gmljp2:dcMetadata or "
2641
                             "gmljp2:metadata");
2642
                }
2643
                else if (oMetadata.bParentCoverageCollection)
8✔
2644
                {
2645
                    /* Insert the gmlcov:metadata link as the next sibling of */
2646
                    /* GMLJP2CoverageCollection.rangeType */
2647
                    CPLXMLNode *psRangeType = CPLGetXMLNode(
7✔
2648
                        psGMLJP2CoverageCollection, "gmlcov:rangeType");
2649
                    CPLAssert(psRangeType);
7✔
2650
                    CPLXMLNode *psNodeAfterWhichToInsert = psRangeType;
7✔
2651
                    CPLXMLNode *psNext = psNodeAfterWhichToInsert->psNext;
7✔
2652
                    while (psNext != nullptr && psNext->eType == CXT_Element &&
10✔
2653
                           strcmp(psNext->pszValue, "gmlcov:metadata") == 0)
10✔
2654
                    {
2655
                        psNodeAfterWhichToInsert = psNext;
3✔
2656
                        psNext = psNext->psNext;
3✔
2657
                    }
2658
                    psNodeAfterWhichToInsert->psNext = nullptr;
7✔
2659
                    CPLXMLNode *psGMLCovMetadata =
2660
                        CPLCreateXMLNode(psGMLJP2CoverageCollection,
7✔
2661
                                         CXT_Element, "gmlcov:metadata");
2662
                    psGMLCovMetadata->psNext = psNext;
7✔
2663
                    CPLXMLNode *psGMLJP2Metadata = CPLCreateXMLNode(
7✔
2664
                        psGMLCovMetadata, CXT_Element, "gmljp2:Metadata");
2665
                    CPLAddXMLChild(psGMLJP2Metadata,
7✔
2666
                                   CPLCloneXMLTree(psMetadataRoot));
2667
                }
2668
                else
2669
                {
2670
                    /* Insert the gmlcov:metadata link as the last child of */
2671
                    /* GMLJP2RectifiedGridCoverage typically */
2672
                    CPLXMLNode *psFeatureMemberOfGridCoverage = CPLGetXMLNode(
1✔
2673
                        psGMLJP2CoverageCollection, "gmljp2:featureMember");
2674
                    CPLAssert(psFeatureMemberOfGridCoverage);
1✔
2675
                    CPLXMLNode *psGridCoverage =
1✔
2676
                        psFeatureMemberOfGridCoverage->psChild;
2677
                    CPLAssert(psGridCoverage);
1✔
2678
                    CPLXMLNode *psGMLCovMetadata = CPLCreateXMLNode(
1✔
2679
                        psGridCoverage, CXT_Element, "gmlcov:metadata");
2680
                    CPLXMLNode *psGMLJP2Metadata = CPLCreateXMLNode(
1✔
2681
                        psGMLCovMetadata, CXT_Element, "gmljp2:Metadata");
2682
                    CPLAddXMLChild(psGMLJP2Metadata,
1✔
2683
                                   CPLCloneXMLTree(psMetadataRoot));
2684
                }
2685
            }
2686
        }
2687

2688
        bool bRootHasXLink = false;
16✔
2689

2690
        // Examples of inline or reference feature collections can be found
2691
        // in http://schemas.opengis.net/gmljp2/2.0/examples/gmljp2.xml
2692
        for (int i = 0; i < static_cast<int>(aoGMLFiles.size()); ++i)
26✔
2693
        {
2694
            // Is the file already a GML file?
2695
            CPLXMLTreeCloser psGMLFile(nullptr);
10✔
2696
            if (!aoGMLFiles[i].osFile.empty())
10✔
2697
            {
2698
                const CPLString osExt =
2699
                    CPLGetExtensionSafe(aoGMLFiles[i].osFile);
9✔
2700
                if (EQUAL(osExt, "gml") || EQUAL(osExt, "xml"))
9✔
2701
                {
2702
                    psGMLFile =
2703
                        CPLXMLTreeCloser(CPLParseXMLFile(aoGMLFiles[i].osFile));
4✔
2704
                }
2705
                GDALDriverH hDrv = nullptr;
9✔
2706
                if (psGMLFile == nullptr)
9✔
2707
                {
2708
                    hDrv = GDALIdentifyDriver(aoGMLFiles[i].osFile, nullptr);
6✔
2709
                    if (hDrv == nullptr)
6✔
2710
                    {
2711
                        CPLError(CE_Failure, CPLE_AppDefined,
2✔
2712
                                 "%s is no a GDAL recognized file",
2713
                                 aoGMLFiles[i].osFile.c_str());
2✔
2714
                        continue;
2✔
2715
                    }
2716
                }
2717
                GDALDriverH hGMLDrv = GDALGetDriverByName("GML");
7✔
2718
                if (psGMLFile == nullptr && hDrv == hGMLDrv)
7✔
2719
                {
2720
                    // Yes, parse it
2721
                    psGMLFile =
2722
                        CPLXMLTreeCloser(CPLParseXMLFile(aoGMLFiles[i].osFile));
×
2723
                }
2724
                else if (psGMLFile == nullptr)
7✔
2725
                {
2726
                    if (hGMLDrv == nullptr)
4✔
2727
                    {
2728
                        CPLError(CE_Failure, CPLE_AppDefined,
×
2729
                                 "Cannot translate %s to GML",
2730
                                 aoGMLFiles[i].osFile.c_str());
×
2731
                        continue;
×
2732
                    }
2733

2734
                    // On-the-fly translation to GML 3.2
2735
                    GDALDatasetH hSrcDS = GDALOpenEx(aoGMLFiles[i].osFile, 0,
4✔
2736
                                                     nullptr, nullptr, nullptr);
2737
                    if (hSrcDS)
4✔
2738
                    {
2739
                        const CPLString osTmpFile =
2740
                            osTmpDir + "/" + std::to_string(i) + "/" +
8✔
2741
                            CPLGetBasenameSafe(aoGMLFiles[i].osFile) + ".gml";
16✔
2742
                        char **papszOptions = nullptr;
4✔
2743
                        papszOptions =
2744
                            CSLSetNameValue(papszOptions, "FORMAT", "GML3.2");
4✔
2745
                        papszOptions =
2746
                            CSLSetNameValue(papszOptions, "SRSNAME_FORMAT",
4✔
2747
                                            (bCRSURL) ? "OGC_URL" : "OGC_URN");
2748
                        if (aoGMLFiles.size() > 1 ||
4✔
2749
                            !aoGMLFiles[i].osNamespace.empty() ||
5✔
2750
                            !aoGMLFiles[i].osNamespacePrefix.empty())
1✔
2751
                        {
2752
                            papszOptions = CSLSetNameValue(
6✔
2753
                                papszOptions, "PREFIX",
2754
                                aoGMLFiles[i].osNamespacePrefix.empty()
3✔
2755
                                    ? CPLSPrintf("ogr%d", i)
3✔
2756
                                    : aoGMLFiles[i].osNamespacePrefix.c_str());
×
2757
                            papszOptions = CSLSetNameValue(
6✔
2758
                                papszOptions, "TARGET_NAMESPACE",
2759
                                aoGMLFiles[i].osNamespace.empty()
3✔
2760
                                    ? CPLSPrintf("http://ogr.maptools.org/%d",
2✔
2761
                                                 i)
2762
                                    : aoGMLFiles[i].osNamespace.c_str());
1✔
2763
                        }
2764
                        GDALDatasetH hDS =
2765
                            GDALCreateCopy(hGMLDrv, osTmpFile, hSrcDS, FALSE,
4✔
2766
                                           papszOptions, nullptr, nullptr);
2767
                        CSLDestroy(papszOptions);
4✔
2768
                        if (hDS)
4✔
2769
                        {
2770
                            GDALClose(hDS);
3✔
2771
                            psGMLFile =
2772
                                CPLXMLTreeCloser(CPLParseXMLFile(osTmpFile));
3✔
2773
                            aoGMLFiles[i].osFile = osTmpFile;
3✔
2774
                            VSIUnlink(osTmpFile);
3✔
2775
                        }
2776
                        else
2777
                        {
2778
                            CPLError(CE_Failure, CPLE_AppDefined,
1✔
2779
                                     "Conversion of %s to GML failed",
2780
                                     aoGMLFiles[i].osFile.c_str());
1✔
2781
                        }
2782
                    }
2783
                    GDALClose(hSrcDS);
4✔
2784
                }
2785
                if (psGMLFile == nullptr)
7✔
2786
                    continue;
1✔
2787
            }
2788

2789
            CPLXMLNode *psGMLFileRoot =
2790
                psGMLFile ? GDALGMLJP2GetXMLRoot(psGMLFile.get()) : nullptr;
7✔
2791
            if (psGMLFileRoot || !aoGMLFiles[i].osRemoteResource.empty())
7✔
2792
            {
2793
                CPLXMLNode *node_f;
2794
                if (aoGMLFiles[i].bParentCoverageCollection)
7✔
2795
                {
2796
                    // Insert in
2797
                    // gmljp2:featureMember.gmljp2:GMLJP2Features.gmljp2:feature
2798
                    CPLXMLNode *node_fm =
2799
                        CPLCreateXMLNode(psGMLJP2CoverageCollection,
5✔
2800
                                         CXT_Element, "gmljp2:featureMember");
2801

2802
                    CPLXMLNode *node_gf = CPLCreateXMLNode(
5✔
2803
                        node_fm, CXT_Element, "gmljp2:GMLJP2Features");
2804

2805
                    CPLSetXMLValue(node_gf, "#gml:id",
5✔
2806
                                   CPLSPrintf("%s_GMLJP2Features_%d",
2807
                                              osRootGMLId.c_str(), i));
2808

2809
                    node_f = CPLCreateXMLNode(node_gf, CXT_Element,
5✔
2810
                                              "gmljp2:feature");
2811
                }
2812
                else
2813
                {
2814
                    CPLXMLNode *psFeatureMemberOfGridCoverage = CPLGetXMLNode(
2✔
2815
                        psGMLJP2CoverageCollection, "gmljp2:featureMember");
2816
                    CPLAssert(psFeatureMemberOfGridCoverage);
2✔
2817
                    CPLXMLNode *psGridCoverage =
2✔
2818
                        psFeatureMemberOfGridCoverage->psChild;
2819
                    CPLAssert(psGridCoverage);
2✔
2820
                    node_f = CPLCreateXMLNode(psGridCoverage, CXT_Element,
2✔
2821
                                              "gmljp2:feature");
2822
                }
2823

2824
                if (!aoGMLFiles[i].bInline ||
10✔
2825
                    !aoGMLFiles[i].osRemoteResource.empty())
3✔
2826
                {
2827
                    if (!bRootHasXLink)
5✔
2828
                    {
2829
                        bRootHasXLink = true;
2✔
2830
                        CPLSetXMLValue(psGMLJP2CoverageCollection,
2✔
2831
                                       "#xmlns:xlink",
2832
                                       "http://www.w3.org/1999/xlink");
2833
                    }
2834
                }
2835

2836
                if (!aoGMLFiles[i].osRemoteResource.empty())
7✔
2837
                {
2838
                    CPLSetXMLValue(node_f, "#xlink:href",
1✔
2839
                                   aoGMLFiles[i].osRemoteResource.c_str());
1✔
2840
                    continue;
1✔
2841
                }
2842

2843
                CPLString osTmpFile;
12✔
2844
                if (!aoGMLFiles[i].bInline ||
8✔
2845
                    !aoGMLFiles[i].osRemoteResource.empty())
2✔
2846
                {
2847
                    osTmpFile = osTmpDir + "/" + std::to_string(i) + "/" +
8✔
2848
                                CPLGetBasenameSafe(aoGMLFiles[i].osFile) +
16✔
2849
                                ".gml";
4✔
2850
                    GMLJP2V2BoxDesc oDesc;
8✔
2851
                    oDesc.osFile = osTmpFile;
4✔
2852
                    oDesc.osLabel = CPLGetFilename(oDesc.osFile);
4✔
2853
                    CPLSetXMLValue(
4✔
2854
                        node_f, "#xlink:href",
2855
                        CPLSPrintf("gmljp2://xml/%s", oDesc.osLabel.c_str()));
2856
                    aoBoxes.push_back(std::move(oDesc));
4✔
2857
                }
2858

2859
                if (CPLGetXMLNode(psGMLFileRoot, "xmlns") == nullptr &&
11✔
2860
                    CPLGetXMLNode(psGMLFileRoot, "xmlns:gml") == nullptr)
5✔
2861
                {
2862
                    CPLSetXMLValue(psGMLFileRoot, "#xmlns",
×
2863
                                   "http://www.opengis.net/gml/3.2");
2864
                }
2865

2866
                // modify the gml id making it unique for this document
2867
                CPLXMLNode *psGMLFileGMLId =
2868
                    CPLGetXMLNode(psGMLFileRoot, "gml:id");
6✔
2869
                if (psGMLFileGMLId && psGMLFileGMLId->eType == CXT_Attribute)
6✔
2870
                    CPLSetXMLValue(
6✔
2871
                        psGMLFileGMLId, "",
2872
                        CPLSPrintf("%s_%d_%s", osRootGMLId.c_str(), i,
2873
                                   psGMLFileGMLId->psChild->pszValue));
6✔
2874
                psGMLFileGMLId = nullptr;
6✔
2875
                // PrefixAllGMLIds(psGMLFileRoot, CPLSPrintf("%s_%d_",
2876
                // osRootGMLId.c_str(), i));
2877

2878
                // replace schema location
2879
                CPLXMLNode *psSchemaLocation =
2880
                    CPLGetXMLNode(psGMLFileRoot, "xsi:schemaLocation");
6✔
2881
                if (psSchemaLocation &&
6✔
2882
                    psSchemaLocation->eType == CXT_Attribute)
5✔
2883
                {
2884
                    char **papszTokens = CSLTokenizeString2(
10✔
2885
                        psSchemaLocation->psChild->pszValue, " \t\n",
5✔
2886
                        CSLT_HONOURSTRINGS | CSLT_STRIPLEADSPACES |
2887
                            CSLT_STRIPENDSPACES);
2888
                    CPLString osSchemaLocation;
10✔
2889

2890
                    if (CSLCount(papszTokens) == 2 &&
9✔
2891
                        aoGMLFiles[i].osNamespace.empty() &&
9✔
2892
                        !aoGMLFiles[i].osSchemaLocation.empty())
3✔
2893
                    {
2894
                        osSchemaLocation += papszTokens[0];
1✔
2895
                        osSchemaLocation += " ";
1✔
2896
                        osSchemaLocation += aoGMLFiles[i].osSchemaLocation;
1✔
2897
                    }
2898

2899
                    else if (CSLCount(papszTokens) == 2 &&
7✔
2900
                             (aoGMLFiles[i].osNamespace.empty() ||
3✔
2901
                              strcmp(papszTokens[0],
1✔
2902
                                     aoGMLFiles[i].osNamespace) == 0) &&
8✔
2903
                             aoGMLFiles[i].osSchemaLocation.empty())
3✔
2904
                    {
2905
                        VSIStatBufL sStat;
2906
                        CPLString osXSD;
4✔
2907
                        if (CSLCount(papszTokens) == 2 &&
2✔
2908
                            !CPLIsFilenameRelative(papszTokens[1]) &&
2✔
2909
                            VSIStatL(papszTokens[1], &sStat) == 0)
×
2910
                        {
2911
                            osXSD = papszTokens[1];
×
2912
                        }
2913
                        else if (CSLCount(papszTokens) == 2 &&
8✔
2914
                                 CPLIsFilenameRelative(papszTokens[1]) &&
4✔
2915
                                 VSIStatL(
2✔
2916
                                     CPLFormFilenameSafe(
4✔
2917
                                         CPLGetDirnameSafe(aoGMLFiles[i].osFile)
4✔
2918
                                             .c_str(),
2919
                                         papszTokens[1], nullptr)
2✔
2920
                                         .c_str(),
2921
                                     &sStat) == 0)
2922
                        {
2923
                            osXSD = CPLFormFilenameSafe(
2✔
2924
                                CPLGetDirnameSafe(aoGMLFiles[i].osFile).c_str(),
4✔
2925
                                papszTokens[1], nullptr);
4✔
2926
                        }
2927
                        if (!osXSD.empty())
2✔
2928
                        {
2929
                            GMLJP2V2BoxDesc oDesc;
4✔
2930
                            oDesc.osFile = std::move(osXSD);
2✔
2931
                            oDesc.osLabel = CPLGetFilename(oDesc.osFile);
2✔
2932
                            osSchemaLocation += papszTokens[0];
2✔
2933
                            osSchemaLocation += " ";
2✔
2934
                            osSchemaLocation += "gmljp2://xml/";
2✔
2935
                            osSchemaLocation += oDesc.osLabel;
2✔
2936
                            int j = 0;  // Used after for.
2✔
2937
                            for (; j < static_cast<int>(aoBoxes.size()); ++j)
5✔
2938
                            {
2939
                                if (aoBoxes[j].osLabel == oDesc.osLabel)
3✔
2940
                                    break;
×
2941
                            }
2942
                            if (j == static_cast<int>(aoBoxes.size()))
2✔
2943
                                aoBoxes.push_back(std::move(oDesc));
2✔
2944
                        }
2945
                    }
2946

2947
                    else if ((CSLCount(papszTokens) % 2) == 0)
2✔
2948
                    {
2949
                        for (char **papszIter = papszTokens; *papszIter;
5✔
2950
                             papszIter += 2)
3✔
2951
                        {
2952
                            if (!osSchemaLocation.empty())
3✔
2953
                                osSchemaLocation += " ";
1✔
2954
                            if (!aoGMLFiles[i].osNamespace.empty() &&
3✔
2955
                                !aoGMLFiles[i].osSchemaLocation.empty() &&
6✔
2956
                                strcmp(papszIter[0],
3✔
2957
                                       aoGMLFiles[i].osNamespace) == 0)
3✔
2958
                            {
2959
                                osSchemaLocation += papszIter[0];
2✔
2960
                                osSchemaLocation += " ";
2✔
2961
                                osSchemaLocation +=
2962
                                    aoGMLFiles[i].osSchemaLocation;
2✔
2963
                            }
2964
                            else
2965
                            {
2966
                                osSchemaLocation += papszIter[0];
1✔
2967
                                osSchemaLocation += " ";
1✔
2968
                                osSchemaLocation += papszIter[1];
1✔
2969
                            }
2970
                        }
2971
                    }
2972
                    CSLDestroy(papszTokens);
5✔
2973
                    CPLSetXMLValue(psSchemaLocation, "", osSchemaLocation);
5✔
2974
                }
2975

2976
                if (aoGMLFiles[i].bInline)
6✔
2977
                    CPLAddXMLChild(node_f, CPLCloneXMLTree(psGMLFileRoot));
2✔
2978
                else
2979
                    CPLSerializeXMLTreeToFile(psGMLFile.get(), osTmpFile);
4✔
2980
            }
2981
        }
2982

2983
        // c.f.
2984
        // http://schemas.opengis.net/gmljp2/2.0/examples/gmljp2_annotation.xml
2985
        for (int i = 0; i < static_cast<int>(aoAnnotations.size()); ++i)
21✔
2986
        {
2987
            // Is the file already a KML file?
2988
            CPLXMLTreeCloser psKMLFile(nullptr);
5✔
2989
            if (EQUAL(CPLGetExtensionSafe(aoAnnotations[i].osFile).c_str(),
5✔
2990
                      "kml"))
2991
                psKMLFile =
2992
                    CPLXMLTreeCloser(CPLParseXMLFile(aoAnnotations[i].osFile));
2✔
2993
            GDALDriverH hDrv = nullptr;
5✔
2994
            if (psKMLFile == nullptr)
5✔
2995
            {
2996
                hDrv = GDALIdentifyDriver(aoAnnotations[i].osFile, nullptr);
4✔
2997
                if (hDrv == nullptr)
4✔
2998
                {
2999
                    CPLError(CE_Failure, CPLE_AppDefined,
2✔
3000
                             "%s is no a GDAL recognized file",
3001
                             aoAnnotations[i].osFile.c_str());
2✔
3002
                    continue;
2✔
3003
                }
3004
            }
3005
            GDALDriverH hKMLDrv = GDALGetDriverByName("KML");
3✔
3006
            GDALDriverH hLIBKMLDrv = GDALGetDriverByName("LIBKML");
3✔
3007
            if (psKMLFile == nullptr && (hDrv == hKMLDrv || hDrv == hLIBKMLDrv))
3✔
3008
            {
3009
                // Yes, parse it
3010
                psKMLFile =
3011
                    CPLXMLTreeCloser(CPLParseXMLFile(aoAnnotations[i].osFile));
×
3012
            }
3013
            else if (psKMLFile == nullptr)
3✔
3014
            {
3015
                if (hKMLDrv == nullptr && hLIBKMLDrv == nullptr)
2✔
3016
                {
3017
                    CPLError(CE_Failure, CPLE_AppDefined,
×
3018
                             "Cannot translate %s to KML",
3019
                             aoAnnotations[i].osFile.c_str());
×
3020
                    continue;
×
3021
                }
3022

3023
                // On-the-fly translation to KML
3024
                GDALDatasetH hSrcDS = GDALOpenEx(aoAnnotations[i].osFile, 0,
2✔
3025
                                                 nullptr, nullptr, nullptr);
3026
                if (hSrcDS)
2✔
3027
                {
3028
                    const CPLString osTmpFile =
3029
                        osTmpDir + "/" + std::to_string(i) + "/" +
4✔
3030
                        CPLGetBasenameSafe(aoAnnotations[i].osFile) + ".kml";
8✔
3031
                    char **papszOptions = nullptr;
2✔
3032
                    if (aoAnnotations.size() > 1)
2✔
3033
                    {
3034
                        papszOptions =
3035
                            CSLSetNameValue(papszOptions, "DOCUMENT_ID",
2✔
3036
                                            CPLSPrintf("root_doc_%d", i));
3037
                    }
3038
                    GDALDatasetH hDS = GDALCreateCopy(
2✔
3039
                        hLIBKMLDrv ? hLIBKMLDrv : hKMLDrv, osTmpFile, hSrcDS,
3040
                        FALSE, papszOptions, nullptr, nullptr);
3041
                    CSLDestroy(papszOptions);
2✔
3042
                    if (hDS)
2✔
3043
                    {
3044
                        GDALClose(hDS);
1✔
3045
                        psKMLFile =
3046
                            CPLXMLTreeCloser(CPLParseXMLFile(osTmpFile));
1✔
3047
                        aoAnnotations[i].osFile = osTmpFile;
1✔
3048
                        VSIUnlink(osTmpFile);
1✔
3049
                    }
3050
                    else
3051
                    {
3052
                        CPLError(CE_Failure, CPLE_AppDefined,
1✔
3053
                                 "Conversion of %s to KML failed",
3054
                                 aoAnnotations[i].osFile.c_str());
1✔
3055
                    }
3056
                }
3057
                GDALClose(hSrcDS);
2✔
3058
            }
3059
            if (psKMLFile == nullptr)
3✔
3060
                continue;
1✔
3061

3062
            CPLXMLNode *psKMLFileRoot = GDALGMLJP2GetXMLRoot(psKMLFile.get());
2✔
3063
            if (psKMLFileRoot)
2✔
3064
            {
3065
                CPLXMLNode *psFeatureMemberOfGridCoverage = CPLGetXMLNode(
2✔
3066
                    psGMLJP2CoverageCollection, "gmljp2:featureMember");
3067
                CPLAssert(psFeatureMemberOfGridCoverage);
2✔
3068
                CPLXMLNode *psGridCoverage =
2✔
3069
                    psFeatureMemberOfGridCoverage->psChild;
3070
                CPLAssert(psGridCoverage);
2✔
3071
                CPLXMLNode *psAnnotation = CPLCreateXMLNode(
2✔
3072
                    psGridCoverage, CXT_Element, "gmljp2:annotation");
3073

3074
                /* Add a xsi:schemaLocation if not already present */
3075
                if (psKMLFileRoot->eType == CXT_Element &&
6✔
3076
                    strcmp(psKMLFileRoot->pszValue, "kml") == 0 &&
2✔
3077
                    CPLGetXMLNode(psKMLFileRoot, "xsi:schemaLocation") ==
2✔
3078
                        nullptr &&
4✔
3079
                    strcmp(CPLGetXMLValue(psKMLFileRoot, "xmlns", ""),
1✔
3080
                           "http://www.opengis.net/kml/2.2") == 0)
3081
                {
3082
                    CPLSetXMLValue(
1✔
3083
                        psKMLFileRoot, "#xsi:schemaLocation",
3084
                        "http://www.opengis.net/kml/2.2 "
3085
                        "http://schemas.opengis.net/kml/2.2.0/ogckml22.xsd");
3086
                }
3087

3088
                CPLAddXMLChild(psAnnotation, CPLCloneXMLTree(psKMLFileRoot));
2✔
3089
            }
3090
        }
3091

3092
        // Add styles.
3093
        for (const auto &oStyle : aoStyles)
23✔
3094
        {
3095
            CPLXMLTreeCloser psStyle(CPLParseXMLFile(oStyle.osFile));
7✔
3096
            if (psStyle == nullptr)
7✔
3097
                continue;
2✔
3098

3099
            CPLXMLNode *psStyleRoot = GDALGMLJP2GetXMLRoot(psStyle.get());
5✔
3100
            if (psStyleRoot)
5✔
3101
            {
3102
                CPLXMLNode *psGMLJP2Style = nullptr;
4✔
3103
                if (oStyle.bParentCoverageCollection)
4✔
3104
                {
3105
                    psGMLJP2Style =
3106
                        CPLCreateXMLNode(psGMLJP2CoverageCollection,
3✔
3107
                                         CXT_Element, "gmljp2:style");
3108
                }
3109
                else
3110
                {
3111
                    CPLXMLNode *psFeatureMemberOfGridCoverage = CPLGetXMLNode(
1✔
3112
                        psGMLJP2CoverageCollection, "gmljp2:featureMember");
3113
                    CPLAssert(psFeatureMemberOfGridCoverage);
1✔
3114
                    CPLXMLNode *psGridCoverage =
1✔
3115
                        psFeatureMemberOfGridCoverage->psChild;
3116
                    CPLAssert(psGridCoverage);
1✔
3117
                    psGMLJP2Style = CPLCreateXMLNode(
1✔
3118
                        psGridCoverage, CXT_Element, "gmljp2:style");
3119
                }
3120

3121
                // Add dummy namespace for validation purposes if needed
3122
                if (strchr(psStyleRoot->pszValue, ':') == nullptr &&
7✔
3123
                    CPLGetXMLValue(psStyleRoot, "xmlns", nullptr) == nullptr)
3✔
3124
                {
3125
                    CPLSetXMLValue(psStyleRoot, "#xmlns",
2✔
3126
                                   "http://undefined_namespace");
3127
                }
3128

3129
                CPLAddXMLChild(psGMLJP2Style, CPLCloneXMLTree(psStyleRoot));
4✔
3130
            }
3131
        }
3132

3133
        // Add extensions.
3134
        for (const auto &oExt : aoExtensions)
23✔
3135
        {
3136
            CPLXMLTreeCloser psExtension(CPLParseXMLFile(oExt.osFile));
7✔
3137
            if (psExtension == nullptr)
7✔
3138
                continue;
2✔
3139

3140
            CPLXMLNode *psExtensionRoot =
3141
                GDALGMLJP2GetXMLRoot(psExtension.get());
5✔
3142
            if (psExtensionRoot)
5✔
3143
            {
3144
                CPLXMLNode *psGMLJP2Extension;
3145
                if (oExt.bParentCoverageCollection)
4✔
3146
                {
3147
                    psGMLJP2Extension =
3148
                        CPLCreateXMLNode(psGMLJP2CoverageCollection,
3✔
3149
                                         CXT_Element, "gmljp2:extension");
3150
                }
3151
                else
3152
                {
3153
                    CPLXMLNode *psFeatureMemberOfGridCoverage = CPLGetXMLNode(
1✔
3154
                        psGMLJP2CoverageCollection, "gmljp2:featureMember");
3155
                    CPLAssert(psFeatureMemberOfGridCoverage);
1✔
3156
                    CPLXMLNode *psGridCoverage =
1✔
3157
                        psFeatureMemberOfGridCoverage->psChild;
3158
                    CPLAssert(psGridCoverage);
1✔
3159
                    psGMLJP2Extension = CPLCreateXMLNode(
1✔
3160
                        psGridCoverage, CXT_Element, "gmljp2:extension");
3161
                }
3162

3163
                // Add dummy namespace for validation purposes if needed
3164
                if (strchr(psExtensionRoot->pszValue, ':') == nullptr &&
7✔
3165
                    CPLGetXMLValue(psExtensionRoot, "xmlns", nullptr) ==
3✔
3166
                        nullptr)
3167
                {
3168
                    CPLSetXMLValue(psExtensionRoot, "#xmlns",
2✔
3169
                                   "http://undefined_namespace");
3170
                }
3171

3172
                CPLAddXMLChild(psGMLJP2Extension,
4✔
3173
                               CPLCloneXMLTree(psExtensionRoot));
3174
            }
3175
        }
3176

3177
        char *pszRoot = CPLSerializeXMLTree(psRoot.get());
16✔
3178
        psRoot.reset();
16✔
3179
        osDoc = pszRoot;
16✔
3180
        CPLFree(pszRoot);
16✔
3181
        pszRoot = nullptr;
16✔
3182
    }
3183

3184
    /* -------------------------------------------------------------------- */
3185
    /*      Setup the gml.data label.                                       */
3186
    /* -------------------------------------------------------------------- */
3187
    std::vector<GDALJP2Box *> apoGMLBoxes;
25✔
3188

3189
    apoGMLBoxes.push_back(GDALJP2Box::CreateLblBox("gml.data"));
25✔
3190

3191
    /* -------------------------------------------------------------------- */
3192
    /*      Setup gml.root-instance.                                        */
3193
    /* -------------------------------------------------------------------- */
3194
    apoGMLBoxes.push_back(
25✔
3195
        GDALJP2Box::CreateLabelledXMLAssoc("gml.root-instance", osDoc));
25✔
3196

3197
    /* -------------------------------------------------------------------- */
3198
    /*      Add optional dictionary.                                        */
3199
    /* -------------------------------------------------------------------- */
3200
    if (!osDictBox.empty())
25✔
3201
        apoGMLBoxes.push_back(
×
3202
            GDALJP2Box::CreateLabelledXMLAssoc("CRSDictionary.gml", osDictBox));
×
3203

3204
    /* -------------------------------------------------------------------- */
3205
    /*      Additional user specified boxes.                                */
3206
    /* -------------------------------------------------------------------- */
3207
    for (const auto &oBox : aoBoxes)
35✔
3208
    {
3209
        GByte *pabyContent = nullptr;
10✔
3210
        if (VSIIngestFile(nullptr, oBox.osFile, &pabyContent, nullptr, -1))
10✔
3211
        {
3212
            CPLXMLTreeCloser psNode(
3213
                CPLParseXMLString(reinterpret_cast<const char *>(pabyContent)));
16✔
3214
            CPLFree(pabyContent);
8✔
3215
            pabyContent = nullptr;
8✔
3216
            if (psNode.get())
8✔
3217
            {
3218
                CPLXMLNode *psRoot = GDALGMLJP2GetXMLRoot(psNode.get());
8✔
3219
                if (psRoot)
8✔
3220
                {
3221
                    GDALGMLJP2PatchFeatureCollectionSubstitutionGroup(psRoot);
8✔
3222
                    pabyContent =
8✔
3223
                        reinterpret_cast<GByte *>(CPLSerializeXMLTree(psRoot));
8✔
3224
                    apoGMLBoxes.push_back(GDALJP2Box::CreateLabelledXMLAssoc(
8✔
3225
                        oBox.osLabel,
3226
                        reinterpret_cast<const char *>(pabyContent)));
3227
                }
3228
            }
3229
        }
3230
        CPLFree(pabyContent);
10✔
3231
    }
3232

3233
    /* -------------------------------------------------------------------- */
3234
    /*      Bundle gml.data boxes into an association.                      */
3235
    /* -------------------------------------------------------------------- */
3236
    GDALJP2Box *poGMLData = GDALJP2Box::CreateAsocBox(
25✔
3237
        static_cast<int>(apoGMLBoxes.size()), &apoGMLBoxes[0]);
25✔
3238

3239
    /* -------------------------------------------------------------------- */
3240
    /*      Cleanup working boxes.                                          */
3241
    /* -------------------------------------------------------------------- */
3242
    for (auto &poGMLBox : apoGMLBoxes)
83✔
3243
        delete poGMLBox;
58✔
3244

3245
    VSIRmdirRecursive(osTmpDir.c_str());
25✔
3246

3247
    return poGMLData;
25✔
3248
}
3249

3250
/************************************************************************/
3251
/*                 CreateGDALMultiDomainMetadataXML()                   */
3252
/************************************************************************/
3253

3254
CPLXMLNode *
3255
GDALJP2Metadata::CreateGDALMultiDomainMetadataXML(GDALDataset *poSrcDS,
26✔
3256
                                                  int bMainMDDomainOnly)
3257
{
3258
    GDALMultiDomainMetadata oLocalMDMD;
26✔
3259
    char **papszSrcMD = CSLDuplicate(poSrcDS->GetMetadata());
26✔
3260
    /* Remove useless metadata */
3261
    papszSrcMD = CSLSetNameValue(papszSrcMD, GDALMD_AREA_OR_POINT, nullptr);
26✔
3262
    papszSrcMD = CSLSetNameValue(papszSrcMD, "TIFFTAG_RESOLUTIONUNIT", nullptr);
26✔
3263
    papszSrcMD = CSLSetNameValue(papszSrcMD,
26✔
3264
                                 "TIFFTAG_XREpsMasterXMLNodeSOLUTION", nullptr);
3265
    papszSrcMD = CSLSetNameValue(papszSrcMD, "TIFFTAG_YRESOLUTION", nullptr);
26✔
3266
    papszSrcMD =
3267
        CSLSetNameValue(papszSrcMD, "Corder", nullptr); /* from JP2KAK */
26✔
3268
    if (poSrcDS->GetDriver() != nullptr &&
52✔
3269
        EQUAL(poSrcDS->GetDriver()->GetDescription(), "JP2ECW"))
26✔
3270
    {
3271
        papszSrcMD =
3272
            CSLSetNameValue(papszSrcMD, "COMPRESSION_RATE_TARGET", nullptr);
×
3273
        papszSrcMD = CSLSetNameValue(papszSrcMD, "COLORSPACE", nullptr);
×
3274
        papszSrcMD = CSLSetNameValue(papszSrcMD, "VERSION", nullptr);
×
3275
    }
3276

3277
    bool bHasMD = false;
26✔
3278
    if (papszSrcMD && *papszSrcMD)
26✔
3279
    {
3280
        bHasMD = true;
6✔
3281
        oLocalMDMD.SetMetadata(papszSrcMD);
6✔
3282
    }
3283
    CSLDestroy(papszSrcMD);
26✔
3284

3285
    if (!bMainMDDomainOnly)
26✔
3286
    {
3287
        char **papszMDList = poSrcDS->GetMetadataDomainList();
25✔
3288
        for (char **papszMDListIter = papszMDList;
87✔
3289
             papszMDListIter && *papszMDListIter; ++papszMDListIter)
87✔
3290
        {
3291
            if (!EQUAL(*papszMDListIter, "") &&
62✔
3292
                !EQUAL(*papszMDListIter, "IMAGE_STRUCTURE") &&
56✔
3293
                !EQUAL(*papszMDListIter, "DERIVED_SUBDATASETS") &&
40✔
3294
                !EQUAL(*papszMDListIter, "JPEG2000") &&
15✔
3295
                !STARTS_WITH_CI(*papszMDListIter, "xml:BOX_") &&
15✔
3296
                !EQUAL(*papszMDListIter, "xml:gml.root-instance") &&
12✔
3297
                !EQUAL(*papszMDListIter, "xml:XMP") &&
9✔
3298
                !EQUAL(*papszMDListIter, "xml:IPR"))
6✔
3299
            {
3300
                papszSrcMD = poSrcDS->GetMetadata(*papszMDListIter);
4✔
3301
                if (papszSrcMD && *papszSrcMD)
4✔
3302
                {
3303
                    bHasMD = true;
4✔
3304
                    oLocalMDMD.SetMetadata(papszSrcMD, *papszMDListIter);
4✔
3305
                }
3306
            }
3307
        }
3308
        CSLDestroy(papszMDList);
25✔
3309
    }
3310

3311
    CPLXMLNode *psMasterXMLNode = nullptr;
26✔
3312
    if (bHasMD)
26✔
3313
    {
3314
        CPLXMLNode *psXMLNode = oLocalMDMD.Serialize();
10✔
3315
        psMasterXMLNode =
3316
            CPLCreateXMLNode(nullptr, CXT_Element, "GDALMultiDomainMetadata");
10✔
3317
        psMasterXMLNode->psChild = psXMLNode;
10✔
3318
    }
3319
    return psMasterXMLNode;
52✔
3320
}
3321

3322
/************************************************************************/
3323
/*                CreateGDALMultiDomainMetadataXMLBox()                 */
3324
/************************************************************************/
3325

3326
GDALJP2Box *
3327
GDALJP2Metadata::CreateGDALMultiDomainMetadataXMLBox(GDALDataset *poSrcDS,
25✔
3328
                                                     int bMainMDDomainOnly)
3329
{
3330
    CPLXMLTreeCloser psMasterXMLNode(
3331
        CreateGDALMultiDomainMetadataXML(poSrcDS, bMainMDDomainOnly));
50✔
3332
    if (psMasterXMLNode == nullptr)
25✔
3333
        return nullptr;
16✔
3334
    char *pszXML = CPLSerializeXMLTree(psMasterXMLNode.get());
9✔
3335
    psMasterXMLNode.reset();
9✔
3336

3337
    GDALJP2Box *poBox = new GDALJP2Box();
9✔
3338
    poBox->SetType("xml ");
9✔
3339
    poBox->SetWritableData(static_cast<int>(strlen(pszXML) + 1),
9✔
3340
                           reinterpret_cast<const GByte *>(pszXML));
3341
    CPLFree(pszXML);
9✔
3342

3343
    return poBox;
9✔
3344
}
3345

3346
/************************************************************************/
3347
/*                         WriteXMLBoxes()                              */
3348
/************************************************************************/
3349

3350
GDALJP2Box **GDALJP2Metadata::CreateXMLBoxes(GDALDataset *poSrcDS, int *pnBoxes)
25✔
3351
{
3352
    GDALJP2Box **papoBoxes = nullptr;
25✔
3353
    *pnBoxes = 0;
25✔
3354
    char **papszMDList = poSrcDS->GetMetadataDomainList();
25✔
3355
    for (char **papszMDListIter = papszMDList;
87✔
3356
         papszMDListIter && *papszMDListIter; ++papszMDListIter)
87✔
3357
    {
3358
        /* Write metadata that look like originating from JP2 XML boxes */
3359
        /* as a standalone JP2 XML box */
3360
        if (STARTS_WITH_CI(*papszMDListIter, "xml:BOX_"))
62✔
3361
        {
3362
            char **papszSrcMD = poSrcDS->GetMetadata(*papszMDListIter);
3✔
3363
            if (papszSrcMD && *papszSrcMD)
3✔
3364
            {
3365
                GDALJP2Box *poBox = new GDALJP2Box();
3✔
3366
                poBox->SetType("xml ");
3✔
3367
                poBox->SetWritableData(
3✔
3368
                    static_cast<int>(strlen(*papszSrcMD) + 1),
3✔
3369
                    reinterpret_cast<const GByte *>(*papszSrcMD));
3370
                papoBoxes = static_cast<GDALJP2Box **>(CPLRealloc(
6✔
3371
                    papoBoxes, sizeof(GDALJP2Box *) * (*pnBoxes + 1)));
3✔
3372
                papoBoxes[(*pnBoxes)++] = poBox;
3✔
3373
            }
3374
        }
3375
    }
3376
    CSLDestroy(papszMDList);
25✔
3377
    return papoBoxes;
25✔
3378
}
3379

3380
/************************************************************************/
3381
/*                          CreateXMPBox()                              */
3382
/************************************************************************/
3383

3384
GDALJP2Box *GDALJP2Metadata::CreateXMPBox(GDALDataset *poSrcDS)
25✔
3385
{
3386
    char **papszSrcMD = poSrcDS->GetMetadata("xml:XMP");
25✔
3387
    GDALJP2Box *poBox = nullptr;
25✔
3388
    if (papszSrcMD && *papszSrcMD)
25✔
3389
    {
3390
        poBox = GDALJP2Box::CreateUUIDBox(
3✔
3391
            xmp_uuid, static_cast<int>(strlen(*papszSrcMD) + 1),
3✔
3392
            reinterpret_cast<const GByte *>(*papszSrcMD));
3393
    }
3394
    return poBox;
25✔
3395
}
3396

3397
/************************************************************************/
3398
/*                          CreateIPRBox()                              */
3399
/************************************************************************/
3400

3401
GDALJP2Box *GDALJP2Metadata::CreateIPRBox(GDALDataset *poSrcDS)
19✔
3402
{
3403
    char **papszSrcMD = poSrcDS->GetMetadata("xml:IPR");
19✔
3404
    GDALJP2Box *poBox = nullptr;
19✔
3405
    if (papszSrcMD && *papszSrcMD)
19✔
3406
    {
3407
        poBox = new GDALJP2Box();
2✔
3408
        poBox->SetType("jp2i");
2✔
3409
        poBox->SetWritableData(static_cast<int>(strlen(*papszSrcMD) + 1),
2✔
3410
                               reinterpret_cast<const GByte *>(*papszSrcMD));
3411
    }
3412
    return poBox;
19✔
3413
}
3414

3415
/************************************************************************/
3416
/*                           IsUUID_MSI()                              */
3417
/************************************************************************/
3418

3419
int GDALJP2Metadata::IsUUID_MSI(const GByte *abyUUID)
38✔
3420
{
3421
    return memcmp(abyUUID, msi_uuid2, 16) == 0;
38✔
3422
}
3423

3424
/************************************************************************/
3425
/*                           IsUUID_XMP()                               */
3426
/************************************************************************/
3427

3428
int GDALJP2Metadata::IsUUID_XMP(const GByte *abyUUID)
1✔
3429
{
3430
    return memcmp(abyUUID, xmp_uuid, 16) == 0;
1✔
3431
}
3432

3433
/*! @endcond */
STATUS · Troubleshooting · Open an Issue · Sales · Support · CAREERS · ENTERPRISE · START FREE · SCHEDULE DEMO
ANNOUNCEMENTS · TWITTER · TOS & SLA · Supported CI Services · What's a CI service? · Automated Testing

© 2025 Coveralls, Inc