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