Coverage Report

Created: 2025-07-23 09:13

/src/gdal/frmts/bsb/bsbdataset.cpp
Line
Count
Source (jump to first uncovered line)
1
/******************************************************************************
2
 *
3
 * Project:  BSB Reader
4
 * Purpose:  BSBDataset implementation for BSB format.
5
 * Author:   Frank Warmerdam, warmerdam@pobox.com
6
 *
7
 ******************************************************************************
8
 * Copyright (c) 2001, Frank Warmerdam <warmerdam@pobox.com>
9
 * Copyright (c) 2008-2012, Even Rouault <even dot rouault at spatialys.com>
10
 *
11
 * SPDX-License-Identifier: MIT
12
 ****************************************************************************/
13
14
#include "bsb_read.h"
15
#include "cpl_string.h"
16
#include "gdal_frmts.h"
17
#include "gdal_pam.h"
18
#include "ogr_spatialref.h"
19
20
#include <cstdlib>
21
#include <algorithm>
22
23
// Disabled as people may worry about the BSB patent
24
// #define BSB_CREATE
25
26
/************************************************************************/
27
/* ==================================================================== */
28
/*                              BSBDataset                              */
29
/* ==================================================================== */
30
/************************************************************************/
31
32
class BSBRasterBand;
33
34
class BSBDataset final : public GDALPamDataset
35
{
36
    int nGCPCount;
37
    GDAL_GCP *pasGCPList;
38
    OGRSpatialReference m_oGCPSRS{};
39
40
    GDALGeoTransform m_gt{};
41
    int bGeoTransformSet;
42
43
    void ScanForGCPs(bool isNos, const char *pszFilename);
44
    void ScanForGCPsNos(const char *pszFilename);
45
    void ScanForGCPsBSB();
46
47
    void ScanForCutline();
48
49
    static int IdentifyInternal(GDALOpenInfo *, bool &isNosOut);
50
51
  public:
52
    BSBDataset();
53
    ~BSBDataset() override;
54
55
    BSBInfo *psInfo;
56
57
    static GDALDataset *Open(GDALOpenInfo *);
58
    static int Identify(GDALOpenInfo *);
59
60
    int GetGCPCount() override;
61
    const OGRSpatialReference *GetSpatialRef() const override;
62
    const GDAL_GCP *GetGCPs() override;
63
64
    CPLErr GetGeoTransform(GDALGeoTransform &gt) const override;
65
    const OGRSpatialReference *GetGCPSpatialRef() const override;
66
};
67
68
/************************************************************************/
69
/* ==================================================================== */
70
/*                            BSBRasterBand                             */
71
/* ==================================================================== */
72
/************************************************************************/
73
74
class BSBRasterBand final : public GDALPamRasterBand
75
{
76
    GDALColorTable oCT;
77
78
  public:
79
    explicit BSBRasterBand(BSBDataset *);
80
81
    CPLErr IReadBlock(int, int, void *) override;
82
    GDALColorTable *GetColorTable() override;
83
    GDALColorInterp GetColorInterpretation() override;
84
};
85
86
/************************************************************************/
87
/*                           BSBRasterBand()                            */
88
/************************************************************************/
89
90
BSBRasterBand::BSBRasterBand(BSBDataset *poDSIn)
91
92
87
{
93
87
    poDS = poDSIn;
94
87
    nBand = 1;
95
96
87
    eDataType = GDT_Byte;
97
98
87
    nBlockXSize = poDS->GetRasterXSize();
99
87
    nBlockYSize = 1;
100
101
    // Note that the first color table entry is dropped, everything is
102
    // shifted down.
103
6.56k
    for (int i = 0; i < poDSIn->psInfo->nPCTSize - 1; i++)
104
6.47k
    {
105
6.47k
        GDALColorEntry oColor = {poDSIn->psInfo->pabyPCT[i * 3 + 0 + 3],
106
6.47k
                                 poDSIn->psInfo->pabyPCT[i * 3 + 1 + 3],
107
6.47k
                                 poDSIn->psInfo->pabyPCT[i * 3 + 2 + 3], 255};
108
109
6.47k
        oCT.SetColorEntry(i, &oColor);
110
6.47k
    }
111
87
}
112
113
/************************************************************************/
114
/*                             IReadBlock()                             */
115
/************************************************************************/
116
117
CPLErr BSBRasterBand::IReadBlock(CPL_UNUSED int nBlockXOff, int nBlockYOff,
118
                                 void *pImage)
119
2.84k
{
120
2.84k
    BSBDataset *poGDS = cpl::down_cast<BSBDataset *>(poDS);
121
2.84k
    GByte *pabyScanline = (GByte *)pImage;
122
123
2.84k
    if (BSBReadScanline(poGDS->psInfo, nBlockYOff, pabyScanline))
124
2.77k
    {
125
1.22M
        for (int i = 0; i < nBlockXSize; i++)
126
1.21M
        {
127
            /* The indices start at 1, except in case of some charts */
128
            /* where there are missing values, which are filled to 0 */
129
            /* by BSBReadScanline */
130
1.21M
            if (pabyScanline[i] > 0)
131
1.21M
                pabyScanline[i] -= 1;
132
1.21M
        }
133
134
2.77k
        return CE_None;
135
2.77k
    }
136
137
71
    return CE_Failure;
138
2.84k
}
139
140
/************************************************************************/
141
/*                           GetColorTable()                            */
142
/************************************************************************/
143
144
GDALColorTable *BSBRasterBand::GetColorTable()
145
146
0
{
147
0
    return &oCT;
148
0
}
149
150
/************************************************************************/
151
/*                       GetColorInterpretation()                       */
152
/************************************************************************/
153
154
GDALColorInterp BSBRasterBand::GetColorInterpretation()
155
156
0
{
157
0
    return GCI_PaletteIndex;
158
0
}
159
160
/************************************************************************/
161
/* ==================================================================== */
162
/*                              BSBDataset                              */
163
/* ==================================================================== */
164
/************************************************************************/
165
166
/************************************************************************/
167
/*                           BSBDataset()                               */
168
/************************************************************************/
169
170
BSBDataset::BSBDataset()
171
807
    : nGCPCount(0), pasGCPList(nullptr), bGeoTransformSet(FALSE),
172
807
      psInfo(nullptr)
173
807
{
174
807
    m_oGCPSRS.SetAxisMappingStrategy(OAMS_TRADITIONAL_GIS_ORDER);
175
807
    m_oGCPSRS.importFromWkt(SRS_WKT_WGS84_LAT_LONG);
176
807
}
177
178
/************************************************************************/
179
/*                            ~BSBDataset()                             */
180
/************************************************************************/
181
182
BSBDataset::~BSBDataset()
183
184
807
{
185
807
    FlushCache(true);
186
187
807
    GDALDeinitGCPs(nGCPCount, pasGCPList);
188
807
    CPLFree(pasGCPList);
189
190
807
    if (psInfo != nullptr)
191
87
        BSBClose(psInfo);
192
807
}
193
194
/************************************************************************/
195
/*                          GetGeoTransform()                           */
196
/************************************************************************/
197
198
CPLErr BSBDataset::GetGeoTransform(GDALGeoTransform &gt) const
199
200
87
{
201
87
    gt = m_gt;
202
203
87
    if (bGeoTransformSet)
204
15
        return CE_None;
205
206
72
    return CE_Failure;
207
87
}
208
209
/************************************************************************/
210
/*                          GetSpatialRef()                             */
211
/************************************************************************/
212
213
const OGRSpatialReference *BSBDataset::GetSpatialRef() const
214
215
87
{
216
87
    if (bGeoTransformSet)
217
15
        return &m_oGCPSRS;
218
219
72
    return nullptr;
220
87
}
221
222
/************************************************************************/
223
/*                     GDALHeuristicDatelineWrap()                      */
224
/************************************************************************/
225
226
static void GDALHeuristicDatelineWrap(int nPointCount, double *padfX)
227
228
47
{
229
47
    if (nPointCount < 2)
230
0
        return;
231
232
    /* -------------------------------------------------------------------- */
233
    /*      Work out what the longitude range will be centering on the      */
234
    /*      prime meridian (-180 to 180) and centering on the dateline      */
235
    /*      (0 to 360).                                                     */
236
    /* -------------------------------------------------------------------- */
237
    /* Following inits are useless but keep GCC happy */
238
47
    double dfX_PM_Min = 0.0;
239
47
    double dfX_PM_Max = 0.0;
240
47
    double dfX_Dateline_Min = 0.0;
241
47
    double dfX_Dateline_Max = 0.0;
242
243
3.24k
    for (int i = 0; i < nPointCount; i++)
244
3.19k
    {
245
3.19k
        double dfX_PM = padfX[i];
246
3.19k
        if (dfX_PM > 180)
247
9
            dfX_PM -= 360.0;
248
249
3.19k
        double dfX_Dateline = padfX[i];
250
3.19k
        if (dfX_Dateline < 0)
251
18
            dfX_Dateline += 360.0;
252
253
3.19k
        if (i == 0)
254
47
        {
255
47
            dfX_PM_Min = dfX_PM;
256
47
            dfX_PM_Max = dfX_PM;
257
47
            dfX_Dateline_Min = dfX_Dateline;
258
47
            dfX_Dateline_Max = dfX_Dateline;
259
47
        }
260
3.14k
        else
261
3.14k
        {
262
3.14k
            dfX_PM_Min = std::min(dfX_PM_Min, dfX_PM);
263
3.14k
            dfX_PM_Max = std::max(dfX_PM_Max, dfX_PM);
264
3.14k
            dfX_Dateline_Min = std::min(dfX_Dateline_Min, dfX_Dateline);
265
3.14k
            dfX_Dateline_Max = std::max(dfX_Dateline_Max, dfX_Dateline);
266
3.14k
        }
267
3.19k
    }
268
269
    /* -------------------------------------------------------------------- */
270
    /*      Do nothing if the range is always fairly small - no apparent    */
271
    /*      wrapping issues.                                                */
272
    /* -------------------------------------------------------------------- */
273
47
    if ((dfX_PM_Max - dfX_PM_Min) < 270.0 &&
274
47
        (dfX_Dateline_Max - dfX_Dateline_Min) < 270.0)
275
40
        return;
276
277
    /* -------------------------------------------------------------------- */
278
    /*      Do nothing if both approach have a wide range - best not to    */
279
    /*      fiddle if we aren't sure we are improving things.               */
280
    /* -------------------------------------------------------------------- */
281
7
    if ((dfX_PM_Max - dfX_PM_Min) > 270.0 &&
282
7
        (dfX_Dateline_Max - dfX_Dateline_Min) > 270.0)
283
0
        return;
284
285
    /* -------------------------------------------------------------------- */
286
    /*      Pick which way to transform things.                             */
287
    /* -------------------------------------------------------------------- */
288
7
    bool bUsePMWrap;
289
290
7
    if ((dfX_PM_Max - dfX_PM_Min) > 270.0 &&
291
7
        (dfX_Dateline_Max - dfX_Dateline_Min) < 270.0)
292
1
        bUsePMWrap = false;
293
6
    else
294
6
        bUsePMWrap = true;
295
296
    /* -------------------------------------------------------------------- */
297
    /*      Apply rewrapping.                                               */
298
    /* -------------------------------------------------------------------- */
299
810
    for (int i = 0; i < nPointCount; i++)
300
803
    {
301
803
        if (bUsePMWrap)
302
649
        {
303
649
            if (padfX[i] > 180)
304
7
                padfX[i] -= 360.0;
305
649
        }
306
154
        else
307
154
        {
308
154
            if (padfX[i] < 0)
309
0
                padfX[i] += 360.0;
310
154
        }
311
803
    }
312
7
}
313
314
/************************************************************************/
315
/*                   GDALHeuristicDatelineWrapGCPs()                    */
316
/************************************************************************/
317
318
static void GDALHeuristicDatelineWrapGCPs(int nPointCount, GDAL_GCP *pasGCPList)
319
47
{
320
47
    std::vector<double> oadfX;
321
322
47
    oadfX.resize(nPointCount);
323
3.24k
    for (int i = 0; i < nPointCount; i++)
324
3.19k
        oadfX[i] = pasGCPList[i].dfGCPX;
325
326
47
    GDALHeuristicDatelineWrap(nPointCount, &(oadfX[0]));
327
328
3.24k
    for (int i = 0; i < nPointCount; i++)
329
3.19k
        pasGCPList[i].dfGCPX = oadfX[i];
330
47
}
331
332
/************************************************************************/
333
/*                            ScanForGCPs()                             */
334
/************************************************************************/
335
336
void BSBDataset::ScanForGCPs(bool isNos, const char *pszFilename)
337
338
87
{
339
    /* -------------------------------------------------------------------- */
340
    /*      Collect GCPs as appropriate to source.                          */
341
    /* -------------------------------------------------------------------- */
342
87
    nGCPCount = 0;
343
344
87
    if (isNos)
345
0
    {
346
0
        ScanForGCPsNos(pszFilename);
347
0
    }
348
87
    else
349
87
    {
350
87
        ScanForGCPsBSB();
351
87
    }
352
353
    /* -------------------------------------------------------------------- */
354
    /*      Apply heuristics to re-wrap GCPs to maintain continuity        */
355
    /*      over the international dateline.                                */
356
    /* -------------------------------------------------------------------- */
357
87
    if (nGCPCount > 1)
358
47
        GDALHeuristicDatelineWrapGCPs(nGCPCount, pasGCPList);
359
360
    /* -------------------------------------------------------------------- */
361
    /*      Collect coordinate system related parameters from header.       */
362
    /* -------------------------------------------------------------------- */
363
87
    const char *pszKNP = nullptr;
364
87
    const char *pszKNQ = nullptr;
365
366
13.8k
    for (int i = 0; psInfo->papszHeader[i] != nullptr; i++)
367
13.8k
    {
368
13.8k
        if (STARTS_WITH_CI(psInfo->papszHeader[i], "KNP/"))
369
111
        {
370
111
            pszKNP = psInfo->papszHeader[i];
371
111
            SetMetadataItem("BSB_KNP", pszKNP + 4);
372
111
        }
373
13.8k
        if (STARTS_WITH_CI(psInfo->papszHeader[i], "KNQ/"))
374
75
        {
375
75
            pszKNQ = psInfo->papszHeader[i];
376
75
            SetMetadataItem("BSB_KNQ", pszKNQ + 4);
377
75
        }
378
13.8k
    }
379
380
    /* -------------------------------------------------------------------- */
381
    /*      Can we derive a reasonable coordinate system definition for     */
382
    /*      this file?  For now we keep it simple, just handling            */
383
    /*      mercator. In the future we should consider others.              */
384
    /* -------------------------------------------------------------------- */
385
87
    CPLString osUnderlyingSRS;
386
87
    if (pszKNP != nullptr)
387
85
    {
388
85
        const char *pszPR = strstr(pszKNP, "PR=");
389
85
        const char *pszGD = strstr(pszKNP, "GD=");
390
85
        const char *pszGEOGCS = SRS_WKT_WGS84_LAT_LONG;
391
85
        CPLString osPP;
392
393
        // Capture the PP string.
394
85
        const char *pszValue = strstr(pszKNP, "PP=");
395
85
        const char *pszEnd = pszValue ? strstr(pszValue, ",") : nullptr;
396
85
        if (pszValue && pszEnd)
397
79
            osPP.assign(pszValue + 3, pszEnd - pszValue - 3);
398
399
        // Look at the datum
400
85
        if (pszGD == nullptr)
401
2
        {
402
            /* no match. We'll default to EPSG:4326 */
403
2
        }
404
83
        else if (STARTS_WITH_CI(pszGD, "GD=European 1950"))
405
0
        {
406
0
            pszGEOGCS =
407
0
                "GEOGCS[\"ED50\",DATUM[\"European_Datum_1950\",SPHEROID["
408
0
                "\"International "
409
0
                "1924\",6378388,297,AUTHORITY[\"EPSG\",\"7022\"]],TOWGS84[-87,-"
410
0
                "98,-121,0,0,0,0],AUTHORITY[\"EPSG\",\"6230\"]],PRIMEM["
411
0
                "\"Greenwich\",0,AUTHORITY[\"EPSG\",\"8901\"]],UNIT[\"degree\","
412
0
                "0.0174532925199433,AUTHORITY[\"EPSG\",\"9122\"]],AUTHORITY["
413
0
                "\"EPSG\",\"4230\"]]";
414
0
        }
415
416
        // Look at the projection
417
85
        if (pszPR == nullptr)
418
3
        {
419
            /* no match */
420
3
        }
421
82
        else if (STARTS_WITH_CI(pszPR, "PR=MERCATOR") && nGCPCount > 0)
422
11
        {
423
            // We somewhat arbitrarily select our first GCPX as our
424
            // central meridian.  This is mostly helpful to ensure
425
            // that regions crossing the dateline will be contiguous
426
            // in mercator.
427
11
            osUnderlyingSRS.Printf(
428
11
                "PROJCS[\"Global "
429
11
                "Mercator\",%s,PROJECTION[\"Mercator_2SP\"],PARAMETER["
430
11
                "\"standard_parallel_1\",0],PARAMETER[\"latitude_of_origin\",0]"
431
11
                ",PARAMETER[\"central_meridian\",%d],PARAMETER[\"false_"
432
11
                "easting\",0],PARAMETER[\"false_northing\",0],UNIT[\"Meter\",1]"
433
11
                "]",
434
11
                pszGEOGCS, (int)pasGCPList[0].dfGCPX);
435
11
        }
436
437
71
        else if (STARTS_WITH_CI(pszPR, "PR=TRANSVERSE MERCATOR") &&
438
71
                 !osPP.empty())
439
12
        {
440
441
12
            osUnderlyingSRS.Printf(
442
12
                "PROJCS[\"unnamed\",%s,PROJECTION[\"Transverse_Mercator\"],"
443
12
                "PARAMETER[\"latitude_of_origin\",0],PARAMETER[\"central_"
444
12
                "meridian\",%s],PARAMETER[\"scale_factor\",1],PARAMETER["
445
12
                "\"false_easting\",0],PARAMETER[\"false_northing\",0],UNIT["
446
12
                "\"Meter\",1]]",
447
12
                pszGEOGCS, osPP.c_str());
448
12
        }
449
450
59
        else if (STARTS_WITH_CI(pszPR, "PR=UNIVERSAL TRANSVERSE MERCATOR") &&
451
59
                 !osPP.empty())
452
0
        {
453
            // This is not *really* UTM unless the central meridian
454
            // matches a zone which it does not in some (most?) maps.
455
0
            osUnderlyingSRS.Printf(
456
0
                "PROJCS[\"unnamed\",%s,PROJECTION[\"Transverse_Mercator\"],"
457
0
                "PARAMETER[\"latitude_of_origin\",0],PARAMETER[\"central_"
458
0
                "meridian\",%s],PARAMETER[\"scale_factor\",0.9996],PARAMETER["
459
0
                "\"false_easting\",500000],PARAMETER[\"false_northing\",0],"
460
0
                "UNIT[\"Meter\",1]]",
461
0
                pszGEOGCS, osPP.c_str());
462
0
        }
463
464
59
        else if (STARTS_WITH_CI(pszPR, "PR=POLYCONIC") && !osPP.empty())
465
0
        {
466
0
            osUnderlyingSRS.Printf(
467
0
                "PROJCS[\"unnamed\",%s,PROJECTION[\"Polyconic\"],PARAMETER["
468
0
                "\"latitude_of_origin\",0],PARAMETER[\"central_meridian\",%s],"
469
0
                "PARAMETER[\"false_easting\",0],PARAMETER[\"false_northing\",0]"
470
0
                ",UNIT[\"Meter\",1]]",
471
0
                pszGEOGCS, osPP.c_str());
472
0
        }
473
474
59
        else if (STARTS_WITH_CI(pszPR, "PR=LAMBERT CONFORMAL CONIC") &&
475
59
                 !osPP.empty() && pszKNQ != nullptr)
476
17
        {
477
17
            CPLString osP2, osP3;
478
479
            // Capture the KNQ/P2 string.
480
17
            pszValue = strstr(pszKNQ, "P2=");
481
17
            if (pszValue)
482
16
                pszEnd = strstr(pszValue, ",");
483
17
            if (pszValue && pszEnd)
484
14
                osP2.assign(pszValue + 3, pszEnd - pszValue - 3);
485
486
            // Capture the KNQ/P3 string.
487
17
            pszValue = strstr(pszKNQ, "P3=");
488
17
            if (pszValue)
489
12
                pszEnd = strstr(pszValue, ",");
490
17
            if (pszValue)
491
12
            {
492
12
                if (pszEnd)
493
12
                    osP3.assign(pszValue + 3, pszEnd - pszValue - 3);
494
0
                else
495
0
                    osP3.assign(pszValue + 3);
496
12
            }
497
498
17
            if (!osP2.empty() && !osP3.empty())
499
12
                osUnderlyingSRS.Printf(
500
12
                    "PROJCS[\"unnamed\",%s,PROJECTION[\"Lambert_Conformal_"
501
12
                    "Conic_2SP\"],PARAMETER[\"standard_parallel_1\",%s],"
502
12
                    "PARAMETER[\"standard_parallel_2\",%s],PARAMETER["
503
12
                    "\"latitude_of_origin\",0.0],PARAMETER[\"central_"
504
12
                    "meridian\",%s],PARAMETER[\"false_easting\",0.0],PARAMETER["
505
12
                    "\"false_northing\",0.0],UNIT[\"Meter\",1]]",
506
12
                    pszGEOGCS, osP2.c_str(), osP3.c_str(), osPP.c_str());
507
17
        }
508
85
    }
509
510
    /* -------------------------------------------------------------------- */
511
    /*      If we got an alternate underlying coordinate system, try        */
512
    /*      converting the GCPs to that coordinate system.                  */
513
    /* -------------------------------------------------------------------- */
514
87
    if (osUnderlyingSRS.length() > 0)
515
35
    {
516
35
        OGRSpatialReference oGeog_SRS, oProjected_SRS;
517
518
35
        oProjected_SRS.SetAxisMappingStrategy(OAMS_TRADITIONAL_GIS_ORDER);
519
35
        oProjected_SRS.SetFromUserInput(osUnderlyingSRS);
520
35
        oGeog_SRS.CopyGeogCSFrom(&oProjected_SRS);
521
35
        oGeog_SRS.SetAxisMappingStrategy(OAMS_TRADITIONAL_GIS_ORDER);
522
523
35
        OGRCoordinateTransformation *poCT =
524
35
            OGRCreateCoordinateTransformation(&oGeog_SRS, &oProjected_SRS);
525
35
        if (poCT != nullptr)
526
30
        {
527
3.12k
            for (int i = 0; i < nGCPCount; i++)
528
3.09k
            {
529
3.09k
                poCT->Transform(1, &(pasGCPList[i].dfGCPX),
530
3.09k
                                &(pasGCPList[i].dfGCPY),
531
3.09k
                                &(pasGCPList[i].dfGCPZ));
532
3.09k
            }
533
534
30
            m_oGCPSRS.importFromWkt(osUnderlyingSRS.c_str());
535
536
30
            delete poCT;
537
30
        }
538
5
        else
539
5
            CPLErrorReset();
540
35
    }
541
542
    /* -------------------------------------------------------------------- */
543
    /*      Attempt to prepare a geotransform from the GCPs.                */
544
    /* -------------------------------------------------------------------- */
545
87
    if (GDALGCPsToGeoTransform(nGCPCount, pasGCPList, m_gt.data(), FALSE))
546
15
    {
547
15
        bGeoTransformSet = TRUE;
548
15
    }
549
87
}
550
551
/************************************************************************/
552
/*                           ScanForGCPsNos()                           */
553
/*                                                                      */
554
/*      Nos files have an accompanying .geo file, that contains some    */
555
/*      of the information normally contained in the header section     */
556
/*      with BSB files. we try and open a file with the same name,      */
557
/*      but a .geo extension, and look for lines like...                */
558
/*      PointX=long lat line pixel    (using the same naming system     */
559
/*      as BSB) Point1=-22.0000 64.250000 197 744                       */
560
/************************************************************************/
561
562
void BSBDataset::ScanForGCPsNos(const char *pszFilename)
563
0
{
564
0
    const std::string extension = CPLGetExtensionSafe(pszFilename);
565
566
    // pseudointelligently try and guess whether we want a .geo or a .GEO
567
0
    std::string geofile;
568
0
    if (extension.size() >= 2 && extension[1] == 'O')
569
0
    {
570
0
        geofile = CPLResetExtensionSafe(pszFilename, "GEO");
571
0
    }
572
0
    else
573
0
    {
574
0
        geofile = CPLResetExtensionSafe(pszFilename, "geo");
575
0
    }
576
577
0
    FILE *gfp = VSIFOpen(geofile.c_str(), "r");  // Text files
578
0
    if (gfp == nullptr)
579
0
    {
580
0
        CPLError(CE_Failure, CPLE_OpenFailed,
581
0
                 "Couldn't find a matching .GEO file: %s", geofile.c_str());
582
0
        return;
583
0
    }
584
585
0
    char *thisLine = (char *)CPLMalloc(80);  // FIXME
586
587
    // Count the GCPs (reference points) and seek the file pointer 'gfp' to the
588
    // starting point
589
0
    int fileGCPCount = 0;
590
0
    while (fgets(thisLine, 80, gfp))
591
0
    {
592
0
        if (STARTS_WITH_CI(thisLine, "Point"))
593
0
            fileGCPCount++;
594
0
    }
595
0
    VSIRewind(gfp);
596
597
    // Memory has not been allocated to fileGCPCount yet
598
0
    pasGCPList = (GDAL_GCP *)CPLCalloc(sizeof(GDAL_GCP), fileGCPCount + 1);
599
600
0
    while (fgets(thisLine, 80, gfp))
601
0
    {
602
0
        if (STARTS_WITH_CI(thisLine, "Point"))
603
0
        {
604
            // got a point line, turn it into a gcp
605
0
            char **Tokens =
606
0
                CSLTokenizeStringComplex(thisLine, "= ", FALSE, FALSE);
607
0
            if (CSLCount(Tokens) >= 5)
608
0
            {
609
0
                GDALInitGCPs(1, pasGCPList + nGCPCount);
610
0
                pasGCPList[nGCPCount].dfGCPX = CPLAtof(Tokens[1]);
611
0
                pasGCPList[nGCPCount].dfGCPY = CPLAtof(Tokens[2]);
612
0
                pasGCPList[nGCPCount].dfGCPPixel = CPLAtof(Tokens[4]);
613
0
                pasGCPList[nGCPCount].dfGCPLine = CPLAtof(Tokens[3]);
614
615
0
                CPLFree(pasGCPList[nGCPCount].pszId);
616
0
                char szName[50];
617
0
                snprintf(szName, sizeof(szName), "GCP_%d", nGCPCount + 1);
618
0
                pasGCPList[nGCPCount].pszId = CPLStrdup(szName);
619
620
0
                nGCPCount++;
621
0
            }
622
0
            CSLDestroy(Tokens);
623
0
        }
624
0
    }
625
626
0
    CPLFree(thisLine);
627
0
    VSIFClose(gfp);
628
0
}
629
630
/************************************************************************/
631
/*                            ScanForGCPsBSB()                          */
632
/************************************************************************/
633
634
void BSBDataset::ScanForGCPsBSB()
635
87
{
636
    /* -------------------------------------------------------------------- */
637
    /*      Collect standalone GCPs.  They look like:                       */
638
    /*                                                                      */
639
    /*      REF/1,115,2727,32.346666666667,-60.881666666667                 */
640
    /*      REF/n,pixel,line,lat,long                                       */
641
    /* -------------------------------------------------------------------- */
642
87
    int fileGCPCount = 0;
643
644
    // Count the GCPs (reference points) in psInfo->papszHeader
645
13.8k
    for (int i = 0; psInfo->papszHeader[i] != nullptr; i++)
646
13.8k
        if (STARTS_WITH_CI(psInfo->papszHeader[i], "REF/"))
647
3.21k
            fileGCPCount++;
648
649
    // Memory has not been allocated to fileGCPCount yet
650
87
    pasGCPList = (GDAL_GCP *)CPLCalloc(sizeof(GDAL_GCP), fileGCPCount + 1);
651
652
13.8k
    for (int i = 0; psInfo->papszHeader[i] != nullptr; i++)
653
13.8k
    {
654
655
13.8k
        if (!STARTS_WITH_CI(psInfo->papszHeader[i], "REF/"))
656
10.5k
            continue;
657
658
3.21k
        char **papszTokens = CSLTokenizeStringComplex(
659
3.21k
            psInfo->papszHeader[i] + 4, ",", FALSE, FALSE);
660
661
3.21k
        if (CSLCount(papszTokens) > 4)
662
3.19k
        {
663
3.19k
            GDALInitGCPs(1, pasGCPList + nGCPCount);
664
665
3.19k
            pasGCPList[nGCPCount].dfGCPX = CPLAtof(papszTokens[4]);
666
3.19k
            pasGCPList[nGCPCount].dfGCPY = CPLAtof(papszTokens[3]);
667
3.19k
            pasGCPList[nGCPCount].dfGCPPixel = CPLAtof(papszTokens[1]);
668
3.19k
            pasGCPList[nGCPCount].dfGCPLine = CPLAtof(papszTokens[2]);
669
670
3.19k
            CPLFree(pasGCPList[nGCPCount].pszId);
671
3.19k
            if (CSLCount(papszTokens) > 5)
672
38
            {
673
38
                pasGCPList[nGCPCount].pszId = CPLStrdup(papszTokens[5]);
674
38
            }
675
3.15k
            else
676
3.15k
            {
677
3.15k
                char szName[50];
678
3.15k
                snprintf(szName, sizeof(szName), "GCP_%d", nGCPCount + 1);
679
3.15k
                pasGCPList[nGCPCount].pszId = CPLStrdup(szName);
680
3.15k
            }
681
682
3.19k
            nGCPCount++;
683
3.19k
        }
684
3.21k
        CSLDestroy(papszTokens);
685
3.21k
    }
686
87
}
687
688
/************************************************************************/
689
/*                            ScanForCutline()                          */
690
/************************************************************************/
691
692
void BSBDataset::ScanForCutline()
693
87
{
694
    /* PLY: Border Polygon Record - coordinates of the panel within the
695
     * raster image, given in chart datum lat/long. Any shape polygon.
696
     * They look like:
697
     *      PLY/1,32.346666666667,-60.881666666667
698
     *      PLY/n,lat,long
699
     *
700
     * If found then we return it via a BSB_CUTLINE metadata item as a WKT
701
     * POLYGON.
702
     */
703
704
87
    std::string wkt;
705
13.8k
    for (int i = 0; psInfo->papszHeader[i] != nullptr; i++)
706
13.8k
    {
707
13.8k
        if (!STARTS_WITH_CI(psInfo->papszHeader[i], "PLY/"))
708
13.2k
            continue;
709
710
507
        const CPLStringList aosTokens(
711
507
            CSLTokenizeString2(psInfo->papszHeader[i] + 4, ",", 0));
712
713
507
        if (aosTokens.size() >= 3)
714
478
        {
715
478
            if (wkt.empty())
716
30
                wkt = "POLYGON ((";
717
448
            else
718
448
                wkt += ',';
719
478
            wkt += aosTokens[2];
720
478
            wkt += ' ';
721
478
            wkt += aosTokens[1];
722
478
        }
723
507
    }
724
725
87
    if (!wkt.empty())
726
30
    {
727
30
        wkt += "))";
728
30
        SetMetadataItem("BSB_CUTLINE", wkt.c_str());
729
30
    }
730
87
}
731
732
/************************************************************************/
733
/*                          IdentifyInternal()                          */
734
/************************************************************************/
735
736
int BSBDataset::IdentifyInternal(GDALOpenInfo *poOpenInfo, bool &isNosOut)
737
738
602k
{
739
    /* -------------------------------------------------------------------- */
740
    /*      Check for BSB/ keyword.                                         */
741
    /* -------------------------------------------------------------------- */
742
602k
    isNosOut = false;
743
744
602k
    if (poOpenInfo->nHeaderBytes < 1000)
745
533k
        return FALSE;
746
747
69.0k
    int i = 0;
748
74.3M
    for (; i < poOpenInfo->nHeaderBytes - 4; i++)
749
74.3M
    {
750
74.3M
        if (poOpenInfo->pabyHeader[i + 0] == 'B' &&
751
74.3M
            poOpenInfo->pabyHeader[i + 1] == 'S' &&
752
74.3M
            poOpenInfo->pabyHeader[i + 2] == 'B' &&
753
74.3M
            poOpenInfo->pabyHeader[i + 3] == '/')
754
1.94k
            break;
755
74.3M
        if (poOpenInfo->pabyHeader[i + 0] == 'N' &&
756
74.3M
            poOpenInfo->pabyHeader[i + 1] == 'O' &&
757
74.3M
            poOpenInfo->pabyHeader[i + 2] == 'S' &&
758
74.3M
            poOpenInfo->pabyHeader[i + 3] == '/')
759
0
        {
760
0
            isNosOut = true;
761
0
            break;
762
0
        }
763
74.3M
        if (poOpenInfo->pabyHeader[i + 0] == 'W' &&
764
74.3M
            poOpenInfo->pabyHeader[i + 1] == 'X' &&
765
74.3M
            poOpenInfo->pabyHeader[i + 2] == '\\' &&
766
74.3M
            poOpenInfo->pabyHeader[i + 3] == '8')
767
0
            break;
768
74.3M
    }
769
770
69.0k
    if (i == poOpenInfo->nHeaderBytes - 4)
771
67.1k
        return FALSE;
772
773
    /* Additional test to avoid false positive. See #2881 */
774
1.94k
    const char *pszHeader =
775
1.94k
        reinterpret_cast<const char *>(poOpenInfo->pabyHeader);
776
1.94k
    const char *pszShiftedHeader = pszHeader + i;
777
1.94k
    const char *pszRA = strstr(pszShiftedHeader, "RA=");
778
1.94k
    if (pszRA == nullptr) /* This may be a NO1 file */
779
850
        pszRA = strstr(pszShiftedHeader, "[JF");
780
1.94k
    if (pszRA == nullptr)
781
290
        return FALSE;
782
1.65k
    if (pszRA - pszShiftedHeader > 100 && !strstr(pszHeader, "VER/") &&
783
1.65k
        !strstr(pszHeader, "KNP/") && !strstr(pszHeader, "KNQ/") &&
784
1.65k
        !strstr(pszHeader, "RGB/"))
785
44
        return FALSE;
786
787
1.61k
    return TRUE;
788
1.65k
}
789
790
/************************************************************************/
791
/*                              Identify()                              */
792
/************************************************************************/
793
794
int BSBDataset::Identify(GDALOpenInfo *poOpenInfo)
795
796
602k
{
797
602k
    bool isNos;
798
602k
    return IdentifyInternal(poOpenInfo, isNos);
799
602k
}
800
801
/************************************************************************/
802
/*                                Open()                                */
803
/************************************************************************/
804
805
GDALDataset *BSBDataset::Open(GDALOpenInfo *poOpenInfo)
806
807
807
{
808
807
    bool isNos = false;
809
807
    if (!IdentifyInternal(poOpenInfo, isNos))
810
0
        return nullptr;
811
812
807
    if (poOpenInfo->eAccess == GA_Update)
813
0
    {
814
0
        ReportUpdateNotSupportedByDriver("BSB");
815
0
        return nullptr;
816
0
    }
817
818
    /* -------------------------------------------------------------------- */
819
    /*      Create a corresponding GDALDataset.                             */
820
    /* -------------------------------------------------------------------- */
821
807
    BSBDataset *poDS = new BSBDataset();
822
823
    /* -------------------------------------------------------------------- */
824
    /*      Open the file.                                                  */
825
    /* -------------------------------------------------------------------- */
826
807
    poDS->psInfo = BSBOpen(poOpenInfo->pszFilename);
827
807
    if (poDS->psInfo == nullptr)
828
720
    {
829
720
        delete poDS;
830
720
        return nullptr;
831
720
    }
832
833
87
    poDS->nRasterXSize = poDS->psInfo->nXSize;
834
87
    poDS->nRasterYSize = poDS->psInfo->nYSize;
835
836
    /* -------------------------------------------------------------------- */
837
    /*      Create band information objects.                                */
838
    /* -------------------------------------------------------------------- */
839
87
    poDS->SetBand(1, new BSBRasterBand(poDS));
840
841
87
    poDS->ScanForGCPs(isNos, poOpenInfo->pszFilename);
842
843
    /* -------------------------------------------------------------------- */
844
    /*      Set CUTLINE metadata if a bounding polygon is available         */
845
    /* -------------------------------------------------------------------- */
846
87
    poDS->ScanForCutline();
847
848
    /* -------------------------------------------------------------------- */
849
    /*      Initialize any PAM information.                                 */
850
    /* -------------------------------------------------------------------- */
851
87
    poDS->SetDescription(poOpenInfo->pszFilename);
852
87
    poDS->TryLoadXML();
853
854
87
    poDS->oOvManager.Initialize(poDS, poOpenInfo->pszFilename);
855
856
87
    return poDS;
857
807
}
858
859
/************************************************************************/
860
/*                            GetGCPCount()                             */
861
/************************************************************************/
862
863
int BSBDataset::GetGCPCount()
864
865
87
{
866
87
    return nGCPCount;
867
87
}
868
869
/************************************************************************/
870
/*                          GetGCPSpatialRef()                          */
871
/************************************************************************/
872
873
const OGRSpatialReference *BSBDataset::GetGCPSpatialRef() const
874
875
87
{
876
87
    return m_oGCPSRS.IsEmpty() ? nullptr : &m_oGCPSRS;
877
87
}
878
879
/************************************************************************/
880
/*                               GetGCP()                               */
881
/************************************************************************/
882
883
const GDAL_GCP *BSBDataset::GetGCPs()
884
885
87
{
886
87
    return pasGCPList;
887
87
}
888
889
#ifdef BSB_CREATE
890
891
/************************************************************************/
892
/*                             BSBIsSRSOK()                             */
893
/************************************************************************/
894
895
static int BSBIsSRSOK(const char *pszWKT)
896
{
897
    bool bOK = false;
898
    OGRSpatialReference oSRS, oSRS_WGS84, oSRS_NAD83;
899
900
    if (pszWKT != NULL && pszWKT[0] != '\0')
901
    {
902
        oSRS.importFromWkt(pszWKT);
903
904
        oSRS_WGS84.SetWellKnownGeogCS("WGS84");
905
        oSRS_NAD83.SetWellKnownGeogCS("NAD83");
906
        if ((oSRS.IsSameGeogCS(&oSRS_WGS84) ||
907
             oSRS.IsSameGeogCS(&oSRS_NAD83)) &&
908
            oSRS.IsGeographic() && oSRS.GetPrimeMeridian() == 0.0)
909
        {
910
            bOK = true;
911
        }
912
    }
913
914
    if (!bOK)
915
    {
916
        CPLError(CE_Warning, CPLE_NotSupported,
917
                 "BSB only supports WGS84 or NAD83 geographic projections.\n");
918
    }
919
920
    return bOK;
921
}
922
923
/************************************************************************/
924
/*                           BSBCreateCopy()                            */
925
/************************************************************************/
926
927
static GDALDataset *BSBCreateCopy(const char *pszFilename, GDALDataset *poSrcDS,
928
                                  int bStrict, char ** /*papszOptions*/,
929
                                  GDALProgressFunc /*pfnProgress*/,
930
                                  void * /*pProgressData*/)
931
932
{
933
    /* -------------------------------------------------------------------- */
934
    /*      Some some rudimentary checks                                    */
935
    /* -------------------------------------------------------------------- */
936
    const int nBands = poSrcDS->GetRasterCount();
937
    if (nBands != 1)
938
    {
939
        CPLError(CE_Failure, CPLE_NotSupported,
940
                 "BSB driver only supports one band images.\n");
941
942
        return NULL;
943
    }
944
945
    if (poSrcDS->GetRasterBand(1)->GetRasterDataType() != GDT_Byte && bStrict)
946
    {
947
        CPLError(CE_Failure, CPLE_NotSupported,
948
                 "BSB driver doesn't support data type %s. "
949
                 "Only eight bit bands supported.\n",
950
                 GDALGetDataTypeName(
951
                     poSrcDS->GetRasterBand(1)->GetRasterDataType()));
952
953
        return NULL;
954
    }
955
956
    const int nXSize = poSrcDS->GetRasterXSize();
957
    const int nYSize = poSrcDS->GetRasterYSize();
958
959
    /* -------------------------------------------------------------------- */
960
    /*      Open the output file.                                           */
961
    /* -------------------------------------------------------------------- */
962
    BSBInfo *psBSB = BSBCreate(pszFilename, 0, 200, nXSize, nYSize);
963
    if (psBSB == NULL)
964
        return NULL;
965
966
    /* -------------------------------------------------------------------- */
967
    /*      Prepare initial color table.colortable.                         */
968
    /* -------------------------------------------------------------------- */
969
    GDALRasterBand *poBand = poSrcDS->GetRasterBand(1);
970
    unsigned char abyPCT[771];
971
    int nPCTSize;
972
    int anRemap[256];
973
974
    abyPCT[0] = 0;
975
    abyPCT[1] = 0;
976
    abyPCT[2] = 0;
977
978
    if (poBand->GetColorTable() == NULL)
979
    {
980
        /* map greyscale down to 63 grey levels. */
981
        for (int iColor = 0; iColor < 256; iColor++)
982
        {
983
            int nOutValue = (int)(iColor / 4.1) + 1;
984
985
            anRemap[iColor] = nOutValue;
986
            abyPCT[nOutValue * 3 + 0] = (unsigned char)iColor;
987
            abyPCT[nOutValue * 3 + 1] = (unsigned char)iColor;
988
            abyPCT[nOutValue * 3 + 2] = (unsigned char)iColor;
989
        }
990
        nPCTSize = 64;
991
    }
992
    else
993
    {
994
        GDALColorTable *poCT = poBand->GetColorTable();
995
        int nColorTableSize = poCT->GetColorEntryCount();
996
        if (nColorTableSize > 255)
997
            nColorTableSize = 255;
998
999
        for (int iColor = 0; iColor < nColorTableSize; iColor++)
1000
        {
1001
            GDALColorEntry sEntry;
1002
1003
            poCT->GetColorEntryAsRGB(iColor, &sEntry);
1004
1005
            anRemap[iColor] = iColor + 1;
1006
            abyPCT[(iColor + 1) * 3 + 0] = (unsigned char)sEntry.c1;
1007
            abyPCT[(iColor + 1) * 3 + 1] = (unsigned char)sEntry.c2;
1008
            abyPCT[(iColor + 1) * 3 + 2] = (unsigned char)sEntry.c3;
1009
        }
1010
1011
        nPCTSize = nColorTableSize + 1;
1012
1013
        // Add entries for pixel values which apparently will not occur.
1014
        for (int iColor = nPCTSize; iColor < 256; iColor++)
1015
            anRemap[iColor] = 1;
1016
    }
1017
1018
    /* -------------------------------------------------------------------- */
1019
    /*      Boil out all duplicate entries.                                 */
1020
    /* -------------------------------------------------------------------- */
1021
    for (int i = 1; i < nPCTSize - 1; i++)
1022
    {
1023
        for (int j = i + 1; j < nPCTSize; j++)
1024
        {
1025
            if (abyPCT[i * 3 + 0] == abyPCT[j * 3 + 0] &&
1026
                abyPCT[i * 3 + 1] == abyPCT[j * 3 + 1] &&
1027
                abyPCT[i * 3 + 2] == abyPCT[j * 3 + 2])
1028
            {
1029
                nPCTSize--;
1030
                abyPCT[j * 3 + 0] = abyPCT[nPCTSize * 3 + 0];
1031
                abyPCT[j * 3 + 1] = abyPCT[nPCTSize * 3 + 1];
1032
                abyPCT[j * 3 + 2] = abyPCT[nPCTSize * 3 + 2];
1033
1034
                for (int k = 0; k < 256; k++)
1035
                {
1036
                    // merge matching entries.
1037
                    if (anRemap[k] == j)
1038
                        anRemap[k] = i;
1039
1040
                    // shift the last PCT entry into the new hole.
1041
                    if (anRemap[k] == nPCTSize)
1042
                        anRemap[k] = j;
1043
                }
1044
            }
1045
        }
1046
    }
1047
1048
    /* -------------------------------------------------------------------- */
1049
    /*      Boil out all duplicate entries.                                 */
1050
    /* -------------------------------------------------------------------- */
1051
    if (nPCTSize > 128)
1052
    {
1053
        CPLError(CE_Warning, CPLE_AppDefined,
1054
                 "Having to merge color table entries to reduce %d real\n"
1055
                 "color table entries down to 127 values.",
1056
                 nPCTSize);
1057
    }
1058
1059
    while (nPCTSize > 128)
1060
    {
1061
        int nBestRange = 768;
1062
        int iBestMatch1 = -1;
1063
        int iBestMatch2 = -1;
1064
1065
        // Find the closest pair of color table entries.
1066
1067
        for (int i = 1; i < nPCTSize - 1; i++)
1068
        {
1069
            for (int j = i + 1; j < nPCTSize; j++)
1070
            {
1071
                int nRange = std::abs(abyPCT[i * 3 + 0] - abyPCT[j * 3 + 0]) +
1072
                             std::abs(abyPCT[i * 3 + 1] - abyPCT[j * 3 + 1]) +
1073
                             std::abs(abyPCT[i * 3 + 2] - abyPCT[j * 3 + 2]);
1074
1075
                if (nRange < nBestRange)
1076
                {
1077
                    iBestMatch1 = i;
1078
                    iBestMatch2 = j;
1079
                    nBestRange = nRange;
1080
                }
1081
            }
1082
        }
1083
1084
        // Merge the second entry into the first.
1085
        nPCTSize--;
1086
        abyPCT[iBestMatch2 * 3 + 0] = abyPCT[nPCTSize * 3 + 0];
1087
        abyPCT[iBestMatch2 * 3 + 1] = abyPCT[nPCTSize * 3 + 1];
1088
        abyPCT[iBestMatch2 * 3 + 2] = abyPCT[nPCTSize * 3 + 2];
1089
1090
        for (int i = 0; i < 256; i++)
1091
        {
1092
            // merge matching entries.
1093
            if (anRemap[i] == iBestMatch2)
1094
                anRemap[i] = iBestMatch1;
1095
1096
            // shift the last PCT entry into the new hole.
1097
            if (anRemap[i] == nPCTSize)
1098
                anRemap[i] = iBestMatch2;
1099
        }
1100
    }
1101
1102
    /* -------------------------------------------------------------------- */
1103
    /*      Write the PCT.                                                  */
1104
    /* -------------------------------------------------------------------- */
1105
    if (!BSBWritePCT(psBSB, nPCTSize, abyPCT))
1106
    {
1107
        BSBClose(psBSB);
1108
        return NULL;
1109
    }
1110
1111
    /* -------------------------------------------------------------------- */
1112
    /*      Write the GCPs.                                                 */
1113
    /* -------------------------------------------------------------------- */
1114
    GDALGeoTransform gt;
1115
    int nGCPCount = poSrcDS->GetGCPCount();
1116
    if (nGCPCount)
1117
    {
1118
        const char *pszGCPProjection = poSrcDS->GetGCPProjection();
1119
        if (BSBIsSRSOK(pszGCPProjection))
1120
        {
1121
            const GDAL_GCP *pasGCPList = poSrcDS->GetGCPs();
1122
            for (int i = 0; i < nGCPCount; i++)
1123
            {
1124
                VSIFPrintfL(psBSB->fp, "REF/%d,%f,%f,%f,%f\n", i + 1,
1125
                            pasGCPList[i].dfGCPPixel, pasGCPList[i].dfGCPLine,
1126
                            pasGCPList[i].dfGCPY, pasGCPList[i].dfGCPX);
1127
            }
1128
        }
1129
    }
1130
    else if (poSrcDS->GetGeoTransform(gt) == CE_None)
1131
    {
1132
        const char *pszProjection = poSrcDS->GetProjectionRef();
1133
        if (BSBIsSRSOK(pszProjection))
1134
        {
1135
            VSIFPrintfL(psBSB->fp, "REF/%d,%d,%d,%f,%f\n", 1, 0, 0,
1136
                        gt[3] + 0 * gt[4] + 0 * gt[5],
1137
                        gt[0] + 0 * gt[1] + 0 * gt[2]);
1138
            VSIFPrintfL(psBSB->fp, "REF/%d,%d,%d,%f,%f\n", 2, nXSize, 0,
1139
                        gt[3] + nXSize * gt[4] + 0 * gt[5],
1140
                        gt[0] + nXSize * gt[1] + 0 * gt[2]);
1141
            VSIFPrintfL(psBSB->fp, "REF/%d,%d,%d,%f,%f\n", 3, nXSize, nYSize,
1142
                        gt[3] + nXSize * gt[4] + nYSize * gt[5],
1143
                        gt[0] + nXSize * gt[1] + nYSize * gt[2]);
1144
            VSIFPrintfL(psBSB->fp, "REF/%d,%d,%d,%f,%f\n", 4, 0, nYSize,
1145
                        gt[3] + 0 * gt[4] + nYSize * gt[5],
1146
                        gt[0] + 0 * gt[1] + nYSize * gt[2]);
1147
        }
1148
    }
1149
1150
    /* -------------------------------------------------------------------- */
1151
    /*      Loop over image, copying image data.                            */
1152
    /* -------------------------------------------------------------------- */
1153
    CPLErr eErr = CE_None;
1154
1155
    GByte *pabyScanline = (GByte *)CPLMalloc(nXSize);
1156
1157
    for (int iLine = 0; iLine < nYSize && eErr == CE_None; iLine++)
1158
    {
1159
        eErr =
1160
            poBand->RasterIO(GF_Read, 0, iLine, nXSize, 1, pabyScanline, nXSize,
1161
                             1, GDT_Byte, nBands, nBands * nXSize, NULL);
1162
        if (eErr == CE_None)
1163
        {
1164
            for (int i = 0; i < nXSize; i++)
1165
                pabyScanline[i] = (GByte)anRemap[pabyScanline[i]];
1166
1167
            if (!BSBWriteScanline(psBSB, pabyScanline))
1168
                eErr = CE_Failure;
1169
        }
1170
    }
1171
1172
    CPLFree(pabyScanline);
1173
1174
    /* -------------------------------------------------------------------- */
1175
    /*      cleanup                                                         */
1176
    /* -------------------------------------------------------------------- */
1177
    BSBClose(psBSB);
1178
1179
    if (eErr != CE_None)
1180
    {
1181
        VSIUnlink(pszFilename);
1182
        return NULL;
1183
    }
1184
1185
    return (GDALDataset *)GDALOpen(pszFilename, GA_ReadOnly);
1186
}
1187
#endif
1188
1189
/************************************************************************/
1190
/*                        GDALRegister_BSB()                            */
1191
/************************************************************************/
1192
1193
void GDALRegister_BSB()
1194
1195
22
{
1196
22
    if (GDALGetDriverByName("BSB") != nullptr)
1197
0
        return;
1198
1199
22
    GDALDriver *poDriver = new GDALDriver();
1200
1201
22
    poDriver->SetDescription("BSB");
1202
22
    poDriver->SetMetadataItem(GDAL_DCAP_RASTER, "YES");
1203
22
    poDriver->SetMetadataItem(GDAL_DMD_LONGNAME, "Maptech BSB Nautical Charts");
1204
22
    poDriver->SetMetadataItem(GDAL_DMD_HELPTOPIC, "drivers/raster/bsb.html");
1205
22
    poDriver->SetMetadataItem(GDAL_DCAP_VIRTUALIO, "YES");
1206
22
    poDriver->SetMetadataItem(GDAL_DMD_EXTENSION, "kap");
1207
#ifdef BSB_CREATE
1208
    poDriver->SetMetadataItem(GDAL_DMD_CREATIONDATATYPES, "Byte");
1209
#endif
1210
22
    poDriver->pfnOpen = BSBDataset::Open;
1211
22
    poDriver->pfnIdentify = BSBDataset::Identify;
1212
#ifdef BSB_CREATE
1213
    poDriver->pfnCreateCopy = BSBCreateCopy;
1214
#endif
1215
1216
22
    GetGDALDriverManager()->RegisterDriver(poDriver);
1217
22
}