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 std::string 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 : const std::string osName =
490 106 : CPLFormFilenameSafe(pszPath, pszFrameName, nullptr);
491 53 : CPLFree(pszPath);
492 53 : pszPath = nullptr;
493 106 : std::string osTOCPath = CPLGetDirnameSafe(pszTOCFilename);
494 53 : const auto nPosFirstSlashInName = osName.find('/');
495 53 : if (nPosFirstSlashInName != std::string::npos)
496 : {
497 53 : if (osTOCPath.size() >= nPosFirstSlashInName + 1 &&
498 53 : (osTOCPath[osTOCPath.size() - (nPosFirstSlashInName + 1)] == '/' ||
499 53 : osTOCPath[osTOCPath.size() - (nPosFirstSlashInName + 1)] ==
500 106 : '\\') &&
501 0 : strncmp(osTOCPath.c_str() + osTOCPath.size() - nPosFirstSlashInName,
502 : osName.c_str(), nPosFirstSlashInName) == 0)
503 : {
504 0 : osTOCPath = CPLGetDirnameSafe(osTOCPath.c_str());
505 : }
506 : }
507 106 : return CPLProjectRelativeFilenameSafe(osTOCPath.c_str(), osName.c_str());
508 : }
509 :
510 : /************************************************************************/
511 : /* Build() */
512 : /************************************************************************/
513 :
514 : /* Builds a ECRGTOCSubDataset from the set of files of the toc entry */
515 11 : GDALDataset *ECRGTOCSubDataset::Build(
516 : const char *pszProductTitle, const char *pszDiscId, int nScale,
517 : int nCountSubDataset, const char *pszTOCFilename,
518 : const std::vector<FrameDesc> &aosFrameDesc, double dfGlobalMinX,
519 : double dfGlobalMinY, double dfGlobalMaxX, double dfGlobalMaxY,
520 : double dfGlobalPixelXSize, double dfGlobalPixelYSize)
521 : {
522 11 : GDALDriver *poDriver = GetGDALDriverManager()->GetDriverByName("VRT");
523 11 : if (poDriver == nullptr)
524 0 : return nullptr;
525 :
526 11 : const int nSizeX = static_cast<int>(
527 11 : (dfGlobalMaxX - dfGlobalMinX) / dfGlobalPixelXSize + 0.5);
528 11 : const int nSizeY = static_cast<int>(
529 11 : (dfGlobalMaxY - dfGlobalMinY) / dfGlobalPixelYSize + 0.5);
530 :
531 : /* ------------------------------------ */
532 : /* Create the VRT with the overall size */
533 : /* ------------------------------------ */
534 11 : ECRGTOCSubDataset *poVirtualDS = new ECRGTOCSubDataset(nSizeX, nSizeY);
535 :
536 11 : poVirtualDS->SetProjection(SRS_WKT_WGS84_LAT_LONG);
537 :
538 11 : double adfGeoTransform[6] = {
539 : dfGlobalMinX, dfGlobalPixelXSize, 0, dfGlobalMaxY, 0,
540 11 : -dfGlobalPixelYSize};
541 11 : poVirtualDS->SetGeoTransform(adfGeoTransform);
542 :
543 44 : for (int i = 0; i < 3; i++)
544 : {
545 33 : poVirtualDS->AddBand(GDT_Byte, nullptr);
546 33 : GDALRasterBand *poBand = poVirtualDS->GetRasterBand(i + 1);
547 33 : poBand->SetColorInterpretation((GDALColorInterp)(GCI_RedBand + i));
548 : }
549 :
550 11 : poVirtualDS->SetDescription(pszTOCFilename);
551 :
552 11 : poVirtualDS->SetMetadataItem("PRODUCT_TITLE", pszProductTitle);
553 11 : poVirtualDS->SetMetadataItem("DISC_ID", pszDiscId);
554 11 : if (nScale != -1)
555 11 : poVirtualDS->SetMetadataItem("SCALE", CPLString().Printf("%d", nScale));
556 :
557 : /* -------------------------------------------------------------------- */
558 : /* Check for overviews. */
559 : /* -------------------------------------------------------------------- */
560 :
561 11 : poVirtualDS->oOvManager.Initialize(
562 : poVirtualDS,
563 22 : CPLString().Printf("%s.%d", pszTOCFilename, nCountSubDataset));
564 :
565 11 : poVirtualDS->papszFileList = poVirtualDS->GDALDataset::GetFileList();
566 :
567 : // Rather hacky... Force GDAL_FORCE_CACHING=NO so that the
568 : // GDALProxyPoolRasterBand do not use the GDALRasterBand::IRasterIO()
569 : // default implementation, which would rely on the block size of
570 : // GDALProxyPoolRasterBand, which we don't know...
571 11 : CPLConfigOptionSetter oSetter("GDAL_FORCE_CACHING", "NO", false);
572 :
573 30 : for (int i = 0; i < static_cast<int>(aosFrameDesc.size()); i++)
574 : {
575 : const std::string osName = BuildFullName(
576 38 : pszTOCFilename, aosFrameDesc[i].pszPath, aosFrameDesc[i].pszName);
577 :
578 19 : double dfMinX = 0.0;
579 19 : double dfMaxX = 0.0;
580 19 : double dfMinY = 0.0;
581 19 : double dfMaxY = 0.0;
582 19 : double dfPixelXSize = 0.0;
583 19 : double dfPixelYSize = 0.0;
584 19 : GetExtent(aosFrameDesc[i].pszName, aosFrameDesc[i].nScale,
585 19 : aosFrameDesc[i].nZone, dfMinX, dfMaxX, dfMinY, dfMaxY,
586 : dfPixelXSize, dfPixelYSize);
587 :
588 19 : const int nFrameXSize =
589 19 : static_cast<int>((dfMaxX - dfMinX) / dfPixelXSize + 0.5);
590 19 : const int nFrameYSize =
591 19 : static_cast<int>((dfMaxY - dfMinY) / dfPixelYSize + 0.5);
592 :
593 19 : poVirtualDS->papszFileList =
594 19 : CSLAddString(poVirtualDS->papszFileList, osName.c_str());
595 :
596 76 : for (int j = 0; j < 3; j++)
597 : {
598 : VRTSourcedRasterBand *poBand =
599 57 : cpl::down_cast<VRTSourcedRasterBand *>(
600 : poVirtualDS->GetRasterBand(j + 1));
601 : /* Place the raster band at the right position in the VRT */
602 : auto poSource = new ECRGTOCSource(
603 57 : osName.c_str(), j + 1, nFrameXSize, nFrameYSize,
604 57 : static_cast<int>((dfMinX - dfGlobalMinX) / dfGlobalPixelXSize +
605 : 0.5),
606 57 : static_cast<int>((dfGlobalMaxY - dfMaxY) / dfGlobalPixelYSize +
607 : 0.5),
608 57 : static_cast<int>((dfMaxX - dfMinX) / dfGlobalPixelXSize + 0.5),
609 57 : static_cast<int>((dfMaxY - dfMinY) / dfGlobalPixelYSize + 0.5),
610 57 : dfMinX, dfMaxY, dfPixelXSize, dfPixelYSize);
611 57 : poBand->AddSource(poSource);
612 : }
613 : }
614 :
615 11 : poVirtualDS->SetMetadataItem("INTERLEAVE", "PIXEL", "IMAGE_STRUCTURE");
616 :
617 11 : return poVirtualDS;
618 : }
619 :
620 : /************************************************************************/
621 : /* Build() */
622 : /************************************************************************/
623 :
624 21 : GDALDataset *ECRGTOCDataset::Build(const char *pszTOCFilename,
625 : CPLXMLNode *psXML,
626 : const std::string &osProduct,
627 : const std::string &osDiscId,
628 : const std::string &osScale,
629 : const char *pszOpenInfoFilename)
630 : {
631 21 : CPLXMLNode *psTOC = CPLGetXMLNode(psXML, "=Table_of_Contents");
632 21 : if (psTOC == nullptr)
633 : {
634 0 : CPLError(CE_Warning, CPLE_AppDefined,
635 : "Cannot find Table_of_Contents element");
636 0 : return nullptr;
637 : }
638 :
639 21 : double dfGlobalMinX = 0.0;
640 21 : double dfGlobalMinY = 0.0;
641 21 : double dfGlobalMaxX = 0.0;
642 21 : double dfGlobalMaxY = 0.0;
643 21 : double dfGlobalPixelXSize = 0.0;
644 21 : double dfGlobalPixelYSize = 0.0;
645 21 : bool bGlobalExtentValid = false;
646 :
647 21 : ECRGTOCDataset *poDS = new ECRGTOCDataset();
648 21 : int nSubDatasets = 0;
649 :
650 21 : int bLookForSubDataset = !osProduct.empty() && !osDiscId.empty();
651 :
652 21 : int nCountSubDataset = 0;
653 :
654 21 : poDS->SetDescription(pszOpenInfoFilename);
655 21 : poDS->papszFileList = poDS->GDALDataset::GetFileList();
656 :
657 62 : for (CPLXMLNode *psIter1 = psTOC->psChild; psIter1 != nullptr;
658 41 : psIter1 = psIter1->psNext)
659 : {
660 52 : if (!(psIter1->eType == CXT_Element && psIter1->pszValue != nullptr &&
661 52 : strcmp(psIter1->pszValue, "product") == 0))
662 31 : continue;
663 :
664 : const char *pszProductTitle =
665 21 : CPLGetXMLValue(psIter1, "product_title", nullptr);
666 21 : if (pszProductTitle == nullptr)
667 : {
668 0 : CPLError(CE_Warning, CPLE_AppDefined,
669 : "Cannot find product_title attribute");
670 0 : continue;
671 : }
672 :
673 35 : if (bLookForSubDataset &&
674 35 : strcmp(LaunderString(pszProductTitle), osProduct.c_str()) != 0)
675 1 : continue;
676 :
677 51 : for (CPLXMLNode *psIter2 = psIter1->psChild; psIter2 != nullptr;
678 31 : psIter2 = psIter2->psNext)
679 : {
680 42 : if (!(psIter2->eType == CXT_Element &&
681 22 : psIter2->pszValue != nullptr &&
682 22 : strcmp(psIter2->pszValue, "disc") == 0))
683 20 : continue;
684 :
685 22 : const char *pszDiscId = CPLGetXMLValue(psIter2, "id", nullptr);
686 22 : if (pszDiscId == nullptr)
687 : {
688 0 : CPLError(CE_Warning, CPLE_AppDefined,
689 : "Cannot find id attribute");
690 0 : continue;
691 : }
692 :
693 36 : if (bLookForSubDataset &&
694 36 : strcmp(LaunderString(pszDiscId), osDiscId.c_str()) != 0)
695 2 : continue;
696 :
697 20 : CPLXMLNode *psFrameList = CPLGetXMLNode(psIter2, "frame_list");
698 20 : if (psFrameList == nullptr)
699 : {
700 0 : CPLError(CE_Warning, CPLE_AppDefined,
701 : "Cannot find frame_list element");
702 0 : continue;
703 : }
704 :
705 51 : for (CPLXMLNode *psIter3 = psFrameList->psChild; psIter3 != nullptr;
706 31 : psIter3 = psIter3->psNext)
707 : {
708 42 : if (!(psIter3->eType == CXT_Element &&
709 22 : psIter3->pszValue != nullptr &&
710 22 : strcmp(psIter3->pszValue, "scale") == 0))
711 22 : continue;
712 :
713 22 : const char *pszSize = CPLGetXMLValue(psIter3, "size", nullptr);
714 22 : if (pszSize == nullptr)
715 : {
716 0 : CPLError(CE_Warning, CPLE_AppDefined,
717 : "Cannot find size attribute");
718 0 : continue;
719 : }
720 :
721 22 : int nScale = GetScaleFromString(pszSize);
722 22 : if (nScale <= 0)
723 : {
724 0 : CPLError(CE_Warning, CPLE_AppDefined, "Invalid scale %s",
725 : pszSize);
726 0 : continue;
727 : }
728 :
729 22 : if (bLookForSubDataset)
730 : {
731 13 : if (!osScale.empty())
732 : {
733 12 : if (strcmp(LaunderString(pszSize), osScale.c_str()) !=
734 : 0)
735 : {
736 2 : continue;
737 : }
738 : }
739 : else
740 : {
741 1 : int nCountScales = 0;
742 1 : for (CPLXMLNode *psIter4 = psFrameList->psChild;
743 3 : psIter4 != nullptr; psIter4 = psIter4->psNext)
744 : {
745 2 : if (!(psIter4->eType == CXT_Element &&
746 1 : psIter4->pszValue != nullptr &&
747 1 : strcmp(psIter4->pszValue, "scale") == 0))
748 1 : continue;
749 1 : nCountScales++;
750 : }
751 1 : if (nCountScales > 1)
752 : {
753 0 : CPLError(CE_Failure, CPLE_AppDefined,
754 : "Scale should be mentioned in "
755 : "subdatasets syntax since this disk "
756 : "contains several scales");
757 0 : delete poDS;
758 11 : return nullptr;
759 : }
760 : }
761 : }
762 :
763 20 : nCountSubDataset++;
764 :
765 20 : std::vector<FrameDesc> aosFrameDesc;
766 20 : int nValidFrames = 0;
767 :
768 74 : for (CPLXMLNode *psIter4 = psIter3->psChild; psIter4 != nullptr;
769 54 : psIter4 = psIter4->psNext)
770 : {
771 54 : if (!(psIter4->eType == CXT_Element &&
772 34 : psIter4->pszValue != nullptr &&
773 34 : strcmp(psIter4->pszValue, "frame") == 0))
774 20 : continue;
775 :
776 : const char *pszFrameName =
777 34 : CPLGetXMLValue(psIter4, "name", nullptr);
778 34 : if (pszFrameName == nullptr)
779 : {
780 0 : CPLError(CE_Warning, CPLE_AppDefined,
781 : "Cannot find name element");
782 0 : continue;
783 : }
784 :
785 34 : if (strlen(pszFrameName) != 18)
786 : {
787 0 : CPLError(CE_Warning, CPLE_AppDefined,
788 : "Invalid value for name element : %s",
789 : pszFrameName);
790 0 : continue;
791 : }
792 :
793 : const char *pszFramePath =
794 34 : CPLGetXMLValue(psIter4, "frame_path", nullptr);
795 34 : if (pszFramePath == nullptr)
796 : {
797 0 : CPLError(CE_Warning, CPLE_AppDefined,
798 : "Cannot find frame_path element");
799 0 : continue;
800 : }
801 :
802 : const char *pszFrameZone =
803 34 : CPLGetXMLValue(psIter4, "frame_zone", nullptr);
804 34 : if (pszFrameZone == nullptr)
805 : {
806 0 : CPLError(CE_Warning, CPLE_AppDefined,
807 : "Cannot find frame_zone element");
808 0 : continue;
809 : }
810 34 : if (strlen(pszFrameZone) != 1)
811 : {
812 0 : CPLError(CE_Warning, CPLE_AppDefined,
813 : "Invalid value for frame_zone element : %s",
814 : pszFrameZone);
815 0 : continue;
816 : }
817 34 : char chZone = pszFrameZone[0];
818 34 : int nZone = 0;
819 34 : if (chZone >= '1' && chZone <= '9')
820 34 : nZone = chZone - '0';
821 0 : else if (chZone >= 'a' && chZone <= 'h')
822 0 : nZone = -(chZone - 'a' + 1);
823 0 : else if (chZone >= 'A' && chZone <= 'H')
824 0 : nZone = -(chZone - 'A' + 1);
825 0 : else if (chZone == 'j' || chZone == 'J')
826 0 : nZone = -9;
827 : else
828 : {
829 0 : CPLError(CE_Warning, CPLE_AppDefined,
830 : "Invalid value for frame_zone element : %s",
831 : pszFrameZone);
832 0 : continue;
833 : }
834 34 : if (nZone == 9 || nZone == -9)
835 : {
836 0 : CPLError(
837 : CE_Warning, CPLE_AppDefined,
838 : "Polar zones unhandled by current implementation");
839 0 : continue;
840 : }
841 :
842 34 : double dfMinX = 0.0;
843 34 : double dfMaxX = 0.0;
844 34 : double dfMinY = 0.0;
845 34 : double dfMaxY = 0.0;
846 34 : double dfPixelXSize = 0.0;
847 34 : double dfPixelYSize = 0.0;
848 34 : GetExtent(pszFrameName, nScale, nZone, dfMinX, dfMaxX,
849 : dfMinY, dfMaxY, dfPixelXSize, dfPixelYSize);
850 :
851 34 : nValidFrames++;
852 :
853 : const std::string osFullName = BuildFullName(
854 68 : pszTOCFilename, pszFramePath, pszFrameName);
855 34 : poDS->papszFileList =
856 34 : CSLAddString(poDS->papszFileList, osFullName.c_str());
857 :
858 34 : if (!bGlobalExtentValid)
859 : {
860 18 : dfGlobalMinX = dfMinX;
861 18 : dfGlobalMinY = dfMinY;
862 18 : dfGlobalMaxX = dfMaxX;
863 18 : dfGlobalMaxY = dfMaxY;
864 18 : dfGlobalPixelXSize = dfPixelXSize;
865 18 : dfGlobalPixelYSize = dfPixelYSize;
866 18 : bGlobalExtentValid = true;
867 : }
868 : else
869 : {
870 16 : if (dfMinX < dfGlobalMinX)
871 0 : dfGlobalMinX = dfMinX;
872 16 : if (dfMinY < dfGlobalMinY)
873 0 : dfGlobalMinY = dfMinY;
874 16 : if (dfMaxX > dfGlobalMaxX)
875 15 : dfGlobalMaxX = dfMaxX;
876 16 : if (dfMaxY > dfGlobalMaxY)
877 1 : dfGlobalMaxY = dfMaxY;
878 16 : if (dfPixelXSize < dfGlobalPixelXSize)
879 0 : dfGlobalPixelXSize = dfPixelXSize;
880 16 : if (dfPixelYSize < dfGlobalPixelYSize)
881 0 : dfGlobalPixelYSize = dfPixelYSize;
882 : }
883 :
884 34 : nValidFrames++;
885 :
886 34 : if (bLookForSubDataset)
887 : {
888 : FrameDesc frameDesc;
889 19 : frameDesc.pszName = pszFrameName;
890 19 : frameDesc.pszPath = pszFramePath;
891 19 : frameDesc.nScale = nScale;
892 19 : frameDesc.nZone = nZone;
893 19 : aosFrameDesc.push_back(frameDesc);
894 : }
895 : }
896 :
897 20 : if (bLookForSubDataset)
898 : {
899 11 : delete poDS;
900 11 : if (nValidFrames == 0)
901 0 : return nullptr;
902 11 : return ECRGTOCSubDataset::Build(
903 : pszProductTitle, pszDiscId, nScale, nCountSubDataset,
904 : pszTOCFilename, aosFrameDesc, dfGlobalMinX,
905 : dfGlobalMinY, dfGlobalMaxX, dfGlobalMaxY,
906 11 : dfGlobalPixelXSize, dfGlobalPixelYSize);
907 : }
908 :
909 9 : if (nValidFrames)
910 : {
911 9 : poDS->AddSubDataset(pszOpenInfoFilename, pszProductTitle,
912 : pszDiscId, pszSize);
913 9 : nSubDatasets++;
914 : }
915 : }
916 : }
917 : }
918 :
919 10 : if (!bGlobalExtentValid)
920 : {
921 3 : delete poDS;
922 3 : return nullptr;
923 : }
924 :
925 7 : if (nSubDatasets == 1)
926 : {
927 12 : const char *pszSubDatasetName = CSLFetchNameValue(
928 6 : poDS->GetMetadata("SUBDATASETS"), "SUBDATASET_1_NAME");
929 6 : GDALOpenInfo oOpenInfo(pszSubDatasetName, GA_ReadOnly);
930 6 : delete poDS;
931 6 : GDALDataset *poRetDS = Open(&oOpenInfo);
932 6 : if (poRetDS)
933 6 : poRetDS->SetDescription(pszOpenInfoFilename);
934 6 : return poRetDS;
935 : }
936 :
937 1 : poDS->adfGeoTransform[0] = dfGlobalMinX;
938 1 : poDS->adfGeoTransform[1] = dfGlobalPixelXSize;
939 1 : poDS->adfGeoTransform[2] = 0.0;
940 1 : poDS->adfGeoTransform[3] = dfGlobalMaxY;
941 1 : poDS->adfGeoTransform[4] = 0.0;
942 1 : poDS->adfGeoTransform[5] = -dfGlobalPixelYSize;
943 :
944 1 : poDS->nRasterXSize = static_cast<int>(0.5 + (dfGlobalMaxX - dfGlobalMinX) /
945 : dfGlobalPixelXSize);
946 1 : poDS->nRasterYSize = static_cast<int>(0.5 + (dfGlobalMaxY - dfGlobalMinY) /
947 : dfGlobalPixelYSize);
948 :
949 : /* -------------------------------------------------------------------- */
950 : /* Initialize any PAM information. */
951 : /* -------------------------------------------------------------------- */
952 1 : poDS->TryLoadXML();
953 :
954 1 : return poDS;
955 : }
956 :
957 : /************************************************************************/
958 : /* Open() */
959 : /************************************************************************/
960 :
961 30 : GDALDataset *ECRGTOCDataset::Open(GDALOpenInfo *poOpenInfo)
962 :
963 : {
964 30 : if (!ECRGTOCDriverIdentify(poOpenInfo))
965 0 : return nullptr;
966 :
967 30 : const char *pszFilename = poOpenInfo->pszFilename;
968 60 : CPLString osFilename;
969 60 : CPLString osProduct, osDiscId, osScale;
970 :
971 30 : if (STARTS_WITH_CI(pszFilename, "ECRG_TOC_ENTRY:"))
972 : {
973 23 : pszFilename += strlen("ECRG_TOC_ENTRY:");
974 :
975 : /* PRODUCT:DISK:SCALE:FILENAME (or PRODUCT:DISK:FILENAME historically)
976 : */
977 : /* with FILENAME potentially C:\BLA... */
978 23 : char **papszTokens = CSLTokenizeString2(pszFilename, ":", 0);
979 23 : int nTokens = CSLCount(papszTokens);
980 23 : if (nTokens != 3 && nTokens != 4 && nTokens != 5)
981 : {
982 4 : CSLDestroy(papszTokens);
983 4 : return nullptr;
984 : }
985 :
986 19 : osProduct = papszTokens[0];
987 19 : osDiscId = papszTokens[1];
988 :
989 19 : if (nTokens == 3)
990 5 : osFilename = papszTokens[2];
991 14 : else if (nTokens == 4)
992 : {
993 13 : if (strlen(papszTokens[2]) == 1 &&
994 1 : (papszTokens[3][0] == '\\' || papszTokens[3][0] == '/'))
995 : {
996 1 : osFilename = papszTokens[2];
997 1 : osFilename += ":";
998 1 : osFilename += papszTokens[3];
999 : }
1000 : else
1001 : {
1002 12 : osScale = papszTokens[2];
1003 12 : osFilename = papszTokens[3];
1004 : }
1005 : }
1006 1 : else if (nTokens == 5 && strlen(papszTokens[3]) == 1 &&
1007 1 : (papszTokens[4][0] == '\\' || papszTokens[4][0] == '/'))
1008 : {
1009 1 : osScale = papszTokens[2];
1010 1 : osFilename = papszTokens[3];
1011 1 : osFilename += ":";
1012 1 : osFilename += papszTokens[4];
1013 : }
1014 : else
1015 : {
1016 0 : CSLDestroy(papszTokens);
1017 0 : return nullptr;
1018 : }
1019 :
1020 19 : CSLDestroy(papszTokens);
1021 19 : pszFilename = osFilename.c_str();
1022 : }
1023 :
1024 : /* -------------------------------------------------------------------- */
1025 : /* Parse the XML file */
1026 : /* -------------------------------------------------------------------- */
1027 26 : CPLXMLNode *psXML = CPLParseXMLFile(pszFilename);
1028 26 : if (psXML == nullptr)
1029 : {
1030 5 : return nullptr;
1031 : }
1032 :
1033 42 : GDALDataset *poDS = Build(pszFilename, psXML, osProduct, osDiscId, osScale,
1034 21 : poOpenInfo->pszFilename);
1035 21 : CPLDestroyXMLNode(psXML);
1036 :
1037 21 : if (poDS && poOpenInfo->eAccess == GA_Update)
1038 : {
1039 0 : CPLError(CE_Failure, CPLE_NotSupported,
1040 : "ECRGTOC driver does not support update mode");
1041 0 : delete poDS;
1042 0 : return nullptr;
1043 : }
1044 :
1045 21 : return poDS;
1046 : }
1047 :
1048 : /************************************************************************/
1049 : /* GDALRegister_ECRGTOC() */
1050 : /************************************************************************/
1051 :
1052 1682 : void GDALRegister_ECRGTOC()
1053 :
1054 : {
1055 1682 : if (GDALGetDriverByName(ECRGTOC_DRIVER_NAME) != nullptr)
1056 301 : return;
1057 :
1058 1381 : GDALDriver *poDriver = new GDALDriver();
1059 1381 : ECRGTOCDriverSetCommonMetadata(poDriver);
1060 :
1061 1381 : poDriver->pfnOpen = ECRGTOCDataset::Open;
1062 :
1063 1381 : GetGDALDriverManager()->RegisterDriver(poDriver);
1064 : }
|