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