LCOV - code coverage report
Current view: top level - ogr/ogrsf_frmts/s101 - ogrs101readercompositecurve.cpp (source / functions) Hit Total Coverage
Test: gdal_filtered.info Lines: 105 112 93.8 %
Date: 2026-05-08 18:52:02 Functions: 6 6 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         248 : bool OGRS101Reader::CreateCompositeCurveFeatureDefn()
      26             : {
      27         248 :     if (m_oCompositeCurveRecordIndex.GetCount() > 0)
      28             :     {
      29             :         m_poFeatureDefnCompositeCurve =
      30         174 :             OGRFeatureDefnRefCountedPtr::makeInstance(
      31          87 :                 OGR_LAYER_NAME_COMPOSITE_CURVE);
      32          87 :         m_poFeatureDefnCompositeCurve->SetGeomType(wkbLineString);
      33          87 :         auto oSRSIter = m_oMapSRS.find(HORIZONTAL_CRS_ID);
      34          87 :         if (oSRSIter != m_oMapSRS.end())
      35             :         {
      36         174 :             m_poFeatureDefnCompositeCurve->GetGeomFieldDefn(0)->SetSpatialRef(
      37         174 :                 OGRSpatialReferenceRefCountedPtr::makeClone(&oSRSIter->second)
      38          87 :                     .get());
      39             :         }
      40             :         {
      41         174 :             OGRFieldDefn oFieldDefn(OGR_FIELD_NAME_RECORD_ID, OFTInteger);
      42          87 :             m_poFeatureDefnCompositeCurve->AddFieldDefn(&oFieldDefn);
      43             :         }
      44             :         {
      45         174 :             OGRFieldDefn oFieldDefn(OGR_FIELD_NAME_RECORD_VERSION, OFTInteger);
      46          87 :             m_poFeatureDefnCompositeCurve->AddFieldDefn(&oFieldDefn);
      47             :         }
      48          87 :         if (!InferFeatureDefn(m_oCompositeCurveRecordIndex, CCID_FIELD,
      49             :                               INAS_FIELD, {}, *m_poFeatureDefnCompositeCurve,
      50          87 :                               m_oMapFieldDomains))
      51             :         {
      52           1 :             return false;
      53             :         }
      54             :     }
      55             : 
      56         247 :     return true;
      57             : }
      58             : 
      59             : /************************************************************************/
      60             : /*                     ReadCompositeCurveGeometry()                     */
      61             : /************************************************************************/
      62             : 
      63             : std::unique_ptr<OGRLineString>
      64         762 : OGRS101Reader::ReadCompositeCurveGeometryInternal(
      65             :     const DDFRecord *poRecord, int iRecord, int nRecordID,
      66             :     const OGRSpatialReference *poSRS,
      67             :     std::set<int> &oSetAlreadyVisitedCompositeCurveRecords) const
      68             : {
      69         762 :     if (nRecordID < 0)
      70         300 :         nRecordID = poRecord->GetIntSubfield(CCID_FIELD, 0, RCID_SUBFIELD, 0);
      71             : 
      72          40 :     const auto GetErrorContext = [iRecord, nRecordID]()
      73             :     {
      74          20 :         if (iRecord >= 0)
      75          18 :             return CPLSPrintf("Record index=%d of CCID", iRecord);
      76             :         else
      77           2 :             return CPLSPrintf("Record ID=%d of CCID", nRecordID);
      78         762 :     };
      79             : 
      80             :     // The spec mentions that a composite curve may only refer to
      81             :     // another composite curve whose definition occurs before in the
      82             :     // file, which, if respected, effectively prevents circular
      83             :     // referencing from happening.
      84             :     // It is not practical for us to take note of the appearance order,
      85             :     // so use a set of record *ids* to detect that (so we are a bit
      86             :     // more permissive)
      87         762 :     if (!oSetAlreadyVisitedCompositeCurveRecords.insert(nRecordID).second)
      88             :     {
      89           0 :         CPL_IGNORE_RET_VAL(EMIT_ERROR_OR_WARNING(CPLSPrintf(
      90             :             "%s: circular dependency on (RCNM=%d, RCID=%d) while "
      91             :             "resolving composite curve geometry.",
      92             :             GetErrorContext(), static_cast<int>(RECORD_NAME_COMPOSITE_CURVE),
      93             :             nRecordID)));
      94           0 :         return nullptr;
      95             :     }
      96             : 
      97        1524 :     const auto apoCUCOFields = poRecord->GetFields(CUCO_FIELD);
      98         762 :     if (apoCUCOFields.empty())
      99             :     {
     100           1 :         CPL_IGNORE_RET_VAL(EMIT_ERROR_OR_WARNING(
     101             :             CPLSPrintf("%s: no CUCO field", GetErrorContext())));
     102           1 :         return nullptr;
     103             :     }
     104             : 
     105        1522 :     auto poLS = std::make_unique<OGRLineString>();
     106        1503 :     for (const auto *poCUCOField : apoCUCOFields)
     107             :     {
     108         761 :         const int nParts = poCUCOField->GetRepeatCount();
     109        1591 :         for (int iPart = 0; iPart < nParts; ++iPart)
     110             :         {
     111             :             const auto GetIntSubfield =
     112        2530 :                 [poRecord, poCUCOField, iPart](const char *pszSubFieldName)
     113             :             {
     114        2530 :                 return poRecord->GetIntSubfield(poCUCOField, pszSubFieldName,
     115        2530 :                                                 iPart);
     116         849 :             };
     117             : 
     118         849 :             const RecordName nRRNM = GetIntSubfield(RRNM_SUBFIELD);
     119         893 :             if (nRRNM != RECORD_NAME_CURVE &&
     120          44 :                 nRRNM != RECORD_NAME_COMPOSITE_CURVE)
     121             :             {
     122           7 :                 CPL_IGNORE_RET_VAL(EMIT_ERROR_OR_WARNING(CPLSPrintf(
     123             :                     "%s: Invalid value for RRNM "
     124             :                     "subfield of %d instance of CUCO field: "
     125             :                     "got %d, expected %d or %d.",
     126             :                     GetErrorContext(), iPart, static_cast<int>(nRRNM),
     127             :                     static_cast<int>(RECORD_NAME_CURVE),
     128             :                     static_cast<int>(RECORD_NAME_COMPOSITE_CURVE))));
     129          19 :                 return nullptr;
     130             :             }
     131             : 
     132         842 :             bool bReverse = false;
     133         842 :             const int nORNT = GetIntSubfield(ORNT_SUBFIELD);
     134         842 :             if (nORNT == ORNT_REVERSE)
     135             :             {
     136          77 :                 bReverse = true;
     137             :             }
     138         765 :             else if (nORNT != ORNT_FORWARD)
     139             :             {
     140           3 :                 CPL_IGNORE_RET_VAL(EMIT_ERROR_OR_WARNING(
     141             :                     CPLSPrintf("%s: Invalid value for ORNT "
     142             :                                "subfield of %d instance of CUCO field: "
     143             :                                "got %d, expected %d or %d.",
     144             :                                GetErrorContext(), iPart, nORNT, ORNT_FORWARD,
     145             :                                ORNT_REVERSE)));
     146           3 :                 return nullptr;
     147             :             }
     148             : 
     149         839 :             const int nRRID = GetIntSubfield(RRID_SUBFIELD);
     150           0 :             std::unique_ptr<OGRLineString> poCurvePart;
     151         839 :             if (nRRNM == RECORD_NAME_CURVE)
     152             :             {
     153             :                 const auto poCurveRecord =
     154         802 :                     m_oCurveRecordIndex.FindRecord(nRRID);
     155         802 :                 if (!poCurveRecord)
     156             :                 {
     157           3 :                     CPL_IGNORE_RET_VAL(EMIT_ERROR_OR_WARNING(CPLSPrintf(
     158             :                         "%s: Value (RRNM=%d, RRID=%d) "
     159             :                         "of instance %d of CUCO field does not point to an "
     160             :                         "existing record.",
     161             :                         GetErrorContext(), static_cast<int>(nRRNM), nRRID,
     162             :                         iPart)));
     163           3 :                     return nullptr;
     164             :                 }
     165             : 
     166        1598 :                 poCurvePart = ReadCurveGeometry(
     167         799 :                     poCurveRecord, /* iRecord = */ -1, nRRID, poSRS);
     168             :             }
     169             :             else
     170             :             {
     171          37 :                 CPLAssert(nRRNM == RECORD_NAME_COMPOSITE_CURVE);
     172             : 
     173             :                 const auto poCompositeCurveRecord =
     174          37 :                     m_oCompositeCurveRecordIndex.FindRecord(nRRID);
     175          37 :                 if (!poCompositeCurveRecord)
     176             :                 {
     177           3 :                     CPL_IGNORE_RET_VAL(EMIT_ERROR_OR_WARNING(CPLSPrintf(
     178             :                         "%s: Value (RRNM=%d, RRID=%d) "
     179             :                         "of instance %d of CUCO field does not point to an "
     180             :                         "existing record.",
     181             :                         GetErrorContext(), static_cast<int>(nRRNM), nRRID,
     182             :                         iPart)));
     183           3 :                     return nullptr;
     184             :                 }
     185             : 
     186          68 :                 poCurvePart = ReadCompositeCurveGeometryInternal(
     187             :                     poCompositeCurveRecord, /* iRecord = */ -1, nRRID, poSRS,
     188          34 :                     oSetAlreadyVisitedCompositeCurveRecords);
     189             :             }
     190             : 
     191         833 :             if (!poCurvePart)
     192             :             {
     193           0 :                 return nullptr;
     194             :             }
     195             : 
     196         833 :             const int nPoints = poLS->getNumPoints();
     197         833 :             const int nPointsPart = poCurvePart->getNumPoints();
     198         833 :             if (nPointsPart == 0)
     199             :             {
     200           0 :                 CPL_IGNORE_RET_VAL(EMIT_ERROR_OR_WARNING(CPLSPrintf(
     201             :                     "%s: Value (RRNM=%d, RRID=%d) "
     202             :                     "%d instance of CUCO field points to an empty curve.",
     203             :                     GetErrorContext(), static_cast<int>(nRRNM), nRRID, iPart)));
     204           0 :                 return nullptr;
     205             :             }
     206         833 :             if (nPoints == 0)
     207             :             {
     208         745 :                 if (bReverse)
     209          40 :                     poLS->addSubLineString(poCurvePart.get(), nPointsPart - 1,
     210             :                                            0);
     211             :                 else
     212         705 :                     poLS->addSubLineString(poCurvePart.get());
     213             :             }
     214             :             else
     215             :             {
     216          88 :                 bool bEndPointMismatch = false;
     217          88 :                 if (bReverse)
     218             :                 {
     219          37 :                     if (poLS->getX(nPoints - 1) ==
     220          74 :                             poCurvePart->getX(nPointsPart - 1) &&
     221          37 :                         poLS->getY(nPoints - 1) ==
     222          37 :                             poCurvePart->getY(nPointsPart - 1))
     223             :                     {
     224          37 :                         poLS->addSubLineString(poCurvePart.get(),
     225             :                                                nPointsPart - 2, 0);
     226             :                     }
     227             :                     else
     228             :                     {
     229           0 :                         bEndPointMismatch = true;
     230             :                     }
     231             :                 }
     232             :                 else
     233             :                 {
     234          99 :                     if (poLS->getX(nPoints - 1) == poCurvePart->getX(0) &&
     235          48 :                         poLS->getY(nPoints - 1) == poCurvePart->getY(0))
     236             :                     {
     237          48 :                         poLS->addSubLineString(poCurvePart.get(), 1);
     238             :                     }
     239             :                     else
     240             :                     {
     241           3 :                         bEndPointMismatch = true;
     242             :                     }
     243             :                 }
     244          88 :                 if (bEndPointMismatch)
     245             :                 {
     246           3 :                     CPL_IGNORE_RET_VAL(EMIT_ERROR_OR_WARNING(CPLSPrintf(
     247             :                         "%s: Value (RRNM=%d, "
     248             :                         "RRID=%d) "
     249             :                         "of curve instance %d extremity does not match "
     250             :                         "composite curve extremity.",
     251             :                         GetErrorContext(), static_cast<int>(nRRNM), nRRID,
     252             :                         iPart)));
     253           3 :                     return nullptr;
     254             :                 }
     255             :             }
     256             :         }
     257             :     }
     258             : 
     259         742 :     poLS->assignSpatialReference(poSRS);
     260         742 :     return poLS;
     261             : }
     262             : 
     263         728 : std::unique_ptr<OGRLineString> OGRS101Reader::ReadCompositeCurveGeometry(
     264             :     const DDFRecord *poRecord, int iRecord, int nRecordID,
     265             :     const OGRSpatialReference *poSRS) const
     266             : {
     267        1456 :     std::set<int> oSetAlreadyVisitedCompositeCurveRecords;
     268             :     return ReadCompositeCurveGeometryInternal(
     269             :         poRecord, iRecord, nRecordID, poSRS,
     270        1456 :         oSetAlreadyVisitedCompositeCurveRecords);
     271             : }
     272             : 
     273             : /************************************************************************/
     274             : /*                     FillFeatureCompositeCurve()                      */
     275             : /************************************************************************/
     276             : 
     277             : /** Fill the content of the provided feature from the identified record
     278             :  * (of m_oCompositeCurveRecordIndex).
     279             :  */
     280         300 : bool OGRS101Reader::FillFeatureCompositeCurve(const DDFRecordIndex &oIndex,
     281             :                                               int iRecord,
     282             :                                               OGRFeature &oFeature) const
     283             : {
     284         300 :     const auto poRecord = oIndex.GetByIndex(iRecord);
     285         300 :     CPLAssert(poRecord);
     286             : 
     287             :     const OGRSpatialReference *poSRS =
     288         300 :         oFeature.GetDefnRef()->GetGeomFieldDefn(0)->GetSpatialRef();
     289             :     auto poCurve = ReadCompositeCurveGeometry(poRecord, iRecord,
     290         600 :                                               /* nRecordID = */ -1, poSRS);
     291         300 :     if (!poCurve)
     292             :     {
     293          18 :         if (m_bStrict)
     294           6 :             return false;  // error message already emitted
     295             :     }
     296             :     else
     297             :     {
     298         282 :         oFeature.SetGeometry(std::move(poCurve));
     299             :     }
     300             : 
     301         588 :     return FillFeatureAttributes(oIndex, iRecord, INAS_FIELD, oFeature) &&
     302         294 :            FillFeatureWithNonAttrAssocSubfields(poRecord, iRecord, INAS_FIELD,
     303         294 :                                                 oFeature);
     304             : }

Generated by: LCOV version 1.14