Line data Source code
1 : /******************************************************************************
2 : *
3 : * Project: MiraMonRaster driver
4 : * Purpose: Implements MMRRel: provides access to the REL file, which
5 : * holds all the necessary metadata to correctly interpret and
6 : * access the associated raw data.
7 : * Author: Abel Pau
8 : *
9 : ******************************************************************************
10 : * Copyright (c) 2025, Xavier Pons
11 : *
12 : * SPDX-License-Identifier: MIT
13 : ****************************************************************************/
14 :
15 : #include "cpl_port.h"
16 : #include "gdal_priv.h"
17 : #include "cpl_string.h"
18 :
19 : #include "miramon_rel.h"
20 : #include "miramon_band.h"
21 :
22 : #include "../miramon_common/mm_gdal_functions.h" // For MMCheck_REL_FILE()
23 :
24 : CPLString MMRRel::m_szImprobableRELChain = "@#&%$|``|$%&#@";
25 :
26 : /************************************************************************/
27 : /* MMRRel() */
28 : /************************************************************************/
29 246 : MMRRel::MMRRel(const CPLString &osRELFilenameIn, bool bIMGMustExist)
30 246 : : m_osRelFileName(osRELFilenameIn)
31 : {
32 492 : CPLString osRelCandidate = osRELFilenameIn;
33 :
34 : // Getting the name of the REL
35 492 : const CPLString osMMRPrefix = "MiraMonRaster:";
36 246 : if (STARTS_WITH(osRelCandidate, osMMRPrefix))
37 : {
38 : // SUBDATASET case: gets the names of the bands in the subdataset
39 6 : size_t nPos = osRelCandidate.ifind(osMMRPrefix);
40 6 : if (nPos != 0)
41 0 : return;
42 :
43 6 : CPLString osSDSReL = osRelCandidate.substr(osMMRPrefix.size());
44 :
45 : // Getting the internal names of the bands
46 6 : const CPLStringList aosTokens(CSLTokenizeString2(osSDSReL, ",", 0));
47 6 : const int nTokens = CSLCount(aosTokens);
48 :
49 6 : if (nTokens < 1)
50 0 : return;
51 :
52 6 : osRelCandidate = aosTokens[0];
53 6 : osRelCandidate.replaceAll("\"", "");
54 :
55 : // Getting the list of bands in the subdataset
56 12 : for (int nIBand = 0; nIBand < nTokens - 1; nIBand++)
57 : {
58 : // Raw band name
59 12 : CPLString osBandName = aosTokens[nIBand + 1];
60 6 : osBandName.replaceAll("\"", "");
61 6 : m_papoSDSBands.emplace_back(osBandName);
62 : }
63 6 : m_bIsAMiraMonFile = true;
64 : }
65 : else
66 : {
67 : // Getting the metadata file name. If it's already a REL file,
68 : // then same name is returned.
69 240 : osRelCandidate = GetAssociatedMetadataFileName(m_osRelFileName.c_str());
70 240 : if (osRelCandidate.empty())
71 : {
72 168 : if (m_bIsAMiraMonFile)
73 : {
74 0 : CPLError(CE_Failure, CPLE_OpenFailed,
75 : "Metadata file for %s should exist.",
76 : m_osRelFileName.c_str());
77 : }
78 168 : if (!bIMGMustExist)
79 : {
80 : // Simulates that we have a MiraMon file
81 : // and we can ask things to this Rel file.
82 4 : UpdateRELNameChar(m_osRelFileName);
83 4 : m_bIsAMiraMonFile = true;
84 4 : if (!OpenRELFile())
85 0 : return;
86 : }
87 168 : return;
88 : }
89 : else
90 : {
91 : // It's a REL and it's not empty, so it's a MiraMon file
92 72 : VSILFILE *pF = VSIFOpenL(osRelCandidate, "r");
93 72 : if (!pF)
94 : {
95 0 : CPLError(CE_Failure, CPLE_OpenFailed,
96 : "Metadata file %s could not be opened.",
97 : m_osRelFileName.c_str());
98 0 : return;
99 : }
100 72 : VSIFSeekL(pF, 0, SEEK_END);
101 72 : if (VSIFTellL(pF))
102 72 : m_bIsAMiraMonFile = true;
103 : else
104 : {
105 0 : CPLError(
106 : CE_Failure, CPLE_OpenFailed,
107 : "Metadata file for %s should have some information in.",
108 : m_osRelFileName.c_str());
109 :
110 0 : VSIFCloseL(pF);
111 0 : return;
112 : }
113 72 : VSIFCloseL(pF);
114 : }
115 : }
116 :
117 : // If rel name was not a REL name, we update that
118 : // from the one found in the process of discovering it.
119 78 : UpdateRELNameChar(osRelCandidate);
120 :
121 : // We let it be opened
122 78 : if (!OpenRELFile())
123 0 : return;
124 :
125 : // Collect band information
126 78 : if (ParseBandInfo() != CE_None)
127 8 : return;
128 :
129 : // We have a valid object MMRREL.
130 70 : m_bIsValid = true;
131 :
132 70 : return;
133 : }
134 :
135 : /************************************************************************/
136 : /* ~MMRRel() */
137 : /************************************************************************/
138 :
139 246 : MMRRel::~MMRRel()
140 : {
141 246 : CloseRELFile();
142 246 : }
143 :
144 : /************************************************************************/
145 : /* Getting section-key-value */
146 : /************************************************************************/
147 : // Used when the MMRREL is not yet constructed.
148 : CPLString
149 247 : MMRRel::GetValueFromSectionKeyPriorToREL(const CPLString &osPriorRelName,
150 : const CPLString &osSection,
151 : const CPLString &osKey)
152 : {
153 247 : if (osPriorRelName.empty())
154 0 : return "";
155 :
156 247 : VSILFILE *pPriorRELFile = VSIFOpenL(osPriorRelName, "rb");
157 247 : if (!pPriorRELFile)
158 0 : return "";
159 :
160 494 : CPLString osValue = GetValueFromSectionKey(pPriorRELFile, osSection, osKey);
161 247 : VSIFCloseL(pPriorRELFile);
162 247 : return osValue;
163 : }
164 :
165 : // Used when the MMRREL is already constructed.
166 3567 : CPLString MMRRel::GetValueFromSectionKeyFromREL(const CPLString &osSection,
167 : const CPLString &osKey)
168 : {
169 3567 : if (!GetRELFile())
170 : {
171 0 : CPLError(CE_Failure, CPLE_AppDefined, "REL file is not opened: \"%s\"",
172 : m_osRelFileName.c_str());
173 0 : return "";
174 : }
175 :
176 3567 : return GetValueFromSectionKey(GetRELFile(), osSection, osKey);
177 : }
178 :
179 : // This function is the C++ equivalent of MMReturnValueFromSectionINIFile().
180 : // It improves upon the original by using CPLString instead of raw char pointers,
181 : // and by operating on an already opened file pointer rather than reopening the file
182 : // on each invocation.
183 : // MMReturnValueFromSectionINIFile() is retained in miramon_common because it is
184 : // widely used by existing, already OGR tested code (and in the common code itself).
185 : // At least in C++ code the modern version is used
186 3814 : CPLString MMRRel::GetValueFromSectionKey(VSILFILE *pf,
187 : const CPLString &osSection,
188 : const CPLString &osKey)
189 : {
190 3814 : if (!pf)
191 0 : return "";
192 :
193 7628 : CPLString osCurrentSection;
194 7628 : CPLString osCurrentKey, osCurrentValue;
195 3814 : bool bIAmInMySection = false;
196 :
197 : const char *pszLine;
198 :
199 3814 : VSIFSeekL(pf, 0, SEEK_SET);
200 427753 : while ((pszLine = CPLReadLine2L(pf, 10000, nullptr)) != nullptr)
201 : {
202 426866 : CPLString rawLine = pszLine;
203 :
204 426866 : rawLine.Recode(CPL_ENC_ISO8859_1, CPL_ENC_UTF8);
205 426866 : rawLine.Trim();
206 :
207 426866 : if (rawLine.empty() || rawLine[0] == ';' || rawLine[0] == '#')
208 71124 : continue;
209 :
210 355742 : if (rawLine[0] == '[' && rawLine[rawLine.size() - 1] == ']')
211 : {
212 74872 : if (bIAmInMySection)
213 : {
214 : // This is the next section to mine, so nothing to find here.
215 906 : return m_szImprobableRELChain;
216 : }
217 :
218 73966 : osCurrentSection = rawLine.substr(1, rawLine.size() - 2);
219 73966 : osCurrentSection.Trim();
220 :
221 73966 : if (!EQUAL(osCurrentSection, osSection))
222 70877 : bIAmInMySection = false;
223 : else
224 3089 : bIAmInMySection = true;
225 :
226 73966 : continue;
227 : }
228 :
229 280870 : if (!bIAmInMySection)
230 269246 : continue;
231 :
232 11624 : size_t equalPos = rawLine.find('=');
233 11624 : if (equalPos != CPLString::npos)
234 : {
235 11624 : osCurrentKey = rawLine.substr(0, equalPos);
236 11624 : osCurrentValue = rawLine.substr(equalPos + 1);
237 11624 : osCurrentKey.Trim();
238 11624 : osCurrentValue.Trim();
239 :
240 11624 : if (EQUAL(osCurrentKey, osKey))
241 2021 : return osCurrentValue;
242 : }
243 : }
244 :
245 887 : return m_szImprobableRELChain; // Key not found
246 : }
247 :
248 : /************************************************************************/
249 : /* Other functions */
250 : /************************************************************************/
251 :
252 : // Converts FileNameI.rel to FileName.img
253 130 : CPLString MMRRel::MMRGetFileNameFromRelName(const CPLString &osRELFile)
254 : {
255 130 : if (osRELFile.empty())
256 0 : return "";
257 :
258 260 : CPLString osFile = CPLString(CPLResetExtensionSafe(osRELFile, "").c_str());
259 :
260 130 : if (osFile.length() < 2)
261 0 : return "";
262 :
263 130 : osFile.resize(osFile.size() - 2); // I.
264 130 : osFile += pszExtRaster;
265 :
266 130 : return osFile;
267 : }
268 :
269 : // Converts FileName.img to FileNameI.rel
270 181 : CPLString MMRRel::MMRGetSimpleMetadataName(const CPLString &osLayerName)
271 : {
272 181 : if (osLayerName.empty())
273 0 : return "";
274 :
275 : // Extract extension
276 : CPLString osRELFile =
277 362 : CPLString(CPLResetExtensionSafe(osLayerName, "").c_str());
278 :
279 181 : if (!osRELFile.length())
280 0 : return "";
281 :
282 : // Extract "."
283 181 : osRELFile.resize(osRELFile.size() - 1);
284 : // Add "I.rel"
285 181 : osRELFile += pszExtRasterREL;
286 :
287 181 : return osRELFile;
288 : }
289 :
290 : // Gets the value from a section-key accessing directly to the RELFile.
291 : // It happens when MMRel is used to access a REL that is not an IMG sidecar
292 : // or at the Identify() process, when we don't have already the MMRRel constructed.
293 187 : bool MMRRel::GetAndExcludeMetadataValueDirectly(const CPLString &osRELFile,
294 : const CPLString &osSection,
295 : const CPLString &osKey,
296 : CPLString &osValue)
297 : {
298 187 : addExcludedSectionKey(osSection, osKey);
299 187 : return GetMetadataValueDirectly(osRELFile, osSection, osKey, osValue);
300 : }
301 :
302 247 : bool MMRRel::GetMetadataValueDirectly(const CPLString &osRELFile,
303 : const CPLString &osSection,
304 : const CPLString &osKey,
305 : CPLString &osValue)
306 : {
307 247 : osValue = GetValueFromSectionKeyPriorToREL(osRELFile, osSection, osKey);
308 :
309 247 : if (osValue != m_szImprobableRELChain)
310 130 : return true; // Found
311 :
312 117 : osValue = "";
313 117 : return false; // Key not found
314 : }
315 :
316 81 : bool MMRRel::SameFile(const CPLString &osFile1, const CPLString &osFile2)
317 : {
318 81 : if (EQUAL(osFile1, osFile2))
319 17 : return true;
320 :
321 : // Just to be more sure:
322 128 : CPLString osLayerName1 = osFile1;
323 64 : osLayerName1.replaceAll("\\", "/");
324 128 : CPLString osLayerName2 = osFile2;
325 64 : osLayerName2.replaceAll("\\", "/");
326 :
327 64 : if (EQUAL(osLayerName1, osLayerName2))
328 0 : return true;
329 :
330 64 : return false;
331 : }
332 :
333 : // Gets the state (enum class MMRNomFitxerState) of NomFitxer in the
334 : // specified section
335 : // [pszSection]
336 : // NomFitxer=Value
337 81 : MMRNomFitxerState MMRRel::MMRStateOfNomFitxerInSection(
338 : const CPLString &osLayerName, const CPLString &osSection,
339 : const CPLString &osRELFile, bool bNomFitxerMustExist)
340 : {
341 162 : CPLString osDocumentedLayerName;
342 :
343 81 : if (!GetAndExcludeMetadataValueDirectly(osRELFile, osSection, KEY_NomFitxer,
344 90 : osDocumentedLayerName) ||
345 9 : osDocumentedLayerName.empty())
346 : {
347 144 : CPLString osIIMGFromREL = MMRGetFileNameFromRelName(osRELFile);
348 72 : if (SameFile(osIIMGFromREL, osLayerName))
349 13 : return MMRNomFitxerState::NOMFITXER_VALUE_EXPECTED;
350 :
351 59 : if (bNomFitxerMustExist)
352 22 : return MMRNomFitxerState::NOMFITXER_VALUE_UNEXPECTED;
353 : else
354 37 : return MMRNomFitxerState::NOMFITXER_NOT_FOUND;
355 : }
356 :
357 18 : CPLString osFileAux = CPLFormFilenameSafe(CPLGetPathSafe(osRELFile).c_str(),
358 18 : osDocumentedLayerName, "");
359 :
360 9 : osDocumentedLayerName.Trim();
361 9 : if (*osDocumentedLayerName == '*' || *osDocumentedLayerName == '?')
362 0 : return MMRNomFitxerState::NOMFITXER_VALUE_UNEXPECTED;
363 :
364 9 : if (SameFile(osFileAux, osLayerName))
365 4 : return MMRNomFitxerState::NOMFITXER_VALUE_EXPECTED;
366 :
367 5 : return MMRNomFitxerState::NOMFITXER_VALUE_UNEXPECTED;
368 : }
369 :
370 : // Tries to find a reference to the IMG file 'pszLayerName'
371 : // we are opening in the REL file 'pszRELFile'
372 51 : CPLString MMRRel::MMRGetAReferenceToIMGFile(const CPLString &osLayerName,
373 : const CPLString &osRELFile)
374 : {
375 51 : if (osRELFile.empty())
376 : {
377 0 : CPLError(CE_Failure, CPLE_OpenFailed, "Expected File name.");
378 0 : return "";
379 : }
380 :
381 : // [ATTRIBUTE_DATA]
382 : // NomFitxer=
383 : // It should be empty but if it's not, at least,
384 : // the value has to be osLayerName
385 51 : MMRNomFitxerState iState = MMRStateOfNomFitxerInSection(
386 : osLayerName, SECTION_ATTRIBUTE_DATA, osRELFile, false);
387 :
388 51 : if (iState == MMRNomFitxerState::NOMFITXER_VALUE_EXPECTED ||
389 : iState == MMRNomFitxerState::NOMFITXER_VALUE_EMPTY)
390 : {
391 14 : return osRELFile;
392 : }
393 37 : else if (iState == MMRNomFitxerState::NOMFITXER_VALUE_UNEXPECTED)
394 : {
395 0 : if (m_bIsAMiraMonFile)
396 : {
397 0 : CPLError(
398 : CE_Failure, CPLE_OpenFailed,
399 : "Unexpected value for SECTION_ATTRIBUTE_DATA [NomFitxer] in "
400 : "%s file.",
401 : osRELFile.c_str());
402 : }
403 0 : return "";
404 : }
405 :
406 : // Discarting not supported via SDE (some files
407 : // could have this option)
408 74 : CPLString osVia;
409 37 : if (GetAndExcludeMetadataValueDirectly(osRELFile, SECTION_ATTRIBUTE_DATA,
410 : KEY_via, osVia))
411 : {
412 0 : if (!osVia.empty() && !EQUAL(osVia, "SDE"))
413 : {
414 0 : if (m_bIsAMiraMonFile)
415 : {
416 0 : CPLError(CE_Failure, CPLE_OpenFailed,
417 : "Unexpected Via in %s file", osRELFile.c_str());
418 : }
419 0 : return "";
420 : }
421 : }
422 :
423 74 : CPLString osFieldNames;
424 :
425 37 : if (!GetAndExcludeMetadataValueDirectly(osRELFile, SECTION_ATTRIBUTE_DATA,
426 : Key_IndexesNomsCamps,
427 68 : osFieldNames) ||
428 31 : osFieldNames.empty())
429 : {
430 8 : if (m_bIsAMiraMonFile)
431 : {
432 0 : CPLError(CE_Failure, CPLE_OpenFailed,
433 : "IndexesNomsCamps not found in %s file",
434 : osRELFile.c_str());
435 : }
436 8 : return "";
437 : }
438 :
439 : // Getting the internal names of the bands
440 58 : const CPLStringList aosTokens(CSLTokenizeString2(osFieldNames, ",", 0));
441 29 : const int nTokenBands = CSLCount(aosTokens);
442 :
443 58 : CPLString osBandSectionKey;
444 58 : CPLString osAttributeDataName;
445 58 : for (int nIBand = 0; nIBand < nTokenBands; nIBand++)
446 : {
447 32 : osBandSectionKey = KEY_NomCamp;
448 32 : osBandSectionKey.append("_");
449 32 : osBandSectionKey.append(aosTokens[nIBand]);
450 :
451 32 : CPLString osBandSectionValue;
452 :
453 32 : if (!GetAndExcludeMetadataValueDirectly(
454 : osRELFile, SECTION_ATTRIBUTE_DATA, osBandSectionKey,
455 62 : osBandSectionValue) ||
456 30 : osBandSectionValue.empty())
457 2 : continue; // A band without name (·$· unexpected)
458 :
459 : // Example: [ATTRIBUTE_DATA:G1]
460 30 : osAttributeDataName = SECTION_ATTRIBUTE_DATA;
461 30 : osAttributeDataName.append(":");
462 30 : osAttributeDataName.append(osBandSectionValue.Trim());
463 :
464 : // Let's see if this band contains the expected name
465 : // or none (in monoband case)
466 30 : iState = MMRStateOfNomFitxerInSection(osLayerName, osAttributeDataName,
467 : osRELFile, true);
468 30 : if (iState == MMRNomFitxerState::NOMFITXER_VALUE_EXPECTED)
469 3 : return osRELFile;
470 :
471 27 : else if (iState == MMRNomFitxerState::NOMFITXER_VALUE_UNEXPECTED)
472 27 : continue;
473 :
474 : // If there is only one band is accepted NOMFITXER_NOT_FOUND/EMPTY iState result
475 0 : if (nTokenBands == 1)
476 0 : return osRELFile;
477 : }
478 :
479 26 : if (m_bIsAMiraMonFile)
480 : {
481 0 : CPLError(CE_Failure, CPLE_OpenFailed,
482 : "REL search failed for all bands in %s file",
483 : osRELFile.c_str());
484 : }
485 26 : return "";
486 : }
487 :
488 : // Finds the metadata filename associated to osFileName (usually an IMG file)
489 240 : CPLString MMRRel::GetAssociatedMetadataFileName(const CPLString &osFileName)
490 : {
491 240 : if (osFileName.empty())
492 : {
493 0 : if (m_bIsAMiraMonFile)
494 0 : CPLError(CE_Failure, CPLE_OpenFailed, "Expected File name.");
495 0 : return "";
496 : }
497 :
498 : // If the string finishes in "I.rel" we consider it can be
499 : // the associated file to all bands that are documented in this file.
500 240 : if (cpl::ends_with(osFileName, pszExtRasterREL))
501 : {
502 55 : m_bIsAMiraMonFile = true;
503 55 : return osFileName;
504 : }
505 :
506 : // If the file is not a REL file, let's try to find the associated REL
507 : // It must be a IMG file.
508 370 : CPLString osExtension = CPLString(CPLGetExtensionSafe(osFileName).c_str());
509 185 : if (!EQUAL(osExtension, pszExtRaster + 1))
510 4 : return "";
511 :
512 : // Converting FileName.img to FileNameI.rel
513 362 : CPLString osRELFile = MMRGetSimpleMetadataName(osFileName);
514 181 : if (osRELFile.empty())
515 : {
516 0 : if (m_bIsAMiraMonFile)
517 : {
518 0 : CPLError(CE_Failure, CPLE_OpenFailed,
519 : "Failing in conversion from .img to I.rel for %s file",
520 : osFileName.c_str());
521 : }
522 0 : return "";
523 : }
524 :
525 : // Checking if the file exists
526 : VSIStatBufL sStat;
527 181 : if (VSIStatExL(osRELFile.c_str(), &sStat, VSI_STAT_EXISTS_FLAG) == 0)
528 14 : return MMRGetAReferenceToIMGFile(osFileName, osRELFile);
529 :
530 : // If the file I.rel doesn't exist then it has to be found
531 : // in the same folder than the .img file.
532 334 : const CPLString osPath = CPLGetPathSafe(osFileName);
533 334 : const CPLStringList folder(VSIReadDir(osPath.c_str()));
534 167 : const int size = folder.size();
535 :
536 1279 : for (int i = 0; i < size; i++)
537 : {
538 1115 : if (folder[i][0] == '.' || !strstr(folder[i], "I.rel"))
539 : {
540 1078 : continue;
541 : }
542 :
543 : const CPLString osFilePath =
544 37 : CPLFormFilenameSafe(osPath, folder[i], nullptr);
545 :
546 37 : osRELFile = MMRGetAReferenceToIMGFile(osFileName, osFilePath);
547 37 : if (!osRELFile.empty())
548 3 : return osRELFile;
549 : }
550 :
551 164 : if (m_bIsAMiraMonFile)
552 : {
553 0 : CPLError(CE_Failure, CPLE_OpenFailed, "REL search failed for %s file",
554 : osFileName.c_str());
555 : }
556 :
557 164 : return "";
558 : }
559 :
560 : /************************************************************************/
561 : /* CheckBandInRel() */
562 : /************************************************************************/
563 12 : CPLErr MMRRel::CheckBandInRel(const CPLString &osRELFileName,
564 : const CPLString &osIMGFile)
565 :
566 : {
567 24 : CPLString osFieldNames;
568 12 : if (!GetMetadataValueDirectly(osRELFileName, SECTION_ATTRIBUTE_DATA,
569 24 : Key_IndexesNomsCamps, osFieldNames) ||
570 12 : osFieldNames.empty())
571 0 : return CE_Failure;
572 :
573 : // Separator ,
574 24 : const CPLStringList aosTokens(CSLTokenizeString2(osFieldNames, ",", 0));
575 12 : const int nTokenCount = CSLCount(aosTokens);
576 :
577 12 : if (!nTokenCount)
578 0 : return CE_Failure;
579 :
580 24 : CPLString osBandSectionKey;
581 24 : CPLString osBandSectionValue;
582 24 : for (int i = 0; i < nTokenCount; i++)
583 : {
584 24 : osBandSectionKey = KEY_NomCamp;
585 24 : osBandSectionKey.append("_");
586 24 : osBandSectionKey.append(aosTokens[i]);
587 :
588 24 : if (!GetMetadataValueDirectly(osRELFileName, SECTION_ATTRIBUTE_DATA,
589 48 : osBandSectionKey, osBandSectionValue) ||
590 24 : osBandSectionValue.empty())
591 0 : return CE_Failure;
592 :
593 24 : CPLString osAttributeDataName;
594 24 : osAttributeDataName = SECTION_ATTRIBUTE_DATA;
595 24 : osAttributeDataName.append(":");
596 24 : osAttributeDataName.append(osBandSectionValue.Trim());
597 :
598 24 : CPLString osRawBandFileName;
599 :
600 24 : if (!GetMetadataValueDirectly(osRELFileName, osAttributeDataName,
601 48 : KEY_NomFitxer, osRawBandFileName) ||
602 24 : osRawBandFileName.empty())
603 : {
604 0 : CPLString osBandFileName = MMRGetFileNameFromRelName(osRELFileName);
605 0 : if (osBandFileName.empty())
606 0 : return CE_Failure;
607 : }
608 : else
609 : {
610 24 : if (!EQUAL(osRawBandFileName, osIMGFile))
611 12 : continue;
612 12 : break; // Found
613 : }
614 : }
615 :
616 12 : return CE_None;
617 : }
618 :
619 55496 : int MMRRel::IdentifySubdataSetFile(const CPLString &osFileName)
620 : {
621 110998 : const CPLString osMMRPrefix = "MiraMonRaster:";
622 55501 : if (!STARTS_WITH(osFileName, osMMRPrefix))
623 55490 : return FALSE;
624 :
625 : // SUBDATASETS
626 12 : size_t nPos = osFileName.ifind(osMMRPrefix);
627 12 : if (nPos != 0)
628 0 : return GDAL_IDENTIFY_FALSE;
629 :
630 24 : CPLString osRELAndBandName = osFileName.substr(osMMRPrefix.size());
631 :
632 24 : const CPLStringList aosTokens(CSLTokenizeString2(osRELAndBandName, ",", 0));
633 12 : const int nTokens = CSLCount(aosTokens);
634 : // Getting the REL associated to the bands
635 : // We need the REL and at least one band (index + name).
636 12 : if (nTokens < 2)
637 0 : return GDAL_IDENTIFY_FALSE;
638 :
639 : // Let's remove "\"" if existent.
640 24 : CPLString osRELName = aosTokens[0];
641 12 : osRELName.replaceAll("\"", "");
642 :
643 : // It must be a I.rel file.
644 12 : if (!cpl::ends_with(osRELName, pszExtRasterREL))
645 0 : return GDAL_IDENTIFY_FALSE;
646 :
647 12 : if (MMCheck_REL_FILE(osRELName))
648 0 : return GDAL_IDENTIFY_FALSE;
649 :
650 : // Let's see if the specified bands are in the REL file
651 : // Getting the index + internal names of the bands
652 24 : for (int nIBand = 1; nIBand < nTokens; nIBand++)
653 : {
654 : // Let's check that this band (papszTokens[nIBand]) is in the REL file.
655 12 : CPLString osBandName = aosTokens[nIBand];
656 :
657 : // Let's remove "\"" if existent.
658 12 : osBandName.replaceAll("\"", "");
659 :
660 : // If it's not an IMG file return FALSE
661 : CPLString osExtension =
662 12 : CPLString(CPLGetExtensionSafe(osBandName).c_str());
663 12 : if (!EQUAL(osExtension, pszExtRaster + 1))
664 0 : return GDAL_IDENTIFY_FALSE;
665 :
666 12 : if (CE_None != CheckBandInRel(osRELName, osBandName))
667 0 : return GDAL_IDENTIFY_FALSE;
668 : }
669 12 : return GDAL_IDENTIFY_TRUE;
670 : }
671 :
672 55489 : int MMRRel::IdentifyFile(const GDALOpenInfo *poOpenInfo)
673 : {
674 : // IMG files are shared for many drivers.
675 : // Identify will mark it as unknown.
676 : // Open function will try to open that, but as it has computation
677 : // cost is better avoid doing it here.
678 55489 : if (poOpenInfo->IsExtensionEqualToCI("IMG"))
679 475 : return GDAL_IDENTIFY_UNKNOWN;
680 :
681 55009 : if (!poOpenInfo->IsExtensionEqualToCI("REL"))
682 54897 : return GDAL_IDENTIFY_FALSE;
683 :
684 : // In fact, the file has to end with I.rel (pszExtRasterREL)
685 115 : if (!cpl::ends_with(std::string_view(poOpenInfo->pszFilename),
686 : pszExtRasterREL))
687 2 : return GDAL_IDENTIFY_FALSE;
688 :
689 : // Some versions of REL files are not allowed.
690 112 : if (MMCheck_REL_FILE(poOpenInfo->pszFilename))
691 2 : return GDAL_IDENTIFY_FALSE;
692 :
693 110 : return GDAL_IDENTIFY_TRUE;
694 : }
695 :
696 : /************************************************************************/
697 : /* GetMetadataValue() */
698 : /************************************************************************/
699 328 : bool MMRRel::GetMetadataValue(const CPLString &osMainSection,
700 : const CPLString &osSubSection,
701 : const CPLString &osSubSubSection,
702 : const CPLString &osKey, CPLString &osValue)
703 : {
704 328 : CPLAssert(
705 : isAMiraMonFile()); // Trying to access metadata from the wrong way
706 :
707 : // Searches in [pszMainSection:pszSubSection]
708 656 : CPLString osAttributeDataName;
709 328 : osAttributeDataName = osMainSection;
710 328 : osAttributeDataName.append(":");
711 328 : osAttributeDataName.append(osSubSection);
712 328 : osAttributeDataName.append(":");
713 328 : osAttributeDataName.append(osSubSubSection);
714 :
715 328 : addExcludedSectionKey(osAttributeDataName, osKey);
716 328 : osValue = GetValueFromSectionKeyFromREL(osAttributeDataName, osKey);
717 328 : if (osValue != m_szImprobableRELChain)
718 0 : return true; // Found
719 :
720 : // If the value is not found then searches in [pszMainSection]
721 328 : addExcludedSectionKey(osSubSubSection, osKey);
722 328 : osValue = GetValueFromSectionKeyFromREL(osSubSubSection, osKey);
723 328 : if (osValue == m_szImprobableRELChain)
724 : {
725 40 : osValue = "";
726 40 : return false; // Key not found
727 : }
728 288 : return true; // Found
729 : }
730 :
731 1109 : bool MMRRel::GetMetadataValue(const CPLString &osMainSection,
732 : const CPLString &osSubSection,
733 : const CPLString &osKey, CPLString &osValue)
734 : {
735 1109 : CPLAssert(
736 : isAMiraMonFile()); // Trying to access metadata from the wrong way
737 :
738 : // Searches in [pszMainSection:pszSubSection]
739 2218 : CPLString osAttributeDataName;
740 1109 : osAttributeDataName = osMainSection;
741 1109 : osAttributeDataName.append(":");
742 1109 : osAttributeDataName.append(osSubSection);
743 :
744 1109 : addExcludedSectionKey(osAttributeDataName, osKey);
745 1109 : osValue = GetValueFromSectionKeyFromREL(osAttributeDataName, osKey);
746 1109 : if (osValue != m_szImprobableRELChain)
747 324 : return true; // Found
748 :
749 : // If the value is not found then searches in [pszMainSection]
750 785 : addExcludedSectionKey(osMainSection, osKey);
751 785 : osValue = GetValueFromSectionKeyFromREL(osMainSection, osKey);
752 785 : if (osValue == m_szImprobableRELChain)
753 : {
754 509 : osValue = "";
755 509 : return false; // Key not found
756 : }
757 276 : return true; // Found
758 : }
759 :
760 1017 : bool MMRRel::GetMetadataValue(const CPLString &osSection,
761 : const CPLString &osKey, CPLString &osValue)
762 : {
763 1017 : CPLAssert(
764 : isAMiraMonFile()); // Trying to access metadata from the wrong way
765 :
766 1017 : addExcludedSectionKey(osSection, osKey);
767 1017 : osValue = GetValueFromSectionKeyFromREL(osSection, osKey);
768 1017 : if (osValue == m_szImprobableRELChain)
769 : {
770 14 : osValue = "";
771 14 : return false; // Key not found
772 : }
773 1003 : return true; // Found
774 : }
775 :
776 82 : void MMRRel::UpdateRELNameChar(const CPLString &osRelFileNameIn)
777 : {
778 82 : m_osRelFileName = osRelFileNameIn;
779 82 : }
780 :
781 : /************************************************************************/
782 : /* ParseBandInfo() */
783 : /************************************************************************/
784 78 : CPLErr MMRRel::ParseBandInfo()
785 : {
786 78 : m_nBands = 0;
787 :
788 156 : CPLString osFieldNames;
789 78 : if (!GetMetadataValue(SECTION_ATTRIBUTE_DATA, Key_IndexesNomsCamps,
790 155 : osFieldNames) ||
791 77 : osFieldNames.empty())
792 : {
793 2 : CPLError(CE_Failure, CPLE_AssertionFailed,
794 : "%s-%s section-key should exist in %s.",
795 : SECTION_ATTRIBUTE_DATA, Key_IndexesNomsCamps,
796 : m_osRelFileName.c_str());
797 2 : return CE_Failure;
798 : }
799 :
800 : // Separator ,
801 152 : const CPLStringList aosTokens(CSLTokenizeString2(osFieldNames, ",", 0));
802 76 : const int nMaxBands = CSLCount(aosTokens);
803 :
804 76 : if (!nMaxBands)
805 : {
806 0 : CPLError(CE_Failure, CPLE_AssertionFailed, "No bands in file %s.",
807 : m_osRelFileName.c_str());
808 0 : return CE_Failure;
809 : }
810 :
811 152 : CPLString osBandSectionKey;
812 152 : CPLString osBandSectionValue;
813 :
814 : int nNBand;
815 76 : if (m_papoSDSBands.size())
816 6 : nNBand = static_cast<int>(m_papoSDSBands.size());
817 : else
818 70 : nNBand = nMaxBands;
819 :
820 76 : m_oBands.reserve(nNBand);
821 :
822 170 : for (int i = 0; i < nMaxBands; i++)
823 : {
824 100 : osBandSectionKey = KEY_NomCamp;
825 100 : osBandSectionKey.append("_");
826 100 : osBandSectionKey.append(aosTokens[i]);
827 :
828 100 : if (!GetMetadataValue(SECTION_ATTRIBUTE_DATA, osBandSectionKey,
829 199 : osBandSectionValue) ||
830 99 : osBandSectionValue.empty())
831 1 : continue;
832 :
833 99 : if (m_papoSDSBands.size())
834 : {
835 18 : CPLString osRawBandFileName;
836 18 : if (!GetMetadataValue(SECTION_ATTRIBUTE_DATA, osBandSectionValue,
837 36 : KEY_NomFitxer, osRawBandFileName) ||
838 18 : osRawBandFileName.empty())
839 0 : return CE_Failure;
840 :
841 : // I'm in a Subataset
842 : size_t nISDSBand;
843 30 : for (nISDSBand = 0; nISDSBand < m_papoSDSBands.size(); nISDSBand++)
844 : {
845 18 : if (m_papoSDSBands[nISDSBand] == osRawBandFileName)
846 6 : break;
847 : }
848 18 : if (nISDSBand == m_papoSDSBands.size())
849 12 : continue;
850 : }
851 :
852 87 : if (m_nBands >= nNBand)
853 0 : break;
854 :
855 : m_oBands.emplace_back(
856 87 : std::make_unique<MMRBand>(*this, osBandSectionValue.Trim()));
857 :
858 87 : if (!m_oBands[m_nBands]->IsValid())
859 : {
860 : // This band is not been completed
861 6 : return CE_Failure;
862 : }
863 :
864 81 : m_nBands++;
865 : }
866 :
867 70 : return CE_None;
868 : }
869 :
870 128 : int MMRRel::GetColumnsNumberFromREL()
871 : {
872 : // Number of columns of the subdataset (if exist)
873 : // Section [OVERVIEW:ASPECTES_TECNICS] in rel file
874 256 : CPLString osValue;
875 :
876 256 : if (!GetMetadataValue(SECTION_OVVW_ASPECTES_TECNICS, "columns", osValue) ||
877 128 : osValue.empty())
878 0 : return 0; // Default value
879 :
880 : int nValue;
881 128 : if (1 != sscanf(osValue, "%d", &nValue))
882 0 : return 0; // Default value
883 :
884 128 : return nValue;
885 : }
886 :
887 128 : int MMRRel::GetRowsNumberFromREL()
888 : {
889 : // Number of columns of the subdataset (if exist)
890 : // Section [OVERVIEW:ASPECTES_TECNICS] in rel file
891 : // Key raws
892 256 : CPLString osValue;
893 :
894 256 : if (!GetMetadataValue(SECTION_OVVW_ASPECTES_TECNICS, "rows", osValue) ||
895 128 : osValue.empty())
896 0 : return 0; // Default value
897 :
898 : int nValue;
899 128 : if (1 != sscanf(osValue, "%d", &nValue))
900 0 : return 0; // Default value
901 :
902 128 : return nValue;
903 : }
904 :
905 : /************************************************************************/
906 : /* Preserving metadata */
907 : /************************************************************************/
908 63 : void MMRRel::RELToGDALMetadata(GDALDataset *poDS)
909 : {
910 63 : if (!m_pRELFile)
911 : {
912 0 : CPLError(CE_Failure, CPLE_AppDefined,
913 : "REL file cannot be opened: \"%s\"", m_osRelFileName.c_str());
914 0 : return;
915 : }
916 :
917 126 : CPLString osCurrentSection;
918 126 : CPLString osPendingKey, osPendingValue;
919 :
920 6994 : auto isExcluded = [&](const CPLString §ion, const CPLString &key)
921 : {
922 20146 : return GetExcludedMetadata().count({section, key}) ||
923 20146 : GetExcludedMetadata().count({section, ""});
924 63 : };
925 :
926 : const char *pszLine;
927 :
928 63 : VSIFSeekL(m_pRELFile, 0, SEEK_SET);
929 10488 : while ((pszLine = CPLReadLine2L(m_pRELFile, 10000, nullptr)) != nullptr)
930 : {
931 10425 : CPLString rawLine = pszLine;
932 :
933 10425 : rawLine.Recode(CPL_ENC_ISO8859_1, CPL_ENC_UTF8);
934 10425 : rawLine.Trim();
935 :
936 10425 : if (rawLine.empty() || rawLine[0] == ';' || rawLine[0] == '#')
937 1685 : continue;
938 :
939 8740 : if (rawLine[0] == '[' && rawLine[rawLine.size() - 1] == ']')
940 : {
941 : // Saves last key
942 1746 : if (!osPendingKey.empty())
943 : {
944 1683 : if (!isExcluded(osCurrentSection, osPendingKey))
945 : {
946 : CPLString fullKey =
947 4692 : osCurrentSection + m_SecKeySeparator + osPendingKey;
948 :
949 1564 : poDS->SetMetadataItem(fullKey.c_str(),
950 1564 : osPendingValue.Trim().c_str(),
951 1564 : m_kMetadataDomain);
952 : }
953 1683 : osPendingKey.clear();
954 1683 : osPendingValue.clear();
955 : }
956 :
957 1746 : osCurrentSection = rawLine.substr(1, rawLine.size() - 2);
958 1746 : osCurrentSection.Trim();
959 1746 : continue;
960 : }
961 :
962 6994 : size_t equalPos = rawLine.find('=');
963 6994 : if (equalPos != CPLString::npos)
964 : {
965 : // Desa clau anterior
966 6994 : if (!osPendingKey.empty())
967 : {
968 5248 : if (!isExcluded(osCurrentSection, osPendingKey))
969 : {
970 : CPLString fullKey =
971 13614 : osCurrentSection + m_SecKeySeparator + osPendingKey;
972 :
973 4538 : poDS->SetMetadataItem(fullKey.c_str(),
974 4538 : osPendingValue.Trim().c_str(),
975 4538 : m_kMetadataDomain);
976 : }
977 : }
978 :
979 6994 : osPendingKey = rawLine.substr(0, equalPos);
980 6994 : osPendingValue = rawLine.substr(equalPos + 1);
981 6994 : osPendingKey.Trim();
982 6994 : osPendingValue.Trim();
983 : }
984 0 : else if (!osPendingKey.empty())
985 : {
986 0 : osPendingValue += "\n" + rawLine;
987 : }
988 : }
989 :
990 : // Saves last key
991 63 : if (!osPendingKey.empty())
992 : {
993 189 : CPLString fullKey = osCurrentSection + m_SecKeySeparator + osPendingKey;
994 63 : if (!isExcluded(osCurrentSection, osPendingKey))
995 56 : poDS->SetMetadataItem(fullKey.c_str(),
996 56 : osPendingValue.Trim().c_str(),
997 56 : m_kMetadataDomain);
998 : }
999 : }
|