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