Coverage Report

Created: 2025-12-03 08:24

next uncovered line (L), next uncovered region (R), next uncovered branch (B)
/src/gdal/frmts/coasp/coasp_dataset.cpp
Line
Count
Source
1
/******************************************************************************
2
 *
3
 * Project:  DRDC Configurable Airborne SAR Processor (COASP) data reader
4
 * Purpose:  Support in GDAL for the DRDC COASP format data, both Metadata
5
 *           and complex imagery.
6
 * Author:   Philippe Vachon <philippe@cowpig.ca>
7
 * Notes:    I have seen a grand total of 2 COASP scenes (3 sets of headers).
8
 *           This is based on my best observations, some educated guesses and
9
 *           such. So if you have a scene that doesn't work, send it to me
10
 *           please and I will make it work... with violence.
11
 *
12
 ******************************************************************************
13
 * Copyright (c) 2007, Philippe Vachon
14
 * Copyright (c) 2009-2012, Even Rouault <even dot rouault at spatialys.com>
15
 *
16
 * SPDX-License-Identifier: MIT
17
 ****************************************************************************/
18
19
#include "cpl_conv.h"
20
#include "cpl_port.h"
21
#include "cpl_string.h"
22
#include "cpl_vsi.h"
23
#include "gdal_frmts.h"
24
#include "gdal_priv.h"
25
26
constexpr int TYPE_GENERIC = 0;
27
constexpr int TYPE_GEOREF = 1;
28
29
enum ePolarization
30
{
31
    hh = 0,
32
    hv,
33
    vh,
34
    vv
35
};
36
37
/*******************************************************************
38
 * Declaration of the COASPMetadata classes                        *
39
 *******************************************************************/
40
41
class COASPMetadataItem;
42
43
class COASPMetadataReader final
44
{
45
    char **papszMetadata;
46
    int nMetadataCount;
47
    int nCurrentItem;
48
49
  public:
50
    explicit COASPMetadataReader(char *pszFname);
51
    ~COASPMetadataReader();
52
    COASPMetadataItem *GetNextItem();
53
    COASPMetadataItem *GetItem(int nItem);
54
    int GotoMetadataItem(const char *pszName);
55
56
    int GetCurrentItem() const
57
0
    {
58
0
        return nCurrentItem;
59
0
    }
60
};
61
62
/* Your average metadata item */
63
class COASPMetadataItem /* non final */
64
{
65
  protected:
66
    char *pszItemName;
67
    char *pszItemValue;
68
69
  public:
70
0
    COASPMetadataItem() : pszItemName(nullptr), pszItemValue(nullptr)
71
0
    {
72
0
    }
73
74
    COASPMetadataItem(char *pszItemName, char *pszItemValue);
75
    ~COASPMetadataItem();
76
77
    char *GetItemName();
78
    char *GetItemValue();
79
80
    static int GetType()
81
0
    {
82
0
        return TYPE_GENERIC;
83
0
    }
84
};
85
86
/* Same as MetadataItem class except parses GCP properly and returns
87
 * a GDAL_GCP struct
88
 */
89
class COASPMetadataGeorefGridItem final : public COASPMetadataItem
90
{
91
#ifdef unused
92
    int nId;
93
    int nPixels;
94
    int nLines;
95
    double ndLat;
96
    double ndLong;
97
#endif
98
99
  public:
100
    COASPMetadataGeorefGridItem(int nId, int nPixels, int nLines, double ndLat,
101
                                double ndLong);
102
103
    static const char *GetItemName()
104
0
    {
105
0
        return "georef_grid";
106
0
    }
107
108
    static GDAL_GCP *GetItemValue();
109
110
    static int GetType()
111
0
    {
112
0
        return TYPE_GEOREF;
113
0
    }
114
};
115
116
/********************************************************************
117
 * ================================================================ *
118
 * Implementation of the COASPMetadataItem Classes                  *
119
 * ================================================================ *
120
 ********************************************************************/
121
122
COASPMetadataItem::COASPMetadataItem(char *pszItemName_, char *pszItemValue_)
123
0
    : pszItemName(VSIStrdup(pszItemName_)),
124
0
      pszItemValue(VSIStrdup(pszItemValue_))
125
0
{
126
0
}
127
128
COASPMetadataItem::~COASPMetadataItem()
129
0
{
130
0
    CPLFree(pszItemName);
131
0
    CPLFree(pszItemValue);
132
0
}
133
134
char *COASPMetadataItem::GetItemName()
135
0
{
136
0
    return VSIStrdup(pszItemName);
137
0
}
138
139
char *COASPMetadataItem::GetItemValue()
140
0
{
141
0
    return VSIStrdup(pszItemValue);
142
0
}
143
144
COASPMetadataGeorefGridItem::COASPMetadataGeorefGridItem(int /*nIdIn*/,
145
                                                         int /*nPixelsIn*/,
146
                                                         int /*nLinesIn*/,
147
                                                         double /*ndLatIn*/,
148
                                                         double /*ndLongIn*/)
149
#ifdef unused
150
    : nId(nIdIn), nPixels(nPixelsIn), nLines(nLinesIn), ndLat(ndLatIn),
151
      ndLong(ndLongIn)
152
#endif
153
0
{
154
0
    pszItemName = VSIStrdup("georef_grid");
155
0
}
156
157
GDAL_GCP *COASPMetadataGeorefGridItem::GetItemValue()
158
0
{
159
0
    return nullptr;
160
0
}
161
162
/********************************************************************
163
 * ================================================================ *
164
 * Implementation of the COASPMetadataReader Class                  *
165
 * ================================================================ *
166
 ********************************************************************/
167
168
COASPMetadataReader::COASPMetadataReader(char *pszFname)
169
0
    : papszMetadata(CSLLoad(pszFname)), nMetadataCount(0), nCurrentItem(0)
170
0
{
171
0
    nMetadataCount = CSLCount(papszMetadata);
172
0
}
173
174
COASPMetadataReader::~COASPMetadataReader()
175
0
{
176
0
    CSLDestroy(papszMetadata);
177
0
}
178
179
COASPMetadataItem *COASPMetadataReader::GetNextItem()
180
0
{
181
0
    if (nCurrentItem < 0 || nCurrentItem >= nMetadataCount)
182
0
        return nullptr;
183
184
0
    COASPMetadataItem *poMetadata = nullptr;
185
186
0
    char **papszMDTokens = CSLTokenizeString2(papszMetadata[nCurrentItem], " ",
187
0
                                              CSLT_HONOURSTRINGS);
188
0
    char *pszItemName = papszMDTokens[0];
189
0
    if (STARTS_WITH_CI(pszItemName, "georef_grid") &&
190
0
        CSLCount(papszMDTokens) >= 8)
191
0
    {
192
        // georef_grid ( pixels lines ) ( lat long )
193
        // 0           1 2      3     4 5 6   7    8
194
0
        int nPixels = atoi(papszMDTokens[2]);
195
0
        int nLines = atoi(papszMDTokens[3]);
196
0
        double dfLat = CPLAtof(papszMDTokens[6]);
197
0
        double dfLong = CPLAtof(papszMDTokens[7]);
198
0
        poMetadata = new COASPMetadataGeorefGridItem(nCurrentItem, nPixels,
199
0
                                                     nLines, dfLat, dfLong);
200
0
    }
201
0
    else
202
0
    {
203
0
        int nCount = CSLCount(papszMDTokens);
204
0
        if (nCount >= 2)
205
0
        {
206
0
            char *pszItemValue = CPLStrdup(papszMDTokens[1]);
207
0
            for (int i = 2; i < nCount; i++)
208
0
            {
209
0
                const size_t nSize =
210
0
                    strlen(pszItemValue) + 1 + strlen(papszMDTokens[i]);
211
0
                pszItemValue = (char *)CPLRealloc(pszItemValue, nSize);
212
0
                snprintf(pszItemValue + strlen(pszItemValue),
213
0
                         nSize - strlen(pszItemValue), " %s", papszMDTokens[i]);
214
0
            }
215
216
0
            poMetadata = new COASPMetadataItem(pszItemName, pszItemValue);
217
218
0
            CPLFree(pszItemValue);
219
0
        }
220
0
    }
221
0
    CSLDestroy(papszMDTokens);
222
0
    nCurrentItem++;
223
0
    return poMetadata;
224
0
}
225
226
/* Goto the first metadata item with a particular name */
227
int COASPMetadataReader::GotoMetadataItem(const char *pszName)
228
0
{
229
0
    nCurrentItem = CSLPartialFindString(papszMetadata, pszName);
230
0
    return nCurrentItem;
231
0
}
232
233
/*******************************************************************
234
 * Declaration of the COASPDataset class                           *
235
 *******************************************************************/
236
237
class COASPRasterBand;
238
239
/* A couple of observations based on the data I have available to me:
240
 * a) the headers don't really change, beyond indicating data sources
241
 *    and such. As such, I only read the first header specified by the
242
 *    user. Note that this is agnostic: you can specify hh, vv, vh, hv and
243
 *    all the data needed will be immediately available.
244
 * b) Lots of GCPs are present in the headers. This is most excellent.
245
 * c) There is no documentation on this format. All the knowledge contained
246
 *    herein is from harassing various Defence Scientists at DRDC Ottawa.
247
 */
248
249
class COASPDataset final : public GDALDataset
250
{
251
    friend class COASPRasterBand;
252
    VSILFILE *fpHdr;   /* File pointer for the header file */
253
    VSILFILE *fpBinHH; /* File pointer for the binary matrix */
254
    VSILFILE *fpBinHV;
255
    VSILFILE *fpBinVH;
256
    VSILFILE *fpBinVV;
257
258
    char *pszFileName; /* line and mission ID, mostly, i.e. l27p7 */
259
260
  public:
261
    COASPDataset()
262
0
        : fpHdr(nullptr), fpBinHH(nullptr), fpBinHV(nullptr), fpBinVH(nullptr),
263
0
          fpBinVV(nullptr), pszFileName(nullptr)
264
0
    {
265
0
    }
266
267
    ~COASPDataset() override;
268
269
    static GDALDataset *Open(GDALOpenInfo *);
270
    static int Identify(GDALOpenInfo *poOpenInfo);
271
};
272
273
/********************************************************************
274
 * ================================================================ *
275
 * Declaration and implementation of the COASPRasterBand Class      *
276
 * ================================================================ *
277
 ********************************************************************/
278
279
class COASPRasterBand final : public GDALRasterBand
280
{
281
    VSILFILE *fp;
282
    // int ePol;
283
  public:
284
    COASPRasterBand(COASPDataset *poDS, GDALDataType eDataType, int ePol,
285
                    VSILFILE *fp);
286
    CPLErr IReadBlock(int nBlockXOff, int nBlockYOff, void *pImage) override;
287
};
288
289
COASPRasterBand::COASPRasterBand(COASPDataset *poDSIn, GDALDataType eDataTypeIn,
290
                                 int /*ePolIn*/, VSILFILE *fpIn)
291
0
    : fp(fpIn) /*,
292
       ePol(ePolIn)*/
293
0
{
294
0
    poDS = poDSIn;
295
0
    eDataType = eDataTypeIn;
296
0
    nBlockXSize = poDS->GetRasterXSize();
297
0
    nBlockYSize = 1;
298
0
}
299
300
CPLErr COASPRasterBand::IReadBlock(CPL_UNUSED int nBlockXOff, int nBlockYOff,
301
                                   void *pImage)
302
0
{
303
0
    if (this->fp == nullptr)
304
0
    {
305
0
        CPLError(CE_Fatal, CPLE_AppDefined, "File pointer freed unexpectedly");
306
0
        return CE_Fatal;
307
0
    }
308
309
    /* 8 bytes per pixel: 4 bytes I, 4 bytes Q */
310
0
    const vsi_l_offset nByteNum =
311
0
        static_cast<vsi_l_offset>(poDS->GetRasterXSize()) * 8 * nBlockYOff;
312
313
0
    VSIFSeekL(this->fp, nByteNum, SEEK_SET);
314
0
    const int nReadSize =
315
0
        GDALGetDataTypeSizeBytes(eDataType) * poDS->GetRasterXSize();
316
0
    VSIFReadL((char *)pImage, 1, nReadSize, this->fp);
317
318
0
#ifdef CPL_LSB
319
0
    GDALSwapWords(pImage, 4, nBlockXSize * 2, 4);
320
0
#endif
321
0
    return CE_None;
322
0
}
323
324
/********************************************************************
325
 * ================================================================ *
326
 * Implementation of the COASPDataset Class                         *
327
 * ================================================================ *
328
 ********************************************************************/
329
330
/************************************************************************/
331
/*                          ~COASPDataset()                             */
332
/************************************************************************/
333
334
COASPDataset::~COASPDataset()
335
0
{
336
0
    CPLFree(pszFileName);
337
0
    if (fpHdr)
338
0
        VSIFCloseL(fpHdr);
339
0
    if (fpBinHH)
340
0
        VSIFCloseL(fpBinHH);
341
0
    if (fpBinHV)
342
0
        VSIFCloseL(fpBinHV);
343
0
    if (fpBinVH)
344
0
        VSIFCloseL(fpBinVH);
345
0
    if (fpBinVV)
346
0
        VSIFCloseL(fpBinVV);
347
0
}
348
349
/************************************************************************/
350
/*                              Identify()                              */
351
/************************************************************************/
352
353
int COASPDataset::Identify(GDALOpenInfo *poOpenInfo)
354
570k
{
355
570k
    if (poOpenInfo->fpL == nullptr || poOpenInfo->nHeaderBytes < 256)
356
512k
        return 0;
357
358
    // With a COASP .hdr file, the first line or so is: time_first_datarec
359
360
58.0k
    if (STARTS_WITH_CI((char *)poOpenInfo->pabyHeader, "time_first_datarec"))
361
0
        return 1;
362
363
58.0k
    return 0;
364
58.0k
}
365
366
/************************************************************************/
367
/*                                Open()                                */
368
/************************************************************************/
369
370
GDALDataset *COASPDataset::Open(GDALOpenInfo *poOpenInfo)
371
0
{
372
0
    if (!COASPDataset::Identify(poOpenInfo))
373
0
        return nullptr;
374
375
    /* -------------------------------------------------------------------- */
376
    /*      Confirm the requested access is supported.                      */
377
    /* -------------------------------------------------------------------- */
378
0
    if (poOpenInfo->eAccess == GA_Update)
379
0
    {
380
0
        ReportUpdateNotSupportedByDriver("COASP");
381
0
        return nullptr;
382
0
    }
383
384
    /* Create a fresh dataset for us to work with */
385
0
    COASPDataset *poDS = new COASPDataset();
386
387
    /* Steal the file pointer for the header */
388
0
    poDS->fpHdr = poOpenInfo->fpL;
389
0
    poOpenInfo->fpL = nullptr;
390
391
0
    poDS->pszFileName = VSIStrdup(poOpenInfo->pszFilename);
392
393
    /* determine the file name prefix */
394
0
    char *pszBaseName =
395
0
        VSIStrdup(CPLGetBasenameSafe(poDS->pszFileName).c_str());
396
0
    char *pszDir = VSIStrdup(CPLGetPathSafe(poDS->pszFileName).c_str());
397
0
    const char *pszExt = "rc";
398
0
    int nNull = static_cast<int>(strlen(pszBaseName)) - 1;
399
0
    if (nNull <= 0)
400
0
    {
401
0
        VSIFree(pszDir);
402
0
        VSIFree(pszBaseName);
403
0
        delete poDS;
404
0
        return nullptr;
405
0
    }
406
0
    char *pszBase = (char *)CPLMalloc(nNull);
407
0
    strncpy(pszBase, pszBaseName, nNull);
408
0
    pszBase[nNull - 1] = '\0';
409
0
    VSIFree(pszBaseName);
410
411
0
    char *psChan = strstr(pszBase, "hh");
412
0
    if (psChan == nullptr)
413
0
    {
414
0
        psChan = strstr(pszBase, "hv");
415
0
    }
416
0
    if (psChan == nullptr)
417
0
    {
418
0
        psChan = strstr(pszBase, "vh");
419
0
    }
420
0
    if (psChan == nullptr)
421
0
    {
422
0
        psChan = strstr(pszBase, "vv");
423
0
    }
424
425
0
    if (psChan == nullptr)
426
0
    {
427
0
        CPLError(CE_Failure, CPLE_AppDefined,
428
0
                 "Unable to recognize file as COASP.");
429
0
        VSIFree(pszBase);
430
0
        VSIFree(pszDir);
431
0
        delete poDS;
432
0
        return nullptr;
433
0
    }
434
435
    /* Read Metadata, set GCPs as is appropriate */
436
0
    COASPMetadataReader oReader(poDS->pszFileName);
437
438
    /* Get Image X and Y widths */
439
0
    oReader.GotoMetadataItem("number_lines");
440
0
    COASPMetadataItem *poItem = oReader.GetNextItem();
441
0
    if (poItem == nullptr)
442
0
    {
443
0
        VSIFree(pszBase);
444
0
        VSIFree(pszDir);
445
0
        delete poDS;
446
0
        return nullptr;
447
0
    }
448
0
    char *nValue = poItem->GetItemValue();
449
0
    poDS->nRasterYSize = atoi(nValue);
450
0
    delete poItem;
451
0
    VSIFree(nValue);
452
453
0
    oReader.GotoMetadataItem("number_samples");
454
0
    poItem = oReader.GetNextItem();
455
0
    if (poItem == nullptr)
456
0
    {
457
0
        VSIFree(pszBase);
458
0
        VSIFree(pszDir);
459
0
        delete poDS;
460
0
        return nullptr;
461
0
    }
462
0
    nValue = poItem->GetItemValue();
463
0
    poDS->nRasterXSize = atoi(nValue);
464
0
    delete poItem;
465
0
    VSIFree(nValue);
466
467
0
    if (!GDALCheckDatasetDimensions(poDS->nRasterXSize, poDS->nRasterYSize))
468
0
    {
469
0
        VSIFree(pszBase);
470
0
        VSIFree(pszDir);
471
0
        delete poDS;
472
0
        return nullptr;
473
0
    }
474
475
    /* Horizontal transmit, horizontal receive */
476
0
    psChan[0] = 'h';
477
0
    psChan[1] = 'h';
478
0
    std::string osFilename = CPLFormFilenameSafe(pszDir, pszBase, pszExt);
479
480
0
    poDS->fpBinHH = VSIFOpenL(osFilename.c_str(), "r");
481
482
0
    if (poDS->fpBinHH != nullptr)
483
0
    {
484
        /* Set raster band */
485
0
        poDS->SetBand(
486
0
            1, new COASPRasterBand(poDS, GDT_CFloat32, hh, poDS->fpBinHH));
487
0
    }
488
489
    /* Horizontal transmit, vertical receive */
490
0
    psChan[0] = 'h';
491
0
    psChan[1] = 'v';
492
0
    osFilename = CPLFormFilenameSafe(pszDir, pszBase, pszExt);
493
494
0
    poDS->fpBinHV = VSIFOpenL(osFilename.c_str(), "r");
495
496
0
    if (poDS->fpBinHV != nullptr)
497
0
    {
498
0
        poDS->SetBand(
499
0
            2, new COASPRasterBand(poDS, GDT_CFloat32, hv, poDS->fpBinHV));
500
0
    }
501
502
    /* Vertical transmit, horizontal receive */
503
0
    psChan[0] = 'v';
504
0
    psChan[1] = 'h';
505
0
    osFilename = CPLFormFilenameSafe(pszDir, pszBase, pszExt);
506
507
0
    poDS->fpBinVH = VSIFOpenL(osFilename.c_str(), "r");
508
509
0
    if (poDS->fpBinVH != nullptr)
510
0
    {
511
0
        poDS->SetBand(
512
0
            3, new COASPRasterBand(poDS, GDT_CFloat32, vh, poDS->fpBinVH));
513
0
    }
514
515
    /* Vertical transmit, vertical receive */
516
0
    psChan[0] = 'v';
517
0
    psChan[1] = 'v';
518
0
    osFilename = CPLFormFilenameSafe(pszDir, pszBase, pszExt);
519
520
0
    poDS->fpBinVV = VSIFOpenL(osFilename.c_str(), "r");
521
522
0
    if (poDS->fpBinVV != nullptr)
523
0
    {
524
0
        poDS->SetBand(
525
0
            4, new COASPRasterBand(poDS, GDT_CFloat32, vv, poDS->fpBinVV));
526
0
    }
527
528
    /* Oops, missing all the data? */
529
0
    if (poDS->fpBinHH == nullptr && poDS->fpBinHV == nullptr &&
530
0
        poDS->fpBinVH == nullptr && poDS->fpBinVV == nullptr)
531
0
    {
532
0
        CPLError(CE_Failure, CPLE_AppDefined, "Unable to find any data!");
533
0
        VSIFree(pszBase);
534
0
        VSIFree(pszDir);
535
0
        delete poDS;
536
0
        return nullptr;
537
0
    }
538
539
0
    if (poDS->GetRasterCount() == 4)
540
0
    {
541
0
        poDS->SetMetadataItem("MATRIX_REPRESENTATION", "SCATTERING");
542
0
    }
543
544
0
    VSIFree(pszBase);
545
0
    VSIFree(pszDir);
546
547
0
    return poDS;
548
0
}
549
550
/************************************************************************/
551
/*                         GDALRegister_COASP()                         */
552
/************************************************************************/
553
554
void GDALRegister_COASP()
555
22
{
556
22
    if (GDALGetDriverByName("COASP") != nullptr)
557
0
        return;
558
559
22
    GDALDriver *poDriver = new GDALDriver();
560
561
22
    poDriver->SetDescription("COASP");
562
22
    poDriver->SetMetadataItem(GDAL_DCAP_RASTER, "YES");
563
22
    poDriver->SetMetadataItem(GDAL_DMD_LONGNAME,
564
22
                              "DRDC COASP SAR Processor Raster");
565
22
    poDriver->SetMetadataItem(GDAL_DMD_EXTENSION, "hdr");
566
22
    poDriver->SetMetadataItem(GDAL_DMD_HELPTOPIC, "drivers/raster/coasp.html");
567
22
    poDriver->pfnIdentify = COASPDataset::Identify;
568
22
    poDriver->pfnOpen = COASPDataset::Open;
569
22
    GetGDALDriverManager()->RegisterDriver(poDriver);
570
22
}