Line data Source code
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 : #include <set>
18 :
19 : /************************************************************************/
20 : /* CreateCompositeCurveFeatureDefn() */
21 : /************************************************************************/
22 :
23 : /** Create the feature definition for the CompositeCurve layer
24 : */
25 344 : bool OGRS101Reader::CreateCompositeCurveFeatureDefn()
26 : {
27 344 : if (m_oCompositeCurveRecordIndex.GetCount() > 0)
28 : {
29 : m_poFeatureDefnCompositeCurve =
30 230 : OGRFeatureDefnRefCountedPtr::makeInstance(
31 115 : OGR_LAYER_NAME_COMPOSITE_CURVE);
32 115 : m_poFeatureDefnCompositeCurve->SetGeomType(wkbLineString);
33 115 : auto oSRSIter = m_oMapSRS.find(HORIZONTAL_CRS_ID);
34 115 : if (oSRSIter != m_oMapSRS.end())
35 : {
36 230 : m_poFeatureDefnCompositeCurve->GetGeomFieldDefn(0)->SetSpatialRef(
37 230 : OGRSpatialReferenceRefCountedPtr::makeClone(&oSRSIter->second)
38 115 : .get());
39 : }
40 115 : m_poFeatureDefnCompositeCurve->GetGeomFieldDefn(0)
41 115 : ->SetCoordinatePrecision(m_coordinatePrecision);
42 : {
43 230 : OGRFieldDefn oFieldDefn(OGR_FIELD_NAME_RECORD_ID, OFTInteger);
44 115 : m_poFeatureDefnCompositeCurve->AddFieldDefn(&oFieldDefn);
45 : }
46 : {
47 230 : OGRFieldDefn oFieldDefn(OGR_FIELD_NAME_RECORD_VERSION, OFTInteger);
48 115 : m_poFeatureDefnCompositeCurve->AddFieldDefn(&oFieldDefn);
49 : }
50 115 : if (!InferFeatureDefn(m_oCompositeCurveRecordIndex, CCID_FIELD,
51 : INAS_FIELD, {}, *m_poFeatureDefnCompositeCurve,
52 115 : m_oMapFieldDomains))
53 : {
54 1 : return false;
55 : }
56 : }
57 :
58 343 : return true;
59 : }
60 :
61 : /************************************************************************/
62 : /* ReadCompositeCurveGeometry() */
63 : /************************************************************************/
64 :
65 : std::unique_ptr<OGRLineString>
66 1054 : OGRS101Reader::ReadCompositeCurveGeometryInternal(
67 : const DDFRecord *poRecord, int iRecord, int nRecordID,
68 : const OGRSpatialReference *poSRS,
69 : std::set<int> &oSetAlreadyVisitedCompositeCurveRecords) const
70 : {
71 1054 : if (nRecordID < 0)
72 436 : nRecordID = poRecord->GetIntSubfield(CCID_FIELD, 0, RCID_SUBFIELD, 0);
73 :
74 46 : const auto GetErrorContext = [iRecord, nRecordID]()
75 : {
76 23 : if (iRecord >= 0)
77 18 : return CPLSPrintf("Record index=%d of CCID", iRecord);
78 : else
79 5 : return CPLSPrintf("Record ID=%d of CCID", nRecordID);
80 1054 : };
81 :
82 : // The spec mentions that a composite curve may only refer to
83 : // another composite curve whose definition occurs before in the
84 : // file, which, if respected, effectively prevents circular
85 : // referencing from happening.
86 : // It is not practical for us to take note of the appearance order,
87 : // so use a set of record *ids* to detect that (so we are a bit
88 : // more permissive)
89 1054 : if (!oSetAlreadyVisitedCompositeCurveRecords.insert(nRecordID).second)
90 : {
91 3 : CPL_IGNORE_RET_VAL(EMIT_ERROR_OR_WARNING(CPLSPrintf(
92 : "%s: circular dependency on (RCNM=%d, RCID=%d) while "
93 : "resolving composite curve geometry.",
94 : GetErrorContext(), static_cast<int>(RECORD_NAME_COMPOSITE_CURVE),
95 : nRecordID)));
96 3 : return nullptr;
97 : }
98 :
99 2102 : const auto apoCUCOFields = poRecord->GetFields(CUCO_FIELD);
100 1051 : if (apoCUCOFields.empty())
101 : {
102 1 : CPL_IGNORE_RET_VAL(EMIT_ERROR_OR_WARNING(
103 : CPLSPrintf("%s: no CUCO field", GetErrorContext())));
104 1 : return nullptr;
105 : }
106 :
107 2100 : auto poLS = std::make_unique<OGRLineString>();
108 2078 : for (const auto *poCUCOField : apoCUCOFields)
109 : {
110 1050 : const int nParts = poCUCOField->GetRepeatCount();
111 2214 : for (int iPart = 0; iPart < nParts; ++iPart)
112 : {
113 : const auto GetIntSubfield =
114 3541 : [poRecord, poCUCOField, iPart](const char *pszSubFieldName)
115 : {
116 3541 : return poRecord->GetIntSubfield(poCUCOField, pszSubFieldName,
117 3541 : iPart);
118 1186 : };
119 :
120 1186 : const RecordName nRRNM = GetIntSubfield(RRNM_SUBFIELD);
121 1233 : if (nRRNM != RECORD_NAME_CURVE &&
122 47 : nRRNM != RECORD_NAME_COMPOSITE_CURVE)
123 : {
124 7 : CPL_IGNORE_RET_VAL(EMIT_ERROR_OR_WARNING(CPLSPrintf(
125 : "%s: Invalid value for RRNM "
126 : "subfield of %d instance of CUCO field: "
127 : "got %d, expected %d or %d.",
128 : GetErrorContext(), iPart, static_cast<int>(nRRNM),
129 : static_cast<int>(RECORD_NAME_CURVE),
130 : static_cast<int>(RECORD_NAME_COMPOSITE_CURVE))));
131 22 : return nullptr;
132 : }
133 :
134 1179 : bool bReverse = false;
135 1179 : const int nORNT = GetIntSubfield(ORNT_SUBFIELD);
136 1179 : if (nORNT == ORNT_REVERSE)
137 : {
138 83 : bReverse = true;
139 : }
140 1096 : else if (nORNT != ORNT_FORWARD)
141 : {
142 3 : CPL_IGNORE_RET_VAL(EMIT_ERROR_OR_WARNING(
143 : CPLSPrintf("%s: Invalid value for ORNT "
144 : "subfield of %d instance of CUCO field: "
145 : "got %d, expected %d or %d.",
146 : GetErrorContext(), iPart, nORNT, ORNT_FORWARD,
147 : ORNT_REVERSE)));
148 3 : return nullptr;
149 : }
150 :
151 1176 : const int nRRID = GetIntSubfield(RRID_SUBFIELD);
152 0 : std::unique_ptr<OGRLineString> poCurvePart;
153 1176 : if (nRRNM == RECORD_NAME_CURVE)
154 : {
155 : const auto poCurveRecord =
156 1136 : m_oCurveRecordIndex.FindRecord(nRRID);
157 1136 : if (!poCurveRecord)
158 : {
159 3 : CPL_IGNORE_RET_VAL(EMIT_ERROR_OR_WARNING(CPLSPrintf(
160 : "%s: Value (RRNM=%d, RRID=%d) "
161 : "of instance %d of CUCO field does not point to an "
162 : "existing record.",
163 : GetErrorContext(), static_cast<int>(nRRNM), nRRID,
164 : iPart)));
165 3 : return nullptr;
166 : }
167 :
168 2266 : poCurvePart = ReadCurveGeometry(
169 1133 : poCurveRecord, /* iRecord = */ -1, nRRID, poSRS);
170 : }
171 : else
172 : {
173 40 : CPLAssert(nRRNM == RECORD_NAME_COMPOSITE_CURVE);
174 :
175 : const auto poCompositeCurveRecord =
176 40 : m_oCompositeCurveRecordIndex.FindRecord(nRRID);
177 40 : if (!poCompositeCurveRecord)
178 : {
179 3 : CPL_IGNORE_RET_VAL(EMIT_ERROR_OR_WARNING(CPLSPrintf(
180 : "%s: Value (RRNM=%d, RRID=%d) "
181 : "of instance %d of CUCO field does not point to an "
182 : "existing record.",
183 : GetErrorContext(), static_cast<int>(nRRNM), nRRID,
184 : iPart)));
185 3 : return nullptr;
186 : }
187 :
188 74 : poCurvePart = ReadCompositeCurveGeometryInternal(
189 : poCompositeCurveRecord, /* iRecord = */ -1, nRRID, poSRS,
190 37 : oSetAlreadyVisitedCompositeCurveRecords);
191 : }
192 :
193 1170 : if (!poCurvePart)
194 : {
195 3 : return nullptr;
196 : }
197 :
198 1167 : const int nPoints = poLS->getNumPoints();
199 1167 : const int nPointsPart = poCurvePart->getNumPoints();
200 1167 : if (nPointsPart == 0)
201 : {
202 0 : CPL_IGNORE_RET_VAL(EMIT_ERROR_OR_WARNING(CPLSPrintf(
203 : "%s: Value (RRNM=%d, RRID=%d) "
204 : "%d instance of CUCO field points to an empty curve.",
205 : GetErrorContext(), static_cast<int>(nRRNM), nRRID, iPart)));
206 0 : return nullptr;
207 : }
208 1167 : if (nPoints == 0)
209 : {
210 1031 : if (bReverse)
211 43 : poLS->addSubLineString(poCurvePart.get(), nPointsPart - 1,
212 : 0);
213 : else
214 988 : poLS->addSubLineString(poCurvePart.get());
215 : }
216 : else
217 : {
218 136 : bool bEndPointMismatch = false;
219 136 : if (bReverse)
220 : {
221 40 : if (poLS->getX(nPoints - 1) ==
222 80 : poCurvePart->getX(nPointsPart - 1) &&
223 40 : poLS->getY(nPoints - 1) ==
224 40 : poCurvePart->getY(nPointsPart - 1))
225 : {
226 40 : poLS->addSubLineString(poCurvePart.get(),
227 : nPointsPart - 2, 0);
228 : }
229 : else
230 : {
231 0 : bEndPointMismatch = true;
232 : }
233 : }
234 : else
235 : {
236 189 : if (poLS->getX(nPoints - 1) == poCurvePart->getX(0) &&
237 93 : poLS->getY(nPoints - 1) == poCurvePart->getY(0))
238 : {
239 93 : poLS->addSubLineString(poCurvePart.get(), 1);
240 : }
241 : else
242 : {
243 3 : bEndPointMismatch = true;
244 : }
245 : }
246 136 : if (bEndPointMismatch)
247 : {
248 3 : CPL_IGNORE_RET_VAL(EMIT_ERROR_OR_WARNING(CPLSPrintf(
249 : "%s: Value (RRNM=%d, "
250 : "RRID=%d) "
251 : "of curve instance %d extremity does not match "
252 : "composite curve extremity.",
253 : GetErrorContext(), static_cast<int>(nRRNM), nRRID,
254 : iPart)));
255 3 : return nullptr;
256 : }
257 : }
258 : }
259 : }
260 :
261 1028 : poLS->assignSpatialReference(poSRS);
262 1028 : return poLS;
263 : }
264 :
265 1017 : std::unique_ptr<OGRLineString> OGRS101Reader::ReadCompositeCurveGeometry(
266 : const DDFRecord *poRecord, int iRecord, int nRecordID,
267 : const OGRSpatialReference *poSRS) const
268 : {
269 2034 : std::set<int> oSetAlreadyVisitedCompositeCurveRecords;
270 : return ReadCompositeCurveGeometryInternal(
271 : poRecord, iRecord, nRecordID, poSRS,
272 2034 : oSetAlreadyVisitedCompositeCurveRecords);
273 : }
274 :
275 : /************************************************************************/
276 : /* FillFeatureCompositeCurve() */
277 : /************************************************************************/
278 :
279 : /** Fill the content of the provided feature from the identified record
280 : * (of m_oCompositeCurveRecordIndex).
281 : */
282 436 : bool OGRS101Reader::FillFeatureCompositeCurve(const DDFRecordIndex &oIndex,
283 : int iRecord,
284 : OGRFeature &oFeature) const
285 : {
286 436 : const auto poRecord = oIndex.GetByIndex(iRecord);
287 436 : CPLAssert(poRecord);
288 :
289 : const OGRSpatialReference *poSRS =
290 436 : oFeature.GetDefnRef()->GetGeomFieldDefn(0)->GetSpatialRef();
291 : auto poCurve = ReadCompositeCurveGeometry(poRecord, iRecord,
292 872 : /* nRecordID = */ -1, poSRS);
293 436 : if (!poCurve)
294 : {
295 21 : if (m_bStrict)
296 7 : return false; // error message already emitted
297 : }
298 : else
299 : {
300 415 : oFeature.SetGeometry(std::move(poCurve));
301 : }
302 :
303 858 : return FillFeatureAttributes(oIndex, iRecord, INAS_FIELD, oFeature) &&
304 429 : FillFeatureWithNonAttrAssocSubfields(poRecord, iRecord, INAS_FIELD,
305 429 : oFeature);
306 : }
307 :
308 : /************************************************************************/
309 : /* ProcessUpdateRecordCompositeCurve() */
310 : /************************************************************************/
311 :
312 : /** Updates the geometry part of poTargetRecord with poUpdateRecord */
313 36 : bool OGRS101Reader::ProcessUpdateRecordCompositeCurve(
314 : const DDFRecord *poUpdateRecord, DDFRecord *poTargetRecord) const
315 : {
316 36 : const auto poIDField = poUpdateRecord->GetField(0);
317 36 : CPLAssert(poIDField);
318 :
319 : // Record name
320 : const RecordName nRCNM =
321 36 : poUpdateRecord->GetIntSubfield(poIDField, RCNM_SUBFIELD, 0);
322 :
323 : // Record identifier
324 : const int nRCID =
325 36 : poUpdateRecord->GetIntSubfield(poIDField, RCID_SUBFIELD, 0);
326 :
327 : // Composite Curve Control field
328 36 : const auto poControlField = poUpdateRecord->FindField(CCOC_FIELD);
329 36 : if (!poControlField)
330 0 : return true;
331 :
332 : // Curve Component update instruction
333 36 : constexpr const char *CCUI_SUBFIELD = "CCUI";
334 : const int nCCUI =
335 36 : poUpdateRecord->GetIntSubfield(poControlField, CCUI_SUBFIELD, 0);
336 36 : if (nCCUI != INSTRUCTION_INSERT && nCCUI != INSTRUCTION_DELETE &&
337 : nCCUI != INSTRUCTION_UPDATE)
338 : {
339 2 : return EMIT_ERROR_OR_WARNING(CPLSPrintf(
340 : "%s, RCNM=%d, RCID=%d: invalid CCUI = %d", m_osFilename.c_str(),
341 : static_cast<int>(nRCNM), nRCID, nCCUI));
342 : }
343 :
344 : // Curve Component index (1-based)
345 34 : constexpr const char *CCIX_SUBFIELD = "CCIX";
346 : const int nCCIX =
347 34 : poUpdateRecord->GetIntSubfield(poControlField, CCIX_SUBFIELD, 0);
348 :
349 : // Number of Curve Components
350 34 : constexpr const char *NCCO_SUBFIELD = "NCCO";
351 : const int nNCCO =
352 34 : poUpdateRecord->GetIntSubfield(poControlField, NCCO_SUBFIELD, 0);
353 :
354 34 : const auto poTargetField = poTargetRecord->FindField(CUCO_FIELD);
355 34 : if (!poTargetField)
356 : {
357 0 : return EMIT_ERROR_OR_WARNING(
358 : CPLSPrintf("%s, RCNM=%d, RCID=%d: missing CUCO field in "
359 : "target record",
360 : m_osFilename.c_str(), static_cast<int>(nRCNM), nRCID));
361 : }
362 34 : const auto poUpdateField = poUpdateRecord->FindField(CUCO_FIELD);
363 :
364 102 : if (poUpdateRecord->GetFields(CUCO_FIELD).size() > 1 ||
365 68 : poTargetRecord->GetFields(CUCO_FIELD).size() > 1)
366 : {
367 0 : return EMIT_ERROR_OR_WARNING(
368 : CPLSPrintf("%s: only one instance of CUCO supported for update",
369 : m_osFilename.c_str()));
370 : }
371 :
372 34 : const int nTargetRepeatCount = poTargetField->GetRepeatCount();
373 :
374 : // Check that start index and count is consistent with update and
375 : // target list of curve components
376 34 : const int nMaxCCIXAllowed =
377 34 : nTargetRepeatCount + (nCCUI == INSTRUCTION_INSERT ? 1 : 0);
378 34 : if (nCCIX <= 0 || nCCIX > nMaxCCIXAllowed)
379 : {
380 6 : return EMIT_ERROR_OR_WARNING(CPLSPrintf(
381 : "%s, RCNM=%d, RCID=%d: invalid CCIX = %d. Must be in [1,%d].",
382 : m_osFilename.c_str(), static_cast<int>(nRCNM), nRCID, nCCIX,
383 : nMaxCCIXAllowed));
384 : }
385 :
386 : const int nUpdateRepeatCount =
387 28 : !poUpdateField ? 0 : poUpdateField->GetRepeatCount();
388 :
389 28 : if (poUpdateField && nNCCO != nUpdateRepeatCount)
390 : {
391 2 : return EMIT_ERROR_OR_WARNING(
392 : CPLSPrintf("%s, RCNM=%d, RCID=%d: invalid NCCO = %d. Expected %d",
393 : m_osFilename.c_str(), static_cast<int>(nRCNM), nRCID,
394 : nNCCO, nUpdateRepeatCount));
395 : }
396 28 : else if (poUpdateField && nCCUI == INSTRUCTION_DELETE &&
397 2 : !EMIT_ERROR_OR_WARNING(CPLSPrintf(
398 : "%s, RCNM=%d, RCID=%d: unexpected CUCO field in "
399 : "update record in CCUI = %d (delete) mode",
400 : m_osFilename.c_str(), static_cast<int>(nRCNM), nRCID, nCCUI)))
401 : {
402 1 : return false;
403 : }
404 :
405 25 : if (nCCUI == INSTRUCTION_UPDATE || nCCUI == INSTRUCTION_DELETE)
406 : {
407 18 : if (nCCIX + nNCCO > nTargetRepeatCount + 1)
408 : {
409 2 : return EMIT_ERROR_OR_WARNING(CPLSPrintf(
410 : "%s, RCNM=%d, RCID=%d: invalid CCIX = %d and/or NCCO=%d",
411 : m_osFilename.c_str(), static_cast<int>(nRCNM), nRCID, nCCIX,
412 : nNCCO));
413 : }
414 : }
415 :
416 : struct CurveComponent
417 : {
418 : int RRNM = 0;
419 : int RRID = 0;
420 : int ORNT = 0;
421 :
422 65 : void Read(const DDFRecord *poRecord, const DDFField *poField, int i)
423 : {
424 65 : RRNM = poRecord->GetIntSubfield(poField, RRNM_SUBFIELD, i);
425 65 : RRID = poRecord->GetIntSubfield(poField, RRID_SUBFIELD, i);
426 65 : ORNT = poRecord->GetIntSubfield(poField, ORNT_SUBFIELD, i);
427 65 : }
428 : };
429 :
430 46 : std::vector<CurveComponent> asTarget;
431 : // Ingest the existing/target record
432 74 : for (int i = 0; i < nTargetRepeatCount; ++i)
433 : {
434 51 : CurveComponent cc;
435 51 : cc.Read(poTargetRecord, poTargetField, i);
436 51 : asTarget.push_back(cc);
437 : }
438 :
439 : // Apply the update record
440 46 : std::vector<CurveComponent> asUpdate;
441 23 : if (poUpdateField)
442 : {
443 28 : for (int i = 0; i < nUpdateRepeatCount; ++i)
444 : {
445 14 : CurveComponent cc;
446 14 : cc.Read(poUpdateRecord, poUpdateField, i);
447 14 : asUpdate.push_back(cc);
448 : }
449 : }
450 :
451 23 : const auto oTargetBeginIter = asTarget.begin() + (nCCIX - 1);
452 23 : if (nCCUI == INSTRUCTION_INSERT)
453 : {
454 7 : asTarget.insert(oTargetBeginIter, asUpdate.begin(), asUpdate.end());
455 : }
456 16 : else if (nCCUI == INSTRUCTION_UPDATE)
457 : {
458 6 : std::copy(asUpdate.begin(), asUpdate.end(), oTargetBeginIter);
459 : }
460 : else
461 : {
462 10 : CPLAssert(nCCUI == INSTRUCTION_DELETE);
463 10 : asTarget.erase(oTargetBeginIter, oTargetBeginIter + nNCCO);
464 : }
465 :
466 : // Compose raw target field
467 23 : std::string s;
468 71 : for (const auto &cc : asTarget)
469 : {
470 48 : AppendUInt8(s, static_cast<uint8_t>(cc.RRNM));
471 48 : AppendInt32(s, cc.RRID);
472 48 : AppendUInt8(s, static_cast<uint8_t>(cc.ORNT));
473 : }
474 23 : AppendUInt8(s, DDF_FIELD_TERMINATOR);
475 :
476 23 : poTargetRecord->SetFieldRaw(poTargetField, s.data(),
477 23 : static_cast<int>(s.size()));
478 :
479 23 : return true;
480 : }
|