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