LCOV - code coverage report
Current view: top level - frmts/nitf - rpftocwriter.cpp (source / functions) Hit Total Coverage
Test: gdal_filtered.info Lines: 355 384 92.4 %
Date: 2026-03-05 10:33:42 Functions: 13 13 100.0 %

          Line data    Source code
       1             : /******************************************************************************
       2             :  *
       3             :  * Project:  NITF Read/Write Library
       4             :  * Purpose:  Creates A.TOC RPF index for CADRG frames
       5             :  * Author:   Even Rouault, even dot rouault at spatialys dot com
       6             :  *
       7             :  **********************************************************************
       8             :  * Copyright (c) 2026, T-Kartor
       9             :  *
      10             :  * SPDX-License-Identifier: MIT
      11             :  ****************************************************************************/
      12             : 
      13             : #include "cpl_vsi.h"
      14             : #include "gdal_dataset.h"
      15             : #include "nitflib.h"
      16             : #include "offsetpatcher.h"
      17             : #include "rpfframewriter.h"
      18             : #include "rpftocwriter.h"
      19             : 
      20             : #include <algorithm>
      21             : #include <cinttypes>
      22             : #include <limits>
      23             : #include <map>
      24             : #include <utility>
      25             : 
      26             : namespace
      27             : {
      28             : struct FrameDesc
      29             : {
      30             :     int nZone = 0;
      31             :     int nReciprocalScale = 0;
      32             :     int nFrameX = 0;
      33             :     int nFrameY = 0;
      34             :     double dfMinX = 0;
      35             :     double dfMinY = 0;
      36             :     std::string osRelativeFilename{};  // relative to osInputDirectory
      37             :     char chClassification = 'U';
      38             : };
      39             : 
      40             : struct MinMaxFrameXY
      41             : {
      42             :     int MinX = std::numeric_limits<int>::max();
      43             :     int MinY = std::numeric_limits<int>::max();
      44             :     int MaxX = 0;
      45             :     int MaxY = 0;
      46             : };
      47             : 
      48             : struct ScaleZone
      49             : {
      50             :     int nReciprocalScale = 0;
      51             :     int nZone = 0;
      52             : 
      53         207 :     bool operator<(const ScaleZone &other) const
      54             :     {
      55             :         // Sort reciprocal scale by decreasing order. This is apparently needed for
      56             :         // some viewers like Falcon Lite to be able to display A.TOC files
      57             :         // with multiple scales.
      58         405 :         return nReciprocalScale > other.nReciprocalScale ||
      59         198 :                (nReciprocalScale == other.nReciprocalScale &&
      60         401 :                 nZone < other.nZone);
      61             :     }
      62             : };
      63             : 
      64             : }  // namespace
      65             : 
      66             : /************************************************************************/
      67             : /*                  Create_RPFTOC_LocationComponent()                   */
      68             : /************************************************************************/
      69             : 
      70             : static void
      71          19 : Create_RPFTOC_LocationComponent(GDALOffsetPatcher::OffsetPatcher &offsetPatcher)
      72             : {
      73          19 :     auto poBuffer = offsetPatcher.CreateBuffer(
      74             :         "LocationComponent", /* bEndiannessIsLittle = */ false);
      75          19 :     CPLAssert(poBuffer);
      76          19 :     poBuffer->DeclareOffsetAtCurrentPosition("LOCATION_COMPONENT_LOCATION");
      77             : 
      78             :     static const struct
      79             :     {
      80             :         uint16_t locationId;
      81             :         const char *locationBufferName;
      82             :         const char *locationOffsetName;
      83             :     } asLocations[] = {
      84             :         {LID_BoundaryRectangleSectionSubheader /* 148 */,
      85             :          "BoundaryRectangleSectionSubheader",
      86             :          "BOUNDARY_RECTANGLE_SECTION_SUBHEADER_LOCATION"},
      87             :         {LID_BoundaryRectangleTable /* 149 */, "BoundaryRectangleTable",
      88             :          "BOUNDARY_RECTANGLE_TABLE_LOCATION"},
      89             :         {LID_FrameFileIndexSectionSubHeader /* 150 */,
      90             :          "FrameFileIndexSectionSubHeader",
      91             :          "FRAME_FILE_INDEX_SECTION_SUBHEADER_LOCATION"},
      92             :         {LID_FrameFileIndexSubsection /* 151 */, "FrameFileIndexSubsection",
      93             :          "FRAME_FILE_INDEX_SUBSECTION_LOCATION"},
      94             :     };
      95             : 
      96          38 :     std::string sumOfSizes;
      97          19 :     uint16_t nComponents = 0;
      98          95 :     for (const auto &sLocation : asLocations)
      99             :     {
     100          76 :         ++nComponents;
     101          76 :         if (!sumOfSizes.empty())
     102          57 :             sumOfSizes += '+';
     103          76 :         sumOfSizes += sLocation.locationBufferName;
     104             :     }
     105             : 
     106          19 :     constexpr uint16_t COMPONENT_LOCATION_OFFSET = 14;
     107          19 :     constexpr uint16_t COMPONENT_LOCATION_RECORD_LENGTH = 10;
     108          19 :     poBuffer->AppendUInt16RefForSizeOfBuffer("LocationComponent");
     109          19 :     poBuffer->AppendUInt32(COMPONENT_LOCATION_OFFSET);
     110          19 :     poBuffer->AppendUInt16(nComponents);
     111          19 :     poBuffer->AppendUInt16(COMPONENT_LOCATION_RECORD_LENGTH);
     112             :     // COMPONENT_AGGREGATE_LENGTH
     113          19 :     poBuffer->AppendUInt32RefForSizeOfBuffer(sumOfSizes);
     114             : 
     115          95 :     for (const auto &sLocation : asLocations)
     116             :     {
     117          76 :         poBuffer->AppendUInt16(sLocation.locationId);
     118          76 :         poBuffer->AppendUInt32RefForSizeOfBuffer(sLocation.locationBufferName);
     119          76 :         poBuffer->AppendUInt32RefForOffset(sLocation.locationOffsetName);
     120             :     }
     121          19 : }
     122             : 
     123             : /************************************************************************/
     124             : /*          Create_RPFTOC_BoundaryRectangleSectionSubheader()           */
     125             : /************************************************************************/
     126             : 
     127          19 : static void Create_RPFTOC_BoundaryRectangleSectionSubheader(
     128             :     GDALOffsetPatcher::OffsetPatcher &offsetPatcher,
     129             :     size_t nNumberOfBoundaryRectangles)
     130             : {
     131          19 :     auto poBuffer = offsetPatcher.CreateBuffer(
     132             :         "BoundaryRectangleSectionSubheader", /* bEndiannessIsLittle = */ false);
     133          19 :     CPLAssert(poBuffer);
     134          19 :     poBuffer->DeclareOffsetAtCurrentPosition(
     135             :         "BOUNDARY_RECTANGLE_SECTION_SUBHEADER_LOCATION");
     136          19 :     constexpr uint32_t BOUNDARY_RECTANGLE_TABLE_OFFSET = 0;
     137          19 :     poBuffer->AppendUInt32(BOUNDARY_RECTANGLE_TABLE_OFFSET);
     138          19 :     poBuffer->AppendUInt16(static_cast<uint16_t>(nNumberOfBoundaryRectangles));
     139          19 :     constexpr uint16_t BOUNDARY_RECTANGLE_RECORD_LENGTH = 132;
     140          19 :     poBuffer->AppendUInt16(BOUNDARY_RECTANGLE_RECORD_LENGTH);
     141          19 : }
     142             : 
     143             : /************************************************************************/
     144             : /*                           StrPadTruncate()                           */
     145             : /************************************************************************/
     146             : 
     147             : #ifndef StrPadTruncate_defined
     148             : #define StrPadTruncate_defined
     149             : 
     150         142 : static std::string StrPadTruncate(const std::string &osIn, size_t nSize)
     151             : {
     152         142 :     std::string osOut(osIn);
     153         142 :     osOut.resize(nSize, ' ');
     154         142 :     return osOut;
     155             : }
     156             : #endif
     157             : 
     158             : /************************************************************************/
     159             : /*                Create_RPFTOC_BoundaryRectangleTable()                */
     160             : /************************************************************************/
     161             : 
     162          19 : static void Create_RPFTOC_BoundaryRectangleTable(
     163             :     GDALOffsetPatcher::OffsetPatcher &offsetPatcher,
     164             :     const std::string &osProducer,
     165             :     const std::map<ScaleZone, MinMaxFrameXY> &oMapScaleZoneToMinMaxFrameXY)
     166             : {
     167          19 :     auto poBuffer = offsetPatcher.CreateBuffer(
     168             :         "BoundaryRectangleTable", /* bEndiannessIsLittle = */ false);
     169          19 :     CPLAssert(poBuffer);
     170          19 :     poBuffer->DeclareOffsetAtCurrentPosition(
     171             :         "BOUNDARY_RECTANGLE_TABLE_LOCATION");
     172             : 
     173          42 :     for (const auto &[scaleZone, extent] : oMapScaleZoneToMinMaxFrameXY)
     174             :     {
     175          23 :         poBuffer->AppendString("CADRG");  // PRODUCT_DATA_TYPE
     176          23 :         poBuffer->AppendString("55:1 ");  // COMPRESSION_RATIO
     177             : 
     178          46 :         std::string osScaleOrResolution;
     179          23 :         const int nReciprocalScale = scaleZone.nReciprocalScale;
     180          23 :         if (nReciprocalScale >= Million && (nReciprocalScale % Million) == 0)
     181          14 :             osScaleOrResolution =
     182          14 :                 CPLSPrintf("1:%dM", nReciprocalScale / Million);
     183           9 :         else if (nReciprocalScale >= Kilo && (nReciprocalScale % Kilo) == 0)
     184           9 :             osScaleOrResolution = CPLSPrintf("1:%dK", nReciprocalScale / Kilo);
     185             :         else
     186           0 :             osScaleOrResolution = CPLSPrintf("1:%d", nReciprocalScale);
     187          23 :         poBuffer->AppendString(StrPadTruncate(osScaleOrResolution, 12));
     188             : 
     189          23 :         const int nZone = scaleZone.nZone;
     190          23 :         poBuffer->AppendString(CPLSPrintf("%c", RPFCADRGZoneNumToChar(nZone)));
     191          23 :         poBuffer->AppendString(StrPadTruncate(osProducer, 5));
     192             : 
     193          23 :         double dfXMin = 0;
     194          23 :         double dfYMin = 0;
     195          23 :         double dfXMax = 0;
     196          23 :         double dfYMax = 0;
     197          23 :         double dfUnused = 0;
     198          23 :         RPFGetCADRGFrameExtent(nZone, nReciprocalScale, extent.MinX,
     199          23 :                                extent.MinY, dfXMin, dfYMin, dfUnused, dfUnused);
     200          23 :         RPFGetCADRGFrameExtent(nZone, nReciprocalScale, extent.MaxX,
     201          23 :                                extent.MaxY, dfUnused, dfUnused, dfXMax, dfYMax);
     202             : 
     203          23 :         double dfULX = dfXMin;
     204          23 :         double dfULY = dfYMax;
     205          23 :         double dfLLX = dfXMin;
     206          23 :         double dfLLY = dfYMin;
     207          23 :         double dfURX = dfXMax;
     208          23 :         double dfURY = dfYMax;
     209          23 :         double dfLRX = dfXMax;
     210          23 :         double dfLRY = dfYMin;
     211             : 
     212          23 :         if (nZone == MAX_ZONE_NORTHERN_HEMISPHERE || nZone == MAX_ZONE)
     213             :         {
     214           4 :             OGRSpatialReference oPolarSRS;
     215           2 :             oPolarSRS.importFromWkt(nZone == MAX_ZONE_NORTHERN_HEMISPHERE
     216             :                                         ? pszNorthPolarProjection
     217             :                                         : pszSouthPolarProjection);
     218           4 :             OGRSpatialReference oSRS_WGS84;
     219           2 :             oSRS_WGS84.SetWellKnownGeogCS("WGS84");
     220           2 :             oSRS_WGS84.SetAxisMappingStrategy(OAMS_TRADITIONAL_GIS_ORDER);
     221             :             auto poCT = std::unique_ptr<OGRCoordinateTransformation>(
     222           4 :                 OGRCreateCoordinateTransformation(&oPolarSRS, &oSRS_WGS84));
     223           2 :             poCT->Transform(1, &dfULX, &dfULY);
     224           2 :             poCT->Transform(1, &dfLLX, &dfLLY);
     225           2 :             poCT->Transform(1, &dfURX, &dfURY);
     226           2 :             poCT->Transform(1, &dfLRX, &dfLRY);
     227             :         }
     228             : 
     229          23 :         poBuffer->AppendFloat64(dfULY);  // NORTHWEST_LATITUDE
     230          23 :         poBuffer->AppendFloat64(dfULX);  // NORTHWEST_LONGITUDE
     231             : 
     232          23 :         poBuffer->AppendFloat64(dfLLY);  // SOUTHWEST_LATITUDE
     233          23 :         poBuffer->AppendFloat64(dfLLX);  // SOUTHWEST_LONGITUDE
     234             : 
     235          23 :         poBuffer->AppendFloat64(dfURY);  // NORTHEAST_LATITUDE
     236          23 :         poBuffer->AppendFloat64(dfURX);  // NORTHEAST_LONGITUDE
     237             : 
     238          23 :         poBuffer->AppendFloat64(dfLRY);  // SOUTHEAST_LATITUDE
     239          23 :         poBuffer->AppendFloat64(dfLRX);  // SOUTHEAST_LONGITUDE
     240             : 
     241          23 :         double latResolution = 0;
     242          23 :         double lonResolution = 0;
     243          23 :         double latInterval = 0;
     244          23 :         double lonInterval = 0;
     245          23 :         RPFGetCADRGResolutionAndInterval(nZone, nReciprocalScale, latResolution,
     246             :                                          lonResolution, latInterval,
     247             :                                          lonInterval);
     248             : 
     249          23 :         poBuffer->AppendFloat64(latResolution);
     250          23 :         poBuffer->AppendFloat64(lonResolution);
     251          23 :         poBuffer->AppendFloat64(latInterval);
     252          23 :         poBuffer->AppendFloat64(lonInterval);
     253             : 
     254          23 :         const int nCountY = extent.MaxY - extent.MinY + 1;
     255          23 :         poBuffer->AppendUInt32(static_cast<uint32_t>(nCountY));
     256             : 
     257          23 :         const int nCountX = extent.MaxX - extent.MinX + 1;
     258          23 :         poBuffer->AppendUInt32(static_cast<uint32_t>(nCountX));
     259             :     }
     260          19 : }
     261             : 
     262             : /************************************************************************/
     263             : /*            Create_RPFTOC_FrameFileIndexSectionSubHeader()            */
     264             : /************************************************************************/
     265             : 
     266          19 : static void Create_RPFTOC_FrameFileIndexSectionSubHeader(
     267             :     GDALOffsetPatcher::OffsetPatcher &offsetPatcher,
     268             :     char chHighestClassification, size_t nCountFrames, uint16_t nCountSubdirs)
     269             : {
     270          19 :     auto poBuffer = offsetPatcher.CreateBuffer(
     271             :         "FrameFileIndexSectionSubHeader", /* bEndiannessIsLittle = */ false);
     272          19 :     CPLAssert(poBuffer);
     273          19 :     poBuffer->DeclareOffsetAtCurrentPosition(
     274             :         "FRAME_FILE_INDEX_SECTION_SUBHEADER_LOCATION");
     275             : 
     276          19 :     poBuffer->AppendString(CPLSPrintf("%c", chHighestClassification));
     277          19 :     constexpr uint32_t FRAME_FILE_INDEX_TABLE_OFFSET = 0;
     278          19 :     poBuffer->AppendUInt32(FRAME_FILE_INDEX_TABLE_OFFSET);
     279          19 :     poBuffer->AppendUInt32(static_cast<uint32_t>(nCountFrames));
     280          19 :     poBuffer->AppendUInt16(nCountSubdirs);
     281          19 :     constexpr uint16_t FRAME_FILE_INDEX_RECORD_LENGTH = 33;
     282          19 :     poBuffer->AppendUInt16(FRAME_FILE_INDEX_RECORD_LENGTH);
     283          19 : }
     284             : 
     285             : /************************************************************************/
     286             : /*                             GetGEOREF()                              */
     287             : /************************************************************************/
     288             : 
     289             : /** Return coordinate as a World Geographic Reference System (GEOREF) string
     290             :  * as described in paragraph 5.4 of DMA TM 8358.1
     291             :  * (https://everyspec.com/DoD/DOD-General/download.php?spec=DMA_TM-8358.1.006300.PDF)
     292             :  */
     293          32 : static std::string GetGEOREF(double dfLon, double dfLat)
     294             : {
     295             :     // clang-format off
     296             :     // letters 'I' and 'O' are omitted to avoid confusiong with one and zero
     297          32 :     constexpr char ALPHABET_WITHOUT_IO[] = {
     298             :         'A', 'B', 'C', 'D', 'E', 'F', 'G', 'H',      'J',
     299             :         'K', 'L', 'M', 'N',      'P', 'Q', 'R', 'S', 'T',
     300             :         'U', 'V', 'W', 'X', 'Y', 'Z'
     301             :     };
     302             :     // clang-format on
     303             : 
     304          32 :     std::string osRes;
     305             : 
     306          32 :     constexpr double LON_ORIGIN = -180;
     307          32 :     constexpr double LAT_ORIGIN = -90;
     308          32 :     constexpr int QUADRANGLE_SIZE = 15;  // degree
     309          32 :     constexpr double EPSILON = 1e-5;
     310             : 
     311             :     // Longitude zone
     312             :     {
     313          32 :         const int nIdx =
     314          32 :             static_cast<int>(dfLon - LON_ORIGIN + EPSILON) / QUADRANGLE_SIZE;
     315          32 :         CPLAssert(nIdx >= 0 && nIdx < 24);
     316          32 :         osRes += ALPHABET_WITHOUT_IO[nIdx];
     317             :     }
     318             : 
     319             :     // Latitude band
     320             :     {
     321          32 :         const int nIdx =
     322          32 :             static_cast<int>(dfLat - LAT_ORIGIN + EPSILON) / QUADRANGLE_SIZE;
     323          32 :         CPLAssert(nIdx >= 0 && nIdx < 12);
     324          32 :         osRes += ALPHABET_WITHOUT_IO[nIdx];
     325             :     }
     326             : 
     327             :     // Longitude index within 15x15 degree quadrangle
     328             :     {
     329          32 :         const int nIdx =
     330          32 :             static_cast<int>(dfLon - LON_ORIGIN + EPSILON) % QUADRANGLE_SIZE;
     331          32 :         osRes += ALPHABET_WITHOUT_IO[nIdx];
     332             :     }
     333             : 
     334             :     // Latitude index within 15x15 degree quadrangle
     335             :     {
     336          32 :         const int nIdx =
     337          32 :             static_cast<int>(dfLat - LAT_ORIGIN + EPSILON) % QUADRANGLE_SIZE;
     338          32 :         osRes += ALPHABET_WITHOUT_IO[nIdx];
     339             :     }
     340             : 
     341             :     // Longitude minutes
     342             :     {
     343          32 :         constexpr int MINUTES_IN_DEGREE = 60;
     344          32 :         const int nMinutes =
     345          32 :             static_cast<int>((dfLon - LON_ORIGIN) * MINUTES_IN_DEGREE +
     346             :                              EPSILON) %
     347             :             MINUTES_IN_DEGREE;
     348          32 :         osRes += CPLSPrintf("%02d", nMinutes);
     349             :     }
     350             : 
     351          64 :     return osRes;
     352             : }
     353             : 
     354             : /************************************************************************/
     355             : /*               Create_RPFTOC_FrameFileIndexSubsection()               */
     356             : /************************************************************************/
     357             : 
     358          19 : static void Create_RPFTOC_FrameFileIndexSubsection(
     359             :     GDALOffsetPatcher::OffsetPatcher &offsetPatcher,
     360             :     const std::string &osSecurityCountryCode,
     361             :     const std::map<ScaleZone, std::vector<FrameDesc>> &oMapScaleZoneToFrames,
     362             :     const std::map<ScaleZone, MinMaxFrameXY> &oMapScaleZoneToMinMaxFrameXY,
     363             :     const std::map<std::string, int> &oMapSubdirToIdx)
     364             : {
     365          19 :     auto poBuffer = offsetPatcher.CreateBuffer(
     366             :         "FrameFileIndexSubsection", /* bEndiannessIsLittle = */ false);
     367          19 :     CPLAssert(poBuffer);
     368          19 :     poBuffer->DeclareOffsetAtCurrentPosition(
     369             :         "FRAME_FILE_INDEX_SUBSECTION_LOCATION");
     370             : 
     371          38 :     std::map<ScaleZone, uint16_t> oMapScaleZoneToIdx;
     372          46 :     for ([[maybe_unused]] const auto &[scaleZone, unused] :
     373          65 :          oMapScaleZoneToFrames)
     374             :     {
     375          23 :         if (!cpl::contains(oMapScaleZoneToIdx, scaleZone))
     376             :         {
     377          23 :             oMapScaleZoneToIdx[scaleZone] =
     378          23 :                 static_cast<uint16_t>(oMapScaleZoneToIdx.size());
     379             :         }
     380             :     }
     381             : 
     382          42 :     for (const auto &[scaleZone, framesDesc] : oMapScaleZoneToFrames)
     383             :     {
     384             :         const auto oIterMapScaleZoneToMinMaxFrameXY =
     385          23 :             oMapScaleZoneToMinMaxFrameXY.find(scaleZone);
     386          23 :         CPLAssert(oIterMapScaleZoneToMinMaxFrameXY !=
     387             :                   oMapScaleZoneToMinMaxFrameXY.end());
     388          23 :         const auto &oMinMaxFrameXY = oIterMapScaleZoneToMinMaxFrameXY->second;
     389             : 
     390          55 :         for (const auto &frameDesc : framesDesc)
     391             :         {
     392             :             const auto oIterMapScaleZoneToIdx =
     393          32 :                 oMapScaleZoneToIdx.find(scaleZone);
     394          32 :             CPLAssert(oIterMapScaleZoneToIdx != oMapScaleZoneToIdx.end());
     395          32 :             poBuffer->AppendUInt16(oIterMapScaleZoneToIdx->second);
     396          32 :             poBuffer->AppendUInt16(
     397          32 :                 static_cast<uint16_t>(frameDesc.nFrameY - oMinMaxFrameXY.MinY));
     398          32 :             poBuffer->AppendUInt16(
     399          32 :                 static_cast<uint16_t>(frameDesc.nFrameX - oMinMaxFrameXY.MinX));
     400             : 
     401             :             const std::string osSubdir =
     402          64 :                 CPLGetPathSafe(frameDesc.osRelativeFilename.c_str());
     403          32 :             const auto oIterSubdirToIdx = oMapSubdirToIdx.find(osSubdir);
     404          32 :             CPLAssert(oIterSubdirToIdx != oMapSubdirToIdx.end());
     405          64 :             poBuffer->AppendUInt32RefForOffset(
     406             :                 CPLSPrintf("PATHNAME_RECORD_OFFSET_%d",
     407          32 :                            oIterSubdirToIdx->second),
     408             :                 /* bRelativeToStartOfBuffer = */ true);
     409          32 :             poBuffer->AppendString(StrPadTruncate(
     410             :                 CPLGetFilename(frameDesc.osRelativeFilename.c_str()), 12));
     411             :             const std::string osGeographicLocation =
     412          32 :                 GetGEOREF(frameDesc.dfMinX, frameDesc.dfMinY);
     413          32 :             CPLAssert(osGeographicLocation.size() == 6);
     414          32 :             poBuffer->AppendString(StrPadTruncate(osGeographicLocation, 6));
     415          32 :             poBuffer->AppendString("U");  // FRAME_FILE_SECURITY_CLASSIFICATION
     416          32 :             poBuffer->AppendString(StrPadTruncate(osSecurityCountryCode, 2));
     417             :             // FRAME_FILE_SECURITY_RELEASE_MARKING
     418          32 :             poBuffer->AppendString("  ");
     419             :         }
     420             :     }
     421             : 
     422             :     struct SortedDirPrefixes
     423             :     {
     424             :         int nIdx = 0;
     425             :         std::string osSubdir{};
     426             :     };
     427             : 
     428          38 :     std::vector<SortedDirPrefixes> asSortedDirPrefixes;
     429          42 :     for (const auto &[osSubdir, nIdx] : oMapSubdirToIdx)
     430             :     {
     431          46 :         SortedDirPrefixes s;
     432          23 :         s.nIdx = nIdx;
     433          23 :         s.osSubdir = osSubdir;
     434          23 :         asSortedDirPrefixes.push_back(std::move(s));
     435             :     }
     436          19 :     std::sort(asSortedDirPrefixes.begin(), asSortedDirPrefixes.end(),
     437           7 :               [](const SortedDirPrefixes &a, const SortedDirPrefixes &b)
     438           7 :               { return a.nIdx < b.nIdx; });
     439             : 
     440          42 :     for (const auto &sortedDirPrefix : asSortedDirPrefixes)
     441             :     {
     442          46 :         poBuffer->DeclareOffsetAtCurrentPosition(
     443          23 :             CPLSPrintf("PATHNAME_RECORD_OFFSET_%d", sortedDirPrefix.nIdx));
     444             :         std::string osPath =
     445          46 :             "./" + CPLString(sortedDirPrefix.osSubdir).replaceAll('\\', '/');
     446          23 :         if (osPath.back() != '/')
     447          23 :             osPath += '/';
     448          23 :         poBuffer->AppendUInt16(static_cast<uint16_t>(osPath.size()));
     449          23 :         poBuffer->AppendString(osPath);
     450             :     }
     451          19 : }
     452             : 
     453             : /************************************************************************/
     454             : /*                         RPCTOCCreateRPFDES()                         */
     455             : /************************************************************************/
     456             : 
     457          19 : static bool RPCTOCCreateRPFDES(
     458             :     VSILFILE *fp, GDALOffsetPatcher::OffsetPatcher &offsetPatcher,
     459             :     const std::string &osProducer, const std::string &osSecurityCountryCode,
     460             :     const std::map<ScaleZone, std::vector<FrameDesc>> &oMapScaleZoneToFrames,
     461             :     const std::map<ScaleZone, MinMaxFrameXY> &oMapScaleZoneToMinMaxFrameXY)
     462             : {
     463             :     (void)oMapScaleZoneToFrames;
     464             : 
     465          19 :     bool bOK = fp->Seek(0, SEEK_END) == 0;
     466             : 
     467          19 :     const char *pszDESHeader = RPFFrameWriteGetDESHeader();
     468          19 :     bOK &=
     469          19 :         fp->Write(pszDESHeader, strlen(pszDESHeader)) == strlen(pszDESHeader);
     470             : 
     471          19 :     const auto nOffsetTRESize = fp->Tell() + strlen("RPFDES");
     472             :     // xxxxx is a placeholder for the TRE size, patched later with the actual
     473             :     // size.
     474          19 :     constexpr const char *pszRPFDESTREStart = "RPFDESxxxxx";
     475          19 :     bOK &= fp->Write(pszRPFDESTREStart, 1, strlen(pszRPFDESTREStart)) ==
     476          19 :            strlen(pszRPFDESTREStart);
     477             : 
     478             :     // Associate an index to each subdir name used by frames
     479          38 :     std::map<std::string, int> oMapSubdirToIdx;
     480          19 :     size_t nCountFrames = 0;
     481             : 
     482             :     // From lowest to highest classification level
     483          19 :     constexpr const char achClassifications[] = {
     484             :         'U',  // Unclassified
     485             :         'R',  // Restricted
     486             :         'C',  // Confidential
     487             :         'S',  // Secret
     488             :         'T',  // Top Secret
     489             :     };
     490          38 :     std::map<char, unsigned> oMapClassificationToLevel;
     491         114 :     for (unsigned i = 0; i < CPL_ARRAYSIZE(achClassifications); ++i)
     492          95 :         oMapClassificationToLevel[achClassifications[i]] = i;
     493             : 
     494          19 :     unsigned nHighestClassification = 0;
     495             : 
     496          46 :     for ([[maybe_unused]] const auto &[unused, framesDesc] :
     497          42 :          oMapScaleZoneToFrames)
     498             :     {
     499          55 :         for (const auto &frameDesc : framesDesc)
     500             :         {
     501             :             const std::string osSubdir =
     502          64 :                 CPLGetPathSafe(frameDesc.osRelativeFilename.c_str());
     503          32 :             if (!cpl::contains(oMapSubdirToIdx, osSubdir))
     504             :             {
     505          23 :                 oMapSubdirToIdx[osSubdir] =
     506          23 :                     static_cast<int>(oMapSubdirToIdx.size());
     507             :             }
     508             : 
     509             :             const auto oClassificationIter =
     510          32 :                 oMapClassificationToLevel.find(frameDesc.chClassification);
     511          32 :             if (oClassificationIter == oMapClassificationToLevel.end())
     512             :             {
     513           0 :                 CPLError(CE_Warning, CPLE_AppDefined,
     514             :                          "Unknown classification level '%c' for %s",
     515           0 :                          frameDesc.chClassification,
     516             :                          frameDesc.osRelativeFilename.c_str());
     517             :             }
     518             :             else
     519             :             {
     520          32 :                 nHighestClassification = std::max(nHighestClassification,
     521          32 :                                                   oClassificationIter->second);
     522             :             }
     523             :         }
     524          23 :         nCountFrames += framesDesc.size();
     525             :     }
     526          19 :     if (oMapSubdirToIdx.size() > std::numeric_limits<uint16_t>::max())
     527             :     {
     528           0 :         CPLError(CE_Failure, CPLE_AppDefined,
     529             :                  "Too many subdirectories: %u. Only up to %u are allowed",
     530           0 :                  static_cast<unsigned>(oMapSubdirToIdx.size()),
     531           0 :                  std::numeric_limits<uint16_t>::max());
     532           0 :         return false;
     533             :     }
     534             : 
     535             :     // Create RPF sections
     536          19 :     Create_RPFTOC_LocationComponent(offsetPatcher);
     537          19 :     Create_RPFTOC_BoundaryRectangleSectionSubheader(
     538             :         offsetPatcher, oMapScaleZoneToMinMaxFrameXY.size());
     539          19 :     Create_RPFTOC_BoundaryRectangleTable(offsetPatcher, osProducer,
     540             :                                          oMapScaleZoneToMinMaxFrameXY);
     541          19 :     const char chHighestClassification =
     542          19 :         achClassifications[nHighestClassification];
     543          19 :     Create_RPFTOC_FrameFileIndexSectionSubHeader(
     544             :         offsetPatcher, chHighestClassification, nCountFrames,
     545          19 :         static_cast<uint16_t>(oMapSubdirToIdx.size()));
     546          19 :     Create_RPFTOC_FrameFileIndexSubsection(
     547             :         offsetPatcher, osSecurityCountryCode, oMapScaleZoneToFrames,
     548             :         oMapScaleZoneToMinMaxFrameXY, oMapSubdirToIdx);
     549             : 
     550             :     // Write RPF sections
     551          19 :     size_t nTREDataSize = 0;
     552         190 :     for (const char *pszName :
     553             :          {"LocationComponent", "BoundaryRectangleSectionSubheader",
     554             :           "BoundaryRectangleTable", "FrameFileIndexSectionSubHeader",
     555         114 :           "FrameFileIndexSubsection"})
     556             :     {
     557          95 :         const auto poBuffer = offsetPatcher.GetBufferFromName(pszName);
     558          95 :         CPLAssert(poBuffer);
     559          95 :         poBuffer->DeclareBufferWrittenAtPosition(fp->Tell());
     560          95 :         bOK &= fp->Write(poBuffer->GetBuffer().data(),
     561          95 :                          poBuffer->GetBuffer().size()) ==
     562          95 :                poBuffer->GetBuffer().size();
     563          95 :         nTREDataSize += poBuffer->GetBuffer().size();
     564             :     }
     565             : 
     566             :     // Patch the size of the RPFDES TRE data
     567          19 :     if (nTREDataSize <= 99999)
     568             :     {
     569          19 :         bOK &= fp->Seek(nOffsetTRESize, SEEK_SET) == 0;
     570             :         const std::string osTRESize =
     571          19 :             CPLSPrintf("%05d", static_cast<int>(nTREDataSize));
     572          19 :         bOK &=
     573          19 :             fp->Write(osTRESize.c_str(), osTRESize.size()) == osTRESize.size();
     574             :     }
     575             :     else
     576             :     {
     577           0 :         CPLError(CE_Warning, CPLE_AppDefined,
     578             :                  "RPFDES TRE size exceeds 99999 bytes. Some readers might not "
     579             :                  "be able to read the A.TOC file correctly");
     580             :     }
     581             : 
     582             :     // Update LDSH and LD in the NITF Header
     583             : 
     584             :     // NUMI offset is at a fixed offset 360 (unless there is a FSDWNG field)
     585          19 :     constexpr vsi_l_offset nNumIOffset = 360;
     586          19 :     constexpr vsi_l_offset nNumGOffset = nNumIOffset + 3;
     587             :     // the last + 3 is for NUMX field, which is not used
     588          19 :     constexpr vsi_l_offset nNumTOffset = nNumGOffset + 3 + 3;
     589          19 :     constexpr vsi_l_offset nNumDESOffset = nNumTOffset + 3;
     590          19 :     constexpr auto nOffsetLDSH = nNumDESOffset + 3;
     591             : 
     592          19 :     constexpr int iDES = 0;
     593          19 :     bOK &= fp->Seek(nOffsetLDSH + iDES * 13, SEEK_SET) == 0;
     594          19 :     bOK &= fp->Write(CPLSPrintf("%04d", static_cast<int>(strlen(pszDESHeader))),
     595          19 :                      4) == 4;
     596          38 :     bOK &= fp->Write(
     597          19 :                CPLSPrintf("%09d", static_cast<int>(nTREDataSize +
     598             :                                                    strlen(pszRPFDESTREStart))),
     599          19 :                9) == 9;
     600             : 
     601             :     // Update total file length
     602          19 :     bOK &= fp->Seek(0, SEEK_END) == 0;
     603          19 :     const uint64_t nFileLen = fp->Tell();
     604          19 :     CPLString osFileLen = CPLString().Printf("%012" PRIu64, nFileLen);
     605          19 :     constexpr vsi_l_offset FILE_LENGTH_OFFSET = 342;
     606          19 :     bOK &= fp->Seek(FILE_LENGTH_OFFSET, SEEK_SET) == 0;
     607          19 :     bOK &= fp->Write(osFileLen.data(), osFileLen.size()) == osFileLen.size();
     608             : 
     609          19 :     return bOK;
     610             : }
     611             : 
     612             : /************************************************************************/
     613             : /*                        RPFTOCCollectFrames()                         */
     614             : /************************************************************************/
     615             : 
     616          78 : static bool RPFTOCCollectFrames(
     617             :     VSIDIR *psDir, const std::string &osInputDirectory,
     618             :     const int nReciprocalScale,
     619             :     std::map<ScaleZone, std::vector<FrameDesc>> &oMapScaleZoneToFrames,
     620             :     std::map<ScaleZone, MinMaxFrameXY> &oMapScaleZoneToMinMaxFrameXY)
     621             : {
     622             : 
     623          78 :     while (const VSIDIREntry *psEntry = VSIGetNextDirEntry(psDir))
     624             :     {
     625          89 :         if (VSI_ISDIR(psEntry->nMode) ||
     626          33 :             EQUAL(CPLGetFilename(psEntry->pszName), "A.TOC"))
     627          23 :             continue;
     628          33 :         const char *const apszAllowedDrivers[] = {"NITF", nullptr};
     629             :         const std::string osFullFilename = CPLFormFilenameSafe(
     630          33 :             osInputDirectory.c_str(), psEntry->pszName, nullptr);
     631             :         auto poDS = std::unique_ptr<GDALDataset>(GDALDataset::Open(
     632          33 :             osFullFilename.c_str(), GDAL_OF_RASTER, apszAllowedDrivers));
     633          33 :         if (!poDS)
     634           0 :             continue;
     635             : 
     636             :         const std::string osFilenamePart =
     637          33 :             CPLGetFilename(osFullFilename.c_str());
     638          33 :         if (osFilenamePart.size() != 12)
     639             :         {
     640           0 :             CPLDebug("RPFTOC", "%s filename is not 12 character long",
     641             :                      osFullFilename.c_str());
     642           0 :             continue;
     643             :         }
     644             : 
     645          66 :         if (poDS->GetRasterXSize() != CADRG_FRAME_PIXEL_COUNT ||
     646          33 :             poDS->GetRasterYSize() != CADRG_FRAME_PIXEL_COUNT)
     647             :         {
     648           0 :             CPLDebug("RPFTOC", "%s has not the dimensions of a CADRG frame",
     649             :                      osFullFilename.c_str());
     650           0 :             continue;
     651             :         }
     652             : 
     653          33 :         const std::string osDataSeriesCode(osFilenamePart.substr(9, 2));
     654          33 :         if (!RPFCADRGIsKnownDataSeriesCode(osDataSeriesCode.c_str()))
     655             :         {
     656           0 :             CPLError(CE_Warning, CPLE_AppDefined,
     657             :                      "Data series code '%s' in %s extension is a unknown CADRG "
     658             :                      "series code",
     659             :                      osDataSeriesCode.c_str(), osFullFilename.c_str());
     660             :         }
     661             : 
     662          33 :         int nThisScale = nReciprocalScale;
     663          33 :         if (nThisScale == 0)
     664             :         {
     665             :             nThisScale =
     666           4 :                 RPFCADRGGetScaleFromDataSeriesCode(osDataSeriesCode.c_str());
     667           4 :             if (nThisScale == 0)
     668             :             {
     669           0 :                 CPLError(CE_Failure, CPLE_AppDefined,
     670             :                          "Scale cannot be inferred from filename %s. Specify "
     671             :                          "the 'scale' argument",
     672             :                          osFullFilename.c_str());
     673           0 :                 return false;
     674             :             }
     675             :         }
     676             : 
     677          33 :         const int nZone = RPFCADRGZoneCharToNum(osFilenamePart.back());
     678          33 :         if (nZone == 0)
     679             :         {
     680           0 :             CPLError(CE_Failure, CPLE_AppDefined,
     681             :                      "CADRG zone cannot be inferred from last character of "
     682             :                      "filename %s.",
     683             :                      osFullFilename.c_str());
     684           0 :             return false;
     685             :         }
     686             : 
     687          33 :         OGREnvelope sExtentWGS84;
     688          33 :         if (poDS->GetExtentWGS84LongLat(&sExtentWGS84) != CE_None)
     689             :         {
     690           0 :             CPLError(CE_Failure, CPLE_AppDefined,
     691             :                      "Cannot get dataset extent for %s",
     692             :                      osFullFilename.c_str());
     693           0 :             return false;
     694             :         }
     695             : 
     696             :         const auto frameDefinitions =
     697          33 :             RPFGetCADRGFramesForEnvelope(nZone, nThisScale, poDS.get());
     698          33 :         if (frameDefinitions.empty())
     699             :         {
     700           0 :             CPLError(CE_Failure, CPLE_AppDefined,
     701             :                      "Cannot establish CADRG frames intersecting dataset "
     702             :                      "extent for %s",
     703             :                      osFullFilename.c_str());
     704           0 :             return false;
     705             :         }
     706          33 :         if (frameDefinitions.size() != 1)
     707             :         {
     708           0 :             CPLError(CE_Failure, CPLE_AppDefined,
     709             :                      "Extent of file %s does not match a single CADRG frame",
     710             :                      osFullFilename.c_str());
     711           0 :             return false;
     712             :         }
     713             : 
     714             :         const std::string osExpectedFilenameStart =
     715             :             RPFGetCADRGFrameNumberAsString(nZone, nThisScale,
     716          66 :                                            frameDefinitions[0].nFrameMinX,
     717          66 :                                            frameDefinitions[0].nFrameMinY);
     718          33 :         if (!cpl::starts_with(CPLString(osFilenamePart).toupper(),
     719             :                               osExpectedFilenameStart))
     720             :         {
     721           0 :             CPLError(CE_Warning, CPLE_AppDefined,
     722             :                      "Filename part of %s should begin with %s",
     723             :                      osFullFilename.c_str(), osExpectedFilenameStart.c_str());
     724             :         }
     725             : 
     726             :         // Store needed metadata on the frame
     727          33 :         FrameDesc desc;
     728          33 :         desc.nZone = nZone;
     729          33 :         desc.nReciprocalScale = nThisScale;
     730          33 :         desc.nFrameX = frameDefinitions[0].nFrameMinX;
     731          33 :         desc.nFrameY = frameDefinitions[0].nFrameMinY;
     732          33 :         desc.dfMinX = sExtentWGS84.MinX;
     733          33 :         desc.dfMinY = sExtentWGS84.MinY;
     734          33 :         desc.osRelativeFilename = psEntry->pszName;
     735          33 :         const char *pszClassification = poDS->GetMetadataItem("FCLASS");
     736          33 :         if (pszClassification)
     737           0 :             desc.chClassification = pszClassification[0];
     738             : 
     739             :         // Update min and max frame indices for this (scale, zone) pair
     740             :         auto &sMinMaxFrameXY =
     741          33 :             oMapScaleZoneToMinMaxFrameXY[{nThisScale, nZone}];
     742          33 :         sMinMaxFrameXY.MinX = std::min(sMinMaxFrameXY.MinX, desc.nFrameX);
     743          33 :         sMinMaxFrameXY.MinY = std::min(sMinMaxFrameXY.MinY, desc.nFrameY);
     744          33 :         sMinMaxFrameXY.MaxX = std::max(sMinMaxFrameXY.MaxX, desc.nFrameX);
     745          33 :         sMinMaxFrameXY.MaxY = std::max(sMinMaxFrameXY.MaxY, desc.nFrameY);
     746             : 
     747          33 :         oMapScaleZoneToFrames[{nThisScale, nZone}].push_back(std::move(desc));
     748          56 :     }
     749             : 
     750             :     // For each (scale, zone) pair, sort by increasing y and then x
     751             :     // to have a reproducible output
     752          46 :     for ([[maybe_unused]] auto &[unused, frameDescs] : oMapScaleZoneToFrames)
     753             :     {
     754          24 :         std::sort(frameDescs.begin(), frameDescs.end(),
     755          16 :                   [](const FrameDesc &a, const FrameDesc &b)
     756             :                   {
     757          32 :                       return a.nFrameY < b.nFrameY ||
     758          32 :                              (a.nFrameY == b.nFrameY && a.nFrameX < b.nFrameX);
     759             :                   });
     760             :     }
     761             : 
     762          22 :     return true;
     763             : }
     764             : 
     765             : /************************************************************************/
     766             : /*                            RPFTOCCreate()                            */
     767             : /************************************************************************/
     768             : 
     769          23 : bool RPFTOCCreate(const std::string &osInputDirectory,
     770             :                   const std::string &osOutputFilename,
     771             :                   const char chIndexClassification, const int nReciprocalScale,
     772             :                   const std::string &osProducerID,
     773             :                   const std::string &osProducerName,
     774             :                   const std::string &osSecurityCountryCode,
     775             :                   bool bDoNotCreateIfNoFrame)
     776             : {
     777             :     std::unique_ptr<VSIDIR, decltype(&VSICloseDir)> psDir(
     778             :         VSIOpenDir(osInputDirectory.c_str(), -1 /* unlimited recursion */,
     779             :                    nullptr),
     780          46 :         VSICloseDir);
     781          23 :     if (!psDir)
     782             :     {
     783           1 :         CPLError(CE_Failure, CPLE_AppDefined,
     784             :                  "%s is not a directory or cannot be opened",
     785             :                  osInputDirectory.c_str());
     786           1 :         return false;
     787             :     }
     788             : 
     789          44 :     std::map<ScaleZone, std::vector<FrameDesc>> oMapScaleZoneToFrames;
     790          44 :     std::map<ScaleZone, MinMaxFrameXY> oMapScaleZoneToMinMaxFrameXY;
     791          22 :     if (!RPFTOCCollectFrames(psDir.get(), osInputDirectory, nReciprocalScale,
     792             :                              oMapScaleZoneToFrames,
     793             :                              oMapScaleZoneToMinMaxFrameXY))
     794             :     {
     795           0 :         return false;
     796             :     }
     797             : 
     798          22 :     if (oMapScaleZoneToFrames.empty())
     799             :     {
     800           2 :         if (bDoNotCreateIfNoFrame)
     801             :         {
     802           1 :             return true;
     803             :         }
     804             :         else
     805             :         {
     806           1 :             CPLError(CE_Failure, CPLE_AppDefined, "No CADRG frame found in %s",
     807             :                      osInputDirectory.c_str());
     808           1 :             return false;
     809             :         }
     810             :     }
     811             : 
     812          40 :     GDALOffsetPatcher::OffsetPatcher offsetPatcher;
     813             : 
     814          40 :     CPLStringList aosOptions;
     815          20 :     aosOptions.SetNameValue("FHDR", "NITF02.00");
     816          20 :     aosOptions.SetNameValue("NUMI", "0");
     817          20 :     aosOptions.SetNameValue("NUMDES", "1");
     818          20 :     constexpr const char pszLeftPaddedATOC[] = "       A.TOC";
     819             :     static_assert(sizeof(pszLeftPaddedATOC) == 12 + 1);
     820          20 :     aosOptions.SetNameValue("FCLASS", CPLSPrintf("%c", chIndexClassification));
     821          20 :     aosOptions.SetNameValue("FDT", "11111111ZJAN26");
     822          20 :     aosOptions.SetNameValue("FTITLE", pszLeftPaddedATOC);
     823          20 :     if (!osProducerID.empty())
     824           0 :         aosOptions.SetNameValue("OSTAID", osProducerID.c_str());
     825          20 :     if (!osProducerName.empty())
     826           0 :         aosOptions.SetNameValue("ONAME", osProducerName.c_str());
     827          20 :     Create_CADRG_RPFHDR(&offsetPatcher, pszLeftPaddedATOC, aosOptions);
     828          20 :     if (!NITFCreateEx(osOutputFilename.c_str(), /* nPixels = */ 0,
     829             :                       /* nLines = */ 0, /* nBands = */ 0,
     830             :                       /* nBitsPerSample = */ 0, /* PVType = */ nullptr,
     831          20 :                       aosOptions.List(), /* pnIndex = */ nullptr,
     832             :                       /* pnImageCount = */ nullptr,
     833             :                       /* pnImageOffset = */ nullptr, /* pnICOffset = */ nullptr,
     834             :                       &offsetPatcher))
     835             :     {
     836           1 :         return false;
     837             :     }
     838             : 
     839          19 :     auto fp = VSIFilesystemHandler::OpenStatic(osOutputFilename.c_str(), "rb+");
     840          19 :     return fp != nullptr &&
     841          19 :            RPCTOCCreateRPFDES(fp.get(), offsetPatcher, osProducerID,
     842             :                               osSecurityCountryCode, oMapScaleZoneToFrames,
     843          19 :                               oMapScaleZoneToMinMaxFrameXY) &&
     844          38 :            offsetPatcher.Finalize(fp.get()) && fp->Close() == 0;
     845             : }

Generated by: LCOV version 1.14