Coverage Report

Created: 2026-06-30 08:33

next uncovered line (L), next uncovered region (R), next uncovered branch (B)
/src/gdal/frmts/netcdf/netcdf_sentinel3_sral_mwr.cpp
Line
Count
Source
1
/******************************************************************************
2
 *
3
 * Project:  netCDF read/write Driver
4
 * Purpose:  Sentinel 3 SRAL/MWR Level 2 products
5
 * Author:   Even Rouault <even.rouault at spatialys.com>
6
 *
7
 ******************************************************************************
8
 * Copyright (c) 2019, Even Rouault <even.rouault at spatialys.com>
9
 *
10
 * SPDX-License-Identifier: MIT
11
 ****************************************************************************/
12
13
// Example product:
14
// https://scihub.copernicus.eu/dhus/odata/v1/Products('65b615b0-0db9-4ced-8020-eb17818f0c26')/$value
15
// Specification:
16
// https://sentinel.esa.int/documents/247904/2753172/Sentinel-3-Product-Data-Format-Specification-Level-2-Land
17
18
#include "netcdfdataset.h"
19
20
#include <limits>
21
22
/************************************************************************/
23
/*                       Sentinel3_SRAL_MWR_Layer                       */
24
/************************************************************************/
25
26
class Sentinel3_SRAL_MWR_Layer final : public OGRLayer
27
{
28
    OGRFeatureDefn *m_poFDefn = nullptr;
29
    int m_cdfid;
30
    size_t m_nCurIdx = 0;
31
    size_t m_nFeatureCount = 0;
32
    CPLStringList m_aosMetadata{};
33
34
    struct VariableInfo
35
    {
36
        int varid;
37
        nc_type nctype;
38
        double scale;
39
        double offset;
40
        double nodata;
41
    };
42
43
    std::vector<VariableInfo> m_asVarInfo{};
44
    int m_iLongitude = -1;
45
    int m_iLatitude = -1;
46
    double m_dfLongScale = 1.0;
47
    double m_dfLongOffset = 0.0;
48
    double m_dfLatScale = 1.0;
49
    double m_dfLatOffset = 0.0;
50
51
    OGRFeature *TranslateFeature(size_t nIndex);
52
    OGRFeature *GetNextRawFeature();
53
54
  public:
55
    Sentinel3_SRAL_MWR_Layer(const std::string &name, int cdfid, int dimid);
56
    ~Sentinel3_SRAL_MWR_Layer() override;
57
58
    const OGRFeatureDefn *GetLayerDefn() const override
59
0
    {
60
0
        return m_poFDefn;
61
0
    }
62
63
    void ResetReading() override;
64
    OGRFeature *GetNextFeature() override;
65
    OGRFeature *GetFeature(GIntBig nFID) override;
66
    GIntBig GetFeatureCount(int bForce) override;
67
    int TestCapability(const char *pszCap) const override;
68
    CSLConstList GetMetadata(const char *pszDomain) override;
69
    const char *GetMetadataItem(const char *pszKey,
70
                                const char *pszDomain) override;
71
};
72
73
/************************************************************************/
74
/*                      Sentinel3_SRAL_MWR_Layer()                      */
75
/************************************************************************/
76
77
Sentinel3_SRAL_MWR_Layer::Sentinel3_SRAL_MWR_Layer(const std::string &name,
78
                                                   int cdfid, int dimid)
79
0
    : m_cdfid(cdfid)
80
0
{
81
0
    m_poFDefn = new OGRFeatureDefn(name.c_str());
82
0
    m_poFDefn->SetGeomType(wkbPoint);
83
0
    m_poFDefn->Reference();
84
0
    SetDescription(name.c_str());
85
86
0
    nc_inq_dimlen(cdfid, dimid, &m_nFeatureCount);
87
88
0
    int nVars = 0;
89
0
    NCDF_ERR(nc_inq(cdfid, nullptr, &nVars, nullptr, nullptr));
90
0
    for (int iVar = 0; iVar < nVars; iVar++)
91
0
    {
92
0
        int nVarDims = 0;
93
0
        NCDF_ERR(nc_inq_varndims(cdfid, iVar, &nVarDims));
94
0
        if (nVarDims != 1)
95
0
            continue;
96
97
0
        int vardimid = -1;
98
0
        NCDF_ERR(nc_inq_vardimid(cdfid, iVar, &vardimid));
99
0
        if (vardimid != dimid)
100
0
            continue;
101
102
0
        char szVarName[NC_MAX_NAME + 1] = {};
103
0
        NCDF_ERR(nc_inq_varname(cdfid, iVar, szVarName));
104
105
0
        nc_type vartype = NC_NAT;
106
0
        nc_inq_vartype(cdfid, iVar, &vartype);
107
108
0
        int nbAttr = 0;
109
0
        NCDF_ERR(nc_inq_varnatts(cdfid, iVar, &nbAttr));
110
0
        std::string scaleFactor;
111
0
        std::string offset;
112
0
        std::string fillValue;
113
0
        CPLStringList aosMetadata;
114
0
        for (int iAttr = 0; iAttr < nbAttr; iAttr++)
115
0
        {
116
0
            char szAttrName[NC_MAX_NAME + 1];
117
0
            szAttrName[0] = 0;
118
0
            NCDF_ERR(nc_inq_attname(cdfid, iVar, iAttr, szAttrName));
119
0
            char *pszMetaTemp = nullptr;
120
0
            if (NCDFGetAttr(cdfid, iVar, szAttrName, &pszMetaTemp) == CE_None &&
121
0
                pszMetaTemp)
122
0
            {
123
0
                if (EQUAL(szAttrName, "scale_factor"))
124
0
                {
125
0
                    scaleFactor = pszMetaTemp;
126
0
                }
127
0
                else if (EQUAL(szAttrName, "add_offset"))
128
0
                {
129
0
                    offset = pszMetaTemp;
130
0
                }
131
0
                else if (EQUAL(szAttrName, "_FillValue"))
132
0
                {
133
0
                    fillValue = pszMetaTemp;
134
0
                }
135
0
                else if (!EQUAL(szAttrName, "coordinates"))
136
0
                {
137
0
                    aosMetadata.SetNameValue(szAttrName, pszMetaTemp);
138
0
                }
139
0
            }
140
0
            CPLFree(pszMetaTemp);
141
0
        }
142
143
0
        const char *pszStandardName =
144
0
            aosMetadata.FetchNameValue("standard_name");
145
0
        if (pszStandardName)
146
0
        {
147
0
            if (EQUAL(pszStandardName, "longitude") && vartype == NC_INT)
148
0
            {
149
0
                m_iLongitude = iVar;
150
0
                if (!scaleFactor.empty())
151
0
                    m_dfLongScale = CPLAtof(scaleFactor.c_str());
152
0
                if (!offset.empty())
153
0
                    m_dfLongOffset = CPLAtof(offset.c_str());
154
0
                continue;
155
0
            }
156
0
            if (EQUAL(pszStandardName, "latitude") && vartype == NC_INT)
157
0
            {
158
0
                m_iLatitude = iVar;
159
0
                if (!scaleFactor.empty())
160
0
                    m_dfLatScale = CPLAtof(scaleFactor.c_str());
161
0
                if (!offset.empty())
162
0
                    m_dfLatOffset = CPLAtof(offset.c_str());
163
0
                continue;
164
0
            }
165
0
        }
166
167
0
        for (int i = 0; i < aosMetadata.size(); i++)
168
0
            m_aosMetadata.AddString(
169
0
                (std::string(szVarName) + '_' + aosMetadata[i]).c_str());
170
171
0
        OGRFieldType eType = OFTReal;
172
0
        if (!scaleFactor.empty())
173
0
        {
174
            // do nothing
175
0
        }
176
0
        else if (!offset.empty())
177
0
        {
178
            // do nothing
179
0
        }
180
0
        else if (vartype == NC_BYTE || vartype == NC_SHORT ||
181
0
                 vartype == NC_INT || vartype == NC_USHORT ||
182
0
                 vartype == NC_UINT)
183
0
        {
184
0
            eType = OFTInteger;
185
0
        }
186
0
        OGRFieldDefn oField(szVarName, eType);
187
0
        m_poFDefn->AddFieldDefn(&oField);
188
0
        VariableInfo varInfo;
189
0
        varInfo.varid = iVar;
190
0
        varInfo.nctype = vartype;
191
0
        varInfo.scale =
192
0
            scaleFactor.empty() ? 1.0 : CPLAtof(scaleFactor.c_str());
193
0
        varInfo.offset = offset.empty() ? 0.0 : CPLAtof(offset.c_str());
194
0
        varInfo.nodata = fillValue.empty()
195
0
                             ? std::numeric_limits<double>::quiet_NaN()
196
0
                             : CPLAtof(fillValue.c_str());
197
0
        m_asVarInfo.emplace_back(varInfo);
198
0
    }
199
0
}
200
201
/************************************************************************/
202
/*                     ~Sentinel3_SRAL_MWR_Layer()                      */
203
/************************************************************************/
204
205
Sentinel3_SRAL_MWR_Layer::~Sentinel3_SRAL_MWR_Layer()
206
0
{
207
0
    m_poFDefn->Release();
208
0
}
209
210
/************************************************************************/
211
/*                            GetMetadata()                             */
212
/************************************************************************/
213
214
CSLConstList Sentinel3_SRAL_MWR_Layer::GetMetadata(const char *pszDomain)
215
0
{
216
0
    if (pszDomain == nullptr || EQUAL(pszDomain, ""))
217
0
        return m_aosMetadata.List();
218
0
    return OGRLayer::GetMetadata(pszDomain);
219
0
}
220
221
/************************************************************************/
222
/*                          GetMetadataItem()                           */
223
/************************************************************************/
224
225
const char *Sentinel3_SRAL_MWR_Layer::GetMetadataItem(const char *pszKey,
226
                                                      const char *pszDomain)
227
0
{
228
0
    if (pszDomain == nullptr || EQUAL(pszDomain, ""))
229
0
        return m_aosMetadata.FetchNameValue(pszKey);
230
0
    return OGRLayer::GetMetadataItem(pszKey, pszDomain);
231
0
}
232
233
/************************************************************************/
234
/*                            ResetReading()                            */
235
/************************************************************************/
236
237
void Sentinel3_SRAL_MWR_Layer::ResetReading()
238
0
{
239
0
    m_nCurIdx = 0;
240
0
}
241
242
/************************************************************************/
243
/*                          GetFeatureCount()                           */
244
/************************************************************************/
245
246
GIntBig Sentinel3_SRAL_MWR_Layer::GetFeatureCount(int bForce)
247
0
{
248
0
    if (m_poFilterGeom == nullptr && m_poAttrQuery == nullptr)
249
0
        return m_nFeatureCount;
250
0
    return OGRLayer::GetFeatureCount(bForce);
251
0
}
252
253
/************************************************************************/
254
/*                           TestCapability()                           */
255
/************************************************************************/
256
257
int Sentinel3_SRAL_MWR_Layer::TestCapability(const char *pszCap) const
258
0
{
259
0
    if (EQUAL(pszCap, OLCFastFeatureCount))
260
0
        return m_poFilterGeom == nullptr && m_poAttrQuery == nullptr;
261
0
    if (EQUAL(pszCap, OLCRandomRead))
262
0
        return true;
263
0
    return false;
264
0
}
265
266
/************************************************************************/
267
/*                          TranslateFeature()                          */
268
/************************************************************************/
269
270
OGRFeature *Sentinel3_SRAL_MWR_Layer::TranslateFeature(size_t nIndex)
271
0
{
272
0
    OGRFeature *poFeat = new OGRFeature(m_poFDefn);
273
0
    poFeat->SetFID(nIndex + 1);
274
0
    if (m_iLongitude >= 0 && m_iLatitude >= 0)
275
0
    {
276
0
        int nLong = 0;
277
0
        int status = nc_get_var1_int(m_cdfid, m_iLongitude, &nIndex, &nLong);
278
0
        if (status == NC_NOERR)
279
0
        {
280
0
            int nLat = 0;
281
0
            status = nc_get_var1_int(m_cdfid, m_iLatitude, &nIndex, &nLat);
282
0
            if (status == NC_NOERR)
283
0
            {
284
0
                const double dfLong = nLong * m_dfLongScale + m_dfLongOffset;
285
0
                const double dfLat = nLat * m_dfLatScale + m_dfLatOffset;
286
0
                auto poGeom = new OGRPoint(dfLong, dfLat);
287
0
                auto poGeomField = m_poFDefn->GetGeomFieldDefn(0);
288
0
                poGeom->assignSpatialReference(poGeomField->GetSpatialRef());
289
0
                poFeat->SetGeometryDirectly(poGeom);
290
0
            }
291
0
        }
292
0
    }
293
294
0
    for (int i = 0; i < static_cast<int>(m_asVarInfo.size()); i++)
295
0
    {
296
0
        if (m_asVarInfo[i].nctype == NC_BYTE)
297
0
        {
298
0
            signed char nVal = 0;
299
0
            int status = nc_get_var1_schar(m_cdfid, m_asVarInfo[i].varid,
300
0
                                           &nIndex, &nVal);
301
0
            if (status == NC_NOERR && nVal != m_asVarInfo[i].nodata)
302
0
            {
303
0
                poFeat->SetField(i, nVal * m_asVarInfo[i].scale +
304
0
                                        m_asVarInfo[i].offset);
305
0
            }
306
0
        }
307
0
        else if (m_asVarInfo[i].nctype == NC_SHORT)
308
0
        {
309
0
            short nVal = 0;
310
0
            int status = nc_get_var1_short(m_cdfid, m_asVarInfo[i].varid,
311
0
                                           &nIndex, &nVal);
312
0
            if (status == NC_NOERR && nVal != m_asVarInfo[i].nodata)
313
0
            {
314
0
                poFeat->SetField(i, nVal * m_asVarInfo[i].scale +
315
0
                                        m_asVarInfo[i].offset);
316
0
            }
317
0
        }
318
0
        else if (m_asVarInfo[i].nctype == NC_USHORT)
319
0
        {
320
0
            unsigned short nVal = 0;
321
0
            int status = nc_get_var1_ushort(m_cdfid, m_asVarInfo[i].varid,
322
0
                                            &nIndex, &nVal);
323
0
            if (status == NC_NOERR && nVal != m_asVarInfo[i].nodata)
324
0
            {
325
0
                poFeat->SetField(i, nVal * m_asVarInfo[i].scale +
326
0
                                        m_asVarInfo[i].offset);
327
0
            }
328
0
        }
329
0
        else if (m_asVarInfo[i].nctype == NC_INT)
330
0
        {
331
0
            int nVal = 0;
332
0
            int status =
333
0
                nc_get_var1_int(m_cdfid, m_asVarInfo[i].varid, &nIndex, &nVal);
334
0
            if (status == NC_NOERR && nVal != m_asVarInfo[i].nodata)
335
0
            {
336
0
                poFeat->SetField(i, nVal * m_asVarInfo[i].scale +
337
0
                                        m_asVarInfo[i].offset);
338
0
            }
339
0
        }
340
0
        else if (m_asVarInfo[i].nctype == NC_UINT)
341
0
        {
342
0
            unsigned int nVal = 0;
343
0
            int status =
344
0
                nc_get_var1_uint(m_cdfid, m_asVarInfo[i].varid, &nIndex, &nVal);
345
0
            if (status == NC_NOERR && nVal != m_asVarInfo[i].nodata)
346
0
            {
347
0
                poFeat->SetField(i, nVal * m_asVarInfo[i].scale +
348
0
                                        m_asVarInfo[i].offset);
349
0
            }
350
0
        }
351
0
        else if (m_asVarInfo[i].nctype == NC_DOUBLE)
352
0
        {
353
0
            double dfVal = 0.0;
354
0
            int status = nc_get_var1_double(m_cdfid, m_asVarInfo[i].varid,
355
0
                                            &nIndex, &dfVal);
356
0
            if (status == NC_NOERR && dfVal != m_asVarInfo[i].nodata)
357
0
            {
358
0
                poFeat->SetField(i, dfVal * m_asVarInfo[i].scale +
359
0
                                        m_asVarInfo[i].offset);
360
0
            }
361
0
        }
362
0
        else
363
0
        {
364
0
            CPLDebug("netCDF", "Unhandled data type %d for %s",
365
0
                     m_asVarInfo[i].nctype,
366
0
                     m_poFDefn->GetFieldDefn(i)->GetNameRef());
367
0
        }
368
0
    }
369
370
0
    return poFeat;
371
0
}
372
373
/************************************************************************/
374
/*                         GetNextRawFeature()                          */
375
/************************************************************************/
376
377
OGRFeature *Sentinel3_SRAL_MWR_Layer::GetNextRawFeature()
378
0
{
379
0
    if (m_nCurIdx == m_nFeatureCount)
380
0
        return nullptr;
381
0
    OGRFeature *poFeat = TranslateFeature(m_nCurIdx);
382
0
    m_nCurIdx++;
383
0
    return poFeat;
384
0
}
385
386
/************************************************************************/
387
/*                             GetFeature()                             */
388
/************************************************************************/
389
390
OGRFeature *Sentinel3_SRAL_MWR_Layer::GetFeature(GIntBig nFID)
391
0
{
392
0
    if (nFID <= 0 || static_cast<size_t>(nFID) > m_nFeatureCount)
393
0
        return nullptr;
394
0
    return TranslateFeature(static_cast<size_t>(nFID - 1));
395
0
}
396
397
/************************************************************************/
398
/*                           GetNextFeature()                           */
399
/************************************************************************/
400
401
OGRFeature *Sentinel3_SRAL_MWR_Layer::GetNextFeature()
402
0
{
403
0
    while (true)
404
0
    {
405
0
        OGRFeature *poFeature = GetNextRawFeature();
406
0
        if (poFeature == nullptr)
407
0
            return nullptr;
408
409
0
        if ((m_poFilterGeom == nullptr ||
410
0
             FilterGeometry(poFeature->GetGeomFieldRef(m_iGeomFieldFilter))) &&
411
0
            (m_poAttrQuery == nullptr || m_poAttrQuery->Evaluate(poFeature)))
412
0
            return poFeature;
413
414
0
        delete poFeature;
415
0
    }
416
0
}
417
418
/************************************************************************/
419
/*                     ProcessSentinel3_SRAL_MWR()                      */
420
/************************************************************************/
421
422
void netCDFDataset::ProcessSentinel3_SRAL_MWR()
423
0
{
424
0
    int nDimCount = -1;
425
0
    int status = nc_inq_ndims(cdfid, &nDimCount);
426
0
    NCDF_ERR(status);
427
0
    if (status != NC_NOERR || nDimCount == 0 || nDimCount > 1000)
428
0
        return;
429
0
    std::vector<int> dimIds(nDimCount);
430
0
    int nDimCount2 = -1;
431
0
    status = nc_inq_dimids(cdfid, &nDimCount2, &dimIds[0], FALSE);
432
0
    NCDF_ERR(status);
433
0
    if (status != NC_NOERR)
434
0
        return;
435
0
    CPLAssert(nDimCount == nDimCount2);
436
437
0
    OGRSpatialReference *poSRS = nullptr;
438
0
    const char *pszSemiMajor =
439
0
        aosMetadata.FetchNameValue("NC_GLOBAL#semi_major_ellipsoid_axis");
440
0
    const char *pszFlattening =
441
0
        aosMetadata.FetchNameValue("NC_GLOBAL#ellipsoid_flattening");
442
0
    if (pszSemiMajor && EQUAL(pszSemiMajor, "6378137") && pszFlattening &&
443
0
        fabs(CPLAtof(pszFlattening) - 0.00335281066474748) < 1e-16)
444
0
    {
445
0
        int iAttr = aosMetadata.FindName("NC_GLOBAL#semi_major_ellipsoid_axis");
446
0
        if (iAttr >= 0)
447
0
            aosMetadata.RemoveStrings(iAttr, 1);
448
449
0
        iAttr = aosMetadata.FindName("NC_GLOBAL#ellipsoid_flattening");
450
0
        if (iAttr >= 0)
451
0
            aosMetadata.RemoveStrings(iAttr, 1);
452
0
        poSRS = new OGRSpatialReference();
453
0
        poSRS->SetAxisMappingStrategy(OAMS_TRADITIONAL_GIS_ORDER);
454
0
        poSRS->importFromEPSG(4326);
455
0
    }
456
457
0
    for (int i = 0; i < nDimCount; i++)
458
0
    {
459
0
        char szDimName[NC_MAX_NAME + 1] = {};
460
0
        status = nc_inq_dimname(cdfid, dimIds[i], szDimName);
461
0
        NCDF_ERR(status);
462
0
        if (status != NC_NOERR)
463
0
            break;
464
0
        std::string name(CPLGetBasenameSafe(GetDescription()));
465
0
        name += '_';
466
0
        name += szDimName;
467
0
        std::shared_ptr<OGRLayer> poLayer(
468
0
            new Sentinel3_SRAL_MWR_Layer(name.c_str(), cdfid, dimIds[i]));
469
0
        auto poGeomField = poLayer->GetLayerDefn()->GetGeomFieldDefn(0);
470
0
        if (poGeomField)
471
0
            poGeomField->SetSpatialRef(poSRS);
472
0
        papoLayers.emplace_back(poLayer);
473
0
    }
474
475
0
    if (poSRS)
476
0
        poSRS->Release();
477
0
}