Coverage Report

Created: 2026-02-14 09:00

next uncovered line (L), next uncovered region (R), next uncovered branch (B)
/src/gdal/frmts/tsx/tsxdataset.cpp
Line
Count
Source
1
/******************************************************************************
2
 *
3
 * Project:     TerraSAR-X XML Product Support
4
 * Purpose:     Support for TerraSAR-X XML Metadata files
5
 * Author:      Philippe Vachon <philippe@cowpig.ca>
6
 * Description: This driver adds support for reading metadata and georef data
7
 *              associated with TerraSAR-X products.
8
 *
9
 ******************************************************************************
10
 * Copyright (c) 2007, Philippe Vachon <philippe@cowpig.ca>
11
 * Copyright (c) 2009-2012, 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 "gdal_driver.h"
20
#include "gdal_drivermanager.h"
21
#include "gdal_openinfo.h"
22
#include "gdal_cpp_functions.h"
23
#include "ogr_spatialref.h"
24
25
0
#define MAX_GCPS 5000  // this should be more than enough ground control points
26
27
namespace gdal::TSX
28
{
29
enum ePolarization
30
{
31
    HH = 0,
32
    HV,
33
    VH,
34
    VV
35
};
36
37
enum eProductType
38
{
39
    eSSC = 0,
40
    eMGD,
41
    eEEC,
42
    eGEC,
43
    eUnknown
44
};
45
}  // namespace gdal::TSX
46
47
using namespace gdal::TSX;
48
49
/************************************************************************/
50
/*                           Helper Functions                           */
51
/************************************************************************/
52
53
/* GetFilePath: return a relative path to a file within an XML node.
54
 * Returns Null on failure
55
 */
56
static CPLString GetFilePath(CPLXMLNode *psXMLNode, const char **pszNodeType)
57
0
{
58
0
    const char *pszDirectory =
59
0
        CPLGetXMLValue(psXMLNode, "file.location.path", "");
60
0
    const char *pszFilename =
61
0
        CPLGetXMLValue(psXMLNode, "file.location.filename", "");
62
0
    *pszNodeType = CPLGetXMLValue(psXMLNode, "type", " ");
63
64
0
    if (pszDirectory == nullptr || pszFilename == nullptr)
65
0
    {
66
0
        return "";
67
0
    }
68
69
0
    return CPLString(pszDirectory) + '/' + pszFilename;
70
0
}
71
72
/************************************************************************/
73
/* ==================================================================== */
74
/*                                TSXDataset                                 */
75
/* ==================================================================== */
76
/************************************************************************/
77
78
class TSXDataset final : public GDALPamDataset
79
{
80
    int nGCPCount;
81
    GDAL_GCP *pasGCPList;
82
83
    OGRSpatialReference m_oGCPSRS{};
84
85
    OGRSpatialReference m_oSRS{};
86
    GDALGeoTransform m_gt{};
87
    bool bHaveGeoTransform;
88
89
    eProductType nProduct;
90
91
  public:
92
    TSXDataset();
93
    ~TSXDataset() override;
94
95
    int GetGCPCount() override;
96
    const OGRSpatialReference *GetGCPSpatialRef() const override;
97
    const GDAL_GCP *GetGCPs() override;
98
99
    CPLErr GetGeoTransform(GDALGeoTransform &gt) const override;
100
    const OGRSpatialReference *GetSpatialRef() const override;
101
102
    static GDALDataset *Open(GDALOpenInfo *poOpenInfo);
103
    static int Identify(GDALOpenInfo *poOpenInfo);
104
105
  private:
106
    bool getGCPsFromGEOREF_XML(const char *pszGeorefFilename);
107
};
108
109
/************************************************************************/
110
/* ==================================================================== */
111
/*                                TSXRasterBand                           */
112
/* ==================================================================== */
113
/************************************************************************/
114
115
class TSXRasterBand final : public GDALPamRasterBand
116
{
117
    GDALDataset *poBand;
118
    ePolarization ePol;
119
120
  public:
121
    TSXRasterBand(TSXDataset *poDSIn, GDALDataType eDataType,
122
                  ePolarization ePol, GDALDataset *poBand);
123
    ~TSXRasterBand() override;
124
125
    CPLErr IReadBlock(int nBlockXOff, int nBlockYOff, void *pImage) override;
126
127
    static GDALDataset *Open(GDALOpenInfo *poOpenInfo);
128
};
129
130
/************************************************************************/
131
/*                            TSXRasterBand                             */
132
/************************************************************************/
133
134
TSXRasterBand::TSXRasterBand(TSXDataset *poDSIn, GDALDataType eDataTypeIn,
135
                             ePolarization ePolIn, GDALDataset *poBandIn)
136
0
    : poBand(poBandIn), ePol(ePolIn)
137
0
{
138
0
    poDS = poDSIn;
139
0
    eDataType = eDataTypeIn;
140
141
0
    switch (ePol)
142
0
    {
143
0
        case HH:
144
0
            SetMetadataItem("POLARIMETRIC_INTERP", "HH");
145
0
            break;
146
0
        case HV:
147
0
            SetMetadataItem("POLARIMETRIC_INTERP", "HV");
148
0
            break;
149
0
        case VH:
150
0
            SetMetadataItem("POLARIMETRIC_INTERP", "VH");
151
0
            break;
152
0
        case VV:
153
0
            SetMetadataItem("POLARIMETRIC_INTERP", "VV");
154
0
            break;
155
0
    }
156
157
    /* now setup the actual raster reader */
158
0
    GDALRasterBand *poSrcBand = poBandIn->GetRasterBand(1);
159
0
    poSrcBand->GetBlockSize(&nBlockXSize, &nBlockYSize);
160
0
}
161
162
/************************************************************************/
163
/*                           TSXRasterBand()                            */
164
/************************************************************************/
165
166
TSXRasterBand::~TSXRasterBand()
167
0
{
168
0
    if (poBand != nullptr)
169
0
        GDALClose(reinterpret_cast<GDALRasterBandH>(poBand));
170
0
}
171
172
/************************************************************************/
173
/*                             IReadBlock()                             */
174
/************************************************************************/
175
176
CPLErr TSXRasterBand::IReadBlock(int nBlockXOff, int nBlockYOff, void *pImage)
177
0
{
178
0
    int nRequestYSize;
179
180
    /* Check if the last strip is partial so we can avoid over-requesting */
181
0
    if ((nBlockYOff + 1) * nBlockYSize > nRasterYSize)
182
0
    {
183
0
        nRequestYSize = nRasterYSize - nBlockYOff * nBlockYSize;
184
0
        memset(pImage, 0,
185
0
               static_cast<size_t>(GDALGetDataTypeSizeBytes(eDataType)) *
186
0
                   nBlockXSize * nBlockYSize);
187
0
    }
188
0
    else
189
0
    {
190
0
        nRequestYSize = nBlockYSize;
191
0
    }
192
193
    /* Read Complex Data */
194
0
    if (eDataType == GDT_CInt16)
195
0
    {
196
0
        return poBand->RasterIO(
197
0
            GF_Read, nBlockXOff * nBlockXSize, nBlockYOff * nBlockYSize,
198
0
            nBlockXSize, nRequestYSize, pImage, nBlockXSize, nRequestYSize,
199
0
            GDT_CInt16, 1, nullptr, 4, nBlockXSize * 4, 0, nullptr);
200
0
    }
201
202
    // Detected Product
203
0
    return poBand->RasterIO(
204
0
        GF_Read, nBlockXOff * nBlockXSize, nBlockYOff * nBlockYSize,
205
0
        nBlockXSize, nRequestYSize, pImage, nBlockXSize, nRequestYSize,
206
0
        GDT_UInt16, 1, nullptr, 2, nBlockXSize * 2, 0, nullptr);
207
0
}
208
209
/************************************************************************/
210
/* ==================================================================== */
211
/*                                TSXDataset                                */
212
/* ==================================================================== */
213
/************************************************************************/
214
215
/************************************************************************/
216
/*                             TSXDataset()                             */
217
/************************************************************************/
218
219
TSXDataset::TSXDataset()
220
0
    : nGCPCount(0), pasGCPList(nullptr), bHaveGeoTransform(false),
221
0
      nProduct(eUnknown)
222
0
{
223
0
    m_oSRS.SetAxisMappingStrategy(OAMS_TRADITIONAL_GIS_ORDER);
224
0
    m_oGCPSRS.SetAxisMappingStrategy(OAMS_TRADITIONAL_GIS_ORDER);
225
0
}
226
227
/************************************************************************/
228
/*                            ~TSXDataset()                             */
229
/************************************************************************/
230
231
TSXDataset::~TSXDataset()
232
0
{
233
0
    FlushCache(true);
234
235
0
    if (nGCPCount > 0)
236
0
    {
237
0
        GDALDeinitGCPs(nGCPCount, pasGCPList);
238
0
        CPLFree(pasGCPList);
239
0
    }
240
0
}
241
242
/************************************************************************/
243
/*                              Identify()                              */
244
/************************************************************************/
245
246
int TSXDataset::Identify(GDALOpenInfo *poOpenInfo)
247
339k
{
248
339k
    if (poOpenInfo->fpL == nullptr || poOpenInfo->nHeaderBytes < 260)
249
272k
    {
250
272k
        if (poOpenInfo->bIsDirectory)
251
3.38k
        {
252
3.38k
            const CPLString osFilename = CPLFormCIFilenameSafe(
253
3.38k
                poOpenInfo->pszFilename,
254
3.38k
                CPLGetFilename(poOpenInfo->pszFilename), "xml");
255
256
            /* Check if the filename contains TSX1_SAR (TerraSAR-X) or TDX1_SAR
257
             * (TanDEM-X) or PAZ1_SAR (PAZ) */
258
3.38k
            if (!(STARTS_WITH_CI(CPLGetBasenameSafe(osFilename).c_str(),
259
3.38k
                                 "TSX1_SAR") ||
260
3.38k
                  STARTS_WITH_CI(CPLGetBasenameSafe(osFilename).c_str(),
261
3.38k
                                 "TDX1_SAR") ||
262
3.38k
                  STARTS_WITH_CI(CPLGetBasenameSafe(osFilename).c_str(),
263
3.38k
                                 "PAZ1_SAR")))
264
3.38k
                return 0;
265
266
0
            VSIStatBufL sStat;
267
0
            if (VSIStatL(osFilename, &sStat) == 0)
268
0
                return 1;
269
0
        }
270
271
268k
        return 0;
272
272k
    }
273
274
    /* Check if the filename contains TSX1_SAR (TerraSAR-X) or TDX1_SAR
275
     * (TanDEM-X) or PAZ1_SAR (PAZ) */
276
67.0k
    if (!(STARTS_WITH_CI(CPLGetBasenameSafe(poOpenInfo->pszFilename).c_str(),
277
67.0k
                         "TSX1_SAR") ||
278
67.0k
          STARTS_WITH_CI(CPLGetBasenameSafe(poOpenInfo->pszFilename).c_str(),
279
67.0k
                         "TDX1_SAR") ||
280
67.0k
          STARTS_WITH_CI(CPLGetBasenameSafe(poOpenInfo->pszFilename).c_str(),
281
67.0k
                         "PAZ1_SAR")))
282
67.0k
        return 0;
283
284
    /* finally look for the <level1Product tag */
285
1
    if (!STARTS_WITH_CI(reinterpret_cast<char *>(poOpenInfo->pabyHeader),
286
1
                        "<level1Product"))
287
1
        return 0;
288
289
0
    return 1;
290
1
}
291
292
/************************************************************************/
293
/*                                getGCPsFromGEOREF_XML()               */
294
/*Reads georeferencing information from the TerraSAR-X GEOREF.XML file    */
295
/*and writes the information to the dataset's gcp list and projection     */
296
/*string.                                                                */
297
/*Returns true on success.                                                */
298
/************************************************************************/
299
bool TSXDataset::getGCPsFromGEOREF_XML(const char *pszGeorefFilename)
300
0
{
301
    // open GEOREF.xml
302
0
    CPLXMLNode *psGeorefData = CPLParseXMLFile(pszGeorefFilename);
303
0
    if (psGeorefData == nullptr)
304
0
        return false;
305
306
    // get the ellipsoid and semi-major, semi-minor axes
307
0
    OGRSpatialReference osr;
308
0
    CPLXMLNode *psSphere =
309
0
        CPLGetXMLNode(psGeorefData, "=geoReference.referenceFrames.sphere");
310
0
    if (psSphere != nullptr)
311
0
    {
312
0
        const char *pszEllipsoidName =
313
0
            CPLGetXMLValue(psSphere, "ellipsoidID", "");
314
0
        const double minor_axis =
315
0
            CPLAtof(CPLGetXMLValue(psSphere, "semiMinorAxis", "0.0"));
316
0
        const double major_axis =
317
0
            CPLAtof(CPLGetXMLValue(psSphere, "semiMajorAxis", "0.0"));
318
        // save datum parameters to the spatial reference
319
0
        if (EQUAL(pszEllipsoidName, "") || minor_axis == 0.0 ||
320
0
            major_axis == 0.0)
321
0
        {
322
0
            CPLError(CE_Warning, CPLE_AppDefined,
323
0
                     "Warning- incomplete"
324
0
                     " ellipsoid information.  Using wgs-84 parameters.\n");
325
0
            osr.SetWellKnownGeogCS("WGS84");
326
0
        }
327
0
        else if (EQUAL(pszEllipsoidName, "WGS84"))
328
0
        {
329
0
            osr.SetWellKnownGeogCS("WGS84");
330
0
        }
331
0
        else
332
0
        {
333
0
            const double inv_flattening =
334
0
                major_axis / (major_axis - minor_axis);
335
0
            osr.SetGeogCS("", "", pszEllipsoidName, major_axis, inv_flattening);
336
0
        }
337
0
    }
338
339
    // get gcps
340
0
    CPLXMLNode *psGeolocationGrid =
341
0
        CPLGetXMLNode(psGeorefData, "=geoReference.geolocationGrid");
342
0
    if (psGeolocationGrid == nullptr)
343
0
    {
344
0
        CPLDestroyXMLNode(psGeorefData);
345
0
        return false;
346
0
    }
347
0
    nGCPCount = atoi(
348
0
        CPLGetXMLValue(psGeolocationGrid, "numberOfGridPoints.total", "0"));
349
    // count the gcps if the given count value is invalid
350
0
    CPLXMLNode *psNode = nullptr;
351
0
    if (nGCPCount <= 0)
352
0
    {
353
0
        for (psNode = psGeolocationGrid->psChild; psNode != nullptr;
354
0
             psNode = psNode->psNext)
355
0
            if (EQUAL(psNode->pszValue, "gridPoint"))
356
0
                nGCPCount++;
357
0
    }
358
    // if there are no gcps, fail
359
0
    if (nGCPCount <= 0)
360
0
    {
361
0
        CPLDestroyXMLNode(psGeorefData);
362
0
        return false;
363
0
    }
364
365
    // put some reasonable limits of the number of gcps
366
0
    if (nGCPCount > MAX_GCPS)
367
0
        nGCPCount = MAX_GCPS;
368
    // allocate memory for the gcps
369
0
    pasGCPList =
370
0
        static_cast<GDAL_GCP *>(CPLCalloc(sizeof(GDAL_GCP), nGCPCount));
371
372
    // loop through all gcps and set info
373
374
    // save the number allocated to ensure it does not run off the end of the
375
    // array
376
0
    const int gcps_allocated = nGCPCount;
377
0
    nGCPCount = 0;  // reset to zero and count
378
    // do a check on the grid point to make sure it has lat,long row, and column
379
    // it seems that only SSC products contain row, col - how to map lat long
380
    // otherwise?? for now fail if row and col are not present - just check the
381
    // first and assume the rest are the same
382
0
    for (psNode = psGeolocationGrid->psChild; psNode != nullptr;
383
0
         psNode = psNode->psNext)
384
0
    {
385
0
        if (!EQUAL(psNode->pszValue, "gridPoint"))
386
0
            continue;
387
388
0
        if (!strcmp(CPLGetXMLValue(psNode, "col", "error"), "error") ||
389
0
            !strcmp(CPLGetXMLValue(psNode, "row", "error"), "error") ||
390
0
            !strcmp(CPLGetXMLValue(psNode, "lon", "error"), "error") ||
391
0
            !strcmp(CPLGetXMLValue(psNode, "lat", "error"), "error"))
392
0
        {
393
0
            CPLDestroyXMLNode(psGeorefData);
394
0
            return false;
395
0
        }
396
0
    }
397
0
    for (psNode = psGeolocationGrid->psChild; psNode != nullptr;
398
0
         psNode = psNode->psNext)
399
0
    {
400
        // break out if the end of the array has been reached
401
0
        if (nGCPCount >= gcps_allocated)
402
0
        {
403
0
            CPLError(CE_Warning, CPLE_AppDefined,
404
0
                     "GDAL TSX driver: Truncating the number of GCPs.");
405
0
            break;
406
0
        }
407
408
0
        GDAL_GCP *psGCP = pasGCPList + nGCPCount;
409
410
0
        if (!EQUAL(psNode->pszValue, "gridPoint"))
411
0
            continue;
412
413
0
        nGCPCount++;
414
415
0
        char szID[32];
416
0
        snprintf(szID, sizeof(szID), "%d", nGCPCount);
417
0
        psGCP->pszId = CPLStrdup(szID);
418
0
        psGCP->pszInfo = CPLStrdup("");
419
0
        psGCP->dfGCPPixel = CPLAtof(CPLGetXMLValue(psNode, "col", "0"));
420
0
        psGCP->dfGCPLine = CPLAtof(CPLGetXMLValue(psNode, "row", "0"));
421
0
        psGCP->dfGCPX = CPLAtof(CPLGetXMLValue(psNode, "lon", ""));
422
0
        psGCP->dfGCPY = CPLAtof(CPLGetXMLValue(psNode, "lat", ""));
423
        // looks like height is in meters - should it be converted so xyz are
424
        // all on the same scale??
425
0
        psGCP->dfGCPZ = 0;
426
        // CPLAtof(CPLGetXMLValue(psNode,"height",""));
427
0
    }
428
429
0
    m_oGCPSRS = std::move(osr);
430
431
0
    CPLDestroyXMLNode(psGeorefData);
432
433
0
    return true;
434
0
}
435
436
/************************************************************************/
437
/*                                Open()                                */
438
/************************************************************************/
439
440
GDALDataset *TSXDataset::Open(GDALOpenInfo *poOpenInfo)
441
0
{
442
    /* -------------------------------------------------------------------- */
443
    /*      Is this a TerraSAR-X product file?                              */
444
    /* -------------------------------------------------------------------- */
445
0
    if (!TSXDataset::Identify(poOpenInfo))
446
0
    {
447
0
        return nullptr; /* nope */
448
0
    }
449
450
    /* -------------------------------------------------------------------- */
451
    /*      Confirm the requested access is supported.                      */
452
    /* -------------------------------------------------------------------- */
453
0
    if (poOpenInfo->eAccess == GA_Update)
454
0
    {
455
0
        ReportUpdateNotSupportedByDriver("TSX");
456
0
        return nullptr;
457
0
    }
458
459
0
    CPLString osFilename;
460
461
0
    if (poOpenInfo->bIsDirectory)
462
0
    {
463
0
        osFilename = CPLFormCIFilenameSafe(
464
0
            poOpenInfo->pszFilename, CPLGetFilename(poOpenInfo->pszFilename),
465
0
            "xml");
466
0
    }
467
0
    else
468
0
        osFilename = poOpenInfo->pszFilename;
469
470
    /* Ingest the XML */
471
0
    CPLXMLTreeCloser psData(CPLParseXMLFile(osFilename));
472
0
    if (psData == nullptr)
473
0
        return nullptr;
474
475
    /* find the product components */
476
0
    const CPLXMLNode *psComponents =
477
0
        CPLGetXMLNode(psData.get(), "=level1Product.productComponents");
478
0
    if (psComponents == nullptr)
479
0
    {
480
0
        CPLError(CE_Failure, CPLE_OpenFailed,
481
0
                 "Unable to find <productComponents> tag in file.\n");
482
0
        return nullptr;
483
0
    }
484
485
    /* find the product info tag */
486
0
    const CPLXMLNode *psProductInfo =
487
0
        CPLGetXMLNode(psData.get(), "=level1Product.productInfo");
488
0
    if (psProductInfo == nullptr)
489
0
    {
490
0
        CPLError(CE_Failure, CPLE_OpenFailed,
491
0
                 "Unable to find <productInfo> tag in file.\n");
492
0
        return nullptr;
493
0
    }
494
495
    /* -------------------------------------------------------------------- */
496
    /*      Create the dataset.                                             */
497
    /* -------------------------------------------------------------------- */
498
499
0
    auto poDS = std::make_unique<TSXDataset>();
500
501
    /* -------------------------------------------------------------------- */
502
    /*      Read in product info.                                           */
503
    /* -------------------------------------------------------------------- */
504
505
0
    poDS->SetMetadataItem(
506
0
        "SCENE_CENTRE_TIME",
507
0
        CPLGetXMLValue(psProductInfo,
508
0
                       "sceneInfo.sceneCenterCoord.azimuthTimeUTC", "unknown"));
509
0
    poDS->SetMetadataItem("OPERATIONAL_MODE",
510
0
                          CPLGetXMLValue(psProductInfo,
511
0
                                         "generationInfo.groundOperationsType",
512
0
                                         "unknown"));
513
0
    poDS->SetMetadataItem(
514
0
        "ORBIT_CYCLE",
515
0
        CPLGetXMLValue(psProductInfo, "missionInfo.orbitCycle", "unknown"));
516
0
    poDS->SetMetadataItem(
517
0
        "ABSOLUTE_ORBIT",
518
0
        CPLGetXMLValue(psProductInfo, "missionInfo.absOrbit", "unknown"));
519
0
    poDS->SetMetadataItem(
520
0
        "ORBIT_DIRECTION",
521
0
        CPLGetXMLValue(psProductInfo, "missionInfo.orbitDirection", "unknown"));
522
0
    poDS->SetMetadataItem("IMAGING_MODE",
523
0
                          CPLGetXMLValue(psProductInfo,
524
0
                                         "acquisitionInfo.imagingMode",
525
0
                                         "unknown"));
526
0
    poDS->SetMetadataItem("PRODUCT_VARIANT",
527
0
                          CPLGetXMLValue(psProductInfo,
528
0
                                         "productVariantInfo.productVariant",
529
0
                                         "unknown"));
530
0
    std::string osDataType =
531
0
        CPLGetXMLValue(psProductInfo, "imageDataInfo.imageDataType", "unknown");
532
0
    poDS->SetMetadataItem("IMAGE_TYPE", osDataType.c_str());
533
534
    /* Get raster information */
535
0
    int nRows = atoi(CPLGetXMLValue(
536
0
        psProductInfo, "imageDataInfo.imageRaster.numberOfRows", ""));
537
0
    int nCols = atoi(CPLGetXMLValue(
538
0
        psProductInfo, "imageDataInfo.imageRaster.numberOfColumns", ""));
539
540
0
    poDS->nRasterXSize = nCols;
541
0
    poDS->nRasterYSize = nRows;
542
543
0
    poDS->SetMetadataItem("ROW_SPACING",
544
0
                          CPLGetXMLValue(psProductInfo,
545
0
                                         "imageDataInfo.imageRaster.rowSpacing",
546
0
                                         "unknown"));
547
0
    poDS->SetMetadataItem(
548
0
        "COL_SPACING",
549
0
        CPLGetXMLValue(psProductInfo, "imageDataInfo.imageRaster.columnSpacing",
550
0
                       "unknown"));
551
0
    poDS->SetMetadataItem(
552
0
        "COL_SPACING_UNITS",
553
0
        CPLGetXMLValue(psProductInfo,
554
0
                       "imageDataInfo.imageRaster.columnSpacing.units",
555
0
                       "unknown"));
556
557
    /* Get equivalent number of looks */
558
0
    poDS->SetMetadataItem(
559
0
        "AZIMUTH_LOOKS",
560
0
        CPLGetXMLValue(psProductInfo, "imageDataInfo.imageRaster.azimuthLooks",
561
0
                       "unknown"));
562
0
    poDS->SetMetadataItem("RANGE_LOOKS",
563
0
                          CPLGetXMLValue(psProductInfo,
564
0
                                         "imageDataInfo.imageRaster.rangeLooks",
565
0
                                         "unknown"));
566
567
0
    const char *pszProductVariant = CPLGetXMLValue(
568
0
        psProductInfo, "productVariantInfo.productVariant", "unknown");
569
570
0
    poDS->SetMetadataItem("PRODUCT_VARIANT", pszProductVariant);
571
572
    /* Determine what product variant this is */
573
0
    if (STARTS_WITH_CI(pszProductVariant, "SSC"))
574
0
        poDS->nProduct = eSSC;
575
0
    else if (STARTS_WITH_CI(pszProductVariant, "MGD"))
576
0
        poDS->nProduct = eMGD;
577
0
    else if (STARTS_WITH_CI(pszProductVariant, "EEC"))
578
0
        poDS->nProduct = eEEC;
579
0
    else if (STARTS_WITH_CI(pszProductVariant, "GEC"))
580
0
        poDS->nProduct = eGEC;
581
0
    else
582
0
        poDS->nProduct = eUnknown;
583
584
    /* Start reading in the product components */
585
0
    std::string osGeorefFile;
586
0
    CPLErr geoTransformErr = CE_Failure;
587
0
    for (CPLXMLNode *psComponent = psComponents->psChild;
588
0
         psComponent != nullptr; psComponent = psComponent->psNext)
589
0
    {
590
0
        const char *pszType = nullptr;
591
0
        const std::string osFilePath = GetFilePath(psComponent, &pszType);
592
0
        if (CPLHasPathTraversal(osFilePath.c_str()))
593
0
        {
594
0
            CPLError(CE_Failure, CPLE_AppDefined,
595
0
                     "Path traversal detected in %s", osFilePath.c_str());
596
0
            return nullptr;
597
0
        }
598
0
        std::string osPath = CPLFormFilenameSafe(
599
0
            CPLGetDirnameSafe(osFilename).c_str(), osFilePath.c_str(), "");
600
0
        const char *pszPolLayer = CPLGetXMLValue(psComponent, "polLayer", " ");
601
602
0
        if (!STARTS_WITH_CI(pszType, " "))
603
0
        {
604
0
            if (STARTS_WITH_CI(pszType, "MAPPING_GRID"))
605
0
            {
606
                /* the mapping grid... save as a metadata item this path */
607
0
                poDS->SetMetadataItem("MAPPING_GRID", osPath.c_str());
608
0
            }
609
0
            else if (STARTS_WITH_CI(pszType, "GEOREF"))
610
0
            {
611
                /* save the path to the georef data for later use */
612
0
                osGeorefFile = std::move(osPath);
613
0
            }
614
0
        }
615
0
        else if (!STARTS_WITH_CI(pszPolLayer, " ") &&
616
0
                 STARTS_WITH_CI(psComponent->pszValue, "imageData"))
617
0
        {
618
            /* determine the polarization of this band */
619
0
            ePolarization ePol;
620
0
            if (STARTS_WITH_CI(pszPolLayer, "HH"))
621
0
            {
622
0
                ePol = HH;
623
0
            }
624
0
            else if (STARTS_WITH_CI(pszPolLayer, "HV"))
625
0
            {
626
0
                ePol = HV;
627
0
            }
628
0
            else if (STARTS_WITH_CI(pszPolLayer, "VH"))
629
0
            {
630
0
                ePol = VH;
631
0
            }
632
0
            else
633
0
            {
634
0
                ePol = VV;
635
0
            }
636
637
0
            GDALDataType eDataType =
638
0
                STARTS_WITH_CI(osDataType.c_str(), "COMPLEX") ? GDT_CInt16
639
0
                                                              : GDT_UInt16;
640
641
            /* try opening the file that represents that band */
642
0
            GDALDataset *poBandData =
643
0
                GDALDataset::FromHandle(GDALOpen(osPath.c_str(), GA_ReadOnly));
644
0
            if (poBandData != nullptr)
645
0
            {
646
0
                TSXRasterBand *poBand =
647
0
                    new TSXRasterBand(poDS.get(), eDataType, ePol, poBandData);
648
0
                poDS->SetBand(poDS->GetRasterCount() + 1, poBand);
649
650
                // copy georeferencing info from the band
651
                // need error checking??
652
                // it will just save the info from the last band
653
0
                const auto poSrcSRS = poBandData->GetSpatialRef();
654
0
                if (poSrcSRS)
655
0
                    poDS->m_oSRS = *poSrcSRS;
656
657
0
                geoTransformErr = poBandData->GetGeoTransform(poDS->m_gt);
658
0
            }
659
0
        }
660
0
    }
661
662
    // now check if there is a geotransform
663
0
    if (!poDS->m_oSRS.IsEmpty() && geoTransformErr == CE_None)
664
0
    {
665
0
        poDS->bHaveGeoTransform = TRUE;
666
0
    }
667
0
    else
668
0
    {
669
0
        poDS->bHaveGeoTransform = FALSE;
670
0
        poDS->m_oSRS.Clear();
671
0
        poDS->m_gt = GDALGeoTransform();
672
0
    }
673
674
    /* -------------------------------------------------------------------- */
675
    /*      Check and set matrix representation.                            */
676
    /* -------------------------------------------------------------------- */
677
678
0
    if (poDS->GetRasterCount() == 4)
679
0
    {
680
0
        poDS->SetMetadataItem("MATRIX_REPRESENTATION", "SCATTERING");
681
0
    }
682
683
    /* -------------------------------------------------------------------- */
684
    /*      Read the four corners and centre GCPs in                        */
685
    /* -------------------------------------------------------------------- */
686
687
0
    const CPLXMLNode *psSceneInfo =
688
0
        CPLGetXMLNode(psData.get(), "=level1Product.productInfo.sceneInfo");
689
0
    if (psSceneInfo != nullptr)
690
0
    {
691
        /* extract the GCPs from the provided file */
692
0
        bool success = false;
693
0
        if (!osGeorefFile.empty())
694
0
            success = poDS->getGCPsFromGEOREF_XML(osGeorefFile.c_str());
695
696
        // if the gcp's cannot be extracted from the georef file, try to get the
697
        // corner coordinates for now just SSC because the others don't have
698
        // refColumn and refRow
699
0
        if (!success && poDS->nProduct == eSSC)
700
0
        {
701
0
            int nGCP = 0;
702
0
            double dfAvgHeight = CPLAtof(
703
0
                CPLGetXMLValue(psSceneInfo, "sceneAverageHeight", "0.0"));
704
705
            // count and allocate gcps - there should be five - 4 corners and a
706
            // centre
707
0
            poDS->nGCPCount = 0;
708
0
            const CPLXMLNode *psNode = psSceneInfo->psChild;
709
0
            for (; psNode != nullptr; psNode = psNode->psNext)
710
0
            {
711
0
                if (!EQUAL(psNode->pszValue, "sceneCenterCoord") &&
712
0
                    !EQUAL(psNode->pszValue, "sceneCornerCoord"))
713
0
                    continue;
714
715
0
                poDS->nGCPCount++;
716
0
            }
717
0
            if (poDS->nGCPCount > 0)
718
0
            {
719
0
                poDS->pasGCPList =
720
0
                    (GDAL_GCP *)CPLCalloc(sizeof(GDAL_GCP), poDS->nGCPCount);
721
722
                /* iterate over GCPs */
723
0
                for (psNode = psSceneInfo->psChild; psNode != nullptr;
724
0
                     psNode = psNode->psNext)
725
0
                {
726
0
                    GDAL_GCP *psGCP = poDS->pasGCPList + nGCP;
727
728
0
                    if (!EQUAL(psNode->pszValue, "sceneCenterCoord") &&
729
0
                        !EQUAL(psNode->pszValue, "sceneCornerCoord"))
730
0
                        continue;
731
732
0
                    psGCP->dfGCPPixel =
733
0
                        CPLAtof(CPLGetXMLValue(psNode, "refColumn", "0.0"));
734
0
                    psGCP->dfGCPLine =
735
0
                        CPLAtof(CPLGetXMLValue(psNode, "refRow", "0.0"));
736
0
                    psGCP->dfGCPX =
737
0
                        CPLAtof(CPLGetXMLValue(psNode, "lon", "0.0"));
738
0
                    psGCP->dfGCPY =
739
0
                        CPLAtof(CPLGetXMLValue(psNode, "lat", "0.0"));
740
0
                    psGCP->dfGCPZ = dfAvgHeight;
741
0
                    psGCP->pszId = CPLStrdup(CPLSPrintf("%d", nGCP));
742
0
                    psGCP->pszInfo = CPLStrdup("");
743
744
0
                    nGCP++;
745
0
                }
746
747
                // set the projection string - the fields are lat/long - seems
748
                // to be WGS84 datum
749
0
                poDS->m_oGCPSRS.SetWellKnownGeogCS("WGS84");
750
0
            }
751
0
        }
752
753
        // gcps override geotransform - does it make sense to have both??
754
0
        if (poDS->nGCPCount > 0)
755
0
        {
756
0
            poDS->bHaveGeoTransform = FALSE;
757
0
            poDS->m_oSRS.Clear();
758
0
            poDS->m_gt = GDALGeoTransform();
759
0
        }
760
0
    }
761
0
    else
762
0
    {
763
0
        CPLError(CE_Warning, CPLE_AppDefined,
764
0
                 "Unable to find sceneInfo tag in XML document. "
765
0
                 "Proceeding with caution.");
766
0
    }
767
768
    /* -------------------------------------------------------------------- */
769
    /*      Initialize any PAM information.                                 */
770
    /* -------------------------------------------------------------------- */
771
0
    poDS->SetDescription(poOpenInfo->pszFilename);
772
0
    poDS->TryLoadXML();
773
774
    /* -------------------------------------------------------------------- */
775
    /*      Check for overviews.                                            */
776
    /* -------------------------------------------------------------------- */
777
0
    poDS->oOvManager.Initialize(poDS.get(), poOpenInfo->pszFilename);
778
779
0
    return poDS.release();
780
0
}
781
782
/************************************************************************/
783
/*                            GetGCPCount()                             */
784
/************************************************************************/
785
786
int TSXDataset::GetGCPCount()
787
0
{
788
0
    return nGCPCount;
789
0
}
790
791
/************************************************************************/
792
/*                          GetGCPSpatialRef()                          */
793
/************************************************************************/
794
795
const OGRSpatialReference *TSXDataset::GetGCPSpatialRef() const
796
0
{
797
0
    return m_oGCPSRS.IsEmpty() ? nullptr : &m_oGCPSRS;
798
0
}
799
800
/************************************************************************/
801
/*                              GetGCPs()                               */
802
/************************************************************************/
803
804
const GDAL_GCP *TSXDataset::GetGCPs()
805
0
{
806
0
    return pasGCPList;
807
0
}
808
809
/************************************************************************/
810
/*                           GetSpatialRef()                            */
811
/************************************************************************/
812
813
const OGRSpatialReference *TSXDataset::GetSpatialRef() const
814
815
0
{
816
0
    return m_oSRS.IsEmpty() ? nullptr : &m_oSRS;
817
0
}
818
819
/************************************************************************/
820
/*                          GetGeotransform()                           */
821
/************************************************************************/
822
CPLErr TSXDataset::GetGeoTransform(GDALGeoTransform &gt) const
823
0
{
824
0
    gt = m_gt;
825
826
0
    if (bHaveGeoTransform)
827
0
        return CE_None;
828
829
0
    return CE_Failure;
830
0
}
831
832
/************************************************************************/
833
/*                          GDALRegister_TSX()                          */
834
/************************************************************************/
835
836
void GDALRegister_TSX()
837
22
{
838
22
    if (GDALGetDriverByName("TSX") != nullptr)
839
0
        return;
840
841
22
    GDALDriver *poDriver = new GDALDriver();
842
843
22
    poDriver->SetDescription("TSX");
844
22
    poDriver->SetMetadataItem(GDAL_DCAP_RASTER, "YES");
845
22
    poDriver->SetMetadataItem(GDAL_DMD_LONGNAME, "TerraSAR-X Product");
846
22
    poDriver->SetMetadataItem(GDAL_DMD_HELPTOPIC, "drivers/raster/tsx.html");
847
22
    poDriver->SetMetadataItem(GDAL_DCAP_VIRTUALIO, "YES");
848
849
22
    poDriver->pfnOpen = TSXDataset::Open;
850
22
    poDriver->pfnIdentify = TSXDataset::Identify;
851
852
22
    GetGDALDriverManager()->RegisterDriver(poDriver);
853
22
}