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