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