LCOV - code coverage report
Current view: top level - frmts/bsb - bsbdataset.cpp (source / functions) Hit Total Coverage
Test: gdal_filtered.info Lines: 247 323 76.5 %
Date: 2025-07-11 10:11:13 Functions: 19 22 86.4 %

          Line data    Source code
       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          13 : BSBRasterBand::BSBRasterBand(BSBDataset *poDSIn)
      91             : 
      92             : {
      93          13 :     poDS = poDSIn;
      94          13 :     nBand = 1;
      95             : 
      96          13 :     eDataType = GDT_Byte;
      97             : 
      98          13 :     nBlockXSize = poDS->GetRasterXSize();
      99          13 :     nBlockYSize = 1;
     100             : 
     101             :     // Note that the first color table entry is dropped, everything is
     102             :     // shifted down.
     103        1430 :     for (int i = 0; i < poDSIn->psInfo->nPCTSize - 1; i++)
     104             :     {
     105        1417 :         GDALColorEntry oColor = {poDSIn->psInfo->pabyPCT[i * 3 + 0 + 3],
     106        1417 :                                  poDSIn->psInfo->pabyPCT[i * 3 + 1 + 3],
     107        1417 :                                  poDSIn->psInfo->pabyPCT[i * 3 + 2 + 3], 255};
     108             : 
     109        1417 :         oCT.SetColorEntry(i, &oColor);
     110             :     }
     111          13 : }
     112             : 
     113             : /************************************************************************/
     114             : /*                             IReadBlock()                             */
     115             : /************************************************************************/
     116             : 
     117         250 : CPLErr BSBRasterBand::IReadBlock(CPL_UNUSED int nBlockXOff, int nBlockYOff,
     118             :                                  void *pImage)
     119             : {
     120         250 :     BSBDataset *poGDS = cpl::down_cast<BSBDataset *>(poDS);
     121         250 :     GByte *pabyScanline = (GByte *)pImage;
     122             : 
     123         250 :     if (BSBReadScanline(poGDS->psInfo, nBlockYOff, pabyScanline))
     124             :     {
     125       12648 :         for (int i = 0; i < nBlockXSize; i++)
     126             :         {
     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       12400 :             if (pabyScanline[i] > 0)
     131       12400 :                 pabyScanline[i] -= 1;
     132             :         }
     133             : 
     134         248 :         return CE_None;
     135             :     }
     136             : 
     137           2 :     return CE_Failure;
     138             : }
     139             : 
     140             : /************************************************************************/
     141             : /*                           GetColorTable()                            */
     142             : /************************************************************************/
     143             : 
     144           0 : GDALColorTable *BSBRasterBand::GetColorTable()
     145             : 
     146             : {
     147           0 :     return &oCT;
     148             : }
     149             : 
     150             : /************************************************************************/
     151             : /*                       GetColorInterpretation()                       */
     152             : /************************************************************************/
     153             : 
     154           0 : GDALColorInterp BSBRasterBand::GetColorInterpretation()
     155             : 
     156             : {
     157           0 :     return GCI_PaletteIndex;
     158             : }
     159             : 
     160             : /************************************************************************/
     161             : /* ==================================================================== */
     162             : /*                              BSBDataset                              */
     163             : /* ==================================================================== */
     164             : /************************************************************************/
     165             : 
     166             : /************************************************************************/
     167             : /*                           BSBDataset()                               */
     168             : /************************************************************************/
     169             : 
     170          13 : BSBDataset::BSBDataset()
     171             :     : nGCPCount(0), pasGCPList(nullptr), bGeoTransformSet(FALSE),
     172          13 :       psInfo(nullptr)
     173             : {
     174          13 :     m_oGCPSRS.SetAxisMappingStrategy(OAMS_TRADITIONAL_GIS_ORDER);
     175          13 :     m_oGCPSRS.importFromWkt(SRS_WKT_WGS84_LAT_LONG);
     176          13 : }
     177             : 
     178             : /************************************************************************/
     179             : /*                            ~BSBDataset()                             */
     180             : /************************************************************************/
     181             : 
     182          26 : BSBDataset::~BSBDataset()
     183             : 
     184             : {
     185          13 :     FlushCache(true);
     186             : 
     187          13 :     GDALDeinitGCPs(nGCPCount, pasGCPList);
     188          13 :     CPLFree(pasGCPList);
     189             : 
     190          13 :     if (psInfo != nullptr)
     191          13 :         BSBClose(psInfo);
     192          26 : }
     193             : 
     194             : /************************************************************************/
     195             : /*                          GetGeoTransform()                           */
     196             : /************************************************************************/
     197             : 
     198           1 : CPLErr BSBDataset::GetGeoTransform(GDALGeoTransform &gt) const
     199             : 
     200             : {
     201           1 :     gt = m_gt;
     202             : 
     203           1 :     if (bGeoTransformSet)
     204           1 :         return CE_None;
     205             : 
     206           0 :     return CE_Failure;
     207             : }
     208             : 
     209             : /************************************************************************/
     210             : /*                          GetSpatialRef()                             */
     211             : /************************************************************************/
     212             : 
     213           1 : const OGRSpatialReference *BSBDataset::GetSpatialRef() const
     214             : 
     215             : {
     216           1 :     if (bGeoTransformSet)
     217           1 :         return &m_oGCPSRS;
     218             : 
     219           0 :     return nullptr;
     220             : }
     221             : 
     222             : /************************************************************************/
     223             : /*                     GDALHeuristicDatelineWrap()                      */
     224             : /************************************************************************/
     225             : 
     226           3 : static void GDALHeuristicDatelineWrap(int nPointCount, double *padfX)
     227             : 
     228             : {
     229           3 :     if (nPointCount < 2)
     230           3 :         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           3 :     double dfX_PM_Min = 0.0;
     239           3 :     double dfX_PM_Max = 0.0;
     240           3 :     double dfX_Dateline_Min = 0.0;
     241           3 :     double dfX_Dateline_Max = 0.0;
     242             : 
     243         110 :     for (int i = 0; i < nPointCount; i++)
     244             :     {
     245         107 :         double dfX_PM = padfX[i];
     246         107 :         if (dfX_PM > 180)
     247           0 :             dfX_PM -= 360.0;
     248             : 
     249         107 :         double dfX_Dateline = padfX[i];
     250         107 :         if (dfX_Dateline < 0)
     251           0 :             dfX_Dateline += 360.0;
     252             : 
     253         107 :         if (i == 0)
     254             :         {
     255           3 :             dfX_PM_Min = dfX_PM;
     256           3 :             dfX_PM_Max = dfX_PM;
     257           3 :             dfX_Dateline_Min = dfX_Dateline;
     258           3 :             dfX_Dateline_Max = dfX_Dateline;
     259             :         }
     260             :         else
     261             :         {
     262         104 :             dfX_PM_Min = std::min(dfX_PM_Min, dfX_PM);
     263         104 :             dfX_PM_Max = std::max(dfX_PM_Max, dfX_PM);
     264         104 :             dfX_Dateline_Min = std::min(dfX_Dateline_Min, dfX_Dateline);
     265         104 :             dfX_Dateline_Max = std::max(dfX_Dateline_Max, dfX_Dateline);
     266             :         }
     267             :     }
     268             : 
     269             :     /* -------------------------------------------------------------------- */
     270             :     /*      Do nothing if the range is always fairly small - no apparent    */
     271             :     /*      wrapping issues.                                                */
     272             :     /* -------------------------------------------------------------------- */
     273           3 :     if ((dfX_PM_Max - dfX_PM_Min) < 270.0 &&
     274           3 :         (dfX_Dateline_Max - dfX_Dateline_Min) < 270.0)
     275           3 :         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           0 :     if ((dfX_PM_Max - dfX_PM_Min) > 270.0 &&
     282           0 :         (dfX_Dateline_Max - dfX_Dateline_Min) > 270.0)
     283           0 :         return;
     284             : 
     285             :     /* -------------------------------------------------------------------- */
     286             :     /*      Pick which way to transform things.                             */
     287             :     /* -------------------------------------------------------------------- */
     288             :     bool bUsePMWrap;
     289             : 
     290           0 :     if ((dfX_PM_Max - dfX_PM_Min) > 270.0 &&
     291           0 :         (dfX_Dateline_Max - dfX_Dateline_Min) < 270.0)
     292           0 :         bUsePMWrap = false;
     293             :     else
     294           0 :         bUsePMWrap = true;
     295             : 
     296             :     /* -------------------------------------------------------------------- */
     297             :     /*      Apply rewrapping.                                               */
     298             :     /* -------------------------------------------------------------------- */
     299           0 :     for (int i = 0; i < nPointCount; i++)
     300             :     {
     301           0 :         if (bUsePMWrap)
     302             :         {
     303           0 :             if (padfX[i] > 180)
     304           0 :                 padfX[i] -= 360.0;
     305             :         }
     306             :         else
     307             :         {
     308           0 :             if (padfX[i] < 0)
     309           0 :                 padfX[i] += 360.0;
     310             :         }
     311             :     }
     312             : }
     313             : 
     314             : /************************************************************************/
     315             : /*                   GDALHeuristicDatelineWrapGCPs()                    */
     316             : /************************************************************************/
     317             : 
     318           3 : static void GDALHeuristicDatelineWrapGCPs(int nPointCount, GDAL_GCP *pasGCPList)
     319             : {
     320           6 :     std::vector<double> oadfX;
     321             : 
     322           3 :     oadfX.resize(nPointCount);
     323         110 :     for (int i = 0; i < nPointCount; i++)
     324         107 :         oadfX[i] = pasGCPList[i].dfGCPX;
     325             : 
     326           3 :     GDALHeuristicDatelineWrap(nPointCount, &(oadfX[0]));
     327             : 
     328         110 :     for (int i = 0; i < nPointCount; i++)
     329         107 :         pasGCPList[i].dfGCPX = oadfX[i];
     330           3 : }
     331             : 
     332             : /************************************************************************/
     333             : /*                            ScanForGCPs()                             */
     334             : /************************************************************************/
     335             : 
     336          13 : void BSBDataset::ScanForGCPs(bool isNos, const char *pszFilename)
     337             : 
     338             : {
     339             :     /* -------------------------------------------------------------------- */
     340             :     /*      Collect GCPs as appropriate to source.                          */
     341             :     /* -------------------------------------------------------------------- */
     342          13 :     nGCPCount = 0;
     343             : 
     344          13 :     if (isNos)
     345             :     {
     346           0 :         ScanForGCPsNos(pszFilename);
     347             :     }
     348             :     else
     349             :     {
     350          13 :         ScanForGCPsBSB();
     351             :     }
     352             : 
     353             :     /* -------------------------------------------------------------------- */
     354             :     /*      Apply heuristics to re-wrap GCPs to maintain continuity        */
     355             :     /*      over the international dateline.                                */
     356             :     /* -------------------------------------------------------------------- */
     357          13 :     if (nGCPCount > 1)
     358           3 :         GDALHeuristicDatelineWrapGCPs(nGCPCount, pasGCPList);
     359             : 
     360             :     /* -------------------------------------------------------------------- */
     361             :     /*      Collect coordinate system related parameters from header.       */
     362             :     /* -------------------------------------------------------------------- */
     363          13 :     const char *pszKNP = nullptr;
     364          13 :     const char *pszKNQ = nullptr;
     365             : 
     366        1765 :     for (int i = 0; psInfo->papszHeader[i] != nullptr; i++)
     367             :     {
     368        1752 :         if (STARTS_WITH_CI(psInfo->papszHeader[i], "KNP/"))
     369             :         {
     370          13 :             pszKNP = psInfo->papszHeader[i];
     371          13 :             SetMetadataItem("BSB_KNP", pszKNP + 4);
     372             :         }
     373        1752 :         if (STARTS_WITH_CI(psInfo->papszHeader[i], "KNQ/"))
     374             :         {
     375           3 :             pszKNQ = psInfo->papszHeader[i];
     376           3 :             SetMetadataItem("BSB_KNQ", pszKNQ + 4);
     377             :         }
     378             :     }
     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          26 :     CPLString osUnderlyingSRS;
     386          13 :     if (pszKNP != nullptr)
     387             :     {
     388          13 :         const char *pszPR = strstr(pszKNP, "PR=");
     389          13 :         const char *pszGD = strstr(pszKNP, "GD=");
     390          13 :         const char *pszGEOGCS = SRS_WKT_WGS84_LAT_LONG;
     391          26 :         CPLString osPP;
     392             : 
     393             :         // Capture the PP string.
     394          13 :         const char *pszValue = strstr(pszKNP, "PP=");
     395          13 :         const char *pszEnd = pszValue ? strstr(pszValue, ",") : nullptr;
     396          13 :         if (pszValue && pszEnd)
     397          13 :             osPP.assign(pszValue + 3, pszEnd - pszValue - 3);
     398             : 
     399             :         // Look at the datum
     400          13 :         if (pszGD == nullptr)
     401             :         {
     402             :             /* no match. We'll default to EPSG:4326 */
     403             :         }
     404          13 :         else if (STARTS_WITH_CI(pszGD, "GD=European 1950"))
     405             :         {
     406           0 :             pszGEOGCS =
     407             :                 "GEOGCS[\"ED50\",DATUM[\"European_Datum_1950\",SPHEROID["
     408             :                 "\"International "
     409             :                 "1924\",6378388,297,AUTHORITY[\"EPSG\",\"7022\"]],TOWGS84[-87,-"
     410             :                 "98,-121,0,0,0,0],AUTHORITY[\"EPSG\",\"6230\"]],PRIMEM["
     411             :                 "\"Greenwich\",0,AUTHORITY[\"EPSG\",\"8901\"]],UNIT[\"degree\","
     412             :                 "0.0174532925199433,AUTHORITY[\"EPSG\",\"9122\"]],AUTHORITY["
     413             :                 "\"EPSG\",\"4230\"]]";
     414             :         }
     415             : 
     416             :         // Look at the projection
     417          13 :         if (pszPR == nullptr)
     418             :         {
     419             :             /* no match */
     420             :         }
     421          13 :         else if (STARTS_WITH_CI(pszPR, "PR=MERCATOR") && nGCPCount > 0)
     422             :         {
     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           1 :             osUnderlyingSRS.Printf(
     428             :                 "PROJCS[\"Global "
     429             :                 "Mercator\",%s,PROJECTION[\"Mercator_2SP\"],PARAMETER["
     430             :                 "\"standard_parallel_1\",0],PARAMETER[\"latitude_of_origin\",0]"
     431             :                 ",PARAMETER[\"central_meridian\",%d],PARAMETER[\"false_"
     432             :                 "easting\",0],PARAMETER[\"false_northing\",0],UNIT[\"Meter\",1]"
     433             :                 "]",
     434           1 :                 pszGEOGCS, (int)pasGCPList[0].dfGCPX);
     435             :         }
     436             : 
     437          13 :         else if (STARTS_WITH_CI(pszPR, "PR=TRANSVERSE MERCATOR") &&
     438           1 :                  !osPP.empty())
     439             :         {
     440             : 
     441             :             osUnderlyingSRS.Printf(
     442             :                 "PROJCS[\"unnamed\",%s,PROJECTION[\"Transverse_Mercator\"],"
     443             :                 "PARAMETER[\"latitude_of_origin\",0],PARAMETER[\"central_"
     444             :                 "meridian\",%s],PARAMETER[\"scale_factor\",1],PARAMETER["
     445             :                 "\"false_easting\",0],PARAMETER[\"false_northing\",0],UNIT["
     446             :                 "\"Meter\",1]]",
     447           1 :                 pszGEOGCS, osPP.c_str());
     448             :         }
     449             : 
     450          11 :         else if (STARTS_WITH_CI(pszPR, "PR=UNIVERSAL TRANSVERSE MERCATOR") &&
     451           0 :                  !osPP.empty())
     452             :         {
     453             :             // This is not *really* UTM unless the central meridian
     454             :             // matches a zone which it does not in some (most?) maps.
     455             :             osUnderlyingSRS.Printf(
     456             :                 "PROJCS[\"unnamed\",%s,PROJECTION[\"Transverse_Mercator\"],"
     457             :                 "PARAMETER[\"latitude_of_origin\",0],PARAMETER[\"central_"
     458             :                 "meridian\",%s],PARAMETER[\"scale_factor\",0.9996],PARAMETER["
     459             :                 "\"false_easting\",500000],PARAMETER[\"false_northing\",0],"
     460             :                 "UNIT[\"Meter\",1]]",
     461           0 :                 pszGEOGCS, osPP.c_str());
     462             :         }
     463             : 
     464          11 :         else if (STARTS_WITH_CI(pszPR, "PR=POLYCONIC") && !osPP.empty())
     465             :         {
     466             :             osUnderlyingSRS.Printf(
     467             :                 "PROJCS[\"unnamed\",%s,PROJECTION[\"Polyconic\"],PARAMETER["
     468             :                 "\"latitude_of_origin\",0],PARAMETER[\"central_meridian\",%s],"
     469             :                 "PARAMETER[\"false_easting\",0],PARAMETER[\"false_northing\",0]"
     470             :                 ",UNIT[\"Meter\",1]]",
     471           0 :                 pszGEOGCS, osPP.c_str());
     472             :         }
     473             : 
     474          23 :         else if (STARTS_WITH_CI(pszPR, "PR=LAMBERT CONFORMAL CONIC") &&
     475          11 :                  !osPP.empty() && pszKNQ != nullptr)
     476             :         {
     477           2 :             CPLString osP2, osP3;
     478             : 
     479             :             // Capture the KNQ/P2 string.
     480           1 :             pszValue = strstr(pszKNQ, "P2=");
     481           1 :             if (pszValue)
     482           1 :                 pszEnd = strstr(pszValue, ",");
     483           1 :             if (pszValue && pszEnd)
     484           1 :                 osP2.assign(pszValue + 3, pszEnd - pszValue - 3);
     485             : 
     486             :             // Capture the KNQ/P3 string.
     487           1 :             pszValue = strstr(pszKNQ, "P3=");
     488           1 :             if (pszValue)
     489           1 :                 pszEnd = strstr(pszValue, ",");
     490           1 :             if (pszValue)
     491             :             {
     492           1 :                 if (pszEnd)
     493           1 :                     osP3.assign(pszValue + 3, pszEnd - pszValue - 3);
     494             :                 else
     495           0 :                     osP3.assign(pszValue + 3);
     496             :             }
     497             : 
     498           1 :             if (!osP2.empty() && !osP3.empty())
     499             :                 osUnderlyingSRS.Printf(
     500             :                     "PROJCS[\"unnamed\",%s,PROJECTION[\"Lambert_Conformal_"
     501             :                     "Conic_2SP\"],PARAMETER[\"standard_parallel_1\",%s],"
     502             :                     "PARAMETER[\"standard_parallel_2\",%s],PARAMETER["
     503             :                     "\"latitude_of_origin\",0.0],PARAMETER[\"central_"
     504             :                     "meridian\",%s],PARAMETER[\"false_easting\",0.0],PARAMETER["
     505             :                     "\"false_northing\",0.0],UNIT[\"Meter\",1]]",
     506           1 :                     pszGEOGCS, osP2.c_str(), osP3.c_str(), osPP.c_str());
     507             :         }
     508             :     }
     509             : 
     510             :     /* -------------------------------------------------------------------- */
     511             :     /*      If we got an alternate underlying coordinate system, try        */
     512             :     /*      converting the GCPs to that coordinate system.                  */
     513             :     /* -------------------------------------------------------------------- */
     514          13 :     if (osUnderlyingSRS.length() > 0)
     515             :     {
     516           6 :         OGRSpatialReference oGeog_SRS, oProjected_SRS;
     517             : 
     518           3 :         oProjected_SRS.SetAxisMappingStrategy(OAMS_TRADITIONAL_GIS_ORDER);
     519           3 :         oProjected_SRS.SetFromUserInput(osUnderlyingSRS);
     520           3 :         oGeog_SRS.CopyGeogCSFrom(&oProjected_SRS);
     521           3 :         oGeog_SRS.SetAxisMappingStrategy(OAMS_TRADITIONAL_GIS_ORDER);
     522             : 
     523             :         OGRCoordinateTransformation *poCT =
     524           3 :             OGRCreateCoordinateTransformation(&oGeog_SRS, &oProjected_SRS);
     525           3 :         if (poCT != nullptr)
     526             :         {
     527         110 :             for (int i = 0; i < nGCPCount; i++)
     528             :             {
     529         107 :                 poCT->Transform(1, &(pasGCPList[i].dfGCPX),
     530         107 :                                 &(pasGCPList[i].dfGCPY),
     531         107 :                                 &(pasGCPList[i].dfGCPZ));
     532             :             }
     533             : 
     534           3 :             m_oGCPSRS.importFromWkt(osUnderlyingSRS.c_str());
     535             : 
     536           3 :             delete poCT;
     537             :         }
     538             :         else
     539           0 :             CPLErrorReset();
     540             :     }
     541             : 
     542             :     /* -------------------------------------------------------------------- */
     543             :     /*      Attempt to prepare a geotransform from the GCPs.                */
     544             :     /* -------------------------------------------------------------------- */
     545          13 :     if (GDALGCPsToGeoTransform(nGCPCount, pasGCPList, m_gt.data(), FALSE))
     546             :     {
     547           2 :         bGeoTransformSet = TRUE;
     548             :     }
     549          13 : }
     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           0 : void BSBDataset::ScanForGCPsNos(const char *pszFilename)
     563             : {
     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             :     {
     570           0 :         geofile = CPLResetExtensionSafe(pszFilename, "GEO");
     571             :     }
     572             :     else
     573             :     {
     574           0 :         geofile = CPLResetExtensionSafe(pszFilename, "geo");
     575             :     }
     576             : 
     577           0 :     FILE *gfp = VSIFOpen(geofile.c_str(), "r");  // Text files
     578           0 :     if (gfp == nullptr)
     579             :     {
     580           0 :         CPLError(CE_Failure, CPLE_OpenFailed,
     581             :                  "Couldn't find a matching .GEO file: %s", geofile.c_str());
     582           0 :         return;
     583             :     }
     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             :     {
     592           0 :         if (STARTS_WITH_CI(thisLine, "Point"))
     593           0 :             fileGCPCount++;
     594             :     }
     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             :     {
     602           0 :         if (STARTS_WITH_CI(thisLine, "Point"))
     603             :         {
     604             :             // got a point line, turn it into a gcp
     605             :             char **Tokens =
     606           0 :                 CSLTokenizeStringComplex(thisLine, "= ", FALSE, FALSE);
     607           0 :             if (CSLCount(Tokens) >= 5)
     608             :             {
     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             :                 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             :             }
     622           0 :             CSLDestroy(Tokens);
     623             :         }
     624             :     }
     625             : 
     626           0 :     CPLFree(thisLine);
     627           0 :     VSIFClose(gfp);
     628             : }
     629             : 
     630             : /************************************************************************/
     631             : /*                            ScanForGCPsBSB()                          */
     632             : /************************************************************************/
     633             : 
     634          13 : void BSBDataset::ScanForGCPsBSB()
     635             : {
     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          13 :     int fileGCPCount = 0;
     643             : 
     644             :     // Count the GCPs (reference points) in psInfo->papszHeader
     645        1765 :     for (int i = 0; psInfo->papszHeader[i] != nullptr; i++)
     646        1752 :         if (STARTS_WITH_CI(psInfo->papszHeader[i], "REF/"))
     647         107 :             fileGCPCount++;
     648             : 
     649             :     // Memory has not been allocated to fileGCPCount yet
     650          13 :     pasGCPList = (GDAL_GCP *)CPLCalloc(sizeof(GDAL_GCP), fileGCPCount + 1);
     651             : 
     652        1765 :     for (int i = 0; psInfo->papszHeader[i] != nullptr; i++)
     653             :     {
     654             : 
     655        1752 :         if (!STARTS_WITH_CI(psInfo->papszHeader[i], "REF/"))
     656        1645 :             continue;
     657             : 
     658         214 :         char **papszTokens = CSLTokenizeStringComplex(
     659         107 :             psInfo->papszHeader[i] + 4, ",", FALSE, FALSE);
     660             : 
     661         107 :         if (CSLCount(papszTokens) > 4)
     662             :         {
     663         107 :             GDALInitGCPs(1, pasGCPList + nGCPCount);
     664             : 
     665         107 :             pasGCPList[nGCPCount].dfGCPX = CPLAtof(papszTokens[4]);
     666         107 :             pasGCPList[nGCPCount].dfGCPY = CPLAtof(papszTokens[3]);
     667         107 :             pasGCPList[nGCPCount].dfGCPPixel = CPLAtof(papszTokens[1]);
     668         107 :             pasGCPList[nGCPCount].dfGCPLine = CPLAtof(papszTokens[2]);
     669             : 
     670         107 :             CPLFree(pasGCPList[nGCPCount].pszId);
     671         107 :             if (CSLCount(papszTokens) > 5)
     672             :             {
     673           0 :                 pasGCPList[nGCPCount].pszId = CPLStrdup(papszTokens[5]);
     674             :             }
     675             :             else
     676             :             {
     677             :                 char szName[50];
     678         107 :                 snprintf(szName, sizeof(szName), "GCP_%d", nGCPCount + 1);
     679         107 :                 pasGCPList[nGCPCount].pszId = CPLStrdup(szName);
     680             :             }
     681             : 
     682         107 :             nGCPCount++;
     683             :         }
     684         107 :         CSLDestroy(papszTokens);
     685             :     }
     686          13 : }
     687             : 
     688             : /************************************************************************/
     689             : /*                            ScanForCutline()                          */
     690             : /************************************************************************/
     691             : 
     692          13 : void BSBDataset::ScanForCutline()
     693             : {
     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          26 :     std::string wkt;
     705        1765 :     for (int i = 0; psInfo->papszHeader[i] != nullptr; i++)
     706             :     {
     707        1752 :         if (!STARTS_WITH_CI(psInfo->papszHeader[i], "PLY/"))
     708        1731 :             continue;
     709             : 
     710             :         const CPLStringList aosTokens(
     711          42 :             CSLTokenizeString2(psInfo->papszHeader[i] + 4, ",", 0));
     712             : 
     713          21 :         if (aosTokens.size() >= 3)
     714             :         {
     715          21 :             if (wkt.empty())
     716           2 :                 wkt = "POLYGON ((";
     717             :             else
     718          19 :                 wkt += ',';
     719          21 :             wkt += aosTokens[2];
     720          21 :             wkt += ' ';
     721          21 :             wkt += aosTokens[1];
     722             :         }
     723             :     }
     724             : 
     725          13 :     if (!wkt.empty())
     726             :     {
     727           2 :         wkt += "))";
     728           2 :         SetMetadataItem("BSB_CUTLINE", wkt.c_str());
     729             :     }
     730          13 : }
     731             : 
     732             : /************************************************************************/
     733             : /*                          IdentifyInternal()                          */
     734             : /************************************************************************/
     735             : 
     736       61549 : int BSBDataset::IdentifyInternal(GDALOpenInfo *poOpenInfo, bool &isNosOut)
     737             : 
     738             : {
     739             :     /* -------------------------------------------------------------------- */
     740             :     /*      Check for BSB/ keyword.                                         */
     741             :     /* -------------------------------------------------------------------- */
     742       61549 :     isNosOut = false;
     743             : 
     744       61549 :     if (poOpenInfo->nHeaderBytes < 1000)
     745       57312 :         return FALSE;
     746             : 
     747        4237 :     int i = 0;
     748     5655040 :     for (; i < poOpenInfo->nHeaderBytes - 4; i++)
     749             :     {
     750     5650820 :         if (poOpenInfo->pabyHeader[i + 0] == 'B' &&
     751       15826 :             poOpenInfo->pabyHeader[i + 1] == 'S' &&
     752         104 :             poOpenInfo->pabyHeader[i + 2] == 'B' &&
     753          28 :             poOpenInfo->pabyHeader[i + 3] == '/')
     754          26 :             break;
     755     5650800 :         if (poOpenInfo->pabyHeader[i + 0] == 'N' &&
     756       16886 :             poOpenInfo->pabyHeader[i + 1] == 'O' &&
     757         596 :             poOpenInfo->pabyHeader[i + 2] == 'S' &&
     758          12 :             poOpenInfo->pabyHeader[i + 3] == '/')
     759             :         {
     760           0 :             isNosOut = true;
     761           0 :             break;
     762             :         }
     763     5650800 :         if (poOpenInfo->pabyHeader[i + 0] == 'W' &&
     764        5132 :             poOpenInfo->pabyHeader[i + 1] == 'X' &&
     765         133 :             poOpenInfo->pabyHeader[i + 2] == '\\' &&
     766           0 :             poOpenInfo->pabyHeader[i + 3] == '8')
     767           0 :             break;
     768             :     }
     769             : 
     770        4237 :     if (i == poOpenInfo->nHeaderBytes - 4)
     771        4211 :         return FALSE;
     772             : 
     773             :     /* Additional test to avoid false positive. See #2881 */
     774          26 :     const char *pszHeader =
     775             :         reinterpret_cast<const char *>(poOpenInfo->pabyHeader);
     776          26 :     const char *pszShiftedHeader = pszHeader + i;
     777          26 :     const char *pszRA = strstr(pszShiftedHeader, "RA=");
     778          26 :     if (pszRA == nullptr) /* This may be a NO1 file */
     779           0 :         pszRA = strstr(pszShiftedHeader, "[JF");
     780          26 :     if (pszRA == nullptr)
     781           0 :         return FALSE;
     782          26 :     if (pszRA - pszShiftedHeader > 100 && !strstr(pszHeader, "VER/") &&
     783           0 :         !strstr(pszHeader, "KNP/") && !strstr(pszHeader, "KNQ/") &&
     784           0 :         !strstr(pszHeader, "RGB/"))
     785           0 :         return FALSE;
     786             : 
     787          26 :     return TRUE;
     788             : }
     789             : 
     790             : /************************************************************************/
     791             : /*                              Identify()                              */
     792             : /************************************************************************/
     793             : 
     794       61536 : int BSBDataset::Identify(GDALOpenInfo *poOpenInfo)
     795             : 
     796             : {
     797             :     bool isNos;
     798       61536 :     return IdentifyInternal(poOpenInfo, isNos);
     799             : }
     800             : 
     801             : /************************************************************************/
     802             : /*                                Open()                                */
     803             : /************************************************************************/
     804             : 
     805          13 : GDALDataset *BSBDataset::Open(GDALOpenInfo *poOpenInfo)
     806             : 
     807             : {
     808          13 :     bool isNos = false;
     809          13 :     if (!IdentifyInternal(poOpenInfo, isNos))
     810           0 :         return nullptr;
     811             : 
     812          13 :     if (poOpenInfo->eAccess == GA_Update)
     813             :     {
     814           0 :         ReportUpdateNotSupportedByDriver("BSB");
     815           0 :         return nullptr;
     816             :     }
     817             : 
     818             :     /* -------------------------------------------------------------------- */
     819             :     /*      Create a corresponding GDALDataset.                             */
     820             :     /* -------------------------------------------------------------------- */
     821          13 :     BSBDataset *poDS = new BSBDataset();
     822             : 
     823             :     /* -------------------------------------------------------------------- */
     824             :     /*      Open the file.                                                  */
     825             :     /* -------------------------------------------------------------------- */
     826          13 :     poDS->psInfo = BSBOpen(poOpenInfo->pszFilename);
     827          13 :     if (poDS->psInfo == nullptr)
     828             :     {
     829           0 :         delete poDS;
     830           0 :         return nullptr;
     831             :     }
     832             : 
     833          13 :     poDS->nRasterXSize = poDS->psInfo->nXSize;
     834          13 :     poDS->nRasterYSize = poDS->psInfo->nYSize;
     835             : 
     836             :     /* -------------------------------------------------------------------- */
     837             :     /*      Create band information objects.                                */
     838             :     /* -------------------------------------------------------------------- */
     839          13 :     poDS->SetBand(1, new BSBRasterBand(poDS));
     840             : 
     841          13 :     poDS->ScanForGCPs(isNos, poOpenInfo->pszFilename);
     842             : 
     843             :     /* -------------------------------------------------------------------- */
     844             :     /*      Set CUTLINE metadata if a bounding polygon is available         */
     845             :     /* -------------------------------------------------------------------- */
     846          13 :     poDS->ScanForCutline();
     847             : 
     848             :     /* -------------------------------------------------------------------- */
     849             :     /*      Initialize any PAM information.                                 */
     850             :     /* -------------------------------------------------------------------- */
     851          13 :     poDS->SetDescription(poOpenInfo->pszFilename);
     852          13 :     poDS->TryLoadXML();
     853             : 
     854          13 :     poDS->oOvManager.Initialize(poDS, poOpenInfo->pszFilename);
     855             : 
     856          13 :     return poDS;
     857             : }
     858             : 
     859             : /************************************************************************/
     860             : /*                            GetGCPCount()                             */
     861             : /************************************************************************/
     862             : 
     863           2 : int BSBDataset::GetGCPCount()
     864             : 
     865             : {
     866           2 :     return nGCPCount;
     867             : }
     868             : 
     869             : /************************************************************************/
     870             : /*                          GetGCPSpatialRef()                          */
     871             : /************************************************************************/
     872             : 
     873           1 : const OGRSpatialReference *BSBDataset::GetGCPSpatialRef() const
     874             : 
     875             : {
     876           1 :     return m_oGCPSRS.IsEmpty() ? nullptr : &m_oGCPSRS;
     877             : }
     878             : 
     879             : /************************************************************************/
     880             : /*                               GetGCP()                               */
     881             : /************************************************************************/
     882             : 
     883           1 : const GDAL_GCP *BSBDataset::GetGCPs()
     884             : 
     885             : {
     886           1 :     return pasGCPList;
     887             : }
     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        1935 : void GDALRegister_BSB()
    1194             : 
    1195             : {
    1196        1935 :     if (GDALGetDriverByName("BSB") != nullptr)
    1197         282 :         return;
    1198             : 
    1199        1653 :     GDALDriver *poDriver = new GDALDriver();
    1200             : 
    1201        1653 :     poDriver->SetDescription("BSB");
    1202        1653 :     poDriver->SetMetadataItem(GDAL_DCAP_RASTER, "YES");
    1203        1653 :     poDriver->SetMetadataItem(GDAL_DMD_LONGNAME, "Maptech BSB Nautical Charts");
    1204        1653 :     poDriver->SetMetadataItem(GDAL_DMD_HELPTOPIC, "drivers/raster/bsb.html");
    1205        1653 :     poDriver->SetMetadataItem(GDAL_DCAP_VIRTUALIO, "YES");
    1206        1653 :     poDriver->SetMetadataItem(GDAL_DMD_EXTENSION, "kap");
    1207             : #ifdef BSB_CREATE
    1208             :     poDriver->SetMetadataItem(GDAL_DMD_CREATIONDATATYPES, "Byte");
    1209             : #endif
    1210        1653 :     poDriver->pfnOpen = BSBDataset::Open;
    1211        1653 :     poDriver->pfnIdentify = BSBDataset::Identify;
    1212             : #ifdef BSB_CREATE
    1213             :     poDriver->pfnCreateCopy = BSBCreateCopy;
    1214             : #endif
    1215             : 
    1216        1653 :     GetGDALDriverManager()->RegisterDriver(poDriver);
    1217             : }

Generated by: LCOV version 1.14