/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 | } |