Coverage Report

Created: 2026-03-30 09:00

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