LCOV - code coverage report
Current view: top level - ogr/ogrsf_frmts/s101 - ogrs101readerdatasetgeneralinformationrecord.cpp (source / functions) Hit Total Coverage
Test: gdal_filtered.info Lines: 314 383 82.0 %
Date: 2026-05-29 23:25:07 Functions: 24 24 100.0 %

          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             : }

Generated by: LCOV version 1.14