Coverage Report

Created: 2025-06-09 08:44

/src/gdal/frmts/dimap/dimapdataset.cpp
Line
Count
Source (jump to first uncovered line)
1
/******************************************************************************
2
 *
3
 * Project:  SPOT Dimap Driver
4
 * Purpose:  Implementation of SPOT Dimap driver.
5
 * Author:   Frank Warmerdam, warmerdam@pobox.com
6
 *
7
 * Docs: http://www.spotimage.fr/dimap/spec/documentation/refdoc.htm
8
 *
9
 ******************************************************************************
10
 * Copyright (c) 2007, Frank Warmerdam <warmerdam@pobox.com>
11
 * Copyright (c) 2008-2013, Even Rouault <even dot rouault at spatialys.com>
12
 *
13
 * SPDX-License-Identifier: MIT
14
 ****************************************************************************/
15
16
#include "cpl_minixml.h"
17
#include "gdal_frmts.h"
18
#include "gdal_pam.h"
19
#include "ogr_spatialref.h"
20
#include "mdreader/reader_pleiades.h"
21
#include "vrtdataset.h"
22
#include <map>
23
#include <algorithm>
24
25
/************************************************************************/
26
/* ==================================================================== */
27
/*                              DIMAPDataset                            */
28
/* ==================================================================== */
29
/************************************************************************/
30
31
class DIMAPDataset final : public GDALPamDataset
32
{
33
    CPLXMLNode *psProduct;
34
35
    CPLXMLNode *psProductDim;    // DIMAP2, DIM_<product_id>.XML
36
    CPLXMLNode *psProductStrip;  // DIMAP2, STRIP_<product_id>.XML
37
    CPLString osRPCFilename;     // DIMAP2, RPC_<product_id>.XML
38
39
    VRTDataset *poVRTDS;
40
41
    int nGCPCount;
42
    GDAL_GCP *pasGCPList;
43
44
    OGRSpatialReference m_oSRS{};
45
    OGRSpatialReference m_oGCPSRS{};
46
47
    int bHaveGeoTransform;
48
    double adfGeoTransform[6];
49
50
    CPLString osMDFilename;
51
    CPLString osImageDSFilename;
52
    CPLString osDIMAPFilename;
53
    int nProductVersion;
54
55
    char **papszXMLDimapMetadata;
56
57
  protected:
58
    int CloseDependentDatasets() override;
59
60
    int ReadImageInformation();
61
    int ReadImageInformation2();  // DIMAP 2.
62
63
    void SetMetadataFromXML(CPLXMLNode *psProduct,
64
                            const char *const apszMetadataTranslation[],
65
                            bool bKeysFromRoot = true);
66
67
  public:
68
    DIMAPDataset();
69
    ~DIMAPDataset() override;
70
71
    const OGRSpatialReference *GetSpatialRef() const override;
72
    CPLErr GetGeoTransform(double *) override;
73
    int GetGCPCount() override;
74
    const OGRSpatialReference *GetGCPSpatialRef() const override;
75
    const GDAL_GCP *GetGCPs() override;
76
    char **GetMetadataDomainList() override;
77
    char **GetMetadata(const char *pszDomain) override;
78
    char **GetFileList() override;
79
80
    CPLErr IRasterIO(GDALRWFlag, int, int, int, int, void *, int, int,
81
                     GDALDataType, int, BANDMAP_TYPE, GSpacing, GSpacing,
82
                     GSpacing, GDALRasterIOExtraArg *psExtraArg) override;
83
84
    static int Identify(GDALOpenInfo *);
85
    static GDALDataset *Open(GDALOpenInfo *);
86
87
    CPLXMLNode *GetProduct()
88
0
    {
89
0
        return psProduct;
90
0
    }
91
};
92
93
/************************************************************************/
94
/* ==================================================================== */
95
/*                              DIMAPDataset                            */
96
/* ==================================================================== */
97
/************************************************************************/
98
99
/************************************************************************/
100
/*                             DIMAPDataset()                            */
101
/************************************************************************/
102
103
DIMAPDataset::DIMAPDataset()
104
545
    : psProduct(nullptr), psProductDim(nullptr), psProductStrip(nullptr),
105
545
      poVRTDS(nullptr), nGCPCount(0), pasGCPList(nullptr),
106
545
      bHaveGeoTransform(FALSE), nProductVersion(1),
107
545
      papszXMLDimapMetadata(nullptr)
108
545
{
109
545
    m_oSRS.SetAxisMappingStrategy(OAMS_TRADITIONAL_GIS_ORDER);
110
545
    m_oGCPSRS.SetAxisMappingStrategy(OAMS_TRADITIONAL_GIS_ORDER);
111
545
    adfGeoTransform[0] = 0.0;
112
545
    adfGeoTransform[1] = 1.0;
113
545
    adfGeoTransform[2] = 0.0;
114
545
    adfGeoTransform[3] = 0.0;
115
545
    adfGeoTransform[4] = 0.0;
116
545
    adfGeoTransform[5] = 1.0;
117
545
}
118
119
/************************************************************************/
120
/*                            ~DIMAPDataset()                           */
121
/************************************************************************/
122
123
DIMAPDataset::~DIMAPDataset()
124
125
545
{
126
545
    DIMAPDataset::FlushCache(true);
127
128
545
    CPLDestroyXMLNode(psProduct);
129
130
545
    if (psProductDim != nullptr && psProductDim != psProduct)
131
353
        CPLDestroyXMLNode(psProductDim);
132
545
    if (psProductStrip != nullptr)
133
0
        CPLDestroyXMLNode(psProductStrip);
134
545
    if (nGCPCount > 0)
135
0
    {
136
0
        GDALDeinitGCPs(nGCPCount, pasGCPList);
137
0
        CPLFree(pasGCPList);
138
0
    }
139
140
545
    CSLDestroy(papszXMLDimapMetadata);
141
142
545
    DIMAPDataset::CloseDependentDatasets();
143
545
}
144
145
/************************************************************************/
146
/*                        CloseDependentDatasets()                      */
147
/************************************************************************/
148
149
int DIMAPDataset::CloseDependentDatasets()
150
545
{
151
545
    int bHasDroppedRef = GDALPamDataset::CloseDependentDatasets();
152
153
545
    if (poVRTDS != nullptr)
154
371
    {
155
371
        delete poVRTDS;
156
371
        poVRTDS = nullptr;
157
371
        bHasDroppedRef = TRUE;
158
371
    }
159
160
545
    return bHasDroppedRef;
161
545
}
162
163
/************************************************************************/
164
/*                      GetMetadataDomainList()                         */
165
/************************************************************************/
166
167
char **DIMAPDataset::GetMetadataDomainList()
168
0
{
169
0
    return BuildMetadataDomainList(GDALPamDataset::GetMetadataDomainList(),
170
0
                                   TRUE, "xml:dimap", nullptr);
171
0
}
172
173
/************************************************************************/
174
/*                            GetMetadata()                             */
175
/*                                                                      */
176
/*      We implement special support for fetching the full product      */
177
/*      metadata as xml.                                                */
178
/************************************************************************/
179
180
char **DIMAPDataset::GetMetadata(const char *pszDomain)
181
182
367
{
183
367
    if (pszDomain && EQUAL(pszDomain, "xml:dimap"))
184
0
    {
185
0
        if (papszXMLDimapMetadata == nullptr)
186
0
        {
187
0
            papszXMLDimapMetadata =
188
0
                reinterpret_cast<char **>(CPLCalloc(sizeof(char *), 2));
189
0
            papszXMLDimapMetadata[0] = CPLSerializeXMLTree(psProduct);
190
0
        }
191
0
        return papszXMLDimapMetadata;
192
0
    }
193
194
367
    return GDALPamDataset::GetMetadata(pszDomain);
195
367
}
196
197
/************************************************************************/
198
/*                          GetSpatialRef()                             */
199
/************************************************************************/
200
201
const OGRSpatialReference *DIMAPDataset::GetSpatialRef() const
202
203
379
{
204
379
    if (!m_oSRS.IsEmpty())
205
17
        return &m_oSRS;
206
207
362
    return GDALPamDataset::GetSpatialRef();
208
379
}
209
210
/************************************************************************/
211
/*                          GetGeoTransform()                           */
212
/************************************************************************/
213
214
CPLErr DIMAPDataset::GetGeoTransform(double *padfGeoTransform)
215
216
379
{
217
379
    if (bHaveGeoTransform)
218
62
    {
219
62
        memcpy(padfGeoTransform, adfGeoTransform, sizeof(double) * 6);
220
62
        return CE_None;
221
62
    }
222
223
317
    return GDALPamDataset::GetGeoTransform(padfGeoTransform);
224
379
}
225
226
/************************************************************************/
227
/*                            GetFileList()                             */
228
/************************************************************************/
229
230
char **DIMAPDataset::GetFileList()
231
232
734
{
233
734
    char **papszFileList = GDALPamDataset::GetFileList();
234
734
    char **papszImageFiles = poVRTDS->GetFileList();
235
236
734
    papszFileList = CSLInsertStrings(papszFileList, -1, papszImageFiles);
237
238
734
    CSLDestroy(papszImageFiles);
239
240
734
    return papszFileList;
241
734
}
242
243
/************************************************************************/
244
/* ==================================================================== */
245
/*                            DIMAPRasterBand                           */
246
/* ==================================================================== */
247
/************************************************************************/
248
249
class DIMAPRasterBand final : public GDALPamRasterBand
250
{
251
    friend class DIMAPDataset;
252
253
    VRTSourcedRasterBand *poVRTBand;
254
255
  public:
256
    DIMAPRasterBand(DIMAPDataset *, int, VRTSourcedRasterBand *);
257
258
    ~DIMAPRasterBand() override
259
0
    {
260
0
    }
261
262
    CPLErr IReadBlock(int, int, void *) override;
263
    CPLErr IRasterIO(GDALRWFlag, int, int, int, int, void *, int, int,
264
                     GDALDataType, GSpacing nPixelSpace, GSpacing nLineSpace,
265
                     GDALRasterIOExtraArg *psExtraArg) override;
266
    int GetOverviewCount() override;
267
    GDALRasterBand *GetOverview(int) override;
268
    CPLErr ComputeRasterMinMax(int bApproxOK, double adfMinMax[2]) override;
269
    CPLErr ComputeStatistics(int bApproxOK, double *pdfMin, double *pdfMax,
270
                             double *pdfMean, double *pdfStdDev,
271
                             GDALProgressFunc, void *pProgressData) override;
272
273
    CPLErr GetHistogram(double dfMin, double dfMax, int nBuckets,
274
                        GUIntBig *panHistogram, int bIncludeOutOfRange,
275
                        int bApproxOK, GDALProgressFunc,
276
                        void *pProgressData) override;
277
};
278
279
/************************************************************************/
280
/*                          DIMAPRasterBand()                           */
281
/************************************************************************/
282
283
DIMAPRasterBand::DIMAPRasterBand(DIMAPDataset *poDIMAPDS, int nBandIn,
284
                                 VRTSourcedRasterBand *poVRTBandIn)
285
1.47k
    : poVRTBand(poVRTBandIn)
286
1.47k
{
287
1.47k
    poDS = poDIMAPDS;
288
1.47k
    nBand = nBandIn;
289
1.47k
    eDataType = poVRTBandIn->GetRasterDataType();
290
291
1.47k
    poVRTBandIn->GetBlockSize(&nBlockXSize, &nBlockYSize);
292
1.47k
}
293
294
/************************************************************************/
295
/*                             IReadBlock()                             */
296
/************************************************************************/
297
298
CPLErr DIMAPRasterBand::IReadBlock(int iBlockX, int iBlockY, void *pBuffer)
299
300
0
{
301
0
    return poVRTBand->ReadBlock(iBlockX, iBlockY, pBuffer);
302
0
}
303
304
/************************************************************************/
305
/*                             IRasterIO()                              */
306
/************************************************************************/
307
308
CPLErr DIMAPRasterBand::IRasterIO(GDALRWFlag eRWFlag, int nXOff, int nYOff,
309
                                  int nXSize, int nYSize, void *pData,
310
                                  int nBufXSize, int nBufYSize,
311
                                  GDALDataType eBufType, GSpacing nPixelSpace,
312
                                  GSpacing nLineSpace,
313
                                  GDALRasterIOExtraArg *psExtraArg)
314
315
11.6k
{
316
11.6k
    if (GDALPamRasterBand::GetOverviewCount() > 0)
317
0
    {
318
0
        return GDALPamRasterBand::IRasterIO(
319
0
            eRWFlag, nXOff, nYOff, nXSize, nYSize, pData, nBufXSize, nBufYSize,
320
0
            eBufType, nPixelSpace, nLineSpace, psExtraArg);
321
0
    }
322
323
    // If not exist DIMAP overviews, try to use band source overviews.
324
11.6k
    return poVRTBand->IRasterIO(eRWFlag, nXOff, nYOff, nXSize, nYSize, pData,
325
11.6k
                                nBufXSize, nBufYSize, eBufType, nPixelSpace,
326
11.6k
                                nLineSpace, psExtraArg);
327
11.6k
}
328
329
/************************************************************************/
330
/*                             IRasterIO()                              */
331
/************************************************************************/
332
333
CPLErr DIMAPDataset::IRasterIO(GDALRWFlag eRWFlag, int nXOff, int nYOff,
334
                               int nXSize, int nYSize, void *pData,
335
                               int nBufXSize, int nBufYSize,
336
                               GDALDataType eBufType, int nBandCount,
337
                               BANDMAP_TYPE panBandMap, GSpacing nPixelSpace,
338
                               GSpacing nLineSpace, GSpacing nBandSpace,
339
                               GDALRasterIOExtraArg *psExtraArg)
340
341
0
{
342
0
    if (cpl::down_cast<DIMAPRasterBand *>(papoBands[0])
343
0
            ->GDALPamRasterBand::GetOverviewCount() > 0)
344
0
    {
345
0
        return GDALPamDataset::IRasterIO(eRWFlag, nXOff, nYOff, nXSize, nYSize,
346
0
                                         pData, nBufXSize, nBufYSize, eBufType,
347
0
                                         nBandCount, panBandMap, nPixelSpace,
348
0
                                         nLineSpace, nBandSpace, psExtraArg);
349
0
    }
350
351
0
    return poVRTDS->IRasterIO(eRWFlag, nXOff, nYOff, nXSize, nYSize, pData,
352
0
                              nBufXSize, nBufYSize, eBufType, nBandCount,
353
0
                              panBandMap, nPixelSpace, nLineSpace, nBandSpace,
354
0
                              psExtraArg);
355
0
}
356
357
/************************************************************************/
358
/*                          GetOverviewCount()                          */
359
/************************************************************************/
360
361
int DIMAPRasterBand::GetOverviewCount()
362
367
{
363
367
    if (GDALPamRasterBand::GetOverviewCount() > 0)
364
0
    {
365
0
        return GDALPamRasterBand::GetOverviewCount();
366
0
    }
367
367
    return poVRTBand->GetOverviewCount();
368
367
}
369
370
/************************************************************************/
371
/*                             GetOverview()                            */
372
/************************************************************************/
373
374
GDALRasterBand *DIMAPRasterBand::GetOverview(int iOvr)
375
0
{
376
0
    if (GDALPamRasterBand::GetOverviewCount() > 0)
377
0
    {
378
0
        return GDALPamRasterBand::GetOverview(iOvr);
379
0
    }
380
0
    return poVRTBand->GetOverview(iOvr);
381
0
}
382
383
/************************************************************************/
384
/*                         ComputeRasterMinMax()                        */
385
/************************************************************************/
386
387
CPLErr DIMAPRasterBand::ComputeRasterMinMax(int bApproxOK, double adfMinMax[2])
388
0
{
389
0
    if (GDALPamRasterBand::GetOverviewCount() > 0)
390
0
    {
391
0
        return GDALPamRasterBand::ComputeRasterMinMax(bApproxOK, adfMinMax);
392
0
    }
393
0
    return poVRTBand->ComputeRasterMinMax(bApproxOK, adfMinMax);
394
0
}
395
396
/************************************************************************/
397
/*                          ComputeStatistics()                         */
398
/************************************************************************/
399
400
CPLErr DIMAPRasterBand::ComputeStatistics(int bApproxOK, double *pdfMin,
401
                                          double *pdfMax, double *pdfMean,
402
                                          double *pdfStdDev,
403
                                          GDALProgressFunc pfnProgress,
404
                                          void *pProgressData)
405
0
{
406
0
    if (GDALPamRasterBand::GetOverviewCount() > 0)
407
0
    {
408
0
        return GDALPamRasterBand::ComputeStatistics(bApproxOK, pdfMin, pdfMax,
409
0
                                                    pdfMean, pdfStdDev,
410
0
                                                    pfnProgress, pProgressData);
411
0
    }
412
0
    return poVRTBand->ComputeStatistics(bApproxOK, pdfMin, pdfMax, pdfMean,
413
0
                                        pdfStdDev, pfnProgress, pProgressData);
414
0
}
415
416
/************************************************************************/
417
/*                            GetHistogram()                            */
418
/************************************************************************/
419
420
CPLErr DIMAPRasterBand::GetHistogram(double dfMin, double dfMax, int nBuckets,
421
                                     GUIntBig *panHistogram,
422
                                     int bIncludeOutOfRange, int bApproxOK,
423
                                     GDALProgressFunc pfnProgress,
424
                                     void *pProgressData)
425
0
{
426
0
    if (GDALPamRasterBand::GetOverviewCount() > 0)
427
0
    {
428
0
        return GDALPamRasterBand::GetHistogram(
429
0
            dfMin, dfMax, nBuckets, panHistogram, bIncludeOutOfRange, bApproxOK,
430
0
            pfnProgress, pProgressData);
431
0
    }
432
0
    return poVRTBand->GetHistogram(dfMin, dfMax, nBuckets, panHistogram,
433
0
                                   bIncludeOutOfRange, bApproxOK, pfnProgress,
434
0
                                   pProgressData);
435
0
}
436
437
/************************************************************************/
438
/*                              Identify()                              */
439
/************************************************************************/
440
441
int DIMAPDataset::Identify(GDALOpenInfo *poOpenInfo)
442
443
917k
{
444
917k
    if (STARTS_WITH(poOpenInfo->pszFilename, "DIMAP:"))
445
0
        return true;
446
447
917k
    if (poOpenInfo->nHeaderBytes >= 100)
448
93.1k
    {
449
93.1k
        if ((strstr(reinterpret_cast<char *>(poOpenInfo->pabyHeader),
450
93.1k
                    "<Dimap_Document") == nullptr) &&
451
93.1k
            (strstr(reinterpret_cast<char *>(poOpenInfo->pabyHeader),
452
92.2k
                    "<PHR_DIMAP_Document") == nullptr))
453
91.3k
            return FALSE;
454
455
1.83k
        return TRUE;
456
93.1k
    }
457
823k
    else if (poOpenInfo->bIsDirectory)
458
7.65k
    {
459
        // DIMAP file.
460
7.65k
        CPLString osMDFilename = CPLFormCIFilenameSafe(poOpenInfo->pszFilename,
461
7.65k
                                                       "METADATA.DIM", nullptr);
462
463
7.65k
        VSIStatBufL sStat;
464
7.65k
        if (VSIStatL(osMDFilename, &sStat) == 0)
465
20
        {
466
            // Make sure this is really a Dimap format.
467
20
            GDALOpenInfo oOpenInfo(osMDFilename, GA_ReadOnly, nullptr);
468
20
            if (oOpenInfo.nHeaderBytes >= 100)
469
20
            {
470
20
                if (strstr(reinterpret_cast<char *>(oOpenInfo.pabyHeader),
471
20
                           "<Dimap_Document") == nullptr)
472
0
                    return FALSE;
473
474
20
                return TRUE;
475
20
            }
476
20
        }
477
7.63k
        else
478
7.63k
        {
479
            // DIMAP 2 file.
480
7.63k
            osMDFilename = CPLFormCIFilenameSafe(poOpenInfo->pszFilename,
481
7.63k
                                                 "VOL_PHR.XML", nullptr);
482
483
7.63k
            if (VSIStatL(osMDFilename, &sStat) == 0)
484
1.04k
                return TRUE;
485
486
            // DIMAP VHR2020 file.
487
6.59k
            osMDFilename = CPLFormCIFilenameSafe(poOpenInfo->pszFilename,
488
6.59k
                                                 "VOL_PNEO.XML", nullptr);
489
490
6.59k
            if (VSIStatL(osMDFilename, &sStat) == 0)
491
22
                return TRUE;
492
493
6.57k
            return FALSE;
494
6.59k
        }
495
7.65k
    }
496
497
816k
    return FALSE;
498
917k
}
499
500
/************************************************************************/
501
/*                                Open()                                */
502
/************************************************************************/
503
504
GDALDataset *DIMAPDataset::Open(GDALOpenInfo *poOpenInfo)
505
506
1.46k
{
507
1.46k
    if (!Identify(poOpenInfo))
508
0
        return nullptr;
509
510
    /* -------------------------------------------------------------------- */
511
    /*      Confirm the requested access is supported.                      */
512
    /* -------------------------------------------------------------------- */
513
1.46k
    if (poOpenInfo->eAccess == GA_Update)
514
0
    {
515
0
        ReportUpdateNotSupportedByDriver("DIMAP");
516
0
        return nullptr;
517
0
    }
518
    /* -------------------------------------------------------------------- */
519
    /*      Get the metadata filename.                                      */
520
    /* -------------------------------------------------------------------- */
521
1.46k
    CPLString osFilename;
522
1.46k
    CPLString osSelectedSubdataset;
523
524
1.46k
    if (STARTS_WITH(poOpenInfo->pszFilename, "DIMAP:"))
525
0
    {
526
0
        CPLStringList aosTokens(CSLTokenizeString2(poOpenInfo->pszFilename, ":",
527
0
                                                   CSLT_HONOURSTRINGS));
528
0
        if (aosTokens.size() != 3)
529
0
            return nullptr;
530
531
0
        osFilename = aosTokens[1];
532
0
        osSelectedSubdataset = aosTokens[2];
533
0
    }
534
1.46k
    else
535
1.46k
    {
536
1.46k
        osFilename = poOpenInfo->pszFilename;
537
1.46k
    }
538
539
1.46k
    VSIStatBufL sStat;
540
1.46k
    std::string osMDFilename(osFilename);
541
1.46k
    if (VSIStatL(osFilename.c_str(), &sStat) == 0 && VSI_ISDIR(sStat.st_mode))
542
542
    {
543
542
        osMDFilename =
544
542
            CPLFormCIFilenameSafe(osFilename, "METADATA.DIM", nullptr);
545
546
        /* DIMAP2 */
547
542
        if (VSIStatL(osMDFilename.c_str(), &sStat) != 0)
548
532
        {
549
532
            osMDFilename =
550
532
                CPLFormCIFilenameSafe(osFilename, "VOL_PHR.XML", nullptr);
551
532
            if (VSIStatL(osMDFilename.c_str(), &sStat) != 0)
552
11
            {
553
                // DIMAP VHR2020 file.
554
11
                osMDFilename =
555
11
                    CPLFormCIFilenameSafe(osFilename, "VOL_PNEO.XML", nullptr);
556
11
            }
557
532
        }
558
542
    }
559
560
    /* -------------------------------------------------------------------- */
561
    /*      Ingest the xml file.                                            */
562
    /* -------------------------------------------------------------------- */
563
1.46k
    CPLXMLNode *psProduct = CPLParseXMLFile(osMDFilename.c_str());
564
1.46k
    if (psProduct == nullptr)
565
725
        return nullptr;
566
567
735
    CPLXMLNode *psDoc = CPLGetXMLNode(psProduct, "=Dimap_Document");
568
735
    if (!psDoc)
569
173
        psDoc = CPLGetXMLNode(psProduct, "=PHR_DIMAP_Document");
570
571
    // We check the for the tag Metadata_Identification.METADATA_FORMAT.
572
    // The metadata will be set to 2.0 for DIMAP2.
573
735
    double dfMetadataFormatVersion = CPLAtof(CPLGetXMLValue(
574
735
        CPLGetXMLNode(psDoc, "Metadata_Identification.METADATA_FORMAT"),
575
735
        "version", "1"));
576
577
735
    const int nProductVersion = dfMetadataFormatVersion >= 2.0 ? 2 : 1;
578
579
735
    std::string osImageDSFilename;
580
735
    std::string osDIMAPFilename;
581
735
    std::string osRPCFilename;
582
735
    CPLXMLNode *psProductDim = nullptr;
583
735
    CPLXMLNode *psProductStrip = nullptr;
584
585
735
    CPLStringList aosSubdatasets;
586
587
    // Check needed information for the DIMAP format.
588
735
    if (nProductVersion == 1)
589
174
    {
590
174
        CPLXMLNode *psImageAttributes =
591
174
            CPLGetXMLNode(psDoc, "Raster_Dimensions");
592
174
        if (psImageAttributes == nullptr)
593
173
        {
594
173
            CPLError(CE_Failure, CPLE_OpenFailed,
595
173
                     "Failed to find <Raster_Dimensions> in document.");
596
173
            CPLDestroyXMLNode(psProduct);
597
173
            return nullptr;
598
173
        }
599
174
    }
600
561
    else  // DIMAP2.
601
561
    {
602
        // Verify if the opened file is not already a product dimap
603
561
        if (CPLGetXMLNode(psDoc, "Raster_Data"))
604
191
        {
605
191
            psProductDim = psProduct;
606
191
            osDIMAPFilename = osMDFilename;
607
191
        }
608
370
        else
609
370
        {
610
            // Verify the presence of the DIMAP product file.
611
370
            CPLXMLNode *psDatasetComponents =
612
370
                CPLGetXMLNode(psDoc, "Dataset_Content.Dataset_Components");
613
614
370
            if (psDatasetComponents == nullptr)
615
0
            {
616
0
                CPLError(CE_Failure, CPLE_OpenFailed,
617
0
                         "Failed to find <Dataset_Components> in document.");
618
0
                CPLDestroyXMLNode(psProduct);
619
0
                return nullptr;
620
0
            }
621
622
370
            for (CPLXMLNode *psDatasetComponent = psDatasetComponents->psChild;
623
1.32k
                 psDatasetComponent != nullptr;
624
957
                 psDatasetComponent = psDatasetComponent->psNext)
625
957
            {
626
957
                const char *pszComponentType =
627
957
                    CPLGetXMLValue(psDatasetComponent, "COMPONENT_TYPE", "");
628
957
                if (strcmp(pszComponentType, "DIMAP") == 0)
629
654
                {
630
                    // DIMAP product found.
631
654
                    const char *pszHref = CPLGetXMLValue(
632
654
                        psDatasetComponent, "COMPONENT_PATH.href", "");
633
654
                    const CPLString osComponentTitle(CPLGetXMLValue(
634
654
                        psDatasetComponent, "COMPONENT_TITLE", ""));
635
654
                    const CPLString osComponentTitleLaundered(
636
654
                        CPLString(osComponentTitle).replaceAll(' ', '_'));
637
638
654
                    if (strlen(pszHref) > 0 && osDIMAPFilename.empty() &&
639
654
                        (osSelectedSubdataset.empty() ||
640
370
                         osSelectedSubdataset == osComponentTitleLaundered))
641
370
                    {
642
370
                        if (poOpenInfo->bIsDirectory)
643
355
                        {
644
355
                            osDIMAPFilename = CPLFormCIFilenameSafe(
645
355
                                poOpenInfo->pszFilename, pszHref, nullptr);
646
355
                        }
647
15
                        else
648
15
                        {
649
15
                            CPLString osPath =
650
15
                                CPLGetPathSafe(osMDFilename.c_str());
651
15
                            osDIMAPFilename =
652
15
                                CPLFormFilenameSafe(osPath, pszHref, nullptr);
653
15
                        }
654
655
                        // Data file might be specified there.
656
370
                        const char *pszDataFileHref = CPLGetXMLValue(
657
370
                            psDatasetComponent,
658
370
                            "Data_Files.Data_File.DATA_FILE_PATH.href", "");
659
660
370
                        if (strlen(pszDataFileHref) > 0)
661
0
                        {
662
0
                            CPLString osPath =
663
0
                                CPLGetPathSafe(osMDFilename.c_str());
664
0
                            osImageDSFilename = CPLFormFilenameSafe(
665
0
                                osPath, pszDataFileHref, nullptr);
666
0
                        }
667
370
                    }
668
669
654
                    const int iIdx =
670
654
                        static_cast<int>(aosSubdatasets.size() / 2 + 1);
671
654
                    aosSubdatasets.SetNameValue(
672
654
                        CPLSPrintf("SUBDATASET_%d_NAME", iIdx),
673
654
                        CPLSPrintf("DIMAP:\"%s\":%s", poOpenInfo->pszFilename,
674
654
                                   osComponentTitleLaundered.c_str()));
675
654
                    aosSubdatasets.SetNameValue(
676
654
                        CPLSPrintf("SUBDATASET_%d_DESC", iIdx),
677
654
                        CPLSPrintf("Component %s", osComponentTitle.c_str()));
678
654
                }
679
957
            }
680
681
370
            psProductDim = CPLParseXMLFile(osDIMAPFilename.c_str());
682
370
            if (psProductDim == nullptr)
683
17
            {
684
17
                CPLDestroyXMLNode(psProduct);
685
17
                return nullptr;
686
17
            }
687
370
        }
688
689
        // We need the {STRIP|RPC}_<product_id>.XML file for a few metadata.
690
544
        CPLXMLNode *psDocDim = CPLGetXMLNode(psProductDim, "=Dimap_Document");
691
544
        if (!psDocDim)
692
0
            psDocDim = CPLGetXMLNode(psProductDim, "=PHR_DIMAP_Document");
693
694
544
        CPLXMLNode *psDatasetSources =
695
544
            CPLGetXMLNode(psDocDim, "Dataset_Sources");
696
544
        if (psDatasetSources != nullptr)
697
543
        {
698
543
            CPLString osSTRIPFilename;
699
700
543
            for (CPLXMLNode *psDatasetSource = psDatasetSources->psChild;
701
1.28k
                 psDatasetSource != nullptr;
702
744
                 psDatasetSource = psDatasetSource->psNext)
703
746
            {
704
746
                const char *pszSourceType =
705
746
                    CPLGetXMLValue(psDatasetSource, "SOURCE_TYPE", "");
706
746
                if (strcmp(pszSourceType, "Strip_Source") == 0)
707
431
                {
708
431
                    const char *pszHref = CPLGetXMLValue(
709
431
                        psDatasetSource, "Component.COMPONENT_PATH.href", "");
710
711
431
                    if (strlen(pszHref) > 0)  // STRIP product found.
712
339
                    {
713
339
                        CPLString osPath =
714
339
                            CPLGetPathSafe(osDIMAPFilename.c_str());
715
339
                        osSTRIPFilename =
716
339
                            CPLFormCIFilenameSafe(osPath, pszHref, nullptr);
717
339
                        if (VSIStatL(osSTRIPFilename, &sStat) == 0)
718
2
                        {
719
2
                            psProductStrip = CPLParseXMLFile(osSTRIPFilename);
720
2
                            break;
721
2
                        }
722
339
                    }
723
431
                }
724
746
            }
725
543
        }
726
727
544
        CPLXMLNode *psDatasetRFMComponents = CPLGetXMLNode(
728
544
            psDocDim, "Geoposition.Geoposition_Models.Rational_Function_Model");
729
544
        if (psDatasetRFMComponents != nullptr)
730
354
        {
731
354
            for (CPLXMLNode *psDatasetRFMComponent =
732
354
                     psDatasetRFMComponents->psChild;
733
1.11k
                 psDatasetRFMComponent != nullptr;
734
764
                 psDatasetRFMComponent = psDatasetRFMComponent->psNext)
735
819
            {
736
819
                const char *pszComponentTitle = CPLGetXMLValue(
737
819
                    psDatasetRFMComponent, "COMPONENT_TITLE", "");
738
819
                if (strcmp(pszComponentTitle, "RPC Model") == 0)
739
56
                {
740
56
                    const char *pszHref = CPLGetXMLValue(
741
56
                        psDatasetRFMComponent, "COMPONENT_PATH.href", "");
742
743
56
                    if (strlen(pszHref) > 0)  // RPC product found.
744
55
                    {
745
55
                        CPLString osPath =
746
55
                            CPLGetPathSafe(osDIMAPFilename.c_str());
747
55
                        osRPCFilename =
748
55
                            CPLFormCIFilenameSafe(osPath, pszHref, nullptr);
749
750
55
                        break;
751
55
                    }
752
56
                }
753
819
            }
754
354
        }
755
544
    }
756
757
    /* -------------------------------------------------------------------- */
758
    /*      Create the dataset.                                             */
759
    /* -------------------------------------------------------------------- */
760
545
    DIMAPDataset *poDS = new DIMAPDataset();
761
762
545
    if (osSelectedSubdataset.empty() && aosSubdatasets.size() > 2)
763
12
    {
764
12
        poDS->GDALDataset::SetMetadata(aosSubdatasets.List(), "SUBDATASETS");
765
12
    }
766
545
    poDS->psProduct = psProduct;
767
545
    poDS->psProductDim = psProductDim;
768
545
    poDS->psProductStrip = psProductStrip;
769
545
    poDS->osRPCFilename = std::move(osRPCFilename);
770
545
    poDS->nProductVersion = nProductVersion;
771
545
    poDS->osMDFilename = std::move(osMDFilename);
772
545
    poDS->osImageDSFilename = std::move(osImageDSFilename);
773
545
    poDS->osDIMAPFilename = std::move(osDIMAPFilename);
774
775
545
    const int res = (nProductVersion == 2) ? poDS->ReadImageInformation2()
776
545
                                           : poDS->ReadImageInformation();
777
778
545
    if (res == FALSE)
779
174
    {
780
174
        delete poDS;
781
174
        return nullptr;
782
174
    }
783
784
371
    return poDS;
785
545
}
786
787
/************************************************************************/
788
/*               ReadImageInformation() DIMAP Version 1                 */
789
/************************************************************************/
790
791
int DIMAPDataset::ReadImageInformation()
792
1
{
793
1
    CPLXMLNode *psDoc = CPLGetXMLNode(psProduct, "=Dimap_Document");
794
1
    if (!psDoc)
795
0
        psDoc = CPLGetXMLNode(psProduct, "=PHR_DIMAP_Document");
796
797
    /* -------------------------------------------------------------------- */
798
    /*      Get overall image information.                                  */
799
    /* -------------------------------------------------------------------- */
800
801
    // TODO: DIMAP 1 probably handle mosaics? Like DIMAP 2?
802
803
    /* -------------------------------------------------------------------- */
804
    /*      Get the name of the underlying file.                            */
805
    /* -------------------------------------------------------------------- */
806
807
1
    const char *pszHref =
808
1
        CPLGetXMLValue(psDoc, "Data_Access.Data_File.DATA_FILE_PATH.href", "");
809
1
    CPLString osPath = CPLGetPathSafe(osMDFilename);
810
1
    CPLString osImageFilename = CPLFormFilenameSafe(osPath, pszHref, nullptr);
811
812
    /* -------------------------------------------------------------------- */
813
    /*      Try and open the file.                                          */
814
    /* -------------------------------------------------------------------- */
815
816
1
    auto poImageDS = std::unique_ptr<GDALDataset>(GDALDataset::Open(
817
1
        osImageFilename, GDAL_OF_RASTER | GDAL_OF_VERBOSE_ERROR));
818
1
    if (poImageDS == nullptr)
819
1
    {
820
1
        return FALSE;
821
1
    }
822
0
    nRasterXSize = poImageDS->GetRasterXSize();
823
0
    nRasterYSize = poImageDS->GetRasterYSize();
824
825
    /* -------------------------------------------------------------------- */
826
    /*      Create and initialize the corresponding VRT dataset used to     */
827
    /*      manage the tiled data access.                                   */
828
    /* -------------------------------------------------------------------- */
829
0
    poVRTDS = new VRTDataset(nRasterXSize, nRasterYSize);
830
831
    // Don't try to write a VRT file.
832
0
    poVRTDS->SetWritable(FALSE);
833
834
0
    for (int iBand = 0; iBand < poImageDS->GetRasterCount(); iBand++)
835
0
    {
836
0
        poVRTDS->AddBand(
837
0
            poImageDS->GetRasterBand(iBand + 1)->GetRasterDataType(), nullptr);
838
839
0
        VRTSourcedRasterBand *poVRTBand =
840
0
            reinterpret_cast<VRTSourcedRasterBand *>(
841
0
                poVRTDS->GetRasterBand(iBand + 1));
842
843
0
        poVRTBand->AddSimpleSource(osImageFilename, iBand + 1, 0, 0,
844
0
                                   nRasterXSize, nRasterYSize, 0, 0,
845
0
                                   nRasterXSize, nRasterYSize);
846
0
    }
847
848
    /* -------------------------------------------------------------------- */
849
    /*      Create band information objects.                                */
850
    /* -------------------------------------------------------------------- */
851
0
    for (int iBand = 1; iBand <= poVRTDS->GetRasterCount(); iBand++)
852
0
    {
853
0
        SetBand(iBand, new DIMAPRasterBand(this, iBand,
854
0
                                           static_cast<VRTSourcedRasterBand *>(
855
0
                                               poVRTDS->GetRasterBand(iBand))));
856
0
    }
857
858
    /* -------------------------------------------------------------------- */
859
    /*      Try to collect simple insertion point.                          */
860
    /* -------------------------------------------------------------------- */
861
0
    CPLXMLNode *psGeoLoc =
862
0
        CPLGetXMLNode(psDoc, "Geoposition.Geoposition_Insert");
863
864
0
    if (psGeoLoc != nullptr)
865
0
    {
866
0
        bHaveGeoTransform = TRUE;
867
0
        adfGeoTransform[0] = CPLAtof(CPLGetXMLValue(psGeoLoc, "ULXMAP", "0"));
868
0
        adfGeoTransform[1] = CPLAtof(CPLGetXMLValue(psGeoLoc, "XDIM", "0"));
869
0
        adfGeoTransform[2] = 0.0;
870
0
        adfGeoTransform[3] = CPLAtof(CPLGetXMLValue(psGeoLoc, "ULYMAP", "0"));
871
0
        adfGeoTransform[4] = 0.0;
872
0
        adfGeoTransform[5] = -CPLAtof(CPLGetXMLValue(psGeoLoc, "YDIM", "0"));
873
0
    }
874
0
    else
875
0
    {
876
        // Try to get geotransform from underlying raster.
877
0
        if (poImageDS->GetGeoTransform(adfGeoTransform) == CE_None)
878
0
            bHaveGeoTransform = TRUE;
879
0
    }
880
881
    /* -------------------------------------------------------------------- */
882
    /*      Collect GCPs.                                                   */
883
    /* -------------------------------------------------------------------- */
884
0
    psGeoLoc = CPLGetXMLNode(psDoc, "Geoposition.Geoposition_Points");
885
886
0
    if (psGeoLoc != nullptr)
887
0
    {
888
        // Count gcps.
889
0
        nGCPCount = 0;
890
0
        for (CPLXMLNode *psNode = psGeoLoc->psChild; psNode != nullptr;
891
0
             psNode = psNode->psNext)
892
0
        {
893
0
            if (EQUAL(psNode->pszValue, "Tie_Point"))
894
0
                nGCPCount++;
895
0
        }
896
897
0
        pasGCPList =
898
0
            static_cast<GDAL_GCP *>(CPLCalloc(sizeof(GDAL_GCP), nGCPCount));
899
900
0
        nGCPCount = 0;
901
902
0
        for (CPLXMLNode *psNode = psGeoLoc->psChild; psNode != nullptr;
903
0
             psNode = psNode->psNext)
904
0
        {
905
0
            GDAL_GCP *psGCP = pasGCPList + nGCPCount;
906
907
0
            if (!EQUAL(psNode->pszValue, "Tie_Point"))
908
0
                continue;
909
910
0
            nGCPCount++;
911
912
0
            char szID[32] = {};
913
0
            snprintf(szID, sizeof(szID), "%d", nGCPCount);
914
0
            psGCP->pszId = CPLStrdup(szID);
915
0
            psGCP->pszInfo = CPLStrdup("");
916
0
            psGCP->dfGCPPixel =
917
0
                CPLAtof(CPLGetXMLValue(psNode, "TIE_POINT_DATA_X", "0")) - 0.5;
918
0
            psGCP->dfGCPLine =
919
0
                CPLAtof(CPLGetXMLValue(psNode, "TIE_POINT_DATA_Y", "0")) - 0.5;
920
0
            psGCP->dfGCPX =
921
0
                CPLAtof(CPLGetXMLValue(psNode, "TIE_POINT_CRS_X", ""));
922
0
            psGCP->dfGCPY =
923
0
                CPLAtof(CPLGetXMLValue(psNode, "TIE_POINT_CRS_Y", ""));
924
0
            psGCP->dfGCPZ =
925
0
                CPLAtof(CPLGetXMLValue(psNode, "TIE_POINT_CRS_Z", ""));
926
0
        }
927
0
    }
928
929
    /* -------------------------------------------------------------------- */
930
    /*      Collect the CRS.  For now we look only for EPSG codes.          */
931
    /* -------------------------------------------------------------------- */
932
0
    const char *pszSRS = CPLGetXMLValue(
933
0
        psDoc, "Coordinate_Reference_System.Horizontal_CS.HORIZONTAL_CS_CODE",
934
0
        nullptr);
935
936
0
    if (pszSRS != nullptr)
937
0
    {
938
0
        OGRSpatialReference &oSRS = nGCPCount > 0 ? m_oGCPSRS : m_oSRS;
939
0
        oSRS.SetFromUserInput(
940
0
            pszSRS, OGRSpatialReference::SET_FROM_USER_INPUT_LIMITATIONS_get());
941
0
    }
942
0
    else
943
0
    {
944
        // Check underlying raster for SRS. We have cases where
945
        // HORIZONTAL_CS_CODE is empty and the underlying raster
946
        // is georeferenced (rprinceley).
947
0
        const auto poSRS = poImageDS->GetSpatialRef();
948
0
        if (poSRS)
949
0
        {
950
0
            m_oSRS = *poSRS;
951
0
        }
952
0
    }
953
954
    /* -------------------------------------------------------------------- */
955
    /*      Translate other metadata of interest.                           */
956
    /* -------------------------------------------------------------------- */
957
0
    static const char *const apszMetadataTranslation[] = {
958
0
        "Production",
959
0
        "",
960
0
        "Production.Facility",
961
0
        "FACILITY_",
962
0
        "Dataset_Sources.Source_Information.Scene_Source",
963
0
        "",
964
0
        "Data_Processing",
965
0
        "",
966
0
        "Image_Interpretation.Spectral_Band_Info",
967
0
        "SPECTRAL_",
968
0
        nullptr,
969
0
        nullptr};
970
971
0
    SetMetadataFromXML(psProduct, apszMetadataTranslation);
972
973
    /* -------------------------------------------------------------------- */
974
    /*      Set Band metadata from the <Spectral_Band_Info> content         */
975
    /* -------------------------------------------------------------------- */
976
977
0
    CPLXMLNode *psImageInterpretationNode =
978
0
        CPLGetXMLNode(psDoc, "Image_Interpretation");
979
0
    if (psImageInterpretationNode != nullptr)
980
0
    {
981
0
        CPLXMLNode *psSpectralBandInfoNode = psImageInterpretationNode->psChild;
982
0
        while (psSpectralBandInfoNode != nullptr)
983
0
        {
984
0
            if (psSpectralBandInfoNode->eType == CXT_Element &&
985
0
                EQUAL(psSpectralBandInfoNode->pszValue, "Spectral_Band_Info"))
986
0
            {
987
0
                CPLXMLNode *psTag = psSpectralBandInfoNode->psChild;
988
0
                int nBandIndex = 0;
989
0
                while (psTag != nullptr)
990
0
                {
991
0
                    if (psTag->eType == CXT_Element &&
992
0
                        psTag->psChild != nullptr &&
993
0
                        psTag->psChild->eType == CXT_Text &&
994
0
                        psTag->pszValue != nullptr)
995
0
                    {
996
0
                        if (EQUAL(psTag->pszValue, "BAND_INDEX"))
997
0
                        {
998
0
                            nBandIndex = atoi(psTag->psChild->pszValue);
999
0
                            if (nBandIndex <= 0 ||
1000
0
                                nBandIndex > poImageDS->GetRasterCount())
1001
0
                            {
1002
0
                                CPLError(CE_Warning, CPLE_AppDefined,
1003
0
                                         "Bad BAND_INDEX value : %s",
1004
0
                                         psTag->psChild->pszValue);
1005
0
                                nBandIndex = 0;
1006
0
                            }
1007
0
                        }
1008
0
                        else if (nBandIndex >= 1)
1009
0
                        {
1010
0
                            GetRasterBand(nBandIndex)
1011
0
                                ->SetMetadataItem(psTag->pszValue,
1012
0
                                                  psTag->psChild->pszValue);
1013
0
                        }
1014
0
                    }
1015
0
                    psTag = psTag->psNext;
1016
0
                }
1017
0
            }
1018
0
            psSpectralBandInfoNode = psSpectralBandInfoNode->psNext;
1019
0
        }
1020
0
    }
1021
1022
    /* -------------------------------------------------------------------- */
1023
    /*      Initialize any PAM information.                                 */
1024
    /* -------------------------------------------------------------------- */
1025
0
    SetDescription(osMDFilename);
1026
0
    TryLoadXML();
1027
1028
    /* -------------------------------------------------------------------- */
1029
    /*      Check for overviews.                                            */
1030
    /* -------------------------------------------------------------------- */
1031
0
    oOvManager.Initialize(this, osMDFilename);
1032
1033
    // CID 163546 - poTileDS dereferenced above.
1034
    // coverity[leaked_storage]
1035
0
    return TRUE;
1036
1
}
1037
1038
/************************************************************************/
1039
/*               ReadImageInformation() DIMAP Version 2                 */
1040
/************************************************************************/
1041
1042
int DIMAPDataset::ReadImageInformation2()
1043
544
{
1044
544
    CPLXMLNode *psDoc = CPLGetXMLNode(psProductDim, "=Dimap_Document");
1045
544
    if (!psDoc)
1046
0
        psDoc = CPLGetXMLNode(psProductDim, "=PHR_DIMAP_Document");
1047
1048
544
    CPLXMLNode *psImageAttributes =
1049
544
        CPLGetXMLNode(psDoc, "Raster_Data.Raster_Dimensions");
1050
544
    if (psImageAttributes == nullptr)
1051
0
    {
1052
0
        CPLError(CE_Failure, CPLE_OpenFailed,
1053
0
                 "Failed to find <Raster_Dimensions> in document.");
1054
0
        return FALSE;
1055
0
    }
1056
1057
    /* -------------------------------------------------------------------- */
1058
    /*      Get overall image information.                                  */
1059
    /* -------------------------------------------------------------------- */
1060
1061
    /*
1062
        <Raster_Dimensions>
1063
           <NROWS>30</NROWS>
1064
           <NCOLS>20</NCOLS>
1065
           <NBANDS>4</NBANDS>
1066
           <Tile_Set>
1067
              <NTILES>2</NTILES>
1068
              <Regular_Tiling>
1069
                 <NTILES_SIZE nrows="20" ncols="20"/>
1070
                 <NTILES_COUNT ntiles_R="2" ntiles_C="1"/>
1071
                 <OVERLAP_ROW>0</OVERLAP_ROW>
1072
                 <OVERLAP_COL>0</OVERLAP_COL>
1073
              </Regular_Tiling>
1074
           </Tile_Set>
1075
        </Raster_Dimensions>
1076
      */
1077
1078
544
    const int l_nBands =
1079
544
        atoi(CPLGetXMLValue(psImageAttributes, "NBANDS", "-1"));
1080
544
    nRasterXSize = atoi(CPLGetXMLValue(psImageAttributes, "NCOLS", "-1"));
1081
544
    nRasterYSize = atoi(CPLGetXMLValue(psImageAttributes, "NROWS", "-1"));
1082
544
    if (nRasterXSize <= 0 || nRasterYSize <= 0)
1083
0
    {
1084
0
        CPLError(CE_Failure, CPLE_AppDefined,
1085
0
                 "Invalid NCOLS(=%d)/NROWS(=%d) value", nRasterXSize,
1086
0
                 nRasterYSize);
1087
0
        return FALSE;
1088
0
    }
1089
544
    int nTileWidth = atoi(CPLGetXMLValue(
1090
544
        psImageAttributes, "Tile_Set.Regular_Tiling.NTILES_SIZE.ncols", "-1"));
1091
544
    int nTileHeight = atoi(CPLGetXMLValue(
1092
544
        psImageAttributes, "Tile_Set.Regular_Tiling.NTILES_SIZE.nrows", "-1"));
1093
544
    int nOverlapRow = atoi(CPLGetXMLValue(
1094
544
        psImageAttributes, "Tile_Set.Regular_Tiling.OVERLAP_ROW", "-1"));
1095
544
    int nOverlapCol = atoi(CPLGetXMLValue(
1096
544
        psImageAttributes, "Tile_Set.Regular_Tiling.OVERLAP_COL", "-1"));
1097
544
    const int nBits =
1098
544
        atoi(CPLGetXMLValue(psDoc, "Raster_Data.Raster_Encoding.NBITS", "-1"));
1099
544
    CPLString osDataFormat =
1100
544
        CPLGetXMLValue(psDoc, "Raster_Data.Data_Access.DATA_FILE_FORMAT", "");
1101
544
    if (osDataFormat == "image/jp2")
1102
1
    {
1103
1
        SetMetadataItem("COMPRESSION", "JPEG2000", "IMAGE_STRUCTURE");
1104
1
    }
1105
1106
    // For VHR2020: SPECTRAL_PROCESSING = PAN, MS, MS-FS, PMS, PMS-N, PMS-X,
1107
    // PMS-FS
1108
544
    const CPLString osSpectralProcessing = CPLGetXMLValue(
1109
544
        psDoc, "Processing_Information.Product_Settings.SPECTRAL_PROCESSING",
1110
544
        "");
1111
544
    const bool bTwoDataFilesPerTile =
1112
544
        osSpectralProcessing == "MS-FS" || osSpectralProcessing == "PMS-FS";
1113
1114
    /* -------------------------------------------------------------------- */
1115
    /*      Get the name of the underlying file.                            */
1116
    /* -------------------------------------------------------------------- */
1117
1118
544
    CPLXMLNode *psDataFiles =
1119
544
        CPLGetXMLNode(psDoc, "Raster_Data.Data_Access.Data_Files");
1120
1121
    /*  <Data_Files>
1122
            <Data_File tile_R="1" tile_C="1">
1123
               <DATA_FILE_PATH href="IMG_foo_R1C1.TIF"/>
1124
            </Data_File>
1125
            <Data_File tile_R="2" tile_C="1">
1126
               <DATA_FILE_PATH href="IMG_foo_R2C1.TIF"/>
1127
            </Data_File>
1128
         </Data_Files>
1129
    */
1130
1131
544
    struct TileIdx
1132
544
    {
1133
544
        int nRow;
1134
544
        int nCol;
1135
544
        int nPart;  // typically 0.  But for VHR2020 0=RGB, 1=NED
1136
1137
544
        TileIdx(int nRowIn, int nColIn, int nPartIn = 0)
1138
20.1k
            : nRow(nRowIn), nCol(nColIn), nPart(nPartIn)
1139
20.1k
        {
1140
20.1k
        }
1141
1142
544
        bool operator<(const TileIdx &other) const
1143
61.8k
        {
1144
61.8k
            if (nRow < other.nRow)
1145
15.0k
                return true;
1146
46.7k
            if (nRow > other.nRow)
1147
2.85k
                return false;
1148
43.8k
            if (nCol < other.nCol)
1149
5.05k
                return true;
1150
38.8k
            if (nCol > other.nCol)
1151
389
                return false;
1152
38.4k
            return nPart < other.nPart;
1153
38.8k
        }
1154
544
    };
1155
1156
544
    std::map<TileIdx, CPLString> oMapTileIdxToName;
1157
544
    int nImageDSRow = 1, nImageDSCol = 1;
1158
544
    if (psDataFiles)
1159
544
    {
1160
544
        const CPLString osPath = CPLGetPathSafe(osDIMAPFilename);
1161
1.20k
        for (int nPart = 0; psDataFiles != nullptr;
1162
657
             psDataFiles = psDataFiles->psNext, nPart++)
1163
657
        {
1164
31.4k
            for (CPLXMLNode *psDataFile = psDataFiles->psChild; psDataFile;
1165
30.8k
                 psDataFile = psDataFile->psNext)
1166
30.8k
            {
1167
30.8k
                if (psDataFile->eType == CXT_Element &&
1168
30.8k
                    strcmp(psDataFile->pszValue, "Data_File") == 0)
1169
23.9k
                {
1170
23.9k
                    const char *pszR =
1171
23.9k
                        CPLGetXMLValue(psDataFile, "tile_R", nullptr);
1172
23.9k
                    const char *pszC =
1173
23.9k
                        CPLGetXMLValue(psDataFile, "tile_C", nullptr);
1174
23.9k
                    const char *pszHref = CPLGetXMLValue(
1175
23.9k
                        psDataFile, "DATA_FILE_PATH.href", nullptr);
1176
23.9k
                    if (pszR && pszC && pszHref)
1177
20.1k
                    {
1178
20.1k
                        int nRow = atoi(pszR);
1179
20.1k
                        int nCol = atoi(pszC);
1180
20.1k
                        if (nRow < 0 || nCol < 0)
1181
0
                        {
1182
0
                            return false;
1183
0
                        }
1184
20.1k
                        std::string osTileFilename(
1185
20.1k
                            CPLFormCIFilenameSafe(osPath, pszHref, nullptr));
1186
20.1k
                        if ((nRow == 1 && nCol == 1 && nPart == 0) ||
1187
20.1k
                            osImageDSFilename.empty())
1188
2.22k
                        {
1189
2.22k
                            osImageDSFilename = osTileFilename;
1190
2.22k
                            nImageDSRow = nRow;
1191
2.22k
                            nImageDSCol = nCol;
1192
2.22k
                        }
1193
20.1k
                        oMapTileIdxToName[TileIdx(nRow, nCol, nPart)] =
1194
20.1k
                            std::move(osTileFilename);
1195
20.1k
                    }
1196
23.9k
                }
1197
30.8k
            }
1198
657
        }
1199
544
        if (nOverlapRow > 0 || nOverlapCol > 0)
1200
0
        {
1201
0
            CPLError(CE_Warning, CPLE_AppDefined,
1202
0
                     "Overlap between tiles is not handled currently. "
1203
0
                     "Only taking into account top left tile");
1204
0
            oMapTileIdxToName.clear();
1205
0
            oMapTileIdxToName[TileIdx(1, 1)] = osImageDSFilename;
1206
0
        }
1207
544
    }
1208
0
    else
1209
0
    {
1210
0
        oMapTileIdxToName[TileIdx(1, 1)] = osImageDSFilename;
1211
0
    }
1212
1213
544
    if (osImageDSFilename.empty())
1214
0
    {
1215
0
        CPLError(CE_Failure, CPLE_OpenFailed,
1216
0
                 "Failed to find <DATA_FILE_PATH> in document.");
1217
0
        return FALSE;
1218
0
    }
1219
1220
    /* -------------------------------------------------------------------- */
1221
    /*      Try and open the file.                                          */
1222
    /* -------------------------------------------------------------------- */
1223
544
    auto poImageDS = std::unique_ptr<GDALDataset>(GDALDataset::Open(
1224
544
        osImageDSFilename, GDAL_OF_RASTER | GDAL_OF_VERBOSE_ERROR));
1225
544
    if (poImageDS == nullptr)
1226
172
    {
1227
172
        return FALSE;
1228
172
    }
1229
372
    if (bTwoDataFilesPerTile)
1230
11
    {
1231
11
        if (l_nBands != 6 || poImageDS->GetRasterCount() != 3)
1232
1
        {
1233
1
            CPLError(CE_Failure, CPLE_AppDefined, "Inconsistent band count");
1234
1
            return FALSE;
1235
1
        }
1236
11
    }
1237
361
    else if (poImageDS->GetRasterCount() != l_nBands)
1238
0
    {
1239
0
        CPLError(CE_Failure, CPLE_AppDefined, "Inconsistent band count");
1240
0
        return FALSE;
1241
0
    }
1242
1243
371
    if (nTileWidth > 0 && nTileHeight > 0)
1244
72
    {
1245
        // ok
1246
72
    }
1247
299
    else if (oMapTileIdxToName.size() == 1 ||
1248
299
             (bTwoDataFilesPerTile && oMapTileIdxToName.size() == 2))
1249
299
    {
1250
299
        nTileWidth = poImageDS->GetRasterXSize();
1251
299
        nTileHeight = poImageDS->GetRasterYSize();
1252
299
    }
1253
1254
371
    if (!(nTileWidth > 0 && nTileHeight > 0))
1255
0
    {
1256
0
        CPLError(CE_Failure, CPLE_AppDefined, "Cannot get tile dimension");
1257
0
        return FALSE;
1258
0
    }
1259
1260
    /* -------------------------------------------------------------------- */
1261
    /*      Create and initialize the corresponding VRT dataset used to     */
1262
    /*      manage the tiled data access.                                   */
1263
    /* -------------------------------------------------------------------- */
1264
371
    poVRTDS = new VRTDataset(nRasterXSize, nRasterYSize);
1265
1266
    // Don't try to write a VRT file.
1267
371
    poVRTDS->SetWritable(FALSE);
1268
1269
1.84k
    for (int iBand = 0; iBand < l_nBands; iBand++)
1270
1.47k
    {
1271
1.47k
        auto poSrcBandFirstImage = poImageDS->GetRasterBand(
1272
1.47k
            iBand < poImageDS->GetRasterCount() ? iBand + 1 : 1);
1273
1.47k
        CPLStringList aosAddBandOptions;
1274
1.47k
        int nSrcBlockXSize, nSrcBlockYSize;
1275
1.47k
        poSrcBandFirstImage->GetBlockSize(&nSrcBlockXSize, &nSrcBlockYSize);
1276
1.47k
        if (oMapTileIdxToName.size() == 1 ||
1277
1.47k
            ((nTileWidth % nSrcBlockXSize) == 0 &&
1278
300
             (nTileHeight % nSrcBlockYSize) == 0))
1279
1.24k
        {
1280
1.24k
            aosAddBandOptions.SetNameValue("BLOCKXSIZE",
1281
1.24k
                                           CPLSPrintf("%d", nSrcBlockXSize));
1282
1.24k
            aosAddBandOptions.SetNameValue("BLOCKYSIZE",
1283
1.24k
                                           CPLSPrintf("%d", nSrcBlockYSize));
1284
1.24k
        }
1285
1.47k
        poVRTDS->AddBand(poSrcBandFirstImage->GetRasterDataType(),
1286
1.47k
                         aosAddBandOptions.List());
1287
1288
1.47k
        VRTSourcedRasterBand *poVRTBand =
1289
1.47k
            reinterpret_cast<VRTSourcedRasterBand *>(
1290
1.47k
                poVRTDS->GetRasterBand(iBand + 1));
1291
1.47k
        if (nBits > 0 && nBits != 8 && nBits != 16)
1292
886
        {
1293
886
            poVRTBand->SetMetadataItem("NBITS", CPLSPrintf("%d", nBits),
1294
886
                                       "IMAGE_STRUCTURE");
1295
886
        }
1296
1297
1.47k
        for (const auto &oTileIdxNameTuple : oMapTileIdxToName)
1298
1.77k
        {
1299
1.77k
            const int nRow = oTileIdxNameTuple.first.nRow;
1300
1.77k
            const int nCol = oTileIdxNameTuple.first.nCol;
1301
1.77k
            if (static_cast<int64_t>(nRow - 1) * nTileHeight < nRasterYSize &&
1302
1.77k
                static_cast<int64_t>(nCol - 1) * nTileWidth < nRasterXSize)
1303
1.74k
            {
1304
1.74k
                int nSrcBand;
1305
1.74k
                if (bTwoDataFilesPerTile)
1306
114
                {
1307
114
                    const int nPart = oTileIdxNameTuple.first.nPart;
1308
114
                    if (nPart == 0 && iBand < 3)
1309
27
                    {
1310
27
                        nSrcBand = iBand + 1;
1311
27
                    }
1312
87
                    else if (nPart == 1 && iBand >= 3)
1313
30
                    {
1314
30
                        nSrcBand = iBand + 1 - 3;
1315
30
                    }
1316
57
                    else
1317
57
                    {
1318
57
                        continue;
1319
57
                    }
1320
114
                }
1321
1.62k
                else
1322
1.62k
                {
1323
1.62k
                    nSrcBand = iBand + 1;
1324
1.62k
                }
1325
1326
1.68k
                int nHeight = nTileHeight;
1327
1.68k
                if (static_cast<int64_t>(nRow) * nTileHeight > nRasterYSize)
1328
1.35k
                {
1329
1.35k
                    nHeight = nRasterYSize - (nRow - 1) * nTileHeight;
1330
1.35k
                }
1331
1.68k
                int nWidth = nTileWidth;
1332
1.68k
                if (static_cast<int64_t>(nCol) * nTileWidth > nRasterXSize)
1333
1.57k
                {
1334
1.57k
                    nWidth = nRasterXSize - (nCol - 1) * nTileWidth;
1335
1.57k
                }
1336
1337
1.68k
                poVRTBand->AddSimpleSource(
1338
1.68k
                    oTileIdxNameTuple.second, nSrcBand, 0, 0, nWidth, nHeight,
1339
1.68k
                    (nCol - 1) * nTileWidth, (nRow - 1) * nTileHeight, nWidth,
1340
1.68k
                    nHeight);
1341
1.68k
            }
1342
1.77k
        }
1343
1.47k
    }
1344
1345
    /* -------------------------------------------------------------------- */
1346
    /*      Expose Overviews if available                                   */
1347
    /* -------------------------------------------------------------------- */
1348
371
    auto poSrcBandFirstImage = poImageDS->GetRasterBand(1);
1349
371
    const int nSrcOverviews =
1350
371
        std::min(30, poSrcBandFirstImage->GetOverviewCount());
1351
371
    if (nSrcOverviews > 0)
1352
1
    {
1353
1
        CPLConfigOptionSetter oSetter("VRT_VIRTUAL_OVERVIEWS", "YES", false);
1354
1
        std::unique_ptr<int[]> ovrLevels(new int[nSrcOverviews]);
1355
1
        int iLvl = 1;
1356
2
        for (int i = 0; i < nSrcOverviews; i++)
1357
1
        {
1358
1
            iLvl *= 2;
1359
1
            ovrLevels[i] = iLvl;
1360
1
        }
1361
1
        poVRTDS->IBuildOverviews("average", nSrcOverviews, ovrLevels.get(), 0,
1362
1
                                 nullptr, nullptr, nullptr, nullptr);
1363
1
    }
1364
1365
#ifdef DEBUG_VERBOSE
1366
    CPLDebug("DIMAP", "VRT XML: %s", poVRTDS->GetMetadata("xml:VRT")[0]);
1367
#endif
1368
1369
    /* -------------------------------------------------------------------- */
1370
    /*      Create band information objects.                                */
1371
    /* -------------------------------------------------------------------- */
1372
1.84k
    for (int iBand = 1; iBand <= poVRTDS->GetRasterCount(); iBand++)
1373
1.47k
    {
1374
1.47k
        GDALRasterBand *poBand = new DIMAPRasterBand(
1375
1.47k
            this, iBand,
1376
1.47k
            static_cast<VRTSourcedRasterBand *>(poVRTDS->GetRasterBand(iBand)));
1377
1.47k
        if (nBits > 0 && nBits != 8 && nBits != 16)
1378
886
        {
1379
886
            poBand->SetMetadataItem("NBITS", CPLSPrintf("%d", nBits),
1380
886
                                    "IMAGE_STRUCTURE");
1381
886
        }
1382
1.47k
        if (bTwoDataFilesPerTile)
1383
60
        {
1384
60
            switch (iBand)
1385
60
            {
1386
10
                case 1:
1387
10
                {
1388
10
                    poBand->SetColorInterpretation(GCI_RedBand);
1389
10
                    poBand->SetDescription("Red");
1390
10
                    break;
1391
0
                }
1392
10
                case 2:
1393
10
                {
1394
10
                    poBand->SetColorInterpretation(GCI_GreenBand);
1395
10
                    poBand->SetDescription("Green");
1396
10
                    break;
1397
0
                }
1398
10
                case 3:
1399
10
                {
1400
10
                    poBand->SetColorInterpretation(GCI_BlueBand);
1401
10
                    poBand->SetDescription("Blue");
1402
10
                    break;
1403
0
                }
1404
10
                case 4:
1405
10
                {
1406
10
                    poBand->SetColorInterpretation(GCI_NIRBand);
1407
10
                    poBand->SetDescription("NIR");
1408
10
                    break;
1409
0
                }
1410
10
                case 5:
1411
10
                {
1412
10
                    poBand->SetColorInterpretation(GCI_RedEdgeBand);
1413
10
                    poBand->SetDescription("Red Edge");
1414
10
                    break;
1415
0
                }
1416
10
                case 6:
1417
10
                {
1418
10
                    poBand->SetColorInterpretation(GCI_CoastalBand);
1419
10
                    poBand->SetDescription("Deep Blue");
1420
10
                    break;
1421
0
                }
1422
0
                default:
1423
0
                    break;
1424
60
            }
1425
60
        }
1426
1.41k
        else if (l_nBands == 1 && osSpectralProcessing == "PAN")
1427
0
        {
1428
0
            poBand->SetColorInterpretation(GCI_PanBand);
1429
0
            poBand->SetDescription("Panchromatic");
1430
0
        }
1431
1.47k
        SetBand(iBand, poBand);
1432
1.47k
    }
1433
1434
    /* -------------------------------------------------------------------- */
1435
    /*      Try to collect simple insertion point.                          */
1436
    /* -------------------------------------------------------------------- */
1437
371
    CPLXMLNode *psGeoLoc =
1438
371
        CPLGetXMLNode(psDoc, "Geoposition.Geoposition_Insert");
1439
1440
371
    if (psGeoLoc != nullptr)
1441
10
    {
1442
10
        bHaveGeoTransform = TRUE;
1443
10
        adfGeoTransform[0] = CPLAtof(CPLGetXMLValue(psGeoLoc, "ULXMAP", "0"));
1444
10
        adfGeoTransform[1] = CPLAtof(CPLGetXMLValue(psGeoLoc, "XDIM", "0"));
1445
10
        adfGeoTransform[2] = 0.0;
1446
10
        adfGeoTransform[3] = CPLAtof(CPLGetXMLValue(psGeoLoc, "ULYMAP", "0"));
1447
10
        adfGeoTransform[4] = 0.0;
1448
10
        adfGeoTransform[5] = -CPLAtof(CPLGetXMLValue(psGeoLoc, "YDIM", "0"));
1449
10
    }
1450
361
    else
1451
361
    {
1452
        // Try to get geotransform from underlying raster,
1453
        // but make sure it is a real geotransform.
1454
361
        if (poImageDS->GetGeoTransform(adfGeoTransform) == CE_None &&
1455
361
            !(adfGeoTransform[0] <= 1.5 && fabs(adfGeoTransform[3]) <= 1.5))
1456
52
        {
1457
52
            bHaveGeoTransform = TRUE;
1458
            // fix up the origin if we did not get the geotransform from the
1459
            // top-left tile
1460
52
            adfGeoTransform[0] -=
1461
52
                (nImageDSCol - 1) * adfGeoTransform[1] * nTileWidth +
1462
52
                (nImageDSRow - 1) * adfGeoTransform[2] * nTileHeight;
1463
52
            adfGeoTransform[3] -=
1464
52
                (nImageDSCol - 1) * adfGeoTransform[4] * nTileWidth +
1465
52
                (nImageDSRow - 1) * adfGeoTransform[5] * nTileHeight;
1466
52
        }
1467
361
    }
1468
1469
    /* -------------------------------------------------------------------- */
1470
    /*      Collect the CRS.  For now we look only for EPSG codes.          */
1471
    /* -------------------------------------------------------------------- */
1472
371
    const char *pszSRS = CPLGetXMLValue(
1473
371
        psDoc, "Coordinate_Reference_System.Projected_CRS.PROJECTED_CRS_CODE",
1474
371
        nullptr);
1475
371
    if (pszSRS == nullptr)
1476
371
        pszSRS = CPLGetXMLValue(
1477
371
            psDoc, "Coordinate_Reference_System.Geodetic_CRS.GEODETIC_CRS_CODE",
1478
371
            nullptr);
1479
1480
371
    if (pszSRS != nullptr)
1481
357
    {
1482
357
        if (bHaveGeoTransform)
1483
57
        {
1484
57
            OGRSpatialReference &oSRS = m_oSRS;
1485
57
            oSRS.SetFromUserInput(
1486
57
                pszSRS,
1487
57
                OGRSpatialReference::SET_FROM_USER_INPUT_LIMITATIONS_get());
1488
57
        }
1489
357
    }
1490
14
    else
1491
14
    {
1492
        // Check underlying raster for SRS. We have cases where
1493
        // HORIZONTAL_CS_CODE is empty and the underlying raster
1494
        // is georeferenced (rprinceley).
1495
14
        const auto poSRS = poImageDS->GetSpatialRef();
1496
14
        double adfGTTmp[6];
1497
14
        if (poSRS && poImageDS->GetGeoTransform(adfGTTmp) == CE_None)
1498
5
        {
1499
5
            m_oSRS = *poSRS;
1500
5
        }
1501
14
    }
1502
1503
    /* -------------------------------------------------------------------- */
1504
    /*      Translate other metadata of interest: DIM_<product_name>.XML    */
1505
    /* -------------------------------------------------------------------- */
1506
1507
371
    static const char *const apszMetadataTranslationDim[] = {
1508
371
        "Product_Information.Delivery_Identification",
1509
371
        "DATASET_",
1510
371
        "Product_Information.Producer_Information",
1511
371
        "DATASET_",
1512
371
        "Dataset_Sources.Source_Identification.Strip_Source",
1513
371
        "",
1514
371
        "Processing_Information.Production_Facility",
1515
371
        "FACILITY_",
1516
371
        "Processing_Information.Product_Settings",
1517
371
        "",
1518
371
        "Processing_Information.Product_Settings.Geometric_Settings",
1519
371
        "GEOMETRIC_",
1520
371
        "Processing_Information.Product_Settings.Radiometric_Settings",
1521
371
        "RADIOMETRIC_",
1522
371
        "Quality_Assessment.Imaging_Quality_Measurement",
1523
371
        "CLOUDCOVER_",
1524
371
        nullptr,
1525
371
        nullptr};
1526
1527
371
    SetMetadataFromXML(psProductDim, apszMetadataTranslationDim);
1528
1529
    /* -------------------------------------------------------------------- */
1530
    /*      Translate other metadata of interest: STRIP_<product_name>.XML    */
1531
    /* -------------------------------------------------------------------- */
1532
1533
371
    static const char *const apszMetadataTranslationStrip[] = {
1534
371
        "Catalog.Full_Strip.Notations.Cloud_And_Quality_Notation."
1535
371
        "Data_Strip_Notation",
1536
371
        "CLOUDCOVER_",
1537
371
        "Acquisition_Configuration.Platform_Configuration."
1538
371
        "Ephemeris_Configuration",
1539
371
        "EPHEMERIS_",
1540
371
        nullptr,
1541
371
        nullptr};
1542
1543
371
    if (psProductStrip != nullptr)
1544
0
        SetMetadataFromXML(psProductStrip, apszMetadataTranslationStrip);
1545
1546
371
    if (!osRPCFilename.empty())
1547
53
    {
1548
53
        GDALMDReaderPleiades *poReader =
1549
53
            GDALMDReaderPleiades::CreateReaderForRPC(osRPCFilename);
1550
53
        char **papszRPC = poReader->LoadRPCXmlFile(psDoc);
1551
53
        delete poReader;
1552
53
        if (papszRPC)
1553
41
            SetMetadata(papszRPC, "RPC");
1554
53
        CSLDestroy(papszRPC);
1555
53
    }
1556
1557
371
    CPLXMLNode *psLocatedUseAreaNode =
1558
371
        CPLGetXMLNode(psDoc, "Geometric_Data.Use_Area");
1559
371
    if (psLocatedUseAreaNode != nullptr)
1560
10
    {
1561
10
        CPLXMLNode *psLocatedGeometricValuesNode =
1562
10
            psLocatedUseAreaNode->psChild;
1563
50
        while (psLocatedGeometricValuesNode != nullptr)
1564
50
        {
1565
50
            CPLXMLNode *psLocationType =
1566
50
                CPLGetXMLNode(psLocatedGeometricValuesNode, "LOCATION_TYPE");
1567
50
            if (psLocationType == nullptr ||
1568
50
                psLocationType->psChild == nullptr ||
1569
50
                !EQUAL(psLocationType->psChild->pszValue, "center"))
1570
40
            {
1571
40
                psLocatedGeometricValuesNode =
1572
40
                    psLocatedGeometricValuesNode->psNext;
1573
40
                continue;
1574
40
            }
1575
10
            static const char *const apszLGVTranslationDim[] = {
1576
10
                "SATELLITE_ALTITUDE",
1577
10
                "",
1578
10
                "Acquisition_Angles",
1579
10
                "",
1580
10
                "Solar_Incidences",
1581
10
                "",
1582
10
                "Ground_Sample_Distance",
1583
10
                "",
1584
10
                nullptr,
1585
10
                nullptr};
1586
1587
10
            SetMetadataFromXML(psLocatedGeometricValuesNode,
1588
10
                               apszLGVTranslationDim, false);
1589
10
            break;
1590
50
        }
1591
10
    }
1592
1593
    /* -------------------------------------------------------------------- */
1594
    /*      Set Band metadata from the <Band_Radiance> and                  */
1595
    /*                                <Band_Spectral_Range> content         */
1596
    /* -------------------------------------------------------------------- */
1597
1598
371
    CPLXMLNode *psImageInterpretationNode = CPLGetXMLNode(
1599
371
        psDoc,
1600
371
        "Radiometric_Data.Radiometric_Calibration.Instrument_Calibration."
1601
371
        "Band_Measurement_List");
1602
371
    if (psImageInterpretationNode != nullptr)
1603
365
    {
1604
365
        CPLXMLNode *psSpectralBandInfoNode = psImageInterpretationNode->psChild;
1605
1.76k
        while (psSpectralBandInfoNode != nullptr)
1606
1.40k
        {
1607
1.40k
            if (psSpectralBandInfoNode->eType == CXT_Element &&
1608
1.40k
                (EQUAL(psSpectralBandInfoNode->pszValue, "Band_Radiance") ||
1609
1.24k
                 EQUAL(psSpectralBandInfoNode->pszValue,
1610
1.24k
                       "Band_Spectral_Range") ||
1611
1.24k
                 EQUAL(psSpectralBandInfoNode->pszValue,
1612
1.24k
                       "Band_Solar_Irradiance")))
1613
1.12k
            {
1614
1.12k
                CPLString osName;
1615
1616
1.12k
                if (EQUAL(psSpectralBandInfoNode->pszValue, "Band_Radiance"))
1617
276
                    osName = "RADIANCE_";
1618
844
                else if (EQUAL(psSpectralBandInfoNode->pszValue,
1619
844
                               "Band_Spectral_Range"))
1620
568
                    osName = "SPECTRAL_RANGE_";
1621
276
                else if (EQUAL(psSpectralBandInfoNode->pszValue,
1622
276
                               "Band_Solar_Irradiance"))
1623
276
                    osName = "SOLAR_IRRADIANCE_";
1624
1625
1.12k
                CPLXMLNode *psTag = psSpectralBandInfoNode->psChild;
1626
1.12k
                int nBandIndex = 0;
1627
34.4k
                while (psTag != nullptr)
1628
33.3k
                {
1629
33.3k
                    if (psTag->eType == CXT_Element &&
1630
33.3k
                        psTag->psChild != nullptr &&
1631
33.3k
                        psTag->pszValue != nullptr &&
1632
33.3k
                        (psTag->psChild->eType == CXT_Text ||
1633
18.6k
                         EQUAL(psTag->pszValue, "FWHM")))
1634
18.1k
                    {
1635
18.1k
                        if (EQUAL(psTag->pszValue, "BAND_ID"))
1636
1.13k
                        {
1637
1.13k
                            nBandIndex = 0;
1638
1.13k
                            if (EQUAL(psTag->psChild->pszValue, "P") ||
1639
1.13k
                                EQUAL(psTag->psChild->pszValue, "PAN") ||
1640
1.13k
                                EQUAL(psTag->psChild->pszValue, "B0") ||
1641
1.13k
                                EQUAL(psTag->psChild->pszValue, "R"))
1642
514
                                nBandIndex = 1;
1643
616
                            else if (EQUAL(psTag->psChild->pszValue, "B1") ||
1644
616
                                     EQUAL(psTag->psChild->pszValue, "G"))
1645
185
                                nBandIndex = 2;
1646
431
                            else if (EQUAL(psTag->psChild->pszValue, "B2") ||
1647
431
                                     EQUAL(psTag->psChild->pszValue, "B"))
1648
167
                                nBandIndex = 3;
1649
264
                            else if (EQUAL(psTag->psChild->pszValue, "B3") ||
1650
264
                                     EQUAL(psTag->psChild->pszValue, "NIR"))
1651
145
                                nBandIndex = 4;
1652
119
                            else if (EQUAL(psTag->psChild->pszValue, "RE"))
1653
30
                                nBandIndex = 5;
1654
89
                            else if (EQUAL(psTag->psChild->pszValue, "DB"))
1655
30
                                nBandIndex = 6;
1656
1657
1.13k
                            if (nBandIndex <= 0 ||
1658
1.13k
                                nBandIndex > GetRasterCount())
1659
69
                            {
1660
69
                                CPLError(CE_Warning, CPLE_AppDefined,
1661
69
                                         "Bad BAND_ID value : %s",
1662
69
                                         psTag->psChild->pszValue);
1663
69
                                nBandIndex = 0;
1664
69
                            }
1665
1.13k
                        }
1666
17.0k
                        else if (nBandIndex >= 1)
1667
16.6k
                        {
1668
16.6k
                            CPLString osMDName = osName;
1669
16.6k
                            osMDName += psTag->pszValue;
1670
1671
16.6k
                            auto poBand = GetRasterBand(nBandIndex);
1672
16.6k
                            if (EQUAL(psTag->pszValue, "FWHM"))
1673
60
                            {
1674
60
                                if (const char *pszMIN =
1675
60
                                        CPLGetXMLValue(psTag, "MIN", nullptr))
1676
60
                                    poBand->SetMetadataItem(
1677
60
                                        (osMDName + "_MIN").c_str(), pszMIN);
1678
60
                                if (const char *pszMAX =
1679
60
                                        CPLGetXMLValue(psTag, "MAX", nullptr))
1680
60
                                    poBand->SetMetadataItem(
1681
60
                                        (osMDName + "_MAX").c_str(), pszMAX);
1682
60
                            }
1683
16.5k
                            else
1684
16.5k
                            {
1685
16.5k
                                poBand->SetMetadataItem(
1686
16.5k
                                    osMDName, psTag->psChild->pszValue);
1687
16.5k
                            }
1688
16.6k
                        }
1689
18.1k
                    }
1690
33.3k
                    psTag = psTag->psNext;
1691
33.3k
                }
1692
1.12k
            }
1693
1.40k
            psSpectralBandInfoNode = psSpectralBandInfoNode->psNext;
1694
1.40k
        }
1695
365
    }
1696
1697
    // Fill raster band IMAGERY metadata domain from FWHM metadata.
1698
1.84k
    for (int i = 1; i <= nBands; ++i)
1699
1.47k
    {
1700
1.47k
        auto poBand = GetRasterBand(i);
1701
1.47k
        const char *SPECTRAL_RANGE_MEASURE_UNIT =
1702
1.47k
            poBand->GetMetadataItem("SPECTRAL_RANGE_MEASURE_UNIT");
1703
1.47k
        const char *SPECTRAL_RANGE_FWHM_MIN =
1704
1.47k
            poBand->GetMetadataItem("SPECTRAL_RANGE_FWHM_MIN");
1705
1.47k
        const char *SPECTRAL_RANGE_FWHM_MAX =
1706
1.47k
            poBand->GetMetadataItem("SPECTRAL_RANGE_FWHM_MAX");
1707
1.47k
        if (SPECTRAL_RANGE_MEASURE_UNIT && SPECTRAL_RANGE_FWHM_MIN &&
1708
1.47k
            SPECTRAL_RANGE_FWHM_MAX &&
1709
1.47k
            (EQUAL(SPECTRAL_RANGE_MEASURE_UNIT, "nanometer") ||
1710
60
             EQUAL(SPECTRAL_RANGE_MEASURE_UNIT, "micrometer")))
1711
60
        {
1712
60
            const double dfFactorToMicrometer =
1713
60
                EQUAL(SPECTRAL_RANGE_MEASURE_UNIT, "nanometer") ? 1e-3 : 1.0;
1714
60
            const double dfMin =
1715
60
                CPLAtof(SPECTRAL_RANGE_FWHM_MIN) * dfFactorToMicrometer;
1716
60
            const double dfMax =
1717
60
                CPLAtof(SPECTRAL_RANGE_FWHM_MAX) * dfFactorToMicrometer;
1718
60
            poBand->SetMetadataItem("CENTRAL_WAVELENGTH_UM",
1719
60
                                    CPLSPrintf("%.3f", (dfMin + dfMax) / 2),
1720
60
                                    "IMAGERY");
1721
60
            poBand->SetMetadataItem(
1722
60
                "FWHM_UM", CPLSPrintf("%.3f", dfMax - dfMin), "IMAGERY");
1723
60
        }
1724
1.47k
    }
1725
1726
    /* -------------------------------------------------------------------- */
1727
    /*      Initialize any PAM information.                                 */
1728
    /* -------------------------------------------------------------------- */
1729
371
    SetDescription(osMDFilename);
1730
371
    TryLoadXML();
1731
1732
    /* -------------------------------------------------------------------- */
1733
    /*      Check for overviews.                                            */
1734
    /* -------------------------------------------------------------------- */
1735
371
    oOvManager.Initialize(this, osMDFilename);
1736
1737
371
    return TRUE;
1738
371
}
1739
1740
/************************************************************************/
1741
/*                          SetMetadataFromXML()                        */
1742
/************************************************************************/
1743
1744
void DIMAPDataset::SetMetadataFromXML(
1745
    CPLXMLNode *psProductIn, const char *const apszMetadataTranslation[],
1746
    bool bKeysFromRoot)
1747
381
{
1748
381
    CPLXMLNode *psDoc = psProductIn;
1749
381
    if (bKeysFromRoot)
1750
371
    {
1751
371
        psDoc = CPLGetXMLNode(psProductIn, "=Dimap_Document");
1752
371
        if (psDoc == nullptr)
1753
0
        {
1754
0
            psDoc = CPLGetXMLNode(psProductIn, "=PHR_DIMAP_Document");
1755
0
        }
1756
371
    }
1757
1758
381
    bool bWarnedDiscarding = false;
1759
1760
3.38k
    for (int iTrItem = 0; apszMetadataTranslation[iTrItem] != nullptr;
1761
3.00k
         iTrItem += 2)
1762
3.00k
    {
1763
3.00k
        CPLXMLNode *psParent =
1764
3.00k
            CPLGetXMLNode(psDoc, apszMetadataTranslation[iTrItem]);
1765
1766
3.00k
        if (psParent == nullptr)
1767
64
            continue;
1768
1769
        // Logic to support directly access a name/value entry
1770
2.94k
        if (psParent->psChild != nullptr &&
1771
2.94k
            psParent->psChild->eType == CXT_Text)
1772
1.00k
        {
1773
1.00k
            CPLString osName = apszMetadataTranslation[iTrItem + 1];
1774
1.00k
            osName += apszMetadataTranslation[iTrItem];
1775
            // Limit size to avoid perf issues when inserting
1776
            // in metadata list
1777
1.00k
            if (osName.size() < 128)
1778
1.00k
                SetMetadataItem(osName, psParent->psChild->pszValue);
1779
0
            else if (!bWarnedDiscarding)
1780
0
            {
1781
0
                bWarnedDiscarding = true;
1782
0
                CPLDebug("DIMAP", "Discarding too long metadata item");
1783
0
            }
1784
1.00k
            continue;
1785
1.00k
        }
1786
1787
        // Logic to support a parent element with many name/values.
1788
1.93k
        CPLXMLNode *psTarget = psParent->psChild;
1789
59.9k
        for (; psTarget != nullptr && psTarget != psParent;
1790
58.0k
             psTarget = psTarget->psNext)
1791
58.0k
        {
1792
58.0k
            if (psTarget->eType == CXT_Element && psTarget->psChild != nullptr)
1793
31.9k
            {
1794
31.9k
                CPLString osName = apszMetadataTranslation[iTrItem + 1];
1795
1796
31.9k
                if (psTarget->psChild->eType == CXT_Text)
1797
29.9k
                {
1798
29.9k
                    osName += psTarget->pszValue;
1799
                    // Limit size to avoid perf issues when inserting
1800
                    // in metadata list
1801
29.9k
                    if (osName.size() < 128)
1802
29.8k
                        SetMetadataItem(osName, psTarget->psChild->pszValue);
1803
99
                    else if (!bWarnedDiscarding)
1804
15
                    {
1805
15
                        bWarnedDiscarding = true;
1806
15
                        CPLDebug("DIMAP", "Discarding too long metadata item");
1807
15
                    }
1808
29.9k
                }
1809
1.98k
                else if (psTarget->psChild->eType == CXT_Attribute)
1810
795
                {
1811
                    // find the tag value, at the end of the attributes.
1812
795
                    for (CPLXMLNode *psNode = psTarget->psChild;
1813
45.2k
                         psNode != nullptr; psNode = psNode->psNext)
1814
44.4k
                    {
1815
44.4k
                        if (psNode->eType == CXT_Attribute)
1816
875
                            continue;
1817
43.6k
                        else if (psNode->eType == CXT_Text)
1818
43.6k
                        {
1819
43.6k
                            osName += psTarget->pszValue;
1820
                            // Limit size to avoid perf issues when inserting
1821
                            // in metadata list
1822
43.6k
                            if (osName.size() < 128)
1823
18.5k
                                SetMetadataItem(osName, psNode->pszValue);
1824
25.0k
                            else if (!bWarnedDiscarding)
1825
2
                            {
1826
2
                                bWarnedDiscarding = true;
1827
2
                                CPLDebug("DIMAP",
1828
2
                                         "Discarding too long metadata item");
1829
2
                            }
1830
43.6k
                        }
1831
44.4k
                    }
1832
795
                }
1833
31.9k
            }
1834
58.0k
        }
1835
1.93k
    }
1836
381
}
1837
1838
/************************************************************************/
1839
/*                            GetGCPCount()                             */
1840
/************************************************************************/
1841
1842
int DIMAPDataset::GetGCPCount()
1843
1844
367
{
1845
367
    return nGCPCount;
1846
367
}
1847
1848
/************************************************************************/
1849
/*                          GetGCPSpatialRef()                          */
1850
/************************************************************************/
1851
1852
const OGRSpatialReference *DIMAPDataset::GetGCPSpatialRef() const
1853
1854
367
{
1855
367
    return m_oGCPSRS.IsEmpty() ? nullptr : &m_oGCPSRS;
1856
367
}
1857
1858
/************************************************************************/
1859
/*                               GetGCPs()                              */
1860
/************************************************************************/
1861
1862
const GDAL_GCP *DIMAPDataset::GetGCPs()
1863
1864
367
{
1865
367
    return pasGCPList;
1866
367
}
1867
1868
/************************************************************************/
1869
/*                         GDALRegister_DIMAP()                         */
1870
/************************************************************************/
1871
1872
void GDALRegister_DIMAP()
1873
1874
26
{
1875
26
    if (GDALGetDriverByName("DIMAP") != nullptr)
1876
0
        return;
1877
1878
26
    GDALDriver *poDriver = new GDALDriver();
1879
1880
26
    poDriver->SetDescription("DIMAP");
1881
26
    poDriver->SetMetadataItem(GDAL_DCAP_RASTER, "YES");
1882
26
    poDriver->SetMetadataItem(GDAL_DMD_LONGNAME, "SPOT DIMAP");
1883
26
    poDriver->SetMetadataItem(GDAL_DMD_HELPTOPIC, "drivers/raster/dimap.html");
1884
26
    poDriver->SetMetadataItem(GDAL_DCAP_VIRTUALIO, "YES");
1885
26
    poDriver->SetMetadataItem(GDAL_DMD_SUBDATASETS, "YES");
1886
1887
26
    poDriver->pfnOpen = DIMAPDataset::Open;
1888
26
    poDriver->pfnIdentify = DIMAPDataset::Identify;
1889
1890
26
    GetGDALDriverManager()->RegisterDriver(poDriver);
1891
26
}