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