Line data Source code
1 : /******************************************************************************
2 : *
3 : * Project: ECRG TOC read Translator
4 : * Purpose: Implementation of ECRGTOCDataset and ECRGTOCSubDataset.
5 : * Author: Even Rouault, even.rouault at spatialys.com
6 : *
7 : ******************************************************************************
8 : * Copyright (c) 2011, Even Rouault <even dot rouault at spatialys.com>
9 : *
10 : * SPDX-License-Identifier: MIT
11 : ****************************************************************************/
12 :
13 : #include "cpl_port.h"
14 :
15 : #include <array>
16 : #include <cassert>
17 : #include <cmath>
18 : #include <cstddef>
19 : #include <cstdio>
20 : #include <cstdlib>
21 : #include <cstring>
22 : #include <memory>
23 : #include <string>
24 : #include <vector>
25 :
26 : #include "cpl_conv.h"
27 : #include "cpl_error.h"
28 : #include "cpl_minixml.h"
29 : #include "cpl_string.h"
30 : #include "gdal.h"
31 : #include "gdal_frmts.h"
32 : #include "gdal_pam.h"
33 : #include "gdal_priv.h"
34 : #include "gdal_proxy.h"
35 : #include "ogr_srs_api.h"
36 : #include "vrtdataset.h"
37 : #include "nitfdrivercore.h"
38 :
39 : /** Overview of used classes :
40 : - ECRGTOCDataset : lists the different subdatasets, listed in the .xml,
41 : as subdatasets
42 : - ECRGTOCSubDataset : one of these subdatasets, implemented as a VRT, of
43 : the relevant NITF tiles
44 : */
45 :
46 : namespace
47 : {
48 : typedef struct
49 : {
50 : const char *pszName;
51 : const char *pszPath;
52 : int nScale;
53 : int nZone;
54 : } FrameDesc;
55 : } // namespace
56 :
57 : /************************************************************************/
58 : /* ==================================================================== */
59 : /* ECRGTOCDataset */
60 : /* ==================================================================== */
61 : /************************************************************************/
62 :
63 : class ECRGTOCDataset final : public GDALPamDataset
64 : {
65 : OGRSpatialReference m_oSRS{};
66 : char **papszSubDatasets = nullptr;
67 : std::array<double, 6> adfGeoTransform = {0};
68 : char **papszFileList = nullptr;
69 :
70 : CPL_DISALLOW_COPY_ASSIGN(ECRGTOCDataset)
71 :
72 : public:
73 21 : ECRGTOCDataset()
74 21 : {
75 21 : m_oSRS.SetAxisMappingStrategy(OAMS_TRADITIONAL_GIS_ORDER);
76 21 : m_oSRS.SetFromUserInput(SRS_WKT_WGS84_LAT_LONG);
77 21 : }
78 :
79 42 : virtual ~ECRGTOCDataset()
80 21 : {
81 21 : CSLDestroy(papszSubDatasets);
82 21 : CSLDestroy(papszFileList);
83 42 : }
84 :
85 : virtual char **GetMetadata(const char *pszDomain = "") override;
86 :
87 1 : virtual char **GetFileList() override
88 : {
89 1 : return CSLDuplicate(papszFileList);
90 : }
91 :
92 : void AddSubDataset(const char *pszFilename, const char *pszProductTitle,
93 : const char *pszDiscId, const char *pszScale);
94 :
95 1 : virtual CPLErr GetGeoTransform(double *padfGeoTransform) override
96 : {
97 1 : memcpy(padfGeoTransform, adfGeoTransform.data(),
98 : sizeof(adfGeoTransform));
99 1 : return CE_None;
100 : }
101 :
102 1 : const OGRSpatialReference *GetSpatialRef() const override
103 : {
104 1 : return &m_oSRS;
105 : }
106 :
107 : static GDALDataset *Build(const char *pszTOCFilename, CPLXMLNode *psXML,
108 : const std::string &osProduct,
109 : const std::string &osDiscId,
110 : const std::string &osScale,
111 : const char *pszFilename);
112 :
113 : static GDALDataset *Open(GDALOpenInfo *poOpenInfo);
114 : };
115 :
116 : /************************************************************************/
117 : /* ==================================================================== */
118 : /* ECRGTOCSubDataset */
119 : /* ==================================================================== */
120 : /************************************************************************/
121 :
122 : class ECRGTOCSubDataset final : public VRTDataset
123 : {
124 : char **papszFileList = nullptr;
125 : CPL_DISALLOW_COPY_ASSIGN(ECRGTOCSubDataset)
126 :
127 : public:
128 11 : ECRGTOCSubDataset(int nXSize, int nYSize) : VRTDataset(nXSize, nYSize)
129 : {
130 : /* Don't try to write a VRT file */
131 11 : SetWritable(FALSE);
132 :
133 : /* The driver is set to VRT in VRTDataset constructor. */
134 : /* We have to set it to the expected value ! */
135 11 : poDriver = GDALDriver::FromHandle(GDALGetDriverByName("ECRGTOC"));
136 11 : }
137 :
138 : ~ECRGTOCSubDataset() override;
139 :
140 2 : virtual char **GetFileList() override
141 : {
142 2 : return CSLDuplicate(papszFileList);
143 : }
144 :
145 : static GDALDataset *
146 : Build(const char *pszProductTitle, const char *pszDiscId, int nScale,
147 : int nCountSubDataset, const char *pszTOCFilename,
148 : const std::vector<FrameDesc> &aosFrameDesc, double dfGlobalMinX,
149 : double dfGlobalMinY, double dfGlobalMaxX, double dfGlobalMaxY,
150 : double dfGlobalPixelXSize, double dfGlobalPixelYSize);
151 : };
152 :
153 22 : ECRGTOCSubDataset::~ECRGTOCSubDataset()
154 : {
155 11 : CSLDestroy(papszFileList);
156 22 : }
157 :
158 : /************************************************************************/
159 : /* LaunderString() */
160 : /************************************************************************/
161 :
162 67 : static CPLString LaunderString(const char *pszStr)
163 : {
164 67 : CPLString osRet(pszStr);
165 632 : for (size_t i = 0; i < osRet.size(); i++)
166 : {
167 565 : if (osRet[i] == ':' || osRet[i] == ' ')
168 42 : osRet[i] = '_';
169 : }
170 67 : return osRet;
171 : }
172 :
173 : /************************************************************************/
174 : /* AddSubDataset() */
175 : /************************************************************************/
176 :
177 9 : void ECRGTOCDataset::AddSubDataset(const char *pszFilename,
178 : const char *pszProductTitle,
179 : const char *pszDiscId, const char *pszScale)
180 :
181 : {
182 : char szName[80];
183 9 : const int nCount = CSLCount(papszSubDatasets) / 2;
184 :
185 9 : snprintf(szName, sizeof(szName), "SUBDATASET_%d_NAME", nCount + 1);
186 27 : papszSubDatasets = CSLSetNameValue(
187 : papszSubDatasets, szName,
188 : CPLSPrintf("ECRG_TOC_ENTRY:%s:%s:%s:%s",
189 18 : LaunderString(pszProductTitle).c_str(),
190 18 : LaunderString(pszDiscId).c_str(),
191 18 : LaunderString(pszScale).c_str(), pszFilename));
192 :
193 9 : snprintf(szName, sizeof(szName), "SUBDATASET_%d_DESC", nCount + 1);
194 9 : papszSubDatasets =
195 9 : CSLSetNameValue(papszSubDatasets, szName,
196 : CPLSPrintf("Product %s, disc %s, scale %s",
197 : pszProductTitle, pszDiscId, pszScale));
198 9 : }
199 :
200 : /************************************************************************/
201 : /* GetMetadata() */
202 : /************************************************************************/
203 :
204 7 : char **ECRGTOCDataset::GetMetadata(const char *pszDomain)
205 :
206 : {
207 7 : if (pszDomain != nullptr && EQUAL(pszDomain, "SUBDATASETS"))
208 7 : return papszSubDatasets;
209 :
210 0 : return GDALPamDataset::GetMetadata(pszDomain);
211 : }
212 :
213 : /************************************************************************/
214 : /* GetScaleFromString() */
215 : /************************************************************************/
216 :
217 22 : static int GetScaleFromString(const char *pszScale)
218 : {
219 22 : const char *pszPtr = strstr(pszScale, "1:");
220 22 : if (pszPtr)
221 22 : pszPtr = pszPtr + 2;
222 : else
223 0 : pszPtr = pszScale;
224 :
225 22 : int nScale = 0;
226 : char ch;
227 112 : while ((ch = *pszPtr) != '\0')
228 : {
229 112 : if (ch >= '0' && ch <= '9')
230 68 : nScale = nScale * 10 + ch - '0';
231 44 : else if (ch == ' ')
232 : ;
233 22 : else if (ch == 'k' || ch == 'K')
234 22 : return nScale * 1000;
235 0 : else if (ch == 'm' || ch == 'M')
236 0 : return nScale * 1000000;
237 : else
238 0 : return 0;
239 90 : pszPtr++;
240 : }
241 0 : return nScale;
242 : }
243 :
244 : /************************************************************************/
245 : /* GetFromBase34() */
246 : /************************************************************************/
247 :
248 53 : static GIntBig GetFromBase34(const char *pszVal, int nMaxSize)
249 : {
250 53 : GIntBig nFrameNumber = 0;
251 583 : for (int i = 0; i < nMaxSize; i++)
252 : {
253 530 : char ch = pszVal[i];
254 530 : if (ch == '\0')
255 0 : break;
256 : int chVal;
257 530 : if (ch >= 'A' && ch <= 'Z')
258 0 : ch += 'a' - 'A';
259 : /* i and o letters are excluded, */
260 530 : if (ch >= '0' && ch <= '9')
261 477 : chVal = ch - '0';
262 53 : else if (ch >= 'a' && ch <= 'h')
263 0 : chVal = ch - 'a' + 10;
264 53 : else if (ch >= 'j' && ch <= 'n')
265 0 : chVal = ch - 'a' + 10 - 1;
266 53 : else if (ch >= 'p' && ch <= 'z')
267 53 : chVal = ch - 'a' + 10 - 2;
268 : else
269 : {
270 0 : CPLDebug("ECRG", "Invalid base34 value : %s", pszVal);
271 0 : break;
272 : }
273 530 : nFrameNumber = nFrameNumber * 34 + chVal;
274 : }
275 :
276 53 : return nFrameNumber;
277 : }
278 :
279 : /************************************************************************/
280 : /* GetExtent() */
281 : /************************************************************************/
282 :
283 : /* MIL-PRF-32283 - Table II. ECRG zone limits. */
284 : /* starting with a fake zone 0 for convenience. */
285 : constexpr int anZoneUpperLat[] = {0, 32, 48, 56, 64, 68, 72, 76, 80};
286 :
287 : /* APPENDIX 70, TABLE III of MIL-A-89007 */
288 : constexpr int anACst_ADRG[] = {369664, 302592, 245760, 199168,
289 : 163328, 137216, 110080, 82432};
290 : constexpr int nBCst_ADRG = 400384;
291 :
292 : // TODO: Why are these two functions done this way?
293 106 : static int CEIL_ROUND(double a, double b)
294 : {
295 106 : return static_cast<int>(ceil(a / b) * b);
296 : }
297 :
298 106 : static int NEAR_ROUND(double a, double b)
299 : {
300 106 : return static_cast<int>(floor((a / b) + 0.5) * b);
301 : }
302 :
303 : constexpr int ECRG_PIXELS = 2304;
304 :
305 53 : static void GetExtent(const char *pszFrameName, int nScale, int nZone,
306 : double &dfMinX, double &dfMaxX, double &dfMinY,
307 : double &dfMaxY, double &dfPixelXSize,
308 : double &dfPixelYSize)
309 : {
310 53 : const int nAbsZone = abs(nZone);
311 : #ifdef DEBUG
312 53 : assert(nAbsZone > 0 && nAbsZone <= 8);
313 : #endif
314 :
315 : /************************************************************************/
316 : /* Compute east-west constant */
317 : /************************************************************************/
318 : /* MIL-PRF-89038 - 60.1.2 - East-west pixel constant. */
319 : const int nEW_ADRG =
320 53 : CEIL_ROUND(anACst_ADRG[nAbsZone - 1] * (1e6 / nScale), 512);
321 53 : const int nEW_CADRG = NEAR_ROUND(nEW_ADRG / (150. / 100.), 256);
322 : /* MIL-PRF-32283 - D.2.1.2 - East-west pixel constant. */
323 53 : const int nEW = nEW_CADRG / 256 * 384;
324 :
325 : /************************************************************************/
326 : /* Compute number of longitudinal frames */
327 : /************************************************************************/
328 : /* MIL-PRF-32283 - D.2.1.7 - Longitudinal frames and subframes */
329 53 : const int nCols =
330 53 : static_cast<int>(ceil(static_cast<double>(nEW) / ECRG_PIXELS));
331 :
332 : /************************************************************************/
333 : /* Compute north-south constant */
334 : /************************************************************************/
335 : /* MIL-PRF-89038 - 60.1.1 - North-south. pixel constant */
336 53 : const int nNS_ADRG = CEIL_ROUND(nBCst_ADRG * (1e6 / nScale), 512) / 4;
337 53 : const int nNS_CADRG = NEAR_ROUND(nNS_ADRG / (150. / 100.), 256);
338 : /* MIL-PRF-32283 - D.2.1.1 - North-south. pixel constant and Frame
339 : * Width/Height */
340 53 : const int nNS = nNS_CADRG / 256 * 384;
341 :
342 : /************************************************************************/
343 : /* Compute number of latitudinal frames and latitude of top of zone */
344 : /************************************************************************/
345 53 : dfPixelYSize = 90.0 / nNS;
346 :
347 53 : const double dfFrameLatHeight = dfPixelYSize * ECRG_PIXELS;
348 :
349 : /* MIL-PRF-32283 - D.2.1.5 - Equatorward and poleward zone extents. */
350 53 : int nUpperZoneFrames =
351 53 : static_cast<int>(ceil(anZoneUpperLat[nAbsZone] / dfFrameLatHeight));
352 53 : int nBottomZoneFrames = static_cast<int>(
353 53 : floor(anZoneUpperLat[nAbsZone - 1] / dfFrameLatHeight));
354 53 : const int nRows = nUpperZoneFrames - nBottomZoneFrames;
355 :
356 : /* Not sure to really understand D.2.1.5.a. Testing needed */
357 53 : if (nZone < 0)
358 : {
359 0 : nUpperZoneFrames = -nBottomZoneFrames;
360 : /*nBottomZoneFrames = nUpperZoneFrames - nRows;*/
361 : }
362 :
363 53 : const double dfUpperZoneTopLat = dfFrameLatHeight * nUpperZoneFrames;
364 :
365 : /************************************************************************/
366 : /* Compute coordinates of the frame in the zone */
367 : /************************************************************************/
368 :
369 : /* Converts the first 10 characters into a number from base 34 */
370 53 : const GIntBig nFrameNumber = GetFromBase34(pszFrameName, 10);
371 :
372 : /* MIL-PRF-32283 - A.2.6.1 */
373 53 : const GIntBig nY = nFrameNumber / nCols;
374 53 : const GIntBig nX = nFrameNumber % nCols;
375 :
376 : /************************************************************************/
377 : /* Compute extent of the frame */
378 : /************************************************************************/
379 :
380 : /* The nY is counted from the bottom of the zone... Pfff */
381 53 : dfMaxY = dfUpperZoneTopLat - (nRows - 1 - nY) * dfFrameLatHeight;
382 53 : dfMinY = dfMaxY - dfFrameLatHeight;
383 :
384 53 : dfPixelXSize = 360.0 / nEW;
385 :
386 53 : const double dfFrameLongWidth = dfPixelXSize * ECRG_PIXELS;
387 53 : dfMinX = -180.0 + nX * dfFrameLongWidth;
388 53 : dfMaxX = dfMinX + dfFrameLongWidth;
389 :
390 : #ifdef DEBUG_VERBOSE
391 : CPLDebug("ECRG",
392 : "Frame %s : minx=%.16g, maxy=%.16g, maxx=%.16g, miny=%.16g",
393 : pszFrameName, dfMinX, dfMaxY, dfMaxX, dfMinY);
394 : #endif
395 53 : }
396 :
397 : /************************************************************************/
398 : /* ECRGTOCSource */
399 : /************************************************************************/
400 :
401 : class ECRGTOCSource final : public VRTSimpleSource
402 : {
403 : int m_nRasterXSize = 0;
404 : int m_nRasterYSize = 0;
405 : double m_dfMinX = 0;
406 : double m_dfMaxY = 0;
407 : double m_dfPixelXSize = 0;
408 : double m_dfPixelYSize = 0;
409 :
410 : bool ValidateOpenedBand(GDALRasterBand *) const override;
411 :
412 : public:
413 57 : ECRGTOCSource(const char *pszFilename, int nBandIn, int nRasterXSize,
414 : int nRasterYSize, double dfDstXOff, double dfDstYOff,
415 : double dfDstXSize, double dfDstYSize, double dfMinX,
416 : double dfMaxY, double dfPixelXSize, double dfPixelYSize)
417 57 : : m_nRasterXSize(nRasterXSize), m_nRasterYSize(nRasterYSize),
418 : m_dfMinX(dfMinX), m_dfMaxY(dfMaxY), m_dfPixelXSize(dfPixelXSize),
419 57 : m_dfPixelYSize(dfPixelYSize)
420 : {
421 57 : SetSrcBand(pszFilename, nBandIn);
422 57 : SetSrcWindow(0, 0, nRasterXSize, nRasterYSize);
423 57 : SetDstWindow(dfDstXOff, dfDstYOff, dfDstXSize, dfDstYSize);
424 57 : }
425 : };
426 :
427 : /************************************************************************/
428 : /* ValidateOpenedBand() */
429 : /************************************************************************/
430 :
431 : #define WARN_CHECK_DS(x) \
432 : do \
433 : { \
434 : if (!(x)) \
435 : { \
436 : CPLError(CE_Warning, CPLE_AppDefined, \
437 : "For %s, assert '" #x "' failed", \
438 : poSourceDS->GetDescription()); \
439 : checkOK = false; \
440 : } \
441 : } while (false)
442 :
443 10 : bool ECRGTOCSource::ValidateOpenedBand(GDALRasterBand *poBand) const
444 : {
445 10 : bool checkOK = true;
446 10 : auto poSourceDS = poBand->GetDataset();
447 10 : CPLAssert(poSourceDS);
448 :
449 10 : double l_adfGeoTransform[6] = {};
450 10 : poSourceDS->GetGeoTransform(l_adfGeoTransform);
451 10 : WARN_CHECK_DS(fabs(l_adfGeoTransform[0] - m_dfMinX) < 1e-10);
452 10 : WARN_CHECK_DS(fabs(l_adfGeoTransform[3] - m_dfMaxY) < 1e-10);
453 10 : WARN_CHECK_DS(fabs(l_adfGeoTransform[1] - m_dfPixelXSize) < 1e-10);
454 10 : WARN_CHECK_DS(fabs(l_adfGeoTransform[5] - (-m_dfPixelYSize)) < 1e-10);
455 10 : WARN_CHECK_DS(l_adfGeoTransform[2] == 0 &&
456 : l_adfGeoTransform[4] == 0); // No rotation.
457 10 : WARN_CHECK_DS(poSourceDS->GetRasterCount() == 3);
458 10 : WARN_CHECK_DS(poSourceDS->GetRasterXSize() == m_nRasterXSize);
459 10 : WARN_CHECK_DS(poSourceDS->GetRasterYSize() == m_nRasterYSize);
460 10 : WARN_CHECK_DS(
461 : EQUAL(poSourceDS->GetProjectionRef(), SRS_WKT_WGS84_LAT_LONG));
462 10 : WARN_CHECK_DS(poSourceDS->GetRasterBand(1)->GetRasterDataType() ==
463 : GDT_Byte);
464 10 : return checkOK;
465 : }
466 :
467 : /************************************************************************/
468 : /* BuildFullName() */
469 : /************************************************************************/
470 :
471 53 : static std::string BuildFullName(const char *pszTOCFilename,
472 : const char *pszFramePath,
473 : const char *pszFrameName)
474 : {
475 53 : char *pszPath = nullptr;
476 53 : if (pszFramePath[0] == '.' &&
477 0 : (pszFramePath[1] == '/' || pszFramePath[1] == '\\'))
478 0 : pszPath = CPLStrdup(pszFramePath + 2);
479 : else
480 53 : pszPath = CPLStrdup(pszFramePath);
481 371 : for (int i = 0; pszPath[i] != '\0'; i++)
482 : {
483 318 : if (pszPath[i] == '\\')
484 53 : pszPath[i] = '/';
485 : }
486 : const std::string osName =
487 106 : CPLFormFilenameSafe(pszPath, pszFrameName, nullptr);
488 53 : CPLFree(pszPath);
489 53 : pszPath = nullptr;
490 106 : std::string osTOCPath = CPLGetDirnameSafe(pszTOCFilename);
491 53 : const auto nPosFirstSlashInName = osName.find('/');
492 53 : if (nPosFirstSlashInName != std::string::npos)
493 : {
494 53 : if (osTOCPath.size() >= nPosFirstSlashInName + 1 &&
495 53 : (osTOCPath[osTOCPath.size() - (nPosFirstSlashInName + 1)] == '/' ||
496 53 : osTOCPath[osTOCPath.size() - (nPosFirstSlashInName + 1)] ==
497 106 : '\\') &&
498 0 : strncmp(osTOCPath.c_str() + osTOCPath.size() - nPosFirstSlashInName,
499 : osName.c_str(), nPosFirstSlashInName) == 0)
500 : {
501 0 : osTOCPath = CPLGetDirnameSafe(osTOCPath.c_str());
502 : }
503 : }
504 106 : return CPLProjectRelativeFilenameSafe(osTOCPath.c_str(), osName.c_str());
505 : }
506 :
507 : /************************************************************************/
508 : /* Build() */
509 : /************************************************************************/
510 :
511 : /* Builds a ECRGTOCSubDataset from the set of files of the toc entry */
512 11 : GDALDataset *ECRGTOCSubDataset::Build(
513 : const char *pszProductTitle, const char *pszDiscId, int nScale,
514 : int nCountSubDataset, const char *pszTOCFilename,
515 : const std::vector<FrameDesc> &aosFrameDesc, double dfGlobalMinX,
516 : double dfGlobalMinY, double dfGlobalMaxX, double dfGlobalMaxY,
517 : double dfGlobalPixelXSize, double dfGlobalPixelYSize)
518 : {
519 11 : GDALDriver *poDriver = GetGDALDriverManager()->GetDriverByName("VRT");
520 11 : if (poDriver == nullptr)
521 0 : return nullptr;
522 :
523 11 : const int nSizeX = static_cast<int>(
524 11 : (dfGlobalMaxX - dfGlobalMinX) / dfGlobalPixelXSize + 0.5);
525 11 : const int nSizeY = static_cast<int>(
526 11 : (dfGlobalMaxY - dfGlobalMinY) / dfGlobalPixelYSize + 0.5);
527 :
528 : /* ------------------------------------ */
529 : /* Create the VRT with the overall size */
530 : /* ------------------------------------ */
531 11 : ECRGTOCSubDataset *poVirtualDS = new ECRGTOCSubDataset(nSizeX, nSizeY);
532 :
533 11 : poVirtualDS->SetProjection(SRS_WKT_WGS84_LAT_LONG);
534 :
535 11 : double adfGeoTransform[6] = {
536 : dfGlobalMinX, dfGlobalPixelXSize, 0, dfGlobalMaxY, 0,
537 11 : -dfGlobalPixelYSize};
538 11 : poVirtualDS->SetGeoTransform(adfGeoTransform);
539 :
540 44 : for (int i = 0; i < 3; i++)
541 : {
542 33 : poVirtualDS->AddBand(GDT_Byte, nullptr);
543 33 : GDALRasterBand *poBand = poVirtualDS->GetRasterBand(i + 1);
544 33 : poBand->SetColorInterpretation(
545 33 : static_cast<GDALColorInterp>(GCI_RedBand + i));
546 : }
547 :
548 11 : poVirtualDS->SetDescription(pszTOCFilename);
549 :
550 11 : poVirtualDS->SetMetadataItem("PRODUCT_TITLE", pszProductTitle);
551 11 : poVirtualDS->SetMetadataItem("DISC_ID", pszDiscId);
552 11 : if (nScale != -1)
553 11 : poVirtualDS->SetMetadataItem("SCALE", CPLString().Printf("%d", nScale));
554 :
555 : /* -------------------------------------------------------------------- */
556 : /* Check for overviews. */
557 : /* -------------------------------------------------------------------- */
558 :
559 11 : poVirtualDS->oOvManager.Initialize(
560 : poVirtualDS,
561 22 : CPLString().Printf("%s.%d", pszTOCFilename, nCountSubDataset));
562 :
563 11 : poVirtualDS->papszFileList = poVirtualDS->GDALDataset::GetFileList();
564 :
565 : // Rather hacky... Force GDAL_FORCE_CACHING=NO so that the
566 : // GDALProxyPoolRasterBand do not use the GDALRasterBand::IRasterIO()
567 : // default implementation, which would rely on the block size of
568 : // GDALProxyPoolRasterBand, which we don't know...
569 11 : CPLConfigOptionSetter oSetter("GDAL_FORCE_CACHING", "NO", false);
570 :
571 30 : for (int i = 0; i < static_cast<int>(aosFrameDesc.size()); i++)
572 : {
573 : const std::string osName = BuildFullName(
574 38 : pszTOCFilename, aosFrameDesc[i].pszPath, aosFrameDesc[i].pszName);
575 :
576 19 : double dfMinX = 0.0;
577 19 : double dfMaxX = 0.0;
578 19 : double dfMinY = 0.0;
579 19 : double dfMaxY = 0.0;
580 19 : double dfPixelXSize = 0.0;
581 19 : double dfPixelYSize = 0.0;
582 19 : ::GetExtent(aosFrameDesc[i].pszName, aosFrameDesc[i].nScale,
583 19 : aosFrameDesc[i].nZone, dfMinX, dfMaxX, dfMinY, dfMaxY,
584 : dfPixelXSize, dfPixelYSize);
585 :
586 19 : const int nFrameXSize =
587 19 : static_cast<int>((dfMaxX - dfMinX) / dfPixelXSize + 0.5);
588 19 : const int nFrameYSize =
589 19 : static_cast<int>((dfMaxY - dfMinY) / dfPixelYSize + 0.5);
590 :
591 19 : poVirtualDS->papszFileList =
592 19 : CSLAddString(poVirtualDS->papszFileList, osName.c_str());
593 :
594 76 : for (int j = 0; j < 3; j++)
595 : {
596 : VRTSourcedRasterBand *poBand =
597 57 : cpl::down_cast<VRTSourcedRasterBand *>(
598 : poVirtualDS->GetRasterBand(j + 1));
599 : /* Place the raster band at the right position in the VRT */
600 : auto poSource = new ECRGTOCSource(
601 57 : osName.c_str(), j + 1, nFrameXSize, nFrameYSize,
602 57 : static_cast<int>((dfMinX - dfGlobalMinX) / dfGlobalPixelXSize +
603 : 0.5),
604 57 : static_cast<int>((dfGlobalMaxY - dfMaxY) / dfGlobalPixelYSize +
605 : 0.5),
606 57 : static_cast<int>((dfMaxX - dfMinX) / dfGlobalPixelXSize + 0.5),
607 57 : static_cast<int>((dfMaxY - dfMinY) / dfGlobalPixelYSize + 0.5),
608 57 : dfMinX, dfMaxY, dfPixelXSize, dfPixelYSize);
609 57 : poBand->AddSource(poSource);
610 : }
611 : }
612 :
613 11 : poVirtualDS->SetMetadataItem("INTERLEAVE", "PIXEL", "IMAGE_STRUCTURE");
614 :
615 11 : return poVirtualDS;
616 : }
617 :
618 : /************************************************************************/
619 : /* Build() */
620 : /************************************************************************/
621 :
622 21 : GDALDataset *ECRGTOCDataset::Build(const char *pszTOCFilename,
623 : CPLXMLNode *psXML,
624 : const std::string &osProduct,
625 : const std::string &osDiscId,
626 : const std::string &osScale,
627 : const char *pszOpenInfoFilename)
628 : {
629 21 : CPLXMLNode *psTOC = CPLGetXMLNode(psXML, "=Table_of_Contents");
630 21 : if (psTOC == nullptr)
631 : {
632 0 : CPLError(CE_Warning, CPLE_AppDefined,
633 : "Cannot find Table_of_Contents element");
634 0 : return nullptr;
635 : }
636 :
637 21 : double dfGlobalMinX = 0.0;
638 21 : double dfGlobalMinY = 0.0;
639 21 : double dfGlobalMaxX = 0.0;
640 21 : double dfGlobalMaxY = 0.0;
641 21 : double dfGlobalPixelXSize = 0.0;
642 21 : double dfGlobalPixelYSize = 0.0;
643 21 : bool bGlobalExtentValid = false;
644 :
645 21 : ECRGTOCDataset *poDS = new ECRGTOCDataset();
646 21 : int nSubDatasets = 0;
647 :
648 21 : int bLookForSubDataset = !osProduct.empty() && !osDiscId.empty();
649 :
650 21 : int nCountSubDataset = 0;
651 :
652 21 : poDS->SetDescription(pszOpenInfoFilename);
653 21 : poDS->papszFileList = poDS->GDALDataset::GetFileList();
654 :
655 62 : for (CPLXMLNode *psIter1 = psTOC->psChild; psIter1 != nullptr;
656 41 : psIter1 = psIter1->psNext)
657 : {
658 52 : if (!(psIter1->eType == CXT_Element && psIter1->pszValue != nullptr &&
659 52 : strcmp(psIter1->pszValue, "product") == 0))
660 31 : continue;
661 :
662 : const char *pszProductTitle =
663 21 : CPLGetXMLValue(psIter1, "product_title", nullptr);
664 21 : if (pszProductTitle == nullptr)
665 : {
666 0 : CPLError(CE_Warning, CPLE_AppDefined,
667 : "Cannot find product_title attribute");
668 0 : continue;
669 : }
670 :
671 35 : if (bLookForSubDataset &&
672 35 : strcmp(LaunderString(pszProductTitle), osProduct.c_str()) != 0)
673 1 : continue;
674 :
675 51 : for (CPLXMLNode *psIter2 = psIter1->psChild; psIter2 != nullptr;
676 31 : psIter2 = psIter2->psNext)
677 : {
678 42 : if (!(psIter2->eType == CXT_Element &&
679 22 : psIter2->pszValue != nullptr &&
680 22 : strcmp(psIter2->pszValue, "disc") == 0))
681 20 : continue;
682 :
683 22 : const char *pszDiscId = CPLGetXMLValue(psIter2, "id", nullptr);
684 22 : if (pszDiscId == nullptr)
685 : {
686 0 : CPLError(CE_Warning, CPLE_AppDefined,
687 : "Cannot find id attribute");
688 0 : continue;
689 : }
690 :
691 36 : if (bLookForSubDataset &&
692 36 : strcmp(LaunderString(pszDiscId), osDiscId.c_str()) != 0)
693 2 : continue;
694 :
695 20 : CPLXMLNode *psFrameList = CPLGetXMLNode(psIter2, "frame_list");
696 20 : if (psFrameList == nullptr)
697 : {
698 0 : CPLError(CE_Warning, CPLE_AppDefined,
699 : "Cannot find frame_list element");
700 0 : continue;
701 : }
702 :
703 51 : for (CPLXMLNode *psIter3 = psFrameList->psChild; psIter3 != nullptr;
704 31 : psIter3 = psIter3->psNext)
705 : {
706 42 : if (!(psIter3->eType == CXT_Element &&
707 22 : psIter3->pszValue != nullptr &&
708 22 : strcmp(psIter3->pszValue, "scale") == 0))
709 22 : continue;
710 :
711 22 : const char *pszSize = CPLGetXMLValue(psIter3, "size", nullptr);
712 22 : if (pszSize == nullptr)
713 : {
714 0 : CPLError(CE_Warning, CPLE_AppDefined,
715 : "Cannot find size attribute");
716 0 : continue;
717 : }
718 :
719 22 : int nScale = GetScaleFromString(pszSize);
720 22 : if (nScale <= 0)
721 : {
722 0 : CPLError(CE_Warning, CPLE_AppDefined, "Invalid scale %s",
723 : pszSize);
724 0 : continue;
725 : }
726 :
727 22 : if (bLookForSubDataset)
728 : {
729 13 : if (!osScale.empty())
730 : {
731 12 : if (strcmp(LaunderString(pszSize), osScale.c_str()) !=
732 : 0)
733 : {
734 2 : continue;
735 : }
736 : }
737 : else
738 : {
739 1 : int nCountScales = 0;
740 1 : for (CPLXMLNode *psIter4 = psFrameList->psChild;
741 3 : psIter4 != nullptr; psIter4 = psIter4->psNext)
742 : {
743 2 : if (!(psIter4->eType == CXT_Element &&
744 1 : psIter4->pszValue != nullptr &&
745 1 : strcmp(psIter4->pszValue, "scale") == 0))
746 1 : continue;
747 1 : nCountScales++;
748 : }
749 1 : if (nCountScales > 1)
750 : {
751 0 : CPLError(CE_Failure, CPLE_AppDefined,
752 : "Scale should be mentioned in "
753 : "subdatasets syntax since this disk "
754 : "contains several scales");
755 0 : delete poDS;
756 11 : return nullptr;
757 : }
758 : }
759 : }
760 :
761 20 : nCountSubDataset++;
762 :
763 20 : std::vector<FrameDesc> aosFrameDesc;
764 20 : int nValidFrames = 0;
765 :
766 74 : for (CPLXMLNode *psIter4 = psIter3->psChild; psIter4 != nullptr;
767 54 : psIter4 = psIter4->psNext)
768 : {
769 54 : if (!(psIter4->eType == CXT_Element &&
770 34 : psIter4->pszValue != nullptr &&
771 34 : strcmp(psIter4->pszValue, "frame") == 0))
772 20 : continue;
773 :
774 : const char *pszFrameName =
775 34 : CPLGetXMLValue(psIter4, "name", nullptr);
776 34 : if (pszFrameName == nullptr)
777 : {
778 0 : CPLError(CE_Warning, CPLE_AppDefined,
779 : "Cannot find name element");
780 0 : continue;
781 : }
782 :
783 34 : if (strlen(pszFrameName) != 18)
784 : {
785 0 : CPLError(CE_Warning, CPLE_AppDefined,
786 : "Invalid value for name element : %s",
787 : pszFrameName);
788 0 : continue;
789 : }
790 :
791 : const char *pszFramePath =
792 34 : CPLGetXMLValue(psIter4, "frame_path", nullptr);
793 34 : if (pszFramePath == nullptr)
794 : {
795 0 : CPLError(CE_Warning, CPLE_AppDefined,
796 : "Cannot find frame_path element");
797 0 : continue;
798 : }
799 :
800 : const char *pszFrameZone =
801 34 : CPLGetXMLValue(psIter4, "frame_zone", nullptr);
802 34 : if (pszFrameZone == nullptr)
803 : {
804 0 : CPLError(CE_Warning, CPLE_AppDefined,
805 : "Cannot find frame_zone element");
806 0 : continue;
807 : }
808 34 : if (strlen(pszFrameZone) != 1)
809 : {
810 0 : CPLError(CE_Warning, CPLE_AppDefined,
811 : "Invalid value for frame_zone element : %s",
812 : pszFrameZone);
813 0 : continue;
814 : }
815 34 : char chZone = pszFrameZone[0];
816 34 : int nZone = 0;
817 34 : if (chZone >= '1' && chZone <= '9')
818 34 : nZone = chZone - '0';
819 0 : else if (chZone >= 'a' && chZone <= 'h')
820 0 : nZone = -(chZone - 'a' + 1);
821 0 : else if (chZone >= 'A' && chZone <= 'H')
822 0 : nZone = -(chZone - 'A' + 1);
823 0 : else if (chZone == 'j' || chZone == 'J')
824 0 : nZone = -9;
825 : else
826 : {
827 0 : CPLError(CE_Warning, CPLE_AppDefined,
828 : "Invalid value for frame_zone element : %s",
829 : pszFrameZone);
830 0 : continue;
831 : }
832 34 : if (nZone == 9 || nZone == -9)
833 : {
834 0 : CPLError(
835 : CE_Warning, CPLE_AppDefined,
836 : "Polar zones unhandled by current implementation");
837 0 : continue;
838 : }
839 :
840 34 : double dfMinX = 0.0;
841 34 : double dfMaxX = 0.0;
842 34 : double dfMinY = 0.0;
843 34 : double dfMaxY = 0.0;
844 34 : double dfPixelXSize = 0.0;
845 34 : double dfPixelYSize = 0.0;
846 34 : ::GetExtent(pszFrameName, nScale, nZone, dfMinX, dfMaxX,
847 : dfMinY, dfMaxY, dfPixelXSize, dfPixelYSize);
848 :
849 34 : nValidFrames++;
850 :
851 : const std::string osFullName = BuildFullName(
852 68 : pszTOCFilename, pszFramePath, pszFrameName);
853 34 : poDS->papszFileList =
854 34 : CSLAddString(poDS->papszFileList, osFullName.c_str());
855 :
856 34 : if (!bGlobalExtentValid)
857 : {
858 18 : dfGlobalMinX = dfMinX;
859 18 : dfGlobalMinY = dfMinY;
860 18 : dfGlobalMaxX = dfMaxX;
861 18 : dfGlobalMaxY = dfMaxY;
862 18 : dfGlobalPixelXSize = dfPixelXSize;
863 18 : dfGlobalPixelYSize = dfPixelYSize;
864 18 : bGlobalExtentValid = true;
865 : }
866 : else
867 : {
868 16 : if (dfMinX < dfGlobalMinX)
869 0 : dfGlobalMinX = dfMinX;
870 16 : if (dfMinY < dfGlobalMinY)
871 0 : dfGlobalMinY = dfMinY;
872 16 : if (dfMaxX > dfGlobalMaxX)
873 15 : dfGlobalMaxX = dfMaxX;
874 16 : if (dfMaxY > dfGlobalMaxY)
875 1 : dfGlobalMaxY = dfMaxY;
876 16 : if (dfPixelXSize < dfGlobalPixelXSize)
877 0 : dfGlobalPixelXSize = dfPixelXSize;
878 16 : if (dfPixelYSize < dfGlobalPixelYSize)
879 0 : dfGlobalPixelYSize = dfPixelYSize;
880 : }
881 :
882 34 : nValidFrames++;
883 :
884 34 : if (bLookForSubDataset)
885 : {
886 : FrameDesc frameDesc;
887 19 : frameDesc.pszName = pszFrameName;
888 19 : frameDesc.pszPath = pszFramePath;
889 19 : frameDesc.nScale = nScale;
890 19 : frameDesc.nZone = nZone;
891 19 : aosFrameDesc.push_back(frameDesc);
892 : }
893 : }
894 :
895 20 : if (bLookForSubDataset)
896 : {
897 11 : delete poDS;
898 11 : if (nValidFrames == 0)
899 0 : return nullptr;
900 11 : return ECRGTOCSubDataset::Build(
901 : pszProductTitle, pszDiscId, nScale, nCountSubDataset,
902 : pszTOCFilename, aosFrameDesc, dfGlobalMinX,
903 : dfGlobalMinY, dfGlobalMaxX, dfGlobalMaxY,
904 11 : dfGlobalPixelXSize, dfGlobalPixelYSize);
905 : }
906 :
907 9 : if (nValidFrames)
908 : {
909 9 : poDS->AddSubDataset(pszOpenInfoFilename, pszProductTitle,
910 : pszDiscId, pszSize);
911 9 : nSubDatasets++;
912 : }
913 : }
914 : }
915 : }
916 :
917 10 : if (!bGlobalExtentValid)
918 : {
919 3 : delete poDS;
920 3 : return nullptr;
921 : }
922 :
923 7 : if (nSubDatasets == 1)
924 : {
925 12 : const char *pszSubDatasetName = CSLFetchNameValue(
926 6 : poDS->GetMetadata("SUBDATASETS"), "SUBDATASET_1_NAME");
927 6 : GDALOpenInfo oOpenInfo(pszSubDatasetName, GA_ReadOnly);
928 6 : delete poDS;
929 6 : GDALDataset *poRetDS = Open(&oOpenInfo);
930 6 : if (poRetDS)
931 6 : poRetDS->SetDescription(pszOpenInfoFilename);
932 6 : return poRetDS;
933 : }
934 :
935 1 : poDS->adfGeoTransform[0] = dfGlobalMinX;
936 1 : poDS->adfGeoTransform[1] = dfGlobalPixelXSize;
937 1 : poDS->adfGeoTransform[2] = 0.0;
938 1 : poDS->adfGeoTransform[3] = dfGlobalMaxY;
939 1 : poDS->adfGeoTransform[4] = 0.0;
940 1 : poDS->adfGeoTransform[5] = -dfGlobalPixelYSize;
941 :
942 1 : poDS->nRasterXSize = static_cast<int>(0.5 + (dfGlobalMaxX - dfGlobalMinX) /
943 : dfGlobalPixelXSize);
944 1 : poDS->nRasterYSize = static_cast<int>(0.5 + (dfGlobalMaxY - dfGlobalMinY) /
945 : dfGlobalPixelYSize);
946 :
947 : /* -------------------------------------------------------------------- */
948 : /* Initialize any PAM information. */
949 : /* -------------------------------------------------------------------- */
950 1 : poDS->TryLoadXML();
951 :
952 1 : return poDS;
953 : }
954 :
955 : /************************************************************************/
956 : /* Open() */
957 : /************************************************************************/
958 :
959 30 : GDALDataset *ECRGTOCDataset::Open(GDALOpenInfo *poOpenInfo)
960 :
961 : {
962 30 : if (!ECRGTOCDriverIdentify(poOpenInfo))
963 0 : return nullptr;
964 :
965 30 : const char *pszFilename = poOpenInfo->pszFilename;
966 60 : CPLString osFilename;
967 60 : CPLString osProduct, osDiscId, osScale;
968 :
969 30 : if (STARTS_WITH_CI(pszFilename, "ECRG_TOC_ENTRY:"))
970 : {
971 23 : pszFilename += strlen("ECRG_TOC_ENTRY:");
972 :
973 : /* PRODUCT:DISK:SCALE:FILENAME (or PRODUCT:DISK:FILENAME historically)
974 : */
975 : /* with FILENAME potentially C:\BLA... */
976 23 : char **papszTokens = CSLTokenizeString2(pszFilename, ":", 0);
977 23 : int nTokens = CSLCount(papszTokens);
978 23 : if (nTokens != 3 && nTokens != 4 && nTokens != 5)
979 : {
980 4 : CSLDestroy(papszTokens);
981 4 : return nullptr;
982 : }
983 :
984 19 : osProduct = papszTokens[0];
985 19 : osDiscId = papszTokens[1];
986 :
987 19 : if (nTokens == 3)
988 5 : osFilename = papszTokens[2];
989 14 : else if (nTokens == 4)
990 : {
991 13 : if (strlen(papszTokens[2]) == 1 &&
992 1 : (papszTokens[3][0] == '\\' || papszTokens[3][0] == '/'))
993 : {
994 1 : osFilename = papszTokens[2];
995 1 : osFilename += ":";
996 1 : osFilename += papszTokens[3];
997 : }
998 : else
999 : {
1000 12 : osScale = papszTokens[2];
1001 12 : osFilename = papszTokens[3];
1002 : }
1003 : }
1004 1 : else if (nTokens == 5 && strlen(papszTokens[3]) == 1 &&
1005 1 : (papszTokens[4][0] == '\\' || papszTokens[4][0] == '/'))
1006 : {
1007 1 : osScale = papszTokens[2];
1008 1 : osFilename = papszTokens[3];
1009 1 : osFilename += ":";
1010 1 : osFilename += papszTokens[4];
1011 : }
1012 : else
1013 : {
1014 0 : CSLDestroy(papszTokens);
1015 0 : return nullptr;
1016 : }
1017 :
1018 19 : CSLDestroy(papszTokens);
1019 19 : pszFilename = osFilename.c_str();
1020 : }
1021 :
1022 : /* -------------------------------------------------------------------- */
1023 : /* Parse the XML file */
1024 : /* -------------------------------------------------------------------- */
1025 26 : CPLXMLNode *psXML = CPLParseXMLFile(pszFilename);
1026 26 : if (psXML == nullptr)
1027 : {
1028 5 : return nullptr;
1029 : }
1030 :
1031 42 : GDALDataset *poDS = Build(pszFilename, psXML, osProduct, osDiscId, osScale,
1032 21 : poOpenInfo->pszFilename);
1033 21 : CPLDestroyXMLNode(psXML);
1034 :
1035 21 : if (poDS && poOpenInfo->eAccess == GA_Update)
1036 : {
1037 0 : ReportUpdateNotSupportedByDriver("ECRGTOC");
1038 0 : delete poDS;
1039 0 : return nullptr;
1040 : }
1041 :
1042 21 : return poDS;
1043 : }
1044 :
1045 : /************************************************************************/
1046 : /* GDALRegister_ECRGTOC() */
1047 : /************************************************************************/
1048 :
1049 1911 : void GDALRegister_ECRGTOC()
1050 :
1051 : {
1052 1911 : if (GDALGetDriverByName(ECRGTOC_DRIVER_NAME) != nullptr)
1053 282 : return;
1054 :
1055 1629 : GDALDriver *poDriver = new GDALDriver();
1056 1629 : ECRGTOCDriverSetCommonMetadata(poDriver);
1057 :
1058 1629 : poDriver->pfnOpen = ECRGTOCDataset::Open;
1059 :
1060 1629 : GetGDALDriverManager()->RegisterDriver(poDriver);
1061 : }
|