Coverage Report

Created: 2026-06-30 08:33

next uncovered line (L), next uncovered region (R), next uncovered branch (B)
/src/gdal/ogr/ogrsf_frmts/s101/ogrs101readersurface.cpp
Line
Count
Source
1
/******************************************************************************
2
 *
3
 * Project:  S-101 driver
4
 * Purpose:  Implements OGRS101Reader
5
 * Author:   Even Rouault <even dot rouault at spatialys.com>
6
 *
7
 ******************************************************************************
8
 * Copyright (c) 2026, Even Rouault <even dot rouault at spatialys.com>
9
 *
10
 * SPDX-License-Identifier: MIT
11
 ****************************************************************************/
12
13
#include "ogr_s101.h"
14
#include "ogrs101readerconstants.h"
15
16
#include <algorithm>
17
#include <memory>
18
19
/************************************************************************/
20
/*                      CreateSurfaceFeatureDefn()                      */
21
/************************************************************************/
22
23
/** Create the feature definition for the Surface layer
24
 */
25
bool OGRS101Reader::CreateSurfaceFeatureDefn()
26
1.46k
{
27
1.46k
    if (m_oSurfaceRecordIndex.GetCount() > 0)
28
446
    {
29
446
        m_poFeatureDefnSurface =
30
446
            OGRFeatureDefnRefCountedPtr::makeInstance(OGR_LAYER_NAME_SURFACE);
31
446
        m_poFeatureDefnSurface->SetGeomType(wkbPolygon);
32
446
        auto oSRSIter = m_oMapSRS.find(HORIZONTAL_CRS_ID);
33
446
        if (oSRSIter != m_oMapSRS.end())
34
446
        {
35
446
            m_poFeatureDefnSurface->GetGeomFieldDefn(0)->SetSpatialRef(
36
446
                OGRSpatialReferenceRefCountedPtr::makeClone(&oSRSIter->second)
37
446
                    .get());
38
446
        }
39
446
        m_poFeatureDefnSurface->GetGeomFieldDefn(0)->SetCoordinatePrecision(
40
446
            m_coordinatePrecision);
41
446
        {
42
446
            OGRFieldDefn oFieldDefn(OGR_FIELD_NAME_RECORD_ID, OFTInteger);
43
446
            m_poFeatureDefnSurface->AddFieldDefn(&oFieldDefn);
44
446
        }
45
446
        {
46
446
            OGRFieldDefn oFieldDefn(OGR_FIELD_NAME_RECORD_VERSION, OFTInteger);
47
446
            m_poFeatureDefnSurface->AddFieldDefn(&oFieldDefn);
48
446
        }
49
446
        if (!InferFeatureDefn(m_oSurfaceRecordIndex, SRID_FIELD, INAS_FIELD, {},
50
446
                              *m_poFeatureDefnSurface, m_oMapFieldDomains))
51
5
        {
52
5
            return false;
53
5
        }
54
446
    }
55
56
1.45k
    return true;
57
1.46k
}
58
59
/************************************************************************/
60
/*                        ReadSurfaceGeometry()                         */
61
/************************************************************************/
62
63
std::unique_ptr<OGRPolygon>
64
OGRS101Reader::ReadSurfaceGeometry(const DDFRecord *poRecord, int iRecord,
65
                                   int nRecordID,
66
                                   const OGRSpatialReference *poSRS) const
67
519
{
68
519
    if (nRecordID < 0)
69
325
        nRecordID = poRecord->GetIntSubfield(SRID_FIELD, 0, RCID_SUBFIELD, 0);
70
71
519
    const auto GetErrorContext = [iRecord, nRecordID]()
72
519
    {
73
129
        if (iRecord >= 0)
74
96
            return CPLSPrintf("Record index=%d of SRID", iRecord);
75
33
        else
76
33
            return CPLSPrintf("Record ID=%d of SRID", nRecordID);
77
129
    };
78
79
519
    if (!poRecord->FindField(RIAS_FIELD))
80
1
    {
81
1
        CPL_IGNORE_RET_VAL(EMIT_ERROR_OR_WARNING(
82
1
            CPLSPrintf("%s: no RIAS field", GetErrorContext())));
83
1
        return nullptr;
84
1
    }
85
86
518
    auto poSurface = std::make_unique<OGRPolygon>();
87
518
    poSurface->assignSpatialReference(poSRS);
88
518
    std::unique_ptr<OGRLinearRing> poExteriorRing;
89
518
    std::vector<std::unique_ptr<OGRLinearRing>> apoInteriorRings;
90
518
    for (const auto *poRIASField : poRecord->GetFields(RIAS_FIELD))
91
518
    {
92
518
        const int nRings = poRIASField->GetRepeatCount();
93
1.06k
        for (int iRing = 0; iRing < nRings; ++iRing)
94
855
        {
95
855
            const auto GetIntSubfield =
96
855
                [poRecord, poRIASField, iRing](const char *pszSubFieldName)
97
3.15k
            {
98
3.15k
                return poRecord->GetIntSubfield(poRIASField, pszSubFieldName,
99
3.15k
                                                iRing);
100
3.15k
            };
101
102
855
            const int nRAUI = GetIntSubfield(RAUI_SUBFIELD);
103
855
            if (nRAUI != INSTRUCTION_INSERT)
104
7
            {
105
7
                CPL_IGNORE_RET_VAL(EMIT_ERROR_OR_WARNING(
106
7
                    CPLSPrintf("%s: wrong value %d for RAUI "
107
7
                               "subfield of %d instance of RIAS field.",
108
7
                               GetErrorContext(), nRAUI, iRing)));
109
7
                return nullptr;
110
7
            }
111
112
848
            const RecordName nRRNM = GetIntSubfield(RRNM_SUBFIELD);
113
848
            if (nRRNM != RECORD_NAME_CURVE &&
114
350
                nRRNM != RECORD_NAME_COMPOSITE_CURVE)
115
12
            {
116
12
                CPL_IGNORE_RET_VAL(EMIT_ERROR_OR_WARNING(CPLSPrintf(
117
12
                    "%s: Invalid value for RRNM "
118
12
                    "subfield of %d instance of RIAS field: "
119
12
                    "got %d, expected %d or %d.",
120
12
                    GetErrorContext(), iRing, static_cast<int>(nRRNM),
121
12
                    static_cast<int>(RECORD_NAME_CURVE),
122
12
                    static_cast<int>(RECORD_NAME_COMPOSITE_CURVE))));
123
12
                return nullptr;
124
12
            }
125
126
836
            const int nRRID = GetIntSubfield(RRID_SUBFIELD);
127
836
            std::unique_ptr<OGRLineString> poCurvePart;
128
836
            if (nRRNM == RECORD_NAME_CURVE)
129
498
            {
130
498
                const auto poCurveRecord =
131
498
                    m_oCurveRecordIndex.FindRecord(nRRID);
132
498
                if (!poCurveRecord)
133
16
                {
134
16
                    CPL_IGNORE_RET_VAL(EMIT_ERROR_OR_WARNING(CPLSPrintf(
135
16
                        "%s: Value (RRNM=%d, RRID=%d) "
136
16
                        "of instance %d of RIAS field does not point to an "
137
16
                        "existing record.",
138
16
                        GetErrorContext(), static_cast<int>(nRRNM), nRRID,
139
16
                        iRing)));
140
16
                    return nullptr;
141
16
                }
142
143
482
                poCurvePart = ReadCurveGeometry(
144
482
                    poCurveRecord, /* iRecord = */ -1, nRRID, poSRS);
145
482
            }
146
338
            else
147
338
            {
148
338
                CPLAssert(nRRNM == RECORD_NAME_COMPOSITE_CURVE);
149
150
338
                const auto poCompositeCurveRecord =
151
338
                    m_oCompositeCurveRecordIndex.FindRecord(nRRID);
152
338
                if (!poCompositeCurveRecord)
153
9
                {
154
9
                    CPL_IGNORE_RET_VAL(EMIT_ERROR_OR_WARNING(CPLSPrintf(
155
9
                        "%s: Value (RRNM=%d, RRID=%d) "
156
9
                        "of instance %d of RIAS field does not point to an "
157
9
                        "existing record.",
158
9
                        GetErrorContext(), static_cast<int>(nRRNM), nRRID,
159
9
                        iRing)));
160
9
                    return nullptr;
161
9
                }
162
163
329
                poCurvePart = ReadCompositeCurveGeometry(
164
329
                    poCompositeCurveRecord, /* iRecord = */ -1, nRRID, poSRS);
165
329
            }
166
167
811
            if (!poCurvePart)
168
192
            {
169
192
                return nullptr;
170
192
            }
171
172
619
            bool bReverse = false;
173
619
            const int nORNT = GetIntSubfield(ORNT_SUBFIELD);
174
619
            if (nORNT == ORNT_REVERSE)
175
373
            {
176
373
                bReverse = true;
177
373
            }
178
246
            else if (nORNT != ORNT_FORWARD)
179
4
            {
180
4
                CPL_IGNORE_RET_VAL(EMIT_ERROR_OR_WARNING(
181
4
                    CPLSPrintf("%s: Invalid value for ORNT "
182
4
                               "subfield of %d instance of RIAS field: "
183
4
                               "got %d, expected %d or %d.",
184
4
                               GetErrorContext(), iRing, nORNT, ORNT_FORWARD,
185
4
                               ORNT_REVERSE)));
186
4
                return nullptr;
187
4
            }
188
189
615
            auto poRing = std::make_unique<OGRLinearRing>();
190
615
            if (bReverse && poCurvePart->getNumPoints() > 0)
191
363
                poRing->addSubLineString(poCurvePart.get(),
192
363
                                         poCurvePart->getNumPoints() - 1, 0);
193
252
            else
194
252
                poRing->addSubLineString(poCurvePart.get());
195
615
            if (poRing->getNumPoints() <= 2 || !poRing->get_IsClosed())
196
20
            {
197
20
                CPL_IGNORE_RET_VAL(EMIT_ERROR_OR_WARNING(
198
20
                    CPLSPrintf("%s: Ring of index %d is not closed.",
199
20
                               GetErrorContext(), iRing)));
200
20
                return nullptr;
201
20
            }
202
203
595
            constexpr int USAG_EXTERIOR = 1;  // Exterior ring
204
595
            constexpr int USAG_INTERIOR = 2;  // Interior ring
205
595
            const int nUSAG =
206
595
                poRecord->GetIntSubfield(RIAS_FIELD, 0, USAG_SUBFIELD, iRing);
207
595
            if (nUSAG == USAG_EXTERIOR)
208
368
            {
209
368
                if (poExteriorRing)
210
11
                {
211
11
                    CPL_IGNORE_RET_VAL(EMIT_ERROR_OR_WARNING(CPLSPrintf(
212
11
                        "%s: several rings tagged as exterior rings.",
213
11
                        GetErrorContext())));
214
11
                    return nullptr;
215
11
                }
216
357
                if (!poRing->isClockwise())
217
13
                {
218
13
                    if (!EMIT_ERROR_OR_WARNING(CPLSPrintf(
219
13
                            "%s: exterior ring orientation is not clockwise.",
220
13
                            GetErrorContext())))
221
13
                    {
222
13
                        return nullptr;
223
13
                    }
224
13
                }
225
344
                poExteriorRing = std::move(poRing);
226
344
            }
227
227
            else if (nUSAG == USAG_INTERIOR)
228
219
            {
229
219
                if (poRing->isClockwise())
230
14
                {
231
14
                    if (!EMIT_ERROR_OR_WARNING(
232
14
                            CPLSPrintf("%s: orientation of interior ring of "
233
14
                                       "index %d is not counter-clockwise.",
234
14
                                       GetErrorContext(), iRing)))
235
14
                    {
236
14
                        return nullptr;
237
14
                    }
238
14
                }
239
205
                apoInteriorRings.push_back(std::move(poRing));
240
205
            }
241
8
            else
242
8
            {
243
8
                CPL_IGNORE_RET_VAL(EMIT_ERROR_OR_WARNING(
244
8
                    CPLSPrintf("%s: Invalid value for USAG "
245
8
                               "subfield of %d instance of RIAS field: "
246
8
                               "got %d, expected %d or %d.",
247
8
                               GetErrorContext(), iRing, nUSAG, USAG_EXTERIOR,
248
8
                               USAG_INTERIOR)));
249
8
                return nullptr;
250
8
            }
251
595
        }
252
518
    }
253
254
212
    if (!poExteriorRing)
255
14
    {
256
14
        CPL_IGNORE_RET_VAL(EMIT_ERROR_OR_WARNING(CPLSPrintf(
257
14
            "%s: no ring tagged as exterior ring.", GetErrorContext())));
258
14
        return nullptr;
259
14
    }
260
261
198
    poSurface->addRing(std::move(poExteriorRing));
262
198
    for (auto &poRing : apoInteriorRings)
263
184
        poSurface->addRing(std::move(poRing));
264
265
198
    if (OGRGeometryFactory::haveGEOS())
266
0
    {
267
0
        std::string osReason;
268
0
        if (!poSurface->IsValid(&osReason))
269
0
        {
270
0
            if (!EMIT_ERROR_OR_WARNING(CPLSPrintf("%s: surface is invalid: %s.",
271
0
                                                  GetErrorContext(),
272
0
                                                  osReason.c_str())))
273
0
            {
274
0
                return nullptr;
275
0
            }
276
0
        }
277
0
    }
278
279
198
    return poSurface;
280
198
}
281
282
/************************************************************************/
283
/*                         FillFeatureSurface()                         */
284
/************************************************************************/
285
286
/** Fill the content of the provided feature from the identified record
287
 * (of m_oSurfaceRecordIndex).
288
 */
289
bool OGRS101Reader::FillFeatureSurface(const DDFRecordIndex &oIndex,
290
                                       int iRecord, OGRFeature &oFeature) const
291
325
{
292
325
    const auto poRecord = oIndex.GetByIndex(iRecord);
293
325
    CPLAssert(poRecord);
294
295
325
    const OGRSpatialReference *poSRS =
296
325
        oFeature.GetDefnRef()->GetGeomFieldDefn(0)->GetSpatialRef();
297
325
    auto poSurface =
298
325
        ReadSurfaceGeometry(poRecord, iRecord, /* nRecordID = */ -1, poSRS);
299
325
    if (!poSurface)
300
239
    {
301
239
        if (m_bStrict)
302
239
            return false;  // error message already emitted
303
239
    }
304
86
    else
305
86
    {
306
86
        oFeature.SetGeometry(std::move(poSurface));
307
86
    }
308
309
86
    return FillFeatureAttributes(oIndex, iRecord, INAS_FIELD, oFeature) &&
310
86
           FillFeatureWithNonAttrAssocSubfields(poRecord, iRecord, INAS_FIELD,
311
86
                                                oFeature);
312
325
}
313
314
/************************************************************************/
315
/*                     ProcessUpdateRecordSurface()                     */
316
/************************************************************************/
317
318
/** Updates the geometry part of poTargetRecord with poUpdateRecord */
319
bool OGRS101Reader::ProcessUpdateRecordSurface(const DDFRecord *poUpdateRecord,
320
                                               DDFRecord *poTargetRecord) const
321
18
{
322
18
    const auto poIDField = poUpdateRecord->GetField(0);
323
18
    CPLAssert(poIDField);
324
325
    // Record name
326
18
    const RecordName nRCNM =
327
18
        poUpdateRecord->GetIntSubfield(poIDField, RCNM_SUBFIELD, 0);
328
329
    // Record identifier
330
18
    const int nRCID =
331
18
        poUpdateRecord->GetIntSubfield(poIDField, RCID_SUBFIELD, 0);
332
333
    // Ring Association Field field
334
18
    const auto apoUpdateFields = poUpdateRecord->GetFields(RIAS_FIELD);
335
18
    if (apoUpdateFields.empty())
336
1
        return true;
337
338
17
    struct Ring
339
17
    {
340
17
        int RRNM = 0;
341
17
        int RRID = 0;
342
17
        int ORNT = 0;
343
17
        int USAG = 0;
344
17
        int RAUI = 0;
345
346
17
        static Ring Read(const DDFRecord *poRecord, const DDFField *poField,
347
17
                         int i)
348
54
        {
349
54
            Ring ring;
350
54
            ring.RRNM = poRecord->GetIntSubfield(poField, RRNM_SUBFIELD, i);
351
54
            ring.RRID = poRecord->GetIntSubfield(poField, RRID_SUBFIELD, i);
352
54
            ring.ORNT = poRecord->GetIntSubfield(poField, ORNT_SUBFIELD, i);
353
54
            ring.USAG = poRecord->GetIntSubfield(poField, USAG_SUBFIELD, i);
354
54
            ring.RAUI = poRecord->GetIntSubfield(poField, RAUI_SUBFIELD, i);
355
54
            return ring;
356
54
        }
357
17
    };
358
359
17
    std::vector<Ring> asTarget;
360
    // Ingest the existing/target record(s)
361
17
    auto apoTargetFields = poTargetRecord->GetFields(RIAS_FIELD);
362
17
    for (auto *poTargetField : apoTargetFields)
363
17
    {
364
17
        const int nTargetRepeatCount = poTargetField->GetRepeatCount();
365
51
        for (int i = 0; i < nTargetRepeatCount; ++i)
366
34
        {
367
34
            asTarget.push_back(Ring::Read(poTargetRecord, poTargetField, i));
368
34
        }
369
17
        poTargetRecord->DeleteField(poTargetField);
370
17
    }
371
372
    // Apply the update record(s)
373
17
    for (auto *poUpdateField : apoUpdateFields)
374
17
    {
375
17
        const int nUpdateRepeatCount = poUpdateField->GetRepeatCount();
376
28
        for (int i = 0; i < nUpdateRepeatCount; ++i)
377
20
        {
378
20
            Ring ring = Ring::Read(poUpdateRecord, poUpdateField, i);
379
20
            if (ring.RAUI == INSTRUCTION_INSERT)
380
7
            {
381
7
                asTarget.push_back(std::move(ring));
382
7
            }
383
13
            else if (ring.RAUI == INSTRUCTION_DELETE)
384
12
            {
385
12
                bool bMatchFound = false;
386
36
                for (size_t j = 0; j < asTarget.size(); ++j)
387
28
                {
388
28
                    if (asTarget[j].RRNM == ring.RRNM &&
389
6
                        asTarget[j].RRID == ring.RRID)
390
4
                    {
391
4
                        bMatchFound = true;
392
4
                        asTarget.erase(asTarget.begin() + j);
393
4
                        break;
394
4
                    }
395
28
                }
396
12
                if (!bMatchFound &&
397
8
                    !EMIT_ERROR_OR_WARNING(CPLSPrintf(
398
12
                        "%s, RCNM=%d, RCID=%d, RIAS iSubField=%d: update "
399
12
                        "field references RRNM=%d, RRID=%d which does not "
400
12
                        "exist in initial or previous update",
401
12
                        m_osFilename.c_str(), static_cast<int>(nRCNM), nRCID, i,
402
12
                        ring.RRNM, ring.RRID)))
403
8
                {
404
8
                    return false;
405
8
                }
406
12
            }
407
1
            else if (!EMIT_ERROR_OR_WARNING(CPLSPrintf(
408
1
                         "%s, RCNM=%d, RCID=%d, SPAS iSubField=%d: invalid "
409
1
                         "RAUI=%d",
410
1
                         m_osFilename.c_str(), static_cast<int>(nRCNM), nRCID,
411
1
                         i, ring.RAUI)))
412
1
            {
413
1
                return false;
414
1
            }
415
20
        }
416
17
    }
417
418
8
    if (!asTarget.empty())
419
7
    {
420
7
        auto poRIASFieldDefn = m_oMainModule.FindFieldDefn(RIAS_FIELD);
421
7
        if (!poRIASFieldDefn)
422
0
        {
423
0
            return EMIT_ERROR("Cannot find RIAS field definition");
424
0
        }
425
7
        auto poRIASFieldTarget = poTargetRecord->AddField(poRIASFieldDefn);
426
7
        CPLAssert(poRIASFieldTarget);
427
7
        const auto poRIASFieldUpdate = apoUpdateFields[0];
428
7
        if (*(poRIASFieldTarget->GetFieldDefn()) !=
429
7
            *(poRIASFieldUpdate->GetFieldDefn()))
430
3
        {
431
3
            return EMIT_ERROR("RIAS field definitions of update and target "
432
3
                              "records are different");
433
3
        }
434
435
        // Compose raw target field
436
4
        std::string s;
437
4
        for (const auto &cc : asTarget)
438
9
        {
439
9
            AppendUInt8(s, static_cast<uint8_t>(cc.RRNM));
440
9
            AppendInt32(s, cc.RRID);
441
9
            AppendUInt8(s, static_cast<uint8_t>(cc.ORNT));
442
9
            AppendUInt8(s, static_cast<uint8_t>(cc.USAG));
443
9
            AppendUInt8(s, static_cast<uint8_t>(cc.RAUI));
444
9
        }
445
4
        AppendUInt8(s, DDF_FIELD_TERMINATOR);
446
447
4
        poTargetRecord->SetFieldRaw(poRIASFieldTarget, s.data(),
448
4
                                    static_cast<int>(s.size()));
449
4
    }
450
451
5
    return true;
452
8
}