Line data Source code
1 : /******************************************************************************
2 : *
3 : * Project: GDAL Core
4 : * Purpose: Read metadata (mainly the remote sensing imagery) from files of
5 : * different providers like DigitalGlobe, GeoEye etc.
6 : * Author: Frank Warmerdam, warmerdam@pobox.com
7 : * Author: Dmitry Baryshnikov, polimax@mail.ru
8 : *
9 : ******************************************************************************
10 : * Copyright (c) HER MAJESTY THE QUEEN IN RIGHT OF CANADA (2008)
11 : * as represented by the Canadian Nuclear Safety Commission
12 : * Copyright (c) 2014-2015, NextGIS info@nextgis.ru
13 : *
14 : * SPDX-License-Identifier: MIT
15 : ****************************************************************************/
16 :
17 : #include "cpl_port.h"
18 : #include "gdal_mdreader.h"
19 :
20 : #include <cctype>
21 : #include <cstddef>
22 : #include <cstdio>
23 : #include <cstring>
24 : #include <ctime>
25 : #include <string>
26 :
27 : #include "cpl_conv.h"
28 : #include "cpl_error.h"
29 : #include "cpl_minixml.h"
30 : #include "cpl_string.h"
31 : #include "cpl_time.h"
32 : #include "cpl_vsi.h"
33 : #include "cplkeywordparser.h"
34 : #include "gdal_priv.h"
35 :
36 : // readers
37 : #include "mdreader/reader_alos.h"
38 : #include "mdreader/reader_digital_globe.h"
39 : #include "mdreader/reader_eros.h"
40 : #include "mdreader/reader_geo_eye.h"
41 : #include "mdreader/reader_kompsat.h"
42 : #include "mdreader/reader_landsat.h"
43 : #include "mdreader/reader_orb_view.h"
44 : #include "mdreader/reader_pleiades.h"
45 : #include "mdreader/reader_rapid_eye.h"
46 : #include "mdreader/reader_rdk1.h"
47 : #include "mdreader/reader_spot.h"
48 :
49 : /**
50 : * The RPC parameters names
51 : */
52 :
53 : static const char *const apszRPCTXTSingleValItems[] = {
54 : RPC_ERR_BIAS, RPC_ERR_RAND, RPC_LINE_OFF, RPC_SAMP_OFF,
55 : RPC_LAT_OFF, RPC_LONG_OFF, RPC_HEIGHT_OFF, RPC_LINE_SCALE,
56 : RPC_SAMP_SCALE, RPC_LAT_SCALE, RPC_LONG_SCALE, RPC_HEIGHT_SCALE,
57 : nullptr};
58 :
59 : static const char *const apszRPCTXT20ValItems[] = {
60 : RPC_LINE_NUM_COEFF, RPC_LINE_DEN_COEFF, RPC_SAMP_NUM_COEFF,
61 : RPC_SAMP_DEN_COEFF, nullptr};
62 :
63 : /**
64 : * GDALMDReaderManager()
65 : */
66 : GDALMDReaderManager::GDALMDReaderManager() = default;
67 :
68 : /**
69 : * ~GDALMDReaderManager()
70 : */
71 10478 : GDALMDReaderManager::~GDALMDReaderManager()
72 : {
73 5239 : if (nullptr != m_pReader)
74 : {
75 73 : delete m_pReader;
76 : }
77 5239 : }
78 :
79 : /**
80 : * GetReader()
81 : */
82 :
83 : #define INIT_READER(reader) \
84 : GDALMDReaderBase *pReaderBase = new reader(pszPath, papszSiblingFiles); \
85 : if (pReaderBase->HasRequiredFiles()) \
86 : { \
87 : m_pReader = pReaderBase; \
88 : return m_pReader; \
89 : } \
90 : delete pReaderBase
91 :
92 5239 : GDALMDReaderBase *GDALMDReaderManager::GetReader(const char *pszPath,
93 : char **papszSiblingFiles,
94 : GUInt32 nType)
95 : {
96 5239 : if (!GDALCanFileAcceptSidecarFile(pszPath))
97 88 : return nullptr;
98 :
99 5151 : if (nType & MDR_DG)
100 : {
101 5151 : INIT_READER(GDALMDReaderDigitalGlobe);
102 : }
103 :
104 : // required filename.tif filename.pvl filename_rpc.txt
105 5113 : if (nType & MDR_OV)
106 : {
107 5113 : INIT_READER(GDALMDReaderOrbView);
108 : }
109 :
110 5112 : if (nType & MDR_GE)
111 : {
112 5112 : INIT_READER(GDALMDReaderGeoEye);
113 : }
114 :
115 5100 : if (nType & MDR_LS)
116 : {
117 5100 : INIT_READER(GDALMDReaderLandsat);
118 : }
119 :
120 5099 : if (nType & MDR_PLEIADES)
121 : {
122 5099 : INIT_READER(GDALMDReaderPleiades);
123 : }
124 :
125 5085 : if (nType & MDR_SPOT)
126 : {
127 5085 : INIT_READER(GDALMDReaderSpot);
128 : }
129 :
130 5084 : if (nType & MDR_RDK1)
131 : {
132 5084 : INIT_READER(GDALMDReaderResursDK1);
133 : }
134 :
135 5083 : if (nType & MDR_RE)
136 : {
137 5083 : INIT_READER(GDALMDReaderRapidEye);
138 : }
139 :
140 : // required filename.tif filename.rpc filename.txt
141 5082 : if (nType & MDR_KOMPSAT)
142 : {
143 5082 : INIT_READER(GDALMDReaderKompsat);
144 : }
145 :
146 5080 : if (nType & MDR_EROS)
147 : {
148 5080 : INIT_READER(GDALMDReaderEROS);
149 : }
150 :
151 5079 : if (nType & MDR_ALOS)
152 : {
153 5079 : INIT_READER(GDALMDReaderALOS);
154 : }
155 :
156 5078 : return nullptr;
157 : }
158 :
159 : /**
160 : * GDALMDReaderBase()
161 : */
162 56074 : GDALMDReaderBase::GDALMDReaderBase(const char * /* pszPath */,
163 56074 : char ** /* papszSiblingFiles */)
164 : {
165 56074 : }
166 :
167 : /**
168 : * ~GDALMDReaderBase()
169 : */
170 112148 : GDALMDReaderBase::~GDALMDReaderBase()
171 : {
172 56074 : CSLDestroy(m_papszIMDMD);
173 56074 : CSLDestroy(m_papszRPCMD);
174 56074 : CSLDestroy(m_papszIMAGERYMD);
175 56074 : CSLDestroy(m_papszDEFAULTMD);
176 56074 : }
177 :
178 : /**
179 : * GetMetadataItem()
180 : */
181 70 : char **GDALMDReaderBase::GetMetadataDomain(const char *pszDomain)
182 : {
183 70 : LoadMetadata();
184 70 : if (EQUAL(pszDomain, MD_DOMAIN_DEFAULT))
185 0 : return m_papszDEFAULTMD;
186 70 : else if (EQUAL(pszDomain, MD_DOMAIN_IMD))
187 5 : return m_papszIMDMD;
188 65 : else if (EQUAL(pszDomain, MD_DOMAIN_RPC))
189 65 : return m_papszRPCMD;
190 0 : else if (EQUAL(pszDomain, MD_DOMAIN_IMAGERY))
191 0 : return m_papszIMAGERYMD;
192 0 : return nullptr;
193 : }
194 :
195 : /**
196 : * LoadMetadata()
197 : */
198 0 : void GDALMDReaderBase::LoadMetadata()
199 : {
200 0 : if (m_bIsMetadataLoad)
201 0 : return;
202 0 : m_bIsMetadataLoad = true;
203 : }
204 :
205 : /**
206 : * GetAcqisitionTimeFromString()
207 : */
208 50 : GIntBig GDALMDReaderBase::GetAcquisitionTimeFromString(const char *pszDateTime)
209 : {
210 50 : if (nullptr == pszDateTime)
211 0 : return 0;
212 :
213 50 : int iYear = 0;
214 50 : int iMonth = 0;
215 50 : int iDay = 0;
216 50 : int iHours = 0;
217 50 : int iMin = 0;
218 50 : int iSec = 0;
219 :
220 50 : const int r = sscanf(pszDateTime, "%d-%d-%dT%d:%d:%d.%*dZ", &iYear, &iMonth,
221 : &iDay, &iHours, &iMin, &iSec);
222 :
223 50 : if (r != 6)
224 0 : return 0;
225 :
226 : struct tm tmDateTime;
227 50 : tmDateTime.tm_sec = iSec;
228 50 : tmDateTime.tm_min = iMin;
229 50 : tmDateTime.tm_hour = iHours;
230 50 : tmDateTime.tm_mday = iDay;
231 50 : tmDateTime.tm_mon = iMonth - 1;
232 50 : tmDateTime.tm_year = iYear - 1900;
233 50 : tmDateTime.tm_isdst = -1;
234 :
235 50 : return CPLYMDHMSToUnixTime(&tmDateTime);
236 : }
237 :
238 : /**
239 : * FillMetadata()
240 : */
241 :
242 : #define SETMETADATA(mdmd, md, domain) \
243 : if (nullptr != md) \
244 : { \
245 : char **papszCurrentMd = CSLDuplicate(mdmd->GetMetadata(domain)); \
246 : papszCurrentMd = CSLMerge(papszCurrentMd, md); \
247 : mdmd->SetMetadata(papszCurrentMd, domain); \
248 : CSLDestroy(papszCurrentMd); \
249 : }
250 :
251 73 : bool GDALMDReaderBase::FillMetadata(GDALMultiDomainMetadata *poMDMD)
252 : {
253 73 : if (nullptr == poMDMD)
254 0 : return false;
255 :
256 73 : LoadMetadata();
257 :
258 73 : SETMETADATA(poMDMD, m_papszIMDMD, MD_DOMAIN_IMD);
259 73 : SETMETADATA(poMDMD, m_papszRPCMD, MD_DOMAIN_RPC);
260 73 : SETMETADATA(poMDMD, m_papszIMAGERYMD, MD_DOMAIN_IMAGERY);
261 73 : SETMETADATA(poMDMD, m_papszDEFAULTMD, MD_DOMAIN_DEFAULT);
262 :
263 73 : return true;
264 : }
265 :
266 : /**
267 : * AddXMLNameValueToList()
268 : */
269 3797 : char **GDALMDReaderBase::AddXMLNameValueToList(char **papszList,
270 : const char *pszName,
271 : const char *pszValue)
272 : {
273 3797 : return CSLAddNameValue(papszList, pszName, pszValue);
274 : }
275 :
276 : /**
277 : * ReadXMLToListFirstPass()
278 : */
279 4216 : bool GDALMDReaderBase::ReadXMLToListFirstPass(
280 : const CPLXMLNode *psNode, std::map<std::string, int> &oMapCountKeysFull,
281 : const std::string &osPrefixFull, int nDepth)
282 : {
283 4216 : if (nDepth == 10)
284 : {
285 0 : CPLError(CE_Failure, CPLE_AppDefined, "Too much nested XML");
286 0 : return false;
287 : }
288 4216 : if (nullptr == psNode)
289 0 : return true;
290 : while (true)
291 : {
292 4379 : if (psNode->eType == CXT_Element)
293 : {
294 4366 : std::string osNewPrefixFull;
295 4366 : for (const CPLXMLNode *psChildNode = psNode->psChild;
296 12332 : nullptr != psChildNode; psChildNode = psChildNode->psNext)
297 : {
298 7966 : if (psChildNode->eType == CXT_Element)
299 : {
300 4187 : osNewPrefixFull = !osPrefixFull.empty()
301 8374 : ? osPrefixFull
302 4187 : : std::string(psNode->pszValue);
303 4187 : osNewPrefixFull += '.';
304 4187 : osNewPrefixFull += psChildNode->pszValue;
305 4187 : osNewPrefixFull +=
306 4187 : CPLSPrintf("_%d", ++oMapCountKeysFull[osNewPrefixFull]);
307 :
308 4187 : if (!ReadXMLToListFirstPass(psChildNode, oMapCountKeysFull,
309 : osNewPrefixFull, nDepth + 1))
310 0 : return false;
311 : }
312 : }
313 : }
314 :
315 : // proceed next only on top level
316 :
317 4379 : if (nullptr != psNode->psNext && osPrefixFull.empty())
318 : {
319 163 : psNode = psNode->psNext;
320 : }
321 : else
322 : {
323 4216 : break;
324 : }
325 163 : }
326 4216 : return true;
327 : }
328 :
329 : /**
330 : * ReadXMLToList()
331 : */
332 7651 : char **GDALMDReaderBase::ReadXMLToList(
333 : const CPLXMLNode *psNode, char **papszList,
334 : const std::map<std::string, int> &oMapCountKeysFullRef,
335 : std::map<std::string, int> &oMapCountKeysFull,
336 : std::map<std::string, int> &oMapCountKeys, const std::string &osPrefix,
337 : const std::string &osPrefixFull)
338 : {
339 7651 : if (nullptr == psNode)
340 0 : return papszList;
341 :
342 : while (true)
343 : {
344 7814 : if (psNode->eType == CXT_Text)
345 : {
346 3435 : papszList = AddXMLNameValueToList(papszList, osPrefix.c_str(),
347 3435 : psNode->pszValue);
348 : }
349 :
350 7814 : if (psNode->eType == CXT_Element)
351 : {
352 8732 : std::string osNewPrefix;
353 8732 : std::string osNewPrefixFull;
354 4366 : for (const CPLXMLNode *psChildNode = psNode->psChild;
355 12332 : nullptr != psChildNode; psChildNode = psChildNode->psNext)
356 : {
357 7966 : if (psChildNode->eType == CXT_Element)
358 : {
359 4187 : osNewPrefixFull = !osPrefixFull.empty()
360 8374 : ? osPrefixFull
361 4187 : : std::string(psNode->pszValue);
362 4187 : osNewPrefixFull += '.';
363 4187 : osNewPrefixFull += psChildNode->pszValue;
364 :
365 : const auto oIter =
366 4187 : oMapCountKeysFullRef.find(osNewPrefixFull);
367 4187 : CPLAssert(oIter != oMapCountKeysFullRef.end());
368 4187 : osNewPrefixFull +=
369 4187 : CPLSPrintf("_%d", ++oMapCountKeysFull[osNewPrefixFull]);
370 :
371 4187 : osNewPrefix = !osPrefix.empty()
372 8374 : ? osPrefix
373 4187 : : std::string(psNode->pszValue);
374 4187 : osNewPrefix += '.';
375 4187 : osNewPrefix += psChildNode->pszValue;
376 4187 : const int nIndex = ++oMapCountKeys[osNewPrefix];
377 4187 : const bool bMultipleInstances = oIter->second >= 2;
378 4187 : if (bMultipleInstances)
379 : {
380 221 : osNewPrefix += CPLSPrintf("_%d", nIndex);
381 : }
382 4187 : papszList = ReadXMLToList(psChildNode, papszList,
383 : oMapCountKeysFullRef,
384 : oMapCountKeysFull, oMapCountKeys,
385 : osNewPrefix, osNewPrefixFull);
386 : }
387 3779 : else if (psChildNode->eType == CXT_Attribute)
388 : {
389 344 : papszList = AddXMLNameValueToList(
390 : papszList,
391 : CPLSPrintf("%s.%s", osPrefix.c_str(),
392 344 : psChildNode->pszValue),
393 344 : psChildNode->psChild->pszValue);
394 : }
395 : else
396 : {
397 : // Text nodes should always have name
398 3435 : if (osPrefix.empty())
399 : {
400 69 : papszList = ReadXMLToList(
401 : psChildNode, papszList, oMapCountKeysFullRef,
402 23 : oMapCountKeysFull, oMapCountKeys, psNode->pszValue,
403 23 : psNode->pszValue);
404 : }
405 : else
406 : {
407 3412 : papszList = ReadXMLToList(
408 : psChildNode, papszList, oMapCountKeysFullRef,
409 : oMapCountKeysFull, oMapCountKeys, osPrefix.c_str(),
410 : osNewPrefixFull.c_str());
411 : }
412 : }
413 : }
414 : }
415 :
416 : // proceed next only on top level
417 :
418 7814 : if (nullptr != psNode->psNext && osPrefix.empty())
419 : {
420 163 : psNode = psNode->psNext;
421 : }
422 : else
423 : {
424 7651 : break;
425 : }
426 163 : }
427 :
428 7651 : return papszList;
429 : }
430 :
431 : /**
432 : * ReadXMLToList()
433 : */
434 29 : char **GDALMDReaderBase::ReadXMLToList(CPLXMLNode *psNode, char **papszList,
435 : const char *pszName)
436 : {
437 58 : std::map<std::string, int> oMapCountKeysFullRef;
438 29 : if (!ReadXMLToListFirstPass(psNode, oMapCountKeysFullRef, pszName, 0))
439 0 : return papszList;
440 58 : std::map<std::string, int> oMapCountKeysFull;
441 29 : std::map<std::string, int> oMapCountKeys;
442 58 : return ReadXMLToList(psNode, papszList, oMapCountKeysFullRef,
443 29 : oMapCountKeysFull, oMapCountKeys, pszName, pszName);
444 : }
445 :
446 : //------------------------------------------------------------------------------
447 : // Miscellaneous functions
448 : //------------------------------------------------------------------------------
449 :
450 : /**
451 : * GDALCheckFileHeader()
452 : */
453 17 : bool GDALCheckFileHeader(const CPLString &soFilePath, const char *pszTestString,
454 : int nBufferSize)
455 : {
456 17 : VSILFILE *fpL = VSIFOpenL(soFilePath, "r");
457 17 : if (fpL == nullptr)
458 0 : return false;
459 17 : char *pBuffer = new char[nBufferSize + 1];
460 : const int nReadBytes =
461 17 : static_cast<int>(VSIFReadL(pBuffer, 1, nBufferSize, fpL));
462 17 : CPL_IGNORE_RET_VAL(VSIFCloseL(fpL));
463 17 : if (nReadBytes == 0)
464 : {
465 0 : delete[] pBuffer;
466 0 : return false;
467 : }
468 17 : pBuffer[nReadBytes] = '\0';
469 :
470 17 : const bool bResult = strstr(pBuffer, pszTestString) != nullptr;
471 17 : delete[] pBuffer;
472 :
473 17 : return bResult;
474 : }
475 :
476 : /**
477 : * CPLStrip()
478 : */
479 164 : CPLString CPLStrip(const CPLString &sString, const char cChar)
480 : {
481 164 : if (sString.empty())
482 0 : return sString;
483 :
484 164 : size_t dCopyFrom = 0;
485 164 : size_t dCopyCount = sString.size();
486 :
487 164 : if (sString[0] == cChar)
488 : {
489 36 : dCopyFrom++;
490 36 : dCopyCount--;
491 : }
492 :
493 164 : if (sString.back() == cChar)
494 36 : dCopyCount--;
495 :
496 164 : if (dCopyCount == 0)
497 0 : return CPLString();
498 :
499 328 : return sString.substr(dCopyFrom, dCopyCount);
500 : }
501 :
502 : /**
503 : * CPLStripQuotes()
504 : */
505 82 : CPLString CPLStripQuotes(const CPLString &sString)
506 : {
507 164 : return CPLStrip(CPLStrip(sString, '"'), '\'');
508 : }
509 :
510 : /************************************************************************/
511 : /* GDALLoadRPBFile() */
512 : /************************************************************************/
513 :
514 : static const char *const apszRPBMap[] = {apszRPCTXTSingleValItems[0],
515 : "IMAGE.errBias",
516 : apszRPCTXTSingleValItems[1],
517 : "IMAGE.errRand",
518 : apszRPCTXTSingleValItems[2],
519 : "IMAGE.lineOffset",
520 : apszRPCTXTSingleValItems[3],
521 : "IMAGE.sampOffset",
522 : apszRPCTXTSingleValItems[4],
523 : "IMAGE.latOffset",
524 : apszRPCTXTSingleValItems[5],
525 : "IMAGE.longOffset",
526 : apszRPCTXTSingleValItems[6],
527 : "IMAGE.heightOffset",
528 : apszRPCTXTSingleValItems[7],
529 : "IMAGE.lineScale",
530 : apszRPCTXTSingleValItems[8],
531 : "IMAGE.sampScale",
532 : apszRPCTXTSingleValItems[9],
533 : "IMAGE.latScale",
534 : apszRPCTXTSingleValItems[10],
535 : "IMAGE.longScale",
536 : apszRPCTXTSingleValItems[11],
537 : "IMAGE.heightScale",
538 : apszRPCTXT20ValItems[0],
539 : "IMAGE.lineNumCoef",
540 : apszRPCTXT20ValItems[1],
541 : "IMAGE.lineDenCoef",
542 : apszRPCTXT20ValItems[2],
543 : "IMAGE.sampNumCoef",
544 : apszRPCTXT20ValItems[3],
545 : "IMAGE.sampDenCoef",
546 : nullptr,
547 : nullptr};
548 :
549 10 : char **GDALLoadRPBFile(const CPLString &soFilePath)
550 : {
551 10 : if (soFilePath.empty())
552 0 : return nullptr;
553 :
554 : /* -------------------------------------------------------------------- */
555 : /* Read file and parse. */
556 : /* -------------------------------------------------------------------- */
557 10 : VSILFILE *fp = VSIFOpenL(soFilePath, "r");
558 :
559 10 : if (fp == nullptr)
560 0 : return nullptr;
561 :
562 20 : CPLKeywordParser oParser;
563 10 : if (!oParser.Ingest(fp))
564 : {
565 0 : CPL_IGNORE_RET_VAL(VSIFCloseL(fp));
566 0 : return nullptr;
567 : }
568 :
569 10 : CPL_IGNORE_RET_VAL(VSIFCloseL(fp));
570 :
571 : /* -------------------------------------------------------------------- */
572 : /* Extract RPC information, in a GDAL "standard" metadata format. */
573 : /* -------------------------------------------------------------------- */
574 10 : char **papszMD = nullptr;
575 170 : for (int i = 0; apszRPBMap[i] != nullptr; i += 2)
576 : {
577 160 : const char *pszRPBVal = oParser.GetKeyword(apszRPBMap[i + 1]);
578 160 : CPLString osAdjVal;
579 :
580 160 : if (pszRPBVal == nullptr)
581 : {
582 0 : if (strcmp(apszRPBMap[i], RPC_ERR_RAND) == 0 ||
583 0 : strcmp(apszRPBMap[i], RPC_ERR_BIAS) == 0)
584 : {
585 0 : continue;
586 : }
587 0 : CPLError(
588 : CE_Failure, CPLE_AppDefined,
589 : "%s file found, but missing %s field (and possibly others).",
590 0 : soFilePath.c_str(), apszRPBMap[i + 1]);
591 0 : CSLDestroy(papszMD);
592 0 : return nullptr;
593 : }
594 :
595 160 : if (strchr(pszRPBVal, ',') == nullptr)
596 120 : osAdjVal = pszRPBVal;
597 : else
598 : {
599 : // strip out commas and turn newlines into spaces.
600 11280 : for (int j = 0; pszRPBVal[j] != '\0'; j++)
601 : {
602 11240 : switch (pszRPBVal[j])
603 : {
604 760 : case ',':
605 : case '\n':
606 : case '\r':
607 760 : osAdjVal += ' ';
608 760 : break;
609 :
610 80 : case '(':
611 : case ')':
612 80 : break;
613 :
614 10400 : default:
615 10400 : osAdjVal += pszRPBVal[j];
616 : }
617 : }
618 : }
619 :
620 160 : papszMD = CSLSetNameValue(papszMD, apszRPBMap[i], osAdjVal);
621 : }
622 :
623 10 : return papszMD;
624 : }
625 :
626 : /************************************************************************/
627 : /* GDALLoadRPCFile() */
628 : /************************************************************************/
629 :
630 : /* Load a GeoEye _rpc.txt file. See ticket
631 : * http://trac.osgeo.org/gdal/ticket/3639 */
632 :
633 20 : char **GDALLoadRPCFile(const CPLString &soFilePath)
634 : {
635 20 : if (soFilePath.empty())
636 0 : return nullptr;
637 :
638 : /* -------------------------------------------------------------------- */
639 : /* Read file and parse. */
640 : /* -------------------------------------------------------------------- */
641 : // 100 lines would be enough, but some .rpc files have CR CR LF end of
642 : // lines, which result in a blank line to be recognized, so accept up
643 : // to 200 lines (#6341)
644 20 : char **papszLines = CSLLoad2(soFilePath, 200, 100, nullptr);
645 20 : if (!papszLines)
646 0 : return nullptr;
647 :
648 20 : char **papszMD = nullptr;
649 :
650 : /* From LINE_OFF to HEIGHT_SCALE */
651 260 : for (size_t i = 0; i < 23; i += 2)
652 : {
653 240 : const char *pszRPBVal = CSLFetchNameValue(papszLines, apszRPBMap[i]);
654 :
655 240 : if (pszRPBVal == nullptr)
656 : {
657 8 : if (strcmp(apszRPBMap[i], RPC_ERR_RAND) == 0 ||
658 4 : strcmp(apszRPBMap[i], RPC_ERR_BIAS) == 0)
659 : {
660 8 : continue;
661 : }
662 0 : CPLError(
663 : CE_Failure, CPLE_AppDefined,
664 : "%s file found, but missing %s field (and possibly others).",
665 0 : soFilePath.c_str(), apszRPBMap[i]);
666 0 : CSLDestroy(papszMD);
667 0 : CSLDestroy(papszLines);
668 0 : return nullptr;
669 : }
670 : else
671 : {
672 500 : while (*pszRPBVal == ' ' || *pszRPBVal == '\t')
673 268 : pszRPBVal++;
674 232 : papszMD = CSLSetNameValue(papszMD, apszRPBMap[i], pszRPBVal);
675 : }
676 : }
677 :
678 : /* For LINE_NUM_COEFF, LINE_DEN_COEFF, SAMP_NUM_COEFF, SAMP_DEN_COEFF */
679 : /* parameters that have 20 values each */
680 100 : for (size_t i = 24; apszRPBMap[i] != nullptr; i += 2)
681 : {
682 80 : CPLString soVal;
683 1680 : for (int j = 1; j <= 20; j++)
684 : {
685 1600 : CPLString soRPBMapItem;
686 1600 : soRPBMapItem.Printf("%s_%d", apszRPBMap[i], j);
687 : const char *pszRPBVal =
688 1600 : CSLFetchNameValue(papszLines, soRPBMapItem.c_str());
689 1600 : if (pszRPBVal == nullptr)
690 : {
691 0 : CPLError(CE_Failure, CPLE_AppDefined,
692 : "%s file found, but missing %s field (and possibly "
693 : "others).",
694 : soFilePath.c_str(), soRPBMapItem.c_str());
695 0 : CSLDestroy(papszMD);
696 0 : CSLDestroy(papszLines);
697 0 : return nullptr;
698 : }
699 : else
700 : {
701 3200 : while (*pszRPBVal == ' ' || *pszRPBVal == '\t')
702 1600 : pszRPBVal++;
703 1600 : soVal += pszRPBVal;
704 1600 : soVal += " ";
705 : }
706 : }
707 80 : papszMD = CSLSetNameValue(papszMD, apszRPBMap[i], soVal.c_str());
708 : }
709 :
710 20 : CSLDestroy(papszLines);
711 20 : return papszMD;
712 : }
713 :
714 : /************************************************************************/
715 : /* GDALWriteRPCTXTFile() */
716 : /************************************************************************/
717 :
718 7 : CPLErr GDALWriteRPCTXTFile(const char *pszFilename, char **papszMD)
719 :
720 : {
721 14 : CPLString osRPCFilename = pszFilename;
722 14 : CPLString soPt(".");
723 7 : size_t found = osRPCFilename.rfind(soPt);
724 7 : if (found == CPLString::npos)
725 0 : return CE_Failure;
726 7 : osRPCFilename.replace(found, osRPCFilename.size() - found, "_RPC.TXT");
727 7 : if (papszMD == nullptr)
728 : {
729 5 : VSIUnlink(osRPCFilename);
730 5 : return CE_None;
731 : }
732 :
733 : /* -------------------------------------------------------------------- */
734 : /* Read file and parse. */
735 : /* -------------------------------------------------------------------- */
736 2 : VSILFILE *fp = VSIFOpenL(osRPCFilename, "w");
737 :
738 2 : if (fp == nullptr)
739 : {
740 0 : CPLError(CE_Failure, CPLE_OpenFailed,
741 : "Unable to create %s for writing.\n%s", osRPCFilename.c_str(),
742 : CPLGetLastErrorMsg());
743 0 : return CE_Failure;
744 : }
745 :
746 : /* -------------------------------------------------------------------- */
747 : /* Write RPC values from our RPC metadata. */
748 : /* -------------------------------------------------------------------- */
749 2 : bool bOK = true;
750 26 : for (int i = 0; apszRPCTXTSingleValItems[i] != nullptr; i++)
751 : {
752 : const char *pszRPCVal =
753 24 : CSLFetchNameValue(papszMD, apszRPCTXTSingleValItems[i]);
754 24 : if (pszRPCVal == nullptr)
755 : {
756 2 : if (strcmp(apszRPCTXTSingleValItems[i], RPC_ERR_BIAS) == 0 ||
757 1 : strcmp(apszRPCTXTSingleValItems[i], RPC_ERR_RAND) == 0)
758 : {
759 2 : continue;
760 : }
761 0 : CPLError(CE_Failure, CPLE_AppDefined,
762 : "%s field missing in metadata, %s file not written.",
763 0 : apszRPCTXTSingleValItems[i], osRPCFilename.c_str());
764 0 : CPL_IGNORE_RET_VAL(VSIFCloseL(fp));
765 0 : VSIUnlink(osRPCFilename);
766 0 : return CE_Failure;
767 : }
768 :
769 22 : bOK &= VSIFPrintfL(fp, "%s: %s\n", apszRPCTXTSingleValItems[i],
770 22 : pszRPCVal) > 0;
771 : }
772 :
773 10 : for (int i = 0; apszRPCTXT20ValItems[i] != nullptr; i++)
774 : {
775 : const char *pszRPCVal =
776 8 : CSLFetchNameValue(papszMD, apszRPCTXT20ValItems[i]);
777 8 : if (pszRPCVal == nullptr)
778 : {
779 0 : CPLError(CE_Failure, CPLE_AppDefined,
780 : "%s field missing in metadata, %s file not written.",
781 0 : apszRPCTXTSingleValItems[i], osRPCFilename.c_str());
782 0 : CPL_IGNORE_RET_VAL(VSIFCloseL(fp));
783 0 : VSIUnlink(osRPCFilename);
784 0 : return CE_Failure;
785 : }
786 :
787 : char **papszItems =
788 8 : CSLTokenizeStringComplex(pszRPCVal, " ,", FALSE, FALSE);
789 :
790 8 : if (CSLCount(papszItems) != 20)
791 : {
792 0 : CPLError(CE_Failure, CPLE_AppDefined,
793 : "%s field is corrupt (not 20 values), %s file not "
794 : "written.\n%s = %s",
795 0 : apszRPCTXT20ValItems[i], osRPCFilename.c_str(),
796 0 : apszRPCTXT20ValItems[i], pszRPCVal);
797 0 : CPL_IGNORE_RET_VAL(VSIFCloseL(fp));
798 0 : VSIUnlink(osRPCFilename);
799 0 : CSLDestroy(papszItems);
800 0 : return CE_Failure;
801 : }
802 :
803 168 : for (int j = 0; j < 20; j++)
804 : {
805 320 : bOK &= VSIFPrintfL(fp, "%s_%d: %s\n", apszRPCTXT20ValItems[i],
806 160 : j + 1, papszItems[j]) > 0;
807 : }
808 8 : CSLDestroy(papszItems);
809 : }
810 :
811 2 : if (VSIFCloseL(fp) != 0)
812 0 : bOK = false;
813 :
814 2 : return bOK ? CE_None : CE_Failure;
815 : }
816 :
817 : /************************************************************************/
818 : /* GDALWriteRPBFile() */
819 : /************************************************************************/
820 :
821 9 : CPLErr GDALWriteRPBFile(const char *pszFilename, char **papszMD)
822 :
823 : {
824 18 : CPLString osRPBFilename = CPLResetExtension(pszFilename, "RPB");
825 9 : if (papszMD == nullptr)
826 : {
827 5 : VSIUnlink(osRPBFilename);
828 5 : return CE_None;
829 : }
830 :
831 : /* -------------------------------------------------------------------- */
832 : /* Read file and parse. */
833 : /* -------------------------------------------------------------------- */
834 4 : VSILFILE *fp = VSIFOpenL(osRPBFilename, "w");
835 :
836 4 : if (fp == nullptr)
837 : {
838 0 : CPLError(CE_Failure, CPLE_OpenFailed,
839 : "Unable to create %s for writing.\n%s", osRPBFilename.c_str(),
840 : CPLGetLastErrorMsg());
841 0 : return CE_Failure;
842 : }
843 :
844 : /* -------------------------------------------------------------------- */
845 : /* Write the prefix information. */
846 : /* -------------------------------------------------------------------- */
847 4 : bool bOK = VSIFPrintfL(fp, "%s", "satId = \"QB02\";\n") > 0;
848 4 : bOK &= VSIFPrintfL(fp, "%s", "bandId = \"P\";\n") > 0;
849 4 : bOK &= VSIFPrintfL(fp, "%s", "SpecId = \"RPC00B\";\n") > 0;
850 4 : bOK &= VSIFPrintfL(fp, "%s", "BEGIN_GROUP = IMAGE\n") > 0;
851 :
852 : /* -------------------------------------------------------------------- */
853 : /* Write RPC values from our RPC metadata. */
854 : /* -------------------------------------------------------------------- */
855 68 : for (int i = 0; apszRPBMap[i] != nullptr; i += 2)
856 : {
857 64 : const char *pszRPBVal = CSLFetchNameValue(papszMD, apszRPBMap[i]);
858 : const char *pszRPBTag;
859 :
860 64 : if (pszRPBVal == nullptr)
861 : {
862 8 : if (strcmp(apszRPBMap[i], RPC_ERR_BIAS) == 0)
863 : {
864 4 : bOK &= VSIFPrintfL(fp, "%s", "\terrBias = 0.0;\n") > 0;
865 4 : continue;
866 : }
867 4 : else if (strcmp(apszRPBMap[i], RPC_ERR_RAND) == 0)
868 : {
869 4 : bOK &= VSIFPrintfL(fp, "%s", "\terrRand = 0.0;\n") > 0;
870 4 : continue;
871 : }
872 0 : CPLError(CE_Failure, CPLE_AppDefined,
873 : "%s field missing in metadata, %s file not written.",
874 0 : apszRPBMap[i], osRPBFilename.c_str());
875 0 : CPL_IGNORE_RET_VAL(VSIFCloseL(fp));
876 0 : VSIUnlink(osRPBFilename);
877 0 : return CE_Failure;
878 : }
879 :
880 56 : pszRPBTag = apszRPBMap[i + 1];
881 56 : if (STARTS_WITH_CI(pszRPBTag, "IMAGE."))
882 56 : pszRPBTag += 6;
883 :
884 56 : if (strstr(apszRPBMap[i], "COEF") == nullptr)
885 : {
886 40 : bOK &= VSIFPrintfL(fp, "\t%s = %s;\n", pszRPBTag, pszRPBVal) > 0;
887 : }
888 : else
889 : {
890 : // Reformat in brackets with commas over multiple lines.
891 :
892 16 : bOK &= VSIFPrintfL(fp, "\t%s = (\n", pszRPBTag) > 0;
893 :
894 : char **papszItems =
895 16 : CSLTokenizeStringComplex(pszRPBVal, " ,", FALSE, FALSE);
896 :
897 16 : if (CSLCount(papszItems) != 20)
898 : {
899 0 : CPLError(CE_Failure, CPLE_AppDefined,
900 : "%s field is corrupt (not 20 values), %s file not "
901 : "written.\n%s = %s",
902 0 : apszRPBMap[i], osRPBFilename.c_str(), apszRPBMap[i],
903 : pszRPBVal);
904 0 : CPL_IGNORE_RET_VAL(VSIFCloseL(fp));
905 0 : VSIUnlink(osRPBFilename);
906 0 : CSLDestroy(papszItems);
907 0 : return CE_Failure;
908 : }
909 :
910 336 : for (int j = 0; j < 20; j++)
911 : {
912 320 : if (j < 19)
913 304 : bOK &= VSIFPrintfL(fp, "\t\t\t%s,\n", papszItems[j]) > 0;
914 : else
915 16 : bOK &= VSIFPrintfL(fp, "\t\t\t%s);\n", papszItems[j]) > 0;
916 : }
917 16 : CSLDestroy(papszItems);
918 : }
919 : }
920 :
921 : /* -------------------------------------------------------------------- */
922 : /* Write end part */
923 : /* -------------------------------------------------------------------- */
924 4 : bOK &= VSIFPrintfL(fp, "%s", "END_GROUP = IMAGE\n") > 0;
925 4 : bOK &= VSIFPrintfL(fp, "END;\n") > 0;
926 4 : if (VSIFCloseL(fp) != 0)
927 0 : bOK = false;
928 :
929 4 : return bOK ? CE_None : CE_Failure;
930 : }
931 :
932 : /************************************************************************/
933 : /* GDAL_IMD_AA2R() */
934 : /* */
935 : /* Translate AA version IMD file to R version. */
936 : /************************************************************************/
937 :
938 0 : static bool GDAL_IMD_AA2R(char ***ppapszIMD)
939 :
940 : {
941 0 : char **papszIMD = *ppapszIMD;
942 :
943 : /* -------------------------------------------------------------------- */
944 : /* Verify that we have a new format file. */
945 : /* -------------------------------------------------------------------- */
946 0 : const char *pszValue = CSLFetchNameValue(papszIMD, "version");
947 :
948 0 : if (pszValue == nullptr)
949 0 : return false;
950 :
951 0 : if (EQUAL(pszValue, "\"R\""))
952 0 : return true;
953 :
954 0 : if (!EQUAL(pszValue, "\"AA\""))
955 : {
956 0 : CPLDebug("IMD",
957 : "The file is not the expected 'version = \"AA\"' format.\n"
958 : "Proceeding, but file may be corrupted.");
959 : }
960 :
961 : /* -------------------------------------------------------------------- */
962 : /* Fix the version line. */
963 : /* -------------------------------------------------------------------- */
964 0 : papszIMD = CSLSetNameValue(papszIMD, "version", "\"R\"");
965 :
966 : /* -------------------------------------------------------------------- */
967 : /* remove a bunch of fields. */
968 : /* -------------------------------------------------------------------- */
969 : static const char *const apszToRemove[] = {
970 : "productCatalogId", "childCatalogId",
971 : "productType", "numberOfLooks",
972 : "effectiveBandwidth", "mode",
973 : "scanDirection", "cloudCover",
974 : "productGSD", nullptr};
975 :
976 0 : for (int iKey = 0; apszToRemove[iKey] != nullptr; iKey++)
977 : {
978 0 : int iTarget = CSLFindName(papszIMD, apszToRemove[iKey]);
979 0 : if (iTarget != -1)
980 0 : papszIMD = CSLRemoveStrings(papszIMD, iTarget, 1, nullptr);
981 : }
982 :
983 : /* -------------------------------------------------------------------- */
984 : /* Replace various min/mean/max with just the mean. */
985 : /* -------------------------------------------------------------------- */
986 : static const char *const keylist[] = {"CollectedRowGSD",
987 : "CollectedColGSD",
988 : "SunAz",
989 : "SunEl",
990 : "SatAz",
991 : "SatEl",
992 : "InTrackViewAngle",
993 : "CrossTrackViewAngle",
994 : "OffNadirViewAngle",
995 : nullptr};
996 :
997 0 : for (int iKey = 0; keylist[iKey] != nullptr; iKey++)
998 : {
999 0 : CPLString osTarget;
1000 0 : osTarget.Printf("IMAGE_1.min%s", keylist[iKey]);
1001 0 : int iTarget = CSLFindName(papszIMD, osTarget);
1002 0 : if (iTarget != -1)
1003 0 : papszIMD = CSLRemoveStrings(papszIMD, iTarget, 1, nullptr);
1004 :
1005 0 : osTarget.Printf("IMAGE_1.max%s", keylist[iKey]);
1006 0 : iTarget = CSLFindName(papszIMD, osTarget);
1007 0 : if (iTarget != -1)
1008 0 : papszIMD = CSLRemoveStrings(papszIMD, iTarget, 1, nullptr);
1009 :
1010 0 : osTarget.Printf("IMAGE_1.mean%s", keylist[iKey]);
1011 0 : iTarget = CSLFindName(papszIMD, osTarget);
1012 0 : if (iTarget != -1)
1013 : {
1014 0 : CPLString osValue = CSLFetchNameValue(papszIMD, osTarget);
1015 0 : CPLString osLine;
1016 : osTarget.Printf(
1017 : "IMAGE_1.%c%s",
1018 0 : CPLTolower(static_cast<unsigned char>(keylist[iKey][0])),
1019 0 : keylist[iKey] + 1);
1020 :
1021 0 : osLine = osTarget + "=" + osValue;
1022 :
1023 0 : CPLFree(papszIMD[iTarget]);
1024 0 : papszIMD[iTarget] = CPLStrdup(osLine);
1025 : }
1026 : }
1027 :
1028 0 : *ppapszIMD = papszIMD;
1029 0 : return true;
1030 : }
1031 :
1032 : /************************************************************************/
1033 : /* GDALLoadIMDFile() */
1034 : /************************************************************************/
1035 :
1036 38 : char **GDALLoadIMDFile(const CPLString &osFilePath)
1037 : {
1038 38 : if (osFilePath.empty())
1039 0 : return nullptr;
1040 :
1041 : /* -------------------------------------------------------------------- */
1042 : /* Read file and parse. */
1043 : /* -------------------------------------------------------------------- */
1044 76 : CPLKeywordParser oParser;
1045 :
1046 38 : VSILFILE *fp = VSIFOpenL(osFilePath, "r");
1047 :
1048 38 : if (fp == nullptr)
1049 0 : return nullptr;
1050 :
1051 38 : if (!oParser.Ingest(fp))
1052 : {
1053 4 : CPL_IGNORE_RET_VAL(VSIFCloseL(fp));
1054 4 : return nullptr;
1055 : }
1056 :
1057 34 : CPL_IGNORE_RET_VAL(VSIFCloseL(fp));
1058 :
1059 : /* -------------------------------------------------------------------- */
1060 : /* Consider version changing. */
1061 : /* -------------------------------------------------------------------- */
1062 34 : char **papszIMD = CSLDuplicate(oParser.GetAllKeywords());
1063 34 : const char *pszVersion = CSLFetchNameValue(papszIMD, "version");
1064 :
1065 34 : if (pszVersion == nullptr)
1066 : {
1067 : /* ? */;
1068 : }
1069 26 : else if (EQUAL(pszVersion, "\"AA\""))
1070 : {
1071 0 : GDAL_IMD_AA2R(&papszIMD);
1072 : }
1073 :
1074 34 : return papszIMD;
1075 : }
1076 :
1077 : /************************************************************************/
1078 : /* GDALWriteIMDMultiLine() */
1079 : /* */
1080 : /* Write a value that is split over multiple lines. */
1081 : /************************************************************************/
1082 :
1083 26 : static void GDALWriteIMDMultiLine(VSILFILE *fp, const char *pszValue)
1084 :
1085 : {
1086 : char **papszItems =
1087 26 : CSLTokenizeStringComplex(pszValue, "(,) ", FALSE, FALSE);
1088 26 : const int nItemCount = CSLCount(papszItems);
1089 :
1090 26 : CPL_IGNORE_RET_VAL(VSIFPrintfL(fp, "(\n"));
1091 :
1092 260 : for (int i = 0; i < nItemCount; i++)
1093 : {
1094 234 : if (i == nItemCount - 1)
1095 26 : CPL_IGNORE_RET_VAL(VSIFPrintfL(fp, "\t%s );\n", papszItems[i]));
1096 : else
1097 208 : CPL_IGNORE_RET_VAL(VSIFPrintfL(fp, "\t%s,\n", papszItems[i]));
1098 : }
1099 26 : CSLDestroy(papszItems);
1100 26 : }
1101 :
1102 : /************************************************************************/
1103 : /* GDALWriteIMDFile() */
1104 : /************************************************************************/
1105 :
1106 20 : CPLErr GDALWriteIMDFile(const char *pszFilename, char **papszMD)
1107 :
1108 : {
1109 40 : CPLString osRPBFilename = CPLResetExtension(pszFilename, "IMD");
1110 :
1111 : /* -------------------------------------------------------------------- */
1112 : /* Read file and parse. */
1113 : /* -------------------------------------------------------------------- */
1114 20 : VSILFILE *fp = VSIFOpenL(osRPBFilename, "w");
1115 :
1116 20 : if (fp == nullptr)
1117 : {
1118 0 : CPLError(CE_Failure, CPLE_OpenFailed,
1119 : "Unable to create %s for writing.\n%s", osRPBFilename.c_str(),
1120 : CPLGetLastErrorMsg());
1121 0 : return CE_Failure;
1122 : }
1123 :
1124 : /* ==================================================================== */
1125 : /* -------------------------------------------------------------------- */
1126 : /* Loop through all values writing. */
1127 : /* -------------------------------------------------------------------- */
1128 : /* ==================================================================== */
1129 20 : CPLString osCurSection;
1130 20 : bool bOK = true;
1131 :
1132 1817 : for (int iKey = 0; papszMD[iKey] != nullptr; iKey++)
1133 : {
1134 1797 : char *pszRawKey = nullptr;
1135 1797 : const char *pszValue = CPLParseNameValue(papszMD[iKey], &pszRawKey);
1136 1797 : if (pszRawKey == nullptr)
1137 0 : continue;
1138 3594 : CPLString osKeySection;
1139 3594 : CPLString osKeyItem;
1140 1797 : char *pszDot = strchr(pszRawKey, '.');
1141 :
1142 : /* --------------------------------------------------------------------
1143 : */
1144 : /* Split stuff like BAND_P.ULLon into section and item. */
1145 : /* --------------------------------------------------------------------
1146 : */
1147 1797 : if (pszDot == nullptr)
1148 : {
1149 186 : osKeyItem = pszRawKey;
1150 : }
1151 : else
1152 : {
1153 1611 : osKeyItem = pszDot + 1;
1154 1611 : *pszDot = '\0';
1155 1611 : osKeySection = pszRawKey;
1156 : }
1157 1797 : CPLFree(pszRawKey);
1158 :
1159 : /* --------------------------------------------------------------------
1160 : */
1161 : /* Close and/or start sections as needed. */
1162 : /* --------------------------------------------------------------------
1163 : */
1164 1797 : if (!osCurSection.empty() && !EQUAL(osCurSection, osKeySection))
1165 73 : bOK &=
1166 73 : VSIFPrintfL(fp, "END_GROUP = %s\n", osCurSection.c_str()) > 0;
1167 :
1168 1797 : if (!osKeySection.empty() && !EQUAL(osCurSection, osKeySection))
1169 76 : bOK &=
1170 76 : VSIFPrintfL(fp, "BEGIN_GROUP = %s\n", osKeySection.c_str()) > 0;
1171 :
1172 1797 : osCurSection = std::move(osKeySection);
1173 :
1174 : /* --------------------------------------------------------------------
1175 : */
1176 : /* Print out simple item. */
1177 : /* --------------------------------------------------------------------
1178 : */
1179 1797 : if (!osCurSection.empty())
1180 1611 : bOK &= VSIFPrintfL(fp, "\t%s = ", osKeyItem.c_str()) > 0;
1181 : else
1182 186 : bOK &= VSIFPrintfL(fp, "%s = ", osKeyItem.c_str()) > 0;
1183 :
1184 1797 : if (pszValue[0] != '(')
1185 : {
1186 1771 : const bool bHasSingleQuote = strchr(pszValue, '\'') != nullptr;
1187 1771 : const bool bHasDoubleQuote = strchr(pszValue, '"') != nullptr;
1188 1771 : if (strchr(pszValue, ' ') != nullptr ||
1189 1584 : strchr(pszValue, ';') != nullptr ||
1190 1583 : strchr(pszValue, '\t') != nullptr || bHasSingleQuote ||
1191 273 : (bHasDoubleQuote && !(pszValue[0] == '"' &&
1192 273 : pszValue[strlen(pszValue) - 1] == '"')))
1193 : {
1194 188 : if (!bHasDoubleQuote)
1195 173 : bOK &= VSIFPrintfL(fp, "\"%s\";\n", pszValue) > 0;
1196 15 : else if (!bHasSingleQuote)
1197 8 : bOK &= VSIFPrintfL(fp, "'%s';\n", pszValue) > 0;
1198 : else
1199 : {
1200 : // There does not seem to have a way to escape double-quotes
1201 : // in a double-quoted string (or single-quote in a
1202 : // single-quoted string), so we are going to convert
1203 : // double-quotes as two single-quotes...
1204 7 : bOK &=
1205 7 : VSIFPrintfL(
1206 : fp, "\"%s\";\n",
1207 14 : CPLString(pszValue).replaceAll('"', "''").c_str()) >
1208 7 : 0;
1209 : }
1210 : }
1211 : else
1212 : {
1213 1583 : bOK &= VSIFPrintfL(fp, "%s;\n", pszValue) > 0;
1214 : }
1215 : }
1216 : else
1217 26 : GDALWriteIMDMultiLine(fp, pszValue);
1218 : }
1219 :
1220 : /* -------------------------------------------------------------------- */
1221 : /* Close off. */
1222 : /* -------------------------------------------------------------------- */
1223 20 : if (!osCurSection.empty())
1224 3 : bOK &= VSIFPrintfL(fp, "END_GROUP = %s\n", osCurSection.c_str()) > 0;
1225 :
1226 20 : bOK &= VSIFPrintfL(fp, "END;\n") > 0;
1227 :
1228 20 : if (VSIFCloseL(fp) != 0)
1229 0 : bOK = false;
1230 :
1231 20 : return bOK ? CE_None : CE_Failure;
1232 : }
|