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 <algorithm>
17 : #include <memory>
18 :
19 : /************************************************************************/
20 : /* CreateSurfaceFeatureDefn() */
21 : /************************************************************************/
22 :
23 : /** Create the feature definition for the Surface layer
24 : */
25 247 : bool OGRS101Reader::CreateSurfaceFeatureDefn()
26 : {
27 247 : if (m_oSurfaceRecordIndex.GetCount() > 0)
28 : {
29 : m_poFeatureDefnSurface =
30 68 : OGRFeatureDefnRefCountedPtr::makeInstance(OGR_LAYER_NAME_SURFACE);
31 68 : m_poFeatureDefnSurface->SetGeomType(wkbPolygon);
32 68 : auto oSRSIter = m_oMapSRS.find(HORIZONTAL_CRS_ID);
33 68 : if (oSRSIter != m_oMapSRS.end())
34 : {
35 136 : m_poFeatureDefnSurface->GetGeomFieldDefn(0)->SetSpatialRef(
36 136 : OGRSpatialReferenceRefCountedPtr::makeClone(&oSRSIter->second)
37 68 : .get());
38 : }
39 : {
40 136 : OGRFieldDefn oFieldDefn(OGR_FIELD_NAME_RECORD_ID, OFTInteger);
41 68 : m_poFeatureDefnSurface->AddFieldDefn(&oFieldDefn);
42 : }
43 : {
44 136 : OGRFieldDefn oFieldDefn(OGR_FIELD_NAME_RECORD_VERSION, OFTInteger);
45 68 : m_poFeatureDefnSurface->AddFieldDefn(&oFieldDefn);
46 : }
47 68 : if (!InferFeatureDefn(m_oSurfaceRecordIndex, SRID_FIELD, INAS_FIELD, {},
48 68 : *m_poFeatureDefnSurface, m_oMapFieldDomains))
49 : {
50 1 : return false;
51 : }
52 : }
53 :
54 246 : return true;
55 : }
56 :
57 : /************************************************************************/
58 : /* ReadSurfaceGeometry() */
59 : /************************************************************************/
60 :
61 : std::unique_ptr<OGRPolygon>
62 344 : OGRS101Reader::ReadSurfaceGeometry(const DDFRecord *poRecord, int iRecord,
63 : int nRecordID,
64 : const OGRSpatialReference *poSRS) const
65 : {
66 344 : if (nRecordID < 0)
67 143 : nRecordID = poRecord->GetIntSubfield(SRID_FIELD, 0, RCID_SUBFIELD, 0);
68 :
69 74 : const auto GetErrorContext = [iRecord, nRecordID]()
70 : {
71 37 : if (iRecord >= 0)
72 37 : return CPLSPrintf("Record index=%d of SRID", iRecord);
73 : else
74 0 : return CPLSPrintf("Record ID=%d of SRID", nRecordID);
75 344 : };
76 :
77 344 : if (!poRecord->FindField(RIAS_FIELD))
78 : {
79 1 : CPL_IGNORE_RET_VAL(EMIT_ERROR_OR_WARNING(
80 : CPLSPrintf("%s: no RIAS field", GetErrorContext())));
81 1 : return nullptr;
82 : }
83 :
84 686 : auto poSurface = std::make_unique<OGRPolygon>();
85 343 : poSurface->assignSpatialReference(poSRS);
86 343 : std::unique_ptr<OGRLinearRing> poExteriorRing;
87 686 : std::vector<std::unique_ptr<OGRLinearRing>> apoInteriorRings;
88 658 : for (const auto *poRIASField : poRecord->GetFields(RIAS_FIELD))
89 : {
90 343 : const int nRings = poRIASField->GetRepeatCount();
91 979 : for (int iRing = 0; iRing < nRings; ++iRing)
92 : {
93 : const auto GetIntSubfield =
94 2633 : [poRecord, poRIASField, iRing](const char *pszSubFieldName)
95 : {
96 2633 : return poRecord->GetIntSubfield(poRIASField, pszSubFieldName,
97 2633 : iRing);
98 664 : };
99 :
100 664 : constexpr const char *RAUI_SUBFIELD = "RAUI";
101 664 : const int nRAUI = GetIntSubfield(RAUI_SUBFIELD);
102 664 : if (nRAUI != INSTRUCTION_INSERT)
103 : {
104 3 : CPL_IGNORE_RET_VAL(EMIT_ERROR_OR_WARNING(
105 : CPLSPrintf("%s: wrong value %d for RAUI "
106 : "subfield of %d instance of RIAS field.",
107 : GetErrorContext(), nRAUI, iRing)));
108 28 : return nullptr;
109 : }
110 :
111 661 : const RecordName nRRNM = GetIntSubfield(RRNM_SUBFIELD);
112 982 : if (nRRNM != RECORD_NAME_CURVE &&
113 321 : nRRNM != RECORD_NAME_COMPOSITE_CURVE)
114 : {
115 3 : CPL_IGNORE_RET_VAL(EMIT_ERROR_OR_WARNING(CPLSPrintf(
116 : "%s: Invalid value for RRNM "
117 : "subfield of %d instance of RIAS field: "
118 : "got %d, expected %d or %d.",
119 : GetErrorContext(), iRing, static_cast<int>(nRRNM),
120 : static_cast<int>(RECORD_NAME_CURVE),
121 : static_cast<int>(RECORD_NAME_COMPOSITE_CURVE))));
122 3 : return nullptr;
123 : }
124 :
125 658 : const int nRRID = GetIntSubfield(RRID_SUBFIELD);
126 0 : std::unique_ptr<OGRLineString> poCurvePart;
127 658 : if (nRRNM == RECORD_NAME_CURVE)
128 : {
129 : const auto poCurveRecord =
130 340 : m_oCurveRecordIndex.FindRecord(nRRID);
131 340 : if (!poCurveRecord)
132 : {
133 3 : CPL_IGNORE_RET_VAL(EMIT_ERROR_OR_WARNING(CPLSPrintf(
134 : "%s: Value (RRNM=%d, RRID=%d) "
135 : "of instance %d of RIAS field does not point to an "
136 : "existing record.",
137 : GetErrorContext(), static_cast<int>(nRRNM), nRRID,
138 : iRing)));
139 3 : return nullptr;
140 : }
141 :
142 674 : poCurvePart = ReadCurveGeometry(
143 337 : poCurveRecord, /* iRecord = */ -1, nRRID, poSRS);
144 : }
145 : else
146 : {
147 318 : CPLAssert(nRRNM == RECORD_NAME_COMPOSITE_CURVE);
148 :
149 : const auto poCompositeCurveRecord =
150 318 : m_oCompositeCurveRecordIndex.FindRecord(nRRID);
151 318 : if (!poCompositeCurveRecord)
152 : {
153 3 : CPL_IGNORE_RET_VAL(EMIT_ERROR_OR_WARNING(CPLSPrintf(
154 : "%s: Value (RRNM=%d, RRID=%d) "
155 : "of instance %d of RIAS field does not point to an "
156 : "existing record.",
157 : GetErrorContext(), static_cast<int>(nRRNM), nRRID,
158 : iRing)));
159 3 : return nullptr;
160 : }
161 :
162 630 : poCurvePart = ReadCompositeCurveGeometry(
163 315 : poCompositeCurveRecord, /* iRecord = */ -1, nRRID, poSRS);
164 : }
165 :
166 652 : if (!poCurvePart)
167 : {
168 2 : return nullptr;
169 : }
170 :
171 650 : bool bReverse = false;
172 650 : const int nORNT = GetIntSubfield(ORNT_SUBFIELD);
173 650 : if (nORNT == ORNT_REVERSE)
174 : {
175 328 : bReverse = true;
176 : }
177 322 : else if (nORNT != ORNT_FORWARD)
178 : {
179 3 : CPL_IGNORE_RET_VAL(EMIT_ERROR_OR_WARNING(
180 : CPLSPrintf("%s: Invalid value for ORNT "
181 : "subfield of %d instance of RIAS field: "
182 : "got %d, expected %d or %d.",
183 : GetErrorContext(), iRing, nORNT, ORNT_FORWARD,
184 : ORNT_REVERSE)));
185 3 : return nullptr;
186 : }
187 :
188 647 : auto poRing = std::make_unique<OGRLinearRing>();
189 647 : if (bReverse && poCurvePart->getNumPoints() > 0)
190 656 : poRing->addSubLineString(poCurvePart.get(),
191 328 : poCurvePart->getNumPoints() - 1, 0);
192 : else
193 319 : poRing->addSubLineString(poCurvePart.get());
194 647 : if (!poRing->get_IsClosed())
195 : {
196 3 : CPL_IGNORE_RET_VAL(EMIT_ERROR_OR_WARNING(
197 : CPLSPrintf("%s: Ring of index %d is not closed.",
198 : GetErrorContext(), iRing)));
199 3 : return nullptr;
200 : }
201 :
202 644 : constexpr const char *USAG_SUBFIELD = "USAG";
203 644 : constexpr int USAG_EXTERIOR = 1; // Exterior ring
204 644 : constexpr int USAG_INTERIOR = 2; // Interior ring
205 : const int nUSAG =
206 644 : poRecord->GetIntSubfield(RIAS_FIELD, 0, USAG_SUBFIELD, iRing);
207 644 : if (nUSAG == USAG_EXTERIOR)
208 : {
209 325 : if (poExteriorRing)
210 : {
211 3 : CPL_IGNORE_RET_VAL(EMIT_ERROR_OR_WARNING(CPLSPrintf(
212 : "%s: several rings tagged as exterior rings.",
213 : GetErrorContext())));
214 3 : return nullptr;
215 : }
216 322 : if (!poRing->isClockwise())
217 : {
218 3 : if (!EMIT_ERROR_OR_WARNING(CPLSPrintf(
219 : "%s: exterior ring orientation is not clockwise.",
220 : GetErrorContext())))
221 : {
222 1 : return nullptr;
223 : }
224 : }
225 321 : poExteriorRing = std::move(poRing);
226 : }
227 319 : else if (nUSAG == USAG_INTERIOR)
228 : {
229 316 : if (poRing->isClockwise())
230 : {
231 3 : if (!EMIT_ERROR_OR_WARNING(
232 : CPLSPrintf("%s: orientation of interior ring of "
233 : "index %d is not counter-clockwise.",
234 : GetErrorContext(), iRing)))
235 : {
236 1 : return nullptr;
237 : }
238 : }
239 315 : apoInteriorRings.push_back(std::move(poRing));
240 : }
241 : else
242 : {
243 3 : CPL_IGNORE_RET_VAL(EMIT_ERROR_OR_WARNING(
244 : CPLSPrintf("%s: Invalid value for USAG "
245 : "subfield of %d instance of RIAS field: "
246 : "got %d, expected %d or %d.",
247 : GetErrorContext(), iRing, nUSAG, USAG_EXTERIOR,
248 : USAG_INTERIOR)));
249 3 : return nullptr;
250 : }
251 : }
252 : }
253 :
254 315 : if (!poExteriorRing)
255 : {
256 3 : CPL_IGNORE_RET_VAL(EMIT_ERROR_OR_WARNING(CPLSPrintf(
257 : "%s: no ring tagged as exterior ring.", GetErrorContext())));
258 3 : return nullptr;
259 : }
260 :
261 312 : poSurface->addRing(std::move(poExteriorRing));
262 624 : for (auto &poRing : apoInteriorRings)
263 312 : poSurface->addRing(std::move(poRing));
264 :
265 312 : if (OGRGeometryFactory::haveGEOS())
266 : {
267 312 : std::string osReason;
268 312 : if (!poSurface->IsValid(&osReason))
269 : {
270 3 : if (!EMIT_ERROR_OR_WARNING(CPLSPrintf("%s: surface is invalid: %s.",
271 : GetErrorContext(),
272 : osReason.c_str())))
273 : {
274 1 : return nullptr;
275 : }
276 : }
277 : }
278 :
279 311 : return poSurface;
280 : }
281 :
282 : /************************************************************************/
283 : /* FillFeatureSurface() */
284 : /************************************************************************/
285 :
286 : /** Fill the content of the provided feature from the identified record
287 : * (of m_oSurfaceRecordIndex).
288 : */
289 143 : bool OGRS101Reader::FillFeatureSurface(const DDFRecordIndex &oIndex,
290 : int iRecord, OGRFeature &oFeature) const
291 : {
292 143 : const auto poRecord = oIndex.GetByIndex(iRecord);
293 143 : CPLAssert(poRecord);
294 :
295 : const OGRSpatialReference *poSRS =
296 143 : oFeature.GetDefnRef()->GetGeomFieldDefn(0)->GetSpatialRef();
297 : auto poSurface =
298 286 : ReadSurfaceGeometry(poRecord, iRecord, /* nRecordID = */ -1, poSRS);
299 143 : if (!poSurface)
300 : {
301 33 : if (m_bStrict)
302 12 : return false; // error message already emitted
303 : }
304 : else
305 : {
306 110 : oFeature.SetGeometry(std::move(poSurface));
307 : }
308 :
309 262 : return FillFeatureAttributes(oIndex, iRecord, INAS_FIELD, oFeature) &&
310 131 : FillFeatureWithNonAttrAssocSubfields(poRecord, iRecord, INAS_FIELD,
311 131 : oFeature);
312 : }
|