Coverage Report

Created: 2026-11-12 08:28

next uncovered line (L), next uncovered region (R), next uncovered branch (B)
/src/gdal/frmts/ers/ersdataset.cpp
Line
Count
Source
1
/******************************************************************************
2
 *
3
 * Project:  ERMapper .ers Driver
4
 * Purpose:  Implementation of .ers driver.
5
 * Author:   Frank Warmerdam, warmerdam@pobox.com
6
 *
7
 ******************************************************************************
8
 * Copyright (c) 2007, Frank Warmerdam <warmerdam@pobox.com>
9
 * Copyright (c) 2008-2013, Even Rouault <even dot rouault at spatialys.com>
10
 *
11
 * SPDX-License-Identifier: MIT
12
 ****************************************************************************/
13
14
#include "cpl_string.h"
15
#include "ershdrnode.h"
16
#include "gdal_frmts.h"
17
#include "gdal_proxy.h"
18
#include "ogr_spatialref.h"
19
#include "rawdataset.h"
20
21
#include <limits>
22
23
/************************************************************************/
24
/* ==================================================================== */
25
/*                              ERSDataset                              */
26
/* ==================================================================== */
27
/************************************************************************/
28
29
class ERSRasterBand;
30
31
class ERSDataset final : public RawDataset
32
{
33
    friend class ERSRasterBand;
34
35
    VSILFILE *fpImage = nullptr;  // Image data file.
36
    GDALDataset *poDepFile = nullptr;
37
38
    bool bGotTransform = false;
39
    GDALGeoTransform m_gt{};
40
    OGRSpatialReference m_oSRS{};
41
42
    CPLString osRawFilename{};
43
44
    bool bHDRDirty = false;
45
    ERSHdrNode *poHeader = nullptr;
46
47
    const char *Find(const char *, const char *);
48
49
    int nGCPCount = 0;
50
    GDAL_GCP *pasGCPList = nullptr;
51
    OGRSpatialReference m_oGCPSRS{};
52
53
    void ReadGCPs();
54
55
    bool bHasNoDataValue = false;
56
    double dfNoDataValue = 0;
57
58
    CPLString osProj{}, osProjForced{};
59
    CPLString osDatum{}, osDatumForced{};
60
    CPLString osUnits{}, osUnitsForced{};
61
    void WriteProjectionInfo(const char *pszProj, const char *pszDatum,
62
                             const char *pszUnits);
63
64
    CPLStringList oERSMetadataList{};
65
66
    CPL_DISALLOW_COPY_ASSIGN(ERSDataset)
67
68
  protected:
69
    int CloseDependentDatasets() override;
70
71
    CPLErr Close(GDALProgressFunc = nullptr, void * = nullptr) override;
72
73
  public:
74
    ERSDataset();
75
    ~ERSDataset() override;
76
77
    CPLErr FlushCache(bool bAtClosing) override;
78
    CPLErr GetGeoTransform(GDALGeoTransform &gt) const override;
79
    CPLErr SetGeoTransform(const GDALGeoTransform &gt) override;
80
    const OGRSpatialReference *GetSpatialRef() const override;
81
    CPLErr SetSpatialRef(const OGRSpatialReference *poSRS) override;
82
    char **GetFileList(void) override;
83
84
    int GetGCPCount() override;
85
    const OGRSpatialReference *GetGCPSpatialRef() const override;
86
    const GDAL_GCP *GetGCPs() override;
87
    CPLErr SetGCPs(int nGCPCountIn, const GDAL_GCP *pasGCPListIn,
88
                   const OGRSpatialReference *poSRS) override;
89
90
    char **GetMetadataDomainList() override;
91
    const char *GetMetadataItem(const char *pszName,
92
                                const char *pszDomain = "") override;
93
    CSLConstList GetMetadata(const char *pszDomain = "") override;
94
95
    static GDALDataset *Open(GDALOpenInfo *);
96
    static int Identify(GDALOpenInfo *);
97
    static GDALDataset *Create(const char *pszFilename, int nXSize, int nYSize,
98
                               int nBandsIn, GDALDataType eType,
99
                               CSLConstList papszOptions);
100
};
101
102
/************************************************************************/
103
/*                             ERSDataset()                             */
104
/************************************************************************/
105
106
ERSDataset::ERSDataset()
107
45.8k
{
108
45.8k
    m_oSRS.SetAxisMappingStrategy(OAMS_TRADITIONAL_GIS_ORDER);
109
45.8k
    m_oGCPSRS.SetAxisMappingStrategy(OAMS_TRADITIONAL_GIS_ORDER);
110
45.8k
}
111
112
/************************************************************************/
113
/*                            ~ERSDataset()                             */
114
/************************************************************************/
115
116
ERSDataset::~ERSDataset()
117
118
45.8k
{
119
45.8k
    ERSDataset::Close();
120
45.8k
}
121
122
/************************************************************************/
123
/*                               Close()                                */
124
/************************************************************************/
125
126
CPLErr ERSDataset::Close(GDALProgressFunc, void *)
127
86.9k
{
128
86.9k
    CPLErr eErr = CE_None;
129
86.9k
    if (nOpenFlags != OPEN_FLAGS_CLOSED)
130
45.8k
    {
131
45.8k
        if (ERSDataset::FlushCache(true) != CE_None)
132
0
            eErr = CE_Failure;
133
134
45.8k
        if (fpImage != nullptr)
135
41.3k
        {
136
41.3k
            VSIFCloseL(fpImage);
137
41.3k
        }
138
139
45.8k
        ERSDataset::CloseDependentDatasets();
140
141
45.8k
        if (nGCPCount > 0)
142
0
        {
143
0
            GDALDeinitGCPs(nGCPCount, pasGCPList);
144
0
            CPLFree(pasGCPList);
145
0
        }
146
147
45.8k
        if (poHeader != nullptr)
148
45.8k
            delete poHeader;
149
150
45.8k
        if (GDALPamDataset::Close() != CE_None)
151
0
            eErr = CE_Failure;
152
45.8k
    }
153
86.9k
    return eErr;
154
86.9k
}
155
156
/************************************************************************/
157
/*                       CloseDependentDatasets()                       */
158
/************************************************************************/
159
160
int ERSDataset::CloseDependentDatasets()
161
45.8k
{
162
45.8k
    int bHasDroppedRef = GDALPamDataset::CloseDependentDatasets();
163
164
45.8k
    if (poDepFile != nullptr)
165
0
    {
166
0
        bHasDroppedRef = TRUE;
167
168
0
        for (int iBand = 0; iBand < nBands; iBand++)
169
0
        {
170
0
            delete papoBands[iBand];
171
0
            papoBands[iBand] = nullptr;
172
0
        }
173
0
        nBands = 0;
174
175
0
        delete poDepFile;
176
0
        poDepFile = nullptr;
177
0
    }
178
179
45.8k
    return bHasDroppedRef;
180
45.8k
}
181
182
/************************************************************************/
183
/*                             FlushCache()                             */
184
/************************************************************************/
185
186
CPLErr ERSDataset::FlushCache(bool bAtClosing)
187
188
53.4k
{
189
53.4k
    CPLErr eErr = CE_None;
190
53.4k
    if (bHDRDirty)
191
0
    {
192
0
        VSILFILE *fpERS = VSIFOpenL(GetDescription(), "w");
193
0
        if (fpERS == nullptr)
194
0
        {
195
0
            eErr = CE_Failure;
196
0
            CPLError(CE_Failure, CPLE_OpenFailed,
197
0
                     "Unable to rewrite %s header.", GetDescription());
198
0
        }
199
0
        else
200
0
        {
201
0
            if (VSIFPrintfL(fpERS, "DatasetHeader Begin\n") <= 0)
202
0
                eErr = CE_Failure;
203
0
            poHeader->WriteSelf(fpERS, 1);
204
0
            if (VSIFPrintfL(fpERS, "DatasetHeader End\n") <= 0)
205
0
                eErr = CE_Failure;
206
0
            if (VSIFCloseL(fpERS) != 0)
207
0
                eErr = CE_Failure;
208
0
        }
209
0
    }
210
211
53.4k
    if (RawDataset::FlushCache(bAtClosing) != CE_None)
212
0
        eErr = CE_Failure;
213
53.4k
    return eErr;
214
53.4k
}
215
216
/************************************************************************/
217
/*                       GetMetadataDomainList()                        */
218
/************************************************************************/
219
220
char **ERSDataset::GetMetadataDomainList()
221
0
{
222
0
    return BuildMetadataDomainList(GDALPamDataset::GetMetadataDomainList(),
223
0
                                   TRUE, "ERS", nullptr);
224
0
}
225
226
/************************************************************************/
227
/*                          GetMetadataItem()                           */
228
/************************************************************************/
229
230
const char *ERSDataset::GetMetadataItem(const char *pszName,
231
                                        const char *pszDomain)
232
125k
{
233
125k
    if (pszDomain != nullptr && EQUAL(pszDomain, "ERS") && pszName != nullptr)
234
0
    {
235
0
        if (EQUAL(pszName, "PROJ"))
236
0
            return osProj.size() ? osProj.c_str() : nullptr;
237
0
        if (EQUAL(pszName, "DATUM"))
238
0
            return osDatum.size() ? osDatum.c_str() : nullptr;
239
0
        if (EQUAL(pszName, "UNITS"))
240
0
            return osUnits.size() ? osUnits.c_str() : nullptr;
241
0
    }
242
125k
    return GDALPamDataset::GetMetadataItem(pszName, pszDomain);
243
125k
}
244
245
/************************************************************************/
246
/*                            GetMetadata()                             */
247
/************************************************************************/
248
249
CSLConstList ERSDataset::GetMetadata(const char *pszDomain)
250
251
9.64k
{
252
9.64k
    if (pszDomain != nullptr && EQUAL(pszDomain, "ERS"))
253
0
    {
254
0
        oERSMetadataList.Clear();
255
0
        if (!osProj.empty())
256
0
            oERSMetadataList.AddString(
257
0
                CPLSPrintf("%s=%s", "PROJ", osProj.c_str()));
258
0
        if (!osDatum.empty())
259
0
            oERSMetadataList.AddString(
260
0
                CPLSPrintf("%s=%s", "DATUM", osDatum.c_str()));
261
0
        if (!osUnits.empty())
262
0
            oERSMetadataList.AddString(
263
0
                CPLSPrintf("%s=%s", "UNITS", osUnits.c_str()));
264
0
        return oERSMetadataList.List();
265
0
    }
266
267
9.64k
    return GDALPamDataset::GetMetadata(pszDomain);
268
9.64k
}
269
270
/************************************************************************/
271
/*                            GetGCPCount()                             */
272
/************************************************************************/
273
274
int ERSDataset::GetGCPCount()
275
276
9.64k
{
277
9.64k
    return nGCPCount;
278
9.64k
}
279
280
/************************************************************************/
281
/*                          GetGCPSpatialRef()                          */
282
/************************************************************************/
283
284
const OGRSpatialReference *ERSDataset::GetGCPSpatialRef() const
285
286
9.64k
{
287
9.64k
    return m_oGCPSRS.IsEmpty() ? nullptr : &m_oGCPSRS;
288
9.64k
}
289
290
/************************************************************************/
291
/*                              GetGCPs()                               */
292
/************************************************************************/
293
294
const GDAL_GCP *ERSDataset::GetGCPs()
295
296
9.64k
{
297
9.64k
    return pasGCPList;
298
9.64k
}
299
300
/************************************************************************/
301
/*                              SetGCPs()                               */
302
/************************************************************************/
303
304
CPLErr ERSDataset::SetGCPs(int nGCPCountIn, const GDAL_GCP *pasGCPListIn,
305
                           const OGRSpatialReference *poSRS)
306
307
0
{
308
    /* -------------------------------------------------------------------- */
309
    /*      Clean old gcps.                                                 */
310
    /* -------------------------------------------------------------------- */
311
0
    m_oGCPSRS.Clear();
312
313
0
    if (nGCPCount > 0)
314
0
    {
315
0
        GDALDeinitGCPs(nGCPCount, pasGCPList);
316
0
        CPLFree(pasGCPList);
317
318
0
        pasGCPList = nullptr;
319
0
        nGCPCount = 0;
320
0
    }
321
322
    /* -------------------------------------------------------------------- */
323
    /*      Copy new ones.                                                  */
324
    /* -------------------------------------------------------------------- */
325
0
    nGCPCount = nGCPCountIn;
326
0
    pasGCPList = GDALDuplicateGCPs(nGCPCount, pasGCPListIn);
327
0
    if (poSRS)
328
0
        m_oGCPSRS = *poSRS;
329
330
    /* -------------------------------------------------------------------- */
331
    /*      Setup the header contents corresponding to these GCPs.          */
332
    /* -------------------------------------------------------------------- */
333
0
    bHDRDirty = TRUE;
334
335
0
    poHeader->Set("RasterInfo.WarpControl.WarpType", "Polynomial");
336
0
    if (nGCPCount > 6)
337
0
        poHeader->Set("RasterInfo.WarpControl.WarpOrder", "2");
338
0
    else
339
0
        poHeader->Set("RasterInfo.WarpControl.WarpOrder", "1");
340
0
    poHeader->Set("RasterInfo.WarpControl.WarpSampling", "Nearest");
341
342
    /* -------------------------------------------------------------------- */
343
    /*      Translate the projection.                                       */
344
    /* -------------------------------------------------------------------- */
345
0
    char szERSProj[32], szERSDatum[32], szERSUnits[32];
346
347
0
    m_oGCPSRS.exportToERM(szERSProj, szERSDatum, szERSUnits);
348
349
    /* Write the above computed values, unless they have been overridden by */
350
    /* the creation options PROJ, DATUM or UNITS */
351
352
0
    poHeader->Set("RasterInfo.WarpControl.CoordinateSpace.Datum",
353
0
                  CPLString().Printf("\"%s\"", (osDatum.size())
354
0
                                                   ? osDatum.c_str()
355
0
                                                   : szERSDatum));
356
0
    poHeader->Set("RasterInfo.WarpControl.CoordinateSpace.Projection",
357
0
                  CPLString().Printf("\"%s\"", (osProj.size()) ? osProj.c_str()
358
0
                                                               : szERSProj));
359
0
    poHeader->Set("RasterInfo.WarpControl.CoordinateSpace.CoordinateType",
360
0
                  CPLString().Printf("EN"));
361
0
    poHeader->Set("RasterInfo.WarpControl.CoordinateSpace.Units",
362
0
                  CPLString().Printf("\"%s\"", (osUnits.size())
363
0
                                                   ? osUnits.c_str()
364
0
                                                   : szERSUnits));
365
0
    poHeader->Set("RasterInfo.WarpControl.CoordinateSpace.Rotation", "0:0:0.0");
366
367
    /* -------------------------------------------------------------------- */
368
    /*      Translate the GCPs.                                             */
369
    /* -------------------------------------------------------------------- */
370
0
    CPLString osControlPoints = "{\n";
371
372
0
    for (int iGCP = 0; iGCP < nGCPCount; iGCP++)
373
0
    {
374
0
        CPLString osLine;
375
376
0
        CPLString osId = pasGCPList[iGCP].pszId;
377
0
        if (osId.empty())
378
0
            osId.Printf("%d", iGCP + 1);
379
380
0
        osLine.Printf(
381
0
            "\t\t\t\t\"%s\"\tYes\tYes\t%.6f\t%.6f\t%.15g\t%.15g\t%.15g\n",
382
0
            osId.c_str(), pasGCPList[iGCP].dfGCPPixel,
383
0
            pasGCPList[iGCP].dfGCPLine, pasGCPList[iGCP].dfGCPX,
384
0
            pasGCPList[iGCP].dfGCPY, pasGCPList[iGCP].dfGCPZ);
385
0
        osControlPoints += osLine;
386
0
    }
387
0
    osControlPoints += "\t\t}";
388
389
0
    poHeader->Set("RasterInfo.WarpControl.ControlPoints", osControlPoints);
390
391
0
    return CE_None;
392
0
}
393
394
/************************************************************************/
395
/*                           GetSpatialRef()                            */
396
/************************************************************************/
397
398
const OGRSpatialReference *ERSDataset::GetSpatialRef() const
399
400
9.64k
{
401
    // try xml first
402
9.64k
    const auto poSRS = GDALPamDataset::GetSpatialRef();
403
9.64k
    if (poSRS)
404
1.62k
        return poSRS;
405
406
8.01k
    return m_oSRS.IsEmpty() ? nullptr : &m_oSRS;
407
9.64k
}
408
409
/************************************************************************/
410
/*                           SetSpatialRef()                            */
411
/************************************************************************/
412
413
CPLErr ERSDataset::SetSpatialRef(const OGRSpatialReference *poSRS)
414
415
0
{
416
0
    if (poSRS == nullptr && m_oSRS.IsEmpty())
417
0
        return CE_None;
418
0
    if (poSRS != nullptr && poSRS->IsSame(&m_oSRS))
419
0
        return CE_None;
420
421
0
    m_oSRS.Clear();
422
0
    if (poSRS)
423
0
        m_oSRS = *poSRS;
424
425
0
    char szERSProj[32], szERSDatum[32], szERSUnits[32];
426
427
0
    m_oSRS.exportToERM(szERSProj, szERSDatum, szERSUnits);
428
429
    /* Write the above computed values, unless they have been overridden by */
430
    /* the creation options PROJ, DATUM or UNITS */
431
0
    if (!osProjForced.empty())
432
0
        osProj = osProjForced;
433
0
    else
434
0
        osProj = szERSProj;
435
0
    if (!osDatumForced.empty())
436
0
        osDatum = osDatumForced;
437
0
    else
438
0
        osDatum = szERSDatum;
439
0
    if (!osUnitsForced.empty())
440
0
        osUnits = osUnitsForced;
441
0
    else
442
0
        osUnits = szERSUnits;
443
444
0
    WriteProjectionInfo(osProj, osDatum, osUnits);
445
446
0
    return CE_None;
447
0
}
448
449
/************************************************************************/
450
/*                        WriteProjectionInfo()                         */
451
/************************************************************************/
452
453
void ERSDataset::WriteProjectionInfo(const char *pszProj, const char *pszDatum,
454
                                     const char *pszUnits)
455
0
{
456
0
    bHDRDirty = TRUE;
457
0
    poHeader->Set("CoordinateSpace.Datum",
458
0
                  CPLString().Printf("\"%s\"", pszDatum));
459
0
    poHeader->Set("CoordinateSpace.Projection",
460
0
                  CPLString().Printf("\"%s\"", pszProj));
461
0
    poHeader->Set("CoordinateSpace.CoordinateType", CPLString().Printf("EN"));
462
0
    poHeader->Set("CoordinateSpace.Units",
463
0
                  CPLString().Printf("\"%s\"", pszUnits));
464
0
    poHeader->Set("CoordinateSpace.Rotation", "0:0:0.0");
465
466
    /* -------------------------------------------------------------------- */
467
    /*      It seems that CoordinateSpace needs to come before              */
468
    /*      RasterInfo.  Try moving it up manually.                         */
469
    /* -------------------------------------------------------------------- */
470
0
    int iRasterInfo = -1;
471
0
    int iCoordSpace = -1;
472
473
0
    for (int i = 0; i < poHeader->nItemCount; i++)
474
0
    {
475
0
        if (EQUAL(poHeader->papszItemName[i], "RasterInfo"))
476
0
            iRasterInfo = i;
477
478
0
        if (EQUAL(poHeader->papszItemName[i], "CoordinateSpace"))
479
0
        {
480
0
            iCoordSpace = i;
481
0
            break;
482
0
        }
483
0
    }
484
485
0
    if (iCoordSpace > iRasterInfo && iRasterInfo != -1)
486
0
    {
487
0
        for (int i = iCoordSpace; i > 0 && i != iRasterInfo; i--)
488
0
        {
489
490
0
            ERSHdrNode *poTemp = poHeader->papoItemChild[i];
491
0
            poHeader->papoItemChild[i] = poHeader->papoItemChild[i - 1];
492
0
            poHeader->papoItemChild[i - 1] = poTemp;
493
494
0
            char *pszTemp = poHeader->papszItemName[i];
495
0
            poHeader->papszItemName[i] = poHeader->papszItemName[i - 1];
496
0
            poHeader->papszItemName[i - 1] = pszTemp;
497
498
0
            pszTemp = poHeader->papszItemValue[i];
499
0
            poHeader->papszItemValue[i] = poHeader->papszItemValue[i - 1];
500
0
            poHeader->papszItemValue[i - 1] = pszTemp;
501
0
        }
502
0
    }
503
0
}
504
505
/************************************************************************/
506
/*                          GetGeoTransform()                           */
507
/************************************************************************/
508
509
CPLErr ERSDataset::GetGeoTransform(GDALGeoTransform &gt) const
510
511
9.64k
{
512
9.64k
    if (bGotTransform)
513
167
    {
514
167
        gt = m_gt;
515
167
        return CE_None;
516
167
    }
517
518
9.47k
    return GDALPamDataset::GetGeoTransform(gt);
519
9.64k
}
520
521
/************************************************************************/
522
/*                          SetGeoTransform()                           */
523
/************************************************************************/
524
525
CPLErr ERSDataset::SetGeoTransform(const GDALGeoTransform &gt)
526
527
0
{
528
0
    if (m_gt == gt)
529
0
        return CE_None;
530
531
0
    if (gt.xrot != 0 || gt.yrot != 0)
532
0
    {
533
0
        CPLError(CE_Failure, CPLE_AppDefined,
534
0
                 "Rotated and skewed geotransforms not currently supported for "
535
0
                 "ERS driver.");
536
0
        return CE_Failure;
537
0
    }
538
539
0
    bGotTransform = TRUE;
540
0
    m_gt = gt;
541
542
0
    bHDRDirty = TRUE;
543
544
0
    poHeader->Set("RasterInfo.CellInfo.Xdimension",
545
0
                  CPLString().Printf("%.15g", fabs(m_gt.xscale)));
546
0
    poHeader->Set("RasterInfo.CellInfo.Ydimension",
547
0
                  CPLString().Printf("%.15g", fabs(m_gt.yscale)));
548
0
    poHeader->Set("RasterInfo.RegistrationCoord.Eastings",
549
0
                  CPLString().Printf("%.15g", m_gt.xorig));
550
0
    poHeader->Set("RasterInfo.RegistrationCoord.Northings",
551
0
                  CPLString().Printf("%.15g", m_gt.yorig));
552
553
0
    if (CPLAtof(poHeader->Find("RasterInfo.RegistrationCellX", "0")) != 0.0 ||
554
0
        CPLAtof(poHeader->Find("RasterInfo.RegistrationCellY", "0")) != 0.0)
555
0
    {
556
        // Reset RegistrationCellX/Y to 0 if the header gets rewritten (#5493)
557
0
        poHeader->Set("RasterInfo.RegistrationCellX", "0");
558
0
        poHeader->Set("RasterInfo.RegistrationCellY", "0");
559
0
    }
560
561
0
    return CE_None;
562
0
}
563
564
/************************************************************************/
565
/*                             ERSDMS2Dec()                             */
566
/*                                                                      */
567
/*      Convert ERS DMS format to decimal degrees.   Input is like      */
568
/*      "-180:00:00".                                                   */
569
/************************************************************************/
570
571
static double ERSDMS2Dec(const char *pszDMS)
572
573
2.72k
{
574
2.72k
    char **papszTokens = CSLTokenizeStringComplex(pszDMS, ":", FALSE, FALSE);
575
576
2.72k
    if (CSLCount(papszTokens) != 3)
577
1.60k
    {
578
1.60k
        CSLDestroy(papszTokens);
579
1.60k
        return CPLAtof(pszDMS);
580
1.60k
    }
581
582
1.11k
    double dfResult = fabs(CPLAtof(papszTokens[0])) +
583
1.11k
                      CPLAtof(papszTokens[1]) / 60.0 +
584
1.11k
                      CPLAtof(papszTokens[2]) / 3600.0;
585
586
1.11k
    if (CPLAtof(papszTokens[0]) < 0)
587
652
        dfResult *= -1;
588
589
1.11k
    CSLDestroy(papszTokens);
590
1.11k
    return dfResult;
591
2.72k
}
592
593
/************************************************************************/
594
/*                            GetFileList()                             */
595
/************************************************************************/
596
597
char **ERSDataset::GetFileList()
598
599
34.5k
{
600
34.5k
    static thread_local int nRecLevel = 0;
601
34.5k
    if (nRecLevel > 0)
602
0
        return nullptr;
603
604
    // Main data file, etc.
605
34.5k
    char **papszFileList = GDALPamDataset::GetFileList();
606
607
    // Add raw data file if we have one.
608
34.5k
    if (!osRawFilename.empty())
609
34.5k
        papszFileList = CSLAddString(papszFileList, osRawFilename);
610
611
    // If we have a dependent file, merge its list of files in.
612
34.5k
    if (poDepFile)
613
0
    {
614
0
        nRecLevel++;
615
0
        char **papszDepFiles = poDepFile->GetFileList();
616
0
        nRecLevel--;
617
0
        papszFileList = CSLInsertStrings(papszFileList, -1, papszDepFiles);
618
0
        CSLDestroy(papszDepFiles);
619
0
    }
620
621
34.5k
    return papszFileList;
622
34.5k
}
623
624
/************************************************************************/
625
/*                              ReadGCPs()                              */
626
/*                                                                      */
627
/*      Read the GCPs from the header.                                  */
628
/************************************************************************/
629
630
void ERSDataset::ReadGCPs()
631
632
10
{
633
10
    const char *pszCP =
634
10
        poHeader->Find("RasterInfo.WarpControl.ControlPoints", nullptr);
635
636
10
    if (pszCP == nullptr)
637
10
        return;
638
639
    /* -------------------------------------------------------------------- */
640
    /*      Parse the control points.  They will look something like:       */
641
    /*                                                                      */
642
    /*   "1035" Yes No 2344.650885 3546.419458 483270.73 3620906.21 3.105   */
643
    /* -------------------------------------------------------------------- */
644
0
    char **papszTokens = CSLTokenizeStringComplex(pszCP, "{ \t}", TRUE, FALSE);
645
0
    int nItemCount = CSLCount(papszTokens);
646
647
    /* -------------------------------------------------------------------- */
648
    /*      Work out if we have elevation values or not.                    */
649
    /* -------------------------------------------------------------------- */
650
0
    int nItemsPerLine;
651
652
0
    if (nItemCount == 7)
653
0
        nItemsPerLine = 7;
654
0
    else if (nItemCount == 8)
655
0
        nItemsPerLine = 8;
656
0
    else if (nItemCount < 14)
657
0
    {
658
0
        CPLDebug("ERS", "Invalid item count for ControlPoints");
659
0
        CSLDestroy(papszTokens);
660
0
        return;
661
0
    }
662
0
    else if (EQUAL(papszTokens[8], "Yes") || EQUAL(papszTokens[8], "No"))
663
0
        nItemsPerLine = 7;
664
0
    else if (EQUAL(papszTokens[9], "Yes") || EQUAL(papszTokens[9], "No"))
665
0
        nItemsPerLine = 8;
666
0
    else
667
0
    {
668
0
        CPLDebug("ERS", "Invalid format for ControlPoints");
669
0
        CSLDestroy(papszTokens);
670
0
        return;
671
0
    }
672
673
    /* -------------------------------------------------------------------- */
674
    /*      Setup GCPs.                                                     */
675
    /* -------------------------------------------------------------------- */
676
0
    CPLAssert(nGCPCount == 0);
677
678
0
    nGCPCount = nItemCount / nItemsPerLine;
679
0
    pasGCPList =
680
0
        static_cast<GDAL_GCP *>(CPLCalloc(nGCPCount, sizeof(GDAL_GCP)));
681
0
    GDALInitGCPs(nGCPCount, pasGCPList);
682
683
0
    for (int iGCP = 0; iGCP < nGCPCount; iGCP++)
684
0
    {
685
0
        GDAL_GCP *psGCP = pasGCPList + iGCP;
686
687
0
        CPLFree(psGCP->pszId);
688
0
        psGCP->pszId = CPLStrdup(papszTokens[iGCP * nItemsPerLine + 0]);
689
0
        psGCP->dfGCPPixel = CPLAtof(papszTokens[iGCP * nItemsPerLine + 3]);
690
0
        psGCP->dfGCPLine = CPLAtof(papszTokens[iGCP * nItemsPerLine + 4]);
691
0
        psGCP->dfGCPX = CPLAtof(papszTokens[iGCP * nItemsPerLine + 5]);
692
0
        psGCP->dfGCPY = CPLAtof(papszTokens[iGCP * nItemsPerLine + 6]);
693
0
        if (nItemsPerLine == 8)
694
0
            psGCP->dfGCPZ = CPLAtof(papszTokens[iGCP * nItemsPerLine + 7]);
695
0
    }
696
697
0
    CSLDestroy(papszTokens);
698
699
    /* -------------------------------------------------------------------- */
700
    /*      Parse the GCP projection.                                       */
701
    /* -------------------------------------------------------------------- */
702
0
    osProj =
703
0
        poHeader->Find("RasterInfo.WarpControl.CoordinateSpace.Projection", "");
704
0
    osDatum =
705
0
        poHeader->Find("RasterInfo.WarpControl.CoordinateSpace.Datum", "");
706
0
    osUnits =
707
0
        poHeader->Find("RasterInfo.WarpControl.CoordinateSpace.Units", "");
708
709
0
    m_oGCPSRS.importFromERM(!osProj.empty() ? osProj.c_str() : "RAW",
710
0
                            !osDatum.empty() ? osDatum.c_str() : "WGS84",
711
0
                            !osUnits.empty() ? osUnits.c_str() : "METERS");
712
0
}
713
714
/************************************************************************/
715
/* ==================================================================== */
716
/*                             ERSRasterBand                            */
717
/* ==================================================================== */
718
/************************************************************************/
719
720
class ERSRasterBand final : public RawRasterBand
721
{
722
  public:
723
    ERSRasterBand(GDALDataset *poDS, int nBand, VSILFILE *fpRaw,
724
                  vsi_l_offset nImgOffset, int nPixelOffset, int nLineOffset,
725
                  GDALDataType eDataType, int bNativeOrder);
726
727
    double GetNoDataValue(int *pbSuccess = nullptr) override;
728
    CPLErr SetNoDataValue(double) override;
729
};
730
731
/************************************************************************/
732
/*                           ERSRasterBand()                            */
733
/************************************************************************/
734
735
ERSRasterBand::ERSRasterBand(GDALDataset *poDSIn, int nBandIn,
736
                             VSILFILE *fpRawIn, vsi_l_offset nImgOffsetIn,
737
                             int nPixelOffsetIn, int nLineOffsetIn,
738
                             GDALDataType eDataTypeIn, int bNativeOrderIn)
739
791k
    : RawRasterBand(poDSIn, nBandIn, fpRawIn, nImgOffsetIn, nPixelOffsetIn,
740
791k
                    nLineOffsetIn, eDataTypeIn, bNativeOrderIn,
741
791k
                    RawRasterBand::OwnFP::NO)
742
791k
{
743
791k
}
744
745
/************************************************************************/
746
/*                           GetNoDataValue()                           */
747
/************************************************************************/
748
749
double ERSRasterBand::GetNoDataValue(int *pbSuccess)
750
31.0k
{
751
31.0k
    ERSDataset *poGDS = cpl::down_cast<ERSDataset *>(poDS);
752
31.0k
    if (poGDS->bHasNoDataValue)
753
4.79k
    {
754
4.79k
        if (pbSuccess)
755
4.35k
            *pbSuccess = TRUE;
756
4.79k
        return poGDS->dfNoDataValue;
757
4.79k
    }
758
759
26.2k
    return RawRasterBand::GetNoDataValue(pbSuccess);
760
31.0k
}
761
762
/************************************************************************/
763
/*                           SetNoDataValue()                           */
764
/************************************************************************/
765
766
CPLErr ERSRasterBand::SetNoDataValue(double dfNoDataValue)
767
0
{
768
0
    ERSDataset *poGDS = cpl::down_cast<ERSDataset *>(poDS);
769
0
    if (!poGDS->bHasNoDataValue || poGDS->dfNoDataValue != dfNoDataValue)
770
0
    {
771
0
        poGDS->bHasNoDataValue = TRUE;
772
0
        poGDS->dfNoDataValue = dfNoDataValue;
773
774
0
        poGDS->bHDRDirty = TRUE;
775
0
        poGDS->poHeader->Set("RasterInfo.NullCellValue",
776
0
                             CPLString().Printf("%.16g", dfNoDataValue));
777
0
    }
778
0
    return CE_None;
779
0
}
780
781
/************************************************************************/
782
/*                              Identify()                              */
783
/************************************************************************/
784
785
int ERSDataset::Identify(GDALOpenInfo *poOpenInfo)
786
787
658k
{
788
    /* -------------------------------------------------------------------- */
789
    /*      We assume the user selects the .ers file.                       */
790
    /* -------------------------------------------------------------------- */
791
658k
    CPLString osHeader(reinterpret_cast<const char *>(poOpenInfo->pabyHeader),
792
658k
                       poOpenInfo->nHeaderBytes);
793
794
658k
    if (osHeader.ifind("Algorithm Begin") != std::string::npos)
795
88
    {
796
88
        CPLError(CE_Failure, CPLE_OpenFailed,
797
88
                 "%s appears to be an algorithm ERS file, which is not "
798
88
                 "currently supported.",
799
88
                 poOpenInfo->pszFilename);
800
88
        return FALSE;
801
88
    }
802
803
658k
    if (osHeader.ifind("DatasetHeader ") != std::string::npos)
804
105k
        return TRUE;
805
806
552k
    return FALSE;
807
658k
}
808
809
/************************************************************************/
810
/*                          ERSProxyRasterBand                          */
811
/************************************************************************/
812
813
namespace
814
{
815
816
static int &GetRecLevel()
817
52.7k
{
818
52.7k
    static thread_local int nRecLevel = 0;
819
52.7k
    return nRecLevel;
820
52.7k
}
821
822
class ERSProxyRasterBand final : public GDALProxyRasterBand
823
{
824
  public:
825
    explicit ERSProxyRasterBand(GDALRasterBand *poUnderlyingBand)
826
0
        : m_poUnderlyingBand(poUnderlyingBand)
827
0
    {
828
0
        poUnderlyingBand->GetBlockSize(&nBlockXSize, &nBlockYSize);
829
0
        eDataType = poUnderlyingBand->GetRasterDataType();
830
0
    }
831
832
    int GetOverviewCount() override;
833
834
  protected:
835
    GDALRasterBand *RefUnderlyingRasterBand(bool /*bForceOpen*/) const override
836
0
    {
837
0
        return m_poUnderlyingBand;
838
0
    }
839
840
  private:
841
    GDALRasterBand *m_poUnderlyingBand = nullptr;
842
843
    CPL_DISALLOW_COPY_ASSIGN(ERSProxyRasterBand)
844
};
845
846
int ERSProxyRasterBand::GetOverviewCount()
847
0
{
848
0
    int &nRecLevel = GetRecLevel();
849
0
    nRecLevel++;
850
0
    int nRet = GDALProxyRasterBand::GetOverviewCount();
851
0
    nRecLevel--;
852
0
    return nRet;
853
0
}
854
855
}  // namespace
856
857
/************************************************************************/
858
/*                                Open()                                */
859
/************************************************************************/
860
861
GDALDataset *ERSDataset::Open(GDALOpenInfo *poOpenInfo)
862
863
52.7k
{
864
52.7k
    if (!Identify(poOpenInfo) || poOpenInfo->fpL == nullptr)
865
0
        return nullptr;
866
867
52.7k
    int &nRecLevel = GetRecLevel();
868
    // cppcheck-suppress knownConditionTrueFalse
869
52.7k
    if (nRecLevel)
870
1.83k
    {
871
1.83k
        CPLError(CE_Failure, CPLE_AppDefined,
872
1.83k
                 "Attempt at recursively opening ERS dataset");
873
1.83k
        return nullptr;
874
1.83k
    }
875
876
    /* -------------------------------------------------------------------- */
877
    /*      Ingest the file as a tree of header nodes.                      */
878
    /* -------------------------------------------------------------------- */
879
50.8k
    ERSHdrNode *poHeader = new ERSHdrNode();
880
881
50.8k
    if (!poHeader->ParseHeader(poOpenInfo->fpL))
882
4.33k
    {
883
4.33k
        delete poHeader;
884
4.33k
        VSIFCloseL(poOpenInfo->fpL);
885
4.33k
        poOpenInfo->fpL = nullptr;
886
4.33k
        return nullptr;
887
4.33k
    }
888
889
46.5k
    VSIFCloseL(poOpenInfo->fpL);
890
46.5k
    poOpenInfo->fpL = nullptr;
891
892
    /* -------------------------------------------------------------------- */
893
    /*      Do we have the minimum required information from this header?   */
894
    /* -------------------------------------------------------------------- */
895
46.5k
    if (poHeader->Find("RasterInfo.NrOfLines") == nullptr ||
896
46.0k
        poHeader->Find("RasterInfo.NrOfCellsPerLine") == nullptr ||
897
45.9k
        poHeader->Find("RasterInfo.NrOfBands") == nullptr)
898
727
    {
899
727
        if (poHeader->FindNode("Algorithm") != nullptr)
900
18
        {
901
18
            CPLError(CE_Failure, CPLE_OpenFailed,
902
18
                     "%s appears to be an algorithm ERS file, which is not "
903
18
                     "currently supported.",
904
18
                     poOpenInfo->pszFilename);
905
18
        }
906
727
        delete poHeader;
907
727
        return nullptr;
908
727
    }
909
910
    /* -------------------------------------------------------------------- */
911
    /*      Create a corresponding GDALDataset.                             */
912
    /* -------------------------------------------------------------------- */
913
45.8k
    auto poDS = std::make_unique<ERSDataset>();
914
45.8k
    poDS->poHeader = poHeader;
915
45.8k
    poDS->eAccess = poOpenInfo->eAccess;
916
917
    /* -------------------------------------------------------------------- */
918
    /*      Capture some information from the file that is of interest.     */
919
    /* -------------------------------------------------------------------- */
920
45.8k
    int nBands = atoi(poHeader->Find("RasterInfo.NrOfBands"));
921
45.8k
    poDS->nRasterXSize = atoi(poHeader->Find("RasterInfo.NrOfCellsPerLine"));
922
45.8k
    poDS->nRasterYSize = atoi(poHeader->Find("RasterInfo.NrOfLines"));
923
924
45.8k
    if (!GDALCheckDatasetDimensions(poDS->nRasterXSize, poDS->nRasterYSize) ||
925
45.6k
        !GDALCheckBandCount(nBands, FALSE))
926
398
    {
927
398
        return nullptr;
928
398
    }
929
930
    /* -------------------------------------------------------------------- */
931
    /*     Get the HeaderOffset if it exists in the header                  */
932
    /* -------------------------------------------------------------------- */
933
45.4k
    GIntBig nHeaderOffset = 0;
934
45.4k
    const char *pszHeaderOffset = poHeader->Find("HeaderOffset");
935
45.4k
    if (pszHeaderOffset != nullptr)
936
454
    {
937
454
        nHeaderOffset = CPLAtoGIntBig(pszHeaderOffset);
938
454
        if (nHeaderOffset < 0)
939
69
        {
940
69
            CPLError(CE_Failure, CPLE_AppDefined,
941
69
                     "Illegal value for HeaderOffset: %s", pszHeaderOffset);
942
69
            return nullptr;
943
69
        }
944
454
    }
945
946
    /* -------------------------------------------------------------------- */
947
    /*      Establish the data type.                                        */
948
    /* -------------------------------------------------------------------- */
949
45.3k
    CPLString osCellType =
950
45.3k
        poHeader->Find("RasterInfo.CellType", "Unsigned8BitInteger");
951
45.3k
    GDALDataType eType;
952
45.3k
    if (EQUAL(osCellType, "Unsigned8BitInteger"))
953
41.4k
        eType = GDT_UInt8;
954
3.93k
    else if (EQUAL(osCellType, "Signed8BitInteger"))
955
281
        eType = GDT_Int8;
956
3.65k
    else if (EQUAL(osCellType, "Unsigned16BitInteger"))
957
91
        eType = GDT_UInt16;
958
3.56k
    else if (EQUAL(osCellType, "Signed16BitInteger"))
959
61
        eType = GDT_Int16;
960
3.50k
    else if (EQUAL(osCellType, "Unsigned32BitInteger"))
961
154
        eType = GDT_UInt32;
962
3.35k
    else if (EQUAL(osCellType, "Signed32BitInteger"))
963
205
        eType = GDT_Int32;
964
3.14k
    else if (EQUAL(osCellType, "IEEE4ByteReal"))
965
151
        eType = GDT_Float32;
966
2.99k
    else if (EQUAL(osCellType, "IEEE8ByteReal"))
967
181
        eType = GDT_Float64;
968
2.81k
    else
969
2.81k
    {
970
2.81k
        CPLDebug("ERS", "Unknown CellType '%s'", osCellType.c_str());
971
2.81k
        eType = GDT_UInt8;
972
2.81k
    }
973
974
    /* -------------------------------------------------------------------- */
975
    /*      Pick up the word order.                                         */
976
    /* -------------------------------------------------------------------- */
977
45.3k
    const int bNative =
978
45.3k
#ifdef CPL_LSB
979
45.3k
        EQUAL(poHeader->Find("ByteOrder", "LSBFirst"), "LSBFirst")
980
#else
981
        EQUAL(poHeader->Find("ByteOrder", "MSBFirst"), "MSBFirst")
982
#endif
983
45.3k
        ;
984
    /* -------------------------------------------------------------------- */
985
    /*      Figure out the name of the target file.                         */
986
    /* -------------------------------------------------------------------- */
987
45.3k
    CPLString osPath = CPLGetPathSafe(poOpenInfo->pszFilename);
988
45.3k
    CPLString osDataFile = poHeader->Find("DataFile", "");
989
990
45.3k
    if (osDataFile.empty())  // just strip off extension.
991
41.8k
    {
992
41.8k
        osDataFile = CPLGetFilename(poOpenInfo->pszFilename);
993
41.8k
        osDataFile = osDataFile.substr(0, osDataFile.find_last_of('.'));
994
41.8k
    }
995
45.3k
    if (CPLHasPathTraversal(osDataFile.c_str()))
996
134
    {
997
134
        CPLError(CE_Failure, CPLE_AppDefined, "Path traversal detected in %s",
998
134
                 osDataFile.c_str());
999
134
        return nullptr;
1000
134
    }
1001
45.2k
    CPLString osDataFilePath = CPLFormFilenameSafe(osPath, osDataFile, nullptr);
1002
1003
    /* -------------------------------------------------------------------- */
1004
    /*      DataSetType = Translated files are links to things like ecw     */
1005
    /*      files.                                                          */
1006
    /* -------------------------------------------------------------------- */
1007
45.2k
    if (EQUAL(poHeader->Find("DataSetType", ""), "Translated"))
1008
2.88k
    {
1009
2.88k
        nRecLevel++;
1010
2.88k
        poDS->poDepFile = GDALDataset::FromHandle(GDALDataset::Open(
1011
2.88k
            osDataFilePath,
1012
2.88k
            poOpenInfo->eAccess | GDAL_OF_RASTER | GDAL_OF_VERBOSE_ERROR));
1013
2.88k
        nRecLevel--;
1014
1015
2.88k
        if (poDS->poDepFile != nullptr &&
1016
0
            poDS->poDepFile->GetRasterXSize() == poDS->GetRasterXSize() &&
1017
0
            poDS->poDepFile->GetRasterYSize() == poDS->GetRasterYSize() &&
1018
0
            poDS->poDepFile->GetRasterCount() >= nBands)
1019
0
        {
1020
0
            for (int iBand = 0; iBand < nBands; iBand++)
1021
0
            {
1022
                // Assume pixel interleaved.
1023
0
                poDS->SetBand(iBand + 1,
1024
0
                              new ERSProxyRasterBand(
1025
0
                                  poDS->poDepFile->GetRasterBand(iBand + 1)));
1026
0
            }
1027
0
        }
1028
2.88k
        else
1029
2.88k
        {
1030
2.88k
            delete poDS->poDepFile;
1031
2.88k
            poDS->poDepFile = nullptr;
1032
2.88k
        }
1033
2.88k
    }
1034
1035
    /* ==================================================================== */
1036
    /*      While ERStorage indicates a raw file.                           */
1037
    /* ==================================================================== */
1038
42.3k
    else if (EQUAL(poHeader->Find("DataSetType", ""), "ERStorage"))
1039
41.8k
    {
1040
        // Open data file.
1041
41.8k
        if (poOpenInfo->eAccess == GA_Update)
1042
0
            poDS->fpImage = VSIFOpenL(osDataFilePath, "r+");
1043
41.8k
        else
1044
41.8k
            poDS->fpImage = VSIFOpenL(osDataFilePath, "r");
1045
1046
41.8k
        poDS->osRawFilename = std::move(osDataFilePath);
1047
1048
41.8k
        if (poDS->fpImage != nullptr && nBands > 0)
1049
41.3k
        {
1050
41.3k
            int iWordSize = GDALGetDataTypeSizeBytes(eType);
1051
1052
41.3k
            const auto knIntMax = std::numeric_limits<int>::max();
1053
41.3k
            if (nBands > knIntMax / iWordSize ||
1054
41.3k
                poDS->nRasterXSize > knIntMax / (nBands * iWordSize))
1055
22
            {
1056
22
                CPLError(CE_Failure, CPLE_AppDefined,
1057
22
                         "int overflow: too large nBands and/or nRasterXSize");
1058
22
                return nullptr;
1059
22
            }
1060
1061
41.2k
            if (!RAWDatasetCheckMemoryUsage(
1062
41.2k
                    poDS->nRasterXSize, poDS->nRasterYSize, nBands, iWordSize,
1063
41.2k
                    iWordSize, iWordSize * nBands * poDS->nRasterXSize,
1064
41.2k
                    nHeaderOffset,
1065
41.2k
                    static_cast<vsi_l_offset>(iWordSize) * poDS->nRasterXSize,
1066
41.2k
                    poDS->fpImage))
1067
116
            {
1068
116
                return nullptr;
1069
116
            }
1070
41.1k
            if (nHeaderOffset > std::numeric_limits<GIntBig>::max() -
1071
41.1k
                                    static_cast<GIntBig>(nBands - 1) *
1072
41.1k
                                        iWordSize * poDS->nRasterXSize)
1073
24
            {
1074
24
                CPLError(CE_Failure, CPLE_AppDefined,
1075
24
                         "int overflow: too large nHeaderOffset");
1076
24
                return nullptr;
1077
24
            }
1078
1079
832k
            for (int iBand = 0; iBand < nBands; iBand++)
1080
791k
            {
1081
                // Assume pixel interleaved.
1082
791k
                auto poBand = std::make_unique<ERSRasterBand>(
1083
791k
                    poDS.get(), iBand + 1, poDS->fpImage,
1084
791k
                    nHeaderOffset + static_cast<vsi_l_offset>(iWordSize) *
1085
791k
                                        iBand * poDS->nRasterXSize,
1086
791k
                    iWordSize, iWordSize * nBands * poDS->nRasterXSize, eType,
1087
791k
                    bNative);
1088
791k
                if (!poBand->IsValid())
1089
20
                    return nullptr;
1090
791k
                poDS->SetBand(iBand + 1, std::move(poBand));
1091
791k
            }
1092
41.1k
        }
1093
41.8k
    }
1094
1095
    /* -------------------------------------------------------------------- */
1096
    /*      Otherwise we have an error!                                     */
1097
    /* -------------------------------------------------------------------- */
1098
45.0k
    if (poDS->nBands == 0)
1099
3.89k
    {
1100
3.89k
        return nullptr;
1101
3.89k
    }
1102
1103
    /* -------------------------------------------------------------------- */
1104
    /*      Look for band descriptions.                                     */
1105
    /* -------------------------------------------------------------------- */
1106
41.1k
    ERSHdrNode *poRI = poHeader->FindNode("RasterInfo");
1107
1108
41.1k
    for (int iChild = 0, iBand = 0;
1109
1.47M
         poRI != nullptr && iChild < poRI->nItemCount && iBand < poDS->nBands;
1110
1.42M
         iChild++)
1111
1.42M
    {
1112
1.42M
        if (poRI->papoItemChild[iChild] != nullptr &&
1113
12.0k
            EQUAL(poRI->papszItemName[iChild], "BandId"))
1114
3.72k
        {
1115
3.72k
            const char *pszValue =
1116
3.72k
                poRI->papoItemChild[iChild]->Find("Value", nullptr);
1117
1118
3.72k
            iBand++;
1119
3.72k
            if (pszValue)
1120
2.57k
            {
1121
2.57k
                CPLPushErrorHandler(CPLQuietErrorHandler);
1122
2.57k
                poDS->GetRasterBand(iBand)->SetDescription(pszValue);
1123
2.57k
                CPLPopErrorHandler();
1124
2.57k
            }
1125
1126
3.72k
            pszValue = poRI->papoItemChild[iChild]->Find("Units", nullptr);
1127
3.72k
            if (pszValue)
1128
1.12k
            {
1129
1.12k
                CPLPushErrorHandler(CPLQuietErrorHandler);
1130
1.12k
                poDS->GetRasterBand(iBand)->SetUnitType(pszValue);
1131
1.12k
                CPLPopErrorHandler();
1132
1.12k
            }
1133
3.72k
        }
1134
1.42M
    }
1135
1136
    /* -------------------------------------------------------------------- */
1137
    /*      Look for projection.                                            */
1138
    /* -------------------------------------------------------------------- */
1139
41.1k
    poDS->osProj = poHeader->Find("CoordinateSpace.Projection", "");
1140
41.1k
    poDS->osDatum = poHeader->Find("CoordinateSpace.Datum", "");
1141
41.1k
    poDS->osUnits = poHeader->Find("CoordinateSpace.Units", "");
1142
1143
41.1k
    poDS->m_oSRS.importFromERM(
1144
41.1k
        !poDS->osProj.empty() ? poDS->osProj.c_str() : "RAW",
1145
41.1k
        !poDS->osDatum.empty() ? poDS->osDatum.c_str() : "WGS84",
1146
41.1k
        !poDS->osUnits.empty() ? poDS->osUnits.c_str() : "METERS");
1147
1148
    /* -------------------------------------------------------------------- */
1149
    /*      Look for the geotransform.                                      */
1150
    /* -------------------------------------------------------------------- */
1151
41.1k
    if (const char *pszEastings =
1152
41.1k
            poHeader->Find("RasterInfo.RegistrationCoord.Eastings", nullptr))
1153
0
    {
1154
0
        poDS->bGotTransform = true;
1155
0
        poDS->m_gt.xorig = CPLAtof(pszEastings);
1156
0
        poDS->m_gt.xscale =
1157
0
            CPLAtof(poHeader->Find("RasterInfo.CellInfo.Xdimension", "1.0"));
1158
0
        poDS->m_gt.xrot = 0.0;
1159
0
        poDS->m_gt.yorig = CPLAtof(
1160
0
            poHeader->Find("RasterInfo.RegistrationCoord.Northings", ""));
1161
0
        poDS->m_gt.yscale =
1162
0
            -CPLAtof(poHeader->Find("RasterInfo.CellInfo.Ydimension", "1.0"));
1163
0
        poDS->m_gt.yrot = 0.0;
1164
0
    }
1165
41.1k
    else if (const char *pszMetersX = poHeader->Find(
1166
41.1k
                 "RasterInfo.RegistrationCoord.MetersX", nullptr))
1167
0
    {
1168
0
        poDS->bGotTransform = true;
1169
0
        poDS->m_gt.xorig = CPLAtof(pszMetersX);
1170
0
        poDS->m_gt.xscale =
1171
0
            CPLAtof(poHeader->Find("RasterInfo.CellInfo.Xdimension", "1.0"));
1172
0
        poDS->m_gt.xrot = 0.0;
1173
0
        poDS->m_gt.yorig =
1174
0
            CPLAtof(poHeader->Find("RasterInfo.RegistrationCoord.MetersY", ""));
1175
0
        poDS->m_gt.yscale =
1176
0
            -CPLAtof(poHeader->Find("RasterInfo.CellInfo.Ydimension", "1.0"));
1177
0
        poDS->m_gt.yrot = 0.0;
1178
0
    }
1179
41.1k
    else if (poHeader->Find("RasterInfo.RegistrationCoord.Latitude", nullptr) &&
1180
1.43k
             poHeader->Find("RasterInfo.CellInfo.Xdimension", nullptr))
1181
1.36k
    {
1182
1.36k
        poDS->bGotTransform = true;
1183
1.36k
        poDS->m_gt.xorig = ERSDMS2Dec(
1184
1.36k
            poHeader->Find("RasterInfo.RegistrationCoord.Longitude", ""));
1185
1.36k
        poDS->m_gt.xscale =
1186
1.36k
            CPLAtof(poHeader->Find("RasterInfo.CellInfo.Xdimension", ""));
1187
1.36k
        poDS->m_gt.xrot = 0.0;
1188
1.36k
        poDS->m_gt.yorig = ERSDMS2Dec(
1189
1.36k
            poHeader->Find("RasterInfo.RegistrationCoord.Latitude", ""));
1190
1.36k
        poDS->m_gt.yrot = 0.0;
1191
1.36k
        poDS->m_gt.yscale =
1192
1.36k
            -CPLAtof(poHeader->Find("RasterInfo.CellInfo.Ydimension", ""));
1193
1.36k
    }
1194
1195
    /* -------------------------------------------------------------------- */
1196
    /*      Adjust if we have a registration cell.                          */
1197
    /* -------------------------------------------------------------------- */
1198
1199
    /* http://geospatial.intergraph.com/Libraries/Tech_Docs/ERDAS_ER_Mapper_Customization_Guide.sflb.ashx
1200
     */
1201
    /* Page 27 : */
1202
    /* RegistrationCellX and RegistrationCellY : The image X and Y
1203
       coordinates of the cell which corresponds to the Registration
1204
       Coordinate. Note that the RegistrationCellX and
1205
       RegistrationCellY can be fractional values. If
1206
       RegistrationCellX and RegistrationCellY are not specified,
1207
       they are assumed to be (0,0), which is the top left corner of the
1208
       image.
1209
       */
1210
41.1k
    double dfCellX =
1211
41.1k
        CPLAtof(poHeader->Find("RasterInfo.RegistrationCellX", "0"));
1212
41.1k
    double dfCellY =
1213
41.1k
        CPLAtof(poHeader->Find("RasterInfo.RegistrationCellY", "0"));
1214
1215
41.1k
    if (poDS->bGotTransform)
1216
1.36k
    {
1217
1.36k
        poDS->m_gt.xorig -=
1218
1.36k
            dfCellX * poDS->m_gt.xscale + dfCellY * poDS->m_gt.xrot;
1219
1.36k
        poDS->m_gt.yorig -=
1220
1.36k
            dfCellX * poDS->m_gt.yrot + dfCellY * poDS->m_gt.yscale;
1221
1.36k
    }
1222
1223
    /* -------------------------------------------------------------------- */
1224
    /*      Check for null values.                                          */
1225
    /* -------------------------------------------------------------------- */
1226
41.1k
    if (poHeader->Find("RasterInfo.NullCellValue", nullptr))
1227
5.61k
    {
1228
5.61k
        poDS->bHasNoDataValue = TRUE;
1229
5.61k
        poDS->dfNoDataValue =
1230
5.61k
            CPLAtofM(poHeader->Find("RasterInfo.NullCellValue"));
1231
1232
5.61k
        if (poDS->poDepFile != nullptr)
1233
0
        {
1234
0
            CPLPushErrorHandler(CPLQuietErrorHandler);
1235
1236
0
            for (int iBand = 1; iBand <= poDS->nBands; iBand++)
1237
0
                poDS->GetRasterBand(iBand)->SetNoDataValue(poDS->dfNoDataValue);
1238
1239
0
            CPLPopErrorHandler();
1240
0
        }
1241
5.61k
    }
1242
1243
    /* -------------------------------------------------------------------- */
1244
    /*      Do we have an "All" region?                                     */
1245
    /* -------------------------------------------------------------------- */
1246
41.1k
    ERSHdrNode *poAll = nullptr;
1247
1248
1.47M
    for (int iChild = 0; poRI != nullptr && iChild < poRI->nItemCount; iChild++)
1249
1.43M
    {
1250
1.43M
        if (poRI->papoItemChild[iChild] != nullptr &&
1251
12.2k
            EQUAL(poRI->papszItemName[iChild], "RegionInfo"))
1252
557
        {
1253
557
            if (EQUAL(poRI->papoItemChild[iChild]->Find("RegionName", ""),
1254
557
                      "All"))
1255
303
                poAll = poRI->papoItemChild[iChild];
1256
557
        }
1257
1.43M
    }
1258
1259
    /* -------------------------------------------------------------------- */
1260
    /*      Do we have statistics?                                          */
1261
    /* -------------------------------------------------------------------- */
1262
41.1k
    if (poAll && poAll->FindNode("Stats"))
1263
244
    {
1264
244
        CPLPushErrorHandler(CPLQuietErrorHandler);
1265
1266
82.8k
        for (int iBand = 1; iBand <= poDS->nBands; iBand++)
1267
82.6k
        {
1268
82.6k
            const char *pszValue =
1269
82.6k
                poAll->FindElem("Stats.MinimumValue", iBand - 1);
1270
1271
82.6k
            if (pszValue)
1272
1.04k
                poDS->GetRasterBand(iBand)->SetMetadataItem(
1273
1.04k
                    "STATISTICS_MINIMUM", pszValue);
1274
1275
82.6k
            pszValue = poAll->FindElem("Stats.MaximumValue", iBand - 1);
1276
1277
82.6k
            if (pszValue)
1278
754
                poDS->GetRasterBand(iBand)->SetMetadataItem(
1279
754
                    "STATISTICS_MAXIMUM", pszValue);
1280
1281
82.6k
            pszValue = poAll->FindElem("Stats.MeanValue", iBand - 1);
1282
1283
82.6k
            if (pszValue)
1284
2.54k
                poDS->GetRasterBand(iBand)->SetMetadataItem("STATISTICS_MEAN",
1285
2.54k
                                                            pszValue);
1286
1287
82.6k
            pszValue = poAll->FindElem("Stats.MedianValue", iBand - 1);
1288
1289
82.6k
            if (pszValue)
1290
1.36k
                poDS->GetRasterBand(iBand)->SetMetadataItem("STATISTICS_MEDIAN",
1291
1.36k
                                                            pszValue);
1292
82.6k
        }
1293
1294
244
        CPLPopErrorHandler();
1295
244
    }
1296
1297
    /* -------------------------------------------------------------------- */
1298
    /*      Do we have GCPs.                                                */
1299
    /* -------------------------------------------------------------------- */
1300
41.1k
    if (poHeader->FindNode("RasterInfo.WarpControl"))
1301
10
        poDS->ReadGCPs();
1302
1303
    /* -------------------------------------------------------------------- */
1304
    /*      Initialize any PAM information.                                 */
1305
    /* -------------------------------------------------------------------- */
1306
41.1k
    poDS->SetDescription(poOpenInfo->pszFilename);
1307
41.1k
    poDS->TryLoadXML();
1308
1309
    // if no SR in xml, try aux
1310
41.1k
    const OGRSpatialReference *poSRS = poDS->GDALPamDataset::GetSpatialRef();
1311
41.1k
    if (poSRS == nullptr)
1312
28.3k
    {
1313
        // try aux
1314
28.3k
        auto poAuxDS = std::unique_ptr<GDALDataset>(GDALFindAssociatedAuxFile(
1315
28.3k
            poOpenInfo->pszFilename, GA_ReadOnly, poDS.get()));
1316
28.3k
        if (poAuxDS)
1317
0
        {
1318
0
            poSRS = poAuxDS->GetSpatialRef();
1319
0
            if (poSRS)
1320
0
            {
1321
0
                poDS->m_oSRS = *poSRS;
1322
0
            }
1323
0
        }
1324
28.3k
    }
1325
    /* -------------------------------------------------------------------- */
1326
    /*      Check for overviews.                                            */
1327
    /* -------------------------------------------------------------------- */
1328
41.1k
    poDS->oOvManager.Initialize(poDS.get(), poOpenInfo->pszFilename);
1329
1330
41.1k
    return poDS.release();
1331
45.0k
}
1332
1333
/************************************************************************/
1334
/*                               Create()                               */
1335
/************************************************************************/
1336
1337
GDALDataset *ERSDataset::Create(const char *pszFilename, int nXSize, int nYSize,
1338
                                int nBandsIn, GDALDataType eType,
1339
                                CSLConstList papszOptions)
1340
1341
0
{
1342
    /* -------------------------------------------------------------------- */
1343
    /*      Verify settings.                                                */
1344
    /* -------------------------------------------------------------------- */
1345
0
    if (nBandsIn <= 0)
1346
0
    {
1347
0
        CPLError(CE_Failure, CPLE_NotSupported,
1348
0
                 "ERS driver does not support %d bands.\n", nBandsIn);
1349
0
        return nullptr;
1350
0
    }
1351
1352
0
    const char *pszCellType = "Unsigned8BitInteger";
1353
0
    switch (eType)
1354
0
    {
1355
0
        case GDT_UInt8:
1356
0
            break;
1357
0
        case GDT_Int8:
1358
0
            pszCellType = "Signed8BitInteger";
1359
0
            break;
1360
0
        case GDT_Int16:
1361
0
            pszCellType = "Signed16BitInteger";
1362
0
            break;
1363
0
        case GDT_UInt16:
1364
0
            pszCellType = "Unsigned16BitInteger";
1365
0
            break;
1366
0
        case GDT_Int32:
1367
0
            pszCellType = "Signed32BitInteger";
1368
0
            break;
1369
0
        case GDT_UInt32:
1370
0
            pszCellType = "Unsigned32BitInteger";
1371
0
            break;
1372
0
        case GDT_Float32:
1373
0
            pszCellType = "IEEE4ByteReal";
1374
0
            break;
1375
0
        case GDT_Float64:
1376
0
            pszCellType = "IEEE8ByteReal";
1377
0
            break;
1378
0
        case GDT_UInt64:
1379
0
        case GDT_Int64:
1380
0
        case GDT_Float16:
1381
0
        case GDT_CInt16:
1382
0
        case GDT_CInt32:
1383
0
        case GDT_CFloat16:
1384
0
        case GDT_CFloat32:
1385
0
        case GDT_CFloat64:
1386
0
        case GDT_Unknown:
1387
0
        case GDT_TypeCount:
1388
0
            CPLError(CE_Failure, CPLE_AppDefined,
1389
0
                     "The ERS driver does not supporting creating files of "
1390
0
                     "types %s.",
1391
0
                     GDALGetDataTypeName(eType));
1392
0
            return nullptr;
1393
0
    }
1394
1395
    /* -------------------------------------------------------------------- */
1396
    /*      Work out the name we want to use for the .ers and binary        */
1397
    /*      data files.                                                     */
1398
    /* -------------------------------------------------------------------- */
1399
0
    CPLString osBinFile, osErsFile;
1400
1401
0
    if (EQUAL(CPLGetExtensionSafe(pszFilename).c_str(), "ers"))
1402
0
    {
1403
0
        osErsFile = pszFilename;
1404
0
        osBinFile = osErsFile.substr(0, osErsFile.length() - 4);
1405
0
    }
1406
0
    else
1407
0
    {
1408
0
        osBinFile = pszFilename;
1409
0
        osErsFile = osBinFile + ".ers";
1410
0
    }
1411
1412
    /* -------------------------------------------------------------------- */
1413
    /*      Handling for signed eight bit data.                             */
1414
    /* -------------------------------------------------------------------- */
1415
0
    const char *pszPixelType = CSLFetchNameValue(papszOptions, "PIXELTYPE");
1416
0
    if (pszPixelType && EQUAL(pszPixelType, "SIGNEDBYTE") && eType == GDT_UInt8)
1417
0
        pszCellType = "Signed8BitInteger";
1418
1419
    /* -------------------------------------------------------------------- */
1420
    /*      Write binary file.                                              */
1421
    /* -------------------------------------------------------------------- */
1422
0
    VSILFILE *fpBin = VSIFOpenL(osBinFile, "w");
1423
1424
0
    if (fpBin == nullptr)
1425
0
    {
1426
0
        CPLError(CE_Failure, CPLE_FileIO, "Failed to create %s:\n%s",
1427
0
                 osBinFile.c_str(), VSIStrerror(errno));
1428
0
        return nullptr;
1429
0
    }
1430
1431
0
    const GUIntBig nSize = static_cast<GUIntBig>(nXSize) * nYSize * nBandsIn *
1432
0
                           GDALGetDataTypeSizeBytes(eType);
1433
0
    GByte byZero = 0;
1434
0
    if (VSIFSeekL(fpBin, nSize - 1, SEEK_SET) != 0 ||
1435
0
        VSIFWriteL(&byZero, 1, 1, fpBin) != 1)
1436
0
    {
1437
0
        CPLError(CE_Failure, CPLE_FileIO, "Failed to write %s:\n%s",
1438
0
                 osBinFile.c_str(), VSIStrerror(errno));
1439
0
        VSIFCloseL(fpBin);
1440
0
        return nullptr;
1441
0
    }
1442
0
    VSIFCloseL(fpBin);
1443
1444
    /* -------------------------------------------------------------------- */
1445
    /*      Try writing header file.                                        */
1446
    /* -------------------------------------------------------------------- */
1447
0
    VSILFILE *fpERS = VSIFOpenL(osErsFile, "w");
1448
1449
0
    if (fpERS == nullptr)
1450
0
    {
1451
0
        CPLError(CE_Failure, CPLE_FileIO, "Failed to create %s:\n%s",
1452
0
                 osErsFile.c_str(), VSIStrerror(errno));
1453
0
        return nullptr;
1454
0
    }
1455
1456
0
    VSIFPrintfL(fpERS, "DatasetHeader Begin\n");
1457
0
    VSIFPrintfL(fpERS, "\tVersion\t\t = \"6.0\"\n");
1458
0
    VSIFPrintfL(fpERS, "\tName\t\t= \"%s\"\n", CPLGetFilename(osErsFile));
1459
1460
    // Last updated requires timezone info which we don't necessarily get
1461
    // get from VSICTime() so perhaps it is better to omit this.
1462
    //    VSIFPrintfL( fpERS, "\tLastUpdated\t= %s",
1463
    //                 VSICTime( VSITime( NULL ) ) );
1464
1465
0
    VSIFPrintfL(fpERS, "\tDataSetType\t= ERStorage\n");
1466
0
    VSIFPrintfL(fpERS, "\tDataType\t= Raster\n");
1467
0
    VSIFPrintfL(fpERS, "\tByteOrder\t= LSBFirst\n");
1468
0
    VSIFPrintfL(fpERS, "\tRasterInfo Begin\n");
1469
0
    VSIFPrintfL(fpERS, "\t\tCellType\t= %s\n", pszCellType);
1470
0
    VSIFPrintfL(fpERS, "\t\tNrOfLines\t= %d\n", nYSize);
1471
0
    VSIFPrintfL(fpERS, "\t\tNrOfCellsPerLine\t= %d\n", nXSize);
1472
0
    VSIFPrintfL(fpERS, "\t\tNrOfBands\t= %d\n", nBandsIn);
1473
0
    VSIFPrintfL(fpERS, "\tRasterInfo End\n");
1474
0
    if (VSIFPrintfL(fpERS, "DatasetHeader End\n") < 17)
1475
0
    {
1476
0
        CPLError(CE_Failure, CPLE_FileIO, "Failed to write %s:\n%s",
1477
0
                 osErsFile.c_str(), VSIStrerror(errno));
1478
0
        return nullptr;
1479
0
    }
1480
1481
0
    VSIFCloseL(fpERS);
1482
1483
    /* -------------------------------------------------------------------- */
1484
    /*      Reopen.                                                         */
1485
    /* -------------------------------------------------------------------- */
1486
0
    GDALOpenInfo oOpenInfo(osErsFile, GA_Update);
1487
0
    ERSDataset *poDS = cpl::down_cast<ERSDataset *>(Open(&oOpenInfo));
1488
0
    if (poDS == nullptr)
1489
0
        return nullptr;
1490
1491
    /* -------------------------------------------------------------------- */
1492
    /*      Fetch DATUM, PROJ and UNITS creation option                     */
1493
    /* -------------------------------------------------------------------- */
1494
0
    const char *pszDatum = CSLFetchNameValue(papszOptions, "DATUM");
1495
0
    if (pszDatum)
1496
0
    {
1497
0
        poDS->osDatumForced = pszDatum;
1498
0
        poDS->osDatum = pszDatum;
1499
0
    }
1500
0
    const char *pszProj = CSLFetchNameValue(papszOptions, "PROJ");
1501
0
    if (pszProj)
1502
0
    {
1503
0
        poDS->osProjForced = pszProj;
1504
0
        poDS->osProj = pszProj;
1505
0
    }
1506
0
    const char *pszUnits = CSLFetchNameValue(papszOptions, "UNITS");
1507
0
    if (pszUnits)
1508
0
    {
1509
0
        poDS->osUnitsForced = pszUnits;
1510
0
        poDS->osUnits = pszUnits;
1511
0
    }
1512
1513
0
    if (pszDatum || pszProj || pszUnits)
1514
0
    {
1515
0
        poDS->WriteProjectionInfo(pszProj ? pszProj : "RAW",
1516
0
                                  pszDatum ? pszDatum : "RAW",
1517
0
                                  pszUnits ? pszUnits : "METERS");
1518
0
    }
1519
1520
0
    return poDS;
1521
0
}
1522
1523
/************************************************************************/
1524
/*                          GDALRegister_ERS()                          */
1525
/************************************************************************/
1526
1527
void GDALRegister_ERS()
1528
1529
26
{
1530
26
    if (GDALGetDriverByName("ERS") != nullptr)
1531
0
        return;
1532
1533
26
    GDALDriver *poDriver = new GDALDriver();
1534
1535
26
    poDriver->SetDescription("ERS");
1536
26
    poDriver->SetMetadataItem(GDAL_DCAP_RASTER, "YES");
1537
26
    poDriver->SetMetadataItem(GDAL_DMD_LONGNAME, "ERMapper .ers Labelled");
1538
26
    poDriver->SetMetadataItem(GDAL_DMD_HELPTOPIC, "drivers/raster/ers.html");
1539
26
    poDriver->SetMetadataItem(GDAL_DMD_EXTENSION, "ers");
1540
26
    poDriver->SetMetadataItem(GDAL_DMD_CREATIONDATATYPES,
1541
26
                              "Byte Int8 Int16 UInt16 Int32 UInt32 "
1542
26
                              "Float32 Float64");
1543
1544
26
    poDriver->SetMetadataItem(
1545
26
        GDAL_DMD_CREATIONOPTIONLIST,
1546
26
        "<CreationOptionList>"
1547
26
        "   <Option name='PIXELTYPE' type='string' description='(deprecated, "
1548
26
        "use Int8 datatype) By setting this to SIGNEDBYTE, a new Byte file can "
1549
26
        "be forced to be written as signed byte'/>"
1550
26
        "   <Option name='PROJ' type='string' description='ERS Projection "
1551
26
        "Name'/>"
1552
26
        "   <Option name='DATUM' type='string' description='ERS Datum Name' />"
1553
26
        "   <Option name='UNITS' type='string-select' description='ERS "
1554
26
        "Projection Units'>"
1555
26
        "       <Value>METERS</Value>"
1556
26
        "       <Value>FEET</Value>"
1557
26
        "   </Option>"
1558
26
        "</CreationOptionList>");
1559
1560
26
    poDriver->SetMetadataItem(GDAL_DCAP_VIRTUALIO, "YES");
1561
1562
26
    poDriver->pfnOpen = ERSDataset::Open;
1563
26
    poDriver->pfnIdentify = ERSDataset::Identify;
1564
26
    poDriver->pfnCreate = ERSDataset::Create;
1565
1566
26
    GetGDALDriverManager()->RegisterDriver(poDriver);
1567
26
}