LCOV - code coverage report
Current view: top level - ogr/ogrsf_frmts/s101 - ogrs101readercompositecurve.cpp (source / functions) Hit Total Coverage
Test: gdal_filtered.info Lines: 176 183 96.2 %
Date: 2026-05-29 23:25:07 Functions: 8 8 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 <memory>
      17             : #include <set>
      18             : 
      19             : /************************************************************************/
      20             : /*                  CreateCompositeCurveFeatureDefn()                   */
      21             : /************************************************************************/
      22             : 
      23             : /** Create the feature definition for the CompositeCurve layer
      24             :  */
      25         344 : bool OGRS101Reader::CreateCompositeCurveFeatureDefn()
      26             : {
      27         344 :     if (m_oCompositeCurveRecordIndex.GetCount() > 0)
      28             :     {
      29             :         m_poFeatureDefnCompositeCurve =
      30         230 :             OGRFeatureDefnRefCountedPtr::makeInstance(
      31         115 :                 OGR_LAYER_NAME_COMPOSITE_CURVE);
      32         115 :         m_poFeatureDefnCompositeCurve->SetGeomType(wkbLineString);
      33         115 :         auto oSRSIter = m_oMapSRS.find(HORIZONTAL_CRS_ID);
      34         115 :         if (oSRSIter != m_oMapSRS.end())
      35             :         {
      36         230 :             m_poFeatureDefnCompositeCurve->GetGeomFieldDefn(0)->SetSpatialRef(
      37         230 :                 OGRSpatialReferenceRefCountedPtr::makeClone(&oSRSIter->second)
      38         115 :                     .get());
      39             :         }
      40         115 :         m_poFeatureDefnCompositeCurve->GetGeomFieldDefn(0)
      41         115 :             ->SetCoordinatePrecision(m_coordinatePrecision);
      42             :         {
      43         230 :             OGRFieldDefn oFieldDefn(OGR_FIELD_NAME_RECORD_ID, OFTInteger);
      44         115 :             m_poFeatureDefnCompositeCurve->AddFieldDefn(&oFieldDefn);
      45             :         }
      46             :         {
      47         230 :             OGRFieldDefn oFieldDefn(OGR_FIELD_NAME_RECORD_VERSION, OFTInteger);
      48         115 :             m_poFeatureDefnCompositeCurve->AddFieldDefn(&oFieldDefn);
      49             :         }
      50         115 :         if (!InferFeatureDefn(m_oCompositeCurveRecordIndex, CCID_FIELD,
      51             :                               INAS_FIELD, {}, *m_poFeatureDefnCompositeCurve,
      52         115 :                               m_oMapFieldDomains))
      53             :         {
      54           1 :             return false;
      55             :         }
      56             :     }
      57             : 
      58         343 :     return true;
      59             : }
      60             : 
      61             : /************************************************************************/
      62             : /*                     ReadCompositeCurveGeometry()                     */
      63             : /************************************************************************/
      64             : 
      65             : std::unique_ptr<OGRLineString>
      66        1054 : OGRS101Reader::ReadCompositeCurveGeometryInternal(
      67             :     const DDFRecord *poRecord, int iRecord, int nRecordID,
      68             :     const OGRSpatialReference *poSRS,
      69             :     std::set<int> &oSetAlreadyVisitedCompositeCurveRecords) const
      70             : {
      71        1054 :     if (nRecordID < 0)
      72         436 :         nRecordID = poRecord->GetIntSubfield(CCID_FIELD, 0, RCID_SUBFIELD, 0);
      73             : 
      74          46 :     const auto GetErrorContext = [iRecord, nRecordID]()
      75             :     {
      76          23 :         if (iRecord >= 0)
      77          18 :             return CPLSPrintf("Record index=%d of CCID", iRecord);
      78             :         else
      79           5 :             return CPLSPrintf("Record ID=%d of CCID", nRecordID);
      80        1054 :     };
      81             : 
      82             :     // The spec mentions that a composite curve may only refer to
      83             :     // another composite curve whose definition occurs before in the
      84             :     // file, which, if respected, effectively prevents circular
      85             :     // referencing from happening.
      86             :     // It is not practical for us to take note of the appearance order,
      87             :     // so use a set of record *ids* to detect that (so we are a bit
      88             :     // more permissive)
      89        1054 :     if (!oSetAlreadyVisitedCompositeCurveRecords.insert(nRecordID).second)
      90             :     {
      91           3 :         CPL_IGNORE_RET_VAL(EMIT_ERROR_OR_WARNING(CPLSPrintf(
      92             :             "%s: circular dependency on (RCNM=%d, RCID=%d) while "
      93             :             "resolving composite curve geometry.",
      94             :             GetErrorContext(), static_cast<int>(RECORD_NAME_COMPOSITE_CURVE),
      95             :             nRecordID)));
      96           3 :         return nullptr;
      97             :     }
      98             : 
      99        2102 :     const auto apoCUCOFields = poRecord->GetFields(CUCO_FIELD);
     100        1051 :     if (apoCUCOFields.empty())
     101             :     {
     102           1 :         CPL_IGNORE_RET_VAL(EMIT_ERROR_OR_WARNING(
     103             :             CPLSPrintf("%s: no CUCO field", GetErrorContext())));
     104           1 :         return nullptr;
     105             :     }
     106             : 
     107        2100 :     auto poLS = std::make_unique<OGRLineString>();
     108        2078 :     for (const auto *poCUCOField : apoCUCOFields)
     109             :     {
     110        1050 :         const int nParts = poCUCOField->GetRepeatCount();
     111        2214 :         for (int iPart = 0; iPart < nParts; ++iPart)
     112             :         {
     113             :             const auto GetIntSubfield =
     114        3541 :                 [poRecord, poCUCOField, iPart](const char *pszSubFieldName)
     115             :             {
     116        3541 :                 return poRecord->GetIntSubfield(poCUCOField, pszSubFieldName,
     117        3541 :                                                 iPart);
     118        1186 :             };
     119             : 
     120        1186 :             const RecordName nRRNM = GetIntSubfield(RRNM_SUBFIELD);
     121        1233 :             if (nRRNM != RECORD_NAME_CURVE &&
     122          47 :                 nRRNM != RECORD_NAME_COMPOSITE_CURVE)
     123             :             {
     124           7 :                 CPL_IGNORE_RET_VAL(EMIT_ERROR_OR_WARNING(CPLSPrintf(
     125             :                     "%s: Invalid value for RRNM "
     126             :                     "subfield of %d instance of CUCO field: "
     127             :                     "got %d, expected %d or %d.",
     128             :                     GetErrorContext(), iPart, static_cast<int>(nRRNM),
     129             :                     static_cast<int>(RECORD_NAME_CURVE),
     130             :                     static_cast<int>(RECORD_NAME_COMPOSITE_CURVE))));
     131          22 :                 return nullptr;
     132             :             }
     133             : 
     134        1179 :             bool bReverse = false;
     135        1179 :             const int nORNT = GetIntSubfield(ORNT_SUBFIELD);
     136        1179 :             if (nORNT == ORNT_REVERSE)
     137             :             {
     138          83 :                 bReverse = true;
     139             :             }
     140        1096 :             else if (nORNT != ORNT_FORWARD)
     141             :             {
     142           3 :                 CPL_IGNORE_RET_VAL(EMIT_ERROR_OR_WARNING(
     143             :                     CPLSPrintf("%s: Invalid value for ORNT "
     144             :                                "subfield of %d instance of CUCO field: "
     145             :                                "got %d, expected %d or %d.",
     146             :                                GetErrorContext(), iPart, nORNT, ORNT_FORWARD,
     147             :                                ORNT_REVERSE)));
     148           3 :                 return nullptr;
     149             :             }
     150             : 
     151        1176 :             const int nRRID = GetIntSubfield(RRID_SUBFIELD);
     152           0 :             std::unique_ptr<OGRLineString> poCurvePart;
     153        1176 :             if (nRRNM == RECORD_NAME_CURVE)
     154             :             {
     155             :                 const auto poCurveRecord =
     156        1136 :                     m_oCurveRecordIndex.FindRecord(nRRID);
     157        1136 :                 if (!poCurveRecord)
     158             :                 {
     159           3 :                     CPL_IGNORE_RET_VAL(EMIT_ERROR_OR_WARNING(CPLSPrintf(
     160             :                         "%s: Value (RRNM=%d, RRID=%d) "
     161             :                         "of instance %d of CUCO field does not point to an "
     162             :                         "existing record.",
     163             :                         GetErrorContext(), static_cast<int>(nRRNM), nRRID,
     164             :                         iPart)));
     165           3 :                     return nullptr;
     166             :                 }
     167             : 
     168        2266 :                 poCurvePart = ReadCurveGeometry(
     169        1133 :                     poCurveRecord, /* iRecord = */ -1, nRRID, poSRS);
     170             :             }
     171             :             else
     172             :             {
     173          40 :                 CPLAssert(nRRNM == RECORD_NAME_COMPOSITE_CURVE);
     174             : 
     175             :                 const auto poCompositeCurveRecord =
     176          40 :                     m_oCompositeCurveRecordIndex.FindRecord(nRRID);
     177          40 :                 if (!poCompositeCurveRecord)
     178             :                 {
     179           3 :                     CPL_IGNORE_RET_VAL(EMIT_ERROR_OR_WARNING(CPLSPrintf(
     180             :                         "%s: Value (RRNM=%d, RRID=%d) "
     181             :                         "of instance %d of CUCO field does not point to an "
     182             :                         "existing record.",
     183             :                         GetErrorContext(), static_cast<int>(nRRNM), nRRID,
     184             :                         iPart)));
     185           3 :                     return nullptr;
     186             :                 }
     187             : 
     188          74 :                 poCurvePart = ReadCompositeCurveGeometryInternal(
     189             :                     poCompositeCurveRecord, /* iRecord = */ -1, nRRID, poSRS,
     190          37 :                     oSetAlreadyVisitedCompositeCurveRecords);
     191             :             }
     192             : 
     193        1170 :             if (!poCurvePart)
     194             :             {
     195           3 :                 return nullptr;
     196             :             }
     197             : 
     198        1167 :             const int nPoints = poLS->getNumPoints();
     199        1167 :             const int nPointsPart = poCurvePart->getNumPoints();
     200        1167 :             if (nPointsPart == 0)
     201             :             {
     202           0 :                 CPL_IGNORE_RET_VAL(EMIT_ERROR_OR_WARNING(CPLSPrintf(
     203             :                     "%s: Value (RRNM=%d, RRID=%d) "
     204             :                     "%d instance of CUCO field points to an empty curve.",
     205             :                     GetErrorContext(), static_cast<int>(nRRNM), nRRID, iPart)));
     206           0 :                 return nullptr;
     207             :             }
     208        1167 :             if (nPoints == 0)
     209             :             {
     210        1031 :                 if (bReverse)
     211          43 :                     poLS->addSubLineString(poCurvePart.get(), nPointsPart - 1,
     212             :                                            0);
     213             :                 else
     214         988 :                     poLS->addSubLineString(poCurvePart.get());
     215             :             }
     216             :             else
     217             :             {
     218         136 :                 bool bEndPointMismatch = false;
     219         136 :                 if (bReverse)
     220             :                 {
     221          40 :                     if (poLS->getX(nPoints - 1) ==
     222          80 :                             poCurvePart->getX(nPointsPart - 1) &&
     223          40 :                         poLS->getY(nPoints - 1) ==
     224          40 :                             poCurvePart->getY(nPointsPart - 1))
     225             :                     {
     226          40 :                         poLS->addSubLineString(poCurvePart.get(),
     227             :                                                nPointsPart - 2, 0);
     228             :                     }
     229             :                     else
     230             :                     {
     231           0 :                         bEndPointMismatch = true;
     232             :                     }
     233             :                 }
     234             :                 else
     235             :                 {
     236         189 :                     if (poLS->getX(nPoints - 1) == poCurvePart->getX(0) &&
     237          93 :                         poLS->getY(nPoints - 1) == poCurvePart->getY(0))
     238             :                     {
     239          93 :                         poLS->addSubLineString(poCurvePart.get(), 1);
     240             :                     }
     241             :                     else
     242             :                     {
     243           3 :                         bEndPointMismatch = true;
     244             :                     }
     245             :                 }
     246         136 :                 if (bEndPointMismatch)
     247             :                 {
     248           3 :                     CPL_IGNORE_RET_VAL(EMIT_ERROR_OR_WARNING(CPLSPrintf(
     249             :                         "%s: Value (RRNM=%d, "
     250             :                         "RRID=%d) "
     251             :                         "of curve instance %d extremity does not match "
     252             :                         "composite curve extremity.",
     253             :                         GetErrorContext(), static_cast<int>(nRRNM), nRRID,
     254             :                         iPart)));
     255           3 :                     return nullptr;
     256             :                 }
     257             :             }
     258             :         }
     259             :     }
     260             : 
     261        1028 :     poLS->assignSpatialReference(poSRS);
     262        1028 :     return poLS;
     263             : }
     264             : 
     265        1017 : std::unique_ptr<OGRLineString> OGRS101Reader::ReadCompositeCurveGeometry(
     266             :     const DDFRecord *poRecord, int iRecord, int nRecordID,
     267             :     const OGRSpatialReference *poSRS) const
     268             : {
     269        2034 :     std::set<int> oSetAlreadyVisitedCompositeCurveRecords;
     270             :     return ReadCompositeCurveGeometryInternal(
     271             :         poRecord, iRecord, nRecordID, poSRS,
     272        2034 :         oSetAlreadyVisitedCompositeCurveRecords);
     273             : }
     274             : 
     275             : /************************************************************************/
     276             : /*                     FillFeatureCompositeCurve()                      */
     277             : /************************************************************************/
     278             : 
     279             : /** Fill the content of the provided feature from the identified record
     280             :  * (of m_oCompositeCurveRecordIndex).
     281             :  */
     282         436 : bool OGRS101Reader::FillFeatureCompositeCurve(const DDFRecordIndex &oIndex,
     283             :                                               int iRecord,
     284             :                                               OGRFeature &oFeature) const
     285             : {
     286         436 :     const auto poRecord = oIndex.GetByIndex(iRecord);
     287         436 :     CPLAssert(poRecord);
     288             : 
     289             :     const OGRSpatialReference *poSRS =
     290         436 :         oFeature.GetDefnRef()->GetGeomFieldDefn(0)->GetSpatialRef();
     291             :     auto poCurve = ReadCompositeCurveGeometry(poRecord, iRecord,
     292         872 :                                               /* nRecordID = */ -1, poSRS);
     293         436 :     if (!poCurve)
     294             :     {
     295          21 :         if (m_bStrict)
     296           7 :             return false;  // error message already emitted
     297             :     }
     298             :     else
     299             :     {
     300         415 :         oFeature.SetGeometry(std::move(poCurve));
     301             :     }
     302             : 
     303         858 :     return FillFeatureAttributes(oIndex, iRecord, INAS_FIELD, oFeature) &&
     304         429 :            FillFeatureWithNonAttrAssocSubfields(poRecord, iRecord, INAS_FIELD,
     305         429 :                                                 oFeature);
     306             : }
     307             : 
     308             : /************************************************************************/
     309             : /*                 ProcessUpdateRecordCompositeCurve()                  */
     310             : /************************************************************************/
     311             : 
     312             : /** Updates the geometry part of poTargetRecord with poUpdateRecord */
     313          36 : bool OGRS101Reader::ProcessUpdateRecordCompositeCurve(
     314             :     const DDFRecord *poUpdateRecord, DDFRecord *poTargetRecord) const
     315             : {
     316          36 :     const auto poIDField = poUpdateRecord->GetField(0);
     317          36 :     CPLAssert(poIDField);
     318             : 
     319             :     // Record name
     320             :     const RecordName nRCNM =
     321          36 :         poUpdateRecord->GetIntSubfield(poIDField, RCNM_SUBFIELD, 0);
     322             : 
     323             :     // Record identifier
     324             :     const int nRCID =
     325          36 :         poUpdateRecord->GetIntSubfield(poIDField, RCID_SUBFIELD, 0);
     326             : 
     327             :     // Composite Curve Control field
     328          36 :     const auto poControlField = poUpdateRecord->FindField(CCOC_FIELD);
     329          36 :     if (!poControlField)
     330           0 :         return true;
     331             : 
     332             :     // Curve Component update instruction
     333          36 :     constexpr const char *CCUI_SUBFIELD = "CCUI";
     334             :     const int nCCUI =
     335          36 :         poUpdateRecord->GetIntSubfield(poControlField, CCUI_SUBFIELD, 0);
     336          36 :     if (nCCUI != INSTRUCTION_INSERT && nCCUI != INSTRUCTION_DELETE &&
     337             :         nCCUI != INSTRUCTION_UPDATE)
     338             :     {
     339           2 :         return EMIT_ERROR_OR_WARNING(CPLSPrintf(
     340             :             "%s, RCNM=%d, RCID=%d: invalid CCUI = %d", m_osFilename.c_str(),
     341             :             static_cast<int>(nRCNM), nRCID, nCCUI));
     342             :     }
     343             : 
     344             :     // Curve Component index (1-based)
     345          34 :     constexpr const char *CCIX_SUBFIELD = "CCIX";
     346             :     const int nCCIX =
     347          34 :         poUpdateRecord->GetIntSubfield(poControlField, CCIX_SUBFIELD, 0);
     348             : 
     349             :     // Number of Curve Components
     350          34 :     constexpr const char *NCCO_SUBFIELD = "NCCO";
     351             :     const int nNCCO =
     352          34 :         poUpdateRecord->GetIntSubfield(poControlField, NCCO_SUBFIELD, 0);
     353             : 
     354          34 :     const auto poTargetField = poTargetRecord->FindField(CUCO_FIELD);
     355          34 :     if (!poTargetField)
     356             :     {
     357           0 :         return EMIT_ERROR_OR_WARNING(
     358             :             CPLSPrintf("%s, RCNM=%d, RCID=%d: missing CUCO field in "
     359             :                        "target record",
     360             :                        m_osFilename.c_str(), static_cast<int>(nRCNM), nRCID));
     361             :     }
     362          34 :     const auto poUpdateField = poUpdateRecord->FindField(CUCO_FIELD);
     363             : 
     364         102 :     if (poUpdateRecord->GetFields(CUCO_FIELD).size() > 1 ||
     365          68 :         poTargetRecord->GetFields(CUCO_FIELD).size() > 1)
     366             :     {
     367           0 :         return EMIT_ERROR_OR_WARNING(
     368             :             CPLSPrintf("%s: only one instance of CUCO supported for update",
     369             :                        m_osFilename.c_str()));
     370             :     }
     371             : 
     372          34 :     const int nTargetRepeatCount = poTargetField->GetRepeatCount();
     373             : 
     374             :     // Check that start index and count is consistent with update and
     375             :     // target list of curve components
     376          34 :     const int nMaxCCIXAllowed =
     377          34 :         nTargetRepeatCount + (nCCUI == INSTRUCTION_INSERT ? 1 : 0);
     378          34 :     if (nCCIX <= 0 || nCCIX > nMaxCCIXAllowed)
     379             :     {
     380           6 :         return EMIT_ERROR_OR_WARNING(CPLSPrintf(
     381             :             "%s, RCNM=%d, RCID=%d: invalid CCIX = %d. Must be in [1,%d].",
     382             :             m_osFilename.c_str(), static_cast<int>(nRCNM), nRCID, nCCIX,
     383             :             nMaxCCIXAllowed));
     384             :     }
     385             : 
     386             :     const int nUpdateRepeatCount =
     387          28 :         !poUpdateField ? 0 : poUpdateField->GetRepeatCount();
     388             : 
     389          28 :     if (poUpdateField && nNCCO != nUpdateRepeatCount)
     390             :     {
     391           2 :         return EMIT_ERROR_OR_WARNING(
     392             :             CPLSPrintf("%s, RCNM=%d, RCID=%d: invalid NCCO = %d. Expected %d",
     393             :                        m_osFilename.c_str(), static_cast<int>(nRCNM), nRCID,
     394             :                        nNCCO, nUpdateRepeatCount));
     395             :     }
     396          28 :     else if (poUpdateField && nCCUI == INSTRUCTION_DELETE &&
     397           2 :              !EMIT_ERROR_OR_WARNING(CPLSPrintf(
     398             :                  "%s, RCNM=%d, RCID=%d: unexpected CUCO field in "
     399             :                  "update record in CCUI = %d (delete) mode",
     400             :                  m_osFilename.c_str(), static_cast<int>(nRCNM), nRCID, nCCUI)))
     401             :     {
     402           1 :         return false;
     403             :     }
     404             : 
     405          25 :     if (nCCUI == INSTRUCTION_UPDATE || nCCUI == INSTRUCTION_DELETE)
     406             :     {
     407          18 :         if (nCCIX + nNCCO > nTargetRepeatCount + 1)
     408             :         {
     409           2 :             return EMIT_ERROR_OR_WARNING(CPLSPrintf(
     410             :                 "%s, RCNM=%d, RCID=%d: invalid CCIX = %d and/or NCCO=%d",
     411             :                 m_osFilename.c_str(), static_cast<int>(nRCNM), nRCID, nCCIX,
     412             :                 nNCCO));
     413             :         }
     414             :     }
     415             : 
     416             :     struct CurveComponent
     417             :     {
     418             :         int RRNM = 0;
     419             :         int RRID = 0;
     420             :         int ORNT = 0;
     421             : 
     422          65 :         void Read(const DDFRecord *poRecord, const DDFField *poField, int i)
     423             :         {
     424          65 :             RRNM = poRecord->GetIntSubfield(poField, RRNM_SUBFIELD, i);
     425          65 :             RRID = poRecord->GetIntSubfield(poField, RRID_SUBFIELD, i);
     426          65 :             ORNT = poRecord->GetIntSubfield(poField, ORNT_SUBFIELD, i);
     427          65 :         }
     428             :     };
     429             : 
     430          46 :     std::vector<CurveComponent> asTarget;
     431             :     // Ingest the existing/target record
     432          74 :     for (int i = 0; i < nTargetRepeatCount; ++i)
     433             :     {
     434          51 :         CurveComponent cc;
     435          51 :         cc.Read(poTargetRecord, poTargetField, i);
     436          51 :         asTarget.push_back(cc);
     437             :     }
     438             : 
     439             :     // Apply the update record
     440          46 :     std::vector<CurveComponent> asUpdate;
     441          23 :     if (poUpdateField)
     442             :     {
     443          28 :         for (int i = 0; i < nUpdateRepeatCount; ++i)
     444             :         {
     445          14 :             CurveComponent cc;
     446          14 :             cc.Read(poUpdateRecord, poUpdateField, i);
     447          14 :             asUpdate.push_back(cc);
     448             :         }
     449             :     }
     450             : 
     451          23 :     const auto oTargetBeginIter = asTarget.begin() + (nCCIX - 1);
     452          23 :     if (nCCUI == INSTRUCTION_INSERT)
     453             :     {
     454           7 :         asTarget.insert(oTargetBeginIter, asUpdate.begin(), asUpdate.end());
     455             :     }
     456          16 :     else if (nCCUI == INSTRUCTION_UPDATE)
     457             :     {
     458           6 :         std::copy(asUpdate.begin(), asUpdate.end(), oTargetBeginIter);
     459             :     }
     460             :     else
     461             :     {
     462          10 :         CPLAssert(nCCUI == INSTRUCTION_DELETE);
     463          10 :         asTarget.erase(oTargetBeginIter, oTargetBeginIter + nNCCO);
     464             :     }
     465             : 
     466             :     // Compose raw target field
     467          23 :     std::string s;
     468          71 :     for (const auto &cc : asTarget)
     469             :     {
     470          48 :         AppendUInt8(s, static_cast<uint8_t>(cc.RRNM));
     471          48 :         AppendInt32(s, cc.RRID);
     472          48 :         AppendUInt8(s, static_cast<uint8_t>(cc.ORNT));
     473             :     }
     474          23 :     AppendUInt8(s, DDF_FIELD_TERMINATOR);
     475             : 
     476          23 :     poTargetRecord->SetFieldRaw(poTargetField, s.data(),
     477          23 :                                 static_cast<int>(s.size()));
     478             : 
     479          23 :     return true;
     480             : }

Generated by: LCOV version 1.14