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 <charconv>
19 : #include <cfloat>
20 : #include <limits>
21 : #include <memory>
22 : #include <optional>
23 : #include <set>
24 : #include <tuple>
25 : #include <utility>
26 :
27 : #include "include_fast_float.h"
28 :
29 : /************************************************************************/
30 : /* IngestAttributes() */
31 : /************************************************************************/
32 :
33 : /** For a given record that has a ATTR/INAS/FACS field, ingest all attributes
34 : * from a particular instance of that field
35 : */
36 1605 : bool OGRS101Reader::IngestAttributes(
37 : const DDFRecord *poRecord, int iRecord, const char *pszIDFieldName,
38 : const char *pszAttrFieldName, const DDFField *poATTRField, int iField,
39 : bool bMultipleFields, std::vector<S101AttrDef> &asS101AttrDefs) const
40 : {
41 3210 : std::set<std::tuple<AttrCode, AttrRepeat, AttrIndex>> oSetNATC_ATIX_PAIX;
42 :
43 52 : const auto GetErrorContext = [iRecord, pszIDFieldName, pszAttrFieldName,
44 104 : iField, bMultipleFields](AttrIndex iATTR)
45 : {
46 52 : if (bMultipleFields)
47 : {
48 0 : return CPLSPrintf(
49 : "Record index=%d of %s, %s[%d] field, attribute idx=%d",
50 : iRecord, pszIDFieldName, pszAttrFieldName, iField,
51 0 : static_cast<int>(iATTR));
52 : }
53 : else
54 : {
55 52 : return CPLSPrintf(
56 : "Record index=%d of %s, %s field, attribute idx=%d", iRecord,
57 52 : pszIDFieldName, pszAttrFieldName, static_cast<int>(iATTR));
58 : }
59 1605 : };
60 :
61 : const AttrCode nLargestNATC(
62 1605 : !m_attributeCodes.empty() ? m_attributeCodes.rbegin()->first : 0);
63 : const bool bAttributeCodesSequential =
64 3207 : !m_attributeCodes.empty() && m_attributeCodes.begin()->first == 1 &&
65 1602 : static_cast<size_t>(static_cast<int>(nLargestNATC)) ==
66 1602 : m_attributeCodes.size();
67 :
68 1605 : const int nRepeatCount = poATTRField->GetRepeatCount();
69 1605 : AttrCode nLastNATC = -1;
70 1605 : AttrRepeat nLastATIX = -1;
71 1605 : AttrIndex nLastPAIX = -1;
72 :
73 : // Find multi-valued parts of the path
74 3210 : std::map<std::pair<AttrCode, AttrIndex>, int> oMapOccurrenceCount;
75 3754 : for (AttrIndex iATTR = 0; iATTR < nRepeatCount; ++iATTR)
76 : {
77 : const auto GetIntSubfield =
78 8596 : [poRecord, poATTRField, iATTR](const char *pszSubFieldName)
79 : {
80 4298 : return poRecord->GetIntSubfield(poATTRField, pszSubFieldName,
81 4298 : static_cast<int>(iATTR));
82 2149 : };
83 :
84 2149 : ++oMapOccurrenceCount[{GetIntSubfield(NATC_SUBFIELD),
85 2149 : GetIntSubfield(PAIX_SUBFIELD)}];
86 : }
87 :
88 1605 : const size_t nS101AttrDefsBaseIdx = asS101AttrDefs.size();
89 :
90 3733 : for (AttrIndex iATTR = 0; iATTR < nRepeatCount; ++iATTR)
91 : {
92 : const auto GetIntSubfield =
93 17060 : [poRecord, poATTRField, iATTR](const char *pszSubFieldName)
94 : {
95 8530 : return poRecord->GetIntSubfield(poATTRField, pszSubFieldName,
96 8530 : static_cast<int>(iATTR));
97 2136 : };
98 :
99 2136 : const int nATIN = GetIntSubfield(ATIN_SUBFIELD);
100 2136 : if (nATIN != INSTRUCTION_INSERT)
101 : {
102 3 : if (!EMIT_ERROR_OR_WARNING(
103 : CPLSPrintf("%s: wrong value %d for ATIN subfield.",
104 : GetErrorContext(iATTR), nATIN)))
105 : {
106 8 : return false;
107 : }
108 2 : nLastNATC = -1;
109 2 : nLastATIX = -1;
110 2 : nLastPAIX = -1;
111 2 : asS101AttrDefs.push_back(S101AttrDef());
112 6 : continue;
113 : }
114 :
115 2133 : const AttrCode nNATC(GetIntSubfield(NATC_SUBFIELD));
116 2162 : if (!cpl::contains(m_attributeCodes, nNATC) &&
117 29 : !EMIT_ERROR_OR_WARNING(
118 : CPLSPrintf("%s: cannot find attribute code %d in ATCS field "
119 : "of the Dataset General Information Record%s.",
120 : GetErrorContext(iATTR), static_cast<int>(nNATC),
121 : bAttributeCodesSequential
122 : ? CPLSPrintf(". Must be in [1, %d]",
123 : static_cast<int>(nLargestNATC))
124 : : "")))
125 : {
126 1 : return false;
127 : }
128 :
129 2132 : const AttrRepeat nATIX(GetIntSubfield(ATIX_SUBFIELD));
130 2132 : if (!(nATIX >= 1 && nATIX <= nRepeatCount))
131 : {
132 3 : if (!EMIT_ERROR_OR_WARNING(
133 : CPLSPrintf("%s: wrong value %d for ATIX subfield. "
134 : "Must be in [1, %d].",
135 : GetErrorContext(iATTR), static_cast<int>(nATIX),
136 : nRepeatCount)))
137 : {
138 1 : return false;
139 : }
140 2 : nLastNATC = -1;
141 2 : nLastATIX = -1;
142 2 : nLastPAIX = -1;
143 2 : asS101AttrDefs.push_back(S101AttrDef());
144 2 : continue;
145 : }
146 :
147 2129 : const AttrIndex nPAIX = GetIntSubfield(PAIX_SUBFIELD);
148 : // The parent index must be lower than the current attribute index,
149 : // since parents are required to be listed before.
150 2129 : if (!(nPAIX >= 0 && nPAIX <= iATTR))
151 : {
152 3 : if (!EMIT_ERROR_OR_WARNING(
153 : CPLSPrintf("%s: wrong value %d for PAIX subfield. "
154 : "Must be in [0, %d].",
155 : GetErrorContext(iATTR), static_cast<int>(nPAIX),
156 : static_cast<int>(iATTR))))
157 : {
158 1 : return false;
159 : }
160 2 : nLastNATC = -1;
161 2 : nLastATIX = -1;
162 2 : nLastPAIX = -1;
163 2 : asS101AttrDefs.push_back(S101AttrDef());
164 2 : continue;
165 : }
166 :
167 2126 : if (nPAIX == nLastPAIX)
168 : {
169 220 : if (nNATC == nLastNATC)
170 : {
171 78 : if (nATIX != nLastATIX + 1 &&
172 78 : !EMIT_ERROR_OR_WARNING(CPLSPrintf(
173 : "%s: wrong value %d for ATIX subfield. Expected %d.",
174 : GetErrorContext(iATTR), static_cast<int>(nATIX),
175 : static_cast<int>(nLastATIX + 1))))
176 : {
177 1 : return false;
178 : }
179 : }
180 145 : else if (nATIX != 1)
181 : {
182 3 : if (!EMIT_ERROR_OR_WARNING(CPLSPrintf(
183 : "%s: wrong value %d for ATIX subfield. Expected %d.",
184 : GetErrorContext(iATTR), static_cast<int>(nATIX), 1)))
185 : {
186 1 : return false;
187 : }
188 : }
189 : }
190 :
191 : // (NATC,ATIX,PAIX) tuple should be unique within a record
192 2129 : if (!oSetNATC_ATIX_PAIX.insert({nNATC, nATIX, nPAIX}).second &&
193 5 : !EMIT_ERROR_OR_WARNING(
194 : CPLSPrintf("%s: several instances of "
195 : "(NATC,ATIX,PAIX)=(%d,%d,%d) "
196 : "in field %s of the same record.",
197 : GetErrorContext(iATTR), static_cast<int>(nNATC),
198 : static_cast<int>(nATIX), static_cast<int>(nPAIX),
199 : pszAttrFieldName)))
200 : {
201 1 : return false;
202 : }
203 :
204 : // Does this attribute have a parent?
205 2123 : const bool bIsMultiValued = oMapOccurrenceCount[{nNATC, nPAIX}] > 1;
206 2123 : PathVector oReversedPath{{nNATC, bIsMultiValued ? nATIX : 0}};
207 2123 : if (nPAIX > 0)
208 : {
209 : // Assertion can't trigger because nPAIX <= iATTR < asS101AttrDefs.size() - nS101AttrDefsBaseIdx
210 935 : CPLAssert(nS101AttrDefsBaseIdx +
211 : static_cast<size_t>(static_cast<int>(nPAIX) - 1) <
212 : asS101AttrDefs.size());
213 935 : auto &sParentAttrDef = asS101AttrDefs[nS101AttrDefsBaseIdx +
214 935 : static_cast<int>(nPAIX) - 1];
215 935 : sParentAttrDef.bIsParent = true;
216 938 : if (!sParentAttrDef.osVal.empty() &&
217 3 : !EMIT_ERROR_OR_WARNING(CPLSPrintf(
218 : "%s: parent attribute of index PAIX=%d has "
219 : "a non empty ATVL subfield.",
220 : GetErrorContext(iATTR), static_cast<int>(nPAIX))))
221 : {
222 1 : return false;
223 : }
224 : #if defined(__GNUC__)
225 : #pragma GCC diagnostic push
226 : #pragma GCC diagnostic ignored "-Wnull-dereference"
227 : #endif
228 934 : oReversedPath.insert(oReversedPath.end(),
229 : sParentAttrDef.oReversedPath.begin(),
230 1868 : sParentAttrDef.oReversedPath.end());
231 : #if defined(__GNUC__)
232 : #pragma GCC diagnostic pop
233 : #endif
234 : }
235 :
236 2122 : const char *pszATVL = poRecord->GetStringSubfield(
237 : poATTRField, ATVL_SUBFIELD, static_cast<int>(iATTR));
238 2122 : if (!pszATVL &&
239 0 : !EMIT_ERROR_OR_WARNING(CPLSPrintf("%s: cannot read ATVL subfield.",
240 : GetErrorContext(iATTR))))
241 : {
242 0 : return false;
243 : }
244 :
245 2122 : S101AttrDef sAttrDef;
246 2122 : sAttrDef.iField = iField;
247 2122 : sAttrDef.bMultipleFields = bMultipleFields;
248 2122 : sAttrDef.oReversedPath = std::move(oReversedPath);
249 2122 : if (pszATVL)
250 2122 : sAttrDef.osVal = pszATVL;
251 2122 : asS101AttrDefs.push_back(std::move(sAttrDef));
252 :
253 2122 : nLastNATC = nNATC;
254 2122 : nLastATIX = nATIX;
255 2122 : nLastPAIX = nPAIX;
256 : }
257 :
258 1597 : return true;
259 : }
260 :
261 : /************************************************************************/
262 : /* IngestAttributes() */
263 : /************************************************************************/
264 :
265 : /** For a given record that has a ATTR/INAS/FACS field, ingest all attributes
266 : * from all instances of this field.
267 : */
268 7103 : bool OGRS101Reader::IngestAttributes(
269 : const DDFRecord *poRecord, int iRecord, const char *pszIDFieldName,
270 : const char *pszAttrFieldName,
271 : std::vector<S101AttrDef> &asS101AttrDefs) const
272 : {
273 7103 : asS101AttrDefs.clear();
274 :
275 14206 : const auto apoATTRFields = poRecord->GetFields(pszAttrFieldName);
276 7103 : const int nATTRFieldCount = static_cast<int>(apoATTRFields.size());
277 7103 : bool bSuccess = true;
278 7103 : if (EQUAL(pszAttrFieldName, ATTR_FIELD))
279 : {
280 2379 : for (int iATTRField = 0; bSuccess && iATTRField < nATTRFieldCount;
281 : ++iATTRField)
282 : {
283 719 : bSuccess = IngestAttributes(poRecord, iRecord, pszIDFieldName,
284 : pszAttrFieldName,
285 719 : apoATTRFields[iATTRField], iATTRField,
286 : nATTRFieldCount > 1, asS101AttrDefs);
287 : }
288 : }
289 : else
290 : {
291 6329 : for (int iATTRField = 0; bSuccess && iATTRField < nATTRFieldCount;
292 : ++iATTRField)
293 : {
294 886 : const auto poINASOrFASCField = apoATTRFields[iATTRField];
295 886 : if (poINASOrFASCField->GetParts().size() != 2)
296 : {
297 0 : if (!EMIT_ERROR_OR_WARNING(
298 : CPLSPrintf("Record index=%d of %s: missing components "
299 : "in %s field.",
300 : iRecord, pszIDFieldName, pszAttrFieldName)))
301 : {
302 0 : return false;
303 : }
304 0 : return true;
305 : }
306 886 : const auto poATTRField = poINASOrFASCField->GetParts()[1].get();
307 :
308 886 : bSuccess = IngestAttributes(
309 : poRecord, iRecord, pszIDFieldName, pszAttrFieldName,
310 : poATTRField, iATTRField, nATTRFieldCount > 1, asS101AttrDefs);
311 : }
312 : }
313 :
314 9231 : for (auto &sAttrDef : asS101AttrDefs)
315 : {
316 2128 : if (sAttrDef.bIsParent || sAttrDef.oReversedPath.empty())
317 504 : continue;
318 :
319 : // For last component, set the repetition part to 0, to be
320 : // actually able to detect multi-valued attributes!
321 1624 : sAttrDef.oReversedPath.front().second = 0;
322 : }
323 :
324 7103 : return bSuccess;
325 : }
326 :
327 : /************************************************************************/
328 : /* BuildFieldName() */
329 : /************************************************************************/
330 :
331 : /** Returns a string with the concatenation of the parts, in reverse order.
332 : */
333 1591 : std::string OGRS101Reader::BuildFieldName(const PathVector &oReversedPath,
334 : const char *pszAttrFieldName,
335 : int iField, bool bMultipleFields,
336 : const char *pszIDFieldName) const
337 : {
338 1591 : std::string osAttrName;
339 4227 : for (size_t i = oReversedPath.size(); i > 0;)
340 : {
341 2636 : --i;
342 2636 : const auto &oPathComp = oReversedPath[i];
343 2636 : if (!osAttrName.empty())
344 1045 : osAttrName += '.';
345 2636 : const auto nCode = oPathComp.first;
346 2636 : const auto oIterNATC = m_attributeCodes.find(nCode);
347 2636 : if (oIterNATC == m_attributeCodes.end())
348 : {
349 42 : osAttrName += CPLSPrintf("code_%d", static_cast<int>(nCode));
350 : }
351 : else
352 : {
353 2594 : osAttrName += oIterNATC->second;
354 : }
355 :
356 2636 : if (bMultipleFields && strcmp(pszAttrFieldName, ATTR_FIELD) == 0)
357 : {
358 347 : osAttrName += '[';
359 347 : osAttrName += std::to_string(iField + 1);
360 347 : osAttrName += ']';
361 347 : bMultipleFields = false;
362 : }
363 :
364 2636 : const auto &nRepeat = oPathComp.second;
365 2636 : if (nRepeat > 0)
366 : {
367 268 : osAttrName += '[';
368 268 : osAttrName += std::to_string(static_cast<int>(nRepeat));
369 268 : osAttrName += ']';
370 : }
371 : }
372 :
373 1591 : if (strcmp(pszAttrFieldName, ATTR_FIELD) != 0)
374 : {
375 395 : std::string osPrefix;
376 395 : if (strcmp(pszIDFieldName, IRID_FIELD) == 0)
377 : {
378 33 : osPrefix = "association";
379 : }
380 362 : else if (strcmp(pszIDFieldName, FRID_FIELD) == 0)
381 : {
382 280 : if (strcmp(pszAttrFieldName, INAS_FIELD) == 0)
383 140 : osPrefix = "infoAssociation";
384 : else
385 140 : osPrefix = "featureAssociation";
386 : }
387 395 : if (!osPrefix.empty())
388 : {
389 313 : if (bMultipleFields)
390 : {
391 120 : osPrefix += '[';
392 120 : osPrefix += std::to_string(iField + 1);
393 120 : osPrefix += ']';
394 : }
395 313 : osPrefix += '_';
396 : }
397 395 : osAttrName = osPrefix + osAttrName;
398 : }
399 :
400 1591 : return osAttrName;
401 : }
402 :
403 : /************************************************************************/
404 : /* InferFeatureDefn() */
405 : /************************************************************************/
406 :
407 : /** Infer the feature definition from the content of INAS or ATTR records
408 : * of the index.
409 : */
410 1693 : bool OGRS101Reader::InferFeatureDefn(
411 : const DDFRecordIndex &oIndex, const char *pszIDFieldName,
412 : const char *pszAttrFieldName, const std::vector<int> &anRecordIndices,
413 : OGRFeatureDefn &oFeatureDefn,
414 : std::map<std::string, std::unique_ptr<OGRFieldDomain>> &oMapFieldDomains,
415 : const OGRS101FeatureCatalogTypes::InformationType * /*psInformationType*/,
416 : const OGRS101FeatureCatalogTypes::FeatureType *psFeatureType) const
417 : {
418 1693 : const bool bIsINAS = EQUAL(pszAttrFieldName, INAS_FIELD);
419 :
420 : struct OGRAttrDef
421 : {
422 : std::optional<OGRFieldType> oeType{};
423 : OGRFieldSubType eSubType = OFSTNone;
424 : bool bIsMultiValued = false;
425 : bool bMultipleFields = false;
426 : std::string osLongerName{};
427 : std::string osFieldDomainName{};
428 : };
429 :
430 : using PathVectorAndAttrIdx = std::pair<PathVector, int>;
431 3386 : std::map<PathVectorAndAttrIdx, OGRAttrDef> oMapFieldTypes;
432 :
433 3386 : std::vector<S101AttrDef> asS101AttrDefs;
434 3386 : std::map<PathVectorAndAttrIdx, int> mapPathToCount;
435 1693 : bool bFoundValidAssocField = false;
436 :
437 : // Iterate over the records (in the index of interest) to fill the
438 : // oMapFieldTypes map object that will be afterwards translated as
439 : // OGR feature definition
440 : // If anRecordIndices is not empty, it defines the subset of record
441 : // indices to iterate over. This is used for geometry records that are
442 : // dispatched to different OGR layers depending on the CRS.
443 1693 : const int nRecords = anRecordIndices.empty()
444 2771 : ? oIndex.GetCount()
445 1078 : : static_cast<int>(anRecordIndices.size());
446 1693 : int nMaxFieldRepeat = 1;
447 4035 : for (int iter = 0; iter < nRecords; ++iter)
448 : {
449 : const int iRecord =
450 2358 : anRecordIndices.empty() ? iter : anRecordIndices[iter];
451 :
452 16 : const auto GetErrorContext = [pszIDFieldName, iRecord]()
453 : {
454 16 : return CPLSPrintf("Record index=%d of %s", iRecord, pszIDFieldName);
455 2358 : };
456 :
457 2358 : const auto poRecord = oIndex.GetByIndex(iRecord);
458 2358 : CPLAssert(poRecord);
459 :
460 : const int nRUIN =
461 2358 : poRecord->GetIntSubfield(pszIDFieldName, 0, RUIN_SUBFIELD, 0);
462 2358 : if (nRUIN != INSTRUCTION_INSERT)
463 : {
464 0 : if (!EMIT_ERROR_OR_WARNING(CPLSPrintf("%s: wrong value %d for RUIN "
465 : "subfield of %s field.",
466 : GetErrorContext(), nRUIN,
467 : pszIDFieldName)))
468 : {
469 16 : return false;
470 : }
471 0 : continue;
472 : }
473 :
474 2358 : if (!EQUAL(pszAttrFieldName, ATTR_FIELD))
475 : {
476 1795 : const auto apoFields = poRecord->GetFields(pszAttrFieldName);
477 1795 : bool bSkipRecord = false;
478 1795 : nMaxFieldRepeat =
479 1795 : std::max(nMaxFieldRepeat, static_cast<int>(apoFields.size()));
480 1795 : if (!apoFields.empty())
481 : {
482 240 : const DDFField *poField = apoFields[0];
483 :
484 : const RecordName nRRNM =
485 240 : poRecord->GetIntSubfield(poField, RRNM_SUBFIELD, 0);
486 240 : const RecordName nExpectedRRNM =
487 240 : bIsINAS ? RECORD_NAME_INFORMATION_TYPE
488 : : RECORD_NAME_FEATURE_TYPE;
489 240 : if (nRRNM != nExpectedRRNM)
490 : {
491 8 : if (!EMIT_ERROR_OR_WARNING(CPLSPrintf(
492 : "%s: Invalid value for RRNM subfield of %s field: "
493 : "got %d, expected %d.",
494 : GetErrorContext(), pszAttrFieldName,
495 : static_cast<int>(nRRNM),
496 : static_cast<int>(nExpectedRRNM))))
497 : {
498 7 : return false;
499 : }
500 : }
501 :
502 : const int nRRID =
503 236 : poRecord->GetIntSubfield(poField, RRID_SUBFIELD, 0);
504 436 : if ((bIsINAS &&
505 470 : !m_oInformationTypeRecordIndex.FindRecord(nRRID)) ||
506 234 : (!bIsINAS && !m_oFeatureTypeRecordIndex.FindRecord(nRRID)))
507 : {
508 2 : if (!EMIT_ERROR_OR_WARNING(CPLSPrintf(
509 : "%s: Invalid value %d for RRID subfield of %s "
510 : "field: "
511 : "does not match the record identifier of an "
512 : "existing "
513 : "%s record.",
514 : GetErrorContext(), static_cast<int>(nRRID),
515 : pszAttrFieldName,
516 : bIsINAS ? "InformationType" : "FeatureType")))
517 : {
518 1 : return false;
519 : }
520 : }
521 :
522 235 : if (bIsINAS)
523 : {
524 : const InfoAssocCode nNIAC =
525 199 : poRecord->GetIntSubfield(poField, NIAC_SUBFIELD, 0);
526 201 : if (!cpl::contains(m_informationAssociationCodes, nNIAC) &&
527 2 : !EMIT_ERROR_OR_WARNING(CPLSPrintf(
528 : "%s: cannot find attribute code %d in IACS field "
529 : "of the Dataset General Information Record.",
530 : GetErrorContext(), static_cast<int>(nNIAC))))
531 : {
532 1 : return false;
533 : }
534 : }
535 : else
536 : {
537 : const FeatureAssocCode nNFAC =
538 36 : poRecord->GetIntSubfield(poField, NFAC_SUBFIELD, 0);
539 36 : if (!cpl::contains(m_featureAssociationCodes, nNFAC) &&
540 0 : !EMIT_ERROR_OR_WARNING(CPLSPrintf(
541 : "%s: cannot find attribute code %d in NFAC field "
542 : "of the Dataset General Information Record.",
543 : GetErrorContext(), static_cast<int>(nNFAC))))
544 : {
545 0 : return false;
546 : }
547 : }
548 :
549 : const AssocRoleCode nNARC =
550 234 : poRecord->GetIntSubfield(poField, NARC_SUBFIELD, 0);
551 236 : if (!cpl::contains(m_associationRoleCodes, nNARC) &&
552 2 : !EMIT_ERROR_OR_WARNING(CPLSPrintf(
553 : "%s: cannot find attribute code %d in ARCS field "
554 : "of the Dataset General Information Record.",
555 : GetErrorContext(), static_cast<int>(nNARC))))
556 : {
557 1 : return false;
558 : }
559 :
560 233 : const char *pszSubFieldName =
561 : bIsINAS ? IUIN_SUBFIELD : FAUI_SUBFIELD;
562 : int nInstruction =
563 233 : poRecord->GetIntSubfield(poField, pszSubFieldName, 0);
564 233 : if (nInstruction == 0)
565 : {
566 : // For 101GB00GB302045.000, non conformant
567 0 : nInstruction = poRecord->GetIntSubfield(poField, "APUI", 0);
568 : }
569 233 : if (nInstruction != INSTRUCTION_INSERT)
570 : {
571 0 : if (!EMIT_ERROR_OR_WARNING(
572 : CPLSPrintf("%s: wrong value %d for %s "
573 : "subfield of %s field.",
574 : GetErrorContext(), nInstruction,
575 : pszSubFieldName, pszAttrFieldName)))
576 : {
577 0 : return false;
578 : }
579 0 : bSkipRecord = true;
580 : }
581 : else
582 : {
583 233 : bFoundValidAssocField = true;
584 : }
585 : }
586 1788 : if (bSkipRecord)
587 0 : continue;
588 : }
589 :
590 : // First (inner) pass over attributes of the current record
591 : // to fill asS101AttrDefs, and do all needed sanity checks
592 2351 : if (!IngestAttributes(poRecord, iRecord, pszIDFieldName,
593 : pszAttrFieldName, asS101AttrDefs))
594 8 : return false;
595 :
596 2343 : mapPathToCount.clear();
597 :
598 : // Update oMapFieldTypes with attributes found in this record
599 2842 : for (const auto &sAttrDef : asS101AttrDefs)
600 : {
601 500 : if (sAttrDef.bIsParent || sAttrDef.oReversedPath.empty())
602 85 : continue;
603 :
604 : // Check that the top-level part of the attribute is expected for
605 : // that feature type (using feature catalog)
606 415 : if (psFeatureType)
607 : {
608 : const auto oIterNATC =
609 0 : m_attributeCodes.find(sAttrDef.oReversedPath.back().first);
610 0 : if (oIterNATC != m_attributeCodes.end())
611 : {
612 0 : const std::string &osAttrCode = oIterNATC->second;
613 0 : if (!cpl::contains(psFeatureType->attributeBindings,
614 : osAttrCode))
615 : {
616 0 : if (!EMIT_ERROR_OR_WARNING(CPLSPrintf(
617 : "%s: attribute code %s not expected in feature "
618 : "type %s",
619 : GetErrorContext(), osAttrCode.c_str(),
620 : psFeatureType->code.c_str())))
621 : {
622 0 : return false;
623 : }
624 : }
625 : }
626 : }
627 :
628 : const auto key =
629 415 : std::make_pair(sAttrDef.oReversedPath, sAttrDef.iField);
630 415 : ++mapPathToCount[key];
631 :
632 : // Must be kept in that scope to create a OGR attribute even if
633 : // there is no field value
634 415 : auto &sOGRAttrDef = oMapFieldTypes[key];
635 :
636 415 : std::string typeFromCatalog;
637 415 : if (m_poFeatureCatalog)
638 : {
639 : const auto oIterNATC =
640 415 : m_attributeCodes.find(sAttrDef.oReversedPath.front().first);
641 415 : if (oIterNATC != m_attributeCodes.end())
642 : {
643 : const auto &oMap =
644 407 : m_poFeatureCatalog->GetSimpleAttributes();
645 407 : const std::string &osAttrCode = oIterNATC->second;
646 407 : const auto oIterAttr = oMap.find(osAttrCode);
647 407 : if (oIterAttr != oMap.end())
648 : {
649 398 : const auto &attrDef = oIterAttr->second;
650 398 : typeFromCatalog = attrDef.type;
651 :
652 398 : sOGRAttrDef.osLongerName = attrDef.name;
653 :
654 398 : if (typeFromCatalog ==
655 : OGRS101FeatureCatalog::VALUE_TYPE_ENUMERATION)
656 : {
657 45 : sOGRAttrDef.osFieldDomainName = osAttrCode;
658 :
659 45 : if (!sAttrDef.osVal.empty())
660 : {
661 : // Checks that the coded value is an allowed code.
662 45 : const int nCode = atoi(sAttrDef.osVal.c_str());
663 45 : if (!cpl::contains(attrDef.enumeratedValues,
664 : nCode))
665 : {
666 2 : if (!EMIT_ERROR_OR_WARNING(CPLSPrintf(
667 : "%s: value %s does not belong to "
668 : "enumeration of attribute code %s",
669 : GetErrorContext(),
670 : sAttrDef.osVal.c_str(),
671 : osAttrCode.c_str())))
672 : {
673 1 : return false;
674 : }
675 : }
676 : }
677 :
678 : // Check if field domain exists. If not, create it.
679 44 : if (!cpl::contains(oMapFieldDomains, osAttrCode))
680 : {
681 68 : std::vector<OGRCodedValue> asValues;
682 884 : for (const auto &[code, value] :
683 918 : attrDef.enumeratedValues)
684 : {
685 : OGRCodedValue codedValue;
686 442 : codedValue.pszCode =
687 442 : CPLStrdup(CPLSPrintf("%d", code));
688 442 : codedValue.pszValue =
689 442 : CPLStrdup(value.c_str());
690 442 : asValues.push_back(std::move(codedValue));
691 : }
692 : auto poFieldDomain =
693 : std::make_unique<OGRCodedFieldDomain>(
694 0 : osAttrCode, attrDef.name, OFTString,
695 68 : OFSTNone, std::move(asValues));
696 34 : oMapFieldDomains[osAttrCode] =
697 68 : std::move(poFieldDomain);
698 : }
699 : }
700 : }
701 : }
702 : }
703 :
704 414 : sOGRAttrDef.bMultipleFields = sAttrDef.bMultipleFields;
705 :
706 414 : if (!sAttrDef.osVal.empty())
707 : {
708 405 : const bool bNewAttrIsMultiValued = mapPathToCount[key] > 1;
709 405 : if (bNewAttrIsMultiValued)
710 13 : sOGRAttrDef.bIsMultiValued = true;
711 405 : const auto eCPLType = CPLGetValueType(sAttrDef.osVal.c_str());
712 487 : auto eOGRType = eCPLType == CPL_VALUE_STRING ? OFTString
713 82 : : eCPLType == CPL_VALUE_INTEGER ? OFTInteger
714 : : OFTReal;
715 :
716 : // Is it YYYYMMDD date ?
717 57 : if (eOGRType == OFTInteger && sAttrDef.osVal.size() == 8 &&
718 462 : sAttrDef.osVal[4] <= '1' && sAttrDef.osVal[6] <= '3')
719 : {
720 : const auto it = m_attributeCodes.find(
721 0 : sAttrDef.oReversedPath.front().first);
722 0 : if (it != m_attributeCodes.end())
723 : {
724 0 : if (cpl::ends_with(it->second, "Date"))
725 : {
726 0 : eOGRType = OFTDate;
727 : }
728 : }
729 : }
730 : // Is it YYYY---- truncated date ?
731 323 : else if (eOGRType == OFTString && sAttrDef.osVal.size() == 8 &&
732 0 : sAttrDef.osVal[4] == '-' && sAttrDef.osVal[5] == '-' &&
733 728 : sAttrDef.osVal[6] == '-' && sAttrDef.osVal[7] == '-')
734 : {
735 : const auto it = m_attributeCodes.find(
736 0 : sAttrDef.oReversedPath.front().first);
737 0 : if (it != m_attributeCodes.end())
738 : {
739 0 : if (cpl::ends_with(it->second, "dateStart") ||
740 0 : cpl::ends_with(it->second, "dateEnd"))
741 : {
742 0 : eOGRType = OFTDate;
743 : }
744 : }
745 : }
746 : // Is it time format ? ("094500", "094500", "094500+0100")
747 728 : else if ((eOGRType == OFTInteger || eOGRType == OFTString) &&
748 675 : sAttrDef.osVal.size() >= 6 &&
749 295 : sAttrDef.osVal.size() <= 11 &&
750 173 : std::all_of(sAttrDef.osVal.begin(),
751 173 : sAttrDef.osVal.begin() + 6, [](char c)
752 988 : { return c >= '0' && c <= '9'; }) &&
753 1 : (sAttrDef.osVal.size() == 6 ||
754 0 : (sAttrDef.osVal.size() == 7 &&
755 0 : sAttrDef.osVal[6] == 'Z') ||
756 0 : (sAttrDef.osVal.size() == 11 &&
757 0 : (sAttrDef.osVal[6] == '+' ||
758 0 : sAttrDef.osVal[6] == '-'))))
759 : {
760 : const auto it = m_attributeCodes.find(
761 1 : sAttrDef.oReversedPath.front().first);
762 1 : if (it != m_attributeCodes.end())
763 : {
764 1 : if (cpl::starts_with(it->second, "time"))
765 : {
766 0 : eOGRType = OFTTime;
767 : }
768 : }
769 : }
770 :
771 405 : if (!sOGRAttrDef.oeType.has_value())
772 : {
773 386 : sOGRAttrDef.oeType = eOGRType;
774 431 : if (eOGRType == OFTInteger &&
775 45 : typeFromCatalog ==
776 : OGRS101FeatureCatalog::VALUE_TYPE_BOOLEAN)
777 : {
778 0 : sOGRAttrDef.eSubType = OFSTBoolean;
779 : }
780 : }
781 25 : else if (eOGRType == OFTString &&
782 6 : *sOGRAttrDef.oeType != OFTString)
783 : {
784 0 : sOGRAttrDef.oeType = OFTString;
785 0 : sOGRAttrDef.eSubType = OFSTNone;
786 : }
787 20 : else if (eOGRType == OFTReal &&
788 1 : *sOGRAttrDef.oeType == OFTInteger)
789 : {
790 1 : sOGRAttrDef.oeType = OFTReal;
791 1 : sOGRAttrDef.eSubType = OFSTNone;
792 : }
793 : }
794 : }
795 : }
796 :
797 1677 : if (bFoundValidAssocField)
798 : {
799 462 : for (int i = 0; i < nMaxFieldRepeat; ++i)
800 : {
801 : const std::string osSuffix =
802 470 : nMaxFieldRepeat > 1 ? CPLSPrintf("[%d]", i + 1) : "";
803 :
804 235 : if (!bIsINAS)
805 : {
806 : OGRFieldDefn oFieldDefn(
807 40 : (OGR_FIELD_NAME_REF_FEAT_LAYER_NAME + osSuffix).c_str(),
808 80 : OFTString);
809 40 : oFeatureDefn.AddFieldDefn(&oFieldDefn);
810 : }
811 :
812 : {
813 : OGRFieldDefn oFieldDefn(
814 : ((bIsINAS ? OGR_FIELD_NAME_REF_INFO_RID
815 235 : : OGR_FIELD_NAME_REF_FEAT_RID) +
816 : osSuffix)
817 : .c_str(),
818 470 : OFTInteger);
819 235 : oFeatureDefn.AddFieldDefn(&oFieldDefn);
820 : }
821 : {
822 : OGRFieldDefn oFieldDefn(
823 235 : ((bIsINAS ? OGR_FIELD_NAME_NIAC : OGR_FIELD_NAME_NFAC) +
824 : osSuffix)
825 : .c_str(),
826 470 : OFTString);
827 235 : oFeatureDefn.AddFieldDefn(&oFieldDefn);
828 : }
829 : {
830 : OGRFieldDefn oFieldDefn(
831 : ((bIsINAS ? OGR_FIELD_NAME_NARC
832 235 : : OGR_FIELD_NAME_FEATURE_NARC) +
833 : osSuffix)
834 : .c_str(),
835 470 : OFTString);
836 235 : oFeatureDefn.AddFieldDefn(&oFieldDefn);
837 : }
838 : }
839 : }
840 :
841 : // Final pass to transform oMapFieldTypes into OGRField instances.
842 2063 : for (const auto &[oReversedPathAndFieldIndex, sOGRAttrDef] : oMapFieldTypes)
843 : {
844 386 : const auto &oReversedPath = oReversedPathAndFieldIndex.first;
845 386 : const int iField = oReversedPathAndFieldIndex.second;
846 :
847 : const std::string osAttrName =
848 : BuildFieldName(oReversedPath, pszAttrFieldName, iField,
849 772 : sOGRAttrDef.bMultipleFields, pszIDFieldName);
850 :
851 386 : OGRFieldType eType = OFTString;
852 386 : if (sOGRAttrDef.oeType.has_value())
853 : {
854 379 : if (sOGRAttrDef.bIsMultiValued)
855 : {
856 14 : eType = (*sOGRAttrDef.oeType) == OFTInteger ? OFTIntegerList
857 1 : : (*sOGRAttrDef.oeType) == OFTReal ? OFTRealList
858 : : OFTStringList;
859 : }
860 : else
861 : {
862 366 : eType = *sOGRAttrDef.oeType;
863 : }
864 : }
865 7 : else if (sOGRAttrDef.bIsMultiValued)
866 : {
867 0 : eType = OFTStringList;
868 : }
869 :
870 386 : if (oFeatureDefn.GetFieldIndex(osAttrName.c_str()) >= 0)
871 : {
872 0 : if (osAttrName == OGR_FIELD_NAME_SMIN ||
873 0 : osAttrName == OGR_FIELD_NAME_SMAX)
874 : {
875 : // 101FR00368570.000 has scaleMinimum as an ATTR, and
876 : // doesn't define SPAS.SMIN
877 : }
878 : else
879 : {
880 0 : CPLError(CE_Warning, CPLE_AppDefined,
881 : "Layer %s: %s field already exists",
882 0 : oFeatureDefn.GetName(), osAttrName.c_str());
883 : }
884 : }
885 : else
886 : {
887 772 : OGRFieldDefn oFieldDefn(osAttrName.c_str(), eType);
888 386 : oFieldDefn.SetSubType(sOGRAttrDef.eSubType);
889 386 : if (!sOGRAttrDef.osLongerName.empty() && oReversedPath.size() == 1)
890 296 : oFieldDefn.SetAlternativeName(sOGRAttrDef.osLongerName.c_str());
891 386 : if (!sOGRAttrDef.osFieldDomainName.empty())
892 33 : oFieldDefn.SetDomainName(sOGRAttrDef.osFieldDomainName.c_str());
893 386 : oFeatureDefn.AddFieldDefn(&oFieldDefn);
894 : }
895 : }
896 :
897 1677 : return true;
898 : }
899 :
900 : /************************************************************************/
901 : /* FillFeatureAttributes() */
902 : /************************************************************************/
903 :
904 : /** Fill attribute fields of the provided feature.
905 : */
906 4752 : bool OGRS101Reader::FillFeatureAttributes(const DDFRecordIndex &oIndex,
907 : int iRecord,
908 : const char *pszAttrFieldName,
909 : OGRFeature &oFeature) const
910 : {
911 4752 : const auto poRecord = oIndex.GetByIndex(iRecord);
912 4752 : if (!poRecord)
913 : {
914 0 : return EMIT_ERROR("Invalid record number");
915 : }
916 :
917 4752 : const auto poIDField = poRecord->GetField(0);
918 4752 : CPLAssert(poIDField);
919 4752 : const char *pszIDFieldName = poIDField->GetFieldDefn()->GetName();
920 :
921 : const int nRCID =
922 4752 : poRecord->GetIntSubfield(pszIDFieldName, 0, RCID_SUBFIELD, 0);
923 4752 : if (nRCID < 1 && !EMIT_ERROR_OR_WARNING(CPLSPrintf(
924 : "Wrong value %d for RCID subfield of %s field.", nRCID,
925 : pszIDFieldName)))
926 : {
927 0 : return false;
928 : }
929 4752 : oFeature.SetField(OGR_FIELD_NAME_RECORD_ID, nRCID);
930 :
931 : const int nRVER =
932 4752 : poRecord->GetIntSubfield(pszIDFieldName, 0, RVER_SUBFIELD, 0);
933 4752 : oFeature.SetField(OGR_FIELD_NAME_RECORD_VERSION, nRVER);
934 :
935 : const int nRUIN =
936 4752 : poRecord->GetIntSubfield(pszIDFieldName, 0, RUIN_SUBFIELD, 0);
937 4752 : if (nRUIN != INSTRUCTION_INSERT)
938 : {
939 0 : return EMIT_ERROR_OR_WARNING(
940 : CPLSPrintf("Wrong value %d for RUIN subfield of %s field.", nRUIN,
941 : pszIDFieldName));
942 : }
943 :
944 4752 : const auto poFeatureDefn = oFeature.GetDefnRef();
945 :
946 : // First pass to detect which attribute entries correspond to parent nodes
947 : // that don't directly hold a field value.
948 9504 : std::vector<S101AttrDef> asS101AttrDefs;
949 4752 : if (!IngestAttributes(poRecord, iRecord, pszIDFieldName, pszAttrFieldName,
950 : asS101AttrDefs))
951 0 : return false;
952 :
953 : struct OGRFieldIndexTag
954 : {
955 : };
956 :
957 : using OGRFieldIndex = cpl::IntWrapper<OGRFieldIndexTag>;
958 :
959 9504 : std::map<OGRFieldIndex, CPLStringList> stringListAttrs;
960 9504 : std::map<OGRFieldIndex, std::vector<int>> intListAttrs;
961 9504 : std::map<OGRFieldIndex, std::vector<double>> doubleListAttrs;
962 : // Second pass to set single-valued attributes, or store multi-valued
963 : // attributes in the 3 above maps.
964 6375 : for (const auto &sAttrDef : asS101AttrDefs)
965 : {
966 1623 : if (sAttrDef.bIsParent || sAttrDef.oReversedPath.empty())
967 418 : continue;
968 :
969 : const std::string osAttrName = BuildFieldName(
970 1205 : sAttrDef.oReversedPath, pszAttrFieldName, sAttrDef.iField,
971 1205 : sAttrDef.bMultipleFields, pszIDFieldName);
972 :
973 : const int iOGRFieldIdx =
974 1205 : poFeatureDefn->GetFieldIndex(osAttrName.c_str());
975 : // Shouldn't normally happen given all preceding run logic
976 1205 : CPLAssert(iOGRFieldIdx >= 0);
977 1205 : const auto eType = poFeatureDefn->GetFieldDefn(iOGRFieldIdx)->GetType();
978 1205 : const char *const pszATVL = sAttrDef.osVal.c_str();
979 1205 : switch (eType)
980 : {
981 227 : case OFTInteger:
982 : case OFTIntegerList:
983 : {
984 227 : if (pszATVL[0])
985 : {
986 227 : const char *const last = pszATVL + sAttrDef.osVal.size();
987 227 : int nVal = -1;
988 227 : auto [ptr, ec] = std::from_chars(pszATVL, last, nVal);
989 227 : if (ec == std::errc() && ptr == last)
990 : {
991 226 : if (eType == OFTInteger)
992 112 : oFeature.SetField(iOGRFieldIdx, nVal);
993 : else
994 114 : intListAttrs[iOGRFieldIdx].push_back(nVal);
995 : }
996 1 : else if (!EMIT_ERROR_OR_WARNING(CPLSPrintf(
997 : "Record index=%d of %s, attribute %s: "
998 : "non integer value '%s'.",
999 : iRecord, pszIDFieldName, osAttrName.c_str(),
1000 : pszATVL)))
1001 : {
1002 0 : return false;
1003 : }
1004 : }
1005 0 : else if (eType == OFTIntegerList)
1006 : {
1007 0 : intListAttrs[iOGRFieldIdx].push_back(
1008 0 : std::numeric_limits<int>::min());
1009 : }
1010 227 : break;
1011 : }
1012 :
1013 114 : case OFTReal:
1014 : case OFTRealList:
1015 : {
1016 114 : if (pszATVL[0])
1017 : {
1018 114 : const char *const last = pszATVL + sAttrDef.osVal.size();
1019 114 : double dfVal = -1;
1020 114 : const fast_float::parse_options options{
1021 : fast_float::chars_format::general, '.'};
1022 : auto [ptr, ec] = fast_float::from_chars_advanced(
1023 114 : pszATVL, last, dfVal, options);
1024 114 : if (ec == std::errc() && ptr == last)
1025 : {
1026 113 : if (eType == OFTReal)
1027 111 : oFeature.SetField(iOGRFieldIdx, dfVal);
1028 : else
1029 2 : doubleListAttrs[iOGRFieldIdx].push_back(dfVal);
1030 : }
1031 1 : else if (!EMIT_ERROR_OR_WARNING(CPLSPrintf(
1032 : "Record index=%d of %s, attribute %s: "
1033 : "non double value '%s'.",
1034 : iRecord, pszIDFieldName, osAttrName.c_str(),
1035 : pszATVL)))
1036 : {
1037 0 : return false;
1038 : }
1039 : }
1040 0 : else if (eType == OFTRealList)
1041 : {
1042 0 : doubleListAttrs[iOGRFieldIdx].push_back(
1043 0 : std::numeric_limits<double>::quiet_NaN());
1044 : }
1045 114 : break;
1046 : }
1047 :
1048 864 : case OFTString:
1049 : case OFTStringList:
1050 : {
1051 0 : std::unique_ptr<char, VSIFreeReleaser> pszTmpStr;
1052 864 : const char *pszStr = pszATVL;
1053 864 : if (!CPLIsUTF8(pszATVL,
1054 864 : static_cast<int>(sAttrDef.osVal.size())))
1055 : {
1056 : // Not supposed to happen in compliant products
1057 1 : if (!EMIT_ERROR_OR_WARNING(CPLSPrintf(
1058 : "Record index=%d of %s, attribute %s: non "
1059 : "UTF-8 string '%s'.",
1060 : iRecord, pszIDFieldName, osAttrName.c_str(),
1061 : pszATVL)))
1062 : {
1063 0 : return false;
1064 : }
1065 1 : pszTmpStr.reset(CPLUTF8ForceToASCII(pszATVL, '_'));
1066 1 : pszStr = pszTmpStr.get();
1067 : }
1068 864 : if (eType == OFTString)
1069 864 : oFeature.SetField(iOGRFieldIdx, pszStr);
1070 : else
1071 0 : stringListAttrs[iOGRFieldIdx].push_back(pszStr);
1072 864 : break;
1073 : }
1074 :
1075 0 : case OFTDate:
1076 : {
1077 0 : if (sAttrDef.osVal.size() == 8 &&
1078 0 : std::all_of(sAttrDef.osVal.begin(), sAttrDef.osVal.end(),
1079 0 : [](char c) { return c >= '0' && c <= '9'; }))
1080 : {
1081 0 : const int nYear = (sAttrDef.osVal[0] - '0') * 1000 +
1082 0 : (sAttrDef.osVal[1] - '0') * 100 +
1083 0 : (sAttrDef.osVal[2] - '0') * 10 +
1084 0 : (sAttrDef.osVal[3] - '0');
1085 0 : const int nMonth = (sAttrDef.osVal[4] - '0') * 10 +
1086 0 : (sAttrDef.osVal[5] - '0');
1087 0 : const int nDay = (sAttrDef.osVal[6] - '0') * 10 +
1088 0 : (sAttrDef.osVal[7] - '0');
1089 0 : oFeature.SetField(iOGRFieldIdx, nYear, nMonth, nDay);
1090 : }
1091 0 : else if (sAttrDef.osVal.size() == 8 &&
1092 0 : sAttrDef.osVal[0] >= '0' && sAttrDef.osVal[0] <= '9' &&
1093 0 : sAttrDef.osVal[1] >= '0' && sAttrDef.osVal[1] <= '9' &&
1094 0 : sAttrDef.osVal[2] >= '0' && sAttrDef.osVal[2] <= '9' &&
1095 0 : sAttrDef.osVal[3] >= '0' && sAttrDef.osVal[3] <= '9' &&
1096 0 : sAttrDef.osVal[4] == '-' && sAttrDef.osVal[5] == '-' &&
1097 0 : sAttrDef.osVal[6] == '-' && sAttrDef.osVal[7] == '-')
1098 : {
1099 0 : const int nYear = (sAttrDef.osVal[0] - '0') * 1000 +
1100 0 : (sAttrDef.osVal[1] - '0') * 100 +
1101 0 : (sAttrDef.osVal[2] - '0') * 10 +
1102 0 : (sAttrDef.osVal[3] - '0');
1103 0 : if (cpl::ends_with(osAttrName, "dateEnd"))
1104 : {
1105 0 : oFeature.SetField(iOGRFieldIdx, nYear, 12, 31);
1106 : }
1107 : else
1108 : {
1109 0 : oFeature.SetField(iOGRFieldIdx, nYear, 1, 1);
1110 : }
1111 : }
1112 0 : break;
1113 : }
1114 :
1115 0 : case OFTTime:
1116 : {
1117 0 : if (sAttrDef.osVal.size() >= 6 &&
1118 0 : std::all_of(sAttrDef.osVal.begin(),
1119 0 : sAttrDef.osVal.begin() + 6,
1120 0 : [](char c) { return c >= '0' && c <= '9'; }))
1121 : {
1122 0 : const int nHour = (sAttrDef.osVal[0] - '0') * 10 +
1123 0 : (sAttrDef.osVal[1] - '0');
1124 0 : const int nMin = (sAttrDef.osVal[2] - '0') * 10 +
1125 0 : (sAttrDef.osVal[3] - '0');
1126 0 : const int nSec = (sAttrDef.osVal[4] - '0') * 10 +
1127 0 : (sAttrDef.osVal[5] - '0');
1128 0 : int nTZFlag = OGR_TZFLAG_UNKNOWN;
1129 0 : if (sAttrDef.osVal.size() == 7 && sAttrDef.osVal[6] == 'Z')
1130 0 : nTZFlag = OGR_TZFLAG_UTC;
1131 0 : else if (sAttrDef.osVal.size() == 11)
1132 : {
1133 0 : const int nTZHour = (sAttrDef.osVal[7] - '0') * 10 +
1134 0 : (sAttrDef.osVal[8] - '0');
1135 0 : const int nTZMin = (sAttrDef.osVal[9] - '0') * 10 +
1136 0 : (sAttrDef.osVal[10] - '0');
1137 0 : const int n15Minutes = (nTZHour * 60 + nTZMin) / 15;
1138 0 : if (sAttrDef.osVal[6] == '+')
1139 0 : nTZFlag = OGR_TZFLAG_UTC + n15Minutes;
1140 : else
1141 0 : nTZFlag = OGR_TZFLAG_UTC - n15Minutes;
1142 : }
1143 0 : oFeature.SetField(iOGRFieldIdx, 0, 0, 0, nHour, nMin,
1144 : static_cast<float>(nSec), nTZFlag);
1145 : }
1146 0 : break;
1147 : }
1148 :
1149 0 : default:
1150 0 : CPLAssert(false);
1151 : }
1152 : }
1153 :
1154 : // Set multi-valued fields.
1155 4752 : for (const auto &[iOGRFieldIdx, aosStrings] : stringListAttrs)
1156 0 : oFeature.SetField(static_cast<int>(iOGRFieldIdx), aosStrings.List());
1157 :
1158 4809 : for (const auto &[iOGRFieldIdx, anVals] : intListAttrs)
1159 114 : oFeature.SetField(static_cast<int>(iOGRFieldIdx),
1160 57 : static_cast<int>(anVals.size()), anVals.data());
1161 :
1162 4753 : for (const auto &[iOGRFieldIdx, adfVals] : doubleListAttrs)
1163 2 : oFeature.SetField(static_cast<int>(iOGRFieldIdx),
1164 1 : static_cast<int>(adfVals.size()), adfVals.data());
1165 :
1166 4752 : return true;
1167 : }
|