LCOV - code coverage report
Current view: top level - ogr/ogrsf_frmts/s101 - ogrs101readersurface.cpp (source / functions) Hit Total Coverage
Test: gdal_filtered.info Lines: 123 125 98.4 %
Date: 2026-05-08 18:52:02 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 <algorithm>
      17             : #include <memory>
      18             : 
      19             : /************************************************************************/
      20             : /*                      CreateSurfaceFeatureDefn()                      */
      21             : /************************************************************************/
      22             : 
      23             : /** Create the feature definition for the Surface layer
      24             :  */
      25         247 : bool OGRS101Reader::CreateSurfaceFeatureDefn()
      26             : {
      27         247 :     if (m_oSurfaceRecordIndex.GetCount() > 0)
      28             :     {
      29             :         m_poFeatureDefnSurface =
      30          68 :             OGRFeatureDefnRefCountedPtr::makeInstance(OGR_LAYER_NAME_SURFACE);
      31          68 :         m_poFeatureDefnSurface->SetGeomType(wkbPolygon);
      32          68 :         auto oSRSIter = m_oMapSRS.find(HORIZONTAL_CRS_ID);
      33          68 :         if (oSRSIter != m_oMapSRS.end())
      34             :         {
      35         136 :             m_poFeatureDefnSurface->GetGeomFieldDefn(0)->SetSpatialRef(
      36         136 :                 OGRSpatialReferenceRefCountedPtr::makeClone(&oSRSIter->second)
      37          68 :                     .get());
      38             :         }
      39             :         {
      40         136 :             OGRFieldDefn oFieldDefn(OGR_FIELD_NAME_RECORD_ID, OFTInteger);
      41          68 :             m_poFeatureDefnSurface->AddFieldDefn(&oFieldDefn);
      42             :         }
      43             :         {
      44         136 :             OGRFieldDefn oFieldDefn(OGR_FIELD_NAME_RECORD_VERSION, OFTInteger);
      45          68 :             m_poFeatureDefnSurface->AddFieldDefn(&oFieldDefn);
      46             :         }
      47          68 :         if (!InferFeatureDefn(m_oSurfaceRecordIndex, SRID_FIELD, INAS_FIELD, {},
      48          68 :                               *m_poFeatureDefnSurface, m_oMapFieldDomains))
      49             :         {
      50           1 :             return false;
      51             :         }
      52             :     }
      53             : 
      54         246 :     return true;
      55             : }
      56             : 
      57             : /************************************************************************/
      58             : /*                        ReadSurfaceGeometry()                         */
      59             : /************************************************************************/
      60             : 
      61             : std::unique_ptr<OGRPolygon>
      62         344 : OGRS101Reader::ReadSurfaceGeometry(const DDFRecord *poRecord, int iRecord,
      63             :                                    int nRecordID,
      64             :                                    const OGRSpatialReference *poSRS) const
      65             : {
      66         344 :     if (nRecordID < 0)
      67         143 :         nRecordID = poRecord->GetIntSubfield(SRID_FIELD, 0, RCID_SUBFIELD, 0);
      68             : 
      69          74 :     const auto GetErrorContext = [iRecord, nRecordID]()
      70             :     {
      71          37 :         if (iRecord >= 0)
      72          37 :             return CPLSPrintf("Record index=%d of SRID", iRecord);
      73             :         else
      74           0 :             return CPLSPrintf("Record ID=%d of SRID", nRecordID);
      75         344 :     };
      76             : 
      77         344 :     if (!poRecord->FindField(RIAS_FIELD))
      78             :     {
      79           1 :         CPL_IGNORE_RET_VAL(EMIT_ERROR_OR_WARNING(
      80             :             CPLSPrintf("%s: no RIAS field", GetErrorContext())));
      81           1 :         return nullptr;
      82             :     }
      83             : 
      84         686 :     auto poSurface = std::make_unique<OGRPolygon>();
      85         343 :     poSurface->assignSpatialReference(poSRS);
      86         343 :     std::unique_ptr<OGRLinearRing> poExteriorRing;
      87         686 :     std::vector<std::unique_ptr<OGRLinearRing>> apoInteriorRings;
      88         658 :     for (const auto *poRIASField : poRecord->GetFields(RIAS_FIELD))
      89             :     {
      90         343 :         const int nRings = poRIASField->GetRepeatCount();
      91         979 :         for (int iRing = 0; iRing < nRings; ++iRing)
      92             :         {
      93             :             const auto GetIntSubfield =
      94        2633 :                 [poRecord, poRIASField, iRing](const char *pszSubFieldName)
      95             :             {
      96        2633 :                 return poRecord->GetIntSubfield(poRIASField, pszSubFieldName,
      97        2633 :                                                 iRing);
      98         664 :             };
      99             : 
     100         664 :             constexpr const char *RAUI_SUBFIELD = "RAUI";
     101         664 :             const int nRAUI = GetIntSubfield(RAUI_SUBFIELD);
     102         664 :             if (nRAUI != INSTRUCTION_INSERT)
     103             :             {
     104           3 :                 CPL_IGNORE_RET_VAL(EMIT_ERROR_OR_WARNING(
     105             :                     CPLSPrintf("%s: wrong value %d for RAUI "
     106             :                                "subfield of %d instance of RIAS field.",
     107             :                                GetErrorContext(), nRAUI, iRing)));
     108          28 :                 return nullptr;
     109             :             }
     110             : 
     111         661 :             const RecordName nRRNM = GetIntSubfield(RRNM_SUBFIELD);
     112         982 :             if (nRRNM != RECORD_NAME_CURVE &&
     113         321 :                 nRRNM != RECORD_NAME_COMPOSITE_CURVE)
     114             :             {
     115           3 :                 CPL_IGNORE_RET_VAL(EMIT_ERROR_OR_WARNING(CPLSPrintf(
     116             :                     "%s: Invalid value for RRNM "
     117             :                     "subfield of %d instance of RIAS field: "
     118             :                     "got %d, expected %d or %d.",
     119             :                     GetErrorContext(), iRing, static_cast<int>(nRRNM),
     120             :                     static_cast<int>(RECORD_NAME_CURVE),
     121             :                     static_cast<int>(RECORD_NAME_COMPOSITE_CURVE))));
     122           3 :                 return nullptr;
     123             :             }
     124             : 
     125         658 :             const int nRRID = GetIntSubfield(RRID_SUBFIELD);
     126           0 :             std::unique_ptr<OGRLineString> poCurvePart;
     127         658 :             if (nRRNM == RECORD_NAME_CURVE)
     128             :             {
     129             :                 const auto poCurveRecord =
     130         340 :                     m_oCurveRecordIndex.FindRecord(nRRID);
     131         340 :                 if (!poCurveRecord)
     132             :                 {
     133           3 :                     CPL_IGNORE_RET_VAL(EMIT_ERROR_OR_WARNING(CPLSPrintf(
     134             :                         "%s: Value (RRNM=%d, RRID=%d) "
     135             :                         "of instance %d of RIAS field does not point to an "
     136             :                         "existing record.",
     137             :                         GetErrorContext(), static_cast<int>(nRRNM), nRRID,
     138             :                         iRing)));
     139           3 :                     return nullptr;
     140             :                 }
     141             : 
     142         674 :                 poCurvePart = ReadCurveGeometry(
     143         337 :                     poCurveRecord, /* iRecord = */ -1, nRRID, poSRS);
     144             :             }
     145             :             else
     146             :             {
     147         318 :                 CPLAssert(nRRNM == RECORD_NAME_COMPOSITE_CURVE);
     148             : 
     149             :                 const auto poCompositeCurveRecord =
     150         318 :                     m_oCompositeCurveRecordIndex.FindRecord(nRRID);
     151         318 :                 if (!poCompositeCurveRecord)
     152             :                 {
     153           3 :                     CPL_IGNORE_RET_VAL(EMIT_ERROR_OR_WARNING(CPLSPrintf(
     154             :                         "%s: Value (RRNM=%d, RRID=%d) "
     155             :                         "of instance %d of RIAS field does not point to an "
     156             :                         "existing record.",
     157             :                         GetErrorContext(), static_cast<int>(nRRNM), nRRID,
     158             :                         iRing)));
     159           3 :                     return nullptr;
     160             :                 }
     161             : 
     162         630 :                 poCurvePart = ReadCompositeCurveGeometry(
     163         315 :                     poCompositeCurveRecord, /* iRecord = */ -1, nRRID, poSRS);
     164             :             }
     165             : 
     166         652 :             if (!poCurvePart)
     167             :             {
     168           2 :                 return nullptr;
     169             :             }
     170             : 
     171         650 :             bool bReverse = false;
     172         650 :             const int nORNT = GetIntSubfield(ORNT_SUBFIELD);
     173         650 :             if (nORNT == ORNT_REVERSE)
     174             :             {
     175         328 :                 bReverse = true;
     176             :             }
     177         322 :             else if (nORNT != ORNT_FORWARD)
     178             :             {
     179           3 :                 CPL_IGNORE_RET_VAL(EMIT_ERROR_OR_WARNING(
     180             :                     CPLSPrintf("%s: Invalid value for ORNT "
     181             :                                "subfield of %d instance of RIAS field: "
     182             :                                "got %d, expected %d or %d.",
     183             :                                GetErrorContext(), iRing, nORNT, ORNT_FORWARD,
     184             :                                ORNT_REVERSE)));
     185           3 :                 return nullptr;
     186             :             }
     187             : 
     188         647 :             auto poRing = std::make_unique<OGRLinearRing>();
     189         647 :             if (bReverse && poCurvePart->getNumPoints() > 0)
     190         656 :                 poRing->addSubLineString(poCurvePart.get(),
     191         328 :                                          poCurvePart->getNumPoints() - 1, 0);
     192             :             else
     193         319 :                 poRing->addSubLineString(poCurvePart.get());
     194         647 :             if (!poRing->get_IsClosed())
     195             :             {
     196           3 :                 CPL_IGNORE_RET_VAL(EMIT_ERROR_OR_WARNING(
     197             :                     CPLSPrintf("%s: Ring of index %d is not closed.",
     198             :                                GetErrorContext(), iRing)));
     199           3 :                 return nullptr;
     200             :             }
     201             : 
     202         644 :             constexpr const char *USAG_SUBFIELD = "USAG";
     203         644 :             constexpr int USAG_EXTERIOR = 1;  // Exterior ring
     204         644 :             constexpr int USAG_INTERIOR = 2;  // Interior ring
     205             :             const int nUSAG =
     206         644 :                 poRecord->GetIntSubfield(RIAS_FIELD, 0, USAG_SUBFIELD, iRing);
     207         644 :             if (nUSAG == USAG_EXTERIOR)
     208             :             {
     209         325 :                 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         322 :                 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         321 :                 poExteriorRing = std::move(poRing);
     226             :             }
     227         319 :             else if (nUSAG == USAG_INTERIOR)
     228             :             {
     229         316 :                 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         315 :                 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         315 :     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         312 :     poSurface->addRing(std::move(poExteriorRing));
     262         624 :     for (auto &poRing : apoInteriorRings)
     263         312 :         poSurface->addRing(std::move(poRing));
     264             : 
     265         312 :     if (OGRGeometryFactory::haveGEOS())
     266             :     {
     267         312 :         std::string osReason;
     268         312 :         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         311 :     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         143 : bool OGRS101Reader::FillFeatureSurface(const DDFRecordIndex &oIndex,
     290             :                                        int iRecord, OGRFeature &oFeature) const
     291             : {
     292         143 :     const auto poRecord = oIndex.GetByIndex(iRecord);
     293         143 :     CPLAssert(poRecord);
     294             : 
     295             :     const OGRSpatialReference *poSRS =
     296         143 :         oFeature.GetDefnRef()->GetGeomFieldDefn(0)->GetSpatialRef();
     297             :     auto poSurface =
     298         286 :         ReadSurfaceGeometry(poRecord, iRecord, /* nRecordID = */ -1, poSRS);
     299         143 :     if (!poSurface)
     300             :     {
     301          33 :         if (m_bStrict)
     302          12 :             return false;  // error message already emitted
     303             :     }
     304             :     else
     305             :     {
     306         110 :         oFeature.SetGeometry(std::move(poSurface));
     307             :     }
     308             : 
     309         262 :     return FillFeatureAttributes(oIndex, iRecord, INAS_FIELD, oFeature) &&
     310         131 :            FillFeatureWithNonAttrAssocSubfields(poRecord, iRecord, INAS_FIELD,
     311         131 :                                                 oFeature);
     312             : }

Generated by: LCOV version 1.14