LCOV - code coverage report
Current view: top level - ogr/ogrsf_frmts/s101 - ogrs101readersurface.cpp (source / functions) Hit Total Coverage
Test: gdal_filtered.info Lines: 183 188 97.3 %
Date: 2026-05-29 23:25:07 Functions: 7 7 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 <memory>
      18             : 
      19             : /************************************************************************/
      20             : /*                      CreateSurfaceFeatureDefn()                      */
      21             : /************************************************************************/
      22             : 
      23             : /** Create the feature definition for the Surface layer
      24             :  */
      25         343 : bool OGRS101Reader::CreateSurfaceFeatureDefn()
      26             : {
      27         343 :     if (m_oSurfaceRecordIndex.GetCount() > 0)
      28             :     {
      29             :         m_poFeatureDefnSurface =
      30          83 :             OGRFeatureDefnRefCountedPtr::makeInstance(OGR_LAYER_NAME_SURFACE);
      31          83 :         m_poFeatureDefnSurface->SetGeomType(wkbPolygon);
      32          83 :         auto oSRSIter = m_oMapSRS.find(HORIZONTAL_CRS_ID);
      33          83 :         if (oSRSIter != m_oMapSRS.end())
      34             :         {
      35         166 :             m_poFeatureDefnSurface->GetGeomFieldDefn(0)->SetSpatialRef(
      36         166 :                 OGRSpatialReferenceRefCountedPtr::makeClone(&oSRSIter->second)
      37          83 :                     .get());
      38             :         }
      39          83 :         m_poFeatureDefnSurface->GetGeomFieldDefn(0)->SetCoordinatePrecision(
      40          83 :             m_coordinatePrecision);
      41             :         {
      42         166 :             OGRFieldDefn oFieldDefn(OGR_FIELD_NAME_RECORD_ID, OFTInteger);
      43          83 :             m_poFeatureDefnSurface->AddFieldDefn(&oFieldDefn);
      44             :         }
      45             :         {
      46         166 :             OGRFieldDefn oFieldDefn(OGR_FIELD_NAME_RECORD_VERSION, OFTInteger);
      47          83 :             m_poFeatureDefnSurface->AddFieldDefn(&oFieldDefn);
      48             :         }
      49          83 :         if (!InferFeatureDefn(m_oSurfaceRecordIndex, SRID_FIELD, INAS_FIELD, {},
      50          83 :                               *m_poFeatureDefnSurface, m_oMapFieldDomains))
      51             :         {
      52           1 :             return false;
      53             :         }
      54             :     }
      55             : 
      56         342 :     return true;
      57             : }
      58             : 
      59             : /************************************************************************/
      60             : /*                        ReadSurfaceGeometry()                         */
      61             : /************************************************************************/
      62             : 
      63             : std::unique_ptr<OGRPolygon>
      64         539 : OGRS101Reader::ReadSurfaceGeometry(const DDFRecord *poRecord, int iRecord,
      65             :                                    int nRecordID,
      66             :                                    const OGRSpatialReference *poSRS) const
      67             : {
      68         539 :     if (nRecordID < 0)
      69         226 :         nRecordID = poRecord->GetIntSubfield(SRID_FIELD, 0, RCID_SUBFIELD, 0);
      70             : 
      71          74 :     const auto GetErrorContext = [iRecord, nRecordID]()
      72             :     {
      73          37 :         if (iRecord >= 0)
      74          37 :             return CPLSPrintf("Record index=%d of SRID", iRecord);
      75             :         else
      76           0 :             return CPLSPrintf("Record ID=%d of SRID", nRecordID);
      77         539 :     };
      78             : 
      79         539 :     if (!poRecord->FindField(RIAS_FIELD))
      80             :     {
      81           1 :         CPL_IGNORE_RET_VAL(EMIT_ERROR_OR_WARNING(
      82             :             CPLSPrintf("%s: no RIAS field", GetErrorContext())));
      83           1 :         return nullptr;
      84             :     }
      85             : 
      86        1076 :     auto poSurface = std::make_unique<OGRPolygon>();
      87         538 :     poSurface->assignSpatialReference(poSRS);
      88         538 :     std::unique_ptr<OGRLinearRing> poExteriorRing;
      89        1076 :     std::vector<std::unique_ptr<OGRLinearRing>> apoInteriorRings;
      90        1048 :     for (const auto *poRIASField : poRecord->GetFields(RIAS_FIELD))
      91             :     {
      92         538 :         const int nRings = poRIASField->GetRepeatCount();
      93        1564 :         for (int iRing = 0; iRing < nRings; ++iRing)
      94             :         {
      95             :             const auto GetIntSubfield =
      96        4193 :                 [poRecord, poRIASField, iRing](const char *pszSubFieldName)
      97             :             {
      98        4193 :                 return poRecord->GetIntSubfield(poRIASField, pszSubFieldName,
      99        4193 :                                                 iRing);
     100        1054 :             };
     101             : 
     102        1054 :             const int nRAUI = GetIntSubfield(RAUI_SUBFIELD);
     103        1054 :             if (nRAUI != INSTRUCTION_INSERT)
     104             :             {
     105           3 :                 CPL_IGNORE_RET_VAL(EMIT_ERROR_OR_WARNING(
     106             :                     CPLSPrintf("%s: wrong value %d for RAUI "
     107             :                                "subfield of %d instance of RIAS field.",
     108             :                                GetErrorContext(), nRAUI, iRing)));
     109          28 :                 return nullptr;
     110             :             }
     111             : 
     112        1051 :             const RecordName nRRNM = GetIntSubfield(RRNM_SUBFIELD);
     113        1525 :             if (nRRNM != RECORD_NAME_CURVE &&
     114         474 :                 nRRNM != RECORD_NAME_COMPOSITE_CURVE)
     115             :             {
     116           3 :                 CPL_IGNORE_RET_VAL(EMIT_ERROR_OR_WARNING(CPLSPrintf(
     117             :                     "%s: Invalid value for RRNM "
     118             :                     "subfield of %d instance of RIAS field: "
     119             :                     "got %d, expected %d or %d.",
     120             :                     GetErrorContext(), iRing, static_cast<int>(nRRNM),
     121             :                     static_cast<int>(RECORD_NAME_CURVE),
     122             :                     static_cast<int>(RECORD_NAME_COMPOSITE_CURVE))));
     123           3 :                 return nullptr;
     124             :             }
     125             : 
     126        1048 :             const int nRRID = GetIntSubfield(RRID_SUBFIELD);
     127           0 :             std::unique_ptr<OGRLineString> poCurvePart;
     128        1048 :             if (nRRNM == RECORD_NAME_CURVE)
     129             :             {
     130             :                 const auto poCurveRecord =
     131         577 :                     m_oCurveRecordIndex.FindRecord(nRRID);
     132         577 :                 if (!poCurveRecord)
     133             :                 {
     134           3 :                     CPL_IGNORE_RET_VAL(EMIT_ERROR_OR_WARNING(CPLSPrintf(
     135             :                         "%s: Value (RRNM=%d, RRID=%d) "
     136             :                         "of instance %d of RIAS field does not point to an "
     137             :                         "existing record.",
     138             :                         GetErrorContext(), static_cast<int>(nRRNM), nRRID,
     139             :                         iRing)));
     140           3 :                     return nullptr;
     141             :                 }
     142             : 
     143        1148 :                 poCurvePart = ReadCurveGeometry(
     144         574 :                     poCurveRecord, /* iRecord = */ -1, nRRID, poSRS);
     145             :             }
     146             :             else
     147             :             {
     148         471 :                 CPLAssert(nRRNM == RECORD_NAME_COMPOSITE_CURVE);
     149             : 
     150             :                 const auto poCompositeCurveRecord =
     151         471 :                     m_oCompositeCurveRecordIndex.FindRecord(nRRID);
     152         471 :                 if (!poCompositeCurveRecord)
     153             :                 {
     154           3 :                     CPL_IGNORE_RET_VAL(EMIT_ERROR_OR_WARNING(CPLSPrintf(
     155             :                         "%s: Value (RRNM=%d, RRID=%d) "
     156             :                         "of instance %d of RIAS field does not point to an "
     157             :                         "existing record.",
     158             :                         GetErrorContext(), static_cast<int>(nRRNM), nRRID,
     159             :                         iRing)));
     160           3 :                     return nullptr;
     161             :                 }
     162             : 
     163         936 :                 poCurvePart = ReadCompositeCurveGeometry(
     164         468 :                     poCompositeCurveRecord, /* iRecord = */ -1, nRRID, poSRS);
     165             :             }
     166             : 
     167        1042 :             if (!poCurvePart)
     168             :             {
     169           2 :                 return nullptr;
     170             :             }
     171             : 
     172        1040 :             bool bReverse = false;
     173        1040 :             const int nORNT = GetIntSubfield(ORNT_SUBFIELD);
     174        1040 :             if (nORNT == ORNT_REVERSE)
     175             :             {
     176         523 :                 bReverse = true;
     177             :             }
     178         517 :             else if (nORNT != ORNT_FORWARD)
     179             :             {
     180           3 :                 CPL_IGNORE_RET_VAL(EMIT_ERROR_OR_WARNING(
     181             :                     CPLSPrintf("%s: Invalid value for ORNT "
     182             :                                "subfield of %d instance of RIAS field: "
     183             :                                "got %d, expected %d or %d.",
     184             :                                GetErrorContext(), iRing, nORNT, ORNT_FORWARD,
     185             :                                ORNT_REVERSE)));
     186           3 :                 return nullptr;
     187             :             }
     188             : 
     189        1037 :             auto poRing = std::make_unique<OGRLinearRing>();
     190        1037 :             if (bReverse && poCurvePart->getNumPoints() > 0)
     191        1046 :                 poRing->addSubLineString(poCurvePart.get(),
     192         523 :                                          poCurvePart->getNumPoints() - 1, 0);
     193             :             else
     194         514 :                 poRing->addSubLineString(poCurvePart.get());
     195        1037 :             if (poRing->getNumPoints() <= 2 || !poRing->get_IsClosed())
     196             :             {
     197           3 :                 CPL_IGNORE_RET_VAL(EMIT_ERROR_OR_WARNING(
     198             :                     CPLSPrintf("%s: Ring of index %d is not closed.",
     199             :                                GetErrorContext(), iRing)));
     200           3 :                 return nullptr;
     201             :             }
     202             : 
     203        1034 :             constexpr int USAG_EXTERIOR = 1;  // Exterior ring
     204        1034 :             constexpr int USAG_INTERIOR = 2;  // Interior ring
     205             :             const int nUSAG =
     206        1034 :                 poRecord->GetIntSubfield(RIAS_FIELD, 0, USAG_SUBFIELD, iRing);
     207        1034 :             if (nUSAG == USAG_EXTERIOR)
     208             :             {
     209         520 :                 if (poExteriorRing)
     210             :                 {
     211           3 :                     CPL_IGNORE_RET_VAL(EMIT_ERROR_OR_WARNING(CPLSPrintf(
     212             :                         "%s: several rings tagged as exterior rings.",
     213             :                         GetErrorContext())));
     214           3 :                     return nullptr;
     215             :                 }
     216         517 :                 if (!poRing->isClockwise())
     217             :                 {
     218           3 :                     if (!EMIT_ERROR_OR_WARNING(CPLSPrintf(
     219             :                             "%s: exterior ring orientation is not clockwise.",
     220             :                             GetErrorContext())))
     221             :                     {
     222           1 :                         return nullptr;
     223             :                     }
     224             :                 }
     225         516 :                 poExteriorRing = std::move(poRing);
     226             :             }
     227         514 :             else if (nUSAG == USAG_INTERIOR)
     228             :             {
     229         511 :                 if (poRing->isClockwise())
     230             :                 {
     231           3 :                     if (!EMIT_ERROR_OR_WARNING(
     232             :                             CPLSPrintf("%s: orientation of interior ring of "
     233             :                                        "index %d is not counter-clockwise.",
     234             :                                        GetErrorContext(), iRing)))
     235             :                     {
     236           1 :                         return nullptr;
     237             :                     }
     238             :                 }
     239         510 :                 apoInteriorRings.push_back(std::move(poRing));
     240             :             }
     241             :             else
     242             :             {
     243           3 :                 CPL_IGNORE_RET_VAL(EMIT_ERROR_OR_WARNING(
     244             :                     CPLSPrintf("%s: Invalid value for USAG "
     245             :                                "subfield of %d instance of RIAS field: "
     246             :                                "got %d, expected %d or %d.",
     247             :                                GetErrorContext(), iRing, nUSAG, USAG_EXTERIOR,
     248             :                                USAG_INTERIOR)));
     249           3 :                 return nullptr;
     250             :             }
     251             :         }
     252             :     }
     253             : 
     254         510 :     if (!poExteriorRing)
     255             :     {
     256           3 :         CPL_IGNORE_RET_VAL(EMIT_ERROR_OR_WARNING(CPLSPrintf(
     257             :             "%s: no ring tagged as exterior ring.", GetErrorContext())));
     258           3 :         return nullptr;
     259             :     }
     260             : 
     261         507 :     poSurface->addRing(std::move(poExteriorRing));
     262        1014 :     for (auto &poRing : apoInteriorRings)
     263         507 :         poSurface->addRing(std::move(poRing));
     264             : 
     265         507 :     if (OGRGeometryFactory::haveGEOS())
     266             :     {
     267         507 :         std::string osReason;
     268         507 :         if (!poSurface->IsValid(&osReason))
     269             :         {
     270           3 :             if (!EMIT_ERROR_OR_WARNING(CPLSPrintf("%s: surface is invalid: %s.",
     271             :                                                   GetErrorContext(),
     272             :                                                   osReason.c_str())))
     273             :             {
     274           1 :                 return nullptr;
     275             :             }
     276             :         }
     277             :     }
     278             : 
     279         506 :     return poSurface;
     280             : }
     281             : 
     282             : /************************************************************************/
     283             : /*                         FillFeatureSurface()                         */
     284             : /************************************************************************/
     285             : 
     286             : /** Fill the content of the provided feature from the identified record
     287             :  * (of m_oSurfaceRecordIndex).
     288             :  */
     289         226 : bool OGRS101Reader::FillFeatureSurface(const DDFRecordIndex &oIndex,
     290             :                                        int iRecord, OGRFeature &oFeature) const
     291             : {
     292         226 :     const auto poRecord = oIndex.GetByIndex(iRecord);
     293         226 :     CPLAssert(poRecord);
     294             : 
     295             :     const OGRSpatialReference *poSRS =
     296         226 :         oFeature.GetDefnRef()->GetGeomFieldDefn(0)->GetSpatialRef();
     297             :     auto poSurface =
     298         452 :         ReadSurfaceGeometry(poRecord, iRecord, /* nRecordID = */ -1, poSRS);
     299         226 :     if (!poSurface)
     300             :     {
     301          33 :         if (m_bStrict)
     302          12 :             return false;  // error message already emitted
     303             :     }
     304             :     else
     305             :     {
     306         193 :         oFeature.SetGeometry(std::move(poSurface));
     307             :     }
     308             : 
     309         428 :     return FillFeatureAttributes(oIndex, iRecord, INAS_FIELD, oFeature) &&
     310         214 :            FillFeatureWithNonAttrAssocSubfields(poRecord, iRecord, INAS_FIELD,
     311         214 :                                                 oFeature);
     312             : }
     313             : 
     314             : /************************************************************************/
     315             : /*                     ProcessUpdateRecordSurface()                     */
     316             : /************************************************************************/
     317             : 
     318             : /** Updates the geometry part of poTargetRecord with poUpdateRecord */
     319           7 : bool OGRS101Reader::ProcessUpdateRecordSurface(const DDFRecord *poUpdateRecord,
     320             :                                                DDFRecord *poTargetRecord) const
     321             : {
     322           7 :     const auto poIDField = poUpdateRecord->GetField(0);
     323           7 :     CPLAssert(poIDField);
     324             : 
     325             :     // Record name
     326             :     const RecordName nRCNM =
     327           7 :         poUpdateRecord->GetIntSubfield(poIDField, RCNM_SUBFIELD, 0);
     328             : 
     329             :     // Record identifier
     330             :     const int nRCID =
     331           7 :         poUpdateRecord->GetIntSubfield(poIDField, RCID_SUBFIELD, 0);
     332             : 
     333             :     // Ring Association Field field
     334          14 :     const auto apoUpdateFields = poUpdateRecord->GetFields(RIAS_FIELD);
     335           7 :     if (apoUpdateFields.empty())
     336           0 :         return true;
     337             : 
     338             :     struct Ring
     339             :     {
     340             :         int RRNM = 0;
     341             :         int RRID = 0;
     342             :         int ORNT = 0;
     343             :         int USAG = 0;
     344             :         int RAUI = 0;
     345             : 
     346          26 :         static Ring Read(const DDFRecord *poRecord, const DDFField *poField,
     347             :                          int i)
     348             :         {
     349          26 :             Ring ring;
     350          26 :             ring.RRNM = poRecord->GetIntSubfield(poField, RRNM_SUBFIELD, i);
     351          26 :             ring.RRID = poRecord->GetIntSubfield(poField, RRID_SUBFIELD, i);
     352          26 :             ring.ORNT = poRecord->GetIntSubfield(poField, ORNT_SUBFIELD, i);
     353          26 :             ring.USAG = poRecord->GetIntSubfield(poField, USAG_SUBFIELD, i);
     354          26 :             ring.RAUI = poRecord->GetIntSubfield(poField, RAUI_SUBFIELD, i);
     355          26 :             return ring;
     356             :         }
     357             :     };
     358             : 
     359          14 :     std::vector<Ring> asTarget;
     360             :     // Ingest the existing/target record(s)
     361          14 :     auto apoTargetFields = poTargetRecord->GetFields(RIAS_FIELD);
     362          14 :     for (auto *poTargetField : apoTargetFields)
     363             :     {
     364           7 :         const int nTargetRepeatCount = poTargetField->GetRepeatCount();
     365          21 :         for (int i = 0; i < nTargetRepeatCount; ++i)
     366             :         {
     367          14 :             asTarget.push_back(Ring::Read(poTargetRecord, poTargetField, i));
     368             :         }
     369           7 :         poTargetRecord->DeleteField(poTargetField);
     370             :     }
     371             : 
     372             :     // Apply the update record(s)
     373          12 :     for (auto *poUpdateField : apoUpdateFields)
     374             :     {
     375           7 :         const int nUpdateRepeatCount = poUpdateField->GetRepeatCount();
     376          17 :         for (int i = 0; i < nUpdateRepeatCount; ++i)
     377             :         {
     378          12 :             Ring ring = Ring::Read(poUpdateRecord, poUpdateField, i);
     379          12 :             if (ring.RAUI == INSTRUCTION_INSERT)
     380             :             {
     381           5 :                 asTarget.push_back(std::move(ring));
     382             :             }
     383           7 :             else if (ring.RAUI == INSTRUCTION_DELETE)
     384             :             {
     385           5 :                 bool bMatchFound = false;
     386          12 :                 for (size_t j = 0; j < asTarget.size(); ++j)
     387             :                 {
     388          13 :                     if (asTarget[j].RRNM == ring.RRNM &&
     389           3 :                         asTarget[j].RRID == ring.RRID)
     390             :                     {
     391           3 :                         bMatchFound = true;
     392           3 :                         asTarget.erase(asTarget.begin() + j);
     393           3 :                         break;
     394             :                     }
     395             :                 }
     396           7 :                 if (!bMatchFound &&
     397           2 :                     !EMIT_ERROR_OR_WARNING(CPLSPrintf(
     398             :                         "%s, RCNM=%d, RCID=%d, RIAS iSubField=%d: update "
     399             :                         "field references RRNM=%d, RRID=%d which does not "
     400             :                         "exist in initial or previous update",
     401             :                         m_osFilename.c_str(), static_cast<int>(nRCNM), nRCID, i,
     402             :                         ring.RRNM, ring.RRID)))
     403             :                 {
     404           2 :                     return false;
     405             :                 }
     406             :             }
     407           2 :             else if (!EMIT_ERROR_OR_WARNING(CPLSPrintf(
     408             :                          "%s, RCNM=%d, RCID=%d, SPAS iSubField=%d: invalid "
     409             :                          "RAUI=%d",
     410             :                          m_osFilename.c_str(), static_cast<int>(nRCNM), nRCID,
     411             :                          i, ring.RAUI)))
     412             :             {
     413           1 :                 return false;
     414             :             }
     415             :         }
     416             :     }
     417             : 
     418           5 :     if (!asTarget.empty())
     419             :     {
     420           5 :         auto poRIASFieldDefn = m_oMainModule.FindFieldDefn(RIAS_FIELD);
     421           5 :         if (!poRIASFieldDefn)
     422             :         {
     423           0 :             return EMIT_ERROR("Cannot find RIAS field definition");
     424             :         }
     425           5 :         auto poRIASFieldTarget = poTargetRecord->AddField(poRIASFieldDefn);
     426           5 :         CPLAssert(poRIASFieldTarget);
     427           5 :         const auto poRIASFieldUpdate = apoUpdateFields[0];
     428           5 :         if (*(poRIASFieldTarget->GetFieldDefn()) !=
     429           5 :             *(poRIASFieldUpdate->GetFieldDefn()))
     430             :         {
     431           0 :             return EMIT_ERROR("RIAS field definitions of update and target "
     432             :                               "records are different");
     433             :         }
     434             : 
     435             :         // Compose raw target field
     436          10 :         std::string s;
     437          17 :         for (const auto &cc : asTarget)
     438             :         {
     439          12 :             AppendUInt8(s, static_cast<uint8_t>(cc.RRNM));
     440          12 :             AppendInt32(s, cc.RRID);
     441          12 :             AppendUInt8(s, static_cast<uint8_t>(cc.ORNT));
     442          12 :             AppendUInt8(s, static_cast<uint8_t>(cc.USAG));
     443          12 :             AppendUInt8(s, static_cast<uint8_t>(cc.RAUI));
     444             :         }
     445           5 :         AppendUInt8(s, DDF_FIELD_TERMINATOR);
     446             : 
     447           5 :         poTargetRecord->SetFieldRaw(poRIASFieldTarget, s.data(),
     448           5 :                                     static_cast<int>(s.size()));
     449             :     }
     450             : 
     451           5 :     return true;
     452             : }

Generated by: LCOV version 1.14