LCOV - code coverage report
Current view: top level - ogr/ogrsf_frmts/s101 - ogrs101readercurve.cpp (source / functions) Hit Total Coverage
Test: gdal_filtered.info Lines: 140 144 97.2 %
Date: 2026-05-29 23:25:07 Functions: 5 5 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             : 
      18             : /************************************************************************/
      19             : /*                       CreateCurveFeatureDefn()                       */
      20             : /************************************************************************/
      21             : 
      22             : /** Create the feature definition for the Curve layer
      23             :  */
      24         345 : bool OGRS101Reader::CreateCurveFeatureDefn()
      25             : {
      26         345 :     if (m_oCurveRecordIndex.GetCount() > 0)
      27             :     {
      28             :         m_poFeatureDefnCurve =
      29         151 :             OGRFeatureDefnRefCountedPtr::makeInstance(OGR_LAYER_NAME_CURVE);
      30         151 :         m_poFeatureDefnCurve->SetGeomType(wkbLineString);
      31         151 :         auto oSRSIter = m_oMapSRS.find(HORIZONTAL_CRS_ID);
      32         151 :         if (oSRSIter != m_oMapSRS.end())
      33             :         {
      34         302 :             m_poFeatureDefnCurve->GetGeomFieldDefn(0)->SetSpatialRef(
      35         302 :                 OGRSpatialReferenceRefCountedPtr::makeClone(&oSRSIter->second)
      36         151 :                     .get());
      37             :         }
      38         151 :         m_poFeatureDefnCurve->GetGeomFieldDefn(0)->SetCoordinatePrecision(
      39         151 :             m_coordinatePrecision);
      40             :         {
      41         302 :             OGRFieldDefn oFieldDefn(OGR_FIELD_NAME_RECORD_ID, OFTInteger);
      42         151 :             m_poFeatureDefnCurve->AddFieldDefn(&oFieldDefn);
      43             :         }
      44             :         {
      45         302 :             OGRFieldDefn oFieldDefn(OGR_FIELD_NAME_RECORD_VERSION, OFTInteger);
      46         151 :             m_poFeatureDefnCurve->AddFieldDefn(&oFieldDefn);
      47             :         }
      48         151 :         if (!InferFeatureDefn(m_oCurveRecordIndex, CRID_FIELD, INAS_FIELD, {},
      49         151 :                               *m_poFeatureDefnCurve, m_oMapFieldDomains))
      50             :         {
      51           1 :             return false;
      52             :         }
      53             :     }
      54             : 
      55         344 :     return true;
      56             : }
      57             : 
      58             : /************************************************************************/
      59             : /*                         ReadCurveGeometry()                          */
      60             : /************************************************************************/
      61             : 
      62             : std::unique_ptr<OGRLineString>
      63        3027 : OGRS101Reader::ReadCurveGeometry(const DDFRecord *poRecord, int iRecord,
      64             :                                  int nRecordID,
      65             :                                  const OGRSpatialReference *poSRS) const
      66             : {
      67        3027 :     const DDFField *poSEGHField = poRecord->FindField(SEGH_FIELD);
      68        3027 :     if (poSEGHField)
      69             :     {
      70        3027 :         constexpr const char *INTP_SUBFIELD = "INTP";
      71             :         const int nINTP =
      72        3027 :             poRecord->GetIntSubfield(SEGH_FIELD, 0, INTP_SUBFIELD, 0);
      73        3027 :         constexpr int INTERPOLATION_LOXODROMIC = 4;
      74        3035 :         if (nINTP != INTERPOLATION_LOXODROMIC &&
      75           8 :             !EMIT_ERROR_OR_WARNING(
      76             :                 CPLSPrintf("Record index %d of CRID: Invalid value for INTP "
      77             :                            "subfield of SEGH field: "
      78             :                            "got %d, expected %d.",
      79             :                            iRecord, nINTP, INTERPOLATION_LOXODROMIC)))
      80             :         {
      81           2 :             return nullptr;
      82             :         }
      83             :     }
      84             : 
      85        6050 :     const auto apoCoordFields = poRecord->GetFields(C2IL_FIELD);
      86        3025 :     if (apoCoordFields.empty())
      87           0 :         return nullptr;
      88             : 
      89        6050 :     auto poLS = std::make_unique<OGRLineString>();
      90        3025 :     poLS->assignSpatialReference(poSRS);
      91        3025 :     int nCoordCount = 0;
      92        6050 :     for (const auto *poCoordField : apoCoordFields)
      93             :     {
      94        3025 :         const int nCoordFieldCount = poCoordField->GetRepeatCount();
      95        3025 :         nCoordCount += nCoordFieldCount;
      96             :     }
      97        3025 :     poLS->setNumPoints(nCoordCount);
      98        3025 :     int iPnt = 0;
      99        6049 :     for (const auto *poCoordField : apoCoordFields)
     100             :     {
     101        3025 :         const int nCoordFieldCount = poCoordField->GetRepeatCount();
     102       16268 :         for (int iPntThisField = 0; iPntThisField < nCoordFieldCount;
     103             :              ++iPntThisField)
     104             :         {
     105             :             auto poPoint = ReadPointGeometryInternal(
     106             :                 poRecord, iRecord, nRecordID, iPntThisField, poSRS,
     107       13244 :                 /* bIs3D = */ false, poCoordField, CRID_FIELD);
     108       13244 :             if (!poPoint)
     109           1 :                 return nullptr;
     110       13243 :             poLS->setPoint(iPnt, poPoint.get());
     111       13243 :             ++iPnt;
     112             :         }
     113             :     }
     114             : 
     115        3024 :     const DDFField *poPTASField = poRecord->FindField(PTAS_FIELD);
     116        3024 :     if (poPTASField)
     117             :     {
     118        3024 :         const int nPTASMembers = poPTASField->GetRepeatCount();
     119        3024 :         constexpr const char *TOPI_SUBFIELD = "TOPI";
     120        3024 :         if (nPTASMembers == 1 || nPTASMembers == 2)
     121             :         {
     122        6845 :             for (int iPTAS = 0; iPTAS < nPTASMembers; ++iPTAS)
     123             :             {
     124             :                 const auto GetIntSubfield =
     125       11488 :                     [poRecord, poPTASField, iPTAS](const char *pszSubFieldName)
     126             :                 {
     127       11488 :                     return poRecord->GetIntSubfield(poPTASField,
     128       11488 :                                                     pszSubFieldName, iPTAS);
     129        3831 :                 };
     130             : 
     131        3831 :                 const RecordName nRRNM = GetIntSubfield(RRNM_SUBFIELD);
     132        3831 :                 constexpr RecordName nExpectedRRNM = RECORD_NAME_POINT;
     133        3834 :                 if (nRRNM != nExpectedRRNM &&
     134           3 :                     !EMIT_ERROR_OR_WARNING(CPLSPrintf(
     135             :                         "Record index %d of CRID: Invalid value for RRNM "
     136             :                         "subfield of %d instance of PTAS field: "
     137             :                         "got %d, expected %d.",
     138             :                         iRecord, iPTAS, static_cast<int>(nRRNM),
     139             :                         static_cast<int>(nExpectedRRNM))))
     140             :                 {
     141           7 :                     return nullptr;
     142             :                 }
     143             : 
     144        3830 :                 const int nTOPI = GetIntSubfield(TOPI_SUBFIELD);
     145        3830 :                 constexpr int TOPOLOGY_INDICATOR_BEGINNING_POINT = 1;
     146        3830 :                 constexpr int TOPOLOGY_INDICATOR_END_POINT = 2;
     147        3830 :                 constexpr int TOPOLOGY_INDICATOR_BEGINNING_AND_END_POINT = 3;
     148        3830 :                 const int nExpectedTOPI =
     149             :                     nPTASMembers == 1
     150        3830 :                         ? TOPOLOGY_INDICATOR_BEGINNING_AND_END_POINT
     151             :                     : iPTAS == 0 ? TOPOLOGY_INDICATOR_BEGINNING_POINT
     152             :                                  : TOPOLOGY_INDICATOR_END_POINT;
     153             : 
     154        3839 :                 if (nTOPI != nExpectedTOPI &&
     155           9 :                     !EMIT_ERROR_OR_WARNING(CPLSPrintf(
     156             :                         "Record index %d of CRID: Invalid value for TOPI "
     157             :                         "subfield of %d instance of PTAS field: "
     158             :                         "got %d, expected %d.",
     159             :                         iRecord, iPTAS, nTOPI, nExpectedTOPI)))
     160             :                 {
     161           3 :                     return nullptr;
     162             :                 }
     163             : 
     164        3827 :                 const int nRRID = GetIntSubfield(RRID_SUBFIELD);
     165             :                 const auto poPointRecord =
     166        3827 :                     m_oPointRecordIndex.FindRecord(nRRID);
     167        3830 :                 if (!poPointRecord &&
     168           3 :                     !EMIT_ERROR_OR_WARNING(CPLSPrintf(
     169             :                         "Record index %d of CRID: No point record matching "
     170             :                         "RRID=%d value of %d instance of PTAS field.",
     171             :                         iRecord, nRRID, iPTAS)))
     172             :                 {
     173           1 :                     return nullptr;
     174             :                 }
     175        3826 :                 if (poPointRecord)
     176             :                 {
     177        3824 :                     if (nTOPI == TOPOLOGY_INDICATOR_BEGINNING_POINT ||
     178             :                         nTOPI == TOPOLOGY_INDICATOR_BEGINNING_AND_END_POINT)
     179             :                     {
     180        3011 :                         if (poPointRecord->GetIntSubfield(C2IT_FIELD, 0,
     181             :                                                           XCOO_SUBFIELD, 0) !=
     182        3011 :                                 poRecord->GetIntSubfield(C2IL_FIELD, 0,
     183        6012 :                                                          XCOO_SUBFIELD, 0) ||
     184        3001 :                             poPointRecord->GetIntSubfield(C2IT_FIELD, 0,
     185             :                                                           YCOO_SUBFIELD, 0) !=
     186        3001 :                                 poRecord->GetIntSubfield(C2IL_FIELD, 0,
     187             :                                                          YCOO_SUBFIELD, 0))
     188             :                         {
     189          15 :                             if (!EMIT_ERROR_OR_WARNING(CPLSPrintf(
     190             :                                     "Record index %d of CRID: Point record %d "
     191             :                                     "pointed by %d instance of PTAS field does "
     192             :                                     "not match first point of curve.",
     193             :                                     iRecord, nRRID, iPTAS)))
     194             :                             {
     195           1 :                                 return nullptr;
     196             :                             }
     197             :                         }
     198             :                     }
     199             : 
     200        3823 :                     if (nTOPI == TOPOLOGY_INDICATOR_END_POINT ||
     201             :                         nTOPI == TOPOLOGY_INDICATOR_BEGINNING_AND_END_POINT)
     202             :                     {
     203        3011 :                         if (poPointRecord->GetIntSubfield(C2IT_FIELD, 0,
     204             :                                                           XCOO_SUBFIELD, 0) !=
     205        3011 :                                 poRecord->GetIntSubfield(C2IL_FIELD, 0,
     206             :                                                          XCOO_SUBFIELD,
     207        6013 :                                                          nCoordCount - 1) ||
     208        3002 :                             poPointRecord->GetIntSubfield(C2IT_FIELD, 0,
     209             :                                                           YCOO_SUBFIELD, 0) !=
     210        3002 :                                 poRecord->GetIntSubfield(C2IL_FIELD, 0,
     211             :                                                          YCOO_SUBFIELD,
     212             :                                                          nCoordCount - 1))
     213             :                         {
     214          12 :                             if (!EMIT_ERROR_OR_WARNING(CPLSPrintf(
     215             :                                     "Record index %d of CRID: Point record %d "
     216             :                                     "pointed by %d instance of PTAS field does "
     217             :                                     "not match end point of curve.",
     218             :                                     iRecord, nRRID, iPTAS)))
     219             :                             {
     220           1 :                                 return nullptr;
     221             :                             }
     222             :                         }
     223             :                     }
     224             :                 }
     225        3014 :             }
     226             :         }
     227             :         else
     228             :         {
     229           3 :             if (!EMIT_ERROR_OR_WARNING(CPLSPrintf(
     230             :                     "Record index %d of CRID: Invalid repeat count for "
     231             :                     "PTAS field: got %d, expected 1 or 2.",
     232             :                     iRecord, nPTASMembers)))
     233             :             {
     234           1 :                 return nullptr;
     235             :             }
     236             :         }
     237             :     }
     238             : 
     239        3016 :     return poLS;
     240             : }
     241             : 
     242             : /************************************************************************/
     243             : /*                          FillFeatureCurve()                          */
     244             : /************************************************************************/
     245             : 
     246             : /** Fill the content of the provided feature from the identified record
     247             :  * (of m_oCurveRecordIndex).
     248             :  */
     249         908 : bool OGRS101Reader::FillFeatureCurve(const DDFRecordIndex &oIndex, int iRecord,
     250             :                                      OGRFeature &oFeature) const
     251             : {
     252         908 :     const auto poRecord = oIndex.GetByIndex(iRecord);
     253         908 :     CPLAssert(poRecord);
     254             : 
     255             :     const OGRSpatialReference *poSRS =
     256         908 :         oFeature.GetDefnRef()->GetGeomFieldDefn(0)->GetSpatialRef();
     257             :     auto poLS =
     258        1816 :         ReadCurveGeometry(poRecord, iRecord, /* nRecordID = */ -1, poSRS);
     259         908 :     if (!poLS)
     260             :     {
     261          11 :         if (m_bStrict)
     262          11 :             return false;  // error message already emitted
     263             :     }
     264             :     else
     265             :     {
     266         897 :         oFeature.SetGeometry(std::move(poLS));
     267             :     }
     268             : 
     269        1794 :     return FillFeatureAttributes(oIndex, iRecord, INAS_FIELD, oFeature) &&
     270         897 :            FillFeatureWithNonAttrAssocSubfields(poRecord, iRecord, INAS_FIELD,
     271         897 :                                                 oFeature);
     272             : }
     273             : 
     274             : /************************************************************************/
     275             : /*                      ProcessUpdateRecordCurve()                      */
     276             : /************************************************************************/
     277             : 
     278             : /** Updates the geometry part of poTargetRecord with poUpdateRecord */
     279          15 : bool OGRS101Reader::ProcessUpdateRecordCurve(const DDFRecord *poUpdateRecord,
     280             :                                              DDFRecord *poTargetRecord) const
     281             : {
     282          15 :     const auto poIDField = poUpdateRecord->GetField(0);
     283          15 :     CPLAssert(poIDField);
     284             : 
     285             :     // Record name
     286             :     const RecordName nRCNM =
     287          15 :         poUpdateRecord->GetIntSubfield(poIDField, RCNM_SUBFIELD, 0);
     288             : 
     289             :     // Record identifier
     290             :     const int nRCID =
     291          15 :         poUpdateRecord->GetIntSubfield(poIDField, RCID_SUBFIELD, 0);
     292             : 
     293          15 :     const auto poUpdatePTASField = poUpdateRecord->FindField(PTAS_FIELD);
     294          15 :     if (poUpdatePTASField)
     295             :     {
     296          15 :         const auto poTargetPTASField = poTargetRecord->FindField(PTAS_FIELD);
     297          15 :         if (!poTargetPTASField)
     298             :         {
     299           0 :             return EMIT_ERROR_OR_WARNING(CPLSPrintf(
     300             :                 "%s, RCNM=%d, RCID=%d: missing PTAS field in "
     301             :                 "target record",
     302             :                 m_osFilename.c_str(), static_cast<int>(nRCNM), nRCID));
     303             :         }
     304             : 
     305             :         // Replace whole target PTAS field with update PTAS field
     306          15 :         poTargetRecord->SetFieldRaw(poTargetPTASField,
     307             :                                     poUpdatePTASField->GetData(),
     308             :                                     poUpdatePTASField->GetDataSize());
     309             :     }
     310             : 
     311             :     // Segment Control field
     312             :     // Update rules described at S-100 Ed 5.2 "10a-7.2.4.1 Encoding rules"
     313          15 :     const auto poSECCField = poUpdateRecord->FindField(SECC_FIELD);
     314          15 :     if (!poSECCField)
     315           0 :         return true;
     316             : 
     317          15 :     const auto poUpdateC2ILField = poUpdateRecord->FindField(C2IL_FIELD);
     318          15 :     if (!poUpdateC2ILField)
     319             :     {
     320           2 :         return EMIT_ERROR_OR_WARNING(
     321             :             CPLSPrintf("%s, RCNM=%d, RCID=%d: missing C2IL field in "
     322             :                        "update record",
     323             :                        m_osFilename.c_str(), static_cast<int>(nRCNM), nRCID));
     324             :     }
     325             : 
     326          13 :     const auto poTargetC2ILField = poTargetRecord->FindField(C2IL_FIELD);
     327          13 :     if (!poTargetC2ILField)
     328             :     {
     329           0 :         return EMIT_ERROR_OR_WARNING(
     330             :             CPLSPrintf("%s, RCNM=%d, RCID=%d: missing C2IL field in "
     331             :                        "target record",
     332             :                        m_osFilename.c_str(), static_cast<int>(nRCNM), nRCID));
     333             :     }
     334             : 
     335             :     // Segment update instruction
     336          13 :     const int SEUI = poUpdateRecord->GetIntSubfield(poSECCField, "SEUI", 0);
     337             : 
     338          13 :     if (SEUI == INSTRUCTION_INSERT)
     339             :     {
     340           2 :         return EMIT_ERROR_OR_WARNING(CPLSPrintf(
     341             :             "%s, RCNM=%d, RCID=%d: SEUI=%d (insert) not supported",
     342             :             m_osFilename.c_str(), static_cast<int>(nRCNM), nRCID, SEUI));
     343             :     }
     344          11 :     else if (SEUI == INSTRUCTION_DELETE)
     345             :     {
     346           2 :         return EMIT_ERROR_OR_WARNING(CPLSPrintf(
     347             :             "%s, RCNM=%d, RCID=%d: SEUI=%d (delete) not supported",
     348             :             m_osFilename.c_str(), static_cast<int>(nRCNM), nRCID, SEUI));
     349             :     }
     350           9 :     else if (SEUI != INSTRUCTION_UPDATE)
     351             :     {
     352           2 :         return EMIT_ERROR_OR_WARNING(CPLSPrintf(
     353             :             "%s, RCNM=%d, RCID=%d: invalid SEUI = %d", m_osFilename.c_str(),
     354             :             static_cast<int>(nRCNM), nRCID, SEUI));
     355             :     }
     356             : 
     357             :     // Segment index
     358           7 :     const int SEIX = poUpdateRecord->GetIntSubfield(poSECCField, "SEIX", 0);
     359           7 :     if (SEIX != 1)
     360             :     {
     361           2 :         return EMIT_ERROR_OR_WARNING(CPLSPrintf(
     362             :             "%s, RCNM=%d, RCID=%d: invalid SEIX = %d", m_osFilename.c_str(),
     363             :             static_cast<int>(nRCNM), nRCID, SEIX));
     364             :     }
     365             : 
     366             :     // Number of segments
     367           5 :     const int NSEG = poUpdateRecord->GetIntSubfield(poSECCField, "NSEG", 0);
     368           5 :     if (NSEG != 1)
     369             :     {
     370           2 :         return EMIT_ERROR_OR_WARNING(CPLSPrintf(
     371             :             "%s, RCNM=%d, RCID=%d: invalid NSEG = %d", m_osFilename.c_str(),
     372             :             static_cast<int>(nRCNM), nRCID, NSEG));
     373             :     }
     374             : 
     375           3 :     return ProcessUpdatePointList(poUpdateRecord, poTargetRecord,
     376           3 :                                   /* bIs3DAllowed = */ false);
     377             : }

Generated by: LCOV version 1.14