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 : }
|