LCOV - code coverage report
Current view: top level - ogr/ogrsf_frmts/s101 - ogrs101readerfeaturetype.cpp (source / functions) Hit Total Coverage
Test: gdal_filtered.info Lines: 411 454 90.5 %
Date: 2026-05-08 18:52:02 Functions: 13 13 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 "ogrs101featurecatalog.h"
      15             : #include "ogrs101readerconstants.h"
      16             : 
      17             : #include <algorithm>
      18             : #include <memory>
      19             : 
      20             : /************************************************************************/
      21             : /*                   CreateFeatureTypeFeatureDefns()                    */
      22             : /************************************************************************/
      23             : 
      24             : /** Create the feature definitions for the various kinds of Feature Type records
      25             :  */
      26         246 : bool OGRS101Reader::CreateFeatureTypeFeatureDefns()
      27             : {
      28         246 :     const int nCount = m_oFeatureTypeRecordIndex.GetCount();
      29             : 
      30             :     struct FeatureTypeDef
      31             :     {
      32             :         int nMaxMaskCount = 0;
      33             :         bool bMultiSpatialAssociations = false;
      34             :         bool bPromotedToMultiPointFromPoint = false;
      35             :         std::vector<int> anRecordIdx{};
      36             :     };
      37             : 
      38         492 :     std::map<FeatureTypeKey, FeatureTypeDef> oMapFeatureClassAndGeomType;
      39             : 
      40             :     // First pass to collect all (feature type code, geometry type, CRS Id) triples
      41         649 :     for (int iRecord = 0; iRecord < nCount; ++iRecord)
      42             :     {
      43         405 :         const auto poRecord = m_oFeatureTypeRecordIndex.GetByIndex(iRecord);
      44         405 :         constexpr const char *NFTC_SUBFIELD = "NFTC";
      45         405 :         FeatureTypeKey key;
      46         405 :         key.nFeatureTypeCode =
      47             :             poRecord->GetIntSubfield(FRID_FIELD, 0, NFTC_SUBFIELD, 0);
      48             : 
      49         898 :         const auto NormalizeCurve = [](RecordName name)
      50             :         {
      51         898 :             return name == RECORD_NAME_COMPOSITE_CURVE ? RECORD_NAME_CURVE
      52         898 :                                                        : name;
      53             :         };
      54             : 
      55         405 :         bool bMultiSpatialAssociations = false;
      56             :         // S-101 page 19: A feature may reference multiple geometries
      57             :         // but must only reference geometries of a single
      58             :         // geometric primitive (point, pointset, curve or surface).
      59         405 :         const auto apoSPASFields = poRecord->GetFields(SPAS_FIELD);
      60         405 :         int nSPASCount = 0;
      61         405 :         bool bHeterogeneous = false;
      62         804 :         for (int iSPASField = 0;
      63         804 :              iSPASField < static_cast<int>(apoSPASFields.size()); ++iSPASField)
      64             :         {
      65         399 :             const auto poSPASField = apoSPASFields[iSPASField];
      66         399 :             if (iSPASField == 0)
      67             :             {
      68             :                 key.nGeometryType = NormalizeCurve(
      69         366 :                     poRecord->GetIntSubfield(poSPASField, RRNM_SUBFIELD, 0));
      70             :             }
      71             : 
      72         399 :             const int nSPASCountThisIter = poSPASField->GetRepeatCount();
      73         399 :             nSPASCount += nSPASCountThisIter;
      74         929 :             for (int iSPAS = 0; iSPAS < nSPASCountThisIter; ++iSPAS)
      75             :             {
      76         532 :                 if (NormalizeCurve(poRecord->GetIntSubfield(
      77         532 :                         poSPASField, RRNM_SUBFIELD, iSPAS)) !=
      78             :                     key.nGeometryType)
      79             :                 {
      80           2 :                     bHeterogeneous = true;
      81           2 :                     break;
      82             :                 }
      83             :             }
      84             :         }
      85         405 :         if (bHeterogeneous)
      86             :         {
      87           2 :             if (!EMIT_ERROR_OR_WARNING(CPLSPrintf(
      88             :                     "Record index %d of FRID: has %d "
      89             :                     "spatial associations with at least 2 "
      90             :                     "not being of the same geometry type%s",
      91             :                     iRecord, nSPASCount, m_bStrict ? "" : ". Ignoring it")))
      92             :             {
      93           1 :                 return false;
      94             :             }
      95           1 :             continue;
      96             :         }
      97         403 :         else if (nSPASCount > 1)
      98             :         {
      99         164 :             bMultiSpatialAssociations = true;
     100             :         }
     101             : 
     102         403 :         if (key.nGeometryType == RECORD_NAME_POINT)
     103             :         {
     104             :             const int nRRID =
     105          98 :                 poRecord->GetIntSubfield(SPAS_FIELD, 0, RRID_SUBFIELD, 0);
     106          98 :             const auto poGeomRecord = m_oPointRecordIndex.FindRecord(nRRID);
     107          98 :             if (!poGeomRecord && !EMIT_ERROR_OR_WARNING(CPLSPrintf(
     108             :                                      "Record index %d of FRID: Point of id %d "
     109             :                                      "does not exist",
     110             :                                      iRecord, nRRID)))
     111             :             {
     112           1 :                 return false;
     113             :             }
     114             : 
     115             :             const auto nCRSId =
     116          96 :                 poGeomRecord ? GetCRSIdForPointRecord(poGeomRecord, -1, nRRID)
     117          97 :                              : HORIZONTAL_CRS_ID;
     118          97 :             if (nCRSId == INVALID_CRS_ID)
     119             :             {
     120             :                 // Error already emitted
     121           0 :                 if (m_bStrict)
     122           0 :                     return false;
     123           0 :                 continue;
     124             :             }
     125          97 :             key.nCRSId = nCRSId;
     126             :         }
     127         305 :         else if (key.nGeometryType == RECORD_NAME_MULTIPOINT)
     128             :         {
     129             :             const int nRRID =
     130          66 :                 poRecord->GetIntSubfield(SPAS_FIELD, 0, RRID_SUBFIELD, 0);
     131             :             const auto poGeomRecord =
     132          66 :                 m_oMultiPointRecordIndex.FindRecord(nRRID);
     133          66 :             if (!poGeomRecord &&
     134           0 :                 !EMIT_ERROR_OR_WARNING(
     135             :                     CPLSPrintf("Record index %d of FRID: MultiPoint of id %d "
     136             :                                "does not exist",
     137             :                                iRecord, nRRID)))
     138             :             {
     139           0 :                 return false;
     140             :             }
     141             : 
     142             :             const auto nCRSId =
     143             :                 poGeomRecord
     144          66 :                     ? GetCRSIdForMultiPointRecord(poGeomRecord, -1, nRRID)
     145          66 :                     : HORIZONTAL_CRS_ID;
     146          66 :             if (nCRSId == INVALID_CRS_ID)
     147             :             {
     148             :                 // Error already emitted
     149           0 :                 if (m_bStrict)
     150           0 :                     return false;
     151           0 :                 continue;
     152             :             }
     153          66 :             key.nCRSId = nCRSId;
     154             :         }
     155         239 :         else if (!apoSPASFields.empty())
     156         200 :             key.nCRSId = HORIZONTAL_CRS_ID;
     157             : 
     158             :         const bool bPromotedToMultiPointFromPoint =
     159         402 :             (key.nGeometryType == RECORD_NAME_POINT &&
     160         402 :              bMultiSpatialAssociations);
     161         402 :         if (bPromotedToMultiPointFromPoint)
     162          65 :             key.nGeometryType = RECORD_NAME_MULTIPOINT;
     163             : 
     164         402 :         auto &featureTypeDef = oMapFeatureClassAndGeomType[key];
     165         402 :         if (bPromotedToMultiPointFromPoint)
     166             :         {
     167          65 :             featureTypeDef.bPromotedToMultiPointFromPoint = true;
     168             :         }
     169         337 :         else if (bMultiSpatialAssociations)
     170             :         {
     171          99 :             featureTypeDef.bMultiSpatialAssociations = true;
     172             :         }
     173             : 
     174         402 :         int nMaskCount = 0;
     175         468 :         for (const auto poField : poRecord->GetFields(MASK_FIELD))
     176             :         {
     177          66 :             nMaskCount += poField->GetRepeatCount();
     178             :         }
     179         402 :         featureTypeDef.nMaxMaskCount =
     180         402 :             std::max(featureTypeDef.nMaxMaskCount, nMaskCount);
     181             : 
     182         402 :         featureTypeDef.anRecordIdx.push_back(iRecord);
     183             :     }
     184             : 
     185         527 :     for (auto &[key, def] : oMapFeatureClassAndGeomType)
     186             :     {
     187         285 :         std::string osLayerCode;
     188         285 :         std::string osName;
     189         285 :         const auto oIter = m_featureTypeCodes.find(key.nFeatureTypeCode);
     190         285 :         if (oIter != m_featureTypeCodes.end())
     191             :         {
     192         283 :             osLayerCode = oIter->second;
     193         283 :             osName = osLayerCode;
     194             :         }
     195             :         else
     196             :         {
     197           2 :             if (!EMIT_ERROR_OR_WARNING(CPLSPrintf(
     198             :                     "Features pointing at unknown feature type code %d",
     199             :                     static_cast<int>(key.nFeatureTypeCode))))
     200             :             {
     201           1 :                 return false;
     202             :             }
     203             :             osName = CPLSPrintf("unknownFeatureType%d",
     204           1 :                                 static_cast<int>(key.nFeatureTypeCode));
     205             :         }
     206             : 
     207         284 :         const auto oIterSRS = m_oMapSRS.find(key.nCRSId);
     208         284 :         CPLAssert(key.nCRSId == INVALID_CRS_ID || oIterSRS != m_oMapSRS.end());
     209             :         const OGRSpatialReference *poSRS =
     210         284 :             key.nCRSId != INVALID_CRS_ID ? &(oIterSRS->second) : nullptr;
     211         284 :         const bool bIs2D = key.nCRSId == HORIZONTAL_CRS_ID;
     212             : 
     213         284 :         OGRwkbGeometryType eGeomType = wkbNone;
     214         284 :         const char *pszExpectedPermittedPrimitive = nullptr;
     215         284 :         switch (static_cast<int>(key.nGeometryType))
     216             :         {
     217          36 :             case static_cast<int>(PSEUDO_RECORD_NAME_NO_GEOM):
     218             :             {
     219          36 :                 osName += "_NoGeom";
     220          36 :                 pszExpectedPermittedPrimitive =
     221             :                     OGRS101FeatureCatalog::PERMITTED_PRIMITIVE_NO_GEOMETRY;
     222          36 :                 break;
     223             :             }
     224             : 
     225          30 :             case static_cast<int>(RECORD_NAME_POINT):
     226             :             {
     227          30 :                 CPLAssert(poSRS);
     228          30 :                 osName += '_';
     229          30 :                 if (def.bMultiSpatialAssociations)
     230             :                 {
     231           0 :                     osName += GetMultiPointLayerName(*poSRS);
     232           0 :                     eGeomType = bIs2D ? wkbMultiPoint : wkbMultiPoint25D;
     233             :                 }
     234             :                 else
     235             :                 {
     236          30 :                     osName += GetPointLayerName(*poSRS);
     237          30 :                     eGeomType = bIs2D ? wkbPoint : wkbPoint25D;
     238             :                 }
     239          30 :                 pszExpectedPermittedPrimitive =
     240             :                     OGRS101FeatureCatalog::PERMITTED_PRIMITIVE_POINT;
     241          30 :                 break;
     242             :             }
     243             : 
     244          92 :             case static_cast<int>(RECORD_NAME_MULTIPOINT):
     245             :             {
     246          92 :                 CPLAssert(poSRS);
     247          92 :                 osName += '_';
     248          92 :                 if (def.bMultiSpatialAssociations)
     249             :                 {
     250          31 :                     osName += "CollectionOfMultiPoint";
     251          31 :                     eGeomType = wkbGeometryCollection;
     252             :                 }
     253             :                 else
     254             :                 {
     255          61 :                     osName += GetMultiPointLayerName(*poSRS);
     256          61 :                     eGeomType = bIs2D ? wkbMultiPoint : wkbMultiPoint25D;
     257             :                 }
     258          92 :                 pszExpectedPermittedPrimitive =
     259             :                     OGRS101FeatureCatalog::PERMITTED_PRIMITIVE_POINTSET;
     260          92 :                 break;
     261             :             }
     262             : 
     263          62 :             case static_cast<int>(RECORD_NAME_CURVE):
     264             :             {
     265          62 :                 if (def.bMultiSpatialAssociations)
     266             :                 {
     267          31 :                     osName += "_MultiLine";
     268          31 :                     eGeomType = wkbMultiLineString;
     269             :                 }
     270             :                 else
     271             :                 {
     272          31 :                     osName += "_Line";
     273          31 :                     eGeomType = wkbLineString;
     274             :                 }
     275          62 :                 pszExpectedPermittedPrimitive =
     276             :                     OGRS101FeatureCatalog::PERMITTED_PRIMITIVE_CURVE;
     277          62 :                 break;
     278             :             }
     279             : 
     280          62 :             case static_cast<int>(RECORD_NAME_SURFACE):
     281             :             {
     282          62 :                 if (def.bMultiSpatialAssociations)
     283             :                 {
     284          31 :                     osName += "_MultiPolygon";
     285          31 :                     eGeomType = wkbMultiPolygon;
     286             :                 }
     287             :                 else
     288             :                 {
     289          31 :                     osName += "_Polygon";
     290          31 :                     eGeomType = wkbPolygon;
     291             :                 }
     292          62 :                 pszExpectedPermittedPrimitive =
     293             :                     OGRS101FeatureCatalog::PERMITTED_PRIMITIVE_SURFACE;
     294          62 :                 break;
     295             :             }
     296             : 
     297           2 :             default:
     298             :             {
     299           2 :                 if (!EMIT_ERROR_OR_WARNING(CPLSPrintf(
     300             :                         "Features pointing at unknown spatial record type %d",
     301             :                         static_cast<int>(key.nGeometryType))))
     302             :                 {
     303           1 :                     return false;
     304             :                 }
     305           1 :                 osName += "_UnknownGeomType";
     306           1 :                 osName += std::to_string(static_cast<int>(key.nGeometryType));
     307           1 :                 break;
     308             :             }
     309             :         }
     310             : 
     311         283 :         const OGRS101FeatureCatalog::FeatureType *psFeatureType = nullptr;
     312         283 :         if (m_poFeatureCatalog)
     313             :         {
     314             :             const auto oIterFT =
     315         283 :                 m_poFeatureCatalog->GetFeatureTypes().find(osLayerCode);
     316         283 :             if (oIterFT != m_poFeatureCatalog->GetFeatureTypes().end())
     317             :             {
     318           0 :                 psFeatureType = &(oIterFT->second);
     319             :             }
     320         284 :             else if (!cpl::starts_with(osLayerCode, "FeatureType") &&
     321           1 :                      !EMIT_ERROR_OR_WARNING(
     322             :                          CPLSPrintf("Feature type %s is not referenced in the "
     323             :                                     "feature catalog",
     324             :                                     osLayerCode.c_str())))
     325             :             {
     326           0 :                 return false;
     327             :             }
     328             :         }
     329             : 
     330         282 :         if (pszExpectedPermittedPrimitive && psFeatureType &&
     331           0 :             !cpl::contains(psFeatureType->permittedPrimitives,
     332         565 :                            pszExpectedPermittedPrimitive) &&
     333           0 :             !EMIT_ERROR_OR_WARNING(CPLSPrintf(
     334             :                 "Features of %s contain records with primitive %s, whereas "
     335             :                 "it is not allowed by the feature catalog",
     336             :                 osLayerCode.c_str(), pszExpectedPermittedPrimitive)))
     337             :         {
     338           0 :             return false;
     339             :         }
     340             : 
     341             :         auto poFDefn =
     342         283 :             OGRFeatureDefnRefCountedPtr::makeInstance(osName.c_str());
     343         283 :         poFDefn->SetGeomType(eGeomType);
     344        1415 :         for (const char *pszOGRFieldName :
     345             :              {OGR_FIELD_NAME_RECORD_ID, OGR_FIELD_NAME_RECORD_VERSION,
     346        1698 :               OGR_FIELD_NAME_AGEN, OGR_FIELD_NAME_FIDN, OGR_FIELD_NAME_FIDS})
     347             :         {
     348        2830 :             OGRFieldDefn oFieldDefn(pszOGRFieldName, OFTInteger);
     349        1415 :             poFDefn->AddFieldDefn(&oFieldDefn);
     350             :         }
     351             : 
     352         283 :         if (eGeomType != wkbNone)
     353             :         {
     354         246 :             CPLAssert(poSRS);
     355         492 :             poFDefn->GetGeomFieldDefn(0)->SetSpatialRef(
     356         492 :                 OGRSpatialReferenceRefCountedPtr::makeClone(poSRS).get());
     357         399 :             const bool bList = def.bMultiSpatialAssociations ||
     358         153 :                                def.bPromotedToMultiPointFromPoint;
     359             : 
     360             :             {
     361             :                 OGRFieldDefn oFieldDefn(OGR_FIELD_NAME_GEOMETRY_LAYER_NAME,
     362         492 :                                         bList ? OFTStringList : OFTString);
     363         246 :                 poFDefn->AddFieldDefn(&oFieldDefn);
     364             :             }
     365         246 :             for (const char *pszOGRFieldName :
     366         492 :                  {OGR_FIELD_NAME_GEOMETRY_RECORD_ID})
     367             :             {
     368             :                 OGRFieldDefn oFieldDefn(pszOGRFieldName,
     369         492 :                                         bList ? OFTIntegerList : OFTInteger);
     370         246 :                 poFDefn->AddFieldDefn(&oFieldDefn);
     371             :             }
     372         246 :             if (eGeomType == wkbLineString || eGeomType == wkbMultiLineString)
     373             :             {
     374             :                 OGRFieldDefn oFieldDefn(OGR_FIELD_NAME_GEOMETRY_ORIENTATION,
     375         124 :                                         bList ? OFTStringList : OFTString);
     376          62 :                 poFDefn->AddFieldDefn(&oFieldDefn);
     377             :             }
     378         492 :             for (const char *pszOGRFieldName :
     379         738 :                  {OGR_FIELD_NAME_SMIN, OGR_FIELD_NAME_SMAX})
     380             :             {
     381             :                 OGRFieldDefn oFieldDefn(pszOGRFieldName,
     382         984 :                                         bList ? OFTIntegerList : OFTInteger);
     383         492 :                 poFDefn->AddFieldDefn(&oFieldDefn);
     384             :             }
     385             :         }
     386             : 
     387         849 :         for (const char *pszAttrFieldName :
     388        1132 :              {ATTR_FIELD, INAS_FIELD, FASC_FIELD})
     389             :         {
     390        1698 :             if (!InferFeatureDefn(
     391         849 :                     m_oFeatureTypeRecordIndex, FRID_FIELD, pszAttrFieldName,
     392         849 :                     def.anRecordIdx, *poFDefn, m_oMapFieldDomains, nullptr,
     393         849 :                     strcmp(pszAttrFieldName, ATTR_FIELD) == 0 ? psFeatureType
     394             :                                                               : nullptr))
     395             :             {
     396           0 :                 return false;
     397             :             }
     398             :         }
     399             : 
     400         283 :         if (def.nMaxMaskCount == 1)
     401             :         {
     402             :             {
     403             :                 OGRFieldDefn oFieldDefn(OGR_FIELD_NAME_MASK_LAYER_NAME,
     404           0 :                                         OFTString);
     405           0 :                 poFDefn->AddFieldDefn(&oFieldDefn);
     406             :             }
     407             :             {
     408             :                 OGRFieldDefn oFieldDefn(OGR_FIELD_NAME_MASK_RECORD_ID,
     409           0 :                                         OFTInteger);
     410           0 :                 poFDefn->AddFieldDefn(&oFieldDefn);
     411             :             }
     412             :             {
     413             :                 OGRFieldDefn oFieldDefn(OGR_FIELD_NAME_MASK_INDICATOR,
     414           0 :                                         OFTString);
     415           0 :                 poFDefn->AddFieldDefn(&oFieldDefn);
     416             :             }
     417             : 
     418           0 :             OGRGeomFieldDefn oGeomFieldDefn("maskGeometry", wkbLineString);
     419           0 :             oGeomFieldDefn.SetSpatialRef(
     420           0 :                 OGRSpatialReferenceRefCountedPtr::makeClone(
     421           0 :                     &(m_oMapSRS[HORIZONTAL_CRS_ID]))
     422           0 :                     .get());
     423           0 :             poFDefn->AddGeomFieldDefn(&oGeomFieldDefn);
     424             :         }
     425         283 :         else if (def.nMaxMaskCount > 1)
     426             :         {
     427             :             {
     428             :                 OGRFieldDefn oFieldDefn(OGR_FIELD_NAME_MASK_LAYER_NAME,
     429          62 :                                         OFTStringList);
     430          31 :                 poFDefn->AddFieldDefn(&oFieldDefn);
     431             :             }
     432             :             {
     433             :                 OGRFieldDefn oFieldDefn(OGR_FIELD_NAME_MASK_RECORD_ID,
     434          62 :                                         OFTIntegerList);
     435          31 :                 poFDefn->AddFieldDefn(&oFieldDefn);
     436             :             }
     437             :             {
     438             :                 OGRFieldDefn oFieldDefn(OGR_FIELD_NAME_MASK_INDICATOR,
     439          62 :                                         OFTStringList);
     440          31 :                 poFDefn->AddFieldDefn(&oFieldDefn);
     441             :             }
     442             : 
     443          62 :             OGRGeomFieldDefn oGeomFieldDefn("maskGeometry", wkbMultiLineString);
     444          31 :             oGeomFieldDefn.SetSpatialRef(
     445          31 :                 OGRSpatialReferenceRefCountedPtr::makeClone(
     446          31 :                     &(m_oMapSRS[HORIZONTAL_CRS_ID]))
     447          31 :                     .get());
     448          31 :             poFDefn->AddGeomFieldDefn(&oGeomFieldDefn);
     449             :         }
     450             : 
     451         659 :         for (int nRecordIdx : def.anRecordIdx)
     452             :         {
     453             :             const int nRCID =
     454             :                 m_oFeatureTypeRecordIndex.GetByIndex(nRecordIdx)
     455         376 :                     ->GetIntSubfield(FRID_FIELD, 0, RCID_SUBFIELD, 0);
     456         376 :             m_oMapFeatureTypeIdToFDefn[nRCID] = poFDefn.get();
     457             :         }
     458             : 
     459         566 :         LayerDef layerDef;
     460         283 :         layerDef.poFeatureDefn = std::move(poFDefn);
     461         283 :         if (psFeatureType)
     462             :         {
     463           0 :             layerDef.osName = psFeatureType->name;
     464           0 :             layerDef.osDefinition = psFeatureType->definition;
     465           0 :             layerDef.osAlias = psFeatureType->alias;
     466             :         }
     467         283 :         layerDef.anRecordIndices = std::move(def.anRecordIdx);
     468         283 :         m_oMapFeatureKeyToLayerDef[key] = std::move(layerDef);
     469             :     }
     470             : 
     471         242 :     return true;
     472             : }
     473             : 
     474             : /************************************************************************/
     475             : /*                            ReadGeometry()                            */
     476             : /************************************************************************/
     477             : 
     478             : template <typename T, typename GeomReaderMethodType>
     479        1145 : bool OGRS101Reader::ReadGeometry(
     480             :     const DDFRecordIndex &oIndex, const char *pszErrorContext,
     481             :     int nGeomRecordID, const char *pszGeomType, bool bReverse,
     482             :     OGRFeature &oFeature, std::unique_ptr<OGRGeometryCollection> &poMultiGeom,
     483             :     GeomReaderMethodType geomReaderMethod, int iGeomField) const
     484             : {
     485        1145 :     const auto poGeomRecord = oIndex.FindRecord(nGeomRecordID);
     486             : 
     487        1145 :     if (!poGeomRecord && !EMIT_ERROR_OR_WARNING(CPLSPrintf(
     488             :                              "%s: %s of ID=%d does not exist", pszErrorContext,
     489             :                              pszGeomType, nGeomRecordID)))
     490             :     {
     491           3 :         return false;
     492             :     }
     493             : 
     494             :     const auto poGeomFieldDefn =
     495        1142 :         oFeature.GetDefnRef()->GetGeomFieldDefn(iGeomField);
     496        1142 :     const OGRSpatialReference *poSRS = poGeomFieldDefn->GetSpatialRef();
     497        1142 :     std::unique_ptr<T> poGeom;
     498        1142 :     if (poGeomRecord)
     499             :     {
     500        1135 :         poGeom = (this->*geomReaderMethod)(poGeomRecord, /* index = */ -1,
     501             :                                            nGeomRecordID, poSRS);
     502             :     }
     503        1142 :     if (!poGeom)
     504             :     {
     505           7 :         if (poGeomRecord && !EMIT_ERROR_OR_WARNING(CPLSPrintf(
     506             :                                 "%s: %s of ID=%d is invalid", pszErrorContext,
     507             :                                 pszGeomType, nGeomRecordID)))
     508             :         {
     509           0 :             return false;
     510             :         }
     511             :     }
     512             :     else if constexpr (std::is_same_v<T, OGRLineString>)
     513             :     {
     514         469 :         if (bReverse)
     515         103 :             poGeom->reversePoints();
     516             :     }
     517             : 
     518        1142 :     const auto eGeomType = poGeomFieldDefn->GetType();
     519             : 
     520        2120 :     if (wkbFlatten(eGeomType) == wkbGeometryCollection ||
     521             :         (!std::is_same_v<T, OGRMultiPoint> &&
     522         978 :          OGR_GT_IsSubClassOf(eGeomType, wkbGeometryCollection)))
     523             :     {
     524         824 :         if (!poMultiGeom)
     525             :         {
     526         378 :             poMultiGeom = std::make_unique<typename T::MultiType>();
     527         378 :             poMultiGeom->assignSpatialReference(poSRS);
     528             :         }
     529         824 :         poMultiGeom->addGeometry(poGeom ? std::move(poGeom)
     530             :                                         : std::make_unique<T>());
     531             :     }
     532             :     else
     533             :     {
     534         318 :         oFeature.SetGeomField(iGeomField, std::move(poGeom));
     535             :     }
     536             : 
     537        1142 :     return true;
     538             : }
     539             : 
     540             : /************************************************************************/
     541             : /*                      FillFeatureTypeGeometry()                       */
     542             : /************************************************************************/
     543             : 
     544             : /** Fill the geometry of the provided feature from the identified record
     545             :  * (of m_oFeatureTypeRecordIndex).
     546             :  */
     547         707 : bool OGRS101Reader::FillFeatureTypeGeometry(const DDFRecord *poRecord,
     548             :                                             int iRecord,
     549             :                                             OGRFeature &oFeature) const
     550             : {
     551             :     // Process geometry (several spatial association per feature possible)
     552        1414 :     CPLStringList aosLayerNames, aosOrientations;
     553        1414 :     std::vector<int> anRRID, anSMIN, anSMAX;
     554         707 :     std::unique_ptr<OGRGeometryCollection> poMultiGeom;
     555             : 
     556        1414 :     const auto apoSPASFields = poRecord->GetFields(SPAS_FIELD);
     557        1394 :     for (const auto &poSPASField : apoSPASFields)
     558             :     {
     559         693 :         const int nSPASCount = poSPASField->GetRepeatCount();
     560             : 
     561        1628 :         for (int iSPAS = 0; iSPAS < nSPASCount; ++iSPAS)
     562             :         {
     563             :             const auto GetIntSubfield =
     564        5629 :                 [poRecord, poSPASField, iSPAS](const char *pszSubFieldName)
     565             :             {
     566        5629 :                 return poRecord->GetIntSubfield(poSPASField, pszSubFieldName,
     567        5629 :                                                 iSPAS);
     568         941 :             };
     569             : 
     570             :             const std::string osErrorContext =
     571             :                 CPLSPrintf("Feature type record index %d, SPAS instance %d",
     572         941 :                            iRecord, iSPAS);
     573             : 
     574         941 :             const int nSAUI = GetIntSubfield("SAUI");
     575         941 :             if (nSAUI != INSTRUCTION_INSERT)
     576             :             {
     577           3 :                 if (!EMIT_ERROR_OR_WARNING(
     578             :                         CPLSPrintf("%s: SAUI value %d is invalid",
     579             :                                    osErrorContext.c_str(), nSAUI)))
     580             :                 {
     581           1 :                     return false;
     582             :                 }
     583             :             }
     584             : 
     585         940 :             const RecordName nRRNM = GetIntSubfield(RRNM_SUBFIELD);
     586             : 
     587         940 :             const int nRRID = GetIntSubfield(RRID_SUBFIELD);
     588             : 
     589         940 :             const int nORNT = GetIntSubfield(ORNT_SUBFIELD);
     590             : 
     591        1659 :             const bool bIsLine = nRRNM == RECORD_NAME_CURVE ||
     592         719 :                                  nRRNM == RECORD_NAME_COMPOSITE_CURVE;
     593         940 :             switch (nORNT)
     594             :             {
     595         164 :                 case ORNT_FORWARD:
     596             :                 {
     597         164 :                     break;
     598             :                 }
     599             : 
     600         106 :                 case ORNT_REVERSE:
     601             :                 {
     602         106 :                     if (!bIsLine)
     603             :                     {
     604           3 :                         if (!EMIT_ERROR_OR_WARNING(
     605             :                                 CPLSPrintf("%s: "
     606             :                                            "ORNT = Reverse is invalid for "
     607             :                                            "non-curve geometry",
     608             :                                            osErrorContext.c_str())))
     609             :                         {
     610           1 :                             return false;
     611             :                         }
     612             :                     }
     613         105 :                     break;
     614             :                 }
     615             : 
     616         667 :                 case ORNT_NULL:
     617             :                 {
     618         667 :                     if (bIsLine)
     619             :                     {
     620           0 :                         if (!EMIT_ERROR_OR_WARNING(CPLSPrintf(
     621             :                                 "%s: ORNT = Null is invalid for curve geometry",
     622             :                                 osErrorContext.c_str())))
     623             :                         {
     624           0 :                             return false;
     625             :                         }
     626             :                     }
     627         667 :                     break;
     628             :                 }
     629             : 
     630           3 :                 default:
     631             :                 {
     632           3 :                     if (!EMIT_ERROR_OR_WARNING(
     633             :                             CPLSPrintf("%s: ORNT = %d is invalid",
     634             :                                        osErrorContext.c_str(), nORNT)))
     635             :                     {
     636           1 :                         return false;
     637             :                     }
     638           2 :                     break;
     639             :                 }
     640             :             }
     641             : 
     642         938 :             bool bInvalidType = false;
     643         938 :             switch (static_cast<int>(nRRNM))
     644             :             {
     645         302 :                 case static_cast<int>(RECORD_NAME_POINT):
     646             :                 {
     647         604 :                     if (!ReadGeometry<OGRPoint>(
     648         302 :                             m_oPointRecordIndex, osErrorContext.c_str(), nRRID,
     649             :                             "Point", false, oFeature, poMultiGeom,
     650             :                             &OGRS101Reader::ReadPointGeometry))
     651             :                     {
     652           0 :                         return false;
     653             :                     }
     654             : 
     655             :                     const auto poGeomFieldDefn =
     656         302 :                         oFeature.GetDefnRef()->GetGeomFieldDefn(0);
     657         302 :                     CPLAssert(poGeomFieldDefn);
     658             :                     const OGRSpatialReference *poSRS =
     659         302 :                         poGeomFieldDefn->GetSpatialRef();
     660         302 :                     CPLAssert(poSRS);
     661             : 
     662         302 :                     aosLayerNames.push_back(GetPointLayerName(*poSRS).c_str());
     663             : 
     664         302 :                     break;
     665             :                 }
     666             : 
     667         164 :                 case static_cast<int>(RECORD_NAME_MULTIPOINT):
     668             :                 {
     669         328 :                     if (!ReadGeometry<OGRMultiPoint>(
     670         164 :                             m_oMultiPointRecordIndex, osErrorContext.c_str(),
     671             :                             nRRID, "MultiPoint", false, oFeature, poMultiGeom,
     672             :                             &OGRS101Reader::ReadMultiPointGeometry))
     673             :                     {
     674           0 :                         return false;
     675             :                     }
     676             : 
     677             :                     const auto poGeomFieldDefn =
     678         164 :                         oFeature.GetDefnRef()->GetGeomFieldDefn(0);
     679         164 :                     CPLAssert(poGeomFieldDefn);
     680             :                     const OGRSpatialReference *poSRS =
     681         164 :                         poGeomFieldDefn->GetSpatialRef();
     682         164 :                     CPLAssert(poSRS);
     683             : 
     684         164 :                     aosLayerNames.push_back(
     685         328 :                         GetMultiPointLayerName(*poSRS).c_str());
     686             : 
     687         164 :                     break;
     688             :                 }
     689             : 
     690         267 :                 case static_cast<int>(RECORD_NAME_CURVE):
     691             :                 case static_cast<int>(RECORD_NAME_COMPOSITE_CURVE):
     692             :                 {
     693             :                     const char *pszLayerName =
     694         267 :                         nRRNM == RECORD_NAME_CURVE
     695             :                             ? OGR_LAYER_NAME_CURVE
     696         267 :                             : OGR_LAYER_NAME_COMPOSITE_CURVE;
     697             : 
     698             :                     bool ret;
     699         267 :                     if (nRRNM == RECORD_NAME_CURVE)
     700             :                     {
     701         442 :                         ret = ReadGeometry<OGRLineString>(
     702         221 :                             m_oCurveRecordIndex, osErrorContext.c_str(), nRRID,
     703             :                             pszLayerName, nORNT == ORNT_REVERSE, oFeature,
     704             :                             poMultiGeom, &OGRS101Reader::ReadCurveGeometry);
     705             :                     }
     706             :                     else
     707             :                     {
     708          92 :                         ret = ReadGeometry<OGRLineString>(
     709          46 :                             m_oCompositeCurveRecordIndex,
     710             :                             osErrorContext.c_str(), nRRID, pszLayerName,
     711             :                             nORNT == ORNT_REVERSE, oFeature, poMultiGeom,
     712             :                             &OGRS101Reader::ReadCompositeCurveGeometry);
     713             :                     }
     714             : 
     715         267 :                     if (!ret)
     716             :                     {
     717           2 :                         return false;
     718             :                     }
     719             : 
     720         265 :                     aosLayerNames.push_back(pszLayerName);
     721         265 :                     aosOrientations.push_back(
     722             :                         nORNT == ORNT_FORWARD ? "forward" : "reverse");
     723             : 
     724         265 :                     break;
     725             :                 }
     726             : 
     727         204 :                 case static_cast<int>(RECORD_NAME_SURFACE):
     728             :                 {
     729         408 :                     if (!ReadGeometry<OGRPolygon>(
     730         204 :                             m_oSurfaceRecordIndex, osErrorContext.c_str(),
     731             :                             nRRID, OGR_LAYER_NAME_SURFACE, false, oFeature,
     732             :                             poMultiGeom, &OGRS101Reader::ReadSurfaceGeometry))
     733             :                     {
     734           1 :                         return false;
     735             :                     }
     736             : 
     737         203 :                     aosLayerNames.push_back(OGR_LAYER_NAME_SURFACE);
     738             : 
     739         203 :                     break;
     740             :                 }
     741             : 
     742           1 :                 default:
     743           1 :                     bInvalidType = true;
     744           1 :                     break;
     745             :             }
     746             : 
     747         935 :             if (bInvalidType)
     748             :             {
     749           1 :                 if (!EMIT_ERROR_OR_WARNING(CPLSPrintf("%s: "
     750             :                                                       "Invalid RRNM = %d",
     751             :                                                       osErrorContext.c_str(),
     752             :                                                       static_cast<int>(nRRNM))))
     753             :                 {
     754           0 :                     return false;
     755             :                 }
     756             :             }
     757             :             else
     758             :             {
     759         934 :                 anRRID.push_back(nRRID);
     760             : 
     761         934 :                 anSMIN.push_back(GetIntSubfield("SMIN"));
     762             : 
     763         934 :                 anSMAX.push_back(GetIntSubfield("SMAX"));
     764             :             }
     765             :         }
     766             :     }
     767             : 
     768         701 :     if (!apoSPASFields.empty())
     769             :     {
     770         627 :         CPLAssert(anRRID.size() == anSMIN.size());
     771         627 :         CPLAssert(anRRID.size() == anSMAX.size());
     772         627 :         CPLAssert(anRRID.size() == static_cast<size_t>(aosLayerNames.size()));
     773         627 :         CPLAssert(aosOrientations.empty() ||
     774             :                   anRRID.size() == static_cast<size_t>(aosOrientations.size()));
     775             : 
     776         627 :         if (poMultiGeom)
     777             :         {
     778         308 :             oFeature.SetGeometry(std::move(poMultiGeom));
     779             : 
     780         308 :             oFeature.SetField(OGR_FIELD_NAME_GEOMETRY_LAYER_NAME,
     781         308 :                               aosLayerNames.List());
     782         308 :             if (!aosOrientations.empty())
     783             :             {
     784          57 :                 oFeature.SetField(OGR_FIELD_NAME_GEOMETRY_ORIENTATION,
     785          57 :                                   aosOrientations.List());
     786             :             }
     787         308 :             oFeature.SetField(OGR_FIELD_NAME_GEOMETRY_RECORD_ID,
     788         308 :                               static_cast<int>(anRRID.size()), anRRID.data());
     789         308 :             if (std::find_if(anSMIN.begin(), anSMIN.end(),
     790         735 :                              [](int x) { return x > 0; }) != anSMIN.end())
     791             :             {
     792         189 :                 oFeature.SetField(OGR_FIELD_NAME_SMIN,
     793         189 :                                   static_cast<int>(anSMIN.size()),
     794         189 :                                   anSMIN.data());
     795             :             }
     796         308 :             if (std::find_if(anSMAX.begin(), anSMAX.end(),
     797         735 :                              [](int x) { return x > 0; }) != anSMAX.end())
     798             :             {
     799         189 :                 oFeature.SetField(OGR_FIELD_NAME_SMAX,
     800         189 :                                   static_cast<int>(anSMAX.size()),
     801         189 :                                   anSMAX.data());
     802             :             }
     803             :         }
     804         319 :         else if (!aosLayerNames.empty())
     805             :         {
     806         318 :             oFeature.SetField(OGR_FIELD_NAME_GEOMETRY_LAYER_NAME,
     807         318 :                               aosLayerNames[0]);
     808         318 :             oFeature.SetField(OGR_FIELD_NAME_GEOMETRY_RECORD_ID, anRRID[0]);
     809         318 :             if (!aosOrientations.empty())
     810             :             {
     811         151 :                 oFeature.SetField(OGR_FIELD_NAME_GEOMETRY_ORIENTATION,
     812         151 :                                   aosOrientations[0]);
     813             :             }
     814         318 :             if (anSMIN[0] > 0)
     815         254 :                 oFeature.SetField(OGR_FIELD_NAME_SMIN, anSMIN[0]);
     816         318 :             if (anSMAX[0] > 0)
     817         254 :                 oFeature.SetField(OGR_FIELD_NAME_SMAX, anSMAX[0]);
     818             :         }
     819             :     }
     820             : 
     821         701 :     return true;
     822             : }
     823             : 
     824             : /************************************************************************/
     825             : /*                        FillFeatureTypeMask()                         */
     826             : /************************************************************************/
     827             : 
     828             : /** Fill the mask info of the provided feature from the identified record
     829             :  * (of m_oFeatureTypeRecordIndex).
     830             :  */
     831         701 : bool OGRS101Reader::FillFeatureTypeMask(const DDFRecord *poRecord, int iRecord,
     832             :                                         OGRFeature &oFeature) const
     833             : {
     834         701 :     std::unique_ptr<OGRGeometryCollection> poMultiGeom;
     835             : 
     836        1402 :     std::vector<int> anRRID;
     837        1402 :     CPLStringList aosLayerNames;
     838        1402 :     CPLStringList aosMaskIndicators;
     839             : 
     840         701 :     constexpr int MASK_GEOM_FIELD_IDX = 1;
     841             : 
     842        1402 :     const auto apoMASKFields = poRecord->GetFields(MASK_FIELD);
     843         841 :     for (const auto *poMASKField : apoMASKFields)
     844             :     {
     845         143 :         const int nMaskCount = poMASKField->GetRepeatCount();
     846             : 
     847         353 :         for (int iMASK = 0; iMASK < nMaskCount; ++iMASK)
     848             :         {
     849             :             const auto GetIntSubfield =
     850         847 :                 [poRecord, poMASKField, iMASK](const char *pszSubFieldName)
     851             :             {
     852         847 :                 return poRecord->GetIntSubfield(poMASKField, pszSubFieldName,
     853         847 :                                                 iMASK);
     854         213 :             };
     855             : 
     856             :             const std::string osErrorContext =
     857             :                 CPLSPrintf("Feature type record index %d, MASK instance %d",
     858         213 :                            iRecord, iMASK);
     859             : 
     860         213 :             const int nMUIN = GetIntSubfield("MUIN");
     861         213 :             if (nMUIN != INSTRUCTION_INSERT)
     862             :             {
     863           3 :                 if (!EMIT_ERROR_OR_WARNING(
     864             :                         CPLSPrintf("%s: MUIN value %d is invalid",
     865             :                                    osErrorContext.c_str(), nMUIN)))
     866             :                 {
     867           1 :                     return false;
     868             :                 }
     869             :             }
     870             : 
     871         212 :             const int nMIND = GetIntSubfield("MIND");
     872         212 :             constexpr int MASK_INDICATOR_TRUNCATED_BY_DATA_COVERAGE_LIMIT = 1;
     873         212 :             constexpr int MASK_INDICATOR_SUPPRESS_PORTRAYAL = 2;
     874         212 :             if (nMIND == MASK_INDICATOR_TRUNCATED_BY_DATA_COVERAGE_LIMIT)
     875             :             {
     876         139 :                 aosMaskIndicators.push_back("truncatedByDataCoverageLimit");
     877             :             }
     878          73 :             else if (nMIND == MASK_INDICATOR_SUPPRESS_PORTRAYAL)
     879             :             {
     880          70 :                 aosMaskIndicators.push_back("suppressPortrayal");
     881             :             }
     882             :             else
     883             :             {
     884           3 :                 if (!EMIT_ERROR_OR_WARNING(CPLSPrintf(
     885             :                         "%s: MIND value %d is invalid. Expected %d or %d",
     886             :                         osErrorContext.c_str(), nMIND,
     887             :                         MASK_INDICATOR_TRUNCATED_BY_DATA_COVERAGE_LIMIT,
     888             :                         MASK_INDICATOR_SUPPRESS_PORTRAYAL)))
     889             :                 {
     890           1 :                     return false;
     891             :                 }
     892           2 :                 aosMaskIndicators.push_back(CPLSPrintf("unknown%d", nMIND));
     893             :             }
     894             : 
     895         211 :             const int nRRID = GetIntSubfield(RRID_SUBFIELD);
     896         211 :             anRRID.push_back(nRRID);
     897             : 
     898         211 :             const RecordName nRRNM = GetIntSubfield(RRNM_SUBFIELD);
     899         284 :             if (nRRNM == RECORD_NAME_CURVE ||
     900          73 :                 nRRNM == RECORD_NAME_COMPOSITE_CURVE)
     901             :             {
     902         208 :                 const char *pszLayerName = nRRNM == RECORD_NAME_CURVE
     903             :                                                ? OGR_LAYER_NAME_CURVE
     904         208 :                                                : OGR_LAYER_NAME_COMPOSITE_CURVE;
     905             : 
     906             :                 bool ret;
     907         208 :                 if (nRRNM == RECORD_NAME_CURVE)
     908             :                 {
     909         276 :                     ret = ReadGeometry<OGRLineString>(
     910         138 :                         m_oCurveRecordIndex, osErrorContext.c_str(), nRRID,
     911             :                         pszLayerName, false, oFeature, poMultiGeom,
     912             :                         &OGRS101Reader::ReadCurveGeometry, MASK_GEOM_FIELD_IDX);
     913             :                 }
     914             :                 else
     915             :                 {
     916         140 :                     ret = ReadGeometry<OGRLineString>(
     917          70 :                         m_oCompositeCurveRecordIndex, osErrorContext.c_str(),
     918             :                         nRRID, pszLayerName, false, oFeature, poMultiGeom,
     919             :                         &OGRS101Reader::ReadCompositeCurveGeometry,
     920             :                         MASK_GEOM_FIELD_IDX);
     921             :                 }
     922             : 
     923         208 :                 if (!ret)
     924             :                 {
     925           0 :                     return false;
     926             :                 }
     927             : 
     928         208 :                 aosLayerNames.push_back(pszLayerName);
     929             :             }
     930             :             else
     931             :             {
     932           3 :                 if (!EMIT_ERROR_OR_WARNING(CPLSPrintf(
     933             :                         "%s: Invalid value for RRNM subfield: "
     934             :                         "got %d, expected %d or %d.",
     935             :                         osErrorContext.c_str(), static_cast<int>(nRRNM),
     936             :                         static_cast<int>(RECORD_NAME_CURVE),
     937             :                         static_cast<int>(RECORD_NAME_COMPOSITE_CURVE))))
     938             :                 {
     939           1 :                     return false;
     940             :                 }
     941             : 
     942           2 :                 aosLayerNames.push_back("");
     943             :             }
     944             :         }
     945             :     }
     946             : 
     947         698 :     if (!aosLayerNames.empty())
     948             :     {
     949          70 :         CPLAssert(anRRID.size() == static_cast<size_t>(aosLayerNames.size()));
     950          70 :         CPLAssert(anRRID.size() ==
     951             :                   static_cast<size_t>(aosMaskIndicators.size()));
     952             : 
     953          70 :         if (poMultiGeom)
     954             :         {
     955          70 :             oFeature.SetGeomField(MASK_GEOM_FIELD_IDX, std::move(poMultiGeom));
     956             : 
     957          70 :             oFeature.SetField(OGR_FIELD_NAME_MASK_LAYER_NAME,
     958          70 :                               aosLayerNames.List());
     959             : 
     960          70 :             oFeature.SetField(OGR_FIELD_NAME_MASK_RECORD_ID,
     961          70 :                               static_cast<int>(anRRID.size()), anRRID.data());
     962             : 
     963          70 :             oFeature.SetField(OGR_FIELD_NAME_MASK_INDICATOR,
     964          70 :                               aosMaskIndicators.List());
     965             :         }
     966             :         else
     967             :         {
     968           0 :             oFeature.SetField(OGR_FIELD_NAME_MASK_LAYER_NAME, aosLayerNames[0]);
     969             : 
     970           0 :             oFeature.SetField(OGR_FIELD_NAME_MASK_RECORD_ID, anRRID[0]);
     971             : 
     972           0 :             oFeature.SetField(OGR_FIELD_NAME_MASK_INDICATOR,
     973           0 :                               aosMaskIndicators[0]);
     974             :         }
     975             :     }
     976             : 
     977         698 :     return true;
     978             : }
     979             : 
     980             : /************************************************************************/
     981             : /*                       FillFeatureFeatureType()                       */
     982             : /************************************************************************/
     983             : 
     984             : /** Fill the content of the provided feature from the identified record
     985             :  * (of m_oFeatureTypeRecordIndex).
     986             :  */
     987         707 : bool OGRS101Reader::FillFeatureFeatureType(const DDFRecordIndex &oIndex,
     988             :                                            int iRecord,
     989             :                                            OGRFeature &oFeature) const
     990             : {
     991         707 :     const auto poRecord = oIndex.GetByIndex(iRecord);
     992         707 :     CPLAssert(poRecord);
     993             : 
     994         707 :     if (const auto poFOIDField = poRecord->FindField(FOID_FIELD))
     995             :     {
     996         706 :         const int nAGEN = poRecord->GetIntSubfield(poFOIDField, "AGEN", 0);
     997         706 :         oFeature.SetField(OGR_FIELD_NAME_AGEN, nAGEN);
     998             : 
     999         706 :         const int nFIDN = poRecord->GetIntSubfield(poFOIDField, "FIDN", 0);
    1000         706 :         oFeature.SetField(OGR_FIELD_NAME_FIDN, nFIDN);
    1001             : 
    1002         706 :         const int nFIDS = poRecord->GetIntSubfield(poFOIDField, "FIDS", 0);
    1003         706 :         oFeature.SetField(OGR_FIELD_NAME_FIDS, nFIDS);
    1004             :     }
    1005           1 :     else if (!EMIT_ERROR_OR_WARNING(CPLSPrintf(
    1006             :                  "Feature type record index %d: no FOID field", iRecord)))
    1007             :     {
    1008           0 :         return false;
    1009             :     }
    1010             : 
    1011             :     // A FRID record might have a ATTR field, a INAS field (pointing to a IRID
    1012             :     // record), a FASC field (thus pointing to another FRID record), or any
    1013             :     // combination of the 3.
    1014             : 
    1015         707 :     return FillFeatureTypeGeometry(poRecord, iRecord, oFeature) &&
    1016         701 :            FillFeatureTypeMask(poRecord, iRecord, oFeature) &&
    1017         698 :            FillFeatureAttributes(oIndex, iRecord, ATTR_FIELD, oFeature) &&
    1018         698 :            FillFeatureAttributes(oIndex, iRecord, INAS_FIELD, oFeature) &&
    1019         698 :            FillFeatureAttributes(oIndex, iRecord, FASC_FIELD, oFeature) &&
    1020         698 :            FillFeatureWithNonAttrAssocSubfields(poRecord, iRecord, INAS_FIELD,
    1021        1408 :                                                 oFeature) &&
    1022         698 :            FillFeatureWithNonAttrAssocSubfields(poRecord, iRecord, FASC_FIELD,
    1023         707 :                                                 oFeature);
    1024             : }

Generated by: LCOV version 1.14