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 626 : bool OGRS101Reader::ReadDatasetGeneralInformationRecord(
30 : const DDFRecord *poRecord)
31 : {
32 626 : if (!poRecord)
33 0 : return EMIT_ERROR("no Dataset General Information record.");
34 :
35 1226 : return ReadDSID(poRecord) && ReadDSSI(poRecord) && ReadATCS(poRecord) &&
36 573 : ReadITCS(poRecord) && ReadFTCS(poRecord) && ReadIACS(poRecord) &&
37 1226 : 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 626 : bool OGRS101Reader::ReadDSID(const DDFRecord *poRecord)
48 : {
49 626 : const auto poField = poRecord->FindField(DSID_FIELD);
50 626 : if (!poField)
51 2 : return EMIT_ERROR("DSID field not found.");
52 :
53 : // Record name
54 : const RecordName nRCNM =
55 624 : poRecord->GetIntSubfield(poField, RCNM_SUBFIELD, 0);
56 628 : if (nRCNM != RECORD_NAME_DATASET_IDENTIFICATION &&
57 4 : !EMIT_ERROR_OR_WARNING("Invalid value for RCNM subfield of DSID."))
58 : {
59 2 : return false;
60 : }
61 :
62 : // Record identifier
63 622 : const int nRCID = poRecord->GetIntSubfield(poField, RCID_SUBFIELD, 0);
64 : // Only one record expected
65 624 : 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 7446 : for (const auto &item : mapMetadataKeys)
90 : {
91 : const char *pszValue =
92 6827 : poRecord->GetStringSubfield(poField, item.pszS101Name, 0);
93 6827 : 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 : else
104 : {
105 6827 : if (m_bInUpdate && EQUAL(item.pszS101Name, "DSED"))
106 : {
107 : const char *pszOldVal =
108 123 : m_aosMetadata.FetchNameValueDef(item.pszGDALName, "");
109 125 : if (EQUAL(pszValue, pszOldVal) &&
110 2 : !EMIT_ERROR_OR_WARNING(
111 : CPLSPrintf("%s has the same DATASET_EDITION value '%s' "
112 : "than the previous update/initial file.",
113 : m_osFilename.c_str(), pszOldVal)))
114 : {
115 1 : return false;
116 122 : }
117 : }
118 6704 : else if (m_bInUpdate && !EQUAL(item.pszS101Name, "PROF") &&
119 1113 : !EQUAL(item.pszS101Name, "DSNM") &&
120 989 : !EQUAL(item.pszS101Name, "DSRD"))
121 : {
122 : const char *pszOldVal =
123 866 : m_aosMetadata.FetchNameValueDef(item.pszGDALName, "");
124 868 : if (!EQUAL(pszValue, pszOldVal) &&
125 2 : !EMIT_ERROR_OR_WARNING(
126 : CPLSPrintf("%s has a different value %s='%s' than the "
127 : "previous update/initial file ('%s').",
128 : m_osFilename.c_str(), item.pszS101Name,
129 : pszValue, pszOldVal)))
130 : {
131 1 : return false;
132 : }
133 : }
134 7445 : if (pszValue[0] ||
135 620 : (m_bInUpdate &&
136 123 : m_aosMetadata.FetchNameValueDef(item.pszGDALName, "")[0] != 0))
137 6205 : m_aosMetadata.SetNameValue(item.pszGDALName, pszValue);
138 : }
139 : }
140 :
141 : const char *pszPRSP =
142 619 : m_aosMetadata.FetchNameValueDef("PRODUCT_IDENTIFIER", "");
143 619 : if (!strstr(pszPRSP, "S-101") &&
144 0 : !EMIT_ERROR_OR_WARNING(
145 : CPLSPrintf("%s is an ISO8211 file, but not a S-101 product. "
146 : "Product identifier is '%s'.",
147 : m_osFilename.c_str(), pszPRSP)))
148 : {
149 0 : return false;
150 : }
151 :
152 : // Accept 1.x, but only >= 2.0 is operational
153 1857 : if (!STARTS_WITH(pszPRSP, "INT.IHO.S-101.1.") &&
154 621 : !STARTS_WITH(pszPRSP, "INT.IHO.S-101.2.") &&
155 2 : !EMIT_ERROR_OR_WARNING(CPLSPrintf(
156 : "Product identifier is '%s', but only 'INT.IHO.S-101.2.0' is "
157 : "nominally handled. Going on but the dataset might not be "
158 : "correctly read.",
159 : pszPRSP)))
160 : {
161 1 : return false;
162 : }
163 :
164 : const char *pszPROF =
165 618 : m_aosMetadata.FetchNameValueDef("APPLICATION_PROFILE", "");
166 618 : if (EQUAL(pszPROF, "1"))
167 : {
168 500 : if (m_bInUpdate &&
169 2 : !EMIT_ERROR_OR_WARNING(
170 : CPLSPrintf("Update file %s has APPLICATION_PROFILE=1 (Initial)",
171 : m_osFilename.c_str())))
172 : {
173 1 : return false;
174 : }
175 : }
176 120 : else if (EQUAL(pszPROF, "2"))
177 : {
178 118 : if (!m_bInUpdate &&
179 0 : !EMIT_ERROR_OR_WARNING(
180 : "Direct opening of files with APPLICATION_PROFILE=2 (Update) "
181 : "is not supported. Open the main .000 file"))
182 : {
183 0 : return false;
184 : }
185 : }
186 : else
187 : {
188 2 : if (!EMIT_ERROR_OR_WARNING(
189 : CPLSPrintf("%s: APPLICATION_PROFILE='%s' is invalid",
190 : m_osFilename.c_str(), pszPROF)))
191 1 : return false;
192 : }
193 :
194 : const char *pszDSED =
195 616 : m_aosMetadata.FetchNameValueDef("DATASET_EDITION", "");
196 616 : if (EQUAL(pszDSED, "0"))
197 : {
198 5 : m_aosMetadata.SetNameValue("STATUS", "CANCELLED");
199 5 : m_bCancelled = true;
200 : }
201 :
202 616 : if (!CheckFieldDefinitions(poRecord->GetModule()))
203 16 : return false;
204 :
205 600 : return true;
206 : }
207 :
208 : /************************************************************************/
209 : /* CheckFieldDefinitions() */
210 : /************************************************************************/
211 :
212 : /** Check that field and subfield definitions conforms to the specification.
213 : */
214 616 : bool OGRS101Reader::CheckFieldDefinitions(const DDFModule *poCurModule) const
215 : {
216 616 : bool bRet = CheckField0000Definition(poCurModule);
217 :
218 : // Note the '\\\\' has 4 bytes instead of the '\\' that appears in the
219 : // spec, because of the escaping of backslash in C++
220 : const std::map<std::string_view, std::pair<const char *, const char *>>
221 : fieldArrayDescrAndFieldControls = {
222 : {DSID_FIELD,
223 0 : {"RCNM!RCID!ENSP!ENED!PRSP!PRED!PROF!DSNM!DSTL!DSRD!DSLG!DSAB!"
224 : "DSED\\\\*DSTC",
225 0 : "(b11,b14,7A,A(8),3A,(b11))"}},
226 : {DSSI_FIELD,
227 0 : {"DCOX!DCOY!DCOZ!CMFX!CMFY!CMFZ!NOIR!NOPN!NOMN!NOCN!NOXN!NOSN!"
228 : "NOFR",
229 0 : "(3b48,10b14)"}},
230 0 : {ATCS_FIELD, {"*ATCD!ANCD", "(A,b12)"}},
231 0 : {ITCS_FIELD, {"*ITCD!ITNC", "(A,b12)"}},
232 0 : {FTCS_FIELD, {"*FTCD!FTNC", "(A,b12)"}},
233 0 : {IACS_FIELD, {"*IACD!IANC", "(A,b12)"}},
234 0 : {FACS_FIELD, {"*FACD!FANC", "(A,b12)"}},
235 0 : {ARCS_FIELD, {"*ARCD!ARNC", "(A,b12)"}},
236 0 : {CSID_FIELD, {"RCNM!RCID!NCRC", "(b11,b14,b11)"}},
237 : {CRSH_FIELD,
238 0 : {"CRIX!CRST!CSTY!CRNM!CRSI!CRSS!SCRI", "(3b11,2A,b11,A)"}},
239 0 : {CSAX_FIELD, {"*AXTY!AXUM", "(2b11)"}},
240 0 : {VDAT_FIELD, {"DTNM!DTID!DTSR!SCRI", "(2A,b11,A)"}},
241 0 : {IRID_FIELD, {"RCNM!RCID!NITC!RVER!RUIN", "(b11,b14,2b12,b11)"}},
242 : {INAS_FIELD,
243 0 : {"RRNM!RRID!NIAC!NARC!IUIN\\\\*NATC!ATIX!PAIX!ATIN!ATVL",
244 0 : "(b11,b14,2b12,b11,(3b12,b11,A))"}},
245 0 : {ATTR_FIELD, {"*NATC!ATIX!PAIX!ATIN!ATVL", "(3b12,b11,A)"}},
246 0 : {C2IT_FIELD, {"YCOO!XCOO", "(2b24)"}},
247 0 : {C3IT_FIELD, {"VCID!YCOO!XCOO!ZCOO", "(b11,3b24)"}},
248 0 : {C2IL_FIELD, {"*YCOO!XCOO", "(2b24)"}},
249 0 : {C3IL_FIELD, {"VCID\\\\*YCOO!XCOO!ZCOO", "(b11,(3b24))"}},
250 0 : {PRID_FIELD, {"RCNM!RCID!RVER!RUIN", "(b11,b14,b12,b11)"}},
251 0 : {COCC_FIELD, {"COUI!COIX!NCOR", "(b11,2b12)"}},
252 0 : {MRID_FIELD, {"RCNM!RCID!RVER!RUIN", "(b11,b14,b12,b11)"}},
253 0 : {CRID_FIELD, {"RCNM!RCID!RVER!RUIN", "(b11,b14,b12,b11)"}},
254 0 : {PTAS_FIELD, {"*RRNM!RRID!TOPI", "(b11,b14,b11)"}},
255 0 : {SECC_FIELD, {"SEUI!SEIX!NSEG", "(b11,2b12)"}},
256 0 : {SEGH_FIELD, {"INTP", "(b11)"}},
257 0 : {CCID_FIELD, {"RCNM!RCID!RVER!RUIN", "(b11,b14,b12,b11)"}},
258 0 : {CCOC_FIELD, {"CCUI!CCIX!NCCO", "(b11,2b12)"}},
259 0 : {CUCO_FIELD, {"*RRNM!RRID!ORNT", "(b11,b14,b11)"}},
260 0 : {SRID_FIELD, {"RCNM!RCID!RVER!RUIN", "(b11,b14,b12,b11)"}},
261 0 : {RIAS_FIELD, {"*RRNM!RRID!ORNT!USAG!RAUI", "(b11,b14,3b11)"}},
262 0 : {FRID_FIELD, {"RCNM!RCID!NFTC!RVER!RUIN", "(b11,b14,2b12,b11)"}},
263 0 : {FOID_FIELD, {"AGEN!FIDN!FIDS", "(b12,b14,b12)"}},
264 : {SPAS_FIELD,
265 0 : {"*RRNM!RRID!ORNT!SMIN!SMAX!SAUI", "(b11,b14,b11,2b14,b11)"}},
266 : {FASC_FIELD,
267 0 : {"RRNM!RRID!NFAC!NARC!FAUI\\\\*NATC!ATIX!PAIX!ATIN!ATVL",
268 0 : "(b11,b14,2b12,b11,(3b12,b11,A))"}},
269 0 : {MASK_FIELD, {"*RRNM!RRID!MIND!MUIN", "(b11,b14,2b11)"}},
270 616 : };
271 :
272 9422 : for (const auto &poFieldDefn : poCurModule->GetFieldDefns())
273 : {
274 8806 : const char *pszFieldName = poFieldDefn->GetName();
275 8806 : if (strcmp(pszFieldName, _0000_FIELD) != 0)
276 : {
277 : const auto oIter =
278 8192 : fieldArrayDescrAndFieldControls.find(pszFieldName);
279 8192 : if (oIter == fieldArrayDescrAndFieldControls.end())
280 : {
281 2 : if (!EMIT_ERROR_OR_WARNING(CPLSPrintf(
282 : "Unknown field definition '%s'.", pszFieldName)))
283 : {
284 1 : bRet = false;
285 : }
286 : }
287 : else
288 : {
289 8190 : const char *pszExpectedArrayDescr = oIter->second.first;
290 8190 : if (strcmp(poFieldDefn->GetArrayDescr(),
291 8192 : pszExpectedArrayDescr) != 0 &&
292 2 : !EMIT_ERROR_OR_WARNING(
293 : CPLSPrintf("For array description of field definition "
294 : "'%s', got '%s' whereas '%s' is expected.",
295 : pszFieldName, poFieldDefn->GetArrayDescr(),
296 : pszExpectedArrayDescr)))
297 : {
298 1 : bRet = false;
299 : }
300 :
301 8190 : const char *pszExpectedFormatControls = oIter->second.second;
302 8190 : if (strcmp(poFieldDefn->GetFormatControls(),
303 8192 : pszExpectedFormatControls) != 0 &&
304 2 : !EMIT_ERROR_OR_WARNING(CPLSPrintf(
305 : "For format controls of field definition '%s', got "
306 : "'%s' whereas '%s' is expected.",
307 : pszFieldName, poFieldDefn->GetFormatControls(),
308 : pszExpectedFormatControls)))
309 : {
310 1 : bRet = false;
311 : }
312 :
313 8190 : const DDF_data_struct_code eExpectedDataStruct =
314 8190 : strstr(pszExpectedArrayDescr, "\\\\") != nullptr
315 15435 : ? dsc_concatenated
316 7245 : : pszExpectedArrayDescr[0] == '*' ? dsc_array
317 : : dsc_vector;
318 8190 : if (poFieldDefn->GetDataStructCode() != eExpectedDataStruct &&
319 0 : !EMIT_ERROR_OR_WARNING(CPLSPrintf(
320 : "Data struct code of field definition '%s', got "
321 : "'%d' whereas '%d' is expected.",
322 : pszFieldName, poFieldDefn->GetDataStructCode(),
323 : eExpectedDataStruct)))
324 : {
325 0 : bRet = false;
326 : }
327 :
328 8190 : const bool bHasIntegerSubfields =
329 8893 : strstr(pszExpectedFormatControls, "b1") != nullptr ||
330 703 : strstr(pszExpectedFormatControls, "b2") != nullptr;
331 8190 : const bool bHasFloatSubfields =
332 8190 : strstr(pszExpectedFormatControls, "b4") != nullptr;
333 8190 : const bool bHasStringSubfields =
334 8190 : strchr(pszExpectedFormatControls, 'A') != nullptr;
335 8190 : const DDF_data_type_code eExpectedDataTypeCode =
336 8190 : (bHasIntegerSubfields && !bHasFloatSubfields &&
337 : !bHasStringSubfields)
338 16380 : ? dtc_implicit_point
339 : :
340 : // Commenting below code, since it doesn't actually
341 : // occur with S-101 fields
342 : // (!bHasIntegerSubfields && bHasFloatSubfields && !bHasStringSubfields) ?
343 : // dtc_explicit_point :
344 : // cppcheck-suppress knownConditionTrueFalse
345 0 : (!bHasIntegerSubfields && !bHasFloatSubfields &&
346 : bHasStringSubfields)
347 3926 : ? dtc_char_string
348 : : dtc_mixed_data_type;
349 8192 : if (poFieldDefn->GetDataTypeCode() != eExpectedDataTypeCode &&
350 2 : !EMIT_ERROR_OR_WARNING(CPLSPrintf(
351 : "Data type code of field definition '%s', got "
352 : "'%d' whereas '%d' is expected.",
353 : pszFieldName, poFieldDefn->GetDataTypeCode(),
354 : eExpectedDataTypeCode)))
355 : {
356 1 : bRet = false;
357 : }
358 : }
359 : }
360 : }
361 :
362 1232 : return bRet;
363 : }
364 :
365 : /************************************************************************/
366 : /* CheckField0000Definition() */
367 : /************************************************************************/
368 :
369 : /** Check that the special "0000" field conforms to the specification.
370 : */
371 616 : bool OGRS101Reader::CheckField0000Definition(const DDFModule *poCurModule) const
372 : {
373 616 : bool bRet = true;
374 :
375 616 : const auto po0000FieldDefn = poCurModule->FindFieldDefn(_0000_FIELD);
376 616 : if (!po0000FieldDefn)
377 : {
378 2 : return EMIT_ERROR_OR_WARNING(
379 : "Field definition of 0000 control field not found.");
380 : }
381 :
382 616 : if (po0000FieldDefn->GetDataStructCode() != dsc_elementary &&
383 2 : !EMIT_ERROR_OR_WARNING("Data struct code of field definition of 0000 "
384 : "control field must be elementary."))
385 : {
386 1 : bRet = false;
387 : }
388 :
389 616 : if (po0000FieldDefn->GetDataTypeCode() != dtc_char_string &&
390 2 : !EMIT_ERROR_OR_WARNING("Data type code of field definition of 0000 "
391 : "control field must be char_string."))
392 : {
393 1 : bRet = false;
394 : }
395 :
396 618 : if (po0000FieldDefn->GetFormatControls()[0] != 0 &&
397 4 : !EMIT_ERROR_OR_WARNING("Format controls of field definition of 0000 "
398 : "control field must be empty."))
399 : {
400 2 : bRet = false;
401 : }
402 :
403 : // Should contain concatenated pairs of parent_field_name,child_field_name
404 : // without any separator: e.g. DSIDDSSICSIDCRSHCRSHCSAXCRSHVDAT
405 614 : const char *psz0000Descr = po0000FieldDefn->GetArrayDescr();
406 614 : const size_t n0000DescrLen = strlen(psz0000Descr);
407 614 : constexpr int FIELD_NAME_SIZE = 4;
408 614 : constexpr int PARENT_CHILD_PAIR_SIZE = 2 * FIELD_NAME_SIZE;
409 616 : if ((n0000DescrLen % PARENT_CHILD_PAIR_SIZE) != 0 &&
410 2 : !EMIT_ERROR_OR_WARNING(
411 : "Length of field tag pairs of field definition of 0000 "
412 : "control field must be a multiple of 8."))
413 : {
414 1 : bRet = false;
415 : }
416 :
417 614 : constexpr int MAX_CHILDREN_COUNT = 8;
418 : using ArrayOfChildren = std::array<const char *, MAX_CHILDREN_COUNT>;
419 :
420 : static const struct
421 : {
422 : const std::string_view svParent;
423 : ArrayOfChildren apszChildren;
424 : } knownPairs[] = {
425 : {DSID_FIELD,
426 : {DSSI_FIELD, ATCS_FIELD, ITCS_FIELD, FTCS_FIELD, IACS_FIELD,
427 : FACS_FIELD, ARCS_FIELD}},
428 : {CSID_FIELD, {CRSH_FIELD}},
429 : {CRSH_FIELD, {CSAX_FIELD, VDAT_FIELD}},
430 : {IRID_FIELD, {ATTR_FIELD, INAS_FIELD}},
431 : {PRID_FIELD, {INAS_FIELD, C2IT_FIELD, C3IT_FIELD}},
432 : {MRID_FIELD, {INAS_FIELD, COCC_FIELD, C2IL_FIELD, C3IL_FIELD}},
433 : {CRID_FIELD, {INAS_FIELD, PTAS_FIELD, SECC_FIELD, SEGH_FIELD}},
434 : {SEGH_FIELD, {COCC_FIELD, C2IL_FIELD}},
435 : {CCID_FIELD, {INAS_FIELD, CCOC_FIELD, CUCO_FIELD}},
436 : {SRID_FIELD, {INAS_FIELD, RIAS_FIELD}},
437 : {FRID_FIELD,
438 : {FOID_FIELD, ATTR_FIELD, INAS_FIELD, SPAS_FIELD, FASC_FIELD,
439 : MASK_FIELD}},
440 614 : };
441 :
442 1228 : const std::set<std::string> oSetUsedFieldNames = [poCurModule]
443 : {
444 614 : std::set<std::string> s;
445 9408 : for (const auto &poFieldDefn : poCurModule->GetFieldDefns())
446 8794 : s.insert(poFieldDefn->GetName());
447 614 : return s;
448 1228 : }();
449 :
450 : // Return an iterator of knownPairs that points to the entry where
451 : // iter->svParent == svParent, but only if one of its allowed children
452 : // is in the set of fields actually found in the file.
453 : const auto GetParentIter =
454 24155 : [&oSetUsedFieldNames](const std::string_view &svParent)
455 : {
456 : const auto iter =
457 14788 : std::find_if(std::begin(knownPairs), std::end(knownPairs),
458 101415 : [&svParent](const auto &item)
459 101415 : { return svParent == item.svParent; });
460 14788 : if (iter != std::end(knownPairs))
461 : {
462 9367 : const auto allowedChildren = iter->apszChildren;
463 9367 : if (std::find_if(allowedChildren.begin(), allowedChildren.end(),
464 22326 : [&oSetUsedFieldNames](const char *pszStr)
465 : {
466 11382 : return pszStr &&
467 10944 : cpl::contains(oSetUsedFieldNames,
468 22764 : pszStr);
469 9367 : }) != allowedChildren.end())
470 : {
471 9294 : return iter;
472 : }
473 : }
474 5494 : return std::end(knownPairs);
475 614 : };
476 :
477 : // Returns an iterator of allowedChildren only is svChild is one of the
478 : // knownPairs::apszChildren.
479 62521 : const auto IsKnownChild = [](const ArrayOfChildren &allowedChildren,
480 : const std::string_view &svChild)
481 : {
482 62521 : return std::find_if(allowedChildren.begin(), allowedChildren.end(),
483 629905 : [&svChild](const char *pszStr)
484 434934 : { return pszStr && svChild == pszStr; }) !=
485 62521 : allowedChildren.end();
486 : };
487 :
488 614 : const size_t nPairs = n0000DescrLen / PARENT_CHILD_PAIR_SIZE;
489 1228 : std::set<std::string> oSetReferencedParent;
490 1228 : std::set<std::string> oSetReferencedChildren;
491 614 : std::set<std::pair<std::string, std::string>> oSetFoundPairs;
492 6715 : for (size_t i = 0; i < nPairs; ++i)
493 : {
494 6101 : bool bUnknownParentOrChild = false;
495 6101 : const std::string osParent(psz0000Descr + i * PARENT_CHILD_PAIR_SIZE,
496 12202 : FIELD_NAME_SIZE);
497 6101 : oSetReferencedParent.insert(osParent);
498 :
499 6101 : const std::string osChild(psz0000Descr + i * PARENT_CHILD_PAIR_SIZE +
500 : FIELD_NAME_SIZE,
501 12202 : FIELD_NAME_SIZE);
502 6101 : oSetReferencedChildren.insert(osChild);
503 :
504 6103 : if (!cpl::contains(oSetUsedFieldNames, osParent) &&
505 2 : !EMIT_ERROR_OR_WARNING(
506 : CPLSPrintf("Field '%s' referenced in field definition of 0000 "
507 : "control field does not exist.",
508 : osParent.c_str())))
509 : {
510 1 : bUnknownParentOrChild = true;
511 1 : bRet = false;
512 : }
513 :
514 6105 : if (!cpl::contains(oSetUsedFieldNames, osChild) &&
515 4 : !EMIT_ERROR_OR_WARNING(
516 : CPLSPrintf("Field '%s' referenced in field definition of 0000 "
517 : "control field does not exist.",
518 : osChild.c_str())))
519 : {
520 2 : bUnknownParentOrChild = true;
521 2 : bRet = false;
522 : }
523 :
524 6103 : if (!oSetFoundPairs.insert({osParent, osChild}).second &&
525 2 : !EMIT_ERROR_OR_WARNING(
526 : CPLSPrintf("Pair ('%s','%s') referenced multiple time in field "
527 : "definition of 0000 control field.",
528 : osParent.c_str(), osChild.c_str())))
529 : {
530 1 : bRet = false;
531 : }
532 :
533 6101 : if (!bUnknownParentOrChild)
534 : {
535 6098 : const auto oIter = GetParentIter(osParent);
536 6098 : if (oIter == std::end(knownPairs))
537 : {
538 3 : if (!EMIT_ERROR_OR_WARNING(CPLSPrintf(
539 : "Field '%s' referenced in field definition of 0000 "
540 : "control field is not a parent of a registered "
541 : "(parent,child) pair.",
542 : osParent.c_str())))
543 : {
544 1 : bRet = false;
545 : }
546 : }
547 : else
548 : {
549 6098 : if (!IsKnownChild(oIter->apszChildren, osChild) &&
550 3 : !EMIT_ERROR_OR_WARNING(CPLSPrintf(
551 : "Field '%s' referenced in field definition of 0000 "
552 : "control field is not an allowed child of a registered "
553 : "('%s',child) pair.",
554 : osChild.c_str(), osParent.c_str())))
555 : {
556 1 : bRet = false;
557 : }
558 : }
559 : }
560 : }
561 :
562 614 : if (nPairs > 0)
563 : {
564 9302 : for (const std::string &osFieldName : oSetUsedFieldNames)
565 : {
566 8690 : if (GetParentIter(osFieldName) != std::end(knownPairs) &&
567 8694 : !cpl::contains(oSetReferencedParent, osFieldName) &&
568 4 : !EMIT_ERROR_OR_WARNING(CPLSPrintf(
569 : "Field '%s' is not referenced as a parent in field "
570 : "definition of 0000 control field.",
571 : osFieldName.c_str())))
572 : {
573 2 : bRet = false;
574 : }
575 :
576 8690 : if (std::find_if(std::begin(knownPairs), std::end(knownPairs),
577 56426 : [&IsKnownChild, &osFieldName](const auto &item)
578 : {
579 112852 : return IsKnownChild(item.apszChildren,
580 112852 : osFieldName);
581 8690 : }) != std::end(knownPairs) &&
582 8698 : !cpl::contains(oSetReferencedChildren, osFieldName) &&
583 8 : !EMIT_ERROR_OR_WARNING(CPLSPrintf(
584 : "Field '%s' is not referenced as a child in field "
585 : "definition of 0000 control field.",
586 : osFieldName.c_str())))
587 : {
588 4 : bRet = false;
589 : }
590 : }
591 : }
592 :
593 614 : return bRet;
594 : }
595 :
596 : /************************************************************************/
597 : /* ReadDSSI() */
598 : /************************************************************************/
599 :
600 : /** Read the Dataset Structure Information (DSSI) field.
601 : */
602 600 : bool OGRS101Reader::ReadDSSI(const DDFRecord *poRecord)
603 : {
604 600 : const auto poField = poRecord->FindField(DSSI_FIELD);
605 600 : if (!poField)
606 2 : return EMIT_ERROR("DSSI field not found");
607 :
608 598 : int bSuccess = false;
609 :
610 : // must NOT be set as static, as it depens on "this" !
611 : const struct
612 : {
613 : const char *pszKey;
614 : double *pdfVal;
615 598 : } doubleFields[] = {
616 598 : {"DCOX", &m_dfXShift},
617 598 : {"DCOY", &m_dfYShift},
618 598 : {"DCOZ", &m_dfZShift},
619 598 : };
620 :
621 2380 : for (const auto &field : doubleFields)
622 : {
623 3576 : *(field.pdfVal) = poRecord->GetFloatSubfield(
624 1788 : DSSI_FIELD, 0, field.pszKey, 0, &bSuccess);
625 1788 : if (!bSuccess && !EMIT_ERROR_OR_WARNING(CPLSPrintf(
626 : "no %s subfield of DSSI.", field.pszKey)))
627 : {
628 0 : return false;
629 : }
630 1788 : if (std::isnan(*(field.pdfVal)))
631 : {
632 6 : return EMIT_ERROR(
633 : CPLSPrintf("NaN value in %s subfield of DSSI.", field.pszKey));
634 : }
635 : }
636 :
637 594 : if (m_dfXShift != S101_SHIFT &&
638 2 : !EMIT_ERROR_OR_WARNING(
639 : "Value of DCOX subfield of DSSI is not at official value."))
640 : {
641 1 : return false;
642 : }
643 :
644 593 : if (m_dfYShift != S101_SHIFT &&
645 2 : !EMIT_ERROR_OR_WARNING(
646 : "Value of DCOY subfield of DSSI is not at official value."))
647 : {
648 1 : return false;
649 : }
650 :
651 592 : if (m_dfZShift != S101_SHIFT &&
652 2 : !EMIT_ERROR_OR_WARNING(
653 : "Value of DCOZ subfield of DSSI is not at official value."))
654 : {
655 1 : return false;
656 : }
657 :
658 : // must NOT be set as static, as it depens on "this" !
659 : struct KeyIntVal
660 : {
661 : const char *pszKey;
662 : int *pnVal;
663 : };
664 :
665 589 : const KeyIntVal intFieldsInitial[] = {
666 589 : {"CMFX", &m_nXScale},
667 589 : {"CMFY", &m_nYScale},
668 589 : {"CMFZ", &m_nZScale},
669 589 : {"NOIR", &m_nCountInformationRecord},
670 589 : {"NOPN", &m_nCountPointRecord},
671 589 : {"NOMN", &m_nCountMultiPointRecord},
672 589 : {"NOCN", &m_nCountCurveRecord},
673 589 : {"NOXN", &m_nCountCompositeCurveRecord},
674 589 : {"NOSN", &m_nCountSurfaceRecord},
675 589 : {"NOFR", &m_nCountFeatureTypeRecord},
676 589 : };
677 589 : const KeyIntVal intFieldsUpdate[] = {
678 589 : {"CMFX", &m_nXScale},
679 589 : {"CMFY", &m_nYScale},
680 589 : {"CMFZ", &m_nZScale},
681 589 : };
682 589 : const auto &begin = m_bInUpdate ? std::begin(intFieldsUpdate)
683 469 : : std::begin(intFieldsInitial);
684 : const auto &end =
685 589 : m_bInUpdate ? std::end(intFieldsUpdate) : std::end(intFieldsInitial);
686 5611 : for (auto oIter = begin; oIter != end; ++oIter)
687 : {
688 5029 : const auto &field = *oIter;
689 10058 : *(field.pnVal) =
690 5029 : poRecord->GetIntSubfield(poField, field.pszKey, 0, &bSuccess);
691 5029 : if (!bSuccess && !EMIT_ERROR_OR_WARNING(CPLSPrintf(
692 : "no %s subfield of DSSI", field.pszKey)))
693 : {
694 0 : return false;
695 : }
696 5043 : if (*(field.pnVal) < 0 &&
697 14 : !EMIT_ERROR_OR_WARNING(CPLSPrintf(
698 : "Invalid value for %s subfield of DSSI.", field.pszKey)))
699 : {
700 7 : return false;
701 : }
702 : }
703 :
704 582 : if (m_nXScale <= 0 || m_nYScale <= 0 || m_nZScale <= 0)
705 : {
706 6 : return EMIT_ERROR(
707 : "Invalid CMFX/CMFY/CMFZ scale factor in DSSI (must be > 0).");
708 : }
709 :
710 578 : if (m_nXScale != S101_XSCALE &&
711 2 : !EMIT_ERROR_OR_WARNING(
712 : "Value of CMFX subfield of DSSI is not at official value."))
713 : {
714 1 : return false;
715 : }
716 :
717 577 : if (m_nYScale != S101_YSCALE &&
718 2 : !EMIT_ERROR_OR_WARNING(
719 : "Value of CMFY subfield of DSSI is not at official value."))
720 : {
721 1 : return false;
722 : }
723 :
724 576 : if (m_nZScale != S101_ZSCALE &&
725 2 : !EMIT_ERROR_OR_WARNING(
726 : CPLSPrintf("Value of CMFZ subfield of DSSI is not at official "
727 : "value. Got %d, expected %d.",
728 : m_nZScale, S101_ZSCALE)))
729 : {
730 1 : return false;
731 : }
732 :
733 573 : if (m_nXScale == m_nYScale)
734 571 : m_coordinatePrecision.dfXYResolution = 1.0 / m_nXScale;
735 573 : m_coordinatePrecision.dfZResolution = 1.0 / m_nZScale;
736 :
737 573 : return true;
738 : }
739 :
740 : /************************************************************************/
741 : /* ReadGenericCodeAssociation() */
742 : /************************************************************************/
743 :
744 : /** Read fields like ATCS, ITCS, etc. that associate a numeric code to a
745 : * string
746 : */
747 : template <class CodeType>
748 3438 : bool OGRS101Reader::ReadGenericCodeAssociation(
749 : const DDFRecord *poRecord, const char *pszFieldName,
750 : const char *pszSubField0Name, const char *pszSubField1Name,
751 : std::map<CodeType, std::string> &map,
752 : std::map<CodeType, CodeType> &mapRemapping) const
753 : {
754 3438 : const auto poField = poRecord->FindField(pszFieldName);
755 3438 : if (!poField)
756 : {
757 2251 : CPLDebugOnly("S101", "No %s field found", pszFieldName);
758 2251 : return true;
759 : }
760 :
761 : // The remapping is only valid when processing the current update file
762 1187 : mapRemapping.clear();
763 :
764 : // Map from string value to code (used when processing update files)
765 2374 : std::map<std::string, int> reversedMap;
766 1852 : for (const auto &[key, value] : map)
767 665 : reversedMap[value] = static_cast<int>(key);
768 :
769 2374 : std::set<CodeType> setCodes;
770 1187 : const int nRepeatCount = poField->GetRepeatCount();
771 6089 : for (int i = 0; i < nRepeatCount; ++i)
772 : {
773 4902 : int bSuccess = false;
774 4902 : const char *pszVal = poRecord->GetStringSubfield(
775 : poField, pszSubField0Name, i, &bSuccess);
776 4902 : if (!bSuccess)
777 : {
778 0 : if (!m_bStrict)
779 0 : continue;
780 0 : return false;
781 : }
782 4902 : const CodeType nCode =
783 : poRecord->GetIntSubfield(poField, pszSubField1Name, i, &bSuccess);
784 4902 : if (!bSuccess)
785 : {
786 0 : if (!m_bStrict)
787 0 : continue;
788 0 : return false;
789 : }
790 4902 : if (!pszVal)
791 0 : pszVal = "(invalid)";
792 4902 : if (!setCodes.insert(nCode).second)
793 : {
794 0 : if (!EMIT_ERROR_OR_WARNING(CPLSPrintf(
795 : "%s: several definitions for %s %d.", pszFieldName,
796 : pszSubField1Name, static_cast<int>(nCode))))
797 : {
798 0 : return false;
799 : }
800 : }
801 4902 : else if (m_bInUpdate)
802 : {
803 : // Update files may re-use codes that were used in the initial file
804 : // but for a different string value ! So we must handle potential
805 : // clashes.
806 587 : const auto oReversedIter = reversedMap.find(pszVal);
807 587 : if (oReversedIter != reversedMap.end())
808 : {
809 587 : const auto nOldCode = oReversedIter->second;
810 587 : mapRemapping.insert({nCode, nOldCode});
811 : }
812 0 : else if (!map.insert({nCode, pszVal}).second)
813 : {
814 0 : const CodeType largestCode = map.rbegin()->first;
815 0 : if (largestCode == INT_MAX)
816 : {
817 : // Very unlikely to happen
818 0 : return EMIT_ERROR("Lack of available codes");
819 : }
820 0 : const CodeType newCode = static_cast<CodeType>(largestCode + 1);
821 0 : map.insert({newCode, pszVal});
822 0 : mapRemapping.insert({nCode, newCode});
823 : }
824 : else
825 : {
826 0 : mapRemapping.insert({nCode, nCode});
827 : }
828 : }
829 : else
830 : {
831 4315 : const bool bInserted = map.insert({nCode, pszVal}).second;
832 4315 : CPL_IGNORE_RET_VAL(bInserted);
833 4315 : CPLAssert(bInserted);
834 : }
835 : }
836 :
837 1187 : return true;
838 : }
839 :
840 : /************************************************************************/
841 : /* ReadATCS() */
842 : /************************************************************************/
843 :
844 : /** Read optional Attribute Codes field
845 : */
846 573 : bool OGRS101Reader::ReadATCS(const DDFRecord *poRecord)
847 : {
848 1146 : return ReadGenericCodeAssociation<AttrCode>(poRecord, ATCS_FIELD, "ATCD",
849 573 : "ANCD", m_attributeCodes,
850 573 : m_attributeCodesRemapping);
851 : }
852 :
853 : /************************************************************************/
854 : /* ReadITCS() */
855 : /************************************************************************/
856 :
857 : /** Read optional Feature Type Codes field
858 : */
859 573 : bool OGRS101Reader::ReadITCS(const DDFRecord *poRecord)
860 : {
861 1146 : return ReadGenericCodeAssociation<InfoTypeCode>(
862 573 : poRecord, ITCS_FIELD, "ITCD", "ITNC", m_informationTypeCodes,
863 573 : m_informationTypeCodesRemapping);
864 : }
865 :
866 : /************************************************************************/
867 : /* ReadFTCS() */
868 : /************************************************************************/
869 :
870 : /** Read optional Feature Type Codes field
871 : */
872 573 : bool OGRS101Reader::ReadFTCS(const DDFRecord *poRecord)
873 : {
874 1146 : return ReadGenericCodeAssociation<FeatureTypeCode>(
875 573 : poRecord, FTCS_FIELD, "FTCD", "FTNC", m_featureTypeCodes,
876 573 : m_featureTypeCodesRemapping);
877 : }
878 :
879 : /************************************************************************/
880 : /* ReadIACS() */
881 : /************************************************************************/
882 :
883 : /** Read optional Information Association Codes field
884 : */
885 573 : bool OGRS101Reader::ReadIACS(const DDFRecord *poRecord)
886 : {
887 1146 : return ReadGenericCodeAssociation<InfoAssocCode>(
888 573 : poRecord, IACS_FIELD, "IACD", "IANC", m_informationAssociationCodes,
889 573 : m_informationAssociationCodesRemapping);
890 : }
891 :
892 : /************************************************************************/
893 : /* ReadFACS() */
894 : /************************************************************************/
895 :
896 : /** Read optional Feature Association Codes field
897 : */
898 573 : bool OGRS101Reader::ReadFACS(const DDFRecord *poRecord)
899 : {
900 1146 : return ReadGenericCodeAssociation<FeatureAssocCode>(
901 573 : poRecord, FACS_FIELD, "FACD", "FANC", m_featureAssociationCodes,
902 573 : m_featureAssociationCodesRemapping);
903 : }
904 :
905 : /************************************************************************/
906 : /* ReadARCS() */
907 : /************************************************************************/
908 :
909 : /** Read optional Association Role Codes field
910 : */
911 573 : bool OGRS101Reader::ReadARCS(const DDFRecord *poRecord)
912 : {
913 1146 : return ReadGenericCodeAssociation<AssocRoleCode>(
914 573 : poRecord, ARCS_FIELD, "ARCD", "ARNC", m_associationRoleCodes,
915 573 : m_associationRoleCodesRemapping);
916 : }
|