Line data Source code
1 : /******************************************************************************
2 : *
3 : * Project: MiraMonRaster driver
4 : * Purpose: Implements MMRDataset class: responsible for generating the
5 : * main dataset or the subdatasets as needed.
6 : * Author: Abel Pau
7 : *
8 : ******************************************************************************
9 : * Copyright (c) 2025, Xavier Pons
10 : *
11 : * SPDX-License-Identifier: MIT
12 : ****************************************************************************/
13 :
14 : #include <algorithm>
15 : #include <cassert>
16 :
17 : #include "miramon_dataset.h"
18 : #include "miramon_rasterband.h"
19 : #include "miramon_band.h" // Per a MMRBand
20 :
21 : #include "gdal_frmts.h"
22 :
23 : #include "../miramon_common/mm_gdal_functions.h" // For MMCheck_REL_FILE()
24 :
25 : /************************************************************************/
26 : /* GDALRegister_MiraMon() */
27 : /************************************************************************/
28 2063 : void GDALRegister_MiraMon()
29 :
30 : {
31 2063 : if (GDALGetDriverByName("MiraMonRaster") != nullptr)
32 283 : return;
33 :
34 1780 : GDALDriver *poDriver = new GDALDriver();
35 :
36 1780 : poDriver->SetDescription("MiraMonRaster");
37 1780 : poDriver->SetMetadataItem(GDAL_DCAP_RASTER, "YES");
38 1780 : poDriver->SetMetadataItem(GDAL_DMD_LONGNAME, "MiraMon Raster Images");
39 1780 : poDriver->SetMetadataItem(GDAL_DMD_HELPTOPIC,
40 1780 : "drivers/raster/miramon.html");
41 1780 : poDriver->SetMetadataItem(GDAL_DMD_EXTENSIONS, "rel img");
42 :
43 1780 : poDriver->SetMetadataItem(GDAL_DCAP_VIRTUALIO, "YES");
44 1780 : poDriver->SetMetadataItem(GDAL_DMD_SUBDATASETS, "YES");
45 :
46 1780 : poDriver->SetMetadataItem(GDAL_DCAP_OPEN, "YES");
47 1780 : poDriver->SetMetadataItem(GDAL_DCAP_CREATECOPY, "YES");
48 :
49 1780 : poDriver->SetMetadataItem(
50 : GDAL_DMD_CREATIONOPTIONLIST,
51 : "<CreationOptionList>"
52 : " <Option name='COMPRESS' type='boolean' description='Indicates "
53 : "whether the file will be compressed in RLE indexed mode'/>"
54 : " <Option name='PATTERN' type='int' description='Indicates the "
55 : "pattern used to create the names of the different bands. In the "
56 : "case of RGB, the suffixes “_R”, “_G”, and “_B” will be added to "
57 : "the base name.'/>"
58 : " <Option name='CATEGORICAL_BANDS' type='string' "
59 : "description='Indicates "
60 : "which bands have to be treat as categorical.'/>"
61 : " <Option name='CONTINUOUS_BANDS' type='string' "
62 : "description='Indicates "
63 : "which bands have to be treat as continuous.'/>"
64 1780 : "</CreationOptionList>");
65 :
66 1780 : poDriver->SetMetadataItem(
67 : GDAL_DMD_OPENOPTIONLIST,
68 : "<OpenOptionList>\n"
69 : " <Option name='RAT_OR_CT' type='string-select' "
70 : "description='Controls whether the Raster Attribute Table (RAT) "
71 : "and/or the Color Table (CT) are exposed.' default='ALL'>\n"
72 : " <Value>ALL</Value>\n"
73 : " <Value>RAT</Value>\n"
74 : " <Value>CT</Value>\n"
75 : " </Option>\n"
76 1780 : "</OpenOptionList>\n");
77 :
78 1780 : poDriver->pfnOpen = MMRDataset::Open;
79 1780 : poDriver->pfnCreateCopy = MMRDataset::CreateCopy;
80 1780 : poDriver->pfnIdentify = MMRDataset::Identify;
81 :
82 1780 : GetGDALDriverManager()->RegisterDriver(poDriver);
83 : }
84 :
85 : /************************************************************************/
86 : /* MMRDataset() */
87 : /************************************************************************/
88 132 : MMRDataset::MMRDataset(GDALProgressFunc pfnProgress, void *pProgressData,
89 : CSLConstList papszOptions, CPLString osRelname,
90 : GDALDataset &oSrcDS, const CPLString &osUsrPattern,
91 132 : const CPLString &osPattern)
92 132 : : m_bIsValid(false)
93 : {
94 132 : nBands = oSrcDS.GetRasterCount();
95 132 : if (nBands == 0)
96 : {
97 1 : ReportError(osRelname, CE_Failure, CPLE_AppDefined,
98 : "Unable to translate to MiraMon files with zero bands.");
99 9 : return;
100 : }
101 :
102 131 : UpdateProjection(oSrcDS);
103 :
104 : // Getting bands information and creating MMRBand objects.
105 : // Also checking if all bands have the same dimensions.
106 131 : bool bNeedOfNomFitxer = (nBands > 1 || !osUsrPattern.empty());
107 :
108 131 : std::vector<MMRBand> oBands{};
109 131 : oBands.reserve(nBands);
110 :
111 131 : bool bAllBandsSameDim = true;
112 293 : for (int nIBand = 0; nIBand < nBands; nIBand++)
113 : {
114 167 : GDALRasterBand *pRasterBand = oSrcDS.GetRasterBand(nIBand + 1);
115 167 : if (!pRasterBand)
116 : {
117 0 : ReportError(
118 : osRelname, CE_Failure, CPLE_AppDefined,
119 : "Unable to translate the band %d to MiraMon. Process canceled.",
120 : nIBand);
121 5 : return;
122 : }
123 :
124 : // Detection of the index of the band in the RGB composition (if it applies).
125 167 : CPLString osIndexBand;
126 167 : CPLString osNumberIndexBand;
127 167 : if (pRasterBand->GetColorInterpretation() == GCI_RedBand)
128 : {
129 1 : osIndexBand = "R";
130 1 : m_nIBandR = nIBand;
131 : }
132 166 : else if (pRasterBand->GetColorInterpretation() == GCI_GreenBand)
133 : {
134 1 : osIndexBand = "G";
135 1 : m_nIBandG = nIBand;
136 : }
137 165 : else if (pRasterBand->GetColorInterpretation() == GCI_BlueBand)
138 : {
139 1 : osIndexBand = "B";
140 1 : m_nIBandB = nIBand;
141 : }
142 164 : else if (pRasterBand->GetColorInterpretation() == GCI_AlphaBand)
143 0 : osIndexBand = "Alpha";
144 : else
145 164 : osIndexBand = CPLSPrintf("%d", nIBand + 1);
146 :
147 167 : osNumberIndexBand = CPLSPrintf("%d", nIBand + 1);
148 167 : bool bCategorical = IsCategoricalBand(oSrcDS, *pRasterBand,
149 167 : papszOptions, osNumberIndexBand);
150 :
151 : bool bCompressDS =
152 167 : EQUAL(CSLFetchNameValueDef(papszOptions, "COMPRESS", "YES"), "YES");
153 :
154 : // Emplace back a MMRBand
155 : oBands.emplace_back(pfnProgress, pProgressData, oSrcDS, nIBand,
156 334 : CPLGetPathSafe(osRelname), *pRasterBand,
157 : bCompressDS, bCategorical, osPattern, osIndexBand,
158 167 : bNeedOfNomFitxer);
159 167 : if (!oBands.back().IsValid())
160 : {
161 5 : ReportError(
162 : osRelname, CE_Failure, CPLE_AppDefined,
163 : "Unable to translate the band %d to MiraMon. Process canceled.",
164 : nIBand);
165 5 : return;
166 : }
167 162 : if (nIBand == 0)
168 : {
169 126 : m_nWidth = oBands.back().GetWidth();
170 126 : m_nHeight = oBands.back().GetHeight();
171 : }
172 72 : else if (m_nWidth != oBands.back().GetWidth() ||
173 36 : m_nHeight != oBands.back().GetHeight())
174 : {
175 0 : bAllBandsSameDim = false;
176 : }
177 : }
178 :
179 : // Getting number of columns and rows
180 126 : if (!bAllBandsSameDim)
181 : {
182 : // It's not an error. MiraMon have Datasets
183 : // with dimensions for each band
184 0 : m_nWidth = 0;
185 0 : m_nHeight = 0;
186 : }
187 : else
188 : {
189 : // Getting geotransform
190 126 : GDALGeoTransform gt;
191 126 : if (oSrcDS.GetGeoTransform(gt) == CE_None)
192 : {
193 111 : m_dfMinX = gt[0];
194 111 : m_dfMaxY = gt[3];
195 111 : m_dfMaxX = m_dfMinX + m_nWidth * gt[1];
196 111 : m_dfMinY = m_dfMaxY + m_nHeight * gt[5];
197 : }
198 : }
199 :
200 : // Creating the MMRRel object with all the information of the dataset.
201 126 : m_pMMRRel = std::make_unique<MMRRel>(
202 126 : osRelname, bNeedOfNomFitxer, m_osEPSG, m_nWidth, m_nHeight, m_dfMinX,
203 126 : m_dfMaxX, m_dfMinY, m_dfMaxY, std::move(oBands));
204 :
205 126 : if (!m_pMMRRel->IsValid())
206 0 : return;
207 :
208 : // Lineage is updated with the source dataset information, if any, and with the creation options.
209 126 : m_pMMRRel->UpdateLineage(papszOptions, oSrcDS);
210 :
211 : // Writing all information in files: I.rel, IMG,...
212 126 : if (!m_pMMRRel->Write(oSrcDS))
213 : {
214 3 : m_pMMRRel->SetIsValid(false);
215 3 : return;
216 : }
217 :
218 : // If the dataset is RGB, we write the .mmm file with the RGB information of the bands.
219 123 : WriteRGBMap();
220 :
221 123 : m_bIsValid = true;
222 : }
223 :
224 366 : MMRDataset::MMRDataset(GDALOpenInfo *poOpenInfo)
225 : {
226 366 : m_oSRS.SetAxisMappingStrategy(OAMS_TRADITIONAL_GIS_ORDER);
227 :
228 : // Creating the class MMRRel.
229 366 : auto pMMfRel = std::make_unique<MMRRel>(poOpenInfo->pszFilename, true);
230 366 : if (!pMMfRel->IsValid())
231 : {
232 174 : if (pMMfRel->isAMiraMonFile())
233 : {
234 8 : CPLError(CE_Failure, CPLE_AppDefined,
235 : "Unable to open %s, probably it's not a MiraMon file.",
236 : poOpenInfo->pszFilename);
237 : }
238 174 : return;
239 : }
240 :
241 192 : if (pMMfRel->GetNBands() == 0)
242 : {
243 1 : if (pMMfRel->isAMiraMonFile())
244 : {
245 1 : CPLError(CE_Failure, CPLE_AppDefined,
246 : "Unable to open %s, it has zero usable bands.",
247 : poOpenInfo->pszFilename);
248 : }
249 1 : return;
250 : }
251 :
252 191 : m_pMMRRel = std::move(pMMfRel);
253 :
254 : // General Dataset information available
255 191 : nRasterXSize = m_pMMRRel->GetColumnsNumberFromREL();
256 191 : nRasterYSize = m_pMMRRel->GetRowsNumberFromREL();
257 191 : ReadProjection();
258 191 : nBands = 0;
259 :
260 : // Getting the open option that determines how to expose subdatasets.
261 : // To avoid recusivity subdatasets are exposed as they are.
262 : const char *pszDataType =
263 191 : CSLFetchNameValue(poOpenInfo->papszOpenOptions, "RAT_OR_CT");
264 191 : if (pszDataType != nullptr)
265 : {
266 3 : if (EQUAL(pszDataType, "RAT"))
267 1 : nRatOrCT = RAT_OR_CT::RAT;
268 2 : else if (EQUAL(pszDataType, "ALL"))
269 1 : nRatOrCT = RAT_OR_CT::ALL;
270 1 : else if (EQUAL(pszDataType, "CT"))
271 1 : nRatOrCT = RAT_OR_CT::CT;
272 : }
273 :
274 191 : AssignBandsToSubdataSets();
275 :
276 : // Create subdatasets or add bands, as needed
277 191 : if (m_nNSubdataSets)
278 : {
279 7 : CreateSubdatasetsFromBands();
280 : // Fills adfGeoTransform if documented
281 7 : UpdateGeoTransform();
282 : }
283 : else
284 : {
285 184 : if (!CreateRasterBands())
286 0 : return;
287 :
288 : // GeoTransform of a subdataset is always the same than the first band
289 184 : if (m_pMMRRel->GetNBands() >= 1)
290 : {
291 184 : MMRBand *poBand = m_pMMRRel->GetBand(m_pMMRRel->GetNBands() - 1);
292 184 : if (poBand)
293 184 : m_gt = poBand->m_gt;
294 : }
295 : }
296 :
297 : // Make sure we don't try to do any pam stuff with this dataset.
298 191 : nPamFlags |= GPF_NOSAVE;
299 :
300 : // We have a valid DataSet.
301 191 : m_bIsValid = true;
302 : }
303 :
304 : /************************************************************************/
305 : /* ~MMRDataset() */
306 : /************************************************************************/
307 :
308 996 : MMRDataset::~MMRDataset()
309 :
310 : {
311 996 : }
312 :
313 : /************************************************************************/
314 : /* Identify() */
315 : /************************************************************************/
316 58825 : int MMRDataset::Identify(GDALOpenInfo *poOpenInfo)
317 : {
318 : // Checking for subdataset
319 : int nIdentifyResult =
320 58825 : MMRRel::IdentifySubdataSetFile(poOpenInfo->pszFilename);
321 58825 : if (nIdentifyResult != GDAL_IDENTIFY_FALSE)
322 12 : return nIdentifyResult;
323 :
324 : // Checking for MiraMon raster file
325 58813 : return MMRRel::IdentifyFile(poOpenInfo);
326 : }
327 :
328 : /************************************************************************/
329 : /* Open() */
330 : /************************************************************************/
331 366 : GDALDataset *MMRDataset::Open(GDALOpenInfo *poOpenInfo)
332 :
333 : {
334 : // Verify that this is a MMR file.
335 366 : if (!Identify(poOpenInfo))
336 0 : return nullptr;
337 :
338 : // Confirm the requested access is supported.
339 366 : if (poOpenInfo->eAccess == GA_Update)
340 : {
341 0 : CPLError(CE_Failure, CPLE_NotSupported,
342 : "The MiraMonRaster driver does not support update "
343 : "access to existing datasets.");
344 0 : return nullptr;
345 : }
346 :
347 : // Create the Dataset (with bands or Subdatasets).
348 732 : auto poDS = std::make_unique<MMRDataset>(poOpenInfo);
349 366 : if (!poDS->IsValid())
350 175 : return nullptr;
351 :
352 : // Set description
353 191 : poDS->SetDescription(poOpenInfo->pszFilename);
354 :
355 191 : return poDS.release();
356 : }
357 :
358 : /************************************************************************/
359 : /* CreateCopy() */
360 : /************************************************************************/
361 132 : GDALDataset *MMRDataset::CreateCopy(const char *pszFilename,
362 : GDALDataset *poSrcDS, int /*bStrict*/,
363 : CSLConstList papszOptions,
364 : GDALProgressFunc pfnProgress,
365 : void *pProgressData)
366 :
367 : {
368 : // pszFilename doesn't have extension or must end in "I.rel"
369 264 : const CPLString osFileName(pszFilename);
370 264 : CPLString osRelName = CreateAssociatedMetadataFileName(osFileName);
371 132 : if (osRelName.empty())
372 0 : return nullptr;
373 :
374 : // osPattern is needed to create band names.
375 264 : CPLString osUsrPattern = CSLFetchNameValueDef(papszOptions, "PATTERN", "");
376 264 : CPLString osPattern = CreatePatternFileName(osRelName, osUsrPattern);
377 :
378 132 : if (osPattern.empty())
379 0 : return nullptr;
380 :
381 : auto poDS = std::make_unique<MMRDataset>(pfnProgress, pProgressData,
382 : papszOptions, osRelName, *poSrcDS,
383 264 : osUsrPattern, osPattern);
384 :
385 132 : if (!poDS->IsValid())
386 9 : return nullptr;
387 :
388 123 : poDS->SetDescription(pszFilename);
389 123 : poDS->eAccess = GA_Update;
390 :
391 123 : return poDS.release();
392 : }
393 :
394 184 : bool MMRDataset::CreateRasterBands()
395 : {
396 : MMRBand *pBand;
397 :
398 394 : for (int nIBand = 0; nIBand < m_pMMRRel->GetNBands(); nIBand++)
399 : {
400 : // Establish raster band info.
401 210 : pBand = m_pMMRRel->GetBand(nIBand);
402 210 : if (!pBand)
403 0 : return false;
404 210 : nRasterXSize = pBand->GetWidth();
405 210 : nRasterYSize = pBand->GetHeight();
406 210 : pBand->UpdateGeoTransform(); // Fills adfGeoTransform for this band
407 :
408 210 : auto poRasterBand = std::make_unique<MMRRasterBand>(this, nBands + 1);
409 210 : if (!poRasterBand->IsValid())
410 : {
411 0 : CPLError(CE_Failure, CPLE_AppDefined,
412 : "Failed to create a RasterBand from '%s'",
413 : m_pMMRRel->GetRELNameChar());
414 :
415 0 : return false;
416 : }
417 :
418 210 : SetBand(nBands + 1, std::move(poRasterBand));
419 : }
420 : // Not used metadata in the REL must be preserved just in case to be restored
421 : // if they are preserved through translations.
422 184 : m_pMMRRel->RELToGDALMetadata(this);
423 :
424 184 : return true;
425 : }
426 :
427 191 : void MMRDataset::ReadProjection()
428 :
429 : {
430 191 : if (!m_pMMRRel)
431 0 : return;
432 :
433 382 : CPLString osSRS;
434 382 : if (!m_pMMRRel->GetMetadataValue("SPATIAL_REFERENCE_SYSTEM:HORIZONTAL",
435 573 : "HorizontalSystemIdentifier", osSRS) ||
436 191 : osSRS.empty())
437 0 : return;
438 :
439 : char szResult[MM_MAX_ID_SNY + 10];
440 191 : int nResult = ReturnEPSGCodeSRSFromMMIDSRS(osSRS.c_str(), szResult);
441 191 : if (nResult == 1 || szResult[0] == '\0')
442 24 : return;
443 :
444 : int nEPSG;
445 167 : if (1 == sscanf(szResult, "%d", &nEPSG))
446 167 : m_oSRS.importFromEPSG(nEPSG);
447 :
448 167 : return;
449 : }
450 :
451 131 : void MMRDataset::UpdateProjection(GDALDataset &oSrcDS)
452 : {
453 131 : const OGRSpatialReference *poSRS = oSrcDS.GetSpatialRef();
454 131 : if (poSRS)
455 : {
456 116 : const char *pszTargetKey = nullptr;
457 116 : const char *pszAuthorityName = nullptr;
458 116 : const char *pszAuthorityCode = nullptr;
459 :
460 : // Reading horizontal reference system and horizontal units
461 116 : if (poSRS->IsProjected())
462 97 : pszTargetKey = "PROJCS";
463 19 : else if (poSRS->IsGeographic() || poSRS->IsDerivedGeographic())
464 19 : pszTargetKey = "GEOGCS";
465 0 : else if (poSRS->IsGeocentric())
466 0 : pszTargetKey = "GEOCCS";
467 0 : else if (poSRS->IsLocal())
468 0 : pszTargetKey = "LOCAL_CS";
469 :
470 116 : if (!poSRS->IsLocal())
471 : {
472 116 : pszAuthorityName = poSRS->GetAuthorityName(pszTargetKey);
473 116 : pszAuthorityCode = poSRS->GetAuthorityCode(pszTargetKey);
474 : }
475 :
476 116 : if (pszAuthorityName && pszAuthorityCode &&
477 98 : EQUAL(pszAuthorityName, "EPSG"))
478 : {
479 98 : CPLDebugOnly("MiraMon", "Setting EPSG code %s", pszAuthorityCode);
480 98 : m_osEPSG = pszAuthorityCode;
481 : }
482 : }
483 131 : }
484 :
485 : /************************************************************************/
486 : /* SUBDATASETS */
487 : /************************************************************************/
488 : // Assigns every band to a subdataset
489 191 : void MMRDataset::AssignBandsToSubdataSets()
490 : {
491 191 : m_nNSubdataSets = 0;
492 191 : if (!m_pMMRRel.get())
493 0 : return;
494 :
495 191 : int nIBand = 0;
496 191 : int nIBand2 = 0;
497 :
498 : MMRBand *pBand;
499 : MMRBand *pOtherBand;
500 442 : for (; nIBand < m_pMMRRel->GetNBands(); nIBand++)
501 : {
502 251 : pBand = m_pMMRRel->GetBand(nIBand);
503 251 : if (!pBand)
504 0 : continue;
505 :
506 251 : if (pBand->GetAssignedSubDataSet() != 0)
507 27 : continue;
508 :
509 224 : m_nNSubdataSets++;
510 224 : pBand->AssignSubDataSet(m_nNSubdataSets);
511 :
512 : // Let's put all suitable bands in the same subdataset
513 356 : for (nIBand2 = nIBand + 1; nIBand2 < m_pMMRRel->GetNBands(); nIBand2++)
514 : {
515 132 : pOtherBand = m_pMMRRel->GetBand(nIBand2);
516 132 : if (!pOtherBand)
517 0 : continue;
518 :
519 132 : if (pOtherBand->GetAssignedSubDataSet() != 0)
520 0 : continue;
521 :
522 132 : if (BandInTheSameDataset(nIBand, nIBand2))
523 27 : pOtherBand->AssignSubDataSet(m_nNSubdataSets);
524 : }
525 : }
526 :
527 : // If there is only one subdataset, it means that
528 : // we don't need subdatasets (all assigned to 0)
529 191 : if (m_nNSubdataSets == 1)
530 : {
531 184 : m_nNSubdataSets = 0;
532 394 : for (nIBand = 0; nIBand < m_pMMRRel->GetNBands(); nIBand++)
533 : {
534 210 : pBand = m_pMMRRel->GetBand(nIBand);
535 210 : if (!pBand)
536 0 : break;
537 210 : pBand->AssignSubDataSet(m_nNSubdataSets);
538 : }
539 : }
540 : }
541 :
542 7 : void MMRDataset::CreateSubdatasetsFromBands()
543 : {
544 7 : CPLStringList oSubdatasetList;
545 7 : CPLString osDSName;
546 7 : CPLString osDSDesc;
547 : MMRBand *pBand;
548 :
549 47 : for (int iSubdataset = 1; iSubdataset <= m_nNSubdataSets; iSubdataset++)
550 : {
551 : int nIBand;
552 154 : for (nIBand = 0; nIBand < m_pMMRRel->GetNBands(); nIBand++)
553 : {
554 154 : pBand = m_pMMRRel->GetBand(nIBand);
555 154 : if (!pBand)
556 0 : return;
557 154 : if (pBand->GetAssignedSubDataSet() == iSubdataset)
558 40 : break;
559 : }
560 :
561 40 : if (nIBand == m_pMMRRel->GetNBands())
562 0 : break;
563 :
564 40 : pBand = m_pMMRRel->GetBand(nIBand);
565 40 : if (!pBand)
566 0 : return;
567 :
568 : osDSName.Printf("MiraMonRaster:\"%s\",\"%s\"",
569 40 : pBand->GetRELFileName().c_str(),
570 80 : pBand->GetRawBandFileName().c_str());
571 : osDSDesc.Printf("Subdataset %d: \"%s\"", iSubdataset,
572 40 : pBand->GetBandName().c_str());
573 40 : nIBand++;
574 :
575 146 : for (; nIBand < m_pMMRRel->GetNBands(); nIBand++)
576 : {
577 106 : pBand = m_pMMRRel->GetBand(nIBand);
578 106 : if (!pBand)
579 0 : return;
580 106 : if (pBand->GetAssignedSubDataSet() != iSubdataset)
581 105 : continue;
582 :
583 : osDSName.append(
584 1 : CPLSPrintf(",\"%s\"", pBand->GetRawBandFileName().c_str()));
585 : osDSDesc.append(
586 1 : CPLSPrintf(",\"%s\"", pBand->GetBandName().c_str()));
587 : }
588 :
589 : oSubdatasetList.AddNameValue(
590 40 : CPLSPrintf("SUBDATASET_%d_NAME", iSubdataset), osDSName);
591 : oSubdatasetList.AddNameValue(
592 40 : CPLSPrintf("SUBDATASET_%d_DESC", iSubdataset), osDSDesc);
593 : }
594 :
595 7 : if (oSubdatasetList.Count() > 0)
596 : {
597 : // Add metadata to the main dataset
598 7 : SetMetadata(oSubdatasetList.List(), "SUBDATASETS");
599 7 : oSubdatasetList.Clear();
600 : }
601 : }
602 :
603 : // Checks if two bands should be in the same subdataset
604 132 : bool MMRDataset::BandInTheSameDataset(int nIBand1, int nIBand2) const
605 : {
606 132 : if (nIBand1 < 0 || nIBand2 < 0)
607 0 : return true;
608 :
609 132 : if (nIBand1 >= m_pMMRRel->GetNBands() || nIBand2 >= m_pMMRRel->GetNBands())
610 0 : return true;
611 :
612 132 : MMRBand *pThisBand = m_pMMRRel->GetBand(nIBand1);
613 132 : MMRBand *pOtherBand = m_pMMRRel->GetBand(nIBand2);
614 132 : if (!pThisBand || !pOtherBand)
615 0 : return true;
616 :
617 : // Two images with different numbers of columns are assigned to different subdatasets
618 132 : if (pThisBand->GetWidth() != pOtherBand->GetWidth())
619 9 : return false;
620 :
621 : // Two images with different numbers of rows are assigned to different subdatasets
622 123 : if (pThisBand->GetHeight() != pOtherBand->GetHeight())
623 0 : return false;
624 :
625 : // Two images with different data type are assigned to different subdatasets
626 123 : if (pThisBand->GeteMMNCDataType() != pOtherBand->GeteMMNCDataType())
627 32 : return false;
628 :
629 : // Two images with different bounding box are assigned to different subdatasets
630 91 : if (pThisBand->GetBoundingBoxMinX() != pOtherBand->GetBoundingBoxMinX())
631 7 : return false;
632 84 : if (pThisBand->GetBoundingBoxMaxX() != pOtherBand->GetBoundingBoxMaxX())
633 6 : return false;
634 78 : if (pThisBand->GetBoundingBoxMinY() != pOtherBand->GetBoundingBoxMinY())
635 0 : return false;
636 78 : if (pThisBand->GetBoundingBoxMaxY() != pOtherBand->GetBoundingBoxMaxY())
637 0 : return false;
638 :
639 : // Two images with different simbolization are assigned to different subdatasets
640 78 : if (!EQUAL(pThisBand->GetColor_Const(), pOtherBand->GetColor_Const()))
641 0 : return false;
642 78 : if (pThisBand->GetConstantColorRGB().c1 !=
643 78 : pOtherBand->GetConstantColorRGB().c1)
644 0 : return false;
645 78 : if (pThisBand->GetConstantColorRGB().c2 !=
646 78 : pOtherBand->GetConstantColorRGB().c2)
647 0 : return false;
648 78 : if (pThisBand->GetConstantColorRGB().c3 !=
649 78 : pOtherBand->GetConstantColorRGB().c3)
650 0 : return false;
651 78 : if (!EQUAL(pThisBand->GetColor_Paleta(), pOtherBand->GetColor_Paleta()))
652 26 : return false;
653 52 : if (!EQUAL(pThisBand->GetColor_TractamentVariable(),
654 : pOtherBand->GetColor_TractamentVariable()))
655 0 : return false;
656 52 : if (!EQUAL(pThisBand->GetTractamentVariable(),
657 : pOtherBand->GetTractamentVariable()))
658 3 : return false;
659 49 : if (!EQUAL(pThisBand->GetColor_EscalatColor(),
660 : pOtherBand->GetColor_EscalatColor()))
661 0 : return false;
662 49 : if (!EQUAL(pThisBand->GetColor_N_SimbolsALaTaula(),
663 : pOtherBand->GetColor_N_SimbolsALaTaula()))
664 0 : return false;
665 49 : if (pThisBand->IsCategorical() != pOtherBand->IsCategorical())
666 0 : return false;
667 49 : if (pThisBand->IsCategorical())
668 : {
669 19 : if (pThisBand->GetMaxSet() != pOtherBand->GetMaxSet())
670 0 : return false;
671 19 : if (pThisBand->GetMaxSet())
672 : {
673 19 : if (pThisBand->GetMax() != pOtherBand->GetMax())
674 12 : return false;
675 : }
676 : }
677 :
678 : // Two images with different RATs are assigned to different subdatasets
679 67 : if (!EQUAL(pThisBand->GetShortRATName(), pOtherBand->GetShortRATName()) ||
680 30 : !EQUAL(pThisBand->GetAssociateREL(), pOtherBand->GetAssociateREL()))
681 7 : return false;
682 :
683 : // One image has NoData values and the other does not;
684 : // they are assigned to different subdatasets
685 30 : if (pThisBand->BandHasNoData() != pOtherBand->BandHasNoData())
686 2 : return false;
687 :
688 : // Two images with different NoData values are assigned to different subdatasets
689 28 : if (pThisBand->GetNoDataValue() != pOtherBand->GetNoDataValue())
690 1 : return false;
691 :
692 27 : return true;
693 : }
694 :
695 : /************************************************************************/
696 : /* UpdateGeoTransform() */
697 : /************************************************************************/
698 7 : int MMRDataset::UpdateGeoTransform()
699 : {
700 : // Bounding box of the band
701 : // Section [EXTENT] in rel file
702 :
703 7 : if (!m_pMMRRel)
704 0 : return 1;
705 :
706 14 : CPLString osMinX;
707 14 : if (!m_pMMRRel->GetMetadataValue(SECTION_EXTENT, "MinX", osMinX) ||
708 7 : osMinX.empty())
709 0 : return 1;
710 :
711 7 : if (1 != CPLsscanf(osMinX, "%lf", &(m_gt.xorig)))
712 0 : m_gt.xorig = 0.0;
713 :
714 7 : int nNCols = m_pMMRRel->GetColumnsNumberFromREL();
715 7 : if (nNCols <= 0)
716 0 : return 1;
717 :
718 14 : CPLString osMaxX;
719 14 : if (!m_pMMRRel->GetMetadataValue(SECTION_EXTENT, "MaxX", osMaxX) ||
720 7 : osMaxX.empty())
721 0 : return 1;
722 :
723 : double dfMaxX;
724 7 : if (1 != CPLsscanf(osMaxX, "%lf", &dfMaxX))
725 0 : dfMaxX = 1.0;
726 :
727 7 : m_gt.xscale = (dfMaxX - m_gt.xorig) / nNCols;
728 7 : m_gt.xrot = 0.0; // No rotation in MiraMon rasters
729 :
730 14 : CPLString osMinY;
731 14 : if (!m_pMMRRel->GetMetadataValue(SECTION_EXTENT, "MinY", osMinY) ||
732 7 : osMinY.empty())
733 0 : return 1;
734 :
735 14 : CPLString osMaxY;
736 14 : if (!m_pMMRRel->GetMetadataValue(SECTION_EXTENT, "MaxY", osMaxY) ||
737 7 : osMaxY.empty())
738 0 : return 1;
739 :
740 7 : int nNRows = m_pMMRRel->GetRowsNumberFromREL();
741 7 : if (nNRows <= 0)
742 0 : return 1;
743 :
744 : double dfMaxY;
745 7 : if (1 != CPLsscanf(osMaxY, "%lf", &dfMaxY))
746 0 : dfMaxY = 1.0;
747 :
748 7 : m_gt.yorig = dfMaxY;
749 7 : m_gt.yrot = 0.0;
750 :
751 : double dfMinY;
752 7 : if (1 != CPLsscanf(osMinY, "%lf", &dfMinY))
753 0 : dfMinY = 0.0;
754 7 : m_gt.yscale = (dfMinY - m_gt.yorig) / nNRows;
755 :
756 7 : return 0;
757 : }
758 :
759 117 : const OGRSpatialReference *MMRDataset::GetSpatialRef() const
760 : {
761 117 : return m_oSRS.IsEmpty() ? nullptr : &m_oSRS;
762 : }
763 :
764 133 : CPLErr MMRDataset::GetGeoTransform(GDALGeoTransform >) const
765 : {
766 133 : if (m_gt.xorig != 0.0 || m_gt.xscale != 1.0 || m_gt.xrot != 0.0 ||
767 3 : m_gt.yorig != 0.0 || m_gt.yrot != 0.0 || m_gt.yscale != 1.0)
768 : {
769 133 : gt = m_gt;
770 133 : return CE_None;
771 : }
772 :
773 0 : return GDALDataset::GetGeoTransform(gt);
774 : }
775 :
776 : /************************************************************************/
777 : /* REL/IMG names */
778 : /************************************************************************/
779 :
780 : // Finds the metadata filename associated to osFileName (usually an IMG file)
781 : CPLString
782 132 : MMRDataset::CreateAssociatedMetadataFileName(const CPLString &osFileName)
783 : {
784 132 : if (osFileName.empty())
785 : {
786 0 : CPLError(CE_Failure, CPLE_OpenFailed, "Expected output file name.");
787 0 : return "";
788 : }
789 :
790 264 : CPLString osRELName = osFileName;
791 :
792 : // If the string finishes in "I.rel" we consider it can be
793 : // the associated file to all bands that are documented in this file.
794 132 : if (cpl::ends_with(osFileName, pszExtRasterREL))
795 112 : return osRELName;
796 :
797 : // If the string finishes in ".img" or ".rel" (and not "I.rel")
798 : // we consider it can converted to "I.rel"
799 40 : if (cpl::ends_with(osFileName, pszExtRaster) ||
800 20 : cpl::ends_with(osFileName, pszExtREL))
801 : {
802 : // Extract extension
803 0 : osRELName = CPLResetExtensionSafe(osRELName, "");
804 :
805 0 : if (!osRELName.length())
806 0 : return "";
807 :
808 : // Extract "."
809 0 : osRELName.resize(osRELName.size() - 1);
810 :
811 0 : if (!osRELName.length())
812 0 : return "";
813 :
814 : // Add "I.rel"
815 0 : osRELName += pszExtRasterREL;
816 0 : return osRELName;
817 : }
818 :
819 : // If the file is not a REL file, let's assume that "I.rel" can be added
820 : // to get the REL file.
821 20 : osRELName += pszExtRasterREL;
822 20 : return osRELName;
823 : }
824 :
825 : // Finds the pattern name to the bands
826 132 : CPLString MMRDataset::CreatePatternFileName(const CPLString &osFileName,
827 : const CPLString &osPattern)
828 : {
829 132 : if (!osPattern.empty())
830 48 : return osPattern;
831 :
832 168 : CPLString osRELName = osFileName;
833 :
834 84 : if (!cpl::ends_with(osFileName, pszExtRasterREL))
835 0 : return "";
836 :
837 : // Extract I.rel and path
838 84 : osRELName.resize(osRELName.size() - strlen("I.rel"));
839 168 : return CPLGetBasenameSafe(osRELName);
840 : }
841 :
842 : // Checks if the band is in the list of categorical or continuous bands
843 : // specified by the user in the creation options.
844 334 : bool MMRDataset::BandInOptionsList(CSLConstList papszOptions,
845 : const CPLString &pszType,
846 : const CPLString &osIndexBand)
847 : {
848 334 : if (!papszOptions)
849 66 : return false;
850 :
851 268 : if (const char *pszCategoricalList =
852 268 : CSLFetchNameValue(papszOptions, pszType))
853 : {
854 : const CPLStringList aosTokens(
855 1 : CSLTokenizeString2(pszCategoricalList, ",", 0));
856 :
857 1 : for (int i = 0; i < aosTokens.size(); ++i)
858 : {
859 1 : if (EQUAL(aosTokens[i], osIndexBand))
860 1 : return true;
861 : }
862 : }
863 267 : return false;
864 : }
865 :
866 : // MiraMon needs to know if the band is categorical or continuous to apply the right default symbology.
867 167 : bool MMRDataset::IsCategoricalBand(GDALDataset &oSrcDS,
868 : GDALRasterBand &pRasterBand,
869 : CSLConstList papszOptions,
870 : const CPLString &osIndexBand)
871 : {
872 : bool bUsrCategorical =
873 167 : BandInOptionsList(papszOptions, "CATEGORICAL_BANDS", osIndexBand);
874 : bool bUsrContinuous =
875 167 : BandInOptionsList(papszOptions, "CONTINUOUS_BANDS", osIndexBand);
876 :
877 167 : if (!bUsrCategorical && !bUsrContinuous)
878 : {
879 : // In case user doesn't specify anything, we try to deduce if the band is categorical or continuous
880 : // First we try to see if there is metadata in the source dataset that can help us. We look for a key like
881 : // "ATTRIBUTE_DATA$$$TractamentVariable" in the domain "MIRAMON"
882 166 : CPLStringList aosMiraMonMetaData(oSrcDS.GetMetadata(MetadataDomain));
883 166 : if (!aosMiraMonMetaData.empty())
884 : {
885 : CPLString osClue = CPLSPrintf("ATTRIBUTE_DATA%sTractamentVariable",
886 1 : SecKeySeparator);
887 : CPLString osTractamentVariable =
888 1 : CSLFetchNameValueDef(aosMiraMonMetaData, osClue, "");
889 1 : if (!osTractamentVariable.empty())
890 : {
891 0 : if (EQUAL(osTractamentVariable, "Categorical"))
892 0 : return true;
893 0 : return false;
894 : }
895 : }
896 :
897 : // In case of no metadata, we try to deduce if the band is categorical or continuous with some heuristics:
898 166 : if (pRasterBand.GetCategoryNames() != nullptr)
899 0 : return true;
900 :
901 : // In case of floating point data, we consider that the band is continuous.
902 309 : if (pRasterBand.GetRasterDataType() == GDT_Float32 ||
903 143 : pRasterBand.GetRasterDataType() == GDT_Float64)
904 46 : return false;
905 :
906 : // In case of 8 bit integer with a color table, we consider that the band is categorical.
907 120 : if ((pRasterBand.GetRasterDataType() == GDT_UInt8 ||
908 165 : pRasterBand.GetRasterDataType() == GDT_Int8) &&
909 45 : pRasterBand.GetColorTable() != nullptr)
910 12 : return true;
911 :
912 : // In case of the band has a RAT, we consider that the band is categorical.
913 : // This is a heuristic that can be wrong in some cases, but in general if
914 : // a band has a RAT it's because it has a limited number of values and they are categorical.
915 108 : if (pRasterBand.GetDefaultRAT() != nullptr)
916 108 : return true;
917 : }
918 1 : else if (bUsrCategorical && bUsrContinuous)
919 : {
920 : // User cannot impose both categorical and continuous treatment
921 0 : CPLError(CE_Failure, CPLE_AppDefined, "%s",
922 : "Unable to interpret band as Categorical and Continuous at "
923 : "the same time. Categorical treatment will be used.");
924 :
925 0 : return true;
926 : }
927 1 : else if (bUsrCategorical)
928 1 : return true;
929 :
930 94 : return false;
931 : }
932 :
933 : // In the RGB case, a map (.mmm) is generated with the RGB information of the bands.
934 : // This allows to visualize the RGB composition in MiraMon without having to create
935 : // a map in MiraMon and set the RGB information.
936 123 : void MMRDataset::WriteRGBMap()
937 : {
938 123 : if (m_nIBandR == -1 || m_nIBandG == -1 || m_nIBandB == -1)
939 122 : return;
940 :
941 1 : CPLString osMapNameAux = m_pMMRRel->GetRELName();
942 : CPLString osMapNameAux2 =
943 2 : m_pMMRRel->MMRGetFileNameFromRelName(osMapNameAux, ".mmm");
944 :
945 1 : auto pMMMap = std::make_unique<MMRRel>(osMapNameAux2);
946 1 : if (!pMMMap->OpenRELFile("wb"))
947 0 : return;
948 :
949 1 : pMMMap->AddSectionStart(SECTION_VERSIO);
950 1 : pMMMap->AddKeyValue(KEY_Vers, "2");
951 1 : pMMMap->AddKeyValue(KEY_SubVers, "0");
952 1 : pMMMap->AddKeyValue("variant", "b");
953 1 : pMMMap->AddSectionEnd();
954 :
955 1 : pMMMap->AddSectionStart("DOCUMENT");
956 1 : pMMMap->AddKeyValue("Titol", CPLGetBasenameSafe(osMapNameAux2));
957 1 : pMMMap->AddSectionEnd();
958 :
959 1 : pMMMap->AddSectionStart("VISTA");
960 1 : pMMMap->AddKeyValue("ordre", "RASTER_RGB_1");
961 1 : pMMMap->AddSectionEnd();
962 :
963 1 : pMMMap->AddSectionStart("RASTER_RGB_1");
964 1 : auto poRedBand = m_pMMRRel->GetBand(m_nIBandR);
965 1 : auto poGreenBand = m_pMMRRel->GetBand(m_nIBandG);
966 1 : auto poBlueBand = m_pMMRRel->GetBand(m_nIBandB);
967 1 : assert(poRedBand);
968 1 : assert(poGreenBand);
969 1 : assert(poBlueBand);
970 2 : pMMMap->AddKeyValue("FitxerR",
971 1 : CPLGetFilename(poRedBand->GetRawBandFileName()));
972 2 : pMMMap->AddKeyValue("FitxerG",
973 1 : CPLGetFilename(poGreenBand->GetRawBandFileName()));
974 2 : pMMMap->AddKeyValue("FitxerB",
975 1 : CPLGetFilename(poBlueBand->GetRawBandFileName()));
976 1 : pMMMap->AddKeyValue("UnificVisCons", "1");
977 1 : pMMMap->AddKeyValue("visualitzable", "1");
978 1 : pMMMap->AddKeyValue("consultable", "1");
979 1 : pMMMap->AddKeyValue("EscalaMaxima", "0");
980 1 : pMMMap->AddKeyValue("EscalaMinima", "900000000");
981 1 : pMMMap->AddKeyValue("LlegSimb_Vers", "4");
982 1 : pMMMap->AddKeyValue("LlegSimb_SubVers", "5");
983 1 : pMMMap->AddKeyValue("Color_VisibleALleg", "1");
984 2 : CPLString osLlegTitle = "RGB:";
985 1 : osLlegTitle.append(CPLGetFilename(poRedBand->GetBandName()));
986 1 : osLlegTitle.append("+");
987 1 : osLlegTitle.append(CPLGetFilename(poGreenBand->GetBandName()));
988 1 : osLlegTitle.append("+");
989 1 : osLlegTitle.append(CPLGetFilename(poBlueBand->GetBandName()));
990 1 : pMMMap->AddKeyValue("Color_TitolLlegenda", osLlegTitle);
991 1 : pMMMap->AddSectionEnd();
992 : }
|