• Home
  • Features
  • Pricing
  • Docs
  • Announcements
  • Sign In

OSGeo / gdal / 15885686134

25 Jun 2025 07:44PM UTC coverage: 71.084%. Remained the same
15885686134

push

github

rouault
gdal_priv.h: fix C++11 compatibility

573814 of 807237 relevant lines covered (71.08%)

250621.56 hits per line

Source File
Press 'n' to go to next uncovered line, 'b' for previous

6.58
/frmts/msgn/msgndataset.cpp
1
/******************************************************************************
2
 *
3
 * Project:  MSG Native Reader
4
 * Purpose:  All code for EUMETSAT Archive format reader
5
 * Author:   Frans van den Bergh, fvdbergh@csir.co.za
6
 *
7
 ******************************************************************************
8
 * Copyright (c) 2005, Frans van den Bergh <fvdbergh@csir.co.za>
9
 * Copyright (c) 2008-2009, Even Rouault <even dot rouault at spatialys.com>
10
 *
11
 * SPDX-License-Identifier: MIT
12
 ****************************************************************************/
13
#include "cpl_port.h"
14
#include "cpl_error.h"
15

16
#include "gdal_frmts.h"
17
#include "gdal_priv.h"
18
#include "ogr_spatialref.h"
19

20
#include "msg_reader_core.h"
21

22
#include <algorithm>
23

24
using namespace msg_native_format;
25

26
typedef enum
27
{
28
    MODE_VISIR,  // Visible and Infrared bands (1 through 11) in 10-bit raw mode
29
    MODE_HRV,    // Pan band (band 11) only, in 10-bit raw mode
30
    MODE_RAD     // Black-body temperature (K) for thermal bands only (4-10),
31
                 // 64-bit float
32
} open_mode_type;
33

34
typedef enum
35
{
36
    WHOLE_DISK,
37
    RSS,       // letterbox of N 1/3 of earth
38
    SPLIT_HRV  // the half-width HRV, may be sheared into two block to follow
39
               // the sun W later in the day
40
} image_shape_type;
41

42
class MSGNRasterBand;
43

44
/************************************************************************/
45
/* ==================================================================== */
46
/*                            MSGNDataset                               */
47
/* ==================================================================== */
48
/************************************************************************/
49

50
class MSGNDataset final : public GDALDataset
51
{
52
    friend class MSGNRasterBand;
53

54
    VSILFILE *fp;
55

56
    Msg_reader_core *msg_reader_core;
57
    open_mode_type m_open_mode = MODE_VISIR;
58
    image_shape_type m_Shape = WHOLE_DISK;
59
    int m_nHRVSplitLine = 0;
60
    int m_nHRVLowerShiftX = 0;
61
    int m_nHRVUpperShiftX = 0;
62
    GDALGeoTransform m_gt{};
63
    OGRSpatialReference m_oSRS{};
64

65
  public:
66
    MSGNDataset();
67
    ~MSGNDataset();
68

69
    static GDALDataset *Open(GDALOpenInfo *);
70

71
    CPLErr GetGeoTransform(GDALGeoTransform &gt) const override;
72
    const OGRSpatialReference *GetSpatialRef() const override;
73
};
74

75
/************************************************************************/
76
/* ==================================================================== */
77
/*                            MSGNRasterBand                            */
78
/* ==================================================================== */
79
/************************************************************************/
80

81
class MSGNRasterBand final : public GDALRasterBand
82
{
83
    friend class MSGNDataset;
84

85
    unsigned int packet_size;
86
    unsigned int bytes_per_line;
87
    unsigned int interline_spacing;
88
    unsigned int orig_band_no;  // The name of the band
89
    unsigned int band_in_file;  // The effective index of the band in the file
90
    open_mode_type open_mode;
91

92
    double GetNoDataValue(int *pbSuccess = nullptr) override
×
93
    {
94
        if (pbSuccess)
×
95
        {
96
            *pbSuccess = 1;
×
97
        }
98
        return MSGN_NODATA_VALUE;
×
99
    }
100

101
    double MSGN_NODATA_VALUE;
102

103
    char band_description[30];
104

105
  public:
106
    MSGNRasterBand(MSGNDataset *, int, open_mode_type mode, int orig_band_no,
107
                   int band_in_file);
108

109
    virtual CPLErr IReadBlock(int, int, void *) override;
110
    virtual double GetMinimum(int *pbSuccess = nullptr) override;
111
    virtual double GetMaximum(int *pbSuccess = nullptr) override;
112

113
    virtual const char *GetDescription() const override
×
114
    {
115
        return band_description;
×
116
    }
117
};
118

119
/************************************************************************/
120
/*                           MSGNRasterBand()                            */
121
/************************************************************************/
122

123
MSGNRasterBand::MSGNRasterBand(MSGNDataset *poDSIn, int nBandIn,
×
124
                               open_mode_type mode, int orig_band_noIn,
125
                               int band_in_fileIn)
×
126
    : packet_size(0), bytes_per_line(0),
127
      interline_spacing(poDSIn->msg_reader_core->get_interline_spacing()),
×
128
      orig_band_no(orig_band_noIn), band_in_file(band_in_fileIn),
×
129
      open_mode(mode)
×
130
{
131
    poDS = poDSIn;
×
132
    nBand = nBandIn;  // GDAL's band number, i.e. always starts at 1.
×
133

134
    snprintf(band_description, sizeof(band_description), "band %02u",
×
135
             orig_band_no);
136

137
    if (mode != MODE_RAD)
×
138
    {
139
        eDataType = GDT_UInt16;
×
140
        MSGN_NODATA_VALUE = 0;
×
141
    }
142
    else
143
    {
144
        eDataType = GDT_Float64;
×
145
        MSGN_NODATA_VALUE = -1000;
×
146
    }
147

148
    nBlockXSize = poDS->GetRasterXSize();
×
149
    nBlockYSize = 1;
×
150

151
    if (mode != MODE_HRV)
×
152
    {
153
        packet_size = poDSIn->msg_reader_core->get_visir_packet_size();
×
154
        bytes_per_line = poDSIn->msg_reader_core->get_visir_bytes_per_line();
×
155
    }
156
    else
157
    {
158
        packet_size = poDSIn->msg_reader_core->get_hrv_packet_size();
×
159
        bytes_per_line = poDSIn->msg_reader_core->get_hrv_bytes_per_line();
×
160
    }
161
}
×
162

163
/************************************************************************/
164
/*                             IReadBlock()                             */
165
/************************************************************************/
166

167
CPLErr MSGNRasterBand::IReadBlock(CPL_UNUSED int nBlockXOff, int nBlockYOff,
×
168
                                  void *pImage)
169

170
{
171
    MSGNDataset *poGDS = cpl::down_cast<MSGNDataset *>(poDS);
×
172

173
    // invert y position
174
    const int i_nBlockYOff = poDS->GetRasterYSize() - 1 - nBlockYOff;
×
175

176
    const int nSamples = static_cast<int>((bytes_per_line * 8) / 10);
×
177
    if (poGDS->m_Shape == WHOLE_DISK && nRasterXSize != nSamples)
×
178
    {
179
        CPLError(CE_Failure, CPLE_AppDefined, "nRasterXSize %d != nSamples %d",
×
180
                 nRasterXSize, nSamples);
181
        return CE_Failure;
×
182
    }
183

184
    unsigned int data_length =
×
185
        bytes_per_line + (unsigned int)sizeof(SUB_VISIRLINE);
×
186
    vsi_l_offset data_offset = 0;
×
187

188
    if (open_mode != MODE_HRV)
×
189
    {
190
        data_offset =
×
191
            poGDS->msg_reader_core->get_f_data_offset() +
×
192
            static_cast<vsi_l_offset>(interline_spacing) * i_nBlockYOff +
×
193
            static_cast<vsi_l_offset>(band_in_file - 1) * packet_size +
×
194
            (packet_size - data_length);
×
195
    }
196
    else
197
    {
198
        data_offset =
×
199
            poGDS->msg_reader_core->get_f_data_offset() +
×
200
            static_cast<vsi_l_offset>(interline_spacing) *
×
201
                (int(i_nBlockYOff / 3) + 1) -
×
202
            static_cast<vsi_l_offset>(packet_size) * (3 - (i_nBlockYOff % 3)) +
×
203
            (packet_size - data_length);
×
204
    }
205

206
    if (VSIFSeekL(poGDS->fp, data_offset, SEEK_SET) != 0)
×
207
        return CE_Failure;
×
208

209
    char *pszRecord = (char *)CPLMalloc(data_length);
×
210
    size_t nread = VSIFReadL(pszRecord, 1, data_length, poGDS->fp);
×
211

212
    SUB_VISIRLINE *p = reinterpret_cast<SUB_VISIRLINE *>(pszRecord);
×
213
    to_native(*p);
×
214

215
    if (p->lineValidity != 1 || poGDS->m_Shape != WHOLE_DISK)
×
216
    {  // Split lines are not full width, so NODATA all first
217
        for (int c = 0; c < nBlockXSize; c++)
×
218
        {
219
            if (open_mode != MODE_RAD)
×
220
            {
221
                ((GUInt16 *)pImage)[c] = (GUInt16)MSGN_NODATA_VALUE;
×
222
            }
223
            else
224
            {
225
                ((double *)pImage)[c] = MSGN_NODATA_VALUE;
×
226
            }
227
        }
228
    }
229

230
    if (nread != data_length ||
×
231
        (p->lineNumberInVisirGrid -
×
232
         ((open_mode == MODE_HRV && poGDS->m_Shape == RSS)
×
233
              ? (3 * poGDS->msg_reader_core->get_line_start()) - 2
×
234
              : poGDS->msg_reader_core->get_line_start())) !=
×
235
            (unsigned int)i_nBlockYOff)
×
236
    {
237
        CPLDebug("MSGN", "Shape %s",
×
238
                 poGDS->m_Shape == RSS
×
239
                     ? "RSS"
240
                     : (poGDS->m_Shape == WHOLE_DISK ? "whole" : "split HRV"));
×
241

242
        CPLDebug(
×
243
            "MSGN", "nread = %lu, data_len %d, linenum %d, start %d, offset %d",
244
            (long unsigned int)nread,  // Mingw_w64 otherwise wants %llu - MSG
245
                                       // read will never exceed 32 bits
246
            data_length, (p->lineNumberInVisirGrid),
247
            poGDS->msg_reader_core->get_line_start(), i_nBlockYOff);
×
248

249
        CPLFree(pszRecord);
×
250

251
        CPLError(CE_Failure, CPLE_AppDefined, "MSGN Scanline corrupt.");
×
252

253
        return CE_Failure;
×
254
    }
255

256
    // unpack the 10-bit values into 16-bit unsigned short ints
257
    unsigned char *cptr =
×
258
        (unsigned char *)pszRecord + (data_length - bytes_per_line);
×
259
    int bitsLeft = 8;
×
260

261
    if (open_mode != MODE_RAD)
×
262
    {
263
        int shift = 0;
×
264
        if (poGDS->m_Shape == SPLIT_HRV)
×
265
            shift = i_nBlockYOff < poGDS->m_nHRVSplitLine
×
266
                        ? poGDS->m_nHRVLowerShiftX
×
267
                        : poGDS->m_nHRVUpperShiftX;
268
        for (int c = 0; c < nSamples; c++)
×
269
        {
270
            unsigned short value = 0;
×
271
            for (int bit = 0; bit < 10; bit++)
×
272
            {
273
                value <<= 1;
×
274
                if (*cptr & 128)
×
275
                {
276
                    value |= 1;
×
277
                }
278
                *cptr <<= 1;
×
279
                bitsLeft--;
×
280
                if (bitsLeft == 0)
×
281
                {
282
                    cptr++;
×
283
                    bitsLeft = 8;
×
284
                }
285
            }
286
            ((GUInt16 *)pImage)[nBlockXSize - 1 - c - shift] = value;
×
287
        }
288
    }
289
    else
290
    {
291
        // radiance mode
292
        for (int c = 0; c < nSamples; c++)
×
293
        {
294
            unsigned short value = 0;
×
295
            for (int bit = 0; bit < 10; bit++)
×
296
            {
297
                value <<= 1;
×
298
                if (*cptr & 128)
×
299
                {
300
                    value |= 1;
×
301
                }
302
                *cptr <<= 1;
×
303
                bitsLeft--;
×
304
                if (bitsLeft == 0)
×
305
                {
306
                    cptr++;
×
307
                    bitsLeft = 8;
×
308
                }
309
            }
310
            double dvalue = double(value);
×
311
            double bbvalue =
312
                dvalue * poGDS->msg_reader_core
×
313
                             ->get_calibration_parameters()[orig_band_no - 1]
×
314
                             .cal_slope +
×
315
                poGDS->msg_reader_core
×
316
                    ->get_calibration_parameters()[orig_band_no - 1]
×
317
                    .cal_offset;
×
318

319
            ((double *)pImage)[nBlockXSize - 1 - c] = bbvalue;
×
320
        }
321
    }
322
    CPLFree(pszRecord);
×
323
    return CE_None;
×
324
}
325

326
/************************************************************************/
327
/*                             GetMinimum()                             */
328
/************************************************************************/
329
double MSGNRasterBand::GetMinimum(int *pbSuccess)
×
330
{
331
    if (pbSuccess)
×
332
    {
333
        *pbSuccess = 1;
×
334
    }
335
    return open_mode != MODE_RAD ? 1 : GDALRasterBand::GetMinimum(pbSuccess);
×
336
}
337

338
/************************************************************************/
339
/*                             GetMaximum()                             */
340
/************************************************************************/
341
double MSGNRasterBand::GetMaximum(int *pbSuccess)
×
342
{
343
    if (pbSuccess)
×
344
    {
345
        *pbSuccess = 1;
×
346
    }
347
    return open_mode != MODE_RAD ? 1023 : GDALRasterBand::GetMaximum(pbSuccess);
×
348
}
349

350
/************************************************************************/
351
/* ==================================================================== */
352
/*                             MSGNDataset                             */
353
/* ==================================================================== */
354
/************************************************************************/
355

356
MSGNDataset::MSGNDataset() : fp(nullptr), msg_reader_core(nullptr)
×
357
{
358
    m_oSRS.SetAxisMappingStrategy(OAMS_TRADITIONAL_GIS_ORDER);
×
359
}
×
360

361
/************************************************************************/
362
/*                            ~MSGNDataset()                             */
363
/************************************************************************/
364

365
MSGNDataset::~MSGNDataset()
×
366

367
{
368
    if (fp != nullptr)
×
369
        VSIFCloseL(fp);
×
370

371
    if (msg_reader_core)
×
372
    {
373
        delete msg_reader_core;
×
374
    }
375
}
×
376

377
/************************************************************************/
378
/*                          GetGeoTransform()                           */
379
/************************************************************************/
380

381
CPLErr MSGNDataset::GetGeoTransform(GDALGeoTransform &gt) const
×
382

383
{
384
    gt = m_gt;
×
385

386
    return CE_None;
×
387
}
388

389
/************************************************************************/
390
/*                          GetSpatialRef()                             */
391
/************************************************************************/
392

393
const OGRSpatialReference *MSGNDataset::GetSpatialRef() const
×
394

395
{
396
    return m_oSRS.IsEmpty() ? nullptr : &m_oSRS;
×
397
}
398

399
/************************************************************************/
400
/*                                Open()                                */
401
/************************************************************************/
402

403
GDALDataset *MSGNDataset::Open(GDALOpenInfo *poOpenInfo)
34,343✔
404

405
{
406
    open_mode_type open_mode = MODE_VISIR;
34,343✔
407
    GDALOpenInfo *open_info = poOpenInfo;
34,343✔
408
    std::unique_ptr<GDALOpenInfo> poOpenInfoToFree;
34,343✔
409

410
    if (!poOpenInfo->bStatOK)
34,343✔
411
    {
412
        if (STARTS_WITH_CI(poOpenInfo->pszFilename, "HRV:"))
29,608✔
413
        {
414
            poOpenInfoToFree = std::make_unique<GDALOpenInfo>(
×
415
                &poOpenInfo->pszFilename[4], poOpenInfo->eAccess);
×
416
            open_info = poOpenInfoToFree.get();
×
417
            open_mode = MODE_HRV;
×
418
        }
419
        else if (STARTS_WITH_CI(poOpenInfo->pszFilename, "RAD:"))
29,608✔
420
        {
421
            poOpenInfoToFree = std::make_unique<GDALOpenInfo>(
×
422
                &poOpenInfo->pszFilename[4], poOpenInfo->eAccess);
×
423
            open_info = poOpenInfoToFree.get();
×
424
            open_mode = MODE_RAD;
×
425
        }
426
    }
427

428
    /* -------------------------------------------------------------------- */
429
    /*      Before trying MSGNOpen() we first verify that there is at        */
430
    /*      least one "\n#keyword" type signature in the first chunk of     */
431
    /*      the file.                                                       */
432
    /* -------------------------------------------------------------------- */
433
    if (open_info->fpL == nullptr || open_info->nHeaderBytes < 50)
34,343✔
434
    {
435
        return nullptr;
30,490✔
436
    }
437

438
    /* check if this is a "NATIVE" MSG format image */
439
    if (!STARTS_WITH_CI((char *)open_info->pabyHeader,
3,853✔
440
                        "FormatName                  : NATIVE"))
441
    {
442
        return nullptr;
3,853✔
443
    }
444

445
    /* -------------------------------------------------------------------- */
446
    /*      Confirm the requested access is supported.                      */
447
    /* -------------------------------------------------------------------- */
448
    if (poOpenInfo->eAccess == GA_Update)
×
449
    {
450
        ReportUpdateNotSupportedByDriver("MSGN");
×
451
        return nullptr;
×
452
    }
453

454
    /* -------------------------------------------------------------------- */
455
    /*      Create a corresponding GDALDataset.                             */
456
    /* -------------------------------------------------------------------- */
457
    VSILFILE *fp = VSIFOpenL(open_info->pszFilename, "rb");
×
458
    if (fp == nullptr)
×
459
    {
460
        return nullptr;
×
461
    }
462

463
    auto poDS = std::make_unique<MSGNDataset>();
×
464

465
    poDS->m_open_mode = open_mode;
×
466
    poDS->fp = fp;
×
467

468
    /* -------------------------------------------------------------------- */
469
    /*      Read the header.                                                */
470
    /* -------------------------------------------------------------------- */
471
    // first reset the file pointer, then hand over to the msg_reader_core
472
    CPL_IGNORE_RET_VAL(VSIFSeekL(poDS->fp, 0, SEEK_SET));
×
473

474
    poDS->msg_reader_core = new Msg_reader_core(poDS->fp);
×
475

476
    if (!poDS->msg_reader_core->get_open_success())
×
477
    {
478
        return nullptr;
×
479
    }
480

481
    poDS->nRasterXSize = poDS->msg_reader_core->get_columns();
×
482
    poDS->nRasterYSize = poDS->msg_reader_core->get_lines();
×
483

484
    if (open_mode == MODE_HRV)
×
485
    {
486
        const int nRawHRVColumns =
487
            (poDS->msg_reader_core->get_hrv_bytes_per_line() * 8) / 10;
×
488
        poDS->nRasterYSize *= 3;
×
489
        const auto &idr = poDS->msg_reader_core->get_image_description_record();
×
490
        // Check if the split layout of the HRV channel meets our expectations
491
        // to re-assemble it in a consistent way
492
        CPLDebug("MSGN", "HRV raw col %d raster X %d raster Y %d",
×
493
                 nRawHRVColumns, poDS->nRasterXSize, poDS->nRasterYSize);
×
494

495
        if (idr.plannedCoverage_hrv.lowerSouthLinePlanned == 1 &&
×
496
            idr.plannedCoverage_hrv.lowerNorthLinePlanned > 1 &&
×
497
            idr.plannedCoverage_hrv.lowerNorthLinePlanned <
×
498
                poDS->nRasterYSize &&
×
499
            idr.plannedCoverage_hrv.upperSouthLinePlanned ==
×
500
                idr.plannedCoverage_hrv.lowerNorthLinePlanned + 1 &&
×
501
            idr.plannedCoverage_hrv.upperNorthLinePlanned ==
×
502
                poDS->nRasterYSize &&
×
503
            idr.plannedCoverage_hrv.lowerEastColumnPlanned >= 1 &&
×
504
            idr.plannedCoverage_hrv.lowerWestColumnPlanned ==
×
505
                idr.plannedCoverage_hrv.lowerEastColumnPlanned +
×
506
                    nRawHRVColumns - 1 &&
×
507
            idr.plannedCoverage_hrv.lowerWestColumnPlanned <=
×
508
                poDS->nRasterXSize * 3 &&
×
509
            idr.plannedCoverage_hrv.upperEastColumnPlanned >= 1 &&
×
510
            idr.plannedCoverage_hrv.upperWestColumnPlanned ==
×
511
                idr.plannedCoverage_hrv.upperEastColumnPlanned +
×
512
                    nRawHRVColumns - 1 &&
×
513
            idr.plannedCoverage_hrv.upperWestColumnPlanned <=
×
514
                poDS->nRasterXSize * 3)
×
515
        {
516
            poDS->nRasterXSize *= 3;
×
517
            poDS->m_Shape = SPLIT_HRV;
×
518
            poDS->m_nHRVSplitLine =
×
519
                idr.plannedCoverage_hrv.upperSouthLinePlanned;
×
520
            poDS->m_nHRVLowerShiftX =
×
521
                idr.plannedCoverage_hrv.lowerEastColumnPlanned - 1;
×
522
            poDS->m_nHRVUpperShiftX =
×
523
                idr.plannedCoverage_hrv.upperEastColumnPlanned - 1;
×
524
        }
525
        else if (idr.plannedCoverage_hrv.upperNorthLinePlanned == 0 &&
×
526
                 idr.plannedCoverage_hrv.upperSouthLinePlanned == 0 &&
×
527
                 idr.plannedCoverage_hrv.upperWestColumnPlanned == 0 &&
×
528
                 idr.plannedCoverage_hrv.upperEastColumnPlanned ==
×
529
                     0 &&  // RSS only uses the lower section
×
530
                 idr.plannedCoverage_hrv.lowerNorthLinePlanned ==
×
531
                     idr.referencegrid_hrv.numberOfLines &&  // start at max N
×
532
                 // full expected width
533
                 idr.plannedCoverage_hrv.lowerWestColumnPlanned ==
×
534
                     idr.plannedCoverage_hrv.lowerEastColumnPlanned +
×
535
                         nRawHRVColumns - 1 &&
×
536
                 idr.plannedCoverage_hrv.lowerSouthLinePlanned > 1 &&
×
537
                 idr.plannedCoverage_hrv.lowerSouthLinePlanned <
×
538
                     idr.referencegrid_hrv.numberOfLines &&
×
539
                 idr.plannedCoverage_hrv.lowerEastColumnPlanned >= 1 &&
×
540
                 idr.plannedCoverage_hrv.lowerWestColumnPlanned <=
×
541
                     poDS->nRasterXSize * 3 &&
×
542
                 // full height
543
                 idr.plannedCoverage_hrv.lowerNorthLinePlanned ==
×
544
                     idr.plannedCoverage_hrv.lowerSouthLinePlanned +
×
545
                         poDS->nRasterYSize - 1)
×
546
        {
547
            poDS->nRasterXSize *= 3;
×
548
            poDS->m_Shape = RSS;
×
549
        }
550
        else
551
        {
552
            CPLError(
×
553
                CE_Failure, CPLE_AppDefined,
554
                "HRV neither Whole Disk nor RSS - don't know how to handle");
555
            return nullptr;
×
556
        }
557
    }
558
    else
559
    {
560
        const int nRawVisIRColumns =
561
            (poDS->msg_reader_core->get_visir_bytes_per_line() * 8) / 10;
×
562

563
        const auto &idr = poDS->msg_reader_core->get_image_description_record();
×
564
        // Check if the VisIR channel is RSS or not, and if it meets our
565
        // expectations to re-assemble it in a consistent way
566
        CPLDebug("MSGN", "raw col %d raster X %d raster Y %d", nRawVisIRColumns,
×
567
                 poDS->nRasterXSize, poDS->nRasterYSize);
×
568

569
        if (idr.plannedCoverage_visir.southernLinePlanned == 1 &&
×
570
            idr.plannedCoverage_visir.northernLinePlanned ==
×
571
                poDS->nRasterYSize &&
×
572
            idr.plannedCoverage_visir.easternColumnPlanned >= 1 &&
×
573
            idr.plannedCoverage_visir.westernColumnPlanned ==
×
574
                idr.plannedCoverage_visir.easternColumnPlanned +
×
575
                    nRawVisIRColumns - 1 &&
×
576
            idr.plannedCoverage_visir.westernColumnPlanned <=
×
577
                poDS->nRasterXSize)
×
578
        {
579
            poDS->m_Shape = WHOLE_DISK;
×
580
        }
581
        else if (idr.plannedCoverage_visir.northernLinePlanned ==
×
582
                     idr.referencegrid_visir.numberOfLines &&  // start at max N
×
583
                 // full expected width
584
                 idr.plannedCoverage_visir.westernColumnPlanned ==
×
585
                     idr.plannedCoverage_visir.easternColumnPlanned +
×
586
                         nRawVisIRColumns - 1 &&
×
587
                 idr.plannedCoverage_visir.southernLinePlanned > 1 &&
×
588
                 idr.plannedCoverage_visir.easternColumnPlanned >= 1 &&
×
589
                 idr.plannedCoverage_visir.westernColumnPlanned <=
×
590
                     poDS->nRasterXSize &&
×
591
                 // full height
592
                 idr.plannedCoverage_visir.northernLinePlanned ==
×
593
                     idr.plannedCoverage_visir.southernLinePlanned +
×
594
                         poDS->nRasterYSize - 1)
×
595
        {
596
            poDS->m_Shape = RSS;
×
597
        }
598
        else
599
        {
600
            CPLError(CE_Failure, CPLE_AppDefined,
×
601
                     "Neither Whole Disk nor RSS - don't know how to handle");
602
            return nullptr;
×
603
        }
604
    }
605

606
    CPLDebug("MSGN", "Shape %s",
×
607
             poDS->m_Shape == RSS
×
608
                 ? "RSS"
609
                 : (poDS->m_Shape == WHOLE_DISK ? "whole" : "split HRV"));
×
610

611
    /* -------------------------------------------------------------------- */
612
    /*      Create band information objects.                                */
613
    /* -------------------------------------------------------------------- */
614
    unsigned int i;
615
    unsigned int band_count = 1;
×
616
    unsigned int missing_band_count = 0;
×
617
    const unsigned char *bands = poDS->msg_reader_core->get_band_map();
×
618
    unsigned char band_map[MSG_NUM_CHANNELS + 1] = {
×
619
        0};  // map GDAL band numbers to MSG channels
620
    for (i = 0; i < MSG_NUM_CHANNELS; i++)
×
621
    {
622
        if (bands[i])
×
623
        {
624
            bool ok_to_add = false;
×
625
            switch (open_mode)
×
626
            {
627
                case MODE_VISIR:
×
628
                    ok_to_add = i < MSG_NUM_CHANNELS - 1;
×
629
                    break;
×
630
                case MODE_RAD:
×
631
                    ok_to_add = (i <= 2) ||
×
632
                                (Msg_reader_core::Blackbody_LUT[i + 1].B != 0);
×
633
                    break;
×
634
                case MODE_HRV:
×
635
                    ok_to_add = i == MSG_NUM_CHANNELS - 1;
×
636
                    break;
×
637
            }
638
            if (ok_to_add)
×
639
            {
640
                poDS->SetBand(band_count,
×
641
                              new MSGNRasterBand(poDS.get(), band_count,
×
642
                                                 open_mode, i + 1,
×
643
                                                 i + 1 - missing_band_count));
×
644
                band_map[band_count] = (unsigned char)(i + 1);
×
645
                band_count++;
×
646
            }
647
        }
648
        else
649
        {
650
            missing_band_count++;
×
651
        }
652
    }
653

654
    double pixel_gsd_x;
655
    double pixel_gsd_y;
656
    double origin_x;
657
    double origin_y;
658

659
    {
660
        const auto &idr = poDS->msg_reader_core->get_image_description_record();
×
661
        /* there are a number of 'magic' constants below
662
           I trimmed them to get registration for MSG4, MSG3, MSG2 with country
663
           outlines  from
664
           http://ec.europa.eu/eurostat/web/gisco/geodata/reference-data/administrative-units-statistical-units
665

666
           Adjust in two phases P1, P2.
667
           I describe direction as outline being NSEW of coast shape when number
668
           is changed
669
        */
670

671
        if (open_mode != MODE_HRV)
×
672
        {
673
            pixel_gsd_x =
×
674
                1000.0 * poDS->msg_reader_core
×
675
                             ->get_col_dir_step();  // convert from km to m
×
676
            pixel_gsd_y =
×
677
                1000.0 * poDS->msg_reader_core
×
678
                             ->get_line_dir_step();  // convert from km to m
×
679
            origin_x = -pixel_gsd_x * (-(Conversions::nlines / 2.0) +
×
680
                                       poDS->msg_reader_core->get_col_start() -
×
681
                                       1);  // all vis/NIR E-W -ve E
682
            origin_y = -pixel_gsd_y * ((Conversions::nlines / 2.0) -
×
683
                                       poDS->msg_reader_core->get_line_start() +
×
684
                                       1.5);  // set with 4  N-S +ve S
685
        }
686
        else
687
        {
688
            pixel_gsd_x =
×
689
                1000.0 * poDS->msg_reader_core
×
690
                             ->get_hrv_col_dir_step();  // convert from km to m
×
691
            pixel_gsd_y =
×
692
                1000.0 * poDS->msg_reader_core
×
693
                             ->get_hrv_line_dir_step();  // convert from km to m
×
694
            if (poDS->m_Shape == RSS)
×
695
            {
696
                origin_x = -pixel_gsd_x *
×
697
                           (-(3 * Conversions::nlines / 2.0) -
×
698
                            idr.plannedCoverage_hrv.lowerEastColumnPlanned -
×
699
                            1);  // MSG3 HRV E-W -ve E
700
                origin_y = -pixel_gsd_y *
×
701
                           ((3 * Conversions::nlines / 2.0) -
×
702
                            idr.plannedCoverage_hrv.lowerSouthLinePlanned +
×
703
                            2);  //          N-S -ve S
704
            }
705
            else
706
            {
707
                origin_x =
×
708
                    -pixel_gsd_x * (-(3 * Conversions::nlines / 2.0) +
×
709
                                    1 * poDS->msg_reader_core->get_col_start() -
×
710
                                    3);  // MSG4, MSG2 HRV E-W -ve E
711
                origin_y = -pixel_gsd_y *
×
712
                           ((3 * Conversions::nlines / 2.0) -
×
713
                            1 * poDS->msg_reader_core->get_line_start() +
×
714
                            4);  //                N-S +ve S
715
            }
716
        }
717

718
        /* the conversion to lat/long is in two parts:
719
           pixels to m (around imaginary circle r=sta height) in the geo
720
           projection (affine transformation) geo to lat/long via the GEOS
721
           projection (in WKT) and the ellipsoid
722

723
           CGMS/DOC/12/0017 section 4.4.2
724
        */
725

726
        poDS->m_gt[0] = -origin_x;
×
727
        poDS->m_gt[1] = pixel_gsd_x;
×
728
        poDS->m_gt[2] = 0.0;
×
729

730
        poDS->m_gt[3] = -origin_y;
×
731
        poDS->m_gt[4] = 0.0;
×
732
        poDS->m_gt[5] = -pixel_gsd_y;
×
733

734
        poDS->m_oSRS.SetProjCS("Geostationary projection (MSG)");
×
735

736
        poDS->m_oSRS.SetGeogCS(
×
737
            "MSG Ellipsoid", "MSG_DATUM", "MSG_SPHEROID",
738
            Conversions::req *
×
739
                1000.0,  // SetGeogCS doesn't specify length units, so all must
740
                         // be the same - here m.
741
            1.0 / Conversions::oblate  // 1 / ( 1 -
×
742
                                       // Conversions::rpol/Conversions::req)
743
        );
744

745
        poDS->m_oSRS.SetGEOS(
×
746
            idr.longitudeOfSSP,
×
747
            (Conversions::altitude - Conversions::req) *
×
748
                1000.0,  // we're using meters as length unit
749
            0.0,
750
            // false northing to handle the fact RSS is only 1/3 disk
751
            pixel_gsd_y *
752
                ((poDS->m_Shape == RSS)
×
753
                     ? ((open_mode != MODE_HRV)
×
754
                            ? -(idr.plannedCoverage_visir.southernLinePlanned -
×
755
                                1)
756
                            :  // MSG-3 vis/NIR N-S P2
757
                            -(idr.plannedCoverage_hrv.lowerSouthLinePlanned +
×
758
                              1))
759
                     :  // MSG-3 HRV N-S P2 -ve N
760
                     0.0));
761
    }
762

763
    const CALIBRATION *cal =
764
        poDS->msg_reader_core->get_calibration_parameters();
×
765
    char tagname[30];
766
    char field[300];
767

768
    poDS->SetMetadataItem("Radiometric parameters format", "offset slope");
×
769
    for (i = 1; i < band_count; i++)
×
770
    {
771
        snprintf(tagname, sizeof(tagname), "ch%02u_cal", band_map[i]);
×
772
        CPLsnprintf(field, sizeof(field), "%.12e %.12e",
×
773
                    cal[band_map[i] - 1].cal_offset,
×
774
                    cal[band_map[i] - 1].cal_slope);
×
775
        poDS->SetMetadataItem(tagname, field);
×
776
    }
777

778
    snprintf(
×
779
        field, sizeof(field), "%04u%02u%02u/%02u:%02u",
780
        poDS->msg_reader_core->get_year(), poDS->msg_reader_core->get_month(),
×
781
        poDS->msg_reader_core->get_day(), poDS->msg_reader_core->get_hour(),
×
782
        poDS->msg_reader_core->get_minute());
×
783
    poDS->SetMetadataItem("Date/Time", field);
×
784

785
    snprintf(field, sizeof(field), "%u %u",
×
786
             poDS->msg_reader_core->get_line_start(),
×
787
             poDS->msg_reader_core->get_col_start());
×
788
    poDS->SetMetadataItem("Origin", field);
×
789

790
    return poDS.release();
×
791
}
792

793
/************************************************************************/
794
/*                          GDALRegister_MSGN()                         */
795
/************************************************************************/
796

797
void GDALRegister_MSGN()
1,911✔
798

799
{
800
    if (GDALGetDriverByName("MSGN") != nullptr)
1,911✔
801
        return;
282✔
802

803
    GDALDriver *poDriver = new GDALDriver();
1,629✔
804

805
    poDriver->SetDescription("MSGN");
1,629✔
806
    poDriver->SetMetadataItem(GDAL_DCAP_RASTER, "YES");
1,629✔
807
    poDriver->SetMetadataItem(GDAL_DMD_LONGNAME,
1,629✔
808
                              "EUMETSAT Archive native (.nat)");
1,629✔
809
    poDriver->SetMetadataItem(GDAL_DMD_HELPTOPIC, "drivers/raster/msgn.html");
1,629✔
810
    poDriver->SetMetadataItem(GDAL_DMD_EXTENSION, "nat");
1,629✔
811
    poDriver->SetMetadataItem(GDAL_DCAP_VIRTUALIO, "YES");
1,629✔
812

813
    poDriver->pfnOpen = MSGNDataset::Open;
1,629✔
814

815
    GetGDALDriverManager()->RegisterDriver(poDriver);
1,629✔
816
}
STATUS · Troubleshooting · Open an Issue · Sales · Support · CAREERS · ENTERPRISE · START FREE · SCHEDULE DEMO
ANNOUNCEMENTS · TWITTER · TOS & SLA · Supported CI Services · What's a CI service? · Automated Testing

© 2025 Coveralls, Inc