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 <array>
18 : #include <cmath>
19 : #include <memory>
20 : #include <set>
21 : #include <string_view>
22 : #include <utility>
23 :
24 : /************************************************************************/
25 : /* ReadDatasetGeneralInformationRecord() */
26 : /************************************************************************/
27 :
28 : /** Read the general information record */
29 355 : bool OGRS101Reader::ReadDatasetGeneralInformationRecord(
30 : const DDFRecord *poRecord)
31 : {
32 355 : if (!poRecord)
33 0 : return EMIT_ERROR("no Dataset General Information record.");
34 :
35 689 : return ReadDSID(poRecord) && ReadDSSI(poRecord) && ReadATCS(poRecord) &&
36 307 : ReadITCS(poRecord) && ReadFTCS(poRecord) && ReadIACS(poRecord) &&
37 689 : ReadFACS(poRecord) && ReadARCS(poRecord);
38 : }
39 :
40 : /************************************************************************/
41 : /* ReadDSID() */
42 : /************************************************************************/
43 :
44 : /** Read the Dataset Identification (DSID) field of the general information
45 : * record.
46 : */
47 355 : bool OGRS101Reader::ReadDSID(const DDFRecord *poRecord)
48 : {
49 355 : const auto poField = poRecord->FindField(DSID_FIELD);
50 355 : if (!poField)
51 2 : return EMIT_ERROR("DSID field not found.");
52 :
53 : // Record name
54 : const RecordName nRCNM =
55 353 : poRecord->GetIntSubfield(poField, RCNM_SUBFIELD, 0);
56 355 : if (nRCNM != RECORD_NAME_DATASET_IDENTIFICATION &&
57 2 : !EMIT_ERROR_OR_WARNING("Invalid value for RCNM subfield of DSID."))
58 : {
59 1 : return false;
60 : }
61 :
62 : // Record identifier
63 352 : const int nRCID = poRecord->GetIntSubfield(poField, RCID_SUBFIELD, 0);
64 : // Only one record expected
65 354 : if (nRCID != 1 &&
66 2 : !EMIT_ERROR_OR_WARNING("Invalid value for RCID subfield of DSID."))
67 : {
68 1 : return false;
69 : }
70 :
71 : static const struct
72 : {
73 : const char *pszS101Name;
74 : const char *pszGDALName;
75 : } mapMetadataKeys[] = {
76 : {"ENSP", "ENCODING_SPECIFICATION"},
77 : {"ENED", "ENCODING_SPECIFICATION_EDITION"},
78 : {"PRSP", "PRODUCT_IDENTIFIER"},
79 : {"PRED", "PRODUCT_EDITION"},
80 : {"PROF", "APPLICATION_PROFILE"},
81 : {"DSNM", "DATASET_IDENTIFIER"},
82 : {"DSTL", "DATASET_TITLE"},
83 : {"DSRD", "DATASET_REFERENCE_DATE"},
84 : {"DSLG", "DATASET_LANGUAGE"},
85 : {"DSAB", "DATASET_ABSTRACT"},
86 : {"DSED", "DATASET_EDITION"},
87 : };
88 :
89 4212 : for (const auto &item : mapMetadataKeys)
90 : {
91 : const char *pszValue =
92 3861 : poRecord->GetStringSubfield(poField, item.pszS101Name, 0);
93 3861 : if (!pszValue)
94 : {
95 0 : if (!EMIT_ERROR_OR_WARNING(std::string("no subfield ")
96 : .append(item.pszS101Name)
97 : .append(" in DSID.")
98 : .c_str()))
99 : {
100 0 : return false;
101 : }
102 : }
103 3861 : else if (pszValue[0])
104 : {
105 3510 : m_aosMetadata.SetNameValue(item.pszGDALName, pszValue);
106 : }
107 : }
108 :
109 : const char *pszPRSP =
110 351 : m_aosMetadata.FetchNameValueDef("PRODUCT_IDENTIFIER", "");
111 351 : if (!strstr(pszPRSP, "S-101") &&
112 0 : !EMIT_ERROR_OR_WARNING(
113 : CPLSPrintf("%s is an ISO8211 file, but not a S-101 product. "
114 : "Product identifier is '%s'.",
115 : m_osFilename.c_str(), pszPRSP)))
116 : {
117 0 : return false;
118 : }
119 :
120 : // Accept 1.x, but only >= 2.0 is operational
121 1053 : if (!STARTS_WITH(pszPRSP, "INT.IHO.S-101.1.") &&
122 353 : !STARTS_WITH(pszPRSP, "INT.IHO.S-101.2.") &&
123 2 : !EMIT_ERROR_OR_WARNING(CPLSPrintf(
124 : "Product identifier is '%s', but only 'INT.IHO.S-101.2.0' is "
125 : "nominally handled. Going on but the dataset might not be "
126 : "correctly read.",
127 : pszPRSP)))
128 : {
129 1 : return false;
130 : }
131 :
132 350 : if (!CheckFieldDefinitions())
133 16 : return false;
134 :
135 334 : return true;
136 : }
137 :
138 : /************************************************************************/
139 : /* CheckFieldDefinitions() */
140 : /************************************************************************/
141 :
142 : /** Check that field and subfield definitions conforms to the specification.
143 : */
144 350 : bool OGRS101Reader::CheckFieldDefinitions() const
145 : {
146 350 : bool bRet = CheckField0000Definition();
147 :
148 : // Note the '\\\\' has 4 bytes instead of the '\\' that appears in the
149 : // spec, because of the escaping of backslash in C++
150 : const std::map<std::string_view, std::pair<const char *, const char *>>
151 : fieldArrayDescrAndFieldControls = {
152 : {DSID_FIELD,
153 0 : {"RCNM!RCID!ENSP!ENED!PRSP!PRED!PROF!DSNM!DSTL!DSRD!DSLG!DSAB!"
154 : "DSED\\\\*DSTC",
155 0 : "(b11,b14,7A,A(8),3A,(b11))"}},
156 : {DSSI_FIELD,
157 0 : {"DCOX!DCOY!DCOZ!CMFX!CMFY!CMFZ!NOIR!NOPN!NOMN!NOCN!NOXN!NOSN!"
158 : "NOFR",
159 0 : "(3b48,10b14)"}},
160 0 : {ATCS_FIELD, {"*ATCD!ANCD", "(A,b12)"}},
161 0 : {ITCS_FIELD, {"*ITCD!ITNC", "(A,b12)"}},
162 0 : {FTCS_FIELD, {"*FTCD!FTNC", "(A,b12)"}},
163 0 : {IACS_FIELD, {"*IACD!IANC", "(A,b12)"}},
164 0 : {FACS_FIELD, {"*FACD!FANC", "(A,b12)"}},
165 0 : {ARCS_FIELD, {"*ARCD!ARNC", "(A,b12)"}},
166 0 : {CSID_FIELD, {"RCNM!RCID!NCRC", "(b11,b14,b11)"}},
167 : {CRSH_FIELD,
168 0 : {"CRIX!CRST!CSTY!CRNM!CRSI!CRSS!SCRI", "(3b11,2A,b11,A)"}},
169 0 : {CSAX_FIELD, {"*AXTY!AXUM", "(2b11)"}},
170 0 : {VDAT_FIELD, {"DTNM!DTID!DTSR!SCRI", "(2A,b11,A)"}},
171 0 : {IRID_FIELD, {"RCNM!RCID!NITC!RVER!RUIN", "(b11,b14,2b12,b11)"}},
172 : {INAS_FIELD,
173 0 : {"RRNM!RRID!NIAC!NARC!IUIN\\\\*NATC!ATIX!PAIX!ATIN!ATVL",
174 0 : "(b11,b14,2b12,b11,(3b12,b11,A))"}},
175 0 : {ATTR_FIELD, {"*NATC!ATIX!PAIX!ATIN!ATVL", "(3b12,b11,A)"}},
176 0 : {C2IT_FIELD, {"YCOO!XCOO", "(2b24)"}},
177 0 : {C3IT_FIELD, {"VCID!YCOO!XCOO!ZCOO", "(b11,3b24)"}},
178 0 : {C2IL_FIELD, {"*YCOO!XCOO", "(2b24)"}},
179 0 : {C3IL_FIELD, {"VCID\\\\*YCOO!XCOO!ZCOO", "(b11,(3b24))"}},
180 0 : {PRID_FIELD, {"RCNM!RCID!RVER!RUIN", "(b11,b14,b12,b11)"}},
181 0 : {MRID_FIELD, {"RCNM!RCID!RVER!RUIN", "(b11,b14,b12,b11)"}},
182 0 : {CRID_FIELD, {"RCNM!RCID!RVER!RUIN", "(b11,b14,b12,b11)"}},
183 0 : {PTAS_FIELD, {"*RRNM!RRID!TOPI", "(b11,b14,b11)"}},
184 0 : {SEGH_FIELD, {"INTP", "(b11)"}},
185 0 : {CCID_FIELD, {"RCNM!RCID!RVER!RUIN", "(b11,b14,b12,b11)"}},
186 0 : {CUCO_FIELD, {"*RRNM!RRID!ORNT", "(b11,b14,b11)"}},
187 0 : {SRID_FIELD, {"RCNM!RCID!RVER!RUIN", "(b11,b14,b12,b11)"}},
188 0 : {RIAS_FIELD, {"*RRNM!RRID!ORNT!USAG!RAUI", "(b11,b14,3b11)"}},
189 0 : {FRID_FIELD, {"RCNM!RCID!NFTC!RVER!RUIN", "(b11,b14,2b12,b11)"}},
190 0 : {FOID_FIELD, {"AGEN!FIDN!FIDS", "(b12,b14,b12)"}},
191 : {SPAS_FIELD,
192 0 : {"*RRNM!RRID!ORNT!SMIN!SMAX!SAUI", "(b11,b14,b11,2b14,b11)"}},
193 : {FASC_FIELD,
194 0 : {"RRNM!RRID!NFAC!NARC!FAUI\\\\*NATC!ATIX!PAIX!ATIN!ATVL",
195 0 : "(b11,b14,2b12,b11,(3b12,b11,A))"}},
196 0 : {MASK_FIELD, {"*RRNM!RRID!MIND!MUIN", "(b11,b14,2b11)"}},
197 350 : };
198 :
199 5525 : for (const auto &poFieldDefn : m_poModule->GetFieldDefns())
200 : {
201 5175 : const char *pszFieldName = poFieldDefn->GetName();
202 5175 : if (strcmp(pszFieldName, _0000_FIELD) != 0)
203 : {
204 : const auto oIter =
205 4827 : fieldArrayDescrAndFieldControls.find(pszFieldName);
206 4827 : if (oIter == fieldArrayDescrAndFieldControls.end())
207 : {
208 2 : if (!EMIT_ERROR_OR_WARNING(CPLSPrintf(
209 : "Unknown field definition '%s'.", pszFieldName)))
210 : {
211 1 : bRet = false;
212 : }
213 : }
214 : else
215 : {
216 4825 : const char *pszExpectedArrayDescr = oIter->second.first;
217 4825 : if (strcmp(poFieldDefn->GetArrayDescr(),
218 4827 : pszExpectedArrayDescr) != 0 &&
219 2 : !EMIT_ERROR_OR_WARNING(
220 : CPLSPrintf("For array description of field definition "
221 : "'%s', got '%s' whereas '%s' is expected.",
222 : pszFieldName, poFieldDefn->GetArrayDescr(),
223 : pszExpectedArrayDescr)))
224 : {
225 1 : bRet = false;
226 : }
227 :
228 4825 : const char *pszExpectedFormatControls = oIter->second.second;
229 4825 : if (strcmp(poFieldDefn->GetFormatControls(),
230 4827 : pszExpectedFormatControls) != 0 &&
231 2 : !EMIT_ERROR_OR_WARNING(CPLSPrintf(
232 : "For format controls of field definition '%s', got "
233 : "'%s' whereas '%s' is expected.",
234 : pszFieldName, poFieldDefn->GetFormatControls(),
235 : pszExpectedFormatControls)))
236 : {
237 1 : bRet = false;
238 : }
239 :
240 4825 : const DDF_data_struct_code eExpectedDataStruct =
241 4825 : strstr(pszExpectedArrayDescr, "\\\\") != nullptr
242 9097 : ? dsc_concatenated
243 4272 : : pszExpectedArrayDescr[0] == '*' ? dsc_array
244 : : dsc_vector;
245 4825 : if (poFieldDefn->GetDataStructCode() != eExpectedDataStruct &&
246 0 : !EMIT_ERROR_OR_WARNING(CPLSPrintf(
247 : "Data struct code of field definition '%s', got "
248 : "'%d' whereas '%d' is expected.",
249 : pszFieldName, poFieldDefn->GetDataStructCode(),
250 : eExpectedDataStruct)))
251 : {
252 0 : bRet = false;
253 : }
254 :
255 4825 : const bool bHasIntegerSubfields =
256 5144 : strstr(pszExpectedFormatControls, "b1") != nullptr ||
257 319 : strstr(pszExpectedFormatControls, "b2") != nullptr;
258 4825 : const bool bHasFloatSubfields =
259 4825 : strstr(pszExpectedFormatControls, "b4") != nullptr;
260 4825 : const bool bHasStringSubfields =
261 4825 : strchr(pszExpectedFormatControls, 'A') != nullptr;
262 4825 : const DDF_data_type_code eExpectedDataTypeCode =
263 4825 : (bHasIntegerSubfields && !bHasFloatSubfields &&
264 : !bHasStringSubfields)
265 9650 : ? dtc_implicit_point
266 : :
267 : // Commenting below code, since it doesn't actually
268 : // occur with S-101 fields
269 : // (!bHasIntegerSubfields && bHasFloatSubfields && !bHasStringSubfields) ?
270 : // dtc_explicit_point :
271 : // cppcheck-suppress knownConditionTrueFalse
272 0 : (!bHasIntegerSubfields && !bHasFloatSubfields &&
273 : bHasStringSubfields)
274 2518 : ? dtc_char_string
275 : : dtc_mixed_data_type;
276 4827 : if (poFieldDefn->GetDataTypeCode() != eExpectedDataTypeCode &&
277 2 : !EMIT_ERROR_OR_WARNING(CPLSPrintf(
278 : "Data type code of field definition '%s', got "
279 : "'%d' whereas '%d' is expected.",
280 : pszFieldName, poFieldDefn->GetDataTypeCode(),
281 : eExpectedDataTypeCode)))
282 : {
283 1 : bRet = false;
284 : }
285 : }
286 : }
287 : }
288 :
289 700 : return bRet;
290 : }
291 :
292 : /************************************************************************/
293 : /* CheckField0000Definition() */
294 : /************************************************************************/
295 :
296 : /** Check that the special "0000" field conforms to the specification.
297 : */
298 350 : bool OGRS101Reader::CheckField0000Definition() const
299 : {
300 350 : bool bRet = true;
301 :
302 350 : const auto po0000FieldDefn = m_poModule->FindFieldDefn(_0000_FIELD);
303 350 : if (!po0000FieldDefn)
304 : {
305 2 : return EMIT_ERROR_OR_WARNING(
306 : "Field definition of 0000 control field not found.");
307 : }
308 :
309 350 : if (po0000FieldDefn->GetDataStructCode() != dsc_elementary &&
310 2 : !EMIT_ERROR_OR_WARNING("Data struct code of field definition of 0000 "
311 : "control field must be elementary."))
312 : {
313 1 : bRet = false;
314 : }
315 :
316 350 : if (po0000FieldDefn->GetDataTypeCode() != dtc_char_string &&
317 2 : !EMIT_ERROR_OR_WARNING("Data type code of field definition of 0000 "
318 : "control field must be char_string."))
319 : {
320 1 : bRet = false;
321 : }
322 :
323 352 : if (po0000FieldDefn->GetFormatControls()[0] != 0 &&
324 4 : !EMIT_ERROR_OR_WARNING("Format controls of field definition of 0000 "
325 : "control field must be empty."))
326 : {
327 2 : bRet = false;
328 : }
329 :
330 : // Should contain concatenated pairs of parent_field_name,child_field_name
331 : // without any separator: e.g. DSIDDSSICSIDCRSHCRSHCSAXCRSHVDAT
332 348 : const char *psz0000Descr = po0000FieldDefn->GetArrayDescr();
333 348 : const size_t n0000DescrLen = strlen(psz0000Descr);
334 348 : constexpr int FIELD_NAME_SIZE = 4;
335 348 : constexpr int PARENT_CHILD_PAIR_SIZE = 2 * FIELD_NAME_SIZE;
336 350 : if ((n0000DescrLen % PARENT_CHILD_PAIR_SIZE) != 0 &&
337 2 : !EMIT_ERROR_OR_WARNING(
338 : "Length of field tag pairs of field definition of 0000 "
339 : "control field must be a multiple of 8."))
340 : {
341 1 : bRet = false;
342 : }
343 :
344 348 : constexpr int MAX_CHILDREN_COUNT = 8;
345 : using ArrayOfChildren = std::array<const char *, MAX_CHILDREN_COUNT>;
346 :
347 : static const struct
348 : {
349 : const std::string_view svParent;
350 : ArrayOfChildren apszChildren;
351 : } knownPairs[] = {
352 : {DSID_FIELD,
353 : {DSSI_FIELD, ATCS_FIELD, ITCS_FIELD, FTCS_FIELD, IACS_FIELD,
354 : FACS_FIELD, ARCS_FIELD}},
355 : {CSID_FIELD, {CRSH_FIELD}},
356 : {CRSH_FIELD, {CSAX_FIELD, VDAT_FIELD}},
357 : {IRID_FIELD, {ATTR_FIELD, INAS_FIELD}},
358 : {PRID_FIELD, {INAS_FIELD, C2IT_FIELD, C3IT_FIELD}},
359 : {MRID_FIELD, {INAS_FIELD, C2IL_FIELD, C3IL_FIELD}},
360 : {CRID_FIELD, {INAS_FIELD, PTAS_FIELD, SEGH_FIELD}},
361 : {SEGH_FIELD, {C2IL_FIELD}},
362 : {CCID_FIELD, {INAS_FIELD, CUCO_FIELD}},
363 : {SRID_FIELD, {INAS_FIELD, RIAS_FIELD}},
364 : {FRID_FIELD,
365 : {FOID_FIELD, ATTR_FIELD, INAS_FIELD, SPAS_FIELD, FASC_FIELD,
366 : MASK_FIELD}},
367 348 : };
368 :
369 696 : const std::set<std::string> oSetUsedFieldNames = [this]
370 : {
371 348 : std::set<std::string> s;
372 5511 : for (const auto &poFieldDefn : m_poModule->GetFieldDefns())
373 5163 : s.insert(poFieldDefn->GetName());
374 348 : return s;
375 696 : }();
376 :
377 : // Return an iterator of knownPairs that points to the entry where
378 : // iter->svParent == svParent, but only if one of its allowed children
379 : // is in the set of fields actually found in the file.
380 : const auto GetParentIter =
381 14344 : [&oSetUsedFieldNames](const std::string_view &svParent)
382 : {
383 : const auto iter =
384 8785 : std::find_if(std::begin(knownPairs), std::end(knownPairs),
385 58370 : [&svParent](const auto &item)
386 58370 : { return svParent == item.svParent; });
387 8785 : if (iter != std::end(knownPairs))
388 : {
389 5559 : const auto allowedChildren = iter->apszChildren;
390 5559 : if (std::find_if(allowedChildren.begin(), allowedChildren.end(),
391 11370 : [&oSetUsedFieldNames](const char *pszStr)
392 : {
393 5718 : return pszStr &&
394 5652 : cpl::contains(oSetUsedFieldNames,
395 11436 : pszStr);
396 5559 : }) != allowedChildren.end())
397 : {
398 5548 : return iter;
399 : }
400 : }
401 3237 : return std::end(knownPairs);
402 348 : };
403 :
404 : // Returns an iterator of allowedChildren only is svChild is one of the
405 : // knownPairs::apszChildren.
406 35045 : const auto IsKnownChild = [](const ArrayOfChildren &allowedChildren,
407 : const std::string_view &svChild)
408 : {
409 35045 : return std::find_if(allowedChildren.begin(), allowedChildren.end(),
410 339405 : [&svChild](const char *pszStr)
411 239662 : { return pszStr && svChild == pszStr; }) !=
412 35045 : allowedChildren.end();
413 : };
414 :
415 348 : const size_t nPairs = n0000DescrLen / PARENT_CHILD_PAIR_SIZE;
416 696 : std::set<std::string> oSetReferencedParent;
417 696 : std::set<std::string> oSetReferencedChildren;
418 348 : std::set<std::pair<std::string, std::string>> oSetFoundPairs;
419 4027 : for (size_t i = 0; i < nPairs; ++i)
420 : {
421 3679 : bool bUnknownParentOrChild = false;
422 3679 : const std::string osParent(psz0000Descr + i * PARENT_CHILD_PAIR_SIZE,
423 7358 : FIELD_NAME_SIZE);
424 3679 : oSetReferencedParent.insert(osParent);
425 :
426 3679 : const std::string osChild(psz0000Descr + i * PARENT_CHILD_PAIR_SIZE +
427 : FIELD_NAME_SIZE,
428 7358 : FIELD_NAME_SIZE);
429 3679 : oSetReferencedChildren.insert(osChild);
430 :
431 3681 : if (!cpl::contains(oSetUsedFieldNames, osParent) &&
432 2 : !EMIT_ERROR_OR_WARNING(
433 : CPLSPrintf("Field '%s' referenced in field definition of 0000 "
434 : "control field does not exist.",
435 : osParent.c_str())))
436 : {
437 1 : bUnknownParentOrChild = true;
438 1 : bRet = false;
439 : }
440 :
441 3683 : if (!cpl::contains(oSetUsedFieldNames, osChild) &&
442 4 : !EMIT_ERROR_OR_WARNING(
443 : CPLSPrintf("Field '%s' referenced in field definition of 0000 "
444 : "control field does not exist.",
445 : osChild.c_str())))
446 : {
447 2 : bUnknownParentOrChild = true;
448 2 : bRet = false;
449 : }
450 :
451 3681 : if (!oSetFoundPairs.insert({osParent, osChild}).second &&
452 2 : !EMIT_ERROR_OR_WARNING(
453 : CPLSPrintf("Pair ('%s','%s') referenced multiple time in field "
454 : "definition of 0000 control field.",
455 : osParent.c_str(), osChild.c_str())))
456 : {
457 1 : bRet = false;
458 : }
459 :
460 3679 : if (!bUnknownParentOrChild)
461 : {
462 3676 : const auto oIter = GetParentIter(osParent);
463 3676 : if (oIter == std::end(knownPairs))
464 : {
465 3 : if (!EMIT_ERROR_OR_WARNING(CPLSPrintf(
466 : "Field '%s' referenced in field definition of 0000 "
467 : "control field is not a parent of a registered "
468 : "(parent,child) pair.",
469 : osParent.c_str())))
470 : {
471 1 : bRet = false;
472 : }
473 : }
474 : else
475 : {
476 3676 : if (!IsKnownChild(oIter->apszChildren, osChild) &&
477 3 : !EMIT_ERROR_OR_WARNING(CPLSPrintf(
478 : "Field '%s' referenced in field definition of 0000 "
479 : "control field is not an allowed child of a registered "
480 : "('%s',child) pair.",
481 : osChild.c_str(), osParent.c_str())))
482 : {
483 1 : bRet = false;
484 : }
485 : }
486 : }
487 : }
488 :
489 348 : if (nPairs > 0)
490 : {
491 5455 : for (const std::string &osFieldName : oSetUsedFieldNames)
492 : {
493 5109 : if (GetParentIter(osFieldName) != std::end(knownPairs) &&
494 5113 : !cpl::contains(oSetReferencedParent, osFieldName) &&
495 4 : !EMIT_ERROR_OR_WARNING(CPLSPrintf(
496 : "Field '%s' is not referenced as a parent in field "
497 : "definition of 0000 control field.",
498 : osFieldName.c_str())))
499 : {
500 2 : bRet = false;
501 : }
502 :
503 5109 : if (std::find_if(std::begin(knownPairs), std::end(knownPairs),
504 31372 : [&IsKnownChild, &osFieldName](const auto &item)
505 : {
506 62744 : return IsKnownChild(item.apszChildren,
507 62744 : osFieldName);
508 5109 : }) != std::end(knownPairs) &&
509 5117 : !cpl::contains(oSetReferencedChildren, osFieldName) &&
510 8 : !EMIT_ERROR_OR_WARNING(CPLSPrintf(
511 : "Field '%s' is not referenced as a child in field "
512 : "definition of 0000 control field.",
513 : osFieldName.c_str())))
514 : {
515 4 : bRet = false;
516 : }
517 : }
518 : }
519 :
520 348 : return bRet;
521 : }
522 :
523 : /************************************************************************/
524 : /* ReadDSSI() */
525 : /************************************************************************/
526 :
527 : /** Read the Dataset Structure Information (DSSI) field.
528 : */
529 334 : bool OGRS101Reader::ReadDSSI(const DDFRecord *poRecord)
530 : {
531 334 : const auto poField = poRecord->FindField(DSSI_FIELD);
532 334 : if (!poField)
533 2 : return EMIT_ERROR("DSSI field not found");
534 :
535 332 : int bSuccess = false;
536 :
537 : // must NOT be set as static, as it depens on "this" !
538 : const struct
539 : {
540 : const char *pszKey;
541 : double *pdfVal;
542 332 : } doubleFields[] = {
543 332 : {"DCOX", &m_dfXShift},
544 332 : {"DCOY", &m_dfYShift},
545 332 : {"DCOZ", &m_dfZShift},
546 332 : };
547 :
548 1316 : for (const auto &field : doubleFields)
549 : {
550 1980 : *(field.pdfVal) = poRecord->GetFloatSubfield(
551 990 : DSSI_FIELD, 0, field.pszKey, 0, &bSuccess);
552 990 : if (!bSuccess && !EMIT_ERROR_OR_WARNING(CPLSPrintf(
553 : "no %s subfield of DSSI.", field.pszKey)))
554 : {
555 0 : return false;
556 : }
557 990 : if (std::isnan(*(field.pdfVal)))
558 : {
559 6 : return EMIT_ERROR(
560 : CPLSPrintf("NaN value in %s subfield of DSSI.", field.pszKey));
561 : }
562 : }
563 :
564 328 : if (m_dfXShift != S101_SHIFT &&
565 2 : !EMIT_ERROR_OR_WARNING(
566 : "Value of DCOX subfield of DSSI is not at official value."))
567 : {
568 1 : return false;
569 : }
570 :
571 327 : if (m_dfYShift != S101_SHIFT &&
572 2 : !EMIT_ERROR_OR_WARNING(
573 : "Value of DCOY subfield of DSSI is not at official value."))
574 : {
575 1 : return false;
576 : }
577 :
578 326 : if (m_dfZShift != S101_SHIFT &&
579 2 : !EMIT_ERROR_OR_WARNING(
580 : "Value of DCOZ subfield of DSSI is not at official value."))
581 : {
582 1 : return false;
583 : }
584 :
585 : // must NOT be set as static, as it depens on "this" !
586 : const struct
587 : {
588 : const char *pszKey;
589 : int *pnVal;
590 323 : } intFields[] = {
591 323 : {"CMFX", &m_nXScale},
592 323 : {"CMFY", &m_nYScale},
593 323 : {"CMFZ", &m_nZScale},
594 323 : {"NOIR", &m_nCountInformationRecord},
595 323 : {"NOPN", &m_nCountPointRecord},
596 323 : {"NOMN", &m_nCountMultiPointRecord},
597 323 : {"NOCN", &m_nCountCurveRecord},
598 323 : {"NOXN", &m_nCountCompositeCurveRecord},
599 323 : {"NOSN", &m_nCountSurfaceRecord},
600 323 : {"NOFR", &m_nCountFeatureTypeRecord},
601 323 : };
602 :
603 3525 : for (const auto &field : intFields)
604 : {
605 6418 : *(field.pnVal) =
606 3209 : poRecord->GetIntSubfield(poField, field.pszKey, 0, &bSuccess);
607 3209 : if (!bSuccess && !EMIT_ERROR_OR_WARNING(CPLSPrintf(
608 : "no %s subfield of DSSI", field.pszKey)))
609 : {
610 0 : return false;
611 : }
612 3223 : if (*(field.pnVal) < 0 &&
613 14 : !EMIT_ERROR_OR_WARNING(CPLSPrintf(
614 : "Invalid value for %s subfield of DSSI.", field.pszKey)))
615 : {
616 7 : return false;
617 : }
618 : }
619 :
620 316 : if (m_nXScale <= 0 || m_nYScale <= 0 || m_nZScale <= 0)
621 : {
622 6 : return EMIT_ERROR(
623 : "Invalid CMFX/CMFY/CMFZ scale factor in DSSI (must be > 0).");
624 : }
625 :
626 312 : if (m_nXScale != S101_XSCALE &&
627 2 : !EMIT_ERROR_OR_WARNING(
628 : "Value of CMFX subfield of DSSI is not at official value."))
629 : {
630 1 : return false;
631 : }
632 :
633 311 : if (m_nYScale != S101_YSCALE &&
634 2 : !EMIT_ERROR_OR_WARNING(
635 : "Value of CMFY subfield of DSSI is not at official value."))
636 : {
637 1 : return false;
638 : }
639 :
640 310 : if (m_nZScale != S101_ZSCALE &&
641 2 : !EMIT_ERROR_OR_WARNING(
642 : CPLSPrintf("Value of CMFZ subfield of DSSI is not at official "
643 : "value. Got %d, expected %d.",
644 : m_nZScale, S101_ZSCALE)))
645 : {
646 1 : return false;
647 : }
648 :
649 307 : return true;
650 : }
651 :
652 : /************************************************************************/
653 : /* ReadGenericCodeAssociation() */
654 : /************************************************************************/
655 :
656 : /** Read fields like ATCS, ITCS, etc. that associate a numeric code to a
657 : * string
658 : */
659 : template <class CodeType>
660 1842 : bool OGRS101Reader::ReadGenericCodeAssociation(
661 : const DDFRecord *poRecord, const char *pszFieldName,
662 : const char *pszSubField0Name, const char *pszSubField1Name,
663 : std::map<CodeType, std::string> &map) const
664 : {
665 1842 : const auto poField = poRecord->FindField(pszFieldName);
666 1842 : if (!poField)
667 : {
668 1090 : CPLDebugOnly("S101", "No %s field found", pszFieldName);
669 1090 : return true;
670 : }
671 :
672 752 : const int nRepeatCount = poField->GetRepeatCount();
673 3900 : for (int i = 0; i < nRepeatCount; ++i)
674 : {
675 3148 : int bSuccess = false;
676 3148 : const char *pszVal = poRecord->GetStringSubfield(
677 : poField, pszSubField0Name, i, &bSuccess);
678 3148 : if (!bSuccess)
679 : {
680 0 : if (!m_bStrict)
681 0 : continue;
682 0 : return false;
683 : }
684 : const int nCode =
685 3148 : poRecord->GetIntSubfield(poField, pszSubField1Name, i, &bSuccess);
686 3148 : if (!bSuccess)
687 : {
688 0 : if (!m_bStrict)
689 0 : continue;
690 0 : return false;
691 : }
692 3148 : if (!pszVal)
693 0 : pszVal = "(invalid)";
694 3148 : if (!map.insert({CodeType(nCode), pszVal}).second &&
695 0 : !EMIT_ERROR_OR_WARNING(
696 : CPLSPrintf("%s: several definitions for %s %d.", pszFieldName,
697 : pszSubField1Name, nCode)))
698 : {
699 0 : return false;
700 : }
701 : }
702 :
703 752 : return true;
704 : }
705 :
706 : /************************************************************************/
707 : /* ReadATCS() */
708 : /************************************************************************/
709 :
710 : /** Read optional Attribute Codes field
711 : */
712 307 : bool OGRS101Reader::ReadATCS(const DDFRecord *poRecord)
713 : {
714 614 : return ReadGenericCodeAssociation<AttrCode>(poRecord, ATCS_FIELD, "ATCD",
715 307 : "ANCD", m_attributeCodes);
716 : }
717 :
718 : /************************************************************************/
719 : /* ReadITCS() */
720 : /************************************************************************/
721 :
722 : /** Read optional Feature Type Codes field
723 : */
724 307 : bool OGRS101Reader::ReadITCS(const DDFRecord *poRecord)
725 : {
726 614 : return ReadGenericCodeAssociation<InfoTypeCode>(
727 307 : poRecord, ITCS_FIELD, "ITCD", "ITNC", m_informationTypeCodes);
728 : }
729 :
730 : /************************************************************************/
731 : /* ReadFTCS() */
732 : /************************************************************************/
733 :
734 : /** Read optional Feature Type Codes field
735 : */
736 307 : bool OGRS101Reader::ReadFTCS(const DDFRecord *poRecord)
737 : {
738 614 : return ReadGenericCodeAssociation<FeatureTypeCode>(
739 307 : poRecord, FTCS_FIELD, "FTCD", "FTNC", m_featureTypeCodes);
740 : }
741 :
742 : /************************************************************************/
743 : /* ReadIACS() */
744 : /************************************************************************/
745 :
746 : /** Read optional Information Association Codes field
747 : */
748 307 : bool OGRS101Reader::ReadIACS(const DDFRecord *poRecord)
749 : {
750 614 : return ReadGenericCodeAssociation<InfoAssocCode>(
751 307 : poRecord, IACS_FIELD, "IACD", "IANC", m_informationAssociationCodes);
752 : }
753 :
754 : /************************************************************************/
755 : /* ReadFACS() */
756 : /************************************************************************/
757 :
758 : /** Read optional Feature Association Codes field
759 : */
760 307 : bool OGRS101Reader::ReadFACS(const DDFRecord *poRecord)
761 : {
762 614 : return ReadGenericCodeAssociation<FeatureAssocCode>(
763 307 : poRecord, FACS_FIELD, "FACD", "FANC", m_featureAssociationCodes);
764 : }
765 :
766 : /************************************************************************/
767 : /* ReadARCS() */
768 : /************************************************************************/
769 :
770 : /** Read optional Association Role Codes field
771 : */
772 307 : bool OGRS101Reader::ReadARCS(const DDFRecord *poRecord)
773 : {
774 614 : return ReadGenericCodeAssociation<AssocRoleCode>(
775 307 : poRecord, ARCS_FIELD, "ARCD", "ARNC", m_associationRoleCodes);
776 : }
|