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 "ogrs101featurecatalog.h"
15 : #include "ogrs101readerconstants.h"
16 :
17 : #include <algorithm>
18 : #include <memory>
19 :
20 : /************************************************************************/
21 : /* CreateFeatureTypeFeatureDefns() */
22 : /************************************************************************/
23 :
24 : /** Create the feature definitions for the various kinds of Feature Type records
25 : */
26 246 : bool OGRS101Reader::CreateFeatureTypeFeatureDefns()
27 : {
28 246 : const int nCount = m_oFeatureTypeRecordIndex.GetCount();
29 :
30 : struct FeatureTypeDef
31 : {
32 : int nMaxMaskCount = 0;
33 : bool bMultiSpatialAssociations = false;
34 : bool bPromotedToMultiPointFromPoint = false;
35 : std::vector<int> anRecordIdx{};
36 : };
37 :
38 492 : std::map<FeatureTypeKey, FeatureTypeDef> oMapFeatureClassAndGeomType;
39 :
40 : // First pass to collect all (feature type code, geometry type, CRS Id) triples
41 649 : for (int iRecord = 0; iRecord < nCount; ++iRecord)
42 : {
43 405 : const auto poRecord = m_oFeatureTypeRecordIndex.GetByIndex(iRecord);
44 405 : constexpr const char *NFTC_SUBFIELD = "NFTC";
45 405 : FeatureTypeKey key;
46 405 : key.nFeatureTypeCode =
47 : poRecord->GetIntSubfield(FRID_FIELD, 0, NFTC_SUBFIELD, 0);
48 :
49 898 : const auto NormalizeCurve = [](RecordName name)
50 : {
51 898 : return name == RECORD_NAME_COMPOSITE_CURVE ? RECORD_NAME_CURVE
52 898 : : name;
53 : };
54 :
55 405 : bool bMultiSpatialAssociations = false;
56 : // S-101 page 19: A feature may reference multiple geometries
57 : // but must only reference geometries of a single
58 : // geometric primitive (point, pointset, curve or surface).
59 405 : const auto apoSPASFields = poRecord->GetFields(SPAS_FIELD);
60 405 : int nSPASCount = 0;
61 405 : bool bHeterogeneous = false;
62 804 : for (int iSPASField = 0;
63 804 : iSPASField < static_cast<int>(apoSPASFields.size()); ++iSPASField)
64 : {
65 399 : const auto poSPASField = apoSPASFields[iSPASField];
66 399 : if (iSPASField == 0)
67 : {
68 : key.nGeometryType = NormalizeCurve(
69 366 : poRecord->GetIntSubfield(poSPASField, RRNM_SUBFIELD, 0));
70 : }
71 :
72 399 : const int nSPASCountThisIter = poSPASField->GetRepeatCount();
73 399 : nSPASCount += nSPASCountThisIter;
74 929 : for (int iSPAS = 0; iSPAS < nSPASCountThisIter; ++iSPAS)
75 : {
76 532 : if (NormalizeCurve(poRecord->GetIntSubfield(
77 532 : poSPASField, RRNM_SUBFIELD, iSPAS)) !=
78 : key.nGeometryType)
79 : {
80 2 : bHeterogeneous = true;
81 2 : break;
82 : }
83 : }
84 : }
85 405 : if (bHeterogeneous)
86 : {
87 2 : if (!EMIT_ERROR_OR_WARNING(CPLSPrintf(
88 : "Record index %d of FRID: has %d "
89 : "spatial associations with at least 2 "
90 : "not being of the same geometry type%s",
91 : iRecord, nSPASCount, m_bStrict ? "" : ". Ignoring it")))
92 : {
93 1 : return false;
94 : }
95 1 : continue;
96 : }
97 403 : else if (nSPASCount > 1)
98 : {
99 164 : bMultiSpatialAssociations = true;
100 : }
101 :
102 403 : if (key.nGeometryType == RECORD_NAME_POINT)
103 : {
104 : const int nRRID =
105 98 : poRecord->GetIntSubfield(SPAS_FIELD, 0, RRID_SUBFIELD, 0);
106 98 : const auto poGeomRecord = m_oPointRecordIndex.FindRecord(nRRID);
107 98 : if (!poGeomRecord && !EMIT_ERROR_OR_WARNING(CPLSPrintf(
108 : "Record index %d of FRID: Point of id %d "
109 : "does not exist",
110 : iRecord, nRRID)))
111 : {
112 1 : return false;
113 : }
114 :
115 : const auto nCRSId =
116 96 : poGeomRecord ? GetCRSIdForPointRecord(poGeomRecord, -1, nRRID)
117 97 : : HORIZONTAL_CRS_ID;
118 97 : if (nCRSId == INVALID_CRS_ID)
119 : {
120 : // Error already emitted
121 0 : if (m_bStrict)
122 0 : return false;
123 0 : continue;
124 : }
125 97 : key.nCRSId = nCRSId;
126 : }
127 305 : else if (key.nGeometryType == RECORD_NAME_MULTIPOINT)
128 : {
129 : const int nRRID =
130 66 : poRecord->GetIntSubfield(SPAS_FIELD, 0, RRID_SUBFIELD, 0);
131 : const auto poGeomRecord =
132 66 : m_oMultiPointRecordIndex.FindRecord(nRRID);
133 66 : if (!poGeomRecord &&
134 0 : !EMIT_ERROR_OR_WARNING(
135 : CPLSPrintf("Record index %d of FRID: MultiPoint of id %d "
136 : "does not exist",
137 : iRecord, nRRID)))
138 : {
139 0 : return false;
140 : }
141 :
142 : const auto nCRSId =
143 : poGeomRecord
144 66 : ? GetCRSIdForMultiPointRecord(poGeomRecord, -1, nRRID)
145 66 : : HORIZONTAL_CRS_ID;
146 66 : if (nCRSId == INVALID_CRS_ID)
147 : {
148 : // Error already emitted
149 0 : if (m_bStrict)
150 0 : return false;
151 0 : continue;
152 : }
153 66 : key.nCRSId = nCRSId;
154 : }
155 239 : else if (!apoSPASFields.empty())
156 200 : key.nCRSId = HORIZONTAL_CRS_ID;
157 :
158 : const bool bPromotedToMultiPointFromPoint =
159 402 : (key.nGeometryType == RECORD_NAME_POINT &&
160 402 : bMultiSpatialAssociations);
161 402 : if (bPromotedToMultiPointFromPoint)
162 65 : key.nGeometryType = RECORD_NAME_MULTIPOINT;
163 :
164 402 : auto &featureTypeDef = oMapFeatureClassAndGeomType[key];
165 402 : if (bPromotedToMultiPointFromPoint)
166 : {
167 65 : featureTypeDef.bPromotedToMultiPointFromPoint = true;
168 : }
169 337 : else if (bMultiSpatialAssociations)
170 : {
171 99 : featureTypeDef.bMultiSpatialAssociations = true;
172 : }
173 :
174 402 : int nMaskCount = 0;
175 468 : for (const auto poField : poRecord->GetFields(MASK_FIELD))
176 : {
177 66 : nMaskCount += poField->GetRepeatCount();
178 : }
179 402 : featureTypeDef.nMaxMaskCount =
180 402 : std::max(featureTypeDef.nMaxMaskCount, nMaskCount);
181 :
182 402 : featureTypeDef.anRecordIdx.push_back(iRecord);
183 : }
184 :
185 527 : for (auto &[key, def] : oMapFeatureClassAndGeomType)
186 : {
187 285 : std::string osLayerCode;
188 285 : std::string osName;
189 285 : const auto oIter = m_featureTypeCodes.find(key.nFeatureTypeCode);
190 285 : if (oIter != m_featureTypeCodes.end())
191 : {
192 283 : osLayerCode = oIter->second;
193 283 : osName = osLayerCode;
194 : }
195 : else
196 : {
197 2 : if (!EMIT_ERROR_OR_WARNING(CPLSPrintf(
198 : "Features pointing at unknown feature type code %d",
199 : static_cast<int>(key.nFeatureTypeCode))))
200 : {
201 1 : return false;
202 : }
203 : osName = CPLSPrintf("unknownFeatureType%d",
204 1 : static_cast<int>(key.nFeatureTypeCode));
205 : }
206 :
207 284 : const auto oIterSRS = m_oMapSRS.find(key.nCRSId);
208 284 : CPLAssert(key.nCRSId == INVALID_CRS_ID || oIterSRS != m_oMapSRS.end());
209 : const OGRSpatialReference *poSRS =
210 284 : key.nCRSId != INVALID_CRS_ID ? &(oIterSRS->second) : nullptr;
211 284 : const bool bIs2D = key.nCRSId == HORIZONTAL_CRS_ID;
212 :
213 284 : OGRwkbGeometryType eGeomType = wkbNone;
214 284 : const char *pszExpectedPermittedPrimitive = nullptr;
215 284 : switch (static_cast<int>(key.nGeometryType))
216 : {
217 36 : case static_cast<int>(PSEUDO_RECORD_NAME_NO_GEOM):
218 : {
219 36 : osName += "_NoGeom";
220 36 : pszExpectedPermittedPrimitive =
221 : OGRS101FeatureCatalog::PERMITTED_PRIMITIVE_NO_GEOMETRY;
222 36 : break;
223 : }
224 :
225 30 : case static_cast<int>(RECORD_NAME_POINT):
226 : {
227 30 : CPLAssert(poSRS);
228 30 : osName += '_';
229 30 : if (def.bMultiSpatialAssociations)
230 : {
231 0 : osName += GetMultiPointLayerName(*poSRS);
232 0 : eGeomType = bIs2D ? wkbMultiPoint : wkbMultiPoint25D;
233 : }
234 : else
235 : {
236 30 : osName += GetPointLayerName(*poSRS);
237 30 : eGeomType = bIs2D ? wkbPoint : wkbPoint25D;
238 : }
239 30 : pszExpectedPermittedPrimitive =
240 : OGRS101FeatureCatalog::PERMITTED_PRIMITIVE_POINT;
241 30 : break;
242 : }
243 :
244 92 : case static_cast<int>(RECORD_NAME_MULTIPOINT):
245 : {
246 92 : CPLAssert(poSRS);
247 92 : osName += '_';
248 92 : if (def.bMultiSpatialAssociations)
249 : {
250 31 : osName += "CollectionOfMultiPoint";
251 31 : eGeomType = wkbGeometryCollection;
252 : }
253 : else
254 : {
255 61 : osName += GetMultiPointLayerName(*poSRS);
256 61 : eGeomType = bIs2D ? wkbMultiPoint : wkbMultiPoint25D;
257 : }
258 92 : pszExpectedPermittedPrimitive =
259 : OGRS101FeatureCatalog::PERMITTED_PRIMITIVE_POINTSET;
260 92 : break;
261 : }
262 :
263 62 : case static_cast<int>(RECORD_NAME_CURVE):
264 : {
265 62 : if (def.bMultiSpatialAssociations)
266 : {
267 31 : osName += "_MultiLine";
268 31 : eGeomType = wkbMultiLineString;
269 : }
270 : else
271 : {
272 31 : osName += "_Line";
273 31 : eGeomType = wkbLineString;
274 : }
275 62 : pszExpectedPermittedPrimitive =
276 : OGRS101FeatureCatalog::PERMITTED_PRIMITIVE_CURVE;
277 62 : break;
278 : }
279 :
280 62 : case static_cast<int>(RECORD_NAME_SURFACE):
281 : {
282 62 : if (def.bMultiSpatialAssociations)
283 : {
284 31 : osName += "_MultiPolygon";
285 31 : eGeomType = wkbMultiPolygon;
286 : }
287 : else
288 : {
289 31 : osName += "_Polygon";
290 31 : eGeomType = wkbPolygon;
291 : }
292 62 : pszExpectedPermittedPrimitive =
293 : OGRS101FeatureCatalog::PERMITTED_PRIMITIVE_SURFACE;
294 62 : break;
295 : }
296 :
297 2 : default:
298 : {
299 2 : if (!EMIT_ERROR_OR_WARNING(CPLSPrintf(
300 : "Features pointing at unknown spatial record type %d",
301 : static_cast<int>(key.nGeometryType))))
302 : {
303 1 : return false;
304 : }
305 1 : osName += "_UnknownGeomType";
306 1 : osName += std::to_string(static_cast<int>(key.nGeometryType));
307 1 : break;
308 : }
309 : }
310 :
311 283 : const OGRS101FeatureCatalog::FeatureType *psFeatureType = nullptr;
312 283 : if (m_poFeatureCatalog)
313 : {
314 : const auto oIterFT =
315 283 : m_poFeatureCatalog->GetFeatureTypes().find(osLayerCode);
316 283 : if (oIterFT != m_poFeatureCatalog->GetFeatureTypes().end())
317 : {
318 0 : psFeatureType = &(oIterFT->second);
319 : }
320 284 : else if (!cpl::starts_with(osLayerCode, "FeatureType") &&
321 1 : !EMIT_ERROR_OR_WARNING(
322 : CPLSPrintf("Feature type %s is not referenced in the "
323 : "feature catalog",
324 : osLayerCode.c_str())))
325 : {
326 0 : return false;
327 : }
328 : }
329 :
330 282 : if (pszExpectedPermittedPrimitive && psFeatureType &&
331 0 : !cpl::contains(psFeatureType->permittedPrimitives,
332 565 : pszExpectedPermittedPrimitive) &&
333 0 : !EMIT_ERROR_OR_WARNING(CPLSPrintf(
334 : "Features of %s contain records with primitive %s, whereas "
335 : "it is not allowed by the feature catalog",
336 : osLayerCode.c_str(), pszExpectedPermittedPrimitive)))
337 : {
338 0 : return false;
339 : }
340 :
341 : auto poFDefn =
342 283 : OGRFeatureDefnRefCountedPtr::makeInstance(osName.c_str());
343 283 : poFDefn->SetGeomType(eGeomType);
344 1415 : for (const char *pszOGRFieldName :
345 : {OGR_FIELD_NAME_RECORD_ID, OGR_FIELD_NAME_RECORD_VERSION,
346 1698 : OGR_FIELD_NAME_AGEN, OGR_FIELD_NAME_FIDN, OGR_FIELD_NAME_FIDS})
347 : {
348 2830 : OGRFieldDefn oFieldDefn(pszOGRFieldName, OFTInteger);
349 1415 : poFDefn->AddFieldDefn(&oFieldDefn);
350 : }
351 :
352 283 : if (eGeomType != wkbNone)
353 : {
354 246 : CPLAssert(poSRS);
355 492 : poFDefn->GetGeomFieldDefn(0)->SetSpatialRef(
356 492 : OGRSpatialReferenceRefCountedPtr::makeClone(poSRS).get());
357 399 : const bool bList = def.bMultiSpatialAssociations ||
358 153 : def.bPromotedToMultiPointFromPoint;
359 :
360 : {
361 : OGRFieldDefn oFieldDefn(OGR_FIELD_NAME_GEOMETRY_LAYER_NAME,
362 492 : bList ? OFTStringList : OFTString);
363 246 : poFDefn->AddFieldDefn(&oFieldDefn);
364 : }
365 246 : for (const char *pszOGRFieldName :
366 492 : {OGR_FIELD_NAME_GEOMETRY_RECORD_ID})
367 : {
368 : OGRFieldDefn oFieldDefn(pszOGRFieldName,
369 492 : bList ? OFTIntegerList : OFTInteger);
370 246 : poFDefn->AddFieldDefn(&oFieldDefn);
371 : }
372 246 : if (eGeomType == wkbLineString || eGeomType == wkbMultiLineString)
373 : {
374 : OGRFieldDefn oFieldDefn(OGR_FIELD_NAME_GEOMETRY_ORIENTATION,
375 124 : bList ? OFTStringList : OFTString);
376 62 : poFDefn->AddFieldDefn(&oFieldDefn);
377 : }
378 492 : for (const char *pszOGRFieldName :
379 738 : {OGR_FIELD_NAME_SMIN, OGR_FIELD_NAME_SMAX})
380 : {
381 : OGRFieldDefn oFieldDefn(pszOGRFieldName,
382 984 : bList ? OFTIntegerList : OFTInteger);
383 492 : poFDefn->AddFieldDefn(&oFieldDefn);
384 : }
385 : }
386 :
387 849 : for (const char *pszAttrFieldName :
388 1132 : {ATTR_FIELD, INAS_FIELD, FASC_FIELD})
389 : {
390 1698 : if (!InferFeatureDefn(
391 849 : m_oFeatureTypeRecordIndex, FRID_FIELD, pszAttrFieldName,
392 849 : def.anRecordIdx, *poFDefn, m_oMapFieldDomains, nullptr,
393 849 : strcmp(pszAttrFieldName, ATTR_FIELD) == 0 ? psFeatureType
394 : : nullptr))
395 : {
396 0 : return false;
397 : }
398 : }
399 :
400 283 : if (def.nMaxMaskCount == 1)
401 : {
402 : {
403 : OGRFieldDefn oFieldDefn(OGR_FIELD_NAME_MASK_LAYER_NAME,
404 0 : OFTString);
405 0 : poFDefn->AddFieldDefn(&oFieldDefn);
406 : }
407 : {
408 : OGRFieldDefn oFieldDefn(OGR_FIELD_NAME_MASK_RECORD_ID,
409 0 : OFTInteger);
410 0 : poFDefn->AddFieldDefn(&oFieldDefn);
411 : }
412 : {
413 : OGRFieldDefn oFieldDefn(OGR_FIELD_NAME_MASK_INDICATOR,
414 0 : OFTString);
415 0 : poFDefn->AddFieldDefn(&oFieldDefn);
416 : }
417 :
418 0 : OGRGeomFieldDefn oGeomFieldDefn("maskGeometry", wkbLineString);
419 0 : oGeomFieldDefn.SetSpatialRef(
420 0 : OGRSpatialReferenceRefCountedPtr::makeClone(
421 0 : &(m_oMapSRS[HORIZONTAL_CRS_ID]))
422 0 : .get());
423 0 : poFDefn->AddGeomFieldDefn(&oGeomFieldDefn);
424 : }
425 283 : else if (def.nMaxMaskCount > 1)
426 : {
427 : {
428 : OGRFieldDefn oFieldDefn(OGR_FIELD_NAME_MASK_LAYER_NAME,
429 62 : OFTStringList);
430 31 : poFDefn->AddFieldDefn(&oFieldDefn);
431 : }
432 : {
433 : OGRFieldDefn oFieldDefn(OGR_FIELD_NAME_MASK_RECORD_ID,
434 62 : OFTIntegerList);
435 31 : poFDefn->AddFieldDefn(&oFieldDefn);
436 : }
437 : {
438 : OGRFieldDefn oFieldDefn(OGR_FIELD_NAME_MASK_INDICATOR,
439 62 : OFTStringList);
440 31 : poFDefn->AddFieldDefn(&oFieldDefn);
441 : }
442 :
443 62 : OGRGeomFieldDefn oGeomFieldDefn("maskGeometry", wkbMultiLineString);
444 31 : oGeomFieldDefn.SetSpatialRef(
445 31 : OGRSpatialReferenceRefCountedPtr::makeClone(
446 31 : &(m_oMapSRS[HORIZONTAL_CRS_ID]))
447 31 : .get());
448 31 : poFDefn->AddGeomFieldDefn(&oGeomFieldDefn);
449 : }
450 :
451 659 : for (int nRecordIdx : def.anRecordIdx)
452 : {
453 : const int nRCID =
454 : m_oFeatureTypeRecordIndex.GetByIndex(nRecordIdx)
455 376 : ->GetIntSubfield(FRID_FIELD, 0, RCID_SUBFIELD, 0);
456 376 : m_oMapFeatureTypeIdToFDefn[nRCID] = poFDefn.get();
457 : }
458 :
459 566 : LayerDef layerDef;
460 283 : layerDef.poFeatureDefn = std::move(poFDefn);
461 283 : if (psFeatureType)
462 : {
463 0 : layerDef.osName = psFeatureType->name;
464 0 : layerDef.osDefinition = psFeatureType->definition;
465 0 : layerDef.osAlias = psFeatureType->alias;
466 : }
467 283 : layerDef.anRecordIndices = std::move(def.anRecordIdx);
468 283 : m_oMapFeatureKeyToLayerDef[key] = std::move(layerDef);
469 : }
470 :
471 242 : return true;
472 : }
473 :
474 : /************************************************************************/
475 : /* ReadGeometry() */
476 : /************************************************************************/
477 :
478 : template <typename T, typename GeomReaderMethodType>
479 1145 : bool OGRS101Reader::ReadGeometry(
480 : const DDFRecordIndex &oIndex, const char *pszErrorContext,
481 : int nGeomRecordID, const char *pszGeomType, bool bReverse,
482 : OGRFeature &oFeature, std::unique_ptr<OGRGeometryCollection> &poMultiGeom,
483 : GeomReaderMethodType geomReaderMethod, int iGeomField) const
484 : {
485 1145 : const auto poGeomRecord = oIndex.FindRecord(nGeomRecordID);
486 :
487 1145 : if (!poGeomRecord && !EMIT_ERROR_OR_WARNING(CPLSPrintf(
488 : "%s: %s of ID=%d does not exist", pszErrorContext,
489 : pszGeomType, nGeomRecordID)))
490 : {
491 3 : return false;
492 : }
493 :
494 : const auto poGeomFieldDefn =
495 1142 : oFeature.GetDefnRef()->GetGeomFieldDefn(iGeomField);
496 1142 : const OGRSpatialReference *poSRS = poGeomFieldDefn->GetSpatialRef();
497 1142 : std::unique_ptr<T> poGeom;
498 1142 : if (poGeomRecord)
499 : {
500 1135 : poGeom = (this->*geomReaderMethod)(poGeomRecord, /* index = */ -1,
501 : nGeomRecordID, poSRS);
502 : }
503 1142 : if (!poGeom)
504 : {
505 7 : if (poGeomRecord && !EMIT_ERROR_OR_WARNING(CPLSPrintf(
506 : "%s: %s of ID=%d is invalid", pszErrorContext,
507 : pszGeomType, nGeomRecordID)))
508 : {
509 0 : return false;
510 : }
511 : }
512 : else if constexpr (std::is_same_v<T, OGRLineString>)
513 : {
514 469 : if (bReverse)
515 103 : poGeom->reversePoints();
516 : }
517 :
518 1142 : const auto eGeomType = poGeomFieldDefn->GetType();
519 :
520 2120 : if (wkbFlatten(eGeomType) == wkbGeometryCollection ||
521 : (!std::is_same_v<T, OGRMultiPoint> &&
522 978 : OGR_GT_IsSubClassOf(eGeomType, wkbGeometryCollection)))
523 : {
524 824 : if (!poMultiGeom)
525 : {
526 378 : poMultiGeom = std::make_unique<typename T::MultiType>();
527 378 : poMultiGeom->assignSpatialReference(poSRS);
528 : }
529 824 : poMultiGeom->addGeometry(poGeom ? std::move(poGeom)
530 : : std::make_unique<T>());
531 : }
532 : else
533 : {
534 318 : oFeature.SetGeomField(iGeomField, std::move(poGeom));
535 : }
536 :
537 1142 : return true;
538 : }
539 :
540 : /************************************************************************/
541 : /* FillFeatureTypeGeometry() */
542 : /************************************************************************/
543 :
544 : /** Fill the geometry of the provided feature from the identified record
545 : * (of m_oFeatureTypeRecordIndex).
546 : */
547 707 : bool OGRS101Reader::FillFeatureTypeGeometry(const DDFRecord *poRecord,
548 : int iRecord,
549 : OGRFeature &oFeature) const
550 : {
551 : // Process geometry (several spatial association per feature possible)
552 1414 : CPLStringList aosLayerNames, aosOrientations;
553 1414 : std::vector<int> anRRID, anSMIN, anSMAX;
554 707 : std::unique_ptr<OGRGeometryCollection> poMultiGeom;
555 :
556 1414 : const auto apoSPASFields = poRecord->GetFields(SPAS_FIELD);
557 1394 : for (const auto &poSPASField : apoSPASFields)
558 : {
559 693 : const int nSPASCount = poSPASField->GetRepeatCount();
560 :
561 1628 : for (int iSPAS = 0; iSPAS < nSPASCount; ++iSPAS)
562 : {
563 : const auto GetIntSubfield =
564 5629 : [poRecord, poSPASField, iSPAS](const char *pszSubFieldName)
565 : {
566 5629 : return poRecord->GetIntSubfield(poSPASField, pszSubFieldName,
567 5629 : iSPAS);
568 941 : };
569 :
570 : const std::string osErrorContext =
571 : CPLSPrintf("Feature type record index %d, SPAS instance %d",
572 941 : iRecord, iSPAS);
573 :
574 941 : const int nSAUI = GetIntSubfield("SAUI");
575 941 : if (nSAUI != INSTRUCTION_INSERT)
576 : {
577 3 : if (!EMIT_ERROR_OR_WARNING(
578 : CPLSPrintf("%s: SAUI value %d is invalid",
579 : osErrorContext.c_str(), nSAUI)))
580 : {
581 1 : return false;
582 : }
583 : }
584 :
585 940 : const RecordName nRRNM = GetIntSubfield(RRNM_SUBFIELD);
586 :
587 940 : const int nRRID = GetIntSubfield(RRID_SUBFIELD);
588 :
589 940 : const int nORNT = GetIntSubfield(ORNT_SUBFIELD);
590 :
591 1659 : const bool bIsLine = nRRNM == RECORD_NAME_CURVE ||
592 719 : nRRNM == RECORD_NAME_COMPOSITE_CURVE;
593 940 : switch (nORNT)
594 : {
595 164 : case ORNT_FORWARD:
596 : {
597 164 : break;
598 : }
599 :
600 106 : case ORNT_REVERSE:
601 : {
602 106 : if (!bIsLine)
603 : {
604 3 : if (!EMIT_ERROR_OR_WARNING(
605 : CPLSPrintf("%s: "
606 : "ORNT = Reverse is invalid for "
607 : "non-curve geometry",
608 : osErrorContext.c_str())))
609 : {
610 1 : return false;
611 : }
612 : }
613 105 : break;
614 : }
615 :
616 667 : case ORNT_NULL:
617 : {
618 667 : if (bIsLine)
619 : {
620 0 : if (!EMIT_ERROR_OR_WARNING(CPLSPrintf(
621 : "%s: ORNT = Null is invalid for curve geometry",
622 : osErrorContext.c_str())))
623 : {
624 0 : return false;
625 : }
626 : }
627 667 : break;
628 : }
629 :
630 3 : default:
631 : {
632 3 : if (!EMIT_ERROR_OR_WARNING(
633 : CPLSPrintf("%s: ORNT = %d is invalid",
634 : osErrorContext.c_str(), nORNT)))
635 : {
636 1 : return false;
637 : }
638 2 : break;
639 : }
640 : }
641 :
642 938 : bool bInvalidType = false;
643 938 : switch (static_cast<int>(nRRNM))
644 : {
645 302 : case static_cast<int>(RECORD_NAME_POINT):
646 : {
647 604 : if (!ReadGeometry<OGRPoint>(
648 302 : m_oPointRecordIndex, osErrorContext.c_str(), nRRID,
649 : "Point", false, oFeature, poMultiGeom,
650 : &OGRS101Reader::ReadPointGeometry))
651 : {
652 0 : return false;
653 : }
654 :
655 : const auto poGeomFieldDefn =
656 302 : oFeature.GetDefnRef()->GetGeomFieldDefn(0);
657 302 : CPLAssert(poGeomFieldDefn);
658 : const OGRSpatialReference *poSRS =
659 302 : poGeomFieldDefn->GetSpatialRef();
660 302 : CPLAssert(poSRS);
661 :
662 302 : aosLayerNames.push_back(GetPointLayerName(*poSRS).c_str());
663 :
664 302 : break;
665 : }
666 :
667 164 : case static_cast<int>(RECORD_NAME_MULTIPOINT):
668 : {
669 328 : if (!ReadGeometry<OGRMultiPoint>(
670 164 : m_oMultiPointRecordIndex, osErrorContext.c_str(),
671 : nRRID, "MultiPoint", false, oFeature, poMultiGeom,
672 : &OGRS101Reader::ReadMultiPointGeometry))
673 : {
674 0 : return false;
675 : }
676 :
677 : const auto poGeomFieldDefn =
678 164 : oFeature.GetDefnRef()->GetGeomFieldDefn(0);
679 164 : CPLAssert(poGeomFieldDefn);
680 : const OGRSpatialReference *poSRS =
681 164 : poGeomFieldDefn->GetSpatialRef();
682 164 : CPLAssert(poSRS);
683 :
684 164 : aosLayerNames.push_back(
685 328 : GetMultiPointLayerName(*poSRS).c_str());
686 :
687 164 : break;
688 : }
689 :
690 267 : case static_cast<int>(RECORD_NAME_CURVE):
691 : case static_cast<int>(RECORD_NAME_COMPOSITE_CURVE):
692 : {
693 : const char *pszLayerName =
694 267 : nRRNM == RECORD_NAME_CURVE
695 : ? OGR_LAYER_NAME_CURVE
696 267 : : OGR_LAYER_NAME_COMPOSITE_CURVE;
697 :
698 : bool ret;
699 267 : if (nRRNM == RECORD_NAME_CURVE)
700 : {
701 442 : ret = ReadGeometry<OGRLineString>(
702 221 : m_oCurveRecordIndex, osErrorContext.c_str(), nRRID,
703 : pszLayerName, nORNT == ORNT_REVERSE, oFeature,
704 : poMultiGeom, &OGRS101Reader::ReadCurveGeometry);
705 : }
706 : else
707 : {
708 92 : ret = ReadGeometry<OGRLineString>(
709 46 : m_oCompositeCurveRecordIndex,
710 : osErrorContext.c_str(), nRRID, pszLayerName,
711 : nORNT == ORNT_REVERSE, oFeature, poMultiGeom,
712 : &OGRS101Reader::ReadCompositeCurveGeometry);
713 : }
714 :
715 267 : if (!ret)
716 : {
717 2 : return false;
718 : }
719 :
720 265 : aosLayerNames.push_back(pszLayerName);
721 265 : aosOrientations.push_back(
722 : nORNT == ORNT_FORWARD ? "forward" : "reverse");
723 :
724 265 : break;
725 : }
726 :
727 204 : case static_cast<int>(RECORD_NAME_SURFACE):
728 : {
729 408 : if (!ReadGeometry<OGRPolygon>(
730 204 : m_oSurfaceRecordIndex, osErrorContext.c_str(),
731 : nRRID, OGR_LAYER_NAME_SURFACE, false, oFeature,
732 : poMultiGeom, &OGRS101Reader::ReadSurfaceGeometry))
733 : {
734 1 : return false;
735 : }
736 :
737 203 : aosLayerNames.push_back(OGR_LAYER_NAME_SURFACE);
738 :
739 203 : break;
740 : }
741 :
742 1 : default:
743 1 : bInvalidType = true;
744 1 : break;
745 : }
746 :
747 935 : if (bInvalidType)
748 : {
749 1 : if (!EMIT_ERROR_OR_WARNING(CPLSPrintf("%s: "
750 : "Invalid RRNM = %d",
751 : osErrorContext.c_str(),
752 : static_cast<int>(nRRNM))))
753 : {
754 0 : return false;
755 : }
756 : }
757 : else
758 : {
759 934 : anRRID.push_back(nRRID);
760 :
761 934 : anSMIN.push_back(GetIntSubfield("SMIN"));
762 :
763 934 : anSMAX.push_back(GetIntSubfield("SMAX"));
764 : }
765 : }
766 : }
767 :
768 701 : if (!apoSPASFields.empty())
769 : {
770 627 : CPLAssert(anRRID.size() == anSMIN.size());
771 627 : CPLAssert(anRRID.size() == anSMAX.size());
772 627 : CPLAssert(anRRID.size() == static_cast<size_t>(aosLayerNames.size()));
773 627 : CPLAssert(aosOrientations.empty() ||
774 : anRRID.size() == static_cast<size_t>(aosOrientations.size()));
775 :
776 627 : if (poMultiGeom)
777 : {
778 308 : oFeature.SetGeometry(std::move(poMultiGeom));
779 :
780 308 : oFeature.SetField(OGR_FIELD_NAME_GEOMETRY_LAYER_NAME,
781 308 : aosLayerNames.List());
782 308 : if (!aosOrientations.empty())
783 : {
784 57 : oFeature.SetField(OGR_FIELD_NAME_GEOMETRY_ORIENTATION,
785 57 : aosOrientations.List());
786 : }
787 308 : oFeature.SetField(OGR_FIELD_NAME_GEOMETRY_RECORD_ID,
788 308 : static_cast<int>(anRRID.size()), anRRID.data());
789 308 : if (std::find_if(anSMIN.begin(), anSMIN.end(),
790 735 : [](int x) { return x > 0; }) != anSMIN.end())
791 : {
792 189 : oFeature.SetField(OGR_FIELD_NAME_SMIN,
793 189 : static_cast<int>(anSMIN.size()),
794 189 : anSMIN.data());
795 : }
796 308 : if (std::find_if(anSMAX.begin(), anSMAX.end(),
797 735 : [](int x) { return x > 0; }) != anSMAX.end())
798 : {
799 189 : oFeature.SetField(OGR_FIELD_NAME_SMAX,
800 189 : static_cast<int>(anSMAX.size()),
801 189 : anSMAX.data());
802 : }
803 : }
804 319 : else if (!aosLayerNames.empty())
805 : {
806 318 : oFeature.SetField(OGR_FIELD_NAME_GEOMETRY_LAYER_NAME,
807 318 : aosLayerNames[0]);
808 318 : oFeature.SetField(OGR_FIELD_NAME_GEOMETRY_RECORD_ID, anRRID[0]);
809 318 : if (!aosOrientations.empty())
810 : {
811 151 : oFeature.SetField(OGR_FIELD_NAME_GEOMETRY_ORIENTATION,
812 151 : aosOrientations[0]);
813 : }
814 318 : if (anSMIN[0] > 0)
815 254 : oFeature.SetField(OGR_FIELD_NAME_SMIN, anSMIN[0]);
816 318 : if (anSMAX[0] > 0)
817 254 : oFeature.SetField(OGR_FIELD_NAME_SMAX, anSMAX[0]);
818 : }
819 : }
820 :
821 701 : return true;
822 : }
823 :
824 : /************************************************************************/
825 : /* FillFeatureTypeMask() */
826 : /************************************************************************/
827 :
828 : /** Fill the mask info of the provided feature from the identified record
829 : * (of m_oFeatureTypeRecordIndex).
830 : */
831 701 : bool OGRS101Reader::FillFeatureTypeMask(const DDFRecord *poRecord, int iRecord,
832 : OGRFeature &oFeature) const
833 : {
834 701 : std::unique_ptr<OGRGeometryCollection> poMultiGeom;
835 :
836 1402 : std::vector<int> anRRID;
837 1402 : CPLStringList aosLayerNames;
838 1402 : CPLStringList aosMaskIndicators;
839 :
840 701 : constexpr int MASK_GEOM_FIELD_IDX = 1;
841 :
842 1402 : const auto apoMASKFields = poRecord->GetFields(MASK_FIELD);
843 841 : for (const auto *poMASKField : apoMASKFields)
844 : {
845 143 : const int nMaskCount = poMASKField->GetRepeatCount();
846 :
847 353 : for (int iMASK = 0; iMASK < nMaskCount; ++iMASK)
848 : {
849 : const auto GetIntSubfield =
850 847 : [poRecord, poMASKField, iMASK](const char *pszSubFieldName)
851 : {
852 847 : return poRecord->GetIntSubfield(poMASKField, pszSubFieldName,
853 847 : iMASK);
854 213 : };
855 :
856 : const std::string osErrorContext =
857 : CPLSPrintf("Feature type record index %d, MASK instance %d",
858 213 : iRecord, iMASK);
859 :
860 213 : const int nMUIN = GetIntSubfield("MUIN");
861 213 : if (nMUIN != INSTRUCTION_INSERT)
862 : {
863 3 : if (!EMIT_ERROR_OR_WARNING(
864 : CPLSPrintf("%s: MUIN value %d is invalid",
865 : osErrorContext.c_str(), nMUIN)))
866 : {
867 1 : return false;
868 : }
869 : }
870 :
871 212 : const int nMIND = GetIntSubfield("MIND");
872 212 : constexpr int MASK_INDICATOR_TRUNCATED_BY_DATA_COVERAGE_LIMIT = 1;
873 212 : constexpr int MASK_INDICATOR_SUPPRESS_PORTRAYAL = 2;
874 212 : if (nMIND == MASK_INDICATOR_TRUNCATED_BY_DATA_COVERAGE_LIMIT)
875 : {
876 139 : aosMaskIndicators.push_back("truncatedByDataCoverageLimit");
877 : }
878 73 : else if (nMIND == MASK_INDICATOR_SUPPRESS_PORTRAYAL)
879 : {
880 70 : aosMaskIndicators.push_back("suppressPortrayal");
881 : }
882 : else
883 : {
884 3 : if (!EMIT_ERROR_OR_WARNING(CPLSPrintf(
885 : "%s: MIND value %d is invalid. Expected %d or %d",
886 : osErrorContext.c_str(), nMIND,
887 : MASK_INDICATOR_TRUNCATED_BY_DATA_COVERAGE_LIMIT,
888 : MASK_INDICATOR_SUPPRESS_PORTRAYAL)))
889 : {
890 1 : return false;
891 : }
892 2 : aosMaskIndicators.push_back(CPLSPrintf("unknown%d", nMIND));
893 : }
894 :
895 211 : const int nRRID = GetIntSubfield(RRID_SUBFIELD);
896 211 : anRRID.push_back(nRRID);
897 :
898 211 : const RecordName nRRNM = GetIntSubfield(RRNM_SUBFIELD);
899 284 : if (nRRNM == RECORD_NAME_CURVE ||
900 73 : nRRNM == RECORD_NAME_COMPOSITE_CURVE)
901 : {
902 208 : const char *pszLayerName = nRRNM == RECORD_NAME_CURVE
903 : ? OGR_LAYER_NAME_CURVE
904 208 : : OGR_LAYER_NAME_COMPOSITE_CURVE;
905 :
906 : bool ret;
907 208 : if (nRRNM == RECORD_NAME_CURVE)
908 : {
909 276 : ret = ReadGeometry<OGRLineString>(
910 138 : m_oCurveRecordIndex, osErrorContext.c_str(), nRRID,
911 : pszLayerName, false, oFeature, poMultiGeom,
912 : &OGRS101Reader::ReadCurveGeometry, MASK_GEOM_FIELD_IDX);
913 : }
914 : else
915 : {
916 140 : ret = ReadGeometry<OGRLineString>(
917 70 : m_oCompositeCurveRecordIndex, osErrorContext.c_str(),
918 : nRRID, pszLayerName, false, oFeature, poMultiGeom,
919 : &OGRS101Reader::ReadCompositeCurveGeometry,
920 : MASK_GEOM_FIELD_IDX);
921 : }
922 :
923 208 : if (!ret)
924 : {
925 0 : return false;
926 : }
927 :
928 208 : aosLayerNames.push_back(pszLayerName);
929 : }
930 : else
931 : {
932 3 : if (!EMIT_ERROR_OR_WARNING(CPLSPrintf(
933 : "%s: Invalid value for RRNM subfield: "
934 : "got %d, expected %d or %d.",
935 : osErrorContext.c_str(), static_cast<int>(nRRNM),
936 : static_cast<int>(RECORD_NAME_CURVE),
937 : static_cast<int>(RECORD_NAME_COMPOSITE_CURVE))))
938 : {
939 1 : return false;
940 : }
941 :
942 2 : aosLayerNames.push_back("");
943 : }
944 : }
945 : }
946 :
947 698 : if (!aosLayerNames.empty())
948 : {
949 70 : CPLAssert(anRRID.size() == static_cast<size_t>(aosLayerNames.size()));
950 70 : CPLAssert(anRRID.size() ==
951 : static_cast<size_t>(aosMaskIndicators.size()));
952 :
953 70 : if (poMultiGeom)
954 : {
955 70 : oFeature.SetGeomField(MASK_GEOM_FIELD_IDX, std::move(poMultiGeom));
956 :
957 70 : oFeature.SetField(OGR_FIELD_NAME_MASK_LAYER_NAME,
958 70 : aosLayerNames.List());
959 :
960 70 : oFeature.SetField(OGR_FIELD_NAME_MASK_RECORD_ID,
961 70 : static_cast<int>(anRRID.size()), anRRID.data());
962 :
963 70 : oFeature.SetField(OGR_FIELD_NAME_MASK_INDICATOR,
964 70 : aosMaskIndicators.List());
965 : }
966 : else
967 : {
968 0 : oFeature.SetField(OGR_FIELD_NAME_MASK_LAYER_NAME, aosLayerNames[0]);
969 :
970 0 : oFeature.SetField(OGR_FIELD_NAME_MASK_RECORD_ID, anRRID[0]);
971 :
972 0 : oFeature.SetField(OGR_FIELD_NAME_MASK_INDICATOR,
973 0 : aosMaskIndicators[0]);
974 : }
975 : }
976 :
977 698 : return true;
978 : }
979 :
980 : /************************************************************************/
981 : /* FillFeatureFeatureType() */
982 : /************************************************************************/
983 :
984 : /** Fill the content of the provided feature from the identified record
985 : * (of m_oFeatureTypeRecordIndex).
986 : */
987 707 : bool OGRS101Reader::FillFeatureFeatureType(const DDFRecordIndex &oIndex,
988 : int iRecord,
989 : OGRFeature &oFeature) const
990 : {
991 707 : const auto poRecord = oIndex.GetByIndex(iRecord);
992 707 : CPLAssert(poRecord);
993 :
994 707 : if (const auto poFOIDField = poRecord->FindField(FOID_FIELD))
995 : {
996 706 : const int nAGEN = poRecord->GetIntSubfield(poFOIDField, "AGEN", 0);
997 706 : oFeature.SetField(OGR_FIELD_NAME_AGEN, nAGEN);
998 :
999 706 : const int nFIDN = poRecord->GetIntSubfield(poFOIDField, "FIDN", 0);
1000 706 : oFeature.SetField(OGR_FIELD_NAME_FIDN, nFIDN);
1001 :
1002 706 : const int nFIDS = poRecord->GetIntSubfield(poFOIDField, "FIDS", 0);
1003 706 : oFeature.SetField(OGR_FIELD_NAME_FIDS, nFIDS);
1004 : }
1005 1 : else if (!EMIT_ERROR_OR_WARNING(CPLSPrintf(
1006 : "Feature type record index %d: no FOID field", iRecord)))
1007 : {
1008 0 : return false;
1009 : }
1010 :
1011 : // A FRID record might have a ATTR field, a INAS field (pointing to a IRID
1012 : // record), a FASC field (thus pointing to another FRID record), or any
1013 : // combination of the 3.
1014 :
1015 707 : return FillFeatureTypeGeometry(poRecord, iRecord, oFeature) &&
1016 701 : FillFeatureTypeMask(poRecord, iRecord, oFeature) &&
1017 698 : FillFeatureAttributes(oIndex, iRecord, ATTR_FIELD, oFeature) &&
1018 698 : FillFeatureAttributes(oIndex, iRecord, INAS_FIELD, oFeature) &&
1019 698 : FillFeatureAttributes(oIndex, iRecord, FASC_FIELD, oFeature) &&
1020 698 : FillFeatureWithNonAttrAssocSubfields(poRecord, iRecord, INAS_FIELD,
1021 1408 : oFeature) &&
1022 698 : FillFeatureWithNonAttrAssocSubfields(poRecord, iRecord, FASC_FIELD,
1023 707 : oFeature);
1024 : }
|