Coverage Report

Created: 2025-11-15 08:43

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