Line data Source code
1 : /******************************************************************************
2 : *
3 : * Project: ENVI .hdr Driver
4 : * Purpose: Implementation of ENVI .hdr labelled raw raster support.
5 : * Author: Frank Warmerdam, warmerdam@pobox.com
6 : * Maintainer: Chris Padwick (cpadwick at ittvis.com)
7 : *
8 : ******************************************************************************
9 : * Copyright (c) 2002, Frank Warmerdam
10 : * Copyright (c) 2007-2013, Even Rouault <even dot rouault at spatialys.com>
11 : *
12 : * SPDX-License-Identifier: MIT
13 : ****************************************************************************/
14 :
15 : #include "cpl_port.h"
16 : #include "envidataset.h"
17 : #include "gdal_priv.h"
18 : #include "rawdataset.h"
19 : #include "gdal_cpp_functions.h"
20 :
21 : #include <climits>
22 : #include <cmath>
23 : #include <cstdlib>
24 : #include <cstring>
25 :
26 : #include <algorithm>
27 : #include <limits>
28 : #include <string>
29 :
30 : #include "cpl_conv.h"
31 : #include "cpl_error.h"
32 : #include "cpl_string.h"
33 : #include "cpl_vsi.h"
34 : #include "gdal.h"
35 : #include "gdal_frmts.h"
36 : #include "gdal_priv.h"
37 : #include "ogr_core.h"
38 : #include "ogr_spatialref.h"
39 : #include "ogr_srs_api.h"
40 :
41 : // TODO(schwehr): This really should be defined in port/somewhere.h.
42 : constexpr double kdfDegToRad = M_PI / 180.0;
43 : constexpr double kdfRadToDeg = 180.0 / M_PI;
44 :
45 : #include "usgs_esri_zones.h"
46 :
47 : /************************************************************************/
48 : /* ITTVISToUSGSZone() */
49 : /* */
50 : /* Convert ITTVIS style state plane zones to NOS style state */
51 : /* plane zones. The ENVI default is to use the new NOS zones, */
52 : /* but the old state plane zones can be used. Handle this. */
53 : /************************************************************************/
54 :
55 0 : static int ITTVISToUSGSZone(int nITTVISZone)
56 :
57 : {
58 : // TODO(schwehr): int anUsgsEsriZones[] -> a std::set and std::map.
59 0 : const int nPairs = sizeof(anUsgsEsriZones) / (2 * sizeof(int));
60 :
61 : // Default is to use the zone as-is, as long as it is in the
62 : // available list
63 0 : for (int i = 0; i < nPairs; i++)
64 : {
65 0 : if (anUsgsEsriZones[i * 2] == nITTVISZone)
66 0 : return anUsgsEsriZones[i * 2];
67 : }
68 :
69 : // If not found in the new style, see if it is present in the
70 : // old style list and convert it. We don't expect to see this
71 : // often, but older files allowed it and may still exist.
72 0 : for (int i = 0; i < nPairs; i++)
73 : {
74 0 : if (anUsgsEsriZones[i * 2 + 1] == nITTVISZone)
75 0 : return anUsgsEsriZones[i * 2];
76 : }
77 :
78 0 : return nITTVISZone; // Perhaps it *is* the USGS zone?
79 : }
80 :
81 : /************************************************************************/
82 : /* ENVIDataset() */
83 : /************************************************************************/
84 :
85 259 : ENVIDataset::ENVIDataset()
86 : : fpImage(nullptr), fp(nullptr), pszHDRFilename(nullptr),
87 259 : bFoundMapinfo(false), bHeaderDirty(false), bFillFile(false)
88 : {
89 259 : }
90 :
91 : /************************************************************************/
92 : /* ~ENVIDataset() */
93 : /************************************************************************/
94 :
95 518 : ENVIDataset::~ENVIDataset()
96 :
97 : {
98 259 : ENVIDataset::Close();
99 518 : }
100 :
101 : /************************************************************************/
102 : /* Close() */
103 : /************************************************************************/
104 :
105 506 : CPLErr ENVIDataset::Close()
106 : {
107 506 : CPLErr eErr = CE_None;
108 506 : if (nOpenFlags != OPEN_FLAGS_CLOSED)
109 : {
110 259 : if (ENVIDataset::FlushCache(true) != CE_None)
111 0 : eErr = CE_Failure;
112 :
113 259 : if (fpImage)
114 : {
115 : // Make sure the binary file has the expected size
116 252 : if (!IsMarkedSuppressOnClose() && bFillFile && nBands > 0)
117 : {
118 93 : const int nDataSize = GDALGetDataTypeSizeBytes(
119 : GetRasterBand(1)->GetRasterDataType());
120 93 : const vsi_l_offset nExpectedFileSize =
121 93 : static_cast<vsi_l_offset>(nRasterXSize) * nRasterYSize *
122 93 : nBands * nDataSize;
123 93 : if (VSIFSeekL(fpImage, 0, SEEK_END) != 0)
124 : {
125 0 : eErr = CE_Failure;
126 0 : CPLError(CE_Failure, CPLE_FileIO, "I/O error");
127 : }
128 93 : if (VSIFTellL(fpImage) < nExpectedFileSize)
129 : {
130 42 : GByte byVal = 0;
131 42 : if (VSIFSeekL(fpImage, nExpectedFileSize - 1, SEEK_SET) !=
132 84 : 0 ||
133 42 : VSIFWriteL(&byVal, 1, 1, fpImage) == 0)
134 : {
135 0 : eErr = CE_Failure;
136 0 : CPLError(CE_Failure, CPLE_FileIO, "I/O error");
137 : }
138 : }
139 : }
140 252 : if (VSIFCloseL(fpImage) != 0)
141 : {
142 0 : eErr = CE_Failure;
143 0 : CPLError(CE_Failure, CPLE_FileIO, "I/O error");
144 : }
145 : }
146 259 : if (fp)
147 : {
148 259 : if (VSIFCloseL(fp) != 0)
149 : {
150 0 : eErr = CE_Failure;
151 0 : CPLError(CE_Failure, CPLE_FileIO, "I/O error");
152 : }
153 : }
154 259 : if (!m_asGCPs.empty())
155 : {
156 2 : GDALDeinitGCPs(static_cast<int>(m_asGCPs.size()), m_asGCPs.data());
157 : }
158 :
159 : // Should be called before pszHDRFilename is freed.
160 259 : CleanupPostFileClosing();
161 :
162 259 : CPLFree(pszHDRFilename);
163 :
164 259 : if (GDALPamDataset::Close() != CE_None)
165 0 : eErr = CE_Failure;
166 : }
167 506 : return eErr;
168 : }
169 :
170 : /************************************************************************/
171 : /* FlushCache() */
172 : /************************************************************************/
173 :
174 269 : CPLErr ENVIDataset::FlushCache(bool bAtClosing)
175 :
176 : {
177 269 : CPLErr eErr = RawDataset::FlushCache(bAtClosing);
178 :
179 269 : GDALRasterBand *band = GetRasterCount() > 0 ? GetRasterBand(1) : nullptr;
180 :
181 269 : if (!band || !bHeaderDirty || (bAtClosing && IsMarkedSuppressOnClose()))
182 185 : return eErr;
183 :
184 : // If opening an existing file in Update mode (i.e. "r+") we need to make
185 : // sure any existing content is cleared, otherwise the file may contain
186 : // trailing content from the previous write.
187 84 : if (VSIFTruncateL(fp, 0) != 0)
188 0 : return CE_Failure;
189 :
190 84 : if (VSIFSeekL(fp, 0, SEEK_SET) != 0)
191 0 : return CE_Failure;
192 :
193 : // Rewrite out the header.
194 84 : bool bOK = VSIFPrintfL(fp, "ENVI\n") >= 0;
195 84 : if ("" != sDescription)
196 84 : bOK &= VSIFPrintfL(fp, "description = {\n%s}\n",
197 84 : sDescription.c_str()) >= 0;
198 84 : bOK &= VSIFPrintfL(fp, "samples = %d\nlines = %d\nbands = %d\n",
199 84 : nRasterXSize, nRasterYSize, nBands) >= 0;
200 :
201 84 : char **catNames = band->GetCategoryNames();
202 :
203 84 : bOK &= VSIFPrintfL(fp, "header offset = 0\n") >= 0;
204 84 : if (nullptr == catNames)
205 83 : bOK &= VSIFPrintfL(fp, "file type = ENVI Standard\n") >= 0;
206 : else
207 1 : bOK &= VSIFPrintfL(fp, "file type = ENVI Classification\n") >= 0;
208 :
209 84 : const int iENVIType = GetEnviType(band->GetRasterDataType());
210 84 : bOK &= VSIFPrintfL(fp, "data type = %d\n", iENVIType) >= 0;
211 84 : const char *pszInterleaving = nullptr;
212 84 : switch (eInterleave)
213 : {
214 4 : case Interleave::BIP:
215 4 : pszInterleaving = "bip"; // Interleaved by pixel.
216 4 : break;
217 2 : case Interleave::BIL:
218 2 : pszInterleaving = "bil"; // Interleaved by line.
219 2 : break;
220 78 : case Interleave::BSQ:
221 78 : pszInterleaving = "bsq"; // Band sequential by default.
222 78 : break;
223 0 : default:
224 0 : pszInterleaving = "bsq";
225 0 : break;
226 : }
227 84 : bOK &= VSIFPrintfL(fp, "interleave = %s\n", pszInterleaving) >= 0;
228 :
229 84 : const char *pszByteOrder = m_aosHeader["byte_order"];
230 84 : if (pszByteOrder)
231 : {
232 : // Supposed to be required
233 84 : bOK &= VSIFPrintfL(fp, "byte order = %s\n", pszByteOrder) >= 0;
234 : }
235 :
236 : // Write class and color information.
237 84 : catNames = band->GetCategoryNames();
238 84 : if (nullptr != catNames)
239 : {
240 1 : int nrClasses = 0;
241 3 : while (*catNames++)
242 2 : ++nrClasses;
243 :
244 1 : if (nrClasses > 0)
245 : {
246 1 : bOK &= VSIFPrintfL(fp, "classes = %d\n", nrClasses) >= 0;
247 :
248 1 : GDALColorTable *colorTable = band->GetColorTable();
249 1 : if (nullptr != colorTable)
250 : {
251 : const int nrColors =
252 1 : std::min(nrClasses, colorTable->GetColorEntryCount());
253 1 : bOK &= VSIFPrintfL(fp, "class lookup = {\n") >= 0;
254 3 : for (int i = 0; i < nrColors; ++i)
255 : {
256 2 : const GDALColorEntry *color = colorTable->GetColorEntry(i);
257 4 : bOK &= VSIFPrintfL(fp, "%d, %d, %d", color->c1, color->c2,
258 2 : color->c3) >= 0;
259 2 : if (i < nrColors - 1)
260 : {
261 1 : bOK &= VSIFPrintfL(fp, ", ") >= 0;
262 1 : if (0 == (i + 1) % 5)
263 0 : bOK &= VSIFPrintfL(fp, "\n") >= 0;
264 : }
265 : }
266 1 : bOK &= VSIFPrintfL(fp, "}\n") >= 0;
267 : }
268 :
269 1 : catNames = band->GetCategoryNames();
270 1 : if (nullptr != *catNames)
271 : {
272 1 : bOK &= VSIFPrintfL(fp, "class names = {\n%s", *catNames) >= 0;
273 1 : catNames++;
274 1 : int i = 0;
275 2 : while (*catNames)
276 : {
277 1 : bOK &= VSIFPrintfL(fp, ",") >= 0;
278 1 : if (0 == (++i) % 5)
279 0 : bOK &= VSIFPrintfL(fp, "\n") >= 0;
280 1 : bOK &= VSIFPrintfL(fp, " %s", *catNames) >= 0;
281 1 : catNames++;
282 : }
283 1 : bOK &= VSIFPrintfL(fp, "}\n") >= 0;
284 : }
285 : }
286 : }
287 :
288 : // Write the rest of header.
289 :
290 : // Only one map info type should be set:
291 : // - rpc
292 : // - pseudo/gcp
293 : // - standard
294 84 : if (!WriteRpcInfo()) // Are rpcs in the metadata?
295 : {
296 83 : if (!WritePseudoGcpInfo()) // are gcps in the metadata
297 : {
298 82 : WriteProjectionInfo(); // standard - affine xform/coord sys str
299 : }
300 : }
301 :
302 84 : bOK &= VSIFPrintfL(fp, "band names = {\n") >= 0;
303 252 : for (int i = 1; i <= nBands; i++)
304 : {
305 336 : CPLString sBandDesc = GetRasterBand(i)->GetDescription();
306 :
307 168 : if (sBandDesc == "")
308 142 : sBandDesc = CPLSPrintf("Band %d", i);
309 168 : bOK &= VSIFPrintfL(fp, "%s", sBandDesc.c_str()) >= 0;
310 168 : if (i != nBands)
311 84 : bOK &= VSIFPrintfL(fp, ",\n") >= 0;
312 : }
313 84 : bOK &= VSIFPrintfL(fp, "}\n") >= 0;
314 :
315 84 : int bHasNoData = FALSE;
316 84 : double dfNoDataValue = band->GetNoDataValue(&bHasNoData);
317 84 : if (bHasNoData)
318 : {
319 6 : bOK &=
320 6 : VSIFPrintfL(fp, "data ignore value = %.17g\n", dfNoDataValue) >= 0;
321 : }
322 :
323 : // Write "data offset values", if needed
324 : {
325 84 : bool bHasOffset = false;
326 252 : for (int i = 1; i <= nBands; i++)
327 : {
328 168 : int bHasValue = FALSE;
329 168 : CPL_IGNORE_RET_VAL(GetRasterBand(i)->GetOffset(&bHasValue));
330 168 : if (bHasValue)
331 1 : bHasOffset = true;
332 : }
333 84 : if (bHasOffset)
334 : {
335 1 : bOK &= VSIFPrintfL(fp, "data offset values = {") >= 0;
336 4 : for (int i = 1; i <= nBands; i++)
337 : {
338 3 : int bHasValue = FALSE;
339 3 : double dfValue = GetRasterBand(i)->GetOffset(&bHasValue);
340 3 : if (!bHasValue)
341 2 : dfValue = 0;
342 3 : bOK &= VSIFPrintfL(fp, "%.17g", dfValue) >= 0;
343 3 : if (i != nBands)
344 2 : bOK &= VSIFPrintfL(fp, ", ") >= 0;
345 : }
346 1 : bOK &= VSIFPrintfL(fp, "}\n") >= 0;
347 : }
348 : }
349 :
350 : // Write "data gain values", if needed
351 : {
352 84 : bool bHasScale = false;
353 252 : for (int i = 1; i <= nBands; i++)
354 : {
355 168 : int bHasValue = FALSE;
356 168 : CPL_IGNORE_RET_VAL(GetRasterBand(i)->GetScale(&bHasValue));
357 168 : if (bHasValue)
358 1 : bHasScale = true;
359 : }
360 84 : if (bHasScale)
361 : {
362 1 : bOK &= VSIFPrintfL(fp, "data gain values = {") >= 0;
363 4 : for (int i = 1; i <= nBands; i++)
364 : {
365 3 : int bHasValue = FALSE;
366 3 : double dfValue = GetRasterBand(i)->GetScale(&bHasValue);
367 3 : if (!bHasValue)
368 2 : dfValue = 1;
369 3 : bOK &= VSIFPrintfL(fp, "%.17g", dfValue) >= 0;
370 3 : if (i != nBands)
371 2 : bOK &= VSIFPrintfL(fp, ", ") >= 0;
372 : }
373 1 : bOK &= VSIFPrintfL(fp, "}\n") >= 0;
374 : }
375 : }
376 :
377 : // Write the metadata that was read into the ENVI domain.
378 84 : char **papszENVIMetadata = GetMetadata("ENVI");
379 168 : if (CSLFetchNameValue(papszENVIMetadata, "default bands") == nullptr &&
380 84 : CSLFetchNameValue(papszENVIMetadata, "default_bands") == nullptr)
381 : {
382 83 : int nGrayBand = 0;
383 83 : int nRBand = 0;
384 83 : int nGBand = 0;
385 83 : int nBBand = 0;
386 250 : for (int i = 1; i <= nBands; i++)
387 : {
388 167 : const auto eInterp = GetRasterBand(i)->GetColorInterpretation();
389 167 : if (eInterp == GCI_GrayIndex)
390 : {
391 5 : if (nGrayBand == 0)
392 4 : nGrayBand = i;
393 : else
394 1 : nGrayBand = -1;
395 : }
396 162 : else if (eInterp == GCI_RedBand)
397 : {
398 5 : if (nRBand == 0)
399 4 : nRBand = i;
400 : else
401 1 : nRBand = -1;
402 : }
403 157 : else if (eInterp == GCI_GreenBand)
404 : {
405 5 : if (nGBand == 0)
406 4 : nGBand = i;
407 : else
408 1 : nGBand = -1;
409 : }
410 152 : else if (eInterp == GCI_BlueBand)
411 : {
412 5 : if (nBBand == 0)
413 4 : nBBand = i;
414 : else
415 1 : nBBand = -1;
416 : }
417 : }
418 83 : if (nRBand > 0 && nGBand > 0 && nBBand > 0)
419 : {
420 3 : bOK &= VSIFPrintfL(fp, "default bands = {%d, %d, %d}\n", nRBand,
421 3 : nGBand, nBBand) >= 0;
422 : }
423 80 : else if (nGrayBand > 0 && nRBand == 0 && nGBand == 0 && nBBand == 0)
424 : {
425 3 : bOK &= VSIFPrintfL(fp, "default bands = {%d}\n", nGrayBand) >= 0;
426 : }
427 : }
428 :
429 84 : const int count = CSLCount(papszENVIMetadata);
430 :
431 : // For every item of metadata in the ENVI domain.
432 763 : for (int i = 0; i < count; i++)
433 : {
434 : // Split the entry into two parts at the = character.
435 679 : char *pszEntry = papszENVIMetadata[i];
436 679 : char **papszTokens = CSLTokenizeString2(
437 : pszEntry, "=", CSLT_STRIPLEADSPACES | CSLT_STRIPENDSPACES);
438 :
439 679 : if (CSLCount(papszTokens) != 2)
440 : {
441 1 : CPLDebug("ENVI",
442 : "Line of header file could not be split at = into "
443 : "two elements: %s",
444 1 : papszENVIMetadata[i]);
445 1 : CSLDestroy(papszTokens);
446 677 : continue;
447 : }
448 : // Replace _'s in the string with spaces
449 678 : std::string poKey(papszTokens[0]);
450 678 : std::replace(poKey.begin(), poKey.end(), '_', ' ');
451 :
452 : // Don't write it out if it is one of the bits of metadata that is
453 : // written out elsewhere in this routine.
454 1861 : if (poKey == "description" || poKey == "samples" || poKey == "lines" ||
455 1275 : poKey == "bands" || poKey == "header offset" ||
456 777 : poKey == "file type" || poKey == "data type" ||
457 279 : poKey == "interleave" || poKey == "byte order" ||
458 27 : poKey == "class names" || poKey == "band names" ||
459 17 : poKey == "map info" || poKey == "projection info" ||
460 15 : poKey == "data ignore value" || poKey == "data offset values" ||
461 1358 : poKey == "data gain values" || poKey == "coordinate system string")
462 : {
463 676 : CSLDestroy(papszTokens);
464 676 : continue;
465 : }
466 2 : bOK &= VSIFPrintfL(fp, "%s = %s\n", poKey.c_str(), papszTokens[1]) >= 0;
467 2 : CSLDestroy(papszTokens);
468 : }
469 :
470 84 : if (!bOK)
471 0 : return CE_Failure;
472 :
473 84 : bHeaderDirty = false;
474 84 : return eErr;
475 : }
476 :
477 : /************************************************************************/
478 : /* GetFileList() */
479 : /************************************************************************/
480 :
481 54 : char **ENVIDataset::GetFileList()
482 :
483 : {
484 : // Main data file, etc.
485 54 : char **papszFileList = RawDataset::GetFileList();
486 :
487 : // Header file.
488 54 : papszFileList = CSLAddString(papszFileList, pszHDRFilename);
489 :
490 : // Statistics file
491 54 : if (!osStaFilename.empty())
492 0 : papszFileList = CSLAddString(papszFileList, osStaFilename);
493 :
494 54 : return papszFileList;
495 : }
496 :
497 : /************************************************************************/
498 : /* GetEPSGGeogCS() */
499 : /* */
500 : /* Try to establish what the EPSG code for this coordinate */
501 : /* systems GEOGCS might be. Returns -1 if no reasonable guess */
502 : /* can be made. */
503 : /* */
504 : /* TODO: We really need to do some name lookups. */
505 : /************************************************************************/
506 :
507 67 : static int ENVIGetEPSGGeogCS(const OGRSpatialReference *poThis)
508 :
509 : {
510 67 : const char *pszAuthName = poThis->GetAuthorityName("GEOGCS");
511 :
512 : // Do we already have it?
513 67 : if (pszAuthName != nullptr && EQUAL(pszAuthName, "epsg"))
514 15 : return atoi(poThis->GetAuthorityCode("GEOGCS"));
515 :
516 : // Get the datum and geogcs names.
517 52 : const char *pszGEOGCS = poThis->GetAttrValue("GEOGCS");
518 52 : const char *pszDatum = poThis->GetAttrValue("DATUM");
519 :
520 : // We can only operate on coordinate systems with a geogcs.
521 52 : if (pszGEOGCS == nullptr || pszDatum == nullptr)
522 0 : return -1;
523 :
524 : // Is this a "well known" geographic coordinate system?
525 6 : const bool bWGS = strstr(pszGEOGCS, "WGS") || strstr(pszDatum, "WGS") ||
526 6 : strstr(pszGEOGCS, "World Geodetic System") ||
527 6 : strstr(pszGEOGCS, "World_Geodetic_System") ||
528 64 : strstr(pszDatum, "World Geodetic System") ||
529 6 : strstr(pszDatum, "World_Geodetic_System");
530 :
531 51 : const bool bNAD = strstr(pszGEOGCS, "NAD") || strstr(pszDatum, "NAD") ||
532 51 : strstr(pszGEOGCS, "North American") ||
533 51 : strstr(pszGEOGCS, "North_American") ||
534 154 : strstr(pszDatum, "North American") ||
535 51 : strstr(pszDatum, "North_American");
536 :
537 52 : if (bWGS && (strstr(pszGEOGCS, "84") || strstr(pszDatum, "84")))
538 46 : return 4326;
539 :
540 6 : if (bWGS && (strstr(pszGEOGCS, "72") || strstr(pszDatum, "72")))
541 0 : return 4322;
542 :
543 6 : if (bNAD && (strstr(pszGEOGCS, "83") || strstr(pszDatum, "83")))
544 1 : return 4269;
545 :
546 5 : if (bNAD && (strstr(pszGEOGCS, "27") || strstr(pszDatum, "27")))
547 0 : return 4267;
548 :
549 : // If we know the datum, associate the most likely GCS with it.
550 5 : pszAuthName = poThis->GetAuthorityName("GEOGCS|DATUM");
551 :
552 5 : if (pszAuthName != nullptr && EQUAL(pszAuthName, "epsg") &&
553 0 : poThis->GetPrimeMeridian() == 0.0)
554 : {
555 0 : const int nDatum = atoi(poThis->GetAuthorityCode("GEOGCS|DATUM"));
556 :
557 0 : if (nDatum >= 6000 && nDatum <= 6999)
558 0 : return nDatum - 2000;
559 : }
560 :
561 5 : return -1;
562 : }
563 :
564 : /************************************************************************/
565 : /* WriteProjectionInfo() */
566 : /************************************************************************/
567 :
568 82 : void ENVIDataset::WriteProjectionInfo()
569 :
570 : {
571 : // Format the location (geotransform) portion of the map info line.
572 82 : CPLString osLocation;
573 82 : CPLString osRotation;
574 :
575 82 : const double dfPixelXSize = sqrt(m_gt[1] * m_gt[1] + m_gt[2] * m_gt[2]);
576 82 : const double dfPixelYSize = sqrt(m_gt[4] * m_gt[4] + m_gt[5] * m_gt[5]);
577 100 : const bool bHasNonDefaultGT = m_gt[0] != 0.0 || m_gt[1] != 1.0 ||
578 16 : m_gt[2] != 0.0 || m_gt[3] != 0.0 ||
579 100 : m_gt[4] != 0.0 || m_gt[5] != 1.0;
580 82 : if (m_gt[1] > 0.0 && m_gt[2] == 0.0 && m_gt[4] == 0.0 && m_gt[5] > 0.0)
581 : {
582 16 : osRotation = ", rotation=180";
583 : }
584 66 : else if (bHasNonDefaultGT)
585 : {
586 66 : const double dfRotation1 = -atan2(-m_gt[2], m_gt[1]) * kdfRadToDeg;
587 66 : const double dfRotation2 = -atan2(-m_gt[4], -m_gt[5]) * kdfRadToDeg;
588 66 : const double dfRotation = (dfRotation1 + dfRotation2) / 2.0;
589 :
590 66 : if (fabs(dfRotation1 - dfRotation2) > 1e-5)
591 : {
592 0 : CPLDebug("ENVI", "rot1 = %.15g, rot2 = %.15g", dfRotation1,
593 : dfRotation2);
594 0 : CPLError(CE_Warning, CPLE_AppDefined,
595 : "Geotransform matrix has non rotational terms");
596 : }
597 66 : if (fabs(dfRotation) > 1e-5)
598 : {
599 1 : osRotation.Printf(", rotation=%.15g", dfRotation);
600 : }
601 : }
602 :
603 82 : osLocation.Printf("1, 1, %.15g, %.15g, %.15g, %.15g", m_gt[0], m_gt[3],
604 82 : dfPixelXSize, dfPixelYSize);
605 :
606 : // Minimal case - write out simple geotransform if we have a
607 : // non-default geotransform.
608 82 : if (m_oSRS.IsEmpty() || m_oSRS.IsLocal())
609 : {
610 15 : if (bHasNonDefaultGT)
611 : {
612 2 : const char *pszHemisphere = "North";
613 2 : if (VSIFPrintfL(fp, "map info = {Arbitrary, %s, %d, %s%s}\n",
614 : osLocation.c_str(), 0, pszHemisphere,
615 2 : osRotation.c_str()) < 0)
616 0 : return;
617 : }
618 15 : return;
619 : }
620 :
621 : // Try to translate the datum and get major/minor ellipsoid values.
622 67 : const OGRSpatialReference &oSRS = m_oSRS;
623 67 : const int nEPSG_GCS = ENVIGetEPSGGeogCS(&oSRS);
624 134 : CPLString osDatum;
625 :
626 67 : if (nEPSG_GCS == 4326)
627 59 : osDatum = "WGS-84";
628 8 : else if (nEPSG_GCS == 4322)
629 0 : osDatum = "WGS-72";
630 8 : else if (nEPSG_GCS == 4269)
631 1 : osDatum = "North America 1983";
632 7 : else if (nEPSG_GCS == 4267)
633 2 : osDatum = "North America 1927";
634 5 : else if (nEPSG_GCS == 4230)
635 0 : osDatum = "European 1950";
636 5 : else if (nEPSG_GCS == 4277)
637 0 : osDatum = "Ordnance Survey of Great Britain '36";
638 5 : else if (nEPSG_GCS == 4291)
639 0 : osDatum = "SAD-69/Brazil";
640 5 : else if (nEPSG_GCS == 4283)
641 0 : osDatum = "Geocentric Datum of Australia 1994";
642 5 : else if (nEPSG_GCS == 4275)
643 0 : osDatum = "Nouvelle Triangulation Francaise IGN";
644 :
645 201 : const CPLString osCommaDatum = osDatum.empty() ? "" : ("," + osDatum);
646 :
647 67 : const double dfA = oSRS.GetSemiMajor();
648 67 : const double dfB = oSRS.GetSemiMinor();
649 :
650 : // Do we have unusual linear units?
651 67 : const double dfFeetPerMeter = 0.3048;
652 : const CPLString osOptionalUnits =
653 67 : fabs(oSRS.GetLinearUnits() - dfFeetPerMeter) < 0.0001 ? ", units=Feet"
654 134 : : "";
655 :
656 : // Handle UTM case.
657 67 : const char *pszProjName = oSRS.GetAttrValue("PROJECTION");
658 67 : int bNorth = FALSE;
659 67 : const int iUTMZone = oSRS.GetUTMZone(&bNorth);
660 67 : bool bOK = true;
661 67 : if (iUTMZone)
662 : {
663 3 : const char *pszHemisphere = bNorth ? "North" : "South";
664 :
665 3 : bOK &= VSIFPrintfL(fp, "map info = {UTM, %s, %d, %s%s%s%s}\n",
666 : osLocation.c_str(), iUTMZone, pszHemisphere,
667 : osCommaDatum.c_str(), osOptionalUnits.c_str(),
668 3 : osRotation.c_str()) >= 0;
669 : }
670 64 : else if (oSRS.IsGeographic())
671 : {
672 57 : bOK &= VSIFPrintfL(fp, "map info = {Geographic Lat/Lon, %s%s%s}\n",
673 : osLocation.c_str(), osCommaDatum.c_str(),
674 57 : osRotation.c_str()) >= 0;
675 : }
676 7 : else if (pszProjName == nullptr)
677 : {
678 : // What to do?
679 : }
680 7 : else if (EQUAL(pszProjName, SRS_PT_NEW_ZEALAND_MAP_GRID))
681 : {
682 0 : bOK &= VSIFPrintfL(fp, "map info = {New Zealand Map Grid, %s%s%s%s}\n",
683 : osLocation.c_str(), osCommaDatum.c_str(),
684 0 : osOptionalUnits.c_str(), osRotation.c_str()) >= 0;
685 :
686 0 : bOK &= VSIFPrintfL(fp,
687 : "projection info = {39, %.16g, %.16g, %.16g, %.16g, "
688 : "%.16g, %.16g%s, New Zealand Map Grid}\n",
689 : dfA, dfB,
690 : oSRS.GetNormProjParm(SRS_PP_LATITUDE_OF_ORIGIN, 0.0),
691 : oSRS.GetNormProjParm(SRS_PP_CENTRAL_MERIDIAN, 0.0),
692 : oSRS.GetNormProjParm(SRS_PP_FALSE_EASTING, 0.0),
693 : oSRS.GetNormProjParm(SRS_PP_FALSE_NORTHING, 0.0),
694 0 : osCommaDatum.c_str()) >= 0;
695 : }
696 7 : else if (EQUAL(pszProjName, SRS_PT_TRANSVERSE_MERCATOR))
697 : {
698 1 : bOK &= VSIFPrintfL(fp, "map info = {Transverse Mercator, %s%s%s%s}\n",
699 : osLocation.c_str(), osCommaDatum.c_str(),
700 1 : osOptionalUnits.c_str(), osRotation.c_str()) >= 0;
701 :
702 1 : bOK &= VSIFPrintfL(fp,
703 : "projection info = {3, %.16g, %.16g, %.16g, "
704 : "%.16g, %.16g, "
705 : "%.16g, %.16g%s, Transverse Mercator}\n",
706 : dfA, dfB,
707 : oSRS.GetNormProjParm(SRS_PP_LATITUDE_OF_ORIGIN, 0.0),
708 : oSRS.GetNormProjParm(SRS_PP_CENTRAL_MERIDIAN, 0.0),
709 : oSRS.GetNormProjParm(SRS_PP_FALSE_EASTING, 0.0),
710 : oSRS.GetNormProjParm(SRS_PP_FALSE_NORTHING, 0.0),
711 : oSRS.GetNormProjParm(SRS_PP_SCALE_FACTOR, 1.0),
712 1 : osCommaDatum.c_str()) >= 0;
713 : }
714 6 : else if (EQUAL(pszProjName, SRS_PT_LAMBERT_CONFORMAL_CONIC_2SP) ||
715 4 : EQUAL(pszProjName, SRS_PT_LAMBERT_CONFORMAL_CONIC_2SP_BELGIUM))
716 : {
717 2 : bOK &=
718 2 : VSIFPrintfL(fp, "map info = {Lambert Conformal Conic, %s%s%s%s}\n",
719 : osLocation.c_str(), osCommaDatum.c_str(),
720 2 : osOptionalUnits.c_str(), osRotation.c_str()) >= 0;
721 :
722 2 : bOK &=
723 2 : VSIFPrintfL(
724 : fp,
725 : "projection info = {4, %.16g, %.16g, %.16g, %.16g, %.16g, "
726 : "%.16g, %.16g, %.16g%s, Lambert Conformal Conic}\n",
727 : dfA, dfB, oSRS.GetNormProjParm(SRS_PP_LATITUDE_OF_ORIGIN, 0.0),
728 : oSRS.GetNormProjParm(SRS_PP_CENTRAL_MERIDIAN, 0.0),
729 : oSRS.GetNormProjParm(SRS_PP_FALSE_EASTING, 0.0),
730 : oSRS.GetNormProjParm(SRS_PP_FALSE_NORTHING, 0.0),
731 : oSRS.GetNormProjParm(SRS_PP_STANDARD_PARALLEL_1, 0.0),
732 : oSRS.GetNormProjParm(SRS_PP_STANDARD_PARALLEL_2, 0.0),
733 2 : osCommaDatum.c_str()) >= 0;
734 : }
735 4 : else if (EQUAL(pszProjName,
736 : SRS_PT_HOTINE_OBLIQUE_MERCATOR_TWO_POINT_NATURAL_ORIGIN))
737 : {
738 0 : bOK &= VSIFPrintfL(fp,
739 : "map info = {Hotine Oblique Mercator A, %s%s%s%s}\n",
740 : osLocation.c_str(), osCommaDatum.c_str(),
741 0 : osOptionalUnits.c_str(), osRotation.c_str()) >= 0;
742 :
743 0 : bOK &=
744 0 : VSIFPrintfL(
745 : fp,
746 : "projection info = {5, %.16g, %.16g, %.16g, %.16g, %.16g, "
747 : "%.16g, %.16g, %.16g, %.16g, %.16g%s, "
748 : "Hotine Oblique Mercator A}\n",
749 : dfA, dfB, oSRS.GetNormProjParm(SRS_PP_LATITUDE_OF_ORIGIN, 0.0),
750 : oSRS.GetNormProjParm(SRS_PP_LATITUDE_OF_POINT_1, 0.0),
751 : oSRS.GetNormProjParm(SRS_PP_LONGITUDE_OF_POINT_1, 0.0),
752 : oSRS.GetNormProjParm(SRS_PP_LATITUDE_OF_POINT_2, 0.0),
753 : oSRS.GetNormProjParm(SRS_PP_LONGITUDE_OF_POINT_2, 0.0),
754 : oSRS.GetNormProjParm(SRS_PP_FALSE_EASTING, 0.0),
755 : oSRS.GetNormProjParm(SRS_PP_FALSE_NORTHING, 0.0),
756 : oSRS.GetNormProjParm(SRS_PP_SCALE_FACTOR, 1.0),
757 0 : osCommaDatum.c_str()) >= 0;
758 : }
759 4 : else if (EQUAL(pszProjName, SRS_PT_HOTINE_OBLIQUE_MERCATOR))
760 : {
761 0 : bOK &= VSIFPrintfL(fp,
762 : "map info = {Hotine Oblique Mercator B, %s%s%s%s}\n",
763 : osLocation.c_str(), osCommaDatum.c_str(),
764 0 : osOptionalUnits.c_str(), osRotation.c_str()) >= 0;
765 :
766 0 : bOK &=
767 0 : VSIFPrintfL(
768 : fp,
769 : "projection info = {6, %.16g, %.16g, %.16g, %.16g, %.16g, "
770 : "%.16g, %.16g, %.16g%s, Hotine Oblique Mercator B}\n",
771 : dfA, dfB, oSRS.GetNormProjParm(SRS_PP_LATITUDE_OF_ORIGIN, 0.0),
772 : oSRS.GetNormProjParm(SRS_PP_CENTRAL_MERIDIAN, 0.0),
773 : oSRS.GetNormProjParm(SRS_PP_AZIMUTH, 0.0),
774 : oSRS.GetNormProjParm(SRS_PP_FALSE_EASTING, 0.0),
775 : oSRS.GetNormProjParm(SRS_PP_FALSE_NORTHING, 0.0),
776 : oSRS.GetNormProjParm(SRS_PP_SCALE_FACTOR, 1.0),
777 0 : osCommaDatum.c_str()) >= 0;
778 : }
779 4 : else if (EQUAL(pszProjName, SRS_PT_STEREOGRAPHIC) ||
780 4 : EQUAL(pszProjName, SRS_PT_OBLIQUE_STEREOGRAPHIC))
781 : {
782 0 : bOK &= VSIFPrintfL(fp,
783 : "map info = {Stereographic (ellipsoid), %s%s%s%s}\n",
784 : osLocation.c_str(), osCommaDatum.c_str(),
785 0 : osOptionalUnits.c_str(), osRotation.c_str()) >= 0;
786 :
787 0 : bOK &=
788 0 : VSIFPrintfL(
789 : fp,
790 : "projection info = {7, %.16g, %.16g, %.16g, %.16g, %.16g, "
791 : "%.16g, %.16g, %s, Stereographic (ellipsoid)}\n",
792 : dfA, dfB, oSRS.GetNormProjParm(SRS_PP_LATITUDE_OF_ORIGIN, 0.0),
793 : oSRS.GetNormProjParm(SRS_PP_CENTRAL_MERIDIAN, 0.0),
794 : oSRS.GetNormProjParm(SRS_PP_FALSE_EASTING, 0.0),
795 : oSRS.GetNormProjParm(SRS_PP_FALSE_NORTHING, 0.0),
796 : oSRS.GetNormProjParm(SRS_PP_SCALE_FACTOR, 1.0),
797 0 : osCommaDatum.c_str()) >= 0;
798 : }
799 4 : else if (EQUAL(pszProjName, SRS_PT_ALBERS_CONIC_EQUAL_AREA))
800 : {
801 3 : bOK &= VSIFPrintfL(fp,
802 : "map info = {Albers Conical Equal Area, %s%s%s%s}\n",
803 : osLocation.c_str(), osCommaDatum.c_str(),
804 3 : osOptionalUnits.c_str(), osRotation.c_str()) >= 0;
805 :
806 3 : bOK &=
807 3 : VSIFPrintfL(
808 : fp,
809 : "projection info = {9, %.16g, %.16g, %.16g, %.16g, %.16g, "
810 : "%.16g, %.16g, %.16g%s, Albers Conical Equal Area}\n",
811 : dfA, dfB, oSRS.GetNormProjParm(SRS_PP_LATITUDE_OF_ORIGIN, 0.0),
812 : oSRS.GetNormProjParm(SRS_PP_CENTRAL_MERIDIAN, 0.0),
813 : oSRS.GetNormProjParm(SRS_PP_FALSE_EASTING, 0.0),
814 : oSRS.GetNormProjParm(SRS_PP_FALSE_NORTHING, 0.0),
815 : oSRS.GetNormProjParm(SRS_PP_STANDARD_PARALLEL_1, 0.0),
816 : oSRS.GetNormProjParm(SRS_PP_STANDARD_PARALLEL_2, 0.0),
817 3 : osCommaDatum.c_str()) >= 0;
818 : }
819 1 : else if (EQUAL(pszProjName, SRS_PT_POLYCONIC))
820 : {
821 0 : bOK &= VSIFPrintfL(fp, "map info = {Polyconic, %s%s%s%s}\n",
822 : osLocation.c_str(), osCommaDatum.c_str(),
823 0 : osOptionalUnits.c_str(), osRotation.c_str()) >= 0;
824 :
825 0 : bOK &=
826 0 : VSIFPrintfL(
827 : fp,
828 : "projection info = {10, %.16g, %.16g, %.16g, %.16g, %.16g, "
829 : "%.16g%s, Polyconic}\n",
830 : dfA, dfB, oSRS.GetNormProjParm(SRS_PP_LATITUDE_OF_ORIGIN, 0.0),
831 : oSRS.GetNormProjParm(SRS_PP_CENTRAL_MERIDIAN, 0.0),
832 : oSRS.GetNormProjParm(SRS_PP_FALSE_EASTING, 0.0),
833 : oSRS.GetNormProjParm(SRS_PP_FALSE_NORTHING, 0.0),
834 0 : osCommaDatum.c_str()) >= 0;
835 : }
836 1 : else if (EQUAL(pszProjName, SRS_PT_LAMBERT_AZIMUTHAL_EQUAL_AREA))
837 : {
838 1 : bOK &= VSIFPrintfL(
839 : fp, "map info = {Lambert Azimuthal Equal Area, %s%s%s%s}\n",
840 : osLocation.c_str(), osCommaDatum.c_str(),
841 1 : osOptionalUnits.c_str(), osRotation.c_str()) >= 0;
842 :
843 1 : bOK &=
844 1 : VSIFPrintfL(
845 : fp,
846 : "projection info = {11, %.16g, %.16g, %.16g, %.16g, %.16g, "
847 : "%.16g%s, Lambert Azimuthal Equal Area}\n",
848 : dfA, dfB, oSRS.GetNormProjParm(SRS_PP_LATITUDE_OF_ORIGIN, 0.0),
849 : oSRS.GetNormProjParm(SRS_PP_CENTRAL_MERIDIAN, 0.0),
850 : oSRS.GetNormProjParm(SRS_PP_FALSE_EASTING, 0.0),
851 : oSRS.GetNormProjParm(SRS_PP_FALSE_NORTHING, 0.0),
852 1 : osCommaDatum.c_str()) >= 0;
853 : }
854 0 : else if (EQUAL(pszProjName, SRS_PT_AZIMUTHAL_EQUIDISTANT))
855 : {
856 0 : bOK &= VSIFPrintfL(fp, "map info = {Azimuthal Equadistant, %s%s%s%s}\n",
857 : osLocation.c_str(), osCommaDatum.c_str(),
858 0 : osOptionalUnits.c_str(), osRotation.c_str()) >= 0;
859 :
860 0 : bOK &=
861 0 : VSIFPrintfL(
862 : fp,
863 : "projection info = {12, %.16g, %.16g, %.16g, %.16g, %.16g, "
864 : "%.16g%s, Azimuthal Equadistant}\n",
865 : dfA, dfB, oSRS.GetNormProjParm(SRS_PP_LATITUDE_OF_ORIGIN, 0.0),
866 : oSRS.GetNormProjParm(SRS_PP_CENTRAL_MERIDIAN, 0.0),
867 : oSRS.GetNormProjParm(SRS_PP_FALSE_EASTING, 0.0),
868 : oSRS.GetNormProjParm(SRS_PP_FALSE_NORTHING, 0.0),
869 0 : osCommaDatum.c_str()) >= 0;
870 : }
871 0 : else if (EQUAL(pszProjName, SRS_PT_POLAR_STEREOGRAPHIC))
872 : {
873 0 : bOK &= VSIFPrintfL(fp, "map info = {Polar Stereographic, %s%s%s%s}\n",
874 : osLocation.c_str(), osCommaDatum.c_str(),
875 0 : osOptionalUnits.c_str(), osRotation.c_str()) >= 0;
876 :
877 0 : bOK &=
878 0 : VSIFPrintfL(
879 : fp,
880 : "projection info = {31, %.16g, %.16g, %.16g, %.16g, %.16g, "
881 : "%.16g%s, Polar Stereographic}\n",
882 : dfA, dfB, oSRS.GetNormProjParm(SRS_PP_LATITUDE_OF_ORIGIN, 90.0),
883 : oSRS.GetNormProjParm(SRS_PP_CENTRAL_MERIDIAN, 0.0),
884 : oSRS.GetNormProjParm(SRS_PP_FALSE_EASTING, 0.0),
885 : oSRS.GetNormProjParm(SRS_PP_FALSE_NORTHING, 0.0),
886 0 : osCommaDatum.c_str()) >= 0;
887 : }
888 : else
889 : {
890 0 : bOK &= VSIFPrintfL(fp, "map info = {%s, %s}\n", pszProjName,
891 0 : osLocation.c_str()) >= 0;
892 : }
893 :
894 : // write out coordinate system string
895 67 : char *pszProjESRI = nullptr;
896 67 : const char *const apszOptions[] = {"FORMAT=WKT1_ESRI", nullptr};
897 67 : if (oSRS.exportToWkt(&pszProjESRI, apszOptions) == OGRERR_NONE)
898 : {
899 67 : if (strlen(pszProjESRI))
900 67 : bOK &= VSIFPrintfL(fp, "coordinate system string = {%s}\n",
901 67 : pszProjESRI) >= 0;
902 : }
903 67 : CPLFree(pszProjESRI);
904 67 : pszProjESRI = nullptr;
905 :
906 67 : if (!bOK)
907 : {
908 0 : CPLError(CE_Failure, CPLE_FileIO, "Write error");
909 : }
910 : }
911 :
912 : /************************************************************************/
913 : /* ParseRpcCoeffsMetaDataString() */
914 : /************************************************************************/
915 :
916 4 : bool ENVIDataset::ParseRpcCoeffsMetaDataString(const char *psName,
917 : char **papszVal, int &idx)
918 : {
919 : // Separate one string with 20 coefficients into an array of 20 strings.
920 4 : const char *psz20Vals = GetMetadataItem(psName, "RPC");
921 4 : if (!psz20Vals)
922 0 : return false;
923 :
924 4 : char **papszArr = CSLTokenizeString2(psz20Vals, " ", 0);
925 4 : if (!papszArr)
926 0 : return false;
927 :
928 4 : int x = 0;
929 84 : while ((x < 20) && (papszArr[x] != nullptr))
930 : {
931 80 : papszVal[idx++] = CPLStrdup(papszArr[x]);
932 80 : x++;
933 : }
934 :
935 4 : CSLDestroy(papszArr);
936 :
937 4 : return x == 20;
938 : }
939 :
940 843 : static char *CPLStrdupIfNotNull(const char *pszString)
941 : {
942 843 : if (!pszString)
943 830 : return nullptr;
944 :
945 13 : return CPLStrdup(pszString);
946 : }
947 :
948 : /************************************************************************/
949 : /* WriteRpcInfo() */
950 : /************************************************************************/
951 :
952 : // TODO: This whole function needs to be cleaned up.
953 84 : bool ENVIDataset::WriteRpcInfo()
954 : {
955 : // Write out 90 rpc coeffs into the envi header plus 3 envi specific rpc
956 : // values returns 0 if the coeffs are not present or not valid.
957 84 : int idx = 0;
958 : // TODO(schwehr): Make 93 a constant.
959 84 : char *papszVal[93] = {nullptr};
960 :
961 84 : papszVal[idx++] = CPLStrdupIfNotNull(GetMetadataItem("LINE_OFF", "RPC"));
962 84 : papszVal[idx++] = CPLStrdupIfNotNull(GetMetadataItem("SAMP_OFF", "RPC"));
963 84 : papszVal[idx++] = CPLStrdupIfNotNull(GetMetadataItem("LAT_OFF", "RPC"));
964 84 : papszVal[idx++] = CPLStrdupIfNotNull(GetMetadataItem("LONG_OFF", "RPC"));
965 84 : papszVal[idx++] = CPLStrdupIfNotNull(GetMetadataItem("HEIGHT_OFF", "RPC"));
966 84 : papszVal[idx++] = CPLStrdupIfNotNull(GetMetadataItem("LINE_SCALE", "RPC"));
967 84 : papszVal[idx++] = CPLStrdupIfNotNull(GetMetadataItem("SAMP_SCALE", "RPC"));
968 84 : papszVal[idx++] = CPLStrdupIfNotNull(GetMetadataItem("LAT_SCALE", "RPC"));
969 84 : papszVal[idx++] = CPLStrdupIfNotNull(GetMetadataItem("LONG_SCALE", "RPC"));
970 84 : papszVal[idx++] =
971 84 : CPLStrdupIfNotNull(GetMetadataItem("HEIGHT_SCALE", "RPC"));
972 :
973 84 : bool bRet = false;
974 :
975 94 : for (int x = 0; x < 10; x++) // If we do not have 10 values we return 0.
976 : {
977 93 : if (!papszVal[x])
978 83 : goto end;
979 : }
980 :
981 1 : if (!ParseRpcCoeffsMetaDataString("LINE_NUM_COEFF", papszVal, idx))
982 0 : goto end;
983 :
984 1 : if (!ParseRpcCoeffsMetaDataString("LINE_DEN_COEFF", papszVal, idx))
985 0 : goto end;
986 :
987 1 : if (!ParseRpcCoeffsMetaDataString("SAMP_NUM_COEFF", papszVal, idx))
988 0 : goto end;
989 :
990 1 : if (!ParseRpcCoeffsMetaDataString("SAMP_DEN_COEFF", papszVal, idx))
991 0 : goto end;
992 :
993 1 : papszVal[idx++] =
994 1 : CPLStrdupIfNotNull(GetMetadataItem("TILE_ROW_OFFSET", "RPC"));
995 1 : papszVal[idx++] =
996 1 : CPLStrdupIfNotNull(GetMetadataItem("TILE_COL_OFFSET", "RPC"));
997 1 : papszVal[idx++] =
998 1 : CPLStrdupIfNotNull(GetMetadataItem("ENVI_RPC_EMULATION", "RPC"));
999 1 : CPLAssert(idx == 93);
1000 4 : for (int x = 90; x < 93; x++)
1001 : {
1002 3 : if (!papszVal[x])
1003 0 : goto end;
1004 : }
1005 :
1006 : // All the needed 93 values are present so write the rpcs into the envi
1007 : // header.
1008 1 : bRet = true;
1009 : {
1010 1 : int x = 1;
1011 1 : bRet &= VSIFPrintfL(fp, "rpc info = {\n") >= 0;
1012 94 : for (int iR = 0; iR < 93; iR++)
1013 : {
1014 93 : if (papszVal[iR][0] == '-')
1015 8 : bRet &= VSIFPrintfL(fp, " %s", papszVal[iR]) >= 0;
1016 : else
1017 85 : bRet &= VSIFPrintfL(fp, " %s", papszVal[iR]) >= 0;
1018 :
1019 93 : if (iR < 92)
1020 92 : bRet &= VSIFPrintfL(fp, ",") >= 0;
1021 :
1022 93 : if ((x % 4) == 0)
1023 23 : bRet &= VSIFPrintfL(fp, "\n") >= 0;
1024 :
1025 93 : x++;
1026 93 : if (x > 4)
1027 23 : x = 1;
1028 : }
1029 : }
1030 1 : bRet &= VSIFPrintfL(fp, "}\n") >= 0;
1031 :
1032 : // TODO(schwehr): Rewrite without goto.
1033 84 : end:
1034 1007 : for (int i = 0; i < idx; i++)
1035 923 : CPLFree(papszVal[i]);
1036 :
1037 84 : return bRet;
1038 : }
1039 :
1040 : /************************************************************************/
1041 : /* WritePseudoGcpInfo() */
1042 : /************************************************************************/
1043 :
1044 83 : bool ENVIDataset::WritePseudoGcpInfo()
1045 : {
1046 : // Write out gcps into the envi header
1047 : // returns 0 if the gcps are not present.
1048 :
1049 83 : const int iNum = std::min(GetGCPCount(), 4);
1050 83 : if (iNum == 0)
1051 82 : return false;
1052 :
1053 1 : const GDAL_GCP *pGcpStructs = GetGCPs();
1054 :
1055 : // double dfGCPPixel; /** Pixel (x) location of GCP on raster */
1056 : // double dfGCPLine; /** Line (y) location of GCP on raster */
1057 : // double dfGCPX; /** X position of GCP in georeferenced space */
1058 : // double dfGCPY; /** Y position of GCP in georeferenced space */
1059 :
1060 1 : bool bRet = VSIFPrintfL(fp, "geo points = {\n") >= 0;
1061 2 : for (int iR = 0; iR < iNum; iR++)
1062 : {
1063 : // Add 1 to pixel and line for ENVI convention
1064 1 : bRet &=
1065 2 : VSIFPrintfL(fp, " %#0.4f, %#0.4f, %#0.8f, %#0.8f",
1066 1 : 1 + pGcpStructs[iR].dfGCPPixel,
1067 1 : 1 + pGcpStructs[iR].dfGCPLine, pGcpStructs[iR].dfGCPY,
1068 1 : pGcpStructs[iR].dfGCPX) >= 0;
1069 1 : if (iR < iNum - 1)
1070 0 : bRet &= VSIFPrintfL(fp, ",\n") >= 0;
1071 : }
1072 :
1073 1 : bRet &= VSIFPrintfL(fp, "}\n") >= 0;
1074 :
1075 1 : return bRet;
1076 : }
1077 :
1078 : /************************************************************************/
1079 : /* GetSpatialRef() */
1080 : /************************************************************************/
1081 :
1082 24 : const OGRSpatialReference *ENVIDataset::GetSpatialRef() const
1083 : {
1084 24 : return m_oSRS.IsEmpty() ? nullptr : &m_oSRS;
1085 : }
1086 :
1087 : /************************************************************************/
1088 : /* SetSpatialRef() */
1089 : /************************************************************************/
1090 :
1091 66 : CPLErr ENVIDataset::SetSpatialRef(const OGRSpatialReference *poSRS)
1092 :
1093 : {
1094 66 : m_oSRS.Clear();
1095 66 : if (poSRS)
1096 66 : m_oSRS = *poSRS;
1097 :
1098 66 : bHeaderDirty = true;
1099 :
1100 66 : return CE_None;
1101 : }
1102 :
1103 : /************************************************************************/
1104 : /* GetGeoTransform() */
1105 : /************************************************************************/
1106 :
1107 58 : CPLErr ENVIDataset::GetGeoTransform(GDALGeoTransform >) const
1108 :
1109 : {
1110 58 : gt = m_gt;
1111 :
1112 58 : if (bFoundMapinfo)
1113 56 : return CE_None;
1114 :
1115 2 : return CE_Failure;
1116 : }
1117 :
1118 : /************************************************************************/
1119 : /* SetGeoTransform() */
1120 : /************************************************************************/
1121 :
1122 67 : CPLErr ENVIDataset::SetGeoTransform(const GDALGeoTransform >)
1123 : {
1124 67 : m_gt = gt;
1125 :
1126 67 : bHeaderDirty = true;
1127 67 : bFoundMapinfo = true;
1128 :
1129 67 : return CE_None;
1130 : }
1131 :
1132 : /************************************************************************/
1133 : /* SetDescription() */
1134 : /************************************************************************/
1135 :
1136 252 : void ENVIDataset::SetDescription(const char *pszDescription)
1137 : {
1138 252 : bHeaderDirty = true;
1139 252 : RawDataset::SetDescription(pszDescription);
1140 252 : }
1141 :
1142 : /************************************************************************/
1143 : /* SetMetadata() */
1144 : /************************************************************************/
1145 :
1146 32 : CPLErr ENVIDataset::SetMetadata(char **papszMetadata, const char *pszDomain)
1147 : {
1148 32 : if (pszDomain && (EQUAL(pszDomain, "RPC") || EQUAL(pszDomain, "ENVI")))
1149 : {
1150 2 : bHeaderDirty = true;
1151 : }
1152 32 : return RawDataset::SetMetadata(papszMetadata, pszDomain);
1153 : }
1154 :
1155 : /************************************************************************/
1156 : /* SetMetadataItem() */
1157 : /************************************************************************/
1158 :
1159 3083 : CPLErr ENVIDataset::SetMetadataItem(const char *pszName, const char *pszValue,
1160 : const char *pszDomain)
1161 : {
1162 3083 : if (pszDomain && (EQUAL(pszDomain, "RPC") || EQUAL(pszDomain, "ENVI")))
1163 : {
1164 2588 : bHeaderDirty = true;
1165 : }
1166 3083 : return RawDataset::SetMetadataItem(pszName, pszValue, pszDomain);
1167 : }
1168 :
1169 : /************************************************************************/
1170 : /* SetGCPs() */
1171 : /************************************************************************/
1172 :
1173 1 : CPLErr ENVIDataset::SetGCPs(int nGCPCount, const GDAL_GCP *pasGCPList,
1174 : const OGRSpatialReference *poSRS)
1175 : {
1176 1 : bHeaderDirty = true;
1177 :
1178 1 : return RawDataset::SetGCPs(nGCPCount, pasGCPList, poSRS);
1179 : }
1180 :
1181 : /************************************************************************/
1182 : /* SplitList() */
1183 : /* */
1184 : /* Split an ENVI value list into component fields, and strip */
1185 : /* white space. */
1186 : /************************************************************************/
1187 :
1188 133 : char **ENVIDataset::SplitList(const char *pszCleanInput)
1189 :
1190 : {
1191 133 : return GDALENVISplitList(pszCleanInput).StealList();
1192 : }
1193 :
1194 : /************************************************************************/
1195 : /* SetENVIDatum() */
1196 : /************************************************************************/
1197 :
1198 2 : void ENVIDataset::SetENVIDatum(OGRSpatialReference *poSRS,
1199 : const char *pszENVIDatumName)
1200 :
1201 : {
1202 : // Datums.
1203 2 : if (EQUAL(pszENVIDatumName, "WGS-84"))
1204 2 : poSRS->SetWellKnownGeogCS("WGS84");
1205 0 : else if (EQUAL(pszENVIDatumName, "WGS-72"))
1206 0 : poSRS->SetWellKnownGeogCS("WGS72");
1207 0 : else if (EQUAL(pszENVIDatumName, "North America 1983"))
1208 0 : poSRS->SetWellKnownGeogCS("NAD83");
1209 0 : else if (EQUAL(pszENVIDatumName, "North America 1927") ||
1210 0 : strstr(pszENVIDatumName, "NAD27") ||
1211 0 : strstr(pszENVIDatumName, "NAD-27"))
1212 0 : poSRS->SetWellKnownGeogCS("NAD27");
1213 0 : else if (STARTS_WITH_CI(pszENVIDatumName, "European 1950"))
1214 0 : poSRS->SetWellKnownGeogCS("EPSG:4230");
1215 0 : else if (EQUAL(pszENVIDatumName, "Ordnance Survey of Great Britain '36"))
1216 0 : poSRS->SetWellKnownGeogCS("EPSG:4277");
1217 0 : else if (EQUAL(pszENVIDatumName, "SAD-69/Brazil"))
1218 0 : poSRS->SetWellKnownGeogCS("EPSG:4291");
1219 0 : else if (EQUAL(pszENVIDatumName, "Geocentric Datum of Australia 1994"))
1220 0 : poSRS->SetWellKnownGeogCS("EPSG:4283");
1221 0 : else if (EQUAL(pszENVIDatumName, "Australian Geodetic 1984"))
1222 0 : poSRS->SetWellKnownGeogCS("EPSG:4203");
1223 0 : else if (EQUAL(pszENVIDatumName, "Nouvelle Triangulation Francaise IGN"))
1224 0 : poSRS->SetWellKnownGeogCS("EPSG:4275");
1225 :
1226 : // Ellipsoids
1227 0 : else if (EQUAL(pszENVIDatumName, "GRS 80"))
1228 0 : poSRS->SetWellKnownGeogCS("NAD83");
1229 0 : else if (EQUAL(pszENVIDatumName, "Airy"))
1230 0 : poSRS->SetWellKnownGeogCS("EPSG:4001");
1231 0 : else if (EQUAL(pszENVIDatumName, "Australian National"))
1232 0 : poSRS->SetWellKnownGeogCS("EPSG:4003");
1233 0 : else if (EQUAL(pszENVIDatumName, "Bessel 1841"))
1234 0 : poSRS->SetWellKnownGeogCS("EPSG:4004");
1235 0 : else if (EQUAL(pszENVIDatumName, "Clark 1866"))
1236 0 : poSRS->SetWellKnownGeogCS("EPSG:4008");
1237 : else
1238 : {
1239 0 : CPLError(CE_Warning, CPLE_AppDefined,
1240 : "Unrecognized datum '%s', defaulting to WGS84.",
1241 : pszENVIDatumName);
1242 0 : poSRS->SetWellKnownGeogCS("WGS84");
1243 : }
1244 2 : }
1245 :
1246 : /************************************************************************/
1247 : /* SetENVIEllipse() */
1248 : /************************************************************************/
1249 :
1250 9 : void ENVIDataset::SetENVIEllipse(OGRSpatialReference *poSRS, char **papszPI_EI)
1251 :
1252 : {
1253 9 : const double dfA = CPLAtofM(papszPI_EI[0]);
1254 9 : const double dfB = CPLAtofM(papszPI_EI[1]);
1255 :
1256 9 : double dfInvF = 0.0;
1257 9 : if (fabs(dfA - dfB) >= 0.1)
1258 9 : dfInvF = dfA / (dfA - dfB);
1259 :
1260 9 : poSRS->SetGeogCS("Ellipse Based", "Ellipse Based", "Unnamed", dfA, dfInvF);
1261 9 : }
1262 :
1263 : /************************************************************************/
1264 : /* ProcessMapinfo() */
1265 : /* */
1266 : /* Extract projection, and geotransform from a mapinfo value in */
1267 : /* the header. */
1268 : /************************************************************************/
1269 :
1270 105 : bool ENVIDataset::ProcessMapinfo(const char *pszMapinfo)
1271 :
1272 : {
1273 105 : char **papszFields = SplitList(pszMapinfo);
1274 105 : const char *pszUnits = nullptr;
1275 105 : double dfRotation = 0.0;
1276 105 : bool bUpsideDown = false;
1277 105 : const int nCount = CSLCount(papszFields);
1278 :
1279 105 : if (nCount < 7)
1280 : {
1281 0 : CSLDestroy(papszFields);
1282 0 : return false;
1283 : }
1284 :
1285 : // Retrieve named values
1286 975 : for (int i = 0; i < nCount; ++i)
1287 : {
1288 870 : if (STARTS_WITH(papszFields[i], "units="))
1289 : {
1290 2 : pszUnits = papszFields[i] + strlen("units=");
1291 : }
1292 868 : else if (STARTS_WITH(papszFields[i], "rotation="))
1293 : {
1294 9 : dfRotation = CPLAtof(papszFields[i] + strlen("rotation="));
1295 9 : bUpsideDown = fabs(dfRotation) == 180.0;
1296 9 : dfRotation *= kdfDegToRad * -1.0;
1297 : }
1298 : }
1299 :
1300 : // Check if we have coordinate system string, and if so parse it.
1301 105 : char **papszCSS = nullptr;
1302 105 : const char *pszCSS = m_aosHeader["coordinate_system_string"];
1303 105 : if (pszCSS != nullptr)
1304 : {
1305 76 : papszCSS = CSLTokenizeString2(pszCSS, "{}", CSLT_PRESERVEQUOTES);
1306 : }
1307 :
1308 : // Check if we have projection info, and if so parse it.
1309 105 : char **papszPI = nullptr;
1310 105 : int nPICount = 0;
1311 105 : const char *pszPI = m_aosHeader["projection_info"];
1312 105 : if (pszPI != nullptr)
1313 : {
1314 23 : papszPI = SplitList(pszPI);
1315 23 : nPICount = CSLCount(papszPI);
1316 : }
1317 :
1318 : // Capture geotransform.
1319 105 : const double xReference = CPLAtof(papszFields[1]);
1320 105 : const double yReference = CPLAtof(papszFields[2]);
1321 105 : const double pixelEasting = CPLAtof(papszFields[3]);
1322 105 : const double pixelNorthing = CPLAtof(papszFields[4]);
1323 105 : const double xPixelSize = CPLAtof(papszFields[5]);
1324 105 : const double yPixelSize = CPLAtof(papszFields[6]);
1325 :
1326 105 : m_gt[0] = pixelEasting - (xReference - 1) * xPixelSize;
1327 105 : m_gt[1] = cos(dfRotation) * xPixelSize;
1328 105 : m_gt[2] = -sin(dfRotation) * xPixelSize;
1329 105 : m_gt[3] = pixelNorthing + (yReference - 1) * yPixelSize;
1330 105 : m_gt[4] = -sin(dfRotation) * yPixelSize;
1331 105 : m_gt[5] = -cos(dfRotation) * yPixelSize;
1332 105 : if (bUpsideDown) // to avoid numeric approximations
1333 : {
1334 5 : m_gt[1] = xPixelSize;
1335 5 : m_gt[2] = 0;
1336 5 : m_gt[4] = 0;
1337 5 : m_gt[5] = yPixelSize;
1338 : }
1339 :
1340 : // TODO(schwehr): Symbolic constants for the fields.
1341 : // Capture projection.
1342 105 : OGRSpatialReference oSRS;
1343 105 : bool bGeogCRSSet = false;
1344 105 : if (oSRS.importFromESRI(papszCSS) != OGRERR_NONE)
1345 : {
1346 29 : oSRS.Clear();
1347 :
1348 29 : if (STARTS_WITH_CI(papszFields[0], "UTM") && nCount >= 9)
1349 : {
1350 17 : oSRS.SetUTM(atoi(papszFields[7]), !EQUAL(papszFields[8], "South"));
1351 17 : if (nCount >= 10 && strstr(papszFields[9], "=") == nullptr)
1352 2 : SetENVIDatum(&oSRS, papszFields[9]);
1353 : else
1354 15 : oSRS.SetWellKnownGeogCS("NAD27");
1355 17 : bGeogCRSSet = true;
1356 : }
1357 12 : else if (STARTS_WITH_CI(papszFields[0], "State Plane (NAD 27)") &&
1358 : nCount > 7)
1359 : {
1360 0 : oSRS.SetStatePlane(ITTVISToUSGSZone(atoi(papszFields[7])), FALSE);
1361 0 : bGeogCRSSet = true;
1362 : }
1363 12 : else if (STARTS_WITH_CI(papszFields[0], "State Plane (NAD 83)") &&
1364 : nCount > 7)
1365 : {
1366 0 : oSRS.SetStatePlane(ITTVISToUSGSZone(atoi(papszFields[7])), TRUE);
1367 0 : bGeogCRSSet = true;
1368 : }
1369 12 : else if (STARTS_WITH_CI(papszFields[0], "Geographic Lat") && nCount > 7)
1370 : {
1371 0 : if (strstr(papszFields[7], "=") == nullptr)
1372 0 : SetENVIDatum(&oSRS, papszFields[7]);
1373 : else
1374 0 : oSRS.SetWellKnownGeogCS("WGS84");
1375 0 : bGeogCRSSet = true;
1376 : }
1377 12 : else if (nPICount > 8 && atoi(papszPI[0]) == 3) // TM
1378 : {
1379 0 : oSRS.SetTM(CPLAtofM(papszPI[3]), CPLAtofM(papszPI[4]),
1380 0 : CPLAtofM(papszPI[7]), CPLAtofM(papszPI[5]),
1381 0 : CPLAtofM(papszPI[6]));
1382 : }
1383 12 : else if (nPICount > 8 && atoi(papszPI[0]) == 4)
1384 : {
1385 : // Lambert Conformal Conic
1386 0 : oSRS.SetLCC(CPLAtofM(papszPI[7]), CPLAtofM(papszPI[8]),
1387 0 : CPLAtofM(papszPI[3]), CPLAtofM(papszPI[4]),
1388 0 : CPLAtofM(papszPI[5]), CPLAtofM(papszPI[6]));
1389 : }
1390 12 : else if (nPICount > 10 && atoi(papszPI[0]) == 5)
1391 : {
1392 : // Oblique Merc (2 point).
1393 0 : oSRS.SetHOM2PNO(CPLAtofM(papszPI[3]), CPLAtofM(papszPI[4]),
1394 0 : CPLAtofM(papszPI[5]), CPLAtofM(papszPI[6]),
1395 0 : CPLAtofM(papszPI[7]), CPLAtofM(papszPI[10]),
1396 0 : CPLAtofM(papszPI[8]), CPLAtofM(papszPI[9]));
1397 : }
1398 12 : else if (nPICount > 8 && atoi(papszPI[0]) == 6) // Oblique Merc
1399 : {
1400 0 : oSRS.SetHOM(CPLAtofM(papszPI[3]), CPLAtofM(papszPI[4]),
1401 0 : CPLAtofM(papszPI[5]), 0.0, CPLAtofM(papszPI[8]),
1402 0 : CPLAtofM(papszPI[6]), CPLAtofM(papszPI[7]));
1403 : }
1404 12 : else if (nPICount > 8 && atoi(papszPI[0]) == 7) // Stereographic
1405 : {
1406 0 : oSRS.SetStereographic(CPLAtofM(papszPI[3]), CPLAtofM(papszPI[4]),
1407 0 : CPLAtofM(papszPI[7]), CPLAtofM(papszPI[5]),
1408 0 : CPLAtofM(papszPI[6]));
1409 : }
1410 12 : else if (nPICount > 8 && atoi(papszPI[0]) == 9) // Albers Equal Area
1411 : {
1412 9 : oSRS.SetACEA(CPLAtofM(papszPI[7]), CPLAtofM(papszPI[8]),
1413 9 : CPLAtofM(papszPI[3]), CPLAtofM(papszPI[4]),
1414 9 : CPLAtofM(papszPI[5]), CPLAtofM(papszPI[6]));
1415 : }
1416 3 : else if (nPICount > 6 && atoi(papszPI[0]) == 10) // Polyconic
1417 : {
1418 0 : oSRS.SetPolyconic(CPLAtofM(papszPI[3]), CPLAtofM(papszPI[4]),
1419 0 : CPLAtofM(papszPI[5]), CPLAtofM(papszPI[6]));
1420 : }
1421 3 : else if (nPICount > 6 && atoi(papszPI[0]) == 11) // LAEA
1422 : {
1423 0 : oSRS.SetLAEA(CPLAtofM(papszPI[3]), CPLAtofM(papszPI[4]),
1424 0 : CPLAtofM(papszPI[5]), CPLAtofM(papszPI[6]));
1425 : }
1426 3 : else if (nPICount > 6 && atoi(papszPI[0]) == 12) // Azimuthal Equid.
1427 : {
1428 0 : oSRS.SetAE(CPLAtofM(papszPI[3]), CPLAtofM(papszPI[4]),
1429 0 : CPLAtofM(papszPI[5]), CPLAtofM(papszPI[6]));
1430 : }
1431 3 : else if (nPICount > 6 && atoi(papszPI[0]) == 31) // Polar Stereographic
1432 : {
1433 0 : oSRS.SetPS(CPLAtofM(papszPI[3]), CPLAtofM(papszPI[4]), 1.0,
1434 0 : CPLAtofM(papszPI[5]), CPLAtofM(papszPI[6]));
1435 : }
1436 : }
1437 : else
1438 : {
1439 76 : bGeogCRSSet = CPL_TO_BOOL(oSRS.IsProjected());
1440 : }
1441 :
1442 105 : CSLDestroy(papszCSS);
1443 :
1444 : // Still lots more that could be added for someone with the patience.
1445 :
1446 : // Fallback to localcs if we don't recognise things.
1447 105 : if (oSRS.IsEmpty())
1448 3 : oSRS.SetLocalCS(papszFields[0]);
1449 :
1450 : // Try to set datum from projection info line if we have a
1451 : // projected coordinate system without a GEOGCS explicitly set.
1452 105 : if (oSRS.IsProjected() && !bGeogCRSSet && nPICount > 3)
1453 : {
1454 : // Do we have a datum on the projection info line?
1455 9 : int iDatum = nPICount - 1;
1456 :
1457 : // Ignore units= items.
1458 9 : if (strstr(papszPI[iDatum], "=") != nullptr)
1459 0 : iDatum--;
1460 :
1461 : // Skip past the name.
1462 9 : iDatum--;
1463 :
1464 18 : const CPLString osDatumName = papszPI[iDatum];
1465 9 : if (osDatumName.find_first_of("abcdefghijklmnopqrstuvwxyz"
1466 9 : "ABCDEFGHIJKLMNOPQRSTUVWXYZ") !=
1467 : CPLString::npos)
1468 : {
1469 0 : SetENVIDatum(&oSRS, osDatumName);
1470 : }
1471 : else
1472 : {
1473 9 : SetENVIEllipse(&oSRS, papszPI + 1);
1474 : }
1475 : }
1476 :
1477 : // Try to process specialized units.
1478 105 : if (pszUnits != nullptr)
1479 : {
1480 : // Handle linear units first.
1481 2 : if (EQUAL(pszUnits, "Feet"))
1482 0 : oSRS.SetLinearUnitsAndUpdateParameters(SRS_UL_FOOT,
1483 : CPLAtof(SRS_UL_FOOT_CONV));
1484 2 : else if (EQUAL(pszUnits, "Meters"))
1485 2 : oSRS.SetLinearUnitsAndUpdateParameters(SRS_UL_METER, 1.0);
1486 0 : else if (EQUAL(pszUnits, "Km"))
1487 0 : oSRS.SetLinearUnitsAndUpdateParameters("Kilometer", 1000.0);
1488 0 : else if (EQUAL(pszUnits, "Yards"))
1489 0 : oSRS.SetLinearUnitsAndUpdateParameters("Yard", 0.9144);
1490 0 : else if (EQUAL(pszUnits, "Miles"))
1491 0 : oSRS.SetLinearUnitsAndUpdateParameters("Mile", 1609.344);
1492 0 : else if (EQUAL(pszUnits, "Nautical Miles"))
1493 0 : oSRS.SetLinearUnitsAndUpdateParameters(
1494 : SRS_UL_NAUTICAL_MILE, CPLAtof(SRS_UL_NAUTICAL_MILE_CONV));
1495 :
1496 : // Only handle angular units if we know the projection is geographic.
1497 2 : if (oSRS.IsGeographic())
1498 : {
1499 0 : if (EQUAL(pszUnits, "Radians"))
1500 : {
1501 0 : oSRS.SetAngularUnits(SRS_UA_RADIAN, 1.0);
1502 : }
1503 : else
1504 : {
1505 : // Degrees, minutes and seconds will all be represented
1506 : // as degrees.
1507 0 : oSRS.SetAngularUnits(SRS_UA_DEGREE,
1508 : CPLAtof(SRS_UA_DEGREE_CONV));
1509 :
1510 0 : double conversionFactor = 1.0;
1511 0 : if (EQUAL(pszUnits, "Minutes"))
1512 0 : conversionFactor = 60.0;
1513 0 : else if (EQUAL(pszUnits, "Seconds"))
1514 0 : conversionFactor = 3600.0;
1515 0 : m_gt[0] /= conversionFactor;
1516 0 : m_gt[1] /= conversionFactor;
1517 0 : m_gt[2] /= conversionFactor;
1518 0 : m_gt[3] /= conversionFactor;
1519 0 : m_gt[4] /= conversionFactor;
1520 0 : m_gt[5] /= conversionFactor;
1521 : }
1522 : }
1523 : }
1524 :
1525 : // Try to identify the CRS with the database
1526 105 : auto poBestCRSMatch = oSRS.FindBestMatch();
1527 105 : if (poBestCRSMatch)
1528 : {
1529 62 : m_oSRS = *poBestCRSMatch;
1530 62 : poBestCRSMatch->Release();
1531 : }
1532 : else
1533 : {
1534 43 : m_oSRS = std::move(oSRS);
1535 : }
1536 105 : m_oSRS.SetAxisMappingStrategy(OAMS_TRADITIONAL_GIS_ORDER);
1537 :
1538 105 : CSLDestroy(papszFields);
1539 105 : CSLDestroy(papszPI);
1540 105 : return true;
1541 : }
1542 :
1543 : /************************************************************************/
1544 : /* ProcessRPCinfo() */
1545 : /* */
1546 : /* Extract RPC transformation coefficients if they are present */
1547 : /* and sets into the standard metadata fields for RPC. */
1548 : /************************************************************************/
1549 :
1550 3 : void ENVIDataset::ProcessRPCinfo(const char *pszRPCinfo, int numCols,
1551 : int numRows)
1552 : {
1553 3 : char **papszFields = SplitList(pszRPCinfo);
1554 3 : const int nCount = CSLCount(papszFields);
1555 :
1556 3 : if (nCount < 90)
1557 : {
1558 0 : CSLDestroy(papszFields);
1559 0 : return;
1560 : }
1561 :
1562 3 : char sVal[1280] = {'\0'};
1563 3 : CPLsnprintf(sVal, sizeof(sVal), "%.16g", CPLAtof(papszFields[0]));
1564 3 : SetMetadataItem("LINE_OFF", sVal, "RPC");
1565 3 : CPLsnprintf(sVal, sizeof(sVal), "%.16g", CPLAtof(papszFields[5]));
1566 3 : SetMetadataItem("LINE_SCALE", sVal, "RPC");
1567 3 : CPLsnprintf(sVal, sizeof(sVal), "%.16g", CPLAtof(papszFields[1]));
1568 3 : SetMetadataItem("SAMP_OFF", sVal, "RPC");
1569 3 : CPLsnprintf(sVal, sizeof(sVal), "%.16g", CPLAtof(papszFields[6]));
1570 3 : SetMetadataItem("SAMP_SCALE", sVal, "RPC");
1571 3 : CPLsnprintf(sVal, sizeof(sVal), "%.16g", CPLAtof(papszFields[2]));
1572 3 : SetMetadataItem("LAT_OFF", sVal, "RPC");
1573 3 : CPLsnprintf(sVal, sizeof(sVal), "%.16g", CPLAtof(papszFields[7]));
1574 3 : SetMetadataItem("LAT_SCALE", sVal, "RPC");
1575 3 : CPLsnprintf(sVal, sizeof(sVal), "%.16g", CPLAtof(papszFields[3]));
1576 3 : SetMetadataItem("LONG_OFF", sVal, "RPC");
1577 3 : CPLsnprintf(sVal, sizeof(sVal), "%.16g", CPLAtof(papszFields[8]));
1578 3 : SetMetadataItem("LONG_SCALE", sVal, "RPC");
1579 3 : CPLsnprintf(sVal, sizeof(sVal), "%.16g", CPLAtof(papszFields[4]));
1580 3 : SetMetadataItem("HEIGHT_OFF", sVal, "RPC");
1581 3 : CPLsnprintf(sVal, sizeof(sVal), "%.16g", CPLAtof(papszFields[9]));
1582 3 : SetMetadataItem("HEIGHT_SCALE", sVal, "RPC");
1583 :
1584 3 : sVal[0] = '\0';
1585 63 : for (int i = 0; i < 20; i++)
1586 60 : CPLsnprintf(sVal + strlen(sVal), sizeof(sVal) - strlen(sVal), "%.16g ",
1587 60 : CPLAtof(papszFields[10 + i]));
1588 3 : SetMetadataItem("LINE_NUM_COEFF", sVal, "RPC");
1589 :
1590 3 : sVal[0] = '\0';
1591 63 : for (int i = 0; i < 20; i++)
1592 60 : CPLsnprintf(sVal + strlen(sVal), sizeof(sVal) - strlen(sVal), "%.16g ",
1593 60 : CPLAtof(papszFields[30 + i]));
1594 3 : SetMetadataItem("LINE_DEN_COEFF", sVal, "RPC");
1595 :
1596 3 : sVal[0] = '\0';
1597 63 : for (int i = 0; i < 20; i++)
1598 60 : CPLsnprintf(sVal + strlen(sVal), sizeof(sVal) - strlen(sVal), "%.16g ",
1599 60 : CPLAtof(papszFields[50 + i]));
1600 3 : SetMetadataItem("SAMP_NUM_COEFF", sVal, "RPC");
1601 :
1602 3 : sVal[0] = '\0';
1603 63 : for (int i = 0; i < 20; i++)
1604 60 : CPLsnprintf(sVal + strlen(sVal), sizeof(sVal) - strlen(sVal), "%.16g ",
1605 60 : CPLAtof(papszFields[70 + i]));
1606 3 : SetMetadataItem("SAMP_DEN_COEFF", sVal, "RPC");
1607 :
1608 3 : CPLsnprintf(sVal, sizeof(sVal), "%.16g",
1609 3 : CPLAtof(papszFields[3]) - CPLAtof(papszFields[8]));
1610 3 : SetMetadataItem("MIN_LONG", sVal, "RPC");
1611 :
1612 3 : CPLsnprintf(sVal, sizeof(sVal), "%.16g",
1613 3 : CPLAtof(papszFields[3]) + CPLAtof(papszFields[8]));
1614 3 : SetMetadataItem("MAX_LONG", sVal, "RPC");
1615 :
1616 3 : CPLsnprintf(sVal, sizeof(sVal), "%.16g",
1617 3 : CPLAtof(papszFields[2]) - CPLAtof(papszFields[7]));
1618 3 : SetMetadataItem("MIN_LAT", sVal, "RPC");
1619 :
1620 3 : CPLsnprintf(sVal, sizeof(sVal), "%.16g",
1621 3 : CPLAtof(papszFields[2]) + CPLAtof(papszFields[7]));
1622 3 : SetMetadataItem("MAX_LAT", sVal, "RPC");
1623 :
1624 3 : if (nCount == 93)
1625 : {
1626 3 : SetMetadataItem("TILE_ROW_OFFSET", papszFields[90], "RPC");
1627 3 : SetMetadataItem("TILE_COL_OFFSET", papszFields[91], "RPC");
1628 3 : SetMetadataItem("ENVI_RPC_EMULATION", papszFields[92], "RPC");
1629 : }
1630 :
1631 : // Handle the chipping case where the image is a subset.
1632 3 : const double rowOffset = (nCount == 93) ? CPLAtof(papszFields[90]) : 0;
1633 3 : const double colOffset = (nCount == 93) ? CPLAtof(papszFields[91]) : 0;
1634 3 : if (rowOffset != 0.0 || colOffset != 0.0)
1635 : {
1636 0 : SetMetadataItem("ICHIP_SCALE_FACTOR", "1");
1637 0 : SetMetadataItem("ICHIP_ANAMORPH_CORR", "0");
1638 0 : SetMetadataItem("ICHIP_SCANBLK_NUM", "0");
1639 :
1640 0 : SetMetadataItem("ICHIP_OP_ROW_11", "0.5");
1641 0 : SetMetadataItem("ICHIP_OP_COL_11", "0.5");
1642 0 : SetMetadataItem("ICHIP_OP_ROW_12", "0.5");
1643 0 : SetMetadataItem("ICHIP_OP_COL_21", "0.5");
1644 0 : CPLsnprintf(sVal, sizeof(sVal), "%.16g", numCols - 0.5);
1645 0 : SetMetadataItem("ICHIP_OP_COL_12", sVal);
1646 0 : SetMetadataItem("ICHIP_OP_COL_22", sVal);
1647 0 : CPLsnprintf(sVal, sizeof(sVal), "%.16g", numRows - 0.5);
1648 0 : SetMetadataItem("ICHIP_OP_ROW_21", sVal);
1649 0 : SetMetadataItem("ICHIP_OP_ROW_22", sVal);
1650 :
1651 0 : CPLsnprintf(sVal, sizeof(sVal), "%.16g", rowOffset + 0.5);
1652 0 : SetMetadataItem("ICHIP_FI_ROW_11", sVal);
1653 0 : SetMetadataItem("ICHIP_FI_ROW_12", sVal);
1654 0 : CPLsnprintf(sVal, sizeof(sVal), "%.16g", colOffset + 0.5);
1655 0 : SetMetadataItem("ICHIP_FI_COL_11", sVal);
1656 0 : SetMetadataItem("ICHIP_FI_COL_21", sVal);
1657 0 : CPLsnprintf(sVal, sizeof(sVal), "%.16g", colOffset + numCols - 0.5);
1658 0 : SetMetadataItem("ICHIP_FI_COL_12", sVal);
1659 0 : SetMetadataItem("ICHIP_FI_COL_22", sVal);
1660 0 : CPLsnprintf(sVal, sizeof(sVal), "%.16g", rowOffset + numRows - 0.5);
1661 0 : SetMetadataItem("ICHIP_FI_ROW_21", sVal);
1662 0 : SetMetadataItem("ICHIP_FI_ROW_22", sVal);
1663 : }
1664 3 : CSLDestroy(papszFields);
1665 : }
1666 :
1667 : /************************************************************************/
1668 : /* GetGCPCount() */
1669 : /************************************************************************/
1670 :
1671 97 : int ENVIDataset::GetGCPCount()
1672 : {
1673 97 : int nGCPCount = RawDataset::GetGCPCount();
1674 97 : if (nGCPCount)
1675 1 : return nGCPCount;
1676 96 : return static_cast<int>(m_asGCPs.size());
1677 : }
1678 :
1679 : /************************************************************************/
1680 : /* GetGCPs() */
1681 : /************************************************************************/
1682 :
1683 2 : const GDAL_GCP *ENVIDataset::GetGCPs()
1684 : {
1685 2 : int nGCPCount = RawDataset::GetGCPCount();
1686 2 : if (nGCPCount)
1687 1 : return RawDataset::GetGCPs();
1688 1 : if (!m_asGCPs.empty())
1689 1 : return m_asGCPs.data();
1690 0 : return nullptr;
1691 : }
1692 :
1693 : /************************************************************************/
1694 : /* ProcessGeoPoints() */
1695 : /* */
1696 : /* Extract GCPs */
1697 : /************************************************************************/
1698 :
1699 2 : void ENVIDataset::ProcessGeoPoints(const char *pszGeoPoints)
1700 : {
1701 2 : char **papszFields = SplitList(pszGeoPoints);
1702 2 : const int nCount = CSLCount(papszFields);
1703 :
1704 2 : if ((nCount % 4) != 0)
1705 : {
1706 0 : CSLDestroy(papszFields);
1707 0 : return;
1708 : }
1709 2 : m_asGCPs.resize(nCount / 4);
1710 2 : if (!m_asGCPs.empty())
1711 : {
1712 2 : GDALInitGCPs(static_cast<int>(m_asGCPs.size()), m_asGCPs.data());
1713 : }
1714 4 : for (int i = 0; i < static_cast<int>(m_asGCPs.size()); i++)
1715 : {
1716 : // Subtract 1 to pixel and line for ENVI convention
1717 2 : m_asGCPs[i].dfGCPPixel = CPLAtof(papszFields[i * 4 + 0]) - 1;
1718 2 : m_asGCPs[i].dfGCPLine = CPLAtof(papszFields[i * 4 + 1]) - 1;
1719 2 : m_asGCPs[i].dfGCPY = CPLAtof(papszFields[i * 4 + 2]);
1720 2 : m_asGCPs[i].dfGCPX = CPLAtof(papszFields[i * 4 + 3]);
1721 2 : m_asGCPs[i].dfGCPZ = 0;
1722 : }
1723 2 : CSLDestroy(papszFields);
1724 : }
1725 :
1726 1 : static unsigned byteSwapUInt(unsigned swapMe)
1727 : {
1728 1 : CPL_MSBPTR32(&swapMe);
1729 1 : return swapMe;
1730 : }
1731 :
1732 252 : void ENVIDataset::ProcessStatsFile()
1733 : {
1734 252 : osStaFilename = CPLResetExtensionSafe(pszHDRFilename, "sta");
1735 252 : VSILFILE *fpStaFile = VSIFOpenL(osStaFilename, "rb");
1736 :
1737 252 : if (!fpStaFile)
1738 : {
1739 251 : osStaFilename = "";
1740 251 : return;
1741 : }
1742 :
1743 1 : int lTestHeader[10] = {0};
1744 1 : if (VSIFReadL(lTestHeader, sizeof(int), 10, fpStaFile) != 10)
1745 : {
1746 0 : CPL_IGNORE_RET_VAL(VSIFCloseL(fpStaFile));
1747 0 : osStaFilename = "";
1748 0 : return;
1749 : }
1750 :
1751 1 : const bool isFloat = byteSwapInt(lTestHeader[0]) == 1111838282;
1752 :
1753 1 : int nb = byteSwapInt(lTestHeader[3]);
1754 :
1755 1 : if (nb < 0 || nb > nBands)
1756 : {
1757 0 : CPLDebug("ENVI",
1758 : ".sta file has statistics for %d bands, "
1759 : "whereas the dataset has only %d bands",
1760 : nb, nBands);
1761 0 : nb = nBands;
1762 : }
1763 :
1764 : // TODO(schwehr): What are 1, 4, 8, and 40?
1765 1 : unsigned lOffset = 0;
1766 1 : if (VSIFSeekL(fpStaFile, 40 + static_cast<vsi_l_offset>(nb + 1) * 4,
1767 1 : SEEK_SET) == 0 &&
1768 2 : VSIFReadL(&lOffset, sizeof(lOffset), 1, fpStaFile) == 1 &&
1769 1 : VSIFSeekL(fpStaFile,
1770 2 : 40 + static_cast<vsi_l_offset>(nb + 1) * 8 +
1771 1 : byteSwapUInt(lOffset) + nb,
1772 : SEEK_SET) == 0)
1773 : {
1774 : // This should be the beginning of the statistics.
1775 1 : if (isFloat)
1776 : {
1777 0 : float *fStats = static_cast<float *>(CPLCalloc(nb * 4, 4));
1778 0 : if (static_cast<int>(VSIFReadL(fStats, 4, nb * 4, fpStaFile)) ==
1779 0 : nb * 4)
1780 : {
1781 0 : for (int i = 0; i < nb; i++)
1782 : {
1783 0 : GetRasterBand(i + 1)->SetStatistics(
1784 0 : byteSwapFloat(fStats[i]), byteSwapFloat(fStats[nb + i]),
1785 0 : byteSwapFloat(fStats[2 * nb + i]),
1786 0 : byteSwapFloat(fStats[3 * nb + i]));
1787 : }
1788 : }
1789 0 : CPLFree(fStats);
1790 : }
1791 : else
1792 : {
1793 1 : double *dStats = static_cast<double *>(CPLCalloc(nb * 4, 8));
1794 1 : if (static_cast<int>(VSIFReadL(dStats, 8, nb * 4, fpStaFile)) ==
1795 1 : nb * 4)
1796 : {
1797 7 : for (int i = 0; i < nb; i++)
1798 : {
1799 6 : const double dMin = byteSwapDouble(dStats[i]);
1800 6 : const double dMax = byteSwapDouble(dStats[nb + i]);
1801 6 : const double dMean = byteSwapDouble(dStats[2 * nb + i]);
1802 6 : const double dStd = byteSwapDouble(dStats[3 * nb + i]);
1803 6 : if (dMin != dMax && dStd != 0)
1804 6 : GetRasterBand(i + 1)->SetStatistics(dMin, dMax, dMean,
1805 6 : dStd);
1806 : }
1807 : }
1808 1 : CPLFree(dStats);
1809 : }
1810 : }
1811 1 : CPL_IGNORE_RET_VAL(VSIFCloseL(fpStaFile));
1812 : }
1813 :
1814 2 : int ENVIDataset::byteSwapInt(int swapMe)
1815 : {
1816 2 : CPL_MSBPTR32(&swapMe);
1817 2 : return swapMe;
1818 : }
1819 :
1820 0 : float ENVIDataset::byteSwapFloat(float swapMe)
1821 : {
1822 0 : CPL_MSBPTR32(&swapMe);
1823 0 : return swapMe;
1824 : }
1825 :
1826 24 : double ENVIDataset::byteSwapDouble(double swapMe)
1827 : {
1828 24 : CPL_MSBPTR64(&swapMe);
1829 24 : return swapMe;
1830 : }
1831 :
1832 : /************************************************************************/
1833 : /* GetRawBinaryLayout() */
1834 : /************************************************************************/
1835 :
1836 4 : bool ENVIDataset::GetRawBinaryLayout(GDALDataset::RawBinaryLayout &sLayout)
1837 : {
1838 : const bool bIsCompressed =
1839 4 : atoi(m_aosHeader.FetchNameValueDef("file_compression", "0")) != 0;
1840 4 : if (bIsCompressed)
1841 0 : return false;
1842 4 : if (!RawDataset::GetRawBinaryLayout(sLayout))
1843 0 : return false;
1844 4 : sLayout.osRawFilename = GetDescription();
1845 4 : return true;
1846 : }
1847 :
1848 : /************************************************************************/
1849 : /* Open() */
1850 : /************************************************************************/
1851 :
1852 31755 : GDALDataset *ENVIDataset::Open(GDALOpenInfo *poOpenInfo)
1853 : {
1854 31755 : return Open(poOpenInfo, true);
1855 : }
1856 :
1857 31843 : ENVIDataset *ENVIDataset::Open(GDALOpenInfo *poOpenInfo, bool bFileSizeCheck)
1858 :
1859 : {
1860 : // Assume the caller is pointing to the binary (i.e. .bil) file.
1861 33432 : if (poOpenInfo->nHeaderBytes < 2 ||
1862 3177 : (!poOpenInfo->IsSingleAllowedDriver("ENVI") &&
1863 1588 : poOpenInfo->IsExtensionEqualToCI("zarr")))
1864 : {
1865 30235 : return nullptr;
1866 : }
1867 :
1868 : // Do we have a .hdr file? Try upper and lower case, and
1869 : // replacing the extension as well as appending the extension
1870 : // to whatever we currently have.
1871 :
1872 1608 : const char *pszMode = nullptr;
1873 1608 : if (poOpenInfo->eAccess == GA_Update)
1874 114 : pszMode = "r+";
1875 : else
1876 1494 : pszMode = "r";
1877 :
1878 3197 : CPLString osHdrFilename;
1879 1589 : VSILFILE *fpHeader = nullptr;
1880 1589 : CSLConstList papszSiblingFiles = poOpenInfo->GetSiblingFiles();
1881 1589 : if (papszSiblingFiles == nullptr)
1882 : {
1883 : // First try hdr as an extra extension
1884 : osHdrFilename =
1885 8 : CPLFormFilenameSafe(nullptr, poOpenInfo->pszFilename, "hdr");
1886 8 : fpHeader = VSIFOpenL(osHdrFilename, pszMode);
1887 :
1888 8 : if (fpHeader == nullptr && VSIIsCaseSensitiveFS(osHdrFilename))
1889 : {
1890 : osHdrFilename =
1891 8 : CPLFormFilenameSafe(nullptr, poOpenInfo->pszFilename, "HDR");
1892 8 : fpHeader = VSIFOpenL(osHdrFilename, pszMode);
1893 : }
1894 :
1895 : // Otherwise, try .hdr as a replacement extension
1896 8 : if (fpHeader == nullptr)
1897 : {
1898 : osHdrFilename =
1899 8 : CPLResetExtensionSafe(poOpenInfo->pszFilename, "hdr");
1900 8 : fpHeader = VSIFOpenL(osHdrFilename, pszMode);
1901 : }
1902 :
1903 8 : if (fpHeader == nullptr && VSIIsCaseSensitiveFS(osHdrFilename))
1904 : {
1905 : osHdrFilename =
1906 7 : CPLResetExtensionSafe(poOpenInfo->pszFilename, "HDR");
1907 7 : fpHeader = VSIFOpenL(osHdrFilename, pszMode);
1908 : }
1909 : }
1910 : else
1911 : {
1912 : // Now we need to tear apart the filename to form a .HDR filename.
1913 3162 : CPLString osPath = CPLGetPathSafe(poOpenInfo->pszFilename);
1914 3162 : CPLString osName = CPLGetFilename(poOpenInfo->pszFilename);
1915 :
1916 : // First try hdr as an extra extension
1917 : int iFile =
1918 1581 : CSLFindString(papszSiblingFiles,
1919 3162 : CPLFormFilenameSafe(nullptr, osName, "hdr").c_str());
1920 1581 : if (iFile < 0)
1921 : {
1922 : // Otherwise, try .hdr as a replacement extension
1923 1445 : iFile = CSLFindString(papszSiblingFiles,
1924 2890 : CPLResetExtensionSafe(osName, "hdr").c_str());
1925 : }
1926 :
1927 1581 : if (iFile >= 0)
1928 : {
1929 : osHdrFilename =
1930 338 : CPLFormFilenameSafe(osPath, papszSiblingFiles[iFile], nullptr);
1931 338 : fpHeader = VSIFOpenL(osHdrFilename, pszMode);
1932 : }
1933 : }
1934 :
1935 1589 : if (fpHeader == nullptr)
1936 1250 : return nullptr;
1937 :
1938 : // Check that the first line says "ENVI".
1939 339 : char szTestHdr[4] = {'\0'};
1940 :
1941 339 : if (VSIFReadL(szTestHdr, 4, 1, fpHeader) != 1)
1942 : {
1943 0 : CPL_IGNORE_RET_VAL(VSIFCloseL(fpHeader));
1944 0 : return nullptr;
1945 : }
1946 339 : if (!STARTS_WITH(szTestHdr, "ENVI"))
1947 : {
1948 80 : CPL_IGNORE_RET_VAL(VSIFCloseL(fpHeader));
1949 80 : return nullptr;
1950 : }
1951 :
1952 : // Create a corresponding GDALDataset.
1953 518 : auto poDS = std::make_unique<ENVIDataset>();
1954 259 : poDS->pszHDRFilename = CPLStrdup(osHdrFilename);
1955 259 : poDS->fp = fpHeader;
1956 259 : poDS->m_aosHeader = GDALReadENVIHeader(fpHeader);
1957 :
1958 : // Has the user selected the .hdr file to open?
1959 259 : if (poOpenInfo->IsExtensionEqualToCI("hdr"))
1960 : {
1961 0 : CPLError(CE_Failure, CPLE_AppDefined,
1962 : "The selected file is an ENVI header file, but to "
1963 : "open ENVI datasets, the data file should be selected "
1964 : "instead of the .hdr file. Please try again selecting "
1965 : "the data file corresponding to the header file: "
1966 : "%s",
1967 : poOpenInfo->pszFilename);
1968 0 : return nullptr;
1969 : }
1970 :
1971 : // Has the user selected the .sta (stats) file to open?
1972 259 : if (poOpenInfo->IsExtensionEqualToCI("sta"))
1973 : {
1974 0 : CPLError(CE_Failure, CPLE_AppDefined,
1975 : "The selected file is an ENVI statistics file. "
1976 : "To open ENVI datasets, the data file should be selected "
1977 : "instead of the .sta file. Please try again selecting "
1978 : "the data file corresponding to the statistics file: "
1979 : "%s",
1980 : poOpenInfo->pszFilename);
1981 0 : return nullptr;
1982 : }
1983 :
1984 : // Extract required values from the .hdr.
1985 259 : const char *pszLines = poDS->m_aosHeader.FetchNameValueDef("lines", "0");
1986 259 : const auto nLines64 = std::strtoll(pszLines, nullptr, 10);
1987 259 : const int nLines = static_cast<int>(std::min<int64_t>(nLines64, INT_MAX));
1988 259 : if (nLines < nLines64)
1989 : {
1990 1 : CPLError(CE_Warning, CPLE_AppDefined,
1991 : "Limiting number of lines from %s to %d due to GDAL raster "
1992 : "data model limitation",
1993 : pszLines, nLines);
1994 : }
1995 :
1996 : const char *pszSamples =
1997 259 : poDS->m_aosHeader.FetchNameValueDef("samples", "0");
1998 259 : const auto nSamples64 = std::strtoll(pszSamples, nullptr, 10);
1999 : const int nSamples =
2000 259 : static_cast<int>(std::min<int64_t>(nSamples64, INT_MAX));
2001 259 : if (nSamples < nSamples64)
2002 : {
2003 1 : CPLError(
2004 : CE_Failure, CPLE_AppDefined,
2005 : "Cannot handle samples=%s due to GDAL raster data model limitation",
2006 : pszSamples);
2007 1 : return nullptr;
2008 : }
2009 :
2010 258 : const char *pszBands = poDS->m_aosHeader.FetchNameValueDef("bands", "0");
2011 258 : const auto nBands64 = std::strtoll(pszBands, nullptr, 10);
2012 258 : const int nBands = static_cast<int>(std::min<int64_t>(nBands64, INT_MAX));
2013 258 : if (nBands < nBands64)
2014 : {
2015 4 : CPLError(
2016 : CE_Failure, CPLE_AppDefined,
2017 : "Cannot handle bands=%s due to GDAL raster data model limitation",
2018 : pszBands);
2019 4 : return nullptr;
2020 : }
2021 :
2022 : // In case, there is no interleave keyword, we try to derive it from the
2023 : // file extension.
2024 254 : CPLString osInterleave = poDS->m_aosHeader.FetchNameValueDef(
2025 508 : "interleave", poOpenInfo->osExtension.c_str());
2026 :
2027 254 : if (!STARTS_WITH_CI(osInterleave, "BSQ") &&
2028 265 : !STARTS_WITH_CI(osInterleave, "BIP") &&
2029 11 : !STARTS_WITH_CI(osInterleave, "BIL"))
2030 : {
2031 0 : CPLDebug("ENVI", "Unset or unknown value for 'interleave' keyword --> "
2032 : "assuming BSQ interleaving");
2033 0 : osInterleave = "bsq";
2034 : }
2035 :
2036 508 : if (!GDALCheckDatasetDimensions(nSamples, nLines) ||
2037 254 : !GDALCheckBandCount(nBands, FALSE))
2038 : {
2039 2 : CPLError(CE_Failure, CPLE_AppDefined,
2040 : "The file appears to have an associated ENVI header, but "
2041 : "one or more of the samples, lines and bands "
2042 : "keywords appears to be missing or invalid.");
2043 2 : return nullptr;
2044 : }
2045 :
2046 : int nHeaderSize =
2047 252 : atoi(poDS->m_aosHeader.FetchNameValueDef("header_offset", "0"));
2048 :
2049 : // Translate the datatype.
2050 252 : GDALDataType eType = GDT_Byte;
2051 :
2052 252 : const char *pszDataType = poDS->m_aosHeader["data_type"];
2053 252 : if (pszDataType != nullptr)
2054 : {
2055 251 : switch (atoi(pszDataType))
2056 : {
2057 171 : case 1:
2058 171 : eType = GDT_Byte;
2059 171 : break;
2060 :
2061 9 : case 2:
2062 9 : eType = GDT_Int16;
2063 9 : break;
2064 :
2065 7 : case 3:
2066 7 : eType = GDT_Int32;
2067 7 : break;
2068 :
2069 12 : case 4:
2070 12 : eType = GDT_Float32;
2071 12 : break;
2072 :
2073 7 : case 5:
2074 7 : eType = GDT_Float64;
2075 7 : break;
2076 :
2077 8 : case 6:
2078 8 : eType = GDT_CFloat32;
2079 8 : break;
2080 :
2081 5 : case 9:
2082 5 : eType = GDT_CFloat64;
2083 5 : break;
2084 :
2085 17 : case 12:
2086 17 : eType = GDT_UInt16;
2087 17 : break;
2088 :
2089 7 : case 13:
2090 7 : eType = GDT_UInt32;
2091 7 : break;
2092 :
2093 4 : case 14:
2094 4 : eType = GDT_Int64;
2095 4 : break;
2096 :
2097 4 : case 15:
2098 4 : eType = GDT_UInt64;
2099 4 : break;
2100 :
2101 0 : default:
2102 0 : CPLError(CE_Failure, CPLE_AppDefined,
2103 : "The file does not have a value for the data_type "
2104 : "that is recognised by the GDAL ENVI driver.");
2105 0 : return nullptr;
2106 : }
2107 : }
2108 :
2109 : // Translate the byte order.
2110 252 : RawRasterBand::ByteOrder eByteOrder = RawRasterBand::NATIVE_BYTE_ORDER;
2111 :
2112 252 : const char *pszByteOrder = poDS->m_aosHeader["byte_order"];
2113 252 : if (pszByteOrder != nullptr)
2114 : {
2115 252 : eByteOrder = atoi(pszByteOrder) == 0
2116 252 : ? RawRasterBand::ByteOrder::ORDER_LITTLE_ENDIAN
2117 : : RawRasterBand::ByteOrder::ORDER_BIG_ENDIAN;
2118 : }
2119 :
2120 : // Warn about unsupported file types virtual mosaic and meta file.
2121 252 : const char *pszEnviFileType = poDS->m_aosHeader["file_type"];
2122 252 : if (pszEnviFileType != nullptr)
2123 : {
2124 : // When the file type is one of these we return an invalid file type err
2125 : // 'envi meta file'
2126 : // 'envi virtual mosaic'
2127 : // 'envi spectral library'
2128 : // 'envi fft result'
2129 :
2130 : // When the file type is one of these we open it
2131 : // 'envi standard'
2132 : // 'envi classification'
2133 :
2134 : // When the file type is anything else we attempt to open it as a
2135 : // raster.
2136 :
2137 : // envi gdal does not support any of these
2138 : // all others we will attempt to open
2139 252 : if (EQUAL(pszEnviFileType, "envi meta file") ||
2140 252 : EQUAL(pszEnviFileType, "envi virtual mosaic") ||
2141 252 : EQUAL(pszEnviFileType, "envi spectral library"))
2142 : {
2143 0 : CPLError(CE_Failure, CPLE_OpenFailed,
2144 : "File %s contains an invalid file type in the ENVI .hdr "
2145 : "GDAL does not support '%s' type files.",
2146 : poOpenInfo->pszFilename, pszEnviFileType);
2147 0 : return nullptr;
2148 : }
2149 : }
2150 :
2151 : // Detect (gzipped) compressed datasets.
2152 : const bool bIsCompressed =
2153 252 : atoi(poDS->m_aosHeader.FetchNameValueDef("file_compression", "0")) != 0;
2154 :
2155 : // Capture some information from the file that is of interest.
2156 252 : poDS->nRasterXSize = nSamples;
2157 252 : poDS->nRasterYSize = nLines;
2158 252 : poDS->eAccess = poOpenInfo->eAccess;
2159 :
2160 : // Reopen file in update mode if necessary.
2161 504 : CPLString osImageFilename(poOpenInfo->pszFilename);
2162 252 : if (bIsCompressed)
2163 1 : osImageFilename = "/vsigzip/" + osImageFilename;
2164 252 : if (poOpenInfo->eAccess == GA_Update)
2165 : {
2166 96 : if (bIsCompressed)
2167 : {
2168 0 : CPLError(CE_Failure, CPLE_OpenFailed,
2169 : "Cannot open compressed file in update mode.");
2170 0 : return nullptr;
2171 : }
2172 96 : poDS->fpImage = VSIFOpenL(osImageFilename, "rb+");
2173 : }
2174 : else
2175 : {
2176 156 : poDS->fpImage = VSIFOpenL(osImageFilename, "rb");
2177 : }
2178 :
2179 252 : if (poDS->fpImage == nullptr)
2180 : {
2181 0 : CPLError(CE_Failure, CPLE_OpenFailed,
2182 : "Failed to re-open %s within ENVI driver.",
2183 : poOpenInfo->pszFilename);
2184 0 : return nullptr;
2185 : }
2186 :
2187 : // Compute the line offset.
2188 252 : const int nDataSize = GDALGetDataTypeSizeBytes(eType);
2189 252 : int nPixelOffset = 0;
2190 252 : int nLineOffset = 0;
2191 252 : vsi_l_offset nBandOffset = 0;
2192 252 : CPLAssert(nDataSize != 0);
2193 252 : CPLAssert(nBands != 0);
2194 :
2195 252 : if (STARTS_WITH_CI(osInterleave, "bil"))
2196 : {
2197 11 : poDS->eInterleave = Interleave::BIL;
2198 11 : poDS->SetMetadataItem("INTERLEAVE", "LINE", "IMAGE_STRUCTURE");
2199 11 : if (nSamples > std::numeric_limits<int>::max() / (nDataSize * nBands))
2200 : {
2201 0 : CPLError(CE_Failure, CPLE_AppDefined, "Int overflow occurred.");
2202 0 : return nullptr;
2203 : }
2204 11 : nLineOffset = nDataSize * nSamples * nBands;
2205 11 : nPixelOffset = nDataSize;
2206 11 : nBandOffset = static_cast<vsi_l_offset>(nDataSize) * nSamples;
2207 : }
2208 241 : else if (STARTS_WITH_CI(osInterleave, "bip"))
2209 : {
2210 37 : poDS->eInterleave = Interleave::BIP;
2211 37 : poDS->SetMetadataItem("INTERLEAVE", "PIXEL", "IMAGE_STRUCTURE");
2212 37 : if (nSamples > std::numeric_limits<int>::max() / (nDataSize * nBands))
2213 : {
2214 0 : CPLError(CE_Failure, CPLE_AppDefined, "Int overflow occurred.");
2215 0 : return nullptr;
2216 : }
2217 37 : nLineOffset = nDataSize * nSamples * nBands;
2218 37 : nPixelOffset = nDataSize * nBands;
2219 37 : nBandOffset = nDataSize;
2220 : }
2221 : else
2222 : {
2223 204 : poDS->eInterleave = Interleave::BSQ;
2224 204 : poDS->SetMetadataItem("INTERLEAVE", "BAND", "IMAGE_STRUCTURE");
2225 204 : if (nSamples > std::numeric_limits<int>::max() / nDataSize)
2226 : {
2227 0 : CPLError(CE_Failure, CPLE_AppDefined, "Int overflow occurred.");
2228 0 : return nullptr;
2229 : }
2230 204 : nLineOffset = nDataSize * nSamples;
2231 204 : nPixelOffset = nDataSize;
2232 204 : nBandOffset = static_cast<vsi_l_offset>(nLineOffset) * nLines64;
2233 : }
2234 :
2235 252 : const char *pszMajorFrameOffset = poDS->m_aosHeader["major_frame_offsets"];
2236 252 : if (pszMajorFrameOffset != nullptr)
2237 : {
2238 0 : char **papszMajorFrameOffsets = poDS->SplitList(pszMajorFrameOffset);
2239 :
2240 0 : const int nTempCount = CSLCount(papszMajorFrameOffsets);
2241 0 : if (nTempCount == 2)
2242 : {
2243 0 : int nOffset1 = atoi(papszMajorFrameOffsets[0]);
2244 0 : int nOffset2 = atoi(papszMajorFrameOffsets[1]);
2245 0 : if (nOffset1 >= 0 && nOffset2 >= 0 &&
2246 0 : nHeaderSize < INT_MAX - nOffset1 &&
2247 0 : nOffset1 < INT_MAX - nOffset2 &&
2248 0 : nOffset1 + nOffset2 < INT_MAX - nLineOffset)
2249 : {
2250 0 : nHeaderSize += nOffset1;
2251 0 : nLineOffset += nOffset1 + nOffset2;
2252 : }
2253 : }
2254 0 : CSLDestroy(papszMajorFrameOffsets);
2255 : }
2256 :
2257 : // Currently each ENVIRasterBand allocates nPixelOffset * nRasterXSize bytes
2258 : // so for a pixel interleaved scheme, this will allocate lots of memory!
2259 : // Actually this is quadratic in the number of bands!
2260 : // Do a few sanity checks to avoid excessive memory allocation on
2261 : // small files.
2262 : // But ultimately we should fix RawRasterBand to have a shared buffer
2263 : // among bands.
2264 411 : if (bFileSizeCheck &&
2265 159 : !RAWDatasetCheckMemoryUsage(
2266 159 : poDS->nRasterXSize, poDS->nRasterYSize, nBands, nDataSize,
2267 159 : nPixelOffset, nLineOffset, nHeaderSize, nBandOffset, poDS->fpImage))
2268 : {
2269 0 : return nullptr;
2270 : }
2271 :
2272 : // Create band information objects.
2273 1326 : for (int i = 0; i < nBands; i++)
2274 : {
2275 : auto poBand = std::make_unique<ENVIRasterBand>(
2276 1074 : poDS.get(), i + 1, poDS->fpImage, nHeaderSize + nBandOffset * i,
2277 1074 : nPixelOffset, nLineOffset, eType, eByteOrder);
2278 1074 : if (!poBand->IsValid())
2279 0 : return nullptr;
2280 1074 : poDS->SetBand(i + 1, std::move(poBand));
2281 : }
2282 :
2283 252 : GDALApplyENVIHeaders(poDS.get(), poDS->m_aosHeader, nullptr);
2284 :
2285 : // Set all the header metadata into the ENVI domain.
2286 : {
2287 252 : char **pTmp = poDS->m_aosHeader.List();
2288 2786 : while (*pTmp != nullptr)
2289 : {
2290 2534 : char **pTokens = CSLTokenizeString2(
2291 : *pTmp, "=", CSLT_STRIPLEADSPACES | CSLT_STRIPENDSPACES);
2292 2534 : if (pTokens[0] != nullptr && pTokens[1] != nullptr &&
2293 2534 : pTokens[2] == nullptr)
2294 : {
2295 2525 : poDS->SetMetadataItem(pTokens[0], pTokens[1], "ENVI");
2296 : }
2297 2534 : CSLDestroy(pTokens);
2298 2534 : pTmp++;
2299 : }
2300 : }
2301 :
2302 : // Read the stats file if it is present.
2303 252 : poDS->ProcessStatsFile();
2304 :
2305 : // Look for mapinfo.
2306 252 : const char *pszMapInfo = poDS->m_aosHeader["map_info"];
2307 252 : if (pszMapInfo != nullptr)
2308 : {
2309 105 : poDS->bFoundMapinfo = CPL_TO_BOOL(poDS->ProcessMapinfo(pszMapInfo));
2310 : }
2311 :
2312 : // Look for RPC.
2313 252 : const char *pszRPCInfo = poDS->m_aosHeader["rpc_info"];
2314 252 : if (!poDS->bFoundMapinfo && pszRPCInfo != nullptr)
2315 : {
2316 6 : poDS->ProcessRPCinfo(pszRPCInfo, poDS->nRasterXSize,
2317 3 : poDS->nRasterYSize);
2318 : }
2319 :
2320 : // Look for geo_points / GCP
2321 252 : const char *pszGeoPoints = poDS->m_aosHeader["geo_points"];
2322 252 : if (!poDS->bFoundMapinfo && pszGeoPoints != nullptr)
2323 : {
2324 2 : poDS->ProcessGeoPoints(pszGeoPoints);
2325 : }
2326 :
2327 : // Initialize any PAM information.
2328 252 : poDS->SetDescription(poOpenInfo->pszFilename);
2329 252 : poDS->TryLoadXML();
2330 :
2331 : // Check for overviews.
2332 252 : poDS->oOvManager.Initialize(poDS.get(), poOpenInfo->pszFilename);
2333 :
2334 : // SetMetadata() calls in Open() makes the header dirty.
2335 : // Don't re-write the header if nothing external has changed the metadata.
2336 252 : poDS->bHeaderDirty = false;
2337 :
2338 252 : return poDS.release();
2339 : }
2340 :
2341 191 : int ENVIDataset::GetEnviType(GDALDataType eType)
2342 : {
2343 191 : int iENVIType = 1;
2344 191 : switch (eType)
2345 : {
2346 118 : case GDT_Byte:
2347 118 : iENVIType = 1;
2348 118 : break;
2349 7 : case GDT_Int16:
2350 7 : iENVIType = 2;
2351 7 : break;
2352 6 : case GDT_Int32:
2353 6 : iENVIType = 3;
2354 6 : break;
2355 8 : case GDT_Float32:
2356 8 : iENVIType = 4;
2357 8 : break;
2358 6 : case GDT_Float64:
2359 6 : iENVIType = 5;
2360 6 : break;
2361 7 : case GDT_CFloat32:
2362 7 : iENVIType = 6;
2363 7 : break;
2364 6 : case GDT_CFloat64:
2365 6 : iENVIType = 9;
2366 6 : break;
2367 11 : case GDT_UInt16:
2368 11 : iENVIType = 12;
2369 11 : break;
2370 6 : case GDT_UInt32:
2371 6 : iENVIType = 13;
2372 6 : break;
2373 4 : case GDT_Int64:
2374 4 : iENVIType = 14;
2375 4 : break;
2376 4 : case GDT_UInt64:
2377 4 : iENVIType = 15;
2378 4 : break;
2379 8 : default:
2380 8 : CPLError(CE_Failure, CPLE_AppDefined,
2381 : "Attempt to create ENVI .hdr labelled dataset with an "
2382 : "illegal data type (%s).",
2383 : GDALGetDataTypeName(eType));
2384 8 : return 1;
2385 : }
2386 183 : return iENVIType;
2387 : }
2388 :
2389 : /************************************************************************/
2390 : /* Create() */
2391 : /************************************************************************/
2392 :
2393 107 : GDALDataset *ENVIDataset::Create(const char *pszFilename, int nXSize,
2394 : int nYSize, int nBandsIn, GDALDataType eType,
2395 : char **papszOptions)
2396 :
2397 : {
2398 : // Verify input options.
2399 107 : int iENVIType = GetEnviType(eType);
2400 107 : if (0 == iENVIType)
2401 0 : return nullptr;
2402 :
2403 : // Try to create the file.
2404 107 : VSILFILE *fp = VSIFOpenL(pszFilename, "wb");
2405 :
2406 107 : if (fp == nullptr)
2407 : {
2408 3 : CPLError(CE_Failure, CPLE_OpenFailed,
2409 : "Attempt to create file `%s' failed.", pszFilename);
2410 3 : return nullptr;
2411 : }
2412 :
2413 : // Just write out a couple of bytes to establish the binary
2414 : // file, and then close it.
2415 : {
2416 : const bool bRet =
2417 104 : VSIFWriteL(static_cast<void *>(const_cast<char *>("\0\0")), 2, 1,
2418 104 : fp) == 1;
2419 104 : if (VSIFCloseL(fp) != 0 || !bRet)
2420 1 : return nullptr;
2421 : }
2422 :
2423 : // Create the .hdr filename.
2424 206 : std::string osHDRFilename;
2425 103 : const char *pszSuffix = CSLFetchNameValue(papszOptions, "SUFFIX");
2426 103 : if (pszSuffix && STARTS_WITH_CI(pszSuffix, "ADD"))
2427 3 : osHDRFilename = CPLFormFilenameSafe(nullptr, pszFilename, "hdr");
2428 : else
2429 100 : osHDRFilename = CPLResetExtensionSafe(pszFilename, "hdr");
2430 :
2431 : // Open the file.
2432 103 : fp = VSIFOpenL(osHDRFilename.c_str(), "wt");
2433 103 : if (fp == nullptr)
2434 : {
2435 0 : CPLError(CE_Failure, CPLE_OpenFailed,
2436 : "Attempt to create file `%s' failed.", osHDRFilename.c_str());
2437 0 : return nullptr;
2438 : }
2439 :
2440 : // Write out the header.
2441 : #ifdef CPL_LSB
2442 103 : int iBigEndian = 0;
2443 : #else
2444 : int iBigEndian = 1;
2445 : #endif
2446 :
2447 : // Undocumented
2448 103 : const char *pszByteOrder = CSLFetchNameValue(papszOptions, "@BYTE_ORDER");
2449 103 : if (pszByteOrder && EQUAL(pszByteOrder, "LITTLE_ENDIAN"))
2450 1 : iBigEndian = 0;
2451 102 : else if (pszByteOrder && EQUAL(pszByteOrder, "BIG_ENDIAN"))
2452 1 : iBigEndian = 1;
2453 :
2454 103 : bool bRet = VSIFPrintfL(fp, "ENVI\n") > 0;
2455 103 : bRet &= VSIFPrintfL(fp, "samples = %d\nlines = %d\nbands = %d\n",
2456 103 : nXSize, nYSize, nBandsIn) > 0;
2457 103 : bRet &=
2458 103 : VSIFPrintfL(fp, "header offset = 0\nfile type = ENVI Standard\n") > 0;
2459 103 : bRet &= VSIFPrintfL(fp, "data type = %d\n", iENVIType) > 0;
2460 103 : const char *pszInterleaving = CSLFetchNameValue(papszOptions, "INTERLEAVE");
2461 103 : if (pszInterleaving)
2462 : {
2463 23 : if (STARTS_WITH_CI(pszInterleaving, "bip"))
2464 6 : pszInterleaving = "bip"; // interleaved by pixel
2465 17 : else if (STARTS_WITH_CI(pszInterleaving, "bil"))
2466 4 : pszInterleaving = "bil"; // interleaved by line
2467 : else
2468 13 : pszInterleaving = "bsq"; // band sequential by default
2469 : }
2470 : else
2471 : {
2472 80 : pszInterleaving = "bsq";
2473 : }
2474 103 : bRet &= VSIFPrintfL(fp, "interleave = %s\n", pszInterleaving) > 0;
2475 103 : bRet &= VSIFPrintfL(fp, "byte order = %d\n", iBigEndian) > 0;
2476 :
2477 103 : if (VSIFCloseL(fp) != 0 || !bRet)
2478 9 : return nullptr;
2479 :
2480 94 : GDALOpenInfo oOpenInfo(pszFilename, GA_Update);
2481 94 : ENVIDataset *poDS = Open(&oOpenInfo, false);
2482 94 : if (poDS)
2483 : {
2484 93 : poDS->SetFillFile();
2485 : }
2486 94 : return poDS;
2487 : }
2488 :
2489 : /************************************************************************/
2490 : /* ENVIRasterBand() */
2491 : /************************************************************************/
2492 :
2493 1074 : ENVIRasterBand::ENVIRasterBand(GDALDataset *poDSIn, int nBandIn,
2494 : VSILFILE *fpRawIn, vsi_l_offset nImgOffsetIn,
2495 : int nPixelOffsetIn, int nLineOffsetIn,
2496 : GDALDataType eDataTypeIn,
2497 1074 : RawRasterBand::ByteOrder eByteOrderIn)
2498 : : RawRasterBand(poDSIn, nBandIn, fpRawIn, nImgOffsetIn, nPixelOffsetIn,
2499 : nLineOffsetIn, eDataTypeIn, eByteOrderIn,
2500 1074 : RawRasterBand::OwnFP::NO)
2501 : {
2502 1074 : }
2503 :
2504 : /************************************************************************/
2505 : /* SetDescription() */
2506 : /************************************************************************/
2507 :
2508 260 : void ENVIRasterBand::SetDescription(const char *pszDescription)
2509 : {
2510 260 : cpl::down_cast<ENVIDataset *>(poDS)->bHeaderDirty = true;
2511 260 : RawRasterBand::SetDescription(pszDescription);
2512 260 : }
2513 :
2514 : /************************************************************************/
2515 : /* SetCategoryNames() */
2516 : /************************************************************************/
2517 :
2518 4 : CPLErr ENVIRasterBand::SetCategoryNames(char **papszCategoryNamesIn)
2519 : {
2520 4 : cpl::down_cast<ENVIDataset *>(poDS)->bHeaderDirty = true;
2521 4 : return RawRasterBand::SetCategoryNames(papszCategoryNamesIn);
2522 : }
2523 :
2524 : /************************************************************************/
2525 : /* SetNoDataValue() */
2526 : /************************************************************************/
2527 :
2528 12 : CPLErr ENVIRasterBand::SetNoDataValue(double dfNoDataValue)
2529 : {
2530 12 : cpl::down_cast<ENVIDataset *>(poDS)->bHeaderDirty = true;
2531 :
2532 12 : if (poDS->GetRasterCount() > 1)
2533 : {
2534 8 : int bOtherBandHasNoData = false;
2535 8 : const int nOtherBand = nBand > 1 ? 1 : 2;
2536 8 : double dfOtherBandNoData = poDS->GetRasterBand(nOtherBand)
2537 8 : ->GetNoDataValue(&bOtherBandHasNoData);
2538 12 : if (bOtherBandHasNoData &&
2539 8 : !(std::isnan(dfOtherBandNoData) && std::isnan(dfNoDataValue)) &&
2540 : dfOtherBandNoData != dfNoDataValue)
2541 : {
2542 2 : CPLError(CE_Warning, CPLE_AppDefined,
2543 : "Nodata value of band %d (%.17g) is different from nodata "
2544 : "value from band %d (%.17g). Only the later will be "
2545 : "written in the ENVI header as the \"data ignore value\"",
2546 : nBand, dfNoDataValue, nOtherBand, dfOtherBandNoData);
2547 : }
2548 : }
2549 :
2550 12 : return RawRasterBand::SetNoDataValue(dfNoDataValue);
2551 : }
2552 :
2553 : /************************************************************************/
2554 : /* SetColorInterpretation() */
2555 : /************************************************************************/
2556 :
2557 51 : CPLErr ENVIRasterBand::SetColorInterpretation(GDALColorInterp eColorInterp)
2558 : {
2559 51 : cpl::down_cast<ENVIDataset *>(poDS)->bHeaderDirty = true;
2560 51 : return RawRasterBand::SetColorInterpretation(eColorInterp);
2561 : }
2562 :
2563 : /************************************************************************/
2564 : /* SetOffset() */
2565 : /************************************************************************/
2566 :
2567 10 : CPLErr ENVIRasterBand::SetOffset(double dfValue)
2568 : {
2569 10 : cpl::down_cast<ENVIDataset *>(poDS)->bHeaderDirty = true;
2570 10 : return RawRasterBand::SetOffset(dfValue);
2571 : }
2572 :
2573 : /************************************************************************/
2574 : /* SetScale() */
2575 : /************************************************************************/
2576 :
2577 10 : CPLErr ENVIRasterBand::SetScale(double dfValue)
2578 : {
2579 10 : cpl::down_cast<ENVIDataset *>(poDS)->bHeaderDirty = true;
2580 10 : return RawRasterBand::SetScale(dfValue);
2581 : }
2582 :
2583 : /************************************************************************/
2584 : /* GDALRegister_ENVI() */
2585 : /************************************************************************/
2586 :
2587 2040 : void GDALRegister_ENVI()
2588 : {
2589 2040 : if (GDALGetDriverByName("ENVI") != nullptr)
2590 283 : return;
2591 :
2592 1757 : GDALDriver *poDriver = new GDALDriver();
2593 :
2594 1757 : poDriver->SetDescription("ENVI");
2595 1757 : poDriver->SetMetadataItem(GDAL_DCAP_RASTER, "YES");
2596 1757 : poDriver->SetMetadataItem(GDAL_DMD_LONGNAME, "ENVI .hdr Labelled");
2597 1757 : poDriver->SetMetadataItem(GDAL_DMD_HELPTOPIC, "drivers/raster/envi.html");
2598 1757 : poDriver->SetMetadataItem(GDAL_DMD_EXTENSION, "");
2599 1757 : poDriver->SetMetadataItem(GDAL_DMD_CREATIONDATATYPES,
2600 : "Byte Int16 UInt16 Int32 UInt32 Int64 UInt64 "
2601 1757 : "Float32 Float64 CFloat32 CFloat64");
2602 1757 : poDriver->SetMetadataItem(
2603 : GDAL_DMD_CREATIONOPTIONLIST,
2604 : "<CreationOptionList>"
2605 : " <Option name='SUFFIX' type='string-select'>"
2606 : " <Value>ADD</Value>"
2607 : " </Option>"
2608 : " <Option name='INTERLEAVE' type='string-select'>"
2609 : " <Value>BIP</Value>"
2610 : " <Value>BIL</Value>"
2611 : " <Value>BSQ</Value>"
2612 : " </Option>"
2613 1757 : "</CreationOptionList>");
2614 :
2615 1757 : poDriver->SetMetadataItem(GDAL_DCAP_UPDATE, "YES");
2616 1757 : poDriver->SetMetadataItem(GDAL_DMD_UPDATE_ITEMS,
2617 : "GeoTransform SRS GCPs NoData "
2618 1757 : "RasterValues DatasetMetadata");
2619 :
2620 1757 : poDriver->SetMetadataItem(GDAL_DCAP_VIRTUALIO, "YES");
2621 1757 : poDriver->pfnOpen = ENVIDataset::Open;
2622 1757 : poDriver->pfnCreate = ENVIDataset::Create;
2623 :
2624 1757 : GetGDALDriverManager()->RegisterDriver(poDriver);
2625 : }
|