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