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

Generated by: LCOV version 1.14