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 248 : bool OGRS101Reader::CreateCompositeCurveFeatureDefn()
26 : {
27 248 : if (m_oCompositeCurveRecordIndex.GetCount() > 0)
28 : {
29 : m_poFeatureDefnCompositeCurve =
30 174 : OGRFeatureDefnRefCountedPtr::makeInstance(
31 87 : OGR_LAYER_NAME_COMPOSITE_CURVE);
32 87 : m_poFeatureDefnCompositeCurve->SetGeomType(wkbLineString);
33 87 : auto oSRSIter = m_oMapSRS.find(HORIZONTAL_CRS_ID);
34 87 : if (oSRSIter != m_oMapSRS.end())
35 : {
36 174 : m_poFeatureDefnCompositeCurve->GetGeomFieldDefn(0)->SetSpatialRef(
37 174 : OGRSpatialReferenceRefCountedPtr::makeClone(&oSRSIter->second)
38 87 : .get());
39 : }
40 : {
41 174 : OGRFieldDefn oFieldDefn(OGR_FIELD_NAME_RECORD_ID, OFTInteger);
42 87 : m_poFeatureDefnCompositeCurve->AddFieldDefn(&oFieldDefn);
43 : }
44 : {
45 174 : OGRFieldDefn oFieldDefn(OGR_FIELD_NAME_RECORD_VERSION, OFTInteger);
46 87 : m_poFeatureDefnCompositeCurve->AddFieldDefn(&oFieldDefn);
47 : }
48 87 : if (!InferFeatureDefn(m_oCompositeCurveRecordIndex, CCID_FIELD,
49 : INAS_FIELD, {}, *m_poFeatureDefnCompositeCurve,
50 87 : m_oMapFieldDomains))
51 : {
52 1 : return false;
53 : }
54 : }
55 :
56 247 : return true;
57 : }
58 :
59 : /************************************************************************/
60 : /* ReadCompositeCurveGeometry() */
61 : /************************************************************************/
62 :
63 : std::unique_ptr<OGRLineString>
64 762 : OGRS101Reader::ReadCompositeCurveGeometryInternal(
65 : const DDFRecord *poRecord, int iRecord, int nRecordID,
66 : const OGRSpatialReference *poSRS,
67 : std::set<int> &oSetAlreadyVisitedCompositeCurveRecords) const
68 : {
69 762 : if (nRecordID < 0)
70 300 : nRecordID = poRecord->GetIntSubfield(CCID_FIELD, 0, RCID_SUBFIELD, 0);
71 :
72 40 : const auto GetErrorContext = [iRecord, nRecordID]()
73 : {
74 20 : if (iRecord >= 0)
75 18 : return CPLSPrintf("Record index=%d of CCID", iRecord);
76 : else
77 2 : return CPLSPrintf("Record ID=%d of CCID", nRecordID);
78 762 : };
79 :
80 : // The spec mentions that a composite curve may only refer to
81 : // another composite curve whose definition occurs before in the
82 : // file, which, if respected, effectively prevents circular
83 : // referencing from happening.
84 : // It is not practical for us to take note of the appearance order,
85 : // so use a set of record *ids* to detect that (so we are a bit
86 : // more permissive)
87 762 : if (!oSetAlreadyVisitedCompositeCurveRecords.insert(nRecordID).second)
88 : {
89 0 : CPL_IGNORE_RET_VAL(EMIT_ERROR_OR_WARNING(CPLSPrintf(
90 : "%s: circular dependency on (RCNM=%d, RCID=%d) while "
91 : "resolving composite curve geometry.",
92 : GetErrorContext(), static_cast<int>(RECORD_NAME_COMPOSITE_CURVE),
93 : nRecordID)));
94 0 : return nullptr;
95 : }
96 :
97 1524 : const auto apoCUCOFields = poRecord->GetFields(CUCO_FIELD);
98 762 : if (apoCUCOFields.empty())
99 : {
100 1 : CPL_IGNORE_RET_VAL(EMIT_ERROR_OR_WARNING(
101 : CPLSPrintf("%s: no CUCO field", GetErrorContext())));
102 1 : return nullptr;
103 : }
104 :
105 1522 : auto poLS = std::make_unique<OGRLineString>();
106 1503 : for (const auto *poCUCOField : apoCUCOFields)
107 : {
108 761 : const int nParts = poCUCOField->GetRepeatCount();
109 1591 : for (int iPart = 0; iPart < nParts; ++iPart)
110 : {
111 : const auto GetIntSubfield =
112 2530 : [poRecord, poCUCOField, iPart](const char *pszSubFieldName)
113 : {
114 2530 : return poRecord->GetIntSubfield(poCUCOField, pszSubFieldName,
115 2530 : iPart);
116 849 : };
117 :
118 849 : const RecordName nRRNM = GetIntSubfield(RRNM_SUBFIELD);
119 893 : if (nRRNM != RECORD_NAME_CURVE &&
120 44 : nRRNM != RECORD_NAME_COMPOSITE_CURVE)
121 : {
122 7 : CPL_IGNORE_RET_VAL(EMIT_ERROR_OR_WARNING(CPLSPrintf(
123 : "%s: Invalid value for RRNM "
124 : "subfield of %d instance of CUCO field: "
125 : "got %d, expected %d or %d.",
126 : GetErrorContext(), iPart, static_cast<int>(nRRNM),
127 : static_cast<int>(RECORD_NAME_CURVE),
128 : static_cast<int>(RECORD_NAME_COMPOSITE_CURVE))));
129 19 : return nullptr;
130 : }
131 :
132 842 : bool bReverse = false;
133 842 : const int nORNT = GetIntSubfield(ORNT_SUBFIELD);
134 842 : if (nORNT == ORNT_REVERSE)
135 : {
136 77 : bReverse = true;
137 : }
138 765 : else if (nORNT != ORNT_FORWARD)
139 : {
140 3 : CPL_IGNORE_RET_VAL(EMIT_ERROR_OR_WARNING(
141 : CPLSPrintf("%s: Invalid value for ORNT "
142 : "subfield of %d instance of CUCO field: "
143 : "got %d, expected %d or %d.",
144 : GetErrorContext(), iPart, nORNT, ORNT_FORWARD,
145 : ORNT_REVERSE)));
146 3 : return nullptr;
147 : }
148 :
149 839 : const int nRRID = GetIntSubfield(RRID_SUBFIELD);
150 0 : std::unique_ptr<OGRLineString> poCurvePart;
151 839 : if (nRRNM == RECORD_NAME_CURVE)
152 : {
153 : const auto poCurveRecord =
154 802 : m_oCurveRecordIndex.FindRecord(nRRID);
155 802 : if (!poCurveRecord)
156 : {
157 3 : CPL_IGNORE_RET_VAL(EMIT_ERROR_OR_WARNING(CPLSPrintf(
158 : "%s: Value (RRNM=%d, RRID=%d) "
159 : "of instance %d of CUCO field does not point to an "
160 : "existing record.",
161 : GetErrorContext(), static_cast<int>(nRRNM), nRRID,
162 : iPart)));
163 3 : return nullptr;
164 : }
165 :
166 1598 : poCurvePart = ReadCurveGeometry(
167 799 : poCurveRecord, /* iRecord = */ -1, nRRID, poSRS);
168 : }
169 : else
170 : {
171 37 : CPLAssert(nRRNM == RECORD_NAME_COMPOSITE_CURVE);
172 :
173 : const auto poCompositeCurveRecord =
174 37 : m_oCompositeCurveRecordIndex.FindRecord(nRRID);
175 37 : if (!poCompositeCurveRecord)
176 : {
177 3 : CPL_IGNORE_RET_VAL(EMIT_ERROR_OR_WARNING(CPLSPrintf(
178 : "%s: Value (RRNM=%d, RRID=%d) "
179 : "of instance %d of CUCO field does not point to an "
180 : "existing record.",
181 : GetErrorContext(), static_cast<int>(nRRNM), nRRID,
182 : iPart)));
183 3 : return nullptr;
184 : }
185 :
186 68 : poCurvePart = ReadCompositeCurveGeometryInternal(
187 : poCompositeCurveRecord, /* iRecord = */ -1, nRRID, poSRS,
188 34 : oSetAlreadyVisitedCompositeCurveRecords);
189 : }
190 :
191 833 : if (!poCurvePart)
192 : {
193 0 : return nullptr;
194 : }
195 :
196 833 : const int nPoints = poLS->getNumPoints();
197 833 : const int nPointsPart = poCurvePart->getNumPoints();
198 833 : if (nPointsPart == 0)
199 : {
200 0 : CPL_IGNORE_RET_VAL(EMIT_ERROR_OR_WARNING(CPLSPrintf(
201 : "%s: Value (RRNM=%d, RRID=%d) "
202 : "%d instance of CUCO field points to an empty curve.",
203 : GetErrorContext(), static_cast<int>(nRRNM), nRRID, iPart)));
204 0 : return nullptr;
205 : }
206 833 : if (nPoints == 0)
207 : {
208 745 : if (bReverse)
209 40 : poLS->addSubLineString(poCurvePart.get(), nPointsPart - 1,
210 : 0);
211 : else
212 705 : poLS->addSubLineString(poCurvePart.get());
213 : }
214 : else
215 : {
216 88 : bool bEndPointMismatch = false;
217 88 : if (bReverse)
218 : {
219 37 : if (poLS->getX(nPoints - 1) ==
220 74 : poCurvePart->getX(nPointsPart - 1) &&
221 37 : poLS->getY(nPoints - 1) ==
222 37 : poCurvePart->getY(nPointsPart - 1))
223 : {
224 37 : poLS->addSubLineString(poCurvePart.get(),
225 : nPointsPart - 2, 0);
226 : }
227 : else
228 : {
229 0 : bEndPointMismatch = true;
230 : }
231 : }
232 : else
233 : {
234 99 : if (poLS->getX(nPoints - 1) == poCurvePart->getX(0) &&
235 48 : poLS->getY(nPoints - 1) == poCurvePart->getY(0))
236 : {
237 48 : poLS->addSubLineString(poCurvePart.get(), 1);
238 : }
239 : else
240 : {
241 3 : bEndPointMismatch = true;
242 : }
243 : }
244 88 : if (bEndPointMismatch)
245 : {
246 3 : CPL_IGNORE_RET_VAL(EMIT_ERROR_OR_WARNING(CPLSPrintf(
247 : "%s: Value (RRNM=%d, "
248 : "RRID=%d) "
249 : "of curve instance %d extremity does not match "
250 : "composite curve extremity.",
251 : GetErrorContext(), static_cast<int>(nRRNM), nRRID,
252 : iPart)));
253 3 : return nullptr;
254 : }
255 : }
256 : }
257 : }
258 :
259 742 : poLS->assignSpatialReference(poSRS);
260 742 : return poLS;
261 : }
262 :
263 728 : std::unique_ptr<OGRLineString> OGRS101Reader::ReadCompositeCurveGeometry(
264 : const DDFRecord *poRecord, int iRecord, int nRecordID,
265 : const OGRSpatialReference *poSRS) const
266 : {
267 1456 : std::set<int> oSetAlreadyVisitedCompositeCurveRecords;
268 : return ReadCompositeCurveGeometryInternal(
269 : poRecord, iRecord, nRecordID, poSRS,
270 1456 : oSetAlreadyVisitedCompositeCurveRecords);
271 : }
272 :
273 : /************************************************************************/
274 : /* FillFeatureCompositeCurve() */
275 : /************************************************************************/
276 :
277 : /** Fill the content of the provided feature from the identified record
278 : * (of m_oCompositeCurveRecordIndex).
279 : */
280 300 : bool OGRS101Reader::FillFeatureCompositeCurve(const DDFRecordIndex &oIndex,
281 : int iRecord,
282 : OGRFeature &oFeature) const
283 : {
284 300 : const auto poRecord = oIndex.GetByIndex(iRecord);
285 300 : CPLAssert(poRecord);
286 :
287 : const OGRSpatialReference *poSRS =
288 300 : oFeature.GetDefnRef()->GetGeomFieldDefn(0)->GetSpatialRef();
289 : auto poCurve = ReadCompositeCurveGeometry(poRecord, iRecord,
290 600 : /* nRecordID = */ -1, poSRS);
291 300 : if (!poCurve)
292 : {
293 18 : if (m_bStrict)
294 6 : return false; // error message already emitted
295 : }
296 : else
297 : {
298 282 : oFeature.SetGeometry(std::move(poCurve));
299 : }
300 :
301 588 : return FillFeatureAttributes(oIndex, iRecord, INAS_FIELD, oFeature) &&
302 294 : FillFeatureWithNonAttrAssocSubfields(poRecord, iRecord, INAS_FIELD,
303 294 : oFeature);
304 : }
|