Coverage Report

Created: 2026-06-30 08:33

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