LCOV - code coverage report
Current view: top level - frmts/miramon - miramon_dataset.cpp (source / functions) Hit Total Coverage
Test: gdal_filtered.info Lines: 416 491 84.7 %
Date: 2026-03-05 10:33:42 Functions: 22 22 100.0 %

          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 &gt) 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             : }

Generated by: LCOV version 1.14