Line data Source code
1 : /******************************************************************************
2 : *
3 : * Project: ISCE Raster Reader
4 : * Purpose: Implementation of the ISCE raster reader
5 : * Author: Matthieu Volat (ISTerre), matthieu.volat@ujf-grenoble.fr
6 : *
7 : ******************************************************************************
8 : * Copyright (c) 2015, Matthieu Volat <matthieu.volat@ujf-grenoble.fr>
9 : *
10 : * SPDX-License-Identifier: MIT
11 : ****************************************************************************/
12 :
13 : #include "gdal_frmts.h"
14 : #include "ogr_spatialref.h"
15 : #include "rawdataset.h"
16 :
17 : #include <algorithm>
18 :
19 : static const char *const apszISCE2GDALDatatypes[] = {
20 : "BYTE:Byte", "CHAR:Byte", "SHORT:Int16", "INT:Int32",
21 : "LONG:Int64", "FLOAT:Float32", "DOUBLE:Float64", "CBYTE:Unknown",
22 : "CCHAR:Unknown", "CSHORT:CInt16", "CINT:CInt32", "CLONG:CInt64",
23 : "CFLOAT:CFloat32", "CDOUBLE:CFloat64", nullptr};
24 :
25 : static const char *const apszGDAL2ISCEDatatypes[] = {
26 : "Byte:BYTE", "Int16:SHORT", "Int32:INT", "Int64:LONG",
27 : "Float32:FLOAT", "Float64:DOUBLE", "CInt16:CSHORT", "CInt32:CINT",
28 : "CInt64:CLONG", "CFloat32:CFLOAT", "CFloat64:CDOUBLE", nullptr};
29 :
30 : enum Scheme
31 : {
32 : BIL = 0,
33 : BIP = 1,
34 : BSQ = 2
35 : };
36 :
37 : static const char *const apszSchemeNames[] = {"BIL", "BIP", "BSQ", nullptr};
38 :
39 : /************************************************************************/
40 : /* ==================================================================== */
41 : /* ISCEDataset */
42 : /* ==================================================================== */
43 : /************************************************************************/
44 :
45 : class ISCERasterBand;
46 :
47 : class ISCEDataset final : public RawDataset
48 : {
49 : friend class ISCERasterBand;
50 :
51 : VSILFILE *fpImage;
52 :
53 : char *pszXMLFilename;
54 :
55 : enum Scheme eScheme;
56 :
57 : CPL_DISALLOW_COPY_ASSIGN(ISCEDataset)
58 :
59 : CPLErr Close() override;
60 :
61 : public:
62 : ISCEDataset();
63 : ~ISCEDataset() override;
64 :
65 : CPLErr FlushCache(bool bAtClosing) override;
66 : char **GetFileList() override;
67 :
68 : static int Identify(GDALOpenInfo *poOpenInfo);
69 : static GDALDataset *Open(GDALOpenInfo *poOpenInfo);
70 : static GDALDataset *Open(GDALOpenInfo *poOpenInfo, bool bFileSizeCheck);
71 : static GDALDataset *Create(const char *pszFilename, int nXSize, int nYSize,
72 : int nBandsIn, GDALDataType eType,
73 : char **papszOptions);
74 : };
75 :
76 : /************************************************************************/
77 : /* ==================================================================== */
78 : /* ISCERasterBand */
79 : /* ==================================================================== */
80 : /************************************************************************/
81 :
82 : class ISCERasterBand final : public RawRasterBand
83 : {
84 : CPL_DISALLOW_COPY_ASSIGN(ISCERasterBand)
85 :
86 : public:
87 : ISCERasterBand(GDALDataset *poDS, int nBand, VSILFILE *fpRaw,
88 : vsi_l_offset nImgOffset, int nPixelOffset, int nLineOffset,
89 : GDALDataType eDataType, int bNativeOrder);
90 : };
91 :
92 : /************************************************************************/
93 : /* getXMLFilename() */
94 : /************************************************************************/
95 :
96 28218 : static CPLString getXMLFilename(GDALOpenInfo *poOpenInfo)
97 : {
98 56383 : CPLString osXMLFilename;
99 :
100 28181 : if (poOpenInfo->fpL == nullptr)
101 26755 : return CPLString();
102 :
103 1426 : char **papszSiblingFiles = poOpenInfo->GetSiblingFiles();
104 1394 : if (papszSiblingFiles == nullptr)
105 : {
106 : osXMLFilename =
107 17 : CPLFormFilenameSafe(nullptr, poOpenInfo->pszFilename, "xml");
108 : VSIStatBufL psXMLStatBuf;
109 17 : if (VSIStatL(osXMLFilename, &psXMLStatBuf) != 0)
110 : {
111 17 : osXMLFilename = "";
112 : }
113 : }
114 : else
115 : {
116 : /* ------------------------------------------------------------ */
117 : /* We need to tear apart the filename to form a .xml */
118 : /* filename. */
119 : /* ------------------------------------------------------------ */
120 2754 : const CPLString osPath = CPLGetPathSafe(poOpenInfo->pszFilename);
121 2754 : const CPLString osName = CPLGetFilename(poOpenInfo->pszFilename);
122 :
123 : const int iFile =
124 1377 : CSLFindString(papszSiblingFiles,
125 2754 : CPLFormFilenameSafe(nullptr, osName, "xml").c_str());
126 1377 : if (iFile >= 0)
127 : {
128 : osXMLFilename =
129 200 : CPLFormFilenameSafe(osPath, papszSiblingFiles[iFile], nullptr);
130 : }
131 : }
132 :
133 1394 : return osXMLFilename;
134 : }
135 :
136 : /************************************************************************/
137 : /* ISCEDataset() */
138 : /************************************************************************/
139 :
140 70 : ISCEDataset::ISCEDataset()
141 70 : : fpImage(nullptr), pszXMLFilename(nullptr), eScheme(BIL)
142 : {
143 70 : }
144 :
145 : /************************************************************************/
146 : /* ~ISCEDataset() */
147 : /************************************************************************/
148 :
149 140 : ISCEDataset::~ISCEDataset()
150 :
151 : {
152 70 : ISCEDataset::Close();
153 140 : }
154 :
155 : /************************************************************************/
156 : /* Close() */
157 : /************************************************************************/
158 :
159 140 : CPLErr ISCEDataset::Close()
160 : {
161 140 : CPLErr eErr = CE_None;
162 140 : if (nOpenFlags != OPEN_FLAGS_CLOSED)
163 : {
164 70 : if (ISCEDataset::FlushCache(true) != CE_None)
165 0 : eErr = CE_Failure;
166 :
167 70 : if (fpImage)
168 : {
169 70 : if (VSIFCloseL(fpImage) != 0)
170 : {
171 0 : CPLError(CE_Failure, CPLE_FileIO, "I/O error");
172 0 : eErr = CE_Failure;
173 : }
174 : }
175 70 : CPLFree(pszXMLFilename);
176 :
177 70 : if (GDALPamDataset::Close() != CE_None)
178 0 : eErr = CE_Failure;
179 : }
180 140 : return eErr;
181 : }
182 :
183 : /************************************************************************/
184 : /* FlushCache() */
185 : /************************************************************************/
186 :
187 70 : CPLErr ISCEDataset::FlushCache(bool bAtClosing)
188 : {
189 70 : CPLErr eErr = RawDataset::FlushCache(bAtClosing);
190 :
191 70 : GDALRasterBand *band = (GetRasterCount() > 0) ? GetRasterBand(1) : nullptr;
192 :
193 70 : if (eAccess == GA_ReadOnly || band == nullptr)
194 32 : return eErr;
195 :
196 : /* -------------------------------------------------------------------- */
197 : /* Recreate a XML doc with the dataset information. */
198 : /* -------------------------------------------------------------------- */
199 38 : char sBuf[64] = {'\0'};
200 38 : CPLXMLNode *psDocNode = CPLCreateXMLNode(nullptr, CXT_Element, "imageFile");
201 :
202 : CPLXMLNode *psTmpNode =
203 38 : CPLCreateXMLNode(psDocNode, CXT_Element, "property");
204 38 : CPLAddXMLAttributeAndValue(psTmpNode, "name", "WIDTH");
205 38 : CPLsnprintf(sBuf, sizeof(sBuf), "%d", nRasterXSize);
206 38 : CPLCreateXMLElementAndValue(psTmpNode, "value", sBuf);
207 :
208 38 : psTmpNode = CPLCreateXMLNode(psDocNode, CXT_Element, "property");
209 38 : CPLAddXMLAttributeAndValue(psTmpNode, "name", "LENGTH");
210 38 : CPLsnprintf(sBuf, sizeof(sBuf), "%d", nRasterYSize);
211 38 : CPLCreateXMLElementAndValue(psTmpNode, "value", sBuf);
212 :
213 38 : psTmpNode = CPLCreateXMLNode(psDocNode, CXT_Element, "property");
214 38 : CPLAddXMLAttributeAndValue(psTmpNode, "name", "NUMBER_BANDS");
215 38 : CPLsnprintf(sBuf, sizeof(sBuf), "%d", nBands);
216 38 : CPLCreateXMLElementAndValue(psTmpNode, "value", sBuf);
217 :
218 38 : const char *sType = GDALGetDataTypeName(band->GetRasterDataType());
219 38 : psTmpNode = CPLCreateXMLNode(psDocNode, CXT_Element, "property");
220 38 : CPLAddXMLAttributeAndValue(psTmpNode, "name", "DATA_TYPE");
221 38 : CPLCreateXMLElementAndValue(
222 : psTmpNode, "value",
223 : CSLFetchNameValue(const_cast<char **>(apszGDAL2ISCEDatatypes), sType));
224 :
225 38 : const char *pszScheme = apszSchemeNames[eScheme];
226 38 : psTmpNode = CPLCreateXMLNode(psDocNode, CXT_Element, "property");
227 38 : CPLAddXMLAttributeAndValue(psTmpNode, "name", "SCHEME");
228 38 : CPLCreateXMLElementAndValue(psTmpNode, "value", pszScheme);
229 :
230 38 : psTmpNode = CPLCreateXMLNode(psDocNode, CXT_Element, "property");
231 38 : CPLAddXMLAttributeAndValue(psTmpNode, "name", "BYTE_ORDER");
232 : #ifdef CPL_LSB
233 38 : CPLCreateXMLElementAndValue(psTmpNode, "value", "l");
234 : #else
235 : CPLCreateXMLElementAndValue(psTmpNode, "value", "b");
236 : #endif
237 :
238 38 : psTmpNode = CPLCreateXMLNode(psDocNode, CXT_Element, "property");
239 38 : CPLAddXMLAttributeAndValue(psTmpNode, "name", "ACCESS_MODE");
240 38 : CPLCreateXMLElementAndValue(psTmpNode, "value", "read");
241 :
242 38 : psTmpNode = CPLCreateXMLNode(psDocNode, CXT_Element, "property");
243 38 : CPLAddXMLAttributeAndValue(psTmpNode, "name", "FILE_NAME");
244 38 : CPLCreateXMLElementAndValue(psTmpNode, "value",
245 76 : CPLGetBasenameSafe(pszXMLFilename).c_str());
246 :
247 : /* -------------------------------------------------------------------- */
248 : /* Then, add the ISCE domain metadata. */
249 : /* -------------------------------------------------------------------- */
250 38 : char **papszISCEMetadata = GetMetadata("ISCE");
251 38 : for (int i = 0; i < CSLCount(papszISCEMetadata); i++)
252 : {
253 : /* Get the tokens from the metadata item */
254 : char **papszTokens =
255 0 : CSLTokenizeString2(papszISCEMetadata[i], "=",
256 : CSLT_STRIPLEADSPACES | CSLT_STRIPENDSPACES);
257 0 : if (CSLCount(papszTokens) != 2)
258 : {
259 0 : CPLDebug("ISCE",
260 : "Line of header file could not be split at = into two"
261 : " elements: %s",
262 0 : papszISCEMetadata[i]);
263 0 : CSLDestroy(papszTokens);
264 0 : continue;
265 : }
266 :
267 : /* Don't write it out if it is one of the bits of metadata that is
268 : * written out elsewhere in this routine */
269 0 : if (EQUAL(papszTokens[0], "WIDTH") || EQUAL(papszTokens[0], "LENGTH") ||
270 0 : EQUAL(papszTokens[0], "NUMBER_BANDS") ||
271 0 : EQUAL(papszTokens[0], "DATA_TYPE") ||
272 0 : EQUAL(papszTokens[0], "SCHEME") ||
273 0 : EQUAL(papszTokens[0], "BYTE_ORDER"))
274 : {
275 0 : CSLDestroy(papszTokens);
276 0 : continue;
277 : }
278 :
279 0 : psTmpNode = CPLCreateXMLNode(psDocNode, CXT_Element, "property");
280 0 : CPLAddXMLAttributeAndValue(psTmpNode, "name", papszTokens[0]);
281 0 : CPLCreateXMLElementAndValue(psTmpNode, "value", papszTokens[1]);
282 :
283 0 : CSLDestroy(papszTokens);
284 : }
285 :
286 : /* -------------------------------------------------------------------- */
287 : /* Create the "Coordinate" component elements, possibly with */
288 : /* georeferencing. */
289 : /* -------------------------------------------------------------------- */
290 : CPLXMLNode *psCoordinate1Node, *psCoordinate2Node;
291 : double adfGeoTransform[6];
292 :
293 : /* Coordinate 1 */
294 38 : psCoordinate1Node = CPLCreateXMLNode(psDocNode, CXT_Element, "component");
295 38 : CPLAddXMLAttributeAndValue(psCoordinate1Node, "name", "Coordinate1");
296 38 : CPLCreateXMLElementAndValue(psCoordinate1Node, "factorymodule",
297 : "isceobj.Image");
298 38 : CPLCreateXMLElementAndValue(psCoordinate1Node, "factoryname",
299 : "createCoordinate");
300 38 : CPLCreateXMLElementAndValue(psCoordinate1Node, "doc",
301 : "First coordinate of a 2D image (width).");
302 : /* Property name */
303 38 : psTmpNode = CPLCreateXMLNode(psCoordinate1Node, CXT_Element, "property");
304 38 : CPLAddXMLAttributeAndValue(psTmpNode, "name", "name");
305 38 : CPLCreateXMLElementAndValue(psTmpNode, "value", "ImageCoordinate_name");
306 : /* Property family */
307 38 : psTmpNode = CPLCreateXMLNode(psCoordinate1Node, CXT_Element, "property");
308 38 : CPLAddXMLAttributeAndValue(psTmpNode, "name", "family");
309 38 : CPLCreateXMLElementAndValue(psTmpNode, "value", "ImageCoordinate");
310 : /* Property size */
311 38 : CPLsnprintf(sBuf, sizeof(sBuf), "%d", nRasterXSize);
312 38 : psTmpNode = CPLCreateXMLNode(psCoordinate1Node, CXT_Element, "property");
313 38 : CPLAddXMLAttributeAndValue(psTmpNode, "name", "size");
314 38 : CPLCreateXMLElementAndValue(psTmpNode, "value", sBuf);
315 :
316 : /* Coordinate 2 */
317 38 : psCoordinate2Node = CPLCreateXMLNode(psDocNode, CXT_Element, "component");
318 38 : CPLAddXMLAttributeAndValue(psCoordinate2Node, "name", "Coordinate2");
319 38 : CPLCreateXMLElementAndValue(psCoordinate2Node, "factorymodule",
320 : "isceobj.Image");
321 38 : CPLCreateXMLElementAndValue(psCoordinate2Node, "factoryname",
322 : "createCoordinate");
323 : /* Property name */
324 38 : psTmpNode = CPLCreateXMLNode(psCoordinate2Node, CXT_Element, "property");
325 38 : CPLAddXMLAttributeAndValue(psTmpNode, "name", "name");
326 38 : CPLCreateXMLElementAndValue(psTmpNode, "value", "ImageCoordinate_name");
327 : /* Property family */
328 38 : psTmpNode = CPLCreateXMLNode(psCoordinate2Node, CXT_Element, "property");
329 38 : CPLAddXMLAttributeAndValue(psTmpNode, "name", "family");
330 38 : CPLCreateXMLElementAndValue(psTmpNode, "value", "ImageCoordinate");
331 : /* Property size */
332 38 : CPLsnprintf(sBuf, sizeof(sBuf), "%d", nRasterYSize);
333 38 : psTmpNode = CPLCreateXMLNode(psCoordinate2Node, CXT_Element, "property");
334 38 : CPLAddXMLAttributeAndValue(psTmpNode, "name", "size");
335 38 : CPLCreateXMLElementAndValue(psTmpNode, "value", sBuf);
336 :
337 38 : if (GetGeoTransform(adfGeoTransform) == CE_None)
338 : {
339 38 : if (adfGeoTransform[2] != 0 || adfGeoTransform[4] != 0)
340 : {
341 0 : CPLError(CE_Warning, CPLE_AppDefined,
342 : "ISCE format do not support geotransform with "
343 : "rotation, discarding info.");
344 : }
345 : else
346 : {
347 38 : CPLsnprintf(sBuf, sizeof(sBuf), "%g", adfGeoTransform[0]);
348 : psTmpNode =
349 38 : CPLCreateXMLNode(psCoordinate1Node, CXT_Element, "property");
350 38 : CPLAddXMLAttributeAndValue(psTmpNode, "name", "startingValue");
351 38 : CPLCreateXMLElementAndValue(psTmpNode, "value", sBuf);
352 :
353 38 : CPLsnprintf(sBuf, sizeof(sBuf), "%g", adfGeoTransform[1]);
354 : psTmpNode =
355 38 : CPLCreateXMLNode(psCoordinate1Node, CXT_Element, "property");
356 38 : CPLAddXMLAttributeAndValue(psTmpNode, "name", "delta");
357 38 : CPLCreateXMLElementAndValue(psTmpNode, "value", sBuf);
358 :
359 38 : CPLsnprintf(sBuf, sizeof(sBuf), "%g", adfGeoTransform[3]);
360 : psTmpNode =
361 38 : CPLCreateXMLNode(psCoordinate2Node, CXT_Element, "property");
362 38 : CPLAddXMLAttributeAndValue(psTmpNode, "name", "startingValue");
363 38 : CPLCreateXMLElementAndValue(psTmpNode, "value", sBuf);
364 :
365 38 : CPLsnprintf(sBuf, sizeof(sBuf), "%g", adfGeoTransform[5]);
366 : psTmpNode =
367 38 : CPLCreateXMLNode(psCoordinate2Node, CXT_Element, "property");
368 38 : CPLAddXMLAttributeAndValue(psTmpNode, "name", "delta");
369 38 : CPLCreateXMLElementAndValue(psTmpNode, "value", sBuf);
370 : }
371 : }
372 :
373 : /* -------------------------------------------------------------------- */
374 : /* Write the XML file. */
375 : /* -------------------------------------------------------------------- */
376 38 : if (!CPLSerializeXMLTreeToFile(psDocNode, pszXMLFilename))
377 0 : eErr = CE_Failure;
378 :
379 : /* -------------------------------------------------------------------- */
380 : /* Free the XML Doc. */
381 : /* -------------------------------------------------------------------- */
382 38 : CPLDestroyXMLNode(psDocNode);
383 :
384 38 : return eErr;
385 : }
386 :
387 : /************************************************************************/
388 : /* GetFileList() */
389 : /************************************************************************/
390 :
391 3 : char **ISCEDataset::GetFileList()
392 : {
393 : /* Main data file, etc. */
394 3 : char **papszFileList = RawDataset::GetFileList();
395 :
396 : /* XML file. */
397 3 : papszFileList = CSLAddString(papszFileList, pszXMLFilename);
398 :
399 3 : return papszFileList;
400 : }
401 :
402 : /************************************************************************/
403 : /* Identify() */
404 : /************************************************************************/
405 :
406 28099 : int ISCEDataset::Identify(GDALOpenInfo *poOpenInfo)
407 : {
408 : /* -------------------------------------------------------------------- */
409 : /* TODO: This function is unusable now: */
410 : /* * we can't just check for the presence of a XML file */
411 : /* * we cannot parse it to check basic tree (Identify() is */
412 : /* supposed to be faster than this */
413 : /* * we could read only a few bytes and strstr() for */
414 : /* "imageData", but what if a file is padded with comments */
415 : /* and/or whitespaces? it would still be legit, but the */
416 : /* driver would fail... */
417 : /* -------------------------------------------------------------------- */
418 : /* -------------------------------------------------------------------- */
419 : /* Check if there is a .xml file */
420 : /* -------------------------------------------------------------------- */
421 56165 : CPLString osXMLFilename = getXMLFilename(poOpenInfo);
422 28055 : if (osXMLFilename.empty())
423 : {
424 27926 : return false;
425 : }
426 :
427 140 : return true;
428 : }
429 :
430 : /************************************************************************/
431 : /* Open() */
432 : /************************************************************************/
433 :
434 28056 : GDALDataset *ISCEDataset::Open(GDALOpenInfo *poOpenInfo)
435 : {
436 28056 : return Open(poOpenInfo, true);
437 : }
438 :
439 28100 : GDALDataset *ISCEDataset::Open(GDALOpenInfo *poOpenInfo, bool bFileSizeCheck)
440 : {
441 : /* -------------------------------------------------------------------- */
442 : /* Confirm that the header is compatible with a ISCE dataset. */
443 : /* -------------------------------------------------------------------- */
444 28100 : if (!Identify(poOpenInfo) || poOpenInfo->fpL == nullptr)
445 : {
446 27909 : return nullptr;
447 : }
448 :
449 : /* -------------------------------------------------------------------- */
450 : /* Open and parse the .xml file */
451 : /* -------------------------------------------------------------------- */
452 221 : const CPLString osXMLFilename = getXMLFilename(poOpenInfo);
453 100 : CPLXMLNode *psNode = CPLParseXMLFile(osXMLFilename);
454 100 : if (psNode == nullptr || CPLGetXMLNode(psNode, "=imageFile") == nullptr)
455 : {
456 10 : CPLDestroyXMLNode(psNode);
457 10 : return nullptr;
458 : }
459 90 : CPLXMLNode *psCur = CPLGetXMLNode(psNode, "=imageFile")->psChild;
460 180 : CPLStringList aosXmlProps;
461 783 : while (psCur != nullptr)
462 : {
463 693 : if (EQUAL(psCur->pszValue, "property"))
464 : {
465 : /* Top-level property */
466 629 : const char *pszName = CPLGetXMLValue(psCur, "name", nullptr);
467 629 : const char *pszValue = CPLGetXMLValue(psCur, "value", nullptr);
468 629 : if (pszName != nullptr && pszValue != nullptr)
469 : {
470 611 : aosXmlProps.SetNameValue(pszName, pszValue);
471 : }
472 : }
473 64 : else if (EQUAL(psCur->pszValue, "component"))
474 : {
475 : /* "components" elements in ISCE store set of properties. */
476 : /* For now, they are avoided as I am not sure the full */
477 : /* scope of these. An exception is made for the ones named */
478 : /* Coordinate1 and Coordinate2, because they may have the */
479 : /* georeferencing information. */
480 64 : const char *pszCurName = CPLGetXMLValue(psCur, "name", nullptr);
481 64 : if (pszCurName != nullptr && (EQUAL(pszCurName, "Coordinate1") ||
482 32 : EQUAL(pszCurName, "Coordinate2")))
483 : {
484 : /* We need two subproperties: startingValue and delta. */
485 : /* To simplify parsing code, we will store them in */
486 : /* aosXmlProps with the coordinate name prefixed to */
487 : /* the property name. */
488 64 : CPLXMLNode *psCur2 = psCur->psChild;
489 613 : while (psCur2 != nullptr)
490 : {
491 549 : if (!EQUAL(psCur2->pszValue, "property"))
492 : {
493 229 : psCur2 = psCur2->psNext;
494 229 : continue; /* Skip non property elements */
495 : }
496 :
497 : const char *pszCur2Name =
498 320 : CPLGetXMLValue(psCur2, "name", nullptr),
499 : *pszCur2Value =
500 320 : CPLGetXMLValue(psCur2, "value", nullptr);
501 :
502 320 : if (pszCur2Name == nullptr || pszCur2Value == nullptr)
503 : {
504 0 : psCur2 = psCur2->psNext;
505 0 : continue; /* Skip malformatted elements */
506 : }
507 :
508 320 : if (EQUAL(pszCur2Name, "startingValue") ||
509 256 : EQUAL(pszCur2Name, "delta"))
510 : {
511 : char szPropName[32];
512 128 : snprintf(szPropName, sizeof(szPropName), "%s%s",
513 : pszCurName, pszCur2Name);
514 :
515 128 : aosXmlProps.SetNameValue(szPropName, pszCur2Value);
516 : }
517 320 : psCur2 = psCur2->psNext;
518 : }
519 : }
520 : }
521 693 : psCur = psCur->psNext;
522 : }
523 :
524 90 : CPLDestroyXMLNode(psNode);
525 :
526 : /* -------------------------------------------------------------------- */
527 : /* Fetch required fields. */
528 : /* -------------------------------------------------------------------- */
529 90 : if (aosXmlProps.FetchNameValue("WIDTH") == nullptr ||
530 90 : aosXmlProps.FetchNameValue("LENGTH") == nullptr ||
531 90 : aosXmlProps.FetchNameValue("NUMBER_BANDS") == nullptr ||
532 252 : aosXmlProps.FetchNameValue("DATA_TYPE") == nullptr ||
533 72 : aosXmlProps.FetchNameValue("SCHEME") == nullptr)
534 : {
535 18 : return nullptr;
536 : }
537 72 : const int nWidth = atoi(aosXmlProps.FetchNameValue("WIDTH"));
538 72 : const int nHeight = atoi(aosXmlProps.FetchNameValue("LENGTH"));
539 72 : const int nBands = atoi(aosXmlProps.FetchNameValue("NUMBER_BANDS"));
540 :
541 144 : if (!GDALCheckDatasetDimensions(nWidth, nHeight) ||
542 72 : !GDALCheckBandCount(nBands, FALSE))
543 : {
544 2 : return nullptr;
545 : }
546 :
547 : /* -------------------------------------------------------------------- */
548 : /* Update byte order info if image specify something. */
549 : /* -------------------------------------------------------------------- */
550 70 : bool bNativeOrder = true;
551 :
552 70 : const char *pszByteOrder = aosXmlProps.FetchNameValue("BYTE_ORDER");
553 70 : if (pszByteOrder != nullptr)
554 : {
555 : #ifdef CPL_LSB
556 70 : if (EQUAL(pszByteOrder, "b"))
557 : #else
558 : if (EQUAL(pszByteOrder, "l"))
559 : #endif
560 0 : bNativeOrder = false;
561 : }
562 :
563 : /* -------------------------------------------------------------------- */
564 : /* Create a corresponding GDALDataset. */
565 : /* -------------------------------------------------------------------- */
566 140 : auto poDS = std::make_unique<ISCEDataset>();
567 70 : poDS->nRasterXSize = nWidth;
568 70 : poDS->nRasterYSize = nHeight;
569 70 : poDS->eAccess = poOpenInfo->eAccess;
570 70 : poDS->pszXMLFilename = CPLStrdup(osXMLFilename.c_str());
571 70 : std::swap(poDS->fpImage, poOpenInfo->fpL);
572 :
573 : /* -------------------------------------------------------------------- */
574 : /* Create band information objects. */
575 : /* -------------------------------------------------------------------- */
576 70 : const char *pszDataType = CSLFetchNameValue(
577 : apszISCE2GDALDatatypes, aosXmlProps.FetchNameValue("DATA_TYPE"));
578 70 : if (pszDataType == nullptr)
579 : {
580 0 : return nullptr;
581 : }
582 70 : const GDALDataType eDataType = GDALGetDataTypeByName(pszDataType);
583 70 : const int nDTSize = GDALGetDataTypeSizeBytes(eDataType);
584 70 : if (nDTSize == 0)
585 : {
586 0 : return nullptr;
587 : }
588 70 : const char *pszScheme = aosXmlProps.FetchNameValue("SCHEME");
589 70 : int nPixelOffset = 0;
590 70 : int nLineOffset = 0;
591 70 : vsi_l_offset nBandOffset = 0;
592 70 : bool bIntOverflow = false;
593 70 : if (EQUAL(pszScheme, "BIL"))
594 : {
595 0 : poDS->eScheme = BIL;
596 0 : nPixelOffset = nDTSize;
597 0 : if (nWidth > INT_MAX / (nPixelOffset * nBands))
598 0 : bIntOverflow = true;
599 : else
600 : {
601 0 : nLineOffset = nPixelOffset * nWidth * nBands;
602 0 : nBandOffset = nDTSize * static_cast<vsi_l_offset>(nWidth);
603 : }
604 : }
605 70 : else if (EQUAL(pszScheme, "BIP"))
606 : {
607 70 : poDS->eScheme = BIP;
608 70 : nPixelOffset = nDTSize * nBands;
609 70 : if (nWidth > INT_MAX / nPixelOffset)
610 0 : bIntOverflow = true;
611 : else
612 : {
613 70 : nLineOffset = nPixelOffset * nWidth;
614 70 : if (nBands > 1 && nLineOffset < INT_MAX / nBands)
615 : {
616 : // GDAL 2.1.0 had a value of nLineOffset that was equal to the
617 : // theoretical nLineOffset multiplied by nBands...
618 30 : VSIFSeekL(poDS->fpImage, 0, SEEK_END);
619 30 : const GUIntBig nWrongFileSize =
620 30 : static_cast<GUIntBig>(nDTSize) * nWidth *
621 30 : (static_cast<GUIntBig>(nHeight - 1) * nBands * nBands +
622 : nBands);
623 30 : if (VSIFTellL(poDS->fpImage) == nWrongFileSize)
624 : {
625 0 : CPLError(
626 : CE_Warning, CPLE_AppDefined,
627 : "This file has been incorrectly generated by an older "
628 : "GDAL version whose line offset computation was "
629 : "erroneous. "
630 : "Taking that into account, but the file should be "
631 : "re-encoded ideally");
632 0 : nLineOffset = nLineOffset * nBands;
633 : }
634 : }
635 70 : nBandOffset = nDTSize;
636 : }
637 : }
638 0 : else if (EQUAL(pszScheme, "BSQ"))
639 : {
640 0 : poDS->eScheme = BSQ;
641 0 : nPixelOffset = nDTSize;
642 0 : if (nWidth > INT_MAX / nPixelOffset)
643 0 : bIntOverflow = true;
644 : else
645 : {
646 0 : nLineOffset = nPixelOffset * nWidth;
647 0 : nBandOffset = nLineOffset * static_cast<vsi_l_offset>(nHeight);
648 : }
649 : }
650 : else
651 : {
652 0 : CPLError(CE_Failure, CPLE_OpenFailed,
653 : "Unknown scheme \"%s\" within ISCE raster.", pszScheme);
654 0 : return nullptr;
655 : }
656 :
657 70 : if (bIntOverflow)
658 : {
659 0 : CPLError(CE_Failure, CPLE_AppDefined, "Int overflow occurred.");
660 0 : return nullptr;
661 : }
662 :
663 102 : if (bFileSizeCheck &&
664 32 : !RAWDatasetCheckMemoryUsage(poDS->nRasterXSize, poDS->nRasterYSize,
665 : nBands, nDTSize, nPixelOffset, nLineOffset,
666 32 : 0, nBandOffset, poDS->fpImage))
667 : {
668 0 : return nullptr;
669 : }
670 :
671 206 : for (int b = 0; b < nBands; b++)
672 : {
673 : auto poBand = std::make_unique<ISCERasterBand>(
674 136 : poDS.get(), b + 1, poDS->fpImage, nBandOffset * b, nPixelOffset,
675 136 : nLineOffset, eDataType, bNativeOrder);
676 136 : if (!poBand->IsValid())
677 0 : return nullptr;
678 136 : poDS->SetBand(b + 1, std::move(poBand));
679 : }
680 :
681 : /* -------------------------------------------------------------------- */
682 : /* Interpret georeferencing, if present. */
683 : /* -------------------------------------------------------------------- */
684 70 : if (aosXmlProps.FetchNameValue("Coordinate1startingValue") != nullptr &&
685 32 : aosXmlProps.FetchNameValue("Coordinate1delta") != nullptr &&
686 134 : aosXmlProps.FetchNameValue("Coordinate2startingValue") != nullptr &&
687 32 : aosXmlProps.FetchNameValue("Coordinate2delta") != nullptr)
688 : {
689 : double adfGeoTransform[6];
690 32 : adfGeoTransform[0] =
691 32 : CPLAtof(aosXmlProps.FetchNameValue("Coordinate1startingValue"));
692 32 : adfGeoTransform[1] =
693 32 : CPLAtof(aosXmlProps.FetchNameValue("Coordinate1delta"));
694 32 : adfGeoTransform[2] = 0.0;
695 32 : adfGeoTransform[3] =
696 32 : CPLAtof(aosXmlProps.FetchNameValue("Coordinate2startingValue"));
697 32 : adfGeoTransform[4] = 0.0;
698 32 : adfGeoTransform[5] =
699 32 : CPLAtof(aosXmlProps.FetchNameValue("Coordinate2delta"));
700 32 : poDS->SetGeoTransform(adfGeoTransform);
701 :
702 : /* ISCE format seems not to have a projection field, but uses */
703 : /* WGS84. */
704 32 : poDS->SetProjection(SRS_WKT_WGS84_LAT_LONG);
705 : }
706 :
707 : /* -------------------------------------------------------------------- */
708 : /* Set all the other header metadata into the ISCE domain */
709 : /* -------------------------------------------------------------------- */
710 707 : for (int i = 0; i < aosXmlProps.size(); i++)
711 : {
712 : const CPLStringList aosTokens(CSLTokenizeString2(
713 637 : aosXmlProps[i], "=", CSLT_STRIPLEADSPACES | CSLT_STRIPENDSPACES));
714 1274 : if (aosTokens.size() < 2 || EQUAL(aosTokens[0], "WIDTH") ||
715 567 : EQUAL(aosTokens[0], "LENGTH") ||
716 497 : EQUAL(aosTokens[0], "NUMBER_BANDS") ||
717 427 : EQUAL(aosTokens[0], "DATA_TYPE") || EQUAL(aosTokens[0], "SCHEME") ||
718 287 : EQUAL(aosTokens[0], "BYTE_ORDER") ||
719 217 : EQUAL(aosTokens[0], "Coordinate1startingValue") ||
720 185 : EQUAL(aosTokens[0], "Coordinate1delta") ||
721 1395 : EQUAL(aosTokens[0], "Coordinate2startingValue") ||
722 121 : EQUAL(aosTokens[0], "Coordinate2delta"))
723 : {
724 548 : continue;
725 : }
726 89 : poDS->SetMetadataItem(aosTokens[0], aosTokens[1], "ISCE");
727 : }
728 :
729 : /* -------------------------------------------------------------------- */
730 : /* Initialize any PAM information. */
731 : /* -------------------------------------------------------------------- */
732 70 : poDS->SetDescription(poOpenInfo->pszFilename);
733 70 : poDS->TryLoadXML();
734 :
735 : /* -------------------------------------------------------------------- */
736 : /* Check for overviews. */
737 : /* -------------------------------------------------------------------- */
738 70 : poDS->oOvManager.Initialize(poDS.get(), poOpenInfo->pszFilename);
739 :
740 70 : return poDS.release();
741 : }
742 :
743 : /************************************************************************/
744 : /* Create() */
745 : /************************************************************************/
746 :
747 62 : GDALDataset *ISCEDataset::Create(const char *pszFilename, int nXSize,
748 : int nYSize, int nBandsIn, GDALDataType eType,
749 : char **papszOptions)
750 : {
751 62 : const char *sType = GDALGetDataTypeName(eType);
752 62 : const char *pszScheme = CSLFetchNameValueDef(papszOptions, "SCHEME", "BIP");
753 :
754 : /* -------------------------------------------------------------------- */
755 : /* Try to create the file. */
756 : /* -------------------------------------------------------------------- */
757 62 : VSILFILE *fp = VSIFOpenL(pszFilename, "wb");
758 62 : if (fp == nullptr)
759 : {
760 3 : CPLError(CE_Failure, CPLE_OpenFailed,
761 : "Attempt to create file `%s' failed.", pszFilename);
762 3 : return nullptr;
763 : }
764 :
765 : /* -------------------------------------------------------------------- */
766 : /* Just write out a couple of bytes to establish the binary */
767 : /* file, and then close it. */
768 : /* -------------------------------------------------------------------- */
769 59 : CPL_IGNORE_RET_VAL(VSIFWriteL("\0\0", 2, 1, fp));
770 59 : CPL_IGNORE_RET_VAL(VSIFCloseL(fp));
771 :
772 : /* -------------------------------------------------------------------- */
773 : /* Create a minimal XML document. */
774 : /* -------------------------------------------------------------------- */
775 59 : CPLXMLNode *psDocNode = CPLCreateXMLNode(nullptr, CXT_Element, "imageFile");
776 :
777 : CPLXMLNode *psTmpNode =
778 59 : CPLCreateXMLNode(psDocNode, CXT_Element, "property");
779 59 : CPLAddXMLAttributeAndValue(psTmpNode, "name", "WIDTH");
780 59 : char sBuf[64] = {'\0'};
781 59 : CPLsnprintf(sBuf, sizeof(sBuf), "%d", nXSize);
782 59 : CPLCreateXMLElementAndValue(psTmpNode, "value", sBuf);
783 :
784 59 : psTmpNode = CPLCreateXMLNode(psDocNode, CXT_Element, "property");
785 59 : CPLAddXMLAttributeAndValue(psTmpNode, "name", "LENGTH");
786 59 : CPLsnprintf(sBuf, sizeof(sBuf), "%d", nYSize);
787 59 : CPLCreateXMLElementAndValue(psTmpNode, "value", sBuf);
788 :
789 59 : psTmpNode = CPLCreateXMLNode(psDocNode, CXT_Element, "property");
790 59 : CPLAddXMLAttributeAndValue(psTmpNode, "name", "NUMBER_BANDS");
791 59 : CPLsnprintf(sBuf, sizeof(sBuf), "%d", nBandsIn);
792 59 : CPLCreateXMLElementAndValue(psTmpNode, "value", sBuf);
793 :
794 59 : psTmpNode = CPLCreateXMLNode(psDocNode, CXT_Element, "property");
795 59 : CPLAddXMLAttributeAndValue(psTmpNode, "name", "DATA_TYPE");
796 59 : CPLCreateXMLElementAndValue(
797 : psTmpNode, "value",
798 : CSLFetchNameValue(const_cast<char **>(apszGDAL2ISCEDatatypes), sType));
799 :
800 59 : psTmpNode = CPLCreateXMLNode(psDocNode, CXT_Element, "property");
801 59 : CPLAddXMLAttributeAndValue(psTmpNode, "name", "SCHEME");
802 59 : CPLCreateXMLElementAndValue(psTmpNode, "value", pszScheme);
803 :
804 59 : psTmpNode = CPLCreateXMLNode(psDocNode, CXT_Element, "property");
805 59 : CPLAddXMLAttributeAndValue(psTmpNode, "name", "BYTE_ORDER");
806 : #ifdef CPL_LSB
807 59 : CPLCreateXMLElementAndValue(psTmpNode, "value", "l");
808 : #else
809 : CPLCreateXMLElementAndValue(psTmpNode, "value", "b");
810 : #endif
811 :
812 : /* -------------------------------------------------------------------- */
813 : /* Write the XML file. */
814 : /* -------------------------------------------------------------------- */
815 : const std::string osXMLFilename =
816 118 : CPLFormFilenameSafe(nullptr, pszFilename, "xml");
817 59 : CPLSerializeXMLTreeToFile(psDocNode, osXMLFilename.c_str());
818 :
819 : /* -------------------------------------------------------------------- */
820 : /* Free the XML Doc. */
821 : /* -------------------------------------------------------------------- */
822 59 : CPLDestroyXMLNode(psDocNode);
823 :
824 118 : GDALOpenInfo oOpenInfo(pszFilename, GA_Update);
825 59 : return Open(&oOpenInfo, false);
826 : }
827 :
828 : /************************************************************************/
829 : /* ISCERasterBand() */
830 : /************************************************************************/
831 :
832 136 : ISCERasterBand::ISCERasterBand(GDALDataset *poDSIn, int nBandIn,
833 : VSILFILE *fpRawIn, vsi_l_offset nImgOffsetIn,
834 : int nPixelOffsetIn, int nLineOffsetIn,
835 136 : GDALDataType eDataTypeIn, int bNativeOrderIn)
836 : : RawRasterBand(poDSIn, nBandIn, fpRawIn, nImgOffsetIn, nPixelOffsetIn,
837 : nLineOffsetIn, eDataTypeIn, bNativeOrderIn,
838 136 : RawRasterBand::OwnFP::NO)
839 : {
840 136 : }
841 :
842 : /************************************************************************/
843 : /* GDALRegister_ISCE() */
844 : /************************************************************************/
845 :
846 1682 : void GDALRegister_ISCE()
847 : {
848 1682 : if (GDALGetDriverByName("ISCE") != nullptr)
849 301 : return;
850 :
851 1381 : GDALDriver *poDriver = new GDALDriver();
852 :
853 1381 : poDriver->SetDescription("ISCE");
854 1381 : poDriver->SetMetadataItem(GDAL_DMD_LONGNAME, "ISCE raster");
855 1381 : poDriver->SetMetadataItem(GDAL_DMD_HELPTOPIC, "drivers/raster/isce.html");
856 1381 : poDriver->SetMetadataItem(GDAL_DMD_CREATIONDATATYPES,
857 : "Byte Int16 Int32 Int64 Float32"
858 : " Float64 CInt16 CInt64 CFloat32 "
859 1381 : " CFloat64");
860 1381 : poDriver->SetMetadataItem(GDAL_DMD_CREATIONOPTIONLIST,
861 : "<CreationOptionList>"
862 : " <Option name='SCHEME' type='string-select'>"
863 : " <Value>BIP</Value>"
864 : " <Value>BIL</Value>"
865 : " <Value>BSQ</Value>"
866 : " </Option>"
867 1381 : "</CreationOptionList>");
868 1381 : poDriver->SetMetadataItem(GDAL_DCAP_RASTER, "YES");
869 1381 : poDriver->SetMetadataItem(GDAL_DCAP_VIRTUALIO, "YES");
870 :
871 1381 : poDriver->pfnOpen = ISCEDataset::Open;
872 1381 : poDriver->pfnCreate = ISCEDataset::Create;
873 :
874 1381 : GetGDALDriverManager()->RegisterDriver(poDriver);
875 : }
|