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 :
16 : #include "miramon_dataset.h"
17 : #include "miramon_rasterband.h"
18 : #include "miramon_band.h" // Per a MMRBand
19 :
20 : #include "gdal_frmts.h"
21 :
22 : #include "../miramon_common/mm_gdal_functions.h" // For MMCheck_REL_FILE()
23 :
24 : /************************************************************************/
25 : /* GDALRegister_MiraMon() */
26 : /************************************************************************/
27 2059 : void GDALRegister_MiraMon()
28 :
29 : {
30 2059 : if (GDALGetDriverByName("MiraMonRaster") != nullptr)
31 283 : return;
32 :
33 1776 : GDALDriver *poDriver = new GDALDriver();
34 :
35 1776 : poDriver->SetDescription("MiraMonRaster");
36 1776 : poDriver->SetMetadataItem(GDAL_DCAP_RASTER, "YES");
37 1776 : poDriver->SetMetadataItem(GDAL_DMD_LONGNAME, "MiraMon Raster Images");
38 1776 : poDriver->SetMetadataItem(GDAL_DMD_HELPTOPIC,
39 1776 : "drivers/raster/miramon.html");
40 1776 : poDriver->SetMetadataItem(GDAL_DMD_EXTENSIONS, "rel img");
41 :
42 1776 : poDriver->SetMetadataItem(GDAL_DCAP_VIRTUALIO, "YES");
43 1776 : poDriver->SetMetadataItem(GDAL_DMD_SUBDATASETS, "YES");
44 :
45 1776 : poDriver->SetMetadataItem(GDAL_DCAP_OPEN, "YES");
46 :
47 1776 : poDriver->SetMetadataItem(
48 : GDAL_DMD_OPENOPTIONLIST,
49 : "<OpenOptionList>\n"
50 : " <Option name='RAT_OR_CT' type='string-select' "
51 : "description='Controls whether the Raster Attribute Table (RAT) "
52 : "and/or the Color Table (CT) are exposed.' default='ALL'>\n"
53 : " <Value>ALL</Value>\n"
54 : " <Value>RAT</Value>\n"
55 : " <Value>CT</Value>\n"
56 : " </Option>\n"
57 1776 : "</OpenOptionList>\n");
58 :
59 1776 : poDriver->pfnOpen = MMRDataset::Open;
60 1776 : poDriver->pfnIdentify = MMRDataset::Identify;
61 :
62 1776 : GetGDALDriverManager()->RegisterDriver(poDriver);
63 : }
64 :
65 : /************************************************************************/
66 : /* MMRDataset() */
67 : /************************************************************************/
68 :
69 243 : MMRDataset::MMRDataset(GDALOpenInfo *poOpenInfo)
70 : {
71 243 : m_oSRS.SetAxisMappingStrategy(OAMS_TRADITIONAL_GIS_ORDER);
72 :
73 : // Creating the class MMRRel.
74 243 : auto pMMfRel = std::make_unique<MMRRel>(poOpenInfo->pszFilename, true);
75 243 : if (!pMMfRel->IsValid())
76 : {
77 174 : if (pMMfRel->isAMiraMonFile())
78 : {
79 8 : CPLError(CE_Failure, CPLE_AppDefined,
80 : "Unable to open %s, probably it's not a MiraMon file.",
81 : poOpenInfo->pszFilename);
82 : }
83 174 : return;
84 : }
85 :
86 69 : if (pMMfRel->GetNBands() == 0)
87 : {
88 1 : if (pMMfRel->isAMiraMonFile())
89 : {
90 1 : CPLError(CE_Failure, CPLE_AppDefined,
91 : "Unable to open %s, it has zero usable bands.",
92 : poOpenInfo->pszFilename);
93 : }
94 1 : return;
95 : }
96 :
97 68 : m_pMMRRel = std::move(pMMfRel);
98 :
99 : // General Dataset information available
100 68 : nRasterXSize = m_pMMRRel->GetColumnsNumberFromREL();
101 68 : nRasterYSize = m_pMMRRel->GetRowsNumberFromREL();
102 68 : ReadProjection();
103 68 : nBands = 0;
104 :
105 : // Getting the open option that determines how to expose subdatasets.
106 : // To avoid recusivity subdatasets are exposed as they are.
107 : const char *pszDataType =
108 68 : CSLFetchNameValue(poOpenInfo->papszOpenOptions, "RAT_OR_CT");
109 68 : if (pszDataType != nullptr)
110 : {
111 0 : if (EQUAL(pszDataType, "RAT"))
112 0 : nRatOrCT = RAT_OR_CT::RAT;
113 0 : else if (EQUAL(pszDataType, "ALL"))
114 0 : nRatOrCT = RAT_OR_CT::ALL;
115 0 : else if (EQUAL(pszDataType, "CT"))
116 0 : nRatOrCT = RAT_OR_CT::CT;
117 : }
118 :
119 68 : AssignBandsToSubdataSets();
120 :
121 : // Create subdatasets or add bands, as needed
122 68 : if (m_nNSubdataSets)
123 : {
124 7 : CreateSubdatasetsFromBands();
125 : // Fills adfGeoTransform if documented
126 7 : UpdateGeoTransform();
127 : }
128 : else
129 : {
130 61 : if (!CreateRasterBands())
131 0 : return;
132 :
133 : // GeoTransform of a subdataset is always the same than the first band
134 61 : if (m_pMMRRel->GetNBands() >= 1)
135 : {
136 61 : MMRBand *poBand = m_pMMRRel->GetBand(m_pMMRRel->GetNBands() - 1);
137 61 : if (poBand)
138 61 : m_gt = poBand->m_gt;
139 : }
140 : }
141 :
142 : // Make sure we don't try to do any pam stuff with this dataset.
143 68 : nPamFlags |= GPF_NOSAVE;
144 :
145 : // We have a valid DataSet.
146 68 : m_bIsValid = true;
147 : }
148 :
149 : /************************************************************************/
150 : /* ~MMRDataset() */
151 : /************************************************************************/
152 :
153 486 : MMRDataset::~MMRDataset()
154 :
155 : {
156 486 : }
157 :
158 : /************************************************************************/
159 : /* Identify() */
160 : /************************************************************************/
161 57983 : int MMRDataset::Identify(GDALOpenInfo *poOpenInfo)
162 : {
163 : // Checking for subdataset
164 : int nIdentifyResult =
165 57983 : MMRRel::IdentifySubdataSetFile(poOpenInfo->pszFilename);
166 57983 : if (nIdentifyResult != GDAL_IDENTIFY_FALSE)
167 12 : return nIdentifyResult;
168 :
169 : // Checking for MiraMon raster file
170 57971 : return MMRRel::IdentifyFile(poOpenInfo);
171 : }
172 :
173 : /************************************************************************/
174 : /* Open() */
175 : /************************************************************************/
176 243 : GDALDataset *MMRDataset::Open(GDALOpenInfo *poOpenInfo)
177 :
178 : {
179 : // Verify that this is a MMR file.
180 243 : if (!Identify(poOpenInfo))
181 0 : return nullptr;
182 :
183 : // Confirm the requested access is supported.
184 243 : if (poOpenInfo->eAccess == GA_Update)
185 : {
186 0 : CPLError(CE_Failure, CPLE_NotSupported,
187 : "The MiraMonRaster driver does not support update "
188 : "access to existing datasets.");
189 0 : return nullptr;
190 : }
191 :
192 : // Create the Dataset (with bands or Subdatasets).
193 486 : auto poDS = std::make_unique<MMRDataset>(poOpenInfo);
194 243 : if (!poDS->IsValid())
195 175 : return nullptr;
196 :
197 : // Set description
198 68 : poDS->SetDescription(poOpenInfo->pszFilename);
199 :
200 68 : return poDS.release();
201 : }
202 :
203 61 : bool MMRDataset::CreateRasterBands()
204 : {
205 : MMRBand *pBand;
206 :
207 122 : for (int nIBand = 0; nIBand < m_pMMRRel->GetNBands(); nIBand++)
208 : {
209 : // Establish raster band info.
210 61 : pBand = m_pMMRRel->GetBand(nIBand);
211 61 : if (!pBand)
212 0 : return false;
213 61 : nRasterXSize = pBand->GetWidth();
214 61 : nRasterYSize = pBand->GetHeight();
215 61 : pBand->UpdateGeoTransform(); // Fills adfGeoTransform for this band
216 :
217 61 : auto poRasterBand = std::make_unique<MMRRasterBand>(this, nBands + 1);
218 61 : if (!poRasterBand->IsValid())
219 : {
220 0 : CPLError(CE_Failure, CPLE_AppDefined,
221 : "Failed to create a RasterBand from '%s'",
222 : m_pMMRRel->GetRELNameChar());
223 :
224 0 : return false;
225 : }
226 :
227 61 : SetBand(nBands + 1, std::move(poRasterBand));
228 :
229 : MMRRasterBand *poBand =
230 61 : cpl::down_cast<MMRRasterBand *>(GetRasterBand(nIBand + 1));
231 :
232 61 : pBand = m_pMMRRel->GetBand(nIBand);
233 61 : if (!pBand)
234 0 : return false;
235 61 : if (!pBand->GetFriendlyDescription().empty())
236 : {
237 51 : poBand->SetMetadataItem("DESCRIPTION",
238 51 : pBand->GetFriendlyDescription());
239 : }
240 : }
241 : // Some metadata items must be preserved just in case to be restored
242 : // if they are preserved through translations.
243 61 : m_pMMRRel->RELToGDALMetadata(this);
244 :
245 61 : return true;
246 : }
247 :
248 68 : void MMRDataset::ReadProjection()
249 :
250 : {
251 68 : if (!m_pMMRRel)
252 0 : return;
253 :
254 136 : CPLString osSRS;
255 :
256 136 : if (!m_pMMRRel->GetMetadataValue("SPATIAL_REFERENCE_SYSTEM:HORIZONTAL",
257 204 : "HorizontalSystemIdentifier", osSRS) ||
258 68 : osSRS.empty())
259 0 : return;
260 :
261 : char szResult[MM_MAX_ID_SNY + 10];
262 68 : int nResult = ReturnEPSGCodeSRSFromMMIDSRS(osSRS.c_str(), szResult);
263 68 : if (nResult == 1 || szResult[0] == '\0')
264 10 : return;
265 :
266 : int nEPSG;
267 58 : if (1 == sscanf(szResult, "%d", &nEPSG))
268 58 : m_oSRS.importFromEPSG(nEPSG);
269 :
270 58 : return;
271 : }
272 :
273 : /************************************************************************/
274 : /* SUBDATASETS */
275 : /************************************************************************/
276 : // Assigns every band to a subdataset
277 68 : void MMRDataset::AssignBandsToSubdataSets()
278 : {
279 68 : m_nNSubdataSets = 0;
280 68 : if (!m_pMMRRel.get())
281 0 : return;
282 :
283 68 : int nIBand = 0;
284 68 : int nIBand2 = 0;
285 :
286 : MMRBand *pBand;
287 : MMRBand *pOtherBand;
288 170 : for (; nIBand < m_pMMRRel->GetNBands(); nIBand++)
289 : {
290 102 : pBand = m_pMMRRel->GetBand(nIBand);
291 102 : if (!pBand)
292 0 : continue;
293 :
294 102 : if (pBand->GetAssignedSubDataSet() != 0)
295 1 : continue;
296 :
297 101 : m_nNSubdataSets++;
298 101 : pBand->AssignSubDataSet(m_nNSubdataSets);
299 :
300 : // Let's put all suitable bands in the same subdataset
301 207 : for (nIBand2 = nIBand + 1; nIBand2 < m_pMMRRel->GetNBands(); nIBand2++)
302 : {
303 106 : pOtherBand = m_pMMRRel->GetBand(nIBand2);
304 106 : if (!pOtherBand)
305 0 : continue;
306 :
307 106 : if (pOtherBand->GetAssignedSubDataSet() != 0)
308 0 : continue;
309 :
310 106 : if (BandInTheSameDataset(nIBand, nIBand2))
311 1 : pOtherBand->AssignSubDataSet(m_nNSubdataSets);
312 : }
313 : }
314 :
315 : // If there is only one subdataset, it means that
316 : // we don't need subdatasets (all assigned to 0)
317 68 : if (m_nNSubdataSets == 1)
318 : {
319 61 : m_nNSubdataSets = 0;
320 122 : for (nIBand = 0; nIBand < m_pMMRRel->GetNBands(); nIBand++)
321 : {
322 61 : pBand = m_pMMRRel->GetBand(nIBand);
323 61 : if (!pBand)
324 0 : break;
325 61 : pBand->AssignSubDataSet(m_nNSubdataSets);
326 : }
327 : }
328 : }
329 :
330 7 : void MMRDataset::CreateSubdatasetsFromBands()
331 : {
332 7 : CPLStringList oSubdatasetList;
333 7 : CPLString osDSName;
334 7 : CPLString osDSDesc;
335 : MMRBand *pBand;
336 :
337 47 : for (int iSubdataset = 1; iSubdataset <= m_nNSubdataSets; iSubdataset++)
338 : {
339 : int nIBand;
340 154 : for (nIBand = 0; nIBand < m_pMMRRel->GetNBands(); nIBand++)
341 : {
342 154 : pBand = m_pMMRRel->GetBand(nIBand);
343 154 : if (!pBand)
344 0 : return;
345 154 : if (pBand->GetAssignedSubDataSet() == iSubdataset)
346 40 : break;
347 : }
348 :
349 40 : if (nIBand == m_pMMRRel->GetNBands())
350 0 : break;
351 :
352 40 : pBand = m_pMMRRel->GetBand(nIBand);
353 40 : if (!pBand)
354 0 : return;
355 :
356 : osDSName.Printf("MiraMonRaster:\"%s\",\"%s\"",
357 80 : pBand->GetRELFileName().c_str(),
358 80 : pBand->GetRawBandFileName().c_str());
359 : osDSDesc.Printf("Subdataset %d: \"%s\"", iSubdataset,
360 40 : pBand->GetBandName().c_str());
361 40 : nIBand++;
362 :
363 146 : for (; nIBand < m_pMMRRel->GetNBands(); nIBand++)
364 : {
365 106 : pBand = m_pMMRRel->GetBand(nIBand);
366 106 : if (!pBand)
367 0 : return;
368 106 : if (pBand->GetAssignedSubDataSet() != iSubdataset)
369 105 : continue;
370 :
371 : osDSName.append(
372 1 : CPLSPrintf(",\"%s\"", pBand->GetRawBandFileName().c_str()));
373 : osDSDesc.append(
374 1 : CPLSPrintf(",\"%s\"", pBand->GetBandName().c_str()));
375 : }
376 :
377 : oSubdatasetList.AddNameValue(
378 40 : CPLSPrintf("SUBDATASET_%d_NAME", iSubdataset), osDSName);
379 : oSubdatasetList.AddNameValue(
380 40 : CPLSPrintf("SUBDATASET_%d_DESC", iSubdataset), osDSDesc);
381 : }
382 :
383 7 : if (oSubdatasetList.Count() > 0)
384 : {
385 : // Add metadata to the main dataset
386 7 : SetMetadata(oSubdatasetList.List(), "SUBDATASETS");
387 7 : oSubdatasetList.Clear();
388 : }
389 : }
390 :
391 106 : bool MMRDataset::BandInTheSameDataset(int nIBand1, int nIBand2) const
392 : {
393 106 : if (nIBand1 < 0 || nIBand2 < 0)
394 0 : return true;
395 :
396 106 : if (nIBand1 >= m_pMMRRel->GetNBands() || nIBand2 >= m_pMMRRel->GetNBands())
397 0 : return true;
398 :
399 106 : MMRBand *pThisBand = m_pMMRRel->GetBand(nIBand1);
400 106 : MMRBand *pOtherBand = m_pMMRRel->GetBand(nIBand2);
401 106 : if (!pThisBand || !pOtherBand)
402 0 : return true;
403 :
404 : // Two images with different numbers of columns are assigned to different subdatasets
405 106 : if (pThisBand->GetWidth() != pOtherBand->GetWidth())
406 9 : return false;
407 :
408 : // Two images with different numbers of rows are assigned to different subdatasets
409 97 : if (pThisBand->GetHeight() != pOtherBand->GetHeight())
410 0 : return false;
411 :
412 : // Two images with different data type are assigned to different subdatasets
413 97 : if (pThisBand->GeteMMNCDataType() != pOtherBand->GeteMMNCDataType())
414 32 : return false;
415 :
416 : // Two images with different bounding box are assigned to different subdatasets
417 65 : if (pThisBand->GetBoundingBoxMinX() != pOtherBand->GetBoundingBoxMinX())
418 7 : return false;
419 58 : if (pThisBand->GetBoundingBoxMaxX() != pOtherBand->GetBoundingBoxMaxX())
420 6 : return false;
421 52 : if (pThisBand->GetBoundingBoxMinY() != pOtherBand->GetBoundingBoxMinY())
422 0 : return false;
423 52 : if (pThisBand->GetBoundingBoxMaxY() != pOtherBand->GetBoundingBoxMaxY())
424 0 : return false;
425 :
426 : // Two images with different simbolization are assigned to different subdatasets
427 52 : if (!EQUAL(pThisBand->GetColor_Const(), pOtherBand->GetColor_Const()))
428 0 : return false;
429 52 : if (pThisBand->GetConstantColorRGB().c1 !=
430 52 : pOtherBand->GetConstantColorRGB().c1)
431 0 : return false;
432 52 : if (pThisBand->GetConstantColorRGB().c2 !=
433 52 : pOtherBand->GetConstantColorRGB().c2)
434 0 : return false;
435 52 : if (pThisBand->GetConstantColorRGB().c3 !=
436 52 : pOtherBand->GetConstantColorRGB().c3)
437 0 : return false;
438 52 : if (!EQUAL(pThisBand->GetColor_Paleta(), pOtherBand->GetColor_Paleta()))
439 26 : return false;
440 26 : if (!EQUAL(pThisBand->GetColor_TractamentVariable(),
441 : pOtherBand->GetColor_TractamentVariable()))
442 0 : return false;
443 26 : if (!EQUAL(pThisBand->GetTractamentVariable(),
444 : pOtherBand->GetTractamentVariable()))
445 3 : return false;
446 23 : if (!EQUAL(pThisBand->GetColor_EscalatColor(),
447 : pOtherBand->GetColor_EscalatColor()))
448 0 : return false;
449 23 : if (!EQUAL(pThisBand->GetColor_N_SimbolsALaTaula(),
450 : pOtherBand->GetColor_N_SimbolsALaTaula()))
451 0 : return false;
452 23 : if (pThisBand->IsCategorical() != pOtherBand->IsCategorical())
453 0 : return false;
454 23 : if (pThisBand->IsCategorical())
455 : {
456 19 : if (pThisBand->GetMaxSet() != pOtherBand->GetMaxSet())
457 0 : return false;
458 19 : if (pThisBand->GetMaxSet())
459 : {
460 19 : if (pThisBand->GetMax() != pOtherBand->GetMax())
461 12 : return false;
462 : }
463 : }
464 :
465 : // Two images with different RATs are assigned to different subdatasets
466 26 : if (!EQUAL(pThisBand->GetShortRATName(), pOtherBand->GetShortRATName()) ||
467 15 : !EQUAL(pThisBand->GetAssociateREL(), pOtherBand->GetAssociateREL()))
468 7 : return false;
469 :
470 : // One image has NoData values and the other does not;
471 : // they are assigned to different subdatasets
472 4 : if (pThisBand->BandHasNoData() != pOtherBand->BandHasNoData())
473 2 : return false;
474 :
475 : // Two images with different NoData values are assigned to different subdatasets
476 2 : if (pThisBand->GetNoDataValue() != pOtherBand->GetNoDataValue())
477 1 : return false;
478 :
479 1 : return true;
480 : }
481 :
482 : /************************************************************************/
483 : /* UpdateGeoTransform() */
484 : /************************************************************************/
485 7 : int MMRDataset::UpdateGeoTransform()
486 : {
487 : // Bounding box of the band
488 : // Section [EXTENT] in rel file
489 :
490 7 : if (!m_pMMRRel)
491 0 : return 1;
492 :
493 14 : CPLString osMinX;
494 14 : if (!m_pMMRRel->GetMetadataValue(SECTION_EXTENT, "MinX", osMinX) ||
495 7 : osMinX.empty())
496 0 : return 1;
497 :
498 7 : if (1 != CPLsscanf(osMinX, "%lf", &(m_gt.xorig)))
499 0 : m_gt.xorig = 0.0;
500 :
501 7 : int nNCols = m_pMMRRel->GetColumnsNumberFromREL();
502 7 : if (nNCols <= 0)
503 0 : return 1;
504 :
505 14 : CPLString osMaxX;
506 14 : if (!m_pMMRRel->GetMetadataValue(SECTION_EXTENT, "MaxX", osMaxX) ||
507 7 : osMaxX.empty())
508 0 : return 1;
509 :
510 : double dfMaxX;
511 7 : if (1 != CPLsscanf(osMaxX, "%lf", &dfMaxX))
512 0 : dfMaxX = 1.0;
513 :
514 7 : m_gt.xscale = (dfMaxX - m_gt.xorig) / nNCols;
515 7 : m_gt.xrot = 0.0; // No rotation in MiraMon rasters
516 :
517 14 : CPLString osMinY;
518 14 : if (!m_pMMRRel->GetMetadataValue(SECTION_EXTENT, "MinY", osMinY) ||
519 7 : osMinY.empty())
520 0 : return 1;
521 :
522 14 : CPLString osMaxY;
523 14 : if (!m_pMMRRel->GetMetadataValue(SECTION_EXTENT, "MaxY", osMaxY) ||
524 7 : osMaxY.empty())
525 0 : return 1;
526 :
527 7 : int nNRows = m_pMMRRel->GetRowsNumberFromREL();
528 7 : if (nNRows <= 0)
529 0 : return 1;
530 :
531 : double dfMaxY;
532 7 : if (1 != CPLsscanf(osMaxY, "%lf", &dfMaxY))
533 0 : dfMaxY = 1.0;
534 :
535 7 : m_gt.yorig = dfMaxY;
536 7 : m_gt.yrot = 0.0;
537 :
538 : double dfMinY;
539 7 : if (1 != CPLsscanf(osMinY, "%lf", &dfMinY))
540 0 : dfMinY = 0.0;
541 7 : m_gt.yscale = (dfMinY - m_gt.yorig) / nNRows;
542 :
543 7 : return 0;
544 : }
545 :
546 14 : const OGRSpatialReference *MMRDataset::GetSpatialRef() const
547 : {
548 14 : return m_oSRS.IsEmpty() ? nullptr : &m_oSRS;
549 : }
550 :
551 35 : CPLErr MMRDataset::GetGeoTransform(GDALGeoTransform >) const
552 : {
553 35 : if (m_gt.xorig != 0.0 || m_gt.xscale != 1.0 || m_gt.xrot != 0.0 ||
554 3 : m_gt.yorig != 0.0 || m_gt.yrot != 0.0 || m_gt.yscale != 1.0)
555 : {
556 35 : gt = m_gt;
557 35 : return CE_None;
558 : }
559 :
560 0 : return GDALDataset::GetGeoTransform(gt);
561 : }
|