Coverage Report

Created: 2026-09-26 08:22

next uncovered line (L), next uncovered region (R), next uncovered branch (B)
/src/gdal/ogr/ogrsf_frmts/s101/ogrs101readercurve.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 <memory>
17
18
/************************************************************************/
19
/*                       CreateCurveFeatureDefn()                       */
20
/************************************************************************/
21
22
/** Create the feature definition for the Curve layer
23
 */
24
bool OGRS101Reader::CreateCurveFeatureDefn()
25
2.13k
{
26
2.13k
    if (m_oCurveRecordIndex.GetCount() > 0)
27
722
    {
28
722
        m_poFeatureDefnCurve =
29
722
            OGRFeatureDefnRefCountedPtr::makeInstance(OGR_LAYER_NAME_CURVE);
30
722
        m_poFeatureDefnCurve->SetGeomType(wkbLineString);
31
722
        auto oSRSIter = m_oMapSRS.find(HORIZONTAL_CRS_ID);
32
722
        if (oSRSIter != m_oMapSRS.end())
33
722
        {
34
722
            m_poFeatureDefnCurve->GetGeomFieldDefn(0)->SetSpatialRef(
35
722
                OGRSpatialReferenceRefCountedPtr::makeClone(&oSRSIter->second)
36
722
                    .get());
37
722
        }
38
722
        m_poFeatureDefnCurve->GetGeomFieldDefn(0)->SetCoordinatePrecision(
39
722
            m_coordinatePrecision);
40
722
        {
41
722
            OGRFieldDefn oFieldDefn(OGR_FIELD_NAME_RECORD_ID, OFTInteger);
42
722
            m_poFeatureDefnCurve->AddFieldDefn(&oFieldDefn);
43
722
        }
44
722
        {
45
722
            OGRFieldDefn oFieldDefn(OGR_FIELD_NAME_RECORD_VERSION, OFTInteger);
46
722
            m_poFeatureDefnCurve->AddFieldDefn(&oFieldDefn);
47
722
        }
48
722
        if (!InferFeatureDefn(m_oCurveRecordIndex, CRID_FIELD, INAS_FIELD, {},
49
722
                              *m_poFeatureDefnCurve, m_oMapFieldDomains))
50
1
        {
51
1
            return false;
52
1
        }
53
722
    }
54
55
2.13k
    return true;
56
2.13k
}
57
58
/************************************************************************/
59
/*                         ReadCurveGeometry()                          */
60
/************************************************************************/
61
62
std::unique_ptr<OGRLineString>
63
OGRS101Reader::ReadCurveGeometry(const DDFRecord *poRecord, int iRecord,
64
                                 int nRecordID,
65
                                 const OGRSpatialReference *poSRS) const
66
1.98k
{
67
1.98k
    const DDFField *poSEGHField = poRecord->FindField(SEGH_FIELD);
68
1.98k
    if (poSEGHField)
69
1.98k
    {
70
1.98k
        constexpr const char *INTP_SUBFIELD = "INTP";
71
1.98k
        const int nINTP =
72
1.98k
            poRecord->GetIntSubfield(SEGH_FIELD, 0, INTP_SUBFIELD, 0);
73
1.98k
        constexpr int INTERPOLATION_LOXODROMIC = 4;
74
1.98k
        if (nINTP != INTERPOLATION_LOXODROMIC &&
75
165
            !EMIT_ERROR_OR_WARNING(
76
1.98k
                CPLSPrintf("Record index %d of CRID: Invalid value for INTP "
77
1.98k
                           "subfield of SEGH field: "
78
1.98k
                           "got %d, expected %d.",
79
1.98k
                           iRecord, nINTP, INTERPOLATION_LOXODROMIC)))
80
165
        {
81
165
            return nullptr;
82
165
        }
83
1.98k
    }
84
85
1.82k
    const auto apoCoordFields = poRecord->GetFields(C2IL_FIELD);
86
1.82k
    if (apoCoordFields.empty())
87
3
        return nullptr;
88
89
1.81k
    auto poLS = std::make_unique<OGRLineString>();
90
1.81k
    poLS->assignSpatialReference(poSRS);
91
1.81k
    int nCoordCount = 0;
92
1.81k
    for (const auto *poCoordField : apoCoordFields)
93
1.81k
    {
94
1.81k
        const int nCoordFieldCount = poCoordField->GetRepeatCount();
95
1.81k
        nCoordCount += nCoordFieldCount;
96
1.81k
    }
97
1.81k
    poLS->setNumPoints(nCoordCount);
98
1.81k
    int iPnt = 0;
99
1.81k
    for (const auto *poCoordField : apoCoordFields)
100
1.81k
    {
101
1.81k
        const int nCoordFieldCount = poCoordField->GetRepeatCount();
102
9.55k
        for (int iPntThisField = 0; iPntThisField < nCoordFieldCount;
103
7.73k
             ++iPntThisField)
104
7.83k
        {
105
7.83k
            auto poPoint = ReadPointGeometryInternal(
106
7.83k
                poRecord, iRecord, nRecordID, iPntThisField, poSRS,
107
7.83k
                /* bIs3D = */ false, poCoordField, CRID_FIELD);
108
7.83k
            if (!poPoint)
109
96
                return nullptr;
110
7.73k
            poLS->setPoint(iPnt, poPoint.get());
111
7.73k
            ++iPnt;
112
7.73k
        }
113
1.81k
    }
114
115
1.72k
    const DDFField *poPTASField = poRecord->FindField(PTAS_FIELD);
116
1.72k
    if (poPTASField)
117
1.72k
    {
118
1.72k
        const int nPTASMembers = poPTASField->GetRepeatCount();
119
1.72k
        constexpr const char *TOPI_SUBFIELD = "TOPI";
120
1.72k
        if (nPTASMembers == 1 || nPTASMembers == 2)
121
1.68k
        {
122
3.08k
            for (int iPTAS = 0; iPTAS < nPTASMembers; ++iPTAS)
123
1.92k
            {
124
1.92k
                const auto GetIntSubfield =
125
1.92k
                    [poRecord, poPTASField, iPTAS](const char *pszSubFieldName)
126
5.65k
                {
127
5.65k
                    return poRecord->GetIntSubfield(poPTASField,
128
5.65k
                                                    pszSubFieldName, iPTAS);
129
5.65k
                };
130
131
1.92k
                const RecordName nRRNM = GetIntSubfield(RRNM_SUBFIELD);
132
1.92k
                constexpr RecordName nExpectedRRNM = RECORD_NAME_POINT;
133
1.92k
                if (nRRNM != nExpectedRRNM &&
134
30
                    !EMIT_ERROR_OR_WARNING(CPLSPrintf(
135
1.92k
                        "Record index %d of CRID: Invalid value for RRNM "
136
1.92k
                        "subfield of %d instance of PTAS field: "
137
1.92k
                        "got %d, expected %d.",
138
1.92k
                        iRecord, iPTAS, static_cast<int>(nRRNM),
139
1.92k
                        static_cast<int>(nExpectedRRNM))))
140
30
                {
141
30
                    return nullptr;
142
30
                }
143
144
1.89k
                const int nTOPI = GetIntSubfield(TOPI_SUBFIELD);
145
1.89k
                constexpr int TOPOLOGY_INDICATOR_BEGINNING_POINT = 1;
146
1.89k
                constexpr int TOPOLOGY_INDICATOR_END_POINT = 2;
147
1.89k
                constexpr int TOPOLOGY_INDICATOR_BEGINNING_AND_END_POINT = 3;
148
1.89k
                const int nExpectedTOPI =
149
1.89k
                    nPTASMembers == 1
150
1.89k
                        ? TOPOLOGY_INDICATOR_BEGINNING_AND_END_POINT
151
1.89k
                    : iPTAS == 0 ? TOPOLOGY_INDICATOR_BEGINNING_POINT
152
593
                                 : TOPOLOGY_INDICATOR_END_POINT;
153
154
1.89k
                if (nTOPI != nExpectedTOPI &&
155
63
                    !EMIT_ERROR_OR_WARNING(CPLSPrintf(
156
1.89k
                        "Record index %d of CRID: Invalid value for TOPI "
157
1.89k
                        "subfield of %d instance of PTAS field: "
158
1.89k
                        "got %d, expected %d.",
159
1.89k
                        iRecord, iPTAS, nTOPI, nExpectedTOPI)))
160
63
                {
161
63
                    return nullptr;
162
63
                }
163
164
1.83k
                const int nRRID = GetIntSubfield(RRID_SUBFIELD);
165
1.83k
                const auto poPointRecord =
166
1.83k
                    m_oPointRecordIndex.FindRecord(nRRID);
167
1.83k
                if (!poPointRecord &&
168
71
                    !EMIT_ERROR_OR_WARNING(CPLSPrintf(
169
1.83k
                        "Record index %d of CRID: No point record matching "
170
1.83k
                        "RRID=%d value of %d instance of PTAS field.",
171
1.83k
                        iRecord, nRRID, iPTAS)))
172
71
                {
173
71
                    return nullptr;
174
71
                }
175
1.76k
                if (poPointRecord)
176
1.76k
                {
177
1.76k
                    if (nTOPI == TOPOLOGY_INDICATOR_BEGINNING_POINT ||
178
1.43k
                        nTOPI == TOPOLOGY_INDICATOR_BEGINNING_AND_END_POINT)
179
1.55k
                    {
180
1.55k
                        if (poPointRecord->GetIntSubfield(C2IT_FIELD, 0,
181
1.55k
                                                          XCOO_SUBFIELD, 0) !=
182
1.55k
                                poRecord->GetIntSubfield(C2IL_FIELD, 0,
183
1.55k
                                                         XCOO_SUBFIELD, 0) ||
184
1.43k
                            poPointRecord->GetIntSubfield(C2IT_FIELD, 0,
185
1.43k
                                                          YCOO_SUBFIELD, 0) !=
186
1.43k
                                poRecord->GetIntSubfield(C2IL_FIELD, 0,
187
1.43k
                                                         YCOO_SUBFIELD, 0))
188
211
                        {
189
211
                            if (!EMIT_ERROR_OR_WARNING(CPLSPrintf(
190
211
                                    "Record index %d of CRID: Point record %d "
191
211
                                    "pointed by %d instance of PTAS field does "
192
211
                                    "not match first point of curve.",
193
211
                                    iRecord, nRRID, iPTAS)))
194
211
                            {
195
211
                                return nullptr;
196
211
                            }
197
211
                        }
198
1.55k
                    }
199
200
1.55k
                    if (nTOPI == TOPOLOGY_INDICATOR_END_POINT ||
201
1.34k
                        nTOPI == TOPOLOGY_INDICATOR_BEGINNING_AND_END_POINT)
202
1.31k
                    {
203
1.31k
                        if (poPointRecord->GetIntSubfield(C2IT_FIELD, 0,
204
1.31k
                                                          XCOO_SUBFIELD, 0) !=
205
1.31k
                                poRecord->GetIntSubfield(C2IL_FIELD, 0,
206
1.31k
                                                         XCOO_SUBFIELD,
207
1.31k
                                                         nCoordCount - 1) ||
208
1.24k
                            poPointRecord->GetIntSubfield(C2IT_FIELD, 0,
209
1.24k
                                                          YCOO_SUBFIELD, 0) !=
210
1.24k
                                poRecord->GetIntSubfield(C2IL_FIELD, 0,
211
1.24k
                                                         YCOO_SUBFIELD,
212
1.24k
                                                         nCoordCount - 1))
213
152
                        {
214
152
                            if (!EMIT_ERROR_OR_WARNING(CPLSPrintf(
215
152
                                    "Record index %d of CRID: Point record %d "
216
152
                                    "pointed by %d instance of PTAS field does "
217
152
                                    "not match end point of curve.",
218
152
                                    iRecord, nRRID, iPTAS)))
219
152
                            {
220
152
                                return nullptr;
221
152
                            }
222
152
                        }
223
1.31k
                    }
224
1.55k
                }
225
1.76k
            }
226
1.68k
        }
227
32
        else
228
32
        {
229
32
            if (!EMIT_ERROR_OR_WARNING(CPLSPrintf(
230
32
                    "Record index %d of CRID: Invalid repeat count for "
231
32
                    "PTAS field: got %d, expected 1 or 2.",
232
32
                    iRecord, nPTASMembers)))
233
32
            {
234
32
                return nullptr;
235
32
            }
236
32
        }
237
1.72k
    }
238
239
1.16k
    return poLS;
240
1.72k
}
241
242
/************************************************************************/
243
/*                          FillFeatureCurve()                          */
244
/************************************************************************/
245
246
/** Fill the content of the provided feature from the identified record
247
 * (of m_oCurveRecordIndex).
248
 */
249
bool OGRS101Reader::FillFeatureCurve(const DDFRecordIndex &oIndex, int iRecord,
250
                                     OGRFeature &oFeature) const
251
845
{
252
845
    const auto poRecord = oIndex.GetByIndex(iRecord);
253
845
    CPLAssert(poRecord);
254
255
845
    const OGRSpatialReference *poSRS =
256
845
        oFeature.GetDefnRef()->GetGeomFieldDefn(0)->GetSpatialRef();
257
845
    auto poLS =
258
845
        ReadCurveGeometry(poRecord, iRecord, /* nRecordID = */ -1, poSRS);
259
845
    if (!poLS)
260
490
    {
261
490
        if (m_bStrict)
262
490
            return false;  // error message already emitted
263
490
    }
264
355
    else
265
355
    {
266
355
        oFeature.SetGeometry(std::move(poLS));
267
355
    }
268
269
355
    return FillFeatureAttributes(oIndex, iRecord, INAS_FIELD, oFeature) &&
270
355
           FillFeatureWithNonAttrAssocSubfields(poRecord, iRecord, INAS_FIELD,
271
355
                                                oFeature);
272
845
}
273
274
/************************************************************************/
275
/*                      ProcessUpdateRecordCurve()                      */
276
/************************************************************************/
277
278
/** Updates the geometry part of poTargetRecord with poUpdateRecord */
279
bool OGRS101Reader::ProcessUpdateRecordCurve(const DDFRecord *poUpdateRecord,
280
                                             DDFRecord *poTargetRecord) const
281
10
{
282
10
    const auto poIDField = poUpdateRecord->GetField(0);
283
10
    CPLAssert(poIDField);
284
285
    // Record name
286
10
    const RecordName nRCNM =
287
10
        poUpdateRecord->GetIntSubfield(poIDField, RCNM_SUBFIELD, 0);
288
289
    // Record identifier
290
10
    const int nRCID =
291
10
        poUpdateRecord->GetIntSubfield(poIDField, RCID_SUBFIELD, 0);
292
293
10
    const auto poUpdatePTASField = poUpdateRecord->FindField(PTAS_FIELD);
294
10
    if (poUpdatePTASField)
295
9
    {
296
9
        const auto poTargetPTASField = poTargetRecord->FindField(PTAS_FIELD);
297
9
        if (!poTargetPTASField)
298
0
        {
299
0
            return EMIT_ERROR_OR_WARNING(CPLSPrintf(
300
0
                "%s, RCNM=%d, RCID=%d: missing PTAS field in "
301
0
                "target record",
302
0
                m_osFilename.c_str(), static_cast<int>(nRCNM), nRCID));
303
0
        }
304
305
        // Replace whole target PTAS field with update PTAS field
306
9
        poTargetRecord->SetFieldRaw(poTargetPTASField,
307
9
                                    poUpdatePTASField->GetData(),
308
9
                                    poUpdatePTASField->GetDataSize());
309
9
    }
310
311
    // Segment Control field
312
    // Update rules described at S-100 Ed 5.2 "10a-7.2.4.1 Encoding rules"
313
10
    const auto poSECCField = poUpdateRecord->FindField(SECC_FIELD);
314
10
    if (!poSECCField)
315
1
        return true;
316
317
9
    const auto poUpdateC2ILField = poUpdateRecord->FindField(C2IL_FIELD);
318
9
    if (!poUpdateC2ILField)
319
1
    {
320
1
        return EMIT_ERROR_OR_WARNING(
321
1
            CPLSPrintf("%s, RCNM=%d, RCID=%d: missing C2IL field in "
322
1
                       "update record",
323
1
                       m_osFilename.c_str(), static_cast<int>(nRCNM), nRCID));
324
1
    }
325
326
8
    const auto poTargetC2ILField = poTargetRecord->FindField(C2IL_FIELD);
327
8
    if (!poTargetC2ILField)
328
0
    {
329
0
        return EMIT_ERROR_OR_WARNING(
330
0
            CPLSPrintf("%s, RCNM=%d, RCID=%d: missing C2IL field in "
331
0
                       "target record",
332
0
                       m_osFilename.c_str(), static_cast<int>(nRCNM), nRCID));
333
0
    }
334
335
    // Segment update instruction
336
8
    const int SEUI = poUpdateRecord->GetIntSubfield(poSECCField, "SEUI", 0);
337
338
8
    if (SEUI == INSTRUCTION_INSERT)
339
1
    {
340
1
        return EMIT_ERROR_OR_WARNING(CPLSPrintf(
341
1
            "%s, RCNM=%d, RCID=%d: SEUI=%d (insert) not supported",
342
1
            m_osFilename.c_str(), static_cast<int>(nRCNM), nRCID, SEUI));
343
1
    }
344
7
    else if (SEUI == INSTRUCTION_DELETE)
345
1
    {
346
1
        return EMIT_ERROR_OR_WARNING(CPLSPrintf(
347
1
            "%s, RCNM=%d, RCID=%d: SEUI=%d (delete) not supported",
348
1
            m_osFilename.c_str(), static_cast<int>(nRCNM), nRCID, SEUI));
349
1
    }
350
6
    else if (SEUI != INSTRUCTION_UPDATE)
351
1
    {
352
1
        return EMIT_ERROR_OR_WARNING(CPLSPrintf(
353
1
            "%s, RCNM=%d, RCID=%d: invalid SEUI = %d", m_osFilename.c_str(),
354
1
            static_cast<int>(nRCNM), nRCID, SEUI));
355
1
    }
356
357
    // Segment index
358
5
    const int SEIX = poUpdateRecord->GetIntSubfield(poSECCField, "SEIX", 0);
359
5
    if (SEIX != 1)
360
1
    {
361
1
        return EMIT_ERROR_OR_WARNING(CPLSPrintf(
362
1
            "%s, RCNM=%d, RCID=%d: invalid SEIX = %d", m_osFilename.c_str(),
363
1
            static_cast<int>(nRCNM), nRCID, SEIX));
364
1
    }
365
366
    // Number of segments
367
4
    const int NSEG = poUpdateRecord->GetIntSubfield(poSECCField, "NSEG", 0);
368
4
    if (NSEG != 1)
369
1
    {
370
1
        return EMIT_ERROR_OR_WARNING(CPLSPrintf(
371
1
            "%s, RCNM=%d, RCID=%d: invalid NSEG = %d", m_osFilename.c_str(),
372
1
            static_cast<int>(nRCNM), nRCID, NSEG));
373
1
    }
374
375
3
    return ProcessUpdatePointList(poUpdateRecord, poTargetRecord,
376
3
                                  /* bIs3DAllowed = */ false);
377
4
}