LCOV - code coverage report
Current view: top level - frmts/raw - nsidcbindataset.cpp (source / functions) Hit Total Coverage
Test: gdal_filtered.info Lines: 98 131 74.8 %
Date: 2025-10-21 22:35:35 Functions: 10 15 66.7 %

          Line data    Source code
       1             : /******************************************************************************
       2             :  *
       3             :  * Project:  GDAL
       4             :  * Purpose:  Implementation for NSIDC binary format.
       5             :  * Author:   Michael Sumner, mdsumner@gmail.com
       6             :  *
       7             :  ******************************************************************************
       8             :  * Copyright (c) 2022, Michael Sumner
       9             :  *
      10             :  * SPDX-License-Identifier: MIT
      11             :  ****************************************************************************/
      12             : 
      13             : #include "cpl_string.h"
      14             : #include "gdal_frmts.h"
      15             : #include "gdal_priv.h"
      16             : #include "ogr_spatialref.h"
      17             : #include "rawdataset.h"
      18             : 
      19             : #include <cmath>
      20             : #include <algorithm>
      21             : 
      22             : /***********************************************************************/
      23             : /* ====================================================================*/
      24             : /*                              NSIDCbinDataset                        */
      25             : /* ====================================================================*/
      26             : /***********************************************************************/
      27             : 
      28             : class NSIDCbinDataset final : public GDALPamDataset
      29             : {
      30             :     friend class NSIDCbinRasterBand;
      31             : 
      32             :     struct NSIDCbinHeader
      33             :     {
      34             : 
      35             :         // page 7, User Guide https://nsidc.org/data/nsidc-0051
      36             :         // 1.3.2 File Contents
      37             :         // The file format consists of a 300-byte descriptive header followed by
      38             :         // a two-dimensional array of one-byte values containing the data. The
      39             :         // file header is composed of:
      40             :         // - a 21-element array of 6-byte character strings that contain
      41             :         // information such as polar stereographic grid characteristics
      42             :         // - a 24-byte character string containing the file name
      43             :         // - a 80-character string containing an optional image title
      44             :         // - a 70-byte character string containing ancillary information such as
      45             :         // data origin, data set creation date, etc. For compatibility with ANSI
      46             :         // C, IDL, and other languages, character strings are terminated with a
      47             :         // NULL byte.
      48             :         // Example file:
      49             :         // ftp://sidads.colorado.edu/pub/DATASETS/nsidc0051_gsfc_nasateam_seaice/final-gsfc/south/daily/2010/nt_20100918_f17_v1.1_s.bin
      50             : 
      51             :         char missing_int[6] = {0};       // "00255"
      52             :         char columns[6] = {0};           // "  316"
      53             :         char rows[6] = {0};              // "  332"
      54             :         char internal1[6] = {0};         // "1.799"
      55             :         char latitude[6] = {0};          // "-51.3"
      56             :         char greenwich[6] = {0};         // "270.0"
      57             :         char internal2[6] = {0};         // "558.4"
      58             :         char jpole[6] = {0};             // "158.0"
      59             :         char ipole[6] = {0};             // "174.0"
      60             :         char instrument[6] = {0};        // "SSMIS"
      61             :         char data_descriptors[6] = {0};  // "17 cn"
      62             :         char julian_start[6] = {0};      // "  261"
      63             :         char hour_start[6] = {0};        // "-9999"
      64             :         char minute_start[6] = {0};      // "-9999"
      65             :         char julian_end[6] = {0};        // "  261"
      66             :         char hour_end[6] = {0};          // "-9999"
      67             :         char minute_end[6] = {0};        // "-9999"
      68             :         char year[6] = {0};              // " 2010"
      69             :         char julian[6] = {0};            // "  261"
      70             :         char channel[6] = {0};           // "  000"
      71             :         char scaling[6] = {0};           // "00250"
      72             : 
      73             :         // 121-126 Integer scaling factor
      74             :         // 127-150 24-character file name (without file-name extension)
      75             :         // 151-230 80-character image title
      76             :         // 231-300 70-character data information (creation date, data source,
      77             :         // etc.)
      78             :         char filename[24] = {0};    // "  nt_20100918_f17_v1.1_s"
      79             :         char imagetitle[80] = {0};  // "ANTARCTIC  SMMR  TOTAL ICE CONCENTRATION
      80             :                                     // NIMBUSN07     DAY 299 10/26/1978"
      81             :         char data_information[70] = {0};  // "ANTARCTIC  SMMR ONSSMIGRID CON
      82             :                                           // Coast253Pole251Land254 06/27/1996"
      83             :     };
      84             : 
      85             :     VSILFILE *fp = nullptr;
      86             :     CPLString osSRS{};
      87             :     NSIDCbinHeader sHeader{};
      88             : 
      89             :     GDALGeoTransform m_gt{};
      90             :     CPL_DISALLOW_COPY_ASSIGN(NSIDCbinDataset)
      91             :     OGRSpatialReference m_oSRS{};
      92             : 
      93             :   public:
      94             :     NSIDCbinDataset();
      95             :     ~NSIDCbinDataset() override;
      96             :     CPLErr GetGeoTransform(GDALGeoTransform &gt) const override;
      97             : 
      98             :     const OGRSpatialReference *GetSpatialRef() const override;
      99             :     static GDALDataset *Open(GDALOpenInfo *);
     100             :     static int Identify(GDALOpenInfo *);
     101             : };
     102             : 
     103           4 : static const char *stripLeadingSpaces_nsidc(const char *buf)
     104             : {
     105           4 :     const char *ptr = buf;
     106             :     /* Go until we run out of characters  or hit something non-zero */
     107           9 :     while (*ptr == ' ')
     108             :     {
     109           5 :         ptr++;
     110             :     }
     111           4 :     return ptr;
     112             : }
     113             : 
     114             : /************************************************************************/
     115             : /* ==================================================================== */
     116             : /*                           NSIDCbinRasterBand                         */
     117             : /* ==================================================================== */
     118             : /************************************************************************/
     119             : 
     120             : class NSIDCbinRasterBand final : public RawRasterBand
     121             : {
     122             :     friend class NSIDCbinDataset;
     123             : 
     124             :     CPL_DISALLOW_COPY_ASSIGN(NSIDCbinRasterBand)
     125             : 
     126             :   public:
     127             :     NSIDCbinRasterBand(GDALDataset *poDS, int nBand, VSILFILE *fpRaw,
     128             :                        vsi_l_offset nImgOffset, int nPixelOffset,
     129             :                        int nLineOffset, GDALDataType eDataType);
     130             :     ~NSIDCbinRasterBand() override;
     131             : 
     132             :     double GetNoDataValue(int *pbSuccess = nullptr) override;
     133             :     double GetScale(int *pbSuccess = nullptr) override;
     134             :     const char *GetUnitType() override;
     135             : };
     136             : 
     137             : /************************************************************************/
     138             : /*                         NSIDCbinRasterBand()                         */
     139             : /************************************************************************/
     140             : 
     141           1 : NSIDCbinRasterBand::NSIDCbinRasterBand(GDALDataset *poDSIn, int nBandIn,
     142             :                                        VSILFILE *fpRawIn,
     143             :                                        vsi_l_offset nImgOffsetIn,
     144             :                                        int nPixelOffsetIn, int nLineOffsetIn,
     145           1 :                                        GDALDataType eDataTypeIn)
     146             :     : RawRasterBand(poDSIn, nBandIn, fpRawIn, nImgOffsetIn, nPixelOffsetIn,
     147             :                     nLineOffsetIn, eDataTypeIn,
     148             :                     RawRasterBand::ByteOrder::ORDER_LITTLE_ENDIAN,
     149           1 :                     RawRasterBand::OwnFP::NO)
     150             : {
     151           1 : }
     152             : 
     153             : /************************************************************************/
     154             : /*                           ~NSIDCbinRasterBand()                      */
     155             : /************************************************************************/
     156             : 
     157           2 : NSIDCbinRasterBand::~NSIDCbinRasterBand()
     158             : {
     159           2 : }
     160             : 
     161             : /************************************************************************/
     162             : /*                           GetNoDataValue()                           */
     163             : /************************************************************************/
     164             : 
     165           0 : double NSIDCbinRasterBand::GetNoDataValue(int *pbSuccess)
     166             : 
     167             : {
     168           0 :     if (pbSuccess != nullptr)
     169           0 :         *pbSuccess = TRUE;
     170             :     // we might check this if other format variants can be different
     171             :     // or if we change the Band type, or if we generalize to choosing Byte vs.
     172             :     // Float type but for now it's constant
     173             :     // https://lists.osgeo.org/pipermail/gdal-dev/2022-August/056144.html
     174             :     // const char  *pszLine = poPDS->sHeader.missing_int;
     175           0 :     return 255.0;  // CPLAtof(pszLine);
     176             : }
     177             : 
     178             : /************************************************************************/
     179             : /*                              GetScale()                              */
     180             : /************************************************************************/
     181             : 
     182           0 : double NSIDCbinRasterBand::GetScale(int *pbSuccess)
     183             : {
     184           0 :     if (pbSuccess != nullptr)
     185           0 :         *pbSuccess = TRUE;
     186             :     // again just use a constant unless we see other file variants
     187             :     // also, this might be fraction rather than percentage
     188             :     // atof(cpl::down_cast<NSIDCbinDataset*>(poDS)->sHeader.scaling)/100;
     189           0 :     return 0.4;
     190             : }
     191             : 
     192             : /************************************************************************/
     193             : /*                            NSIDCbinDataset()                         */
     194             : /************************************************************************/
     195             : 
     196           1 : NSIDCbinDataset::NSIDCbinDataset() : fp(nullptr), m_oSRS(OGRSpatialReference())
     197             : {
     198           1 : }
     199             : 
     200             : /************************************************************************/
     201             : /*                            ~NSIDCbinDataset()                        */
     202             : /************************************************************************/
     203             : 
     204           2 : NSIDCbinDataset::~NSIDCbinDataset()
     205             : 
     206             : {
     207           1 :     if (fp)
     208           1 :         CPL_IGNORE_RET_VAL(VSIFCloseL(fp));
     209           1 :     fp = nullptr;
     210           2 : }
     211             : 
     212             : /************************************************************************/
     213             : /*                                Open()                                */
     214             : /************************************************************************/
     215             : 
     216       33110 : GDALDataset *NSIDCbinDataset::Open(GDALOpenInfo *poOpenInfo)
     217             : 
     218             : {
     219             : 
     220             :     // Confirm that the header is compatible with a NSIDC dataset.
     221       33110 :     if (!Identify(poOpenInfo))
     222       33099 :         return nullptr;
     223             : 
     224             :     // Confirm the requested access is supported.
     225           9 :     if (poOpenInfo->eAccess == GA_Update)
     226             :     {
     227           0 :         ReportUpdateNotSupportedByDriver("NSIDCbin");
     228           0 :         return nullptr;
     229             :     }
     230             : 
     231             :     // Check that the file pointer from GDALOpenInfo* is available
     232           9 :     if (poOpenInfo->fpL == nullptr)
     233             :     {
     234           0 :         return nullptr;
     235             :     }
     236             : 
     237             :     /* -------------------------------------------------------------------- */
     238             :     /*      Create a corresponding GDALDataset.                             */
     239             :     /* -------------------------------------------------------------------- */
     240          10 :     auto poDS = std::make_unique<NSIDCbinDataset>();
     241             : 
     242           1 :     poDS->eAccess = poOpenInfo->eAccess;
     243           1 :     std::swap(poDS->fp, poOpenInfo->fpL);
     244             : 
     245             :     /* -------------------------------------------------------------------- */
     246             :     /*      Read the header information.                                    */
     247             :     /* -------------------------------------------------------------------- */
     248           1 :     if (VSIFReadL(&(poDS->sHeader), 300, 1, poDS->fp) != 1)
     249             :     {
     250           0 :         CPLError(CE_Failure, CPLE_FileIO,
     251             :                  "Attempt to read 300 byte header filed on file %s\n",
     252             :                  poOpenInfo->pszFilename);
     253           0 :         return nullptr;
     254             :     }
     255             : 
     256             :     // avoid unused warnings
     257           1 :     CPL_IGNORE_RET_VAL(poDS->sHeader.missing_int);
     258           1 :     CPL_IGNORE_RET_VAL(poDS->sHeader.internal1);
     259           1 :     CPL_IGNORE_RET_VAL(poDS->sHeader.latitude);
     260           1 :     CPL_IGNORE_RET_VAL(poDS->sHeader.greenwich);
     261           1 :     CPL_IGNORE_RET_VAL(poDS->sHeader.internal2);
     262           1 :     CPL_IGNORE_RET_VAL(poDS->sHeader.jpole);
     263           1 :     CPL_IGNORE_RET_VAL(poDS->sHeader.ipole);
     264           1 :     CPL_IGNORE_RET_VAL(poDS->sHeader.julian_start);
     265           1 :     CPL_IGNORE_RET_VAL(poDS->sHeader.hour_start);
     266           1 :     CPL_IGNORE_RET_VAL(poDS->sHeader.minute_start);
     267           1 :     CPL_IGNORE_RET_VAL(poDS->sHeader.julian_end);
     268           1 :     CPL_IGNORE_RET_VAL(poDS->sHeader.hour_end);
     269           1 :     CPL_IGNORE_RET_VAL(poDS->sHeader.minute_end);
     270           1 :     CPL_IGNORE_RET_VAL(poDS->sHeader.channel);
     271           1 :     CPL_IGNORE_RET_VAL(poDS->sHeader.scaling);
     272             : 
     273             :     /* -------------------------------------------------------------------- */
     274             :     /*      Extract information of interest from the header.                */
     275             :     /* -------------------------------------------------------------------- */
     276             : 
     277           1 :     poDS->nRasterXSize = atoi(poDS->sHeader.columns);
     278           1 :     poDS->nRasterYSize = atoi(poDS->sHeader.rows);
     279             : 
     280           1 :     const char *psHeader = reinterpret_cast<char *>(poOpenInfo->pabyHeader);
     281           1 :     bool south = STARTS_WITH(psHeader + 230, "ANTARCTIC");
     282             : 
     283           1 :     if (!GDALCheckDatasetDimensions(poDS->nRasterXSize, poDS->nRasterYSize))
     284             :     {
     285           0 :         return nullptr;
     286             :     }
     287             : 
     288             :     /* -------------------------------------------------------------------- */
     289             :     /*      Extract metadata from the header.                               */
     290             :     /* -------------------------------------------------------------------- */
     291             : 
     292           1 :     poDS->SetMetadataItem("INSTRUMENT", poDS->sHeader.instrument);
     293           1 :     poDS->SetMetadataItem("YEAR", stripLeadingSpaces_nsidc(poDS->sHeader.year));
     294           2 :     poDS->SetMetadataItem("JULIAN_DAY",
     295           1 :                           stripLeadingSpaces_nsidc(poDS->sHeader.julian));
     296           2 :     poDS->SetMetadataItem(
     297             :         "DATA_DESCRIPTORS",
     298           1 :         stripLeadingSpaces_nsidc(poDS->sHeader.data_descriptors));
     299           1 :     poDS->SetMetadataItem("IMAGE_TITLE", poDS->sHeader.imagetitle);
     300           2 :     poDS->SetMetadataItem("FILENAME",
     301           1 :                           stripLeadingSpaces_nsidc(poDS->sHeader.filename));
     302           1 :     poDS->SetMetadataItem("DATA_INFORMATION", poDS->sHeader.data_information);
     303             : 
     304             :     /* -------------------------------------------------------------------- */
     305             :     /*      Create band information objects.                                */
     306             :     /* -------------------------------------------------------------------- */
     307           1 :     int nBytesPerSample = 1;
     308             : 
     309             :     auto poBand = std::make_unique<NSIDCbinRasterBand>(
     310           1 :         poDS.get(), 1, poDS->fp, 300, nBytesPerSample, poDS->nRasterXSize,
     311           3 :         GDT_Byte);
     312           1 :     if (!poBand->IsValid())
     313           0 :         return nullptr;
     314           1 :     poDS->SetBand(1, std::move(poBand));
     315             : 
     316             :     /* -------------------------------------------------------------------- */
     317             :     /*      Geotransform, we simply know this from the documentation.       */
     318             :     /*       If we have similar binary files (at 12.5km for example) then   */
     319             :     /*        need more nuanced handling                                    */
     320             :     /*      Projection,  this is not technically enough, because the old    */
     321             :     /*       stuff is Hughes 1980.                                          */
     322             :     /*      FIXME: old or new epsg codes based on header info, or jul/year  */
     323             :     /* -------------------------------------------------------------------- */
     324             : 
     325           1 :     int epsg = -1;
     326           1 :     if (south)
     327             :     {
     328           1 :         poDS->m_gt[0] = -3950000.0;
     329           1 :         poDS->m_gt[1] = 25000;
     330           1 :         poDS->m_gt[2] = 0.0;
     331           1 :         poDS->m_gt[3] = 4350000.0;
     332           1 :         poDS->m_gt[4] = 0.0;
     333           1 :         poDS->m_gt[5] = -25000;
     334             : 
     335           1 :         epsg = 3976;
     336             :     }
     337             :     else
     338             :     {
     339           0 :         poDS->m_gt[0] = -3837500;
     340           0 :         poDS->m_gt[1] = 25000;
     341           0 :         poDS->m_gt[2] = 0.0;
     342           0 :         poDS->m_gt[3] = 5837500;
     343           0 :         poDS->m_gt[4] = 0.0;
     344           0 :         poDS->m_gt[5] = -25000;
     345             : 
     346           0 :         epsg = 3413;
     347             :     }
     348             : 
     349           1 :     if (poDS->m_oSRS.importFromEPSG(epsg) != OGRERR_NONE)
     350             :     {
     351           0 :         CPLError(CE_Failure, CPLE_AppDefined,
     352             :                  "Unknown error initializing SRS from ESPG code. ");
     353           0 :         return nullptr;
     354             :     }
     355             : 
     356             :     /* -------------------------------------------------------------------- */
     357             :     /*      Initialize any PAM information.                                 */
     358             :     /* -------------------------------------------------------------------- */
     359           1 :     poDS->SetDescription(poOpenInfo->pszFilename);
     360           1 :     poDS->TryLoadXML();
     361             : 
     362           1 :     return poDS.release();
     363             : }
     364             : 
     365             : /************************************************************************/
     366             : /*                              Identify()                              */
     367             : /************************************************************************/
     368       33110 : int NSIDCbinDataset::Identify(GDALOpenInfo *poOpenInfo)
     369             : {
     370             : 
     371             :     // -------------------------------------------------------------------- /
     372             :     //      Works for daily and monthly, north and south NSIDC binary files /
     373             :     //      north and south are different dimensions, different extents but /
     374             :     //      both are 25000m resolution.
     375             :     //
     376             :     //      First we check to see if the file has the expected header       /
     377             :     //      bytes.                                                          /
     378             :     // -------------------------------------------------------------------- /
     379             : 
     380       33110 :     if (poOpenInfo->nHeaderBytes < 300 || poOpenInfo->fpL == nullptr)
     381       31048 :         return FALSE;
     382             : 
     383        2062 :     const char *psHeader = reinterpret_cast<char *>(poOpenInfo->pabyHeader);
     384             :     // Check if century values seem reasonable.
     385        2062 :     if (!(EQUALN(psHeader + 103, "20", 2) || EQUALN(psHeader + 103, "19", 2) ||
     386             :           // the first files 1978 don't have a space at the start
     387        2061 :           EQUALN(psHeader + 102, "20", 2) || EQUALN(psHeader + 102, "19", 2)))
     388             :     {
     389        2061 :         return FALSE;
     390             :     }
     391             : 
     392             :     // Check if descriptors reasonable.
     393           1 :     if (!(STARTS_WITH(psHeader + 230, "ANTARCTIC") ||
     394           0 :           STARTS_WITH(psHeader + 230, "ARCTIC")))
     395             :     {
     396             : 
     397           0 :         return FALSE;
     398             :     }
     399             : 
     400           1 :     return TRUE;
     401             : }
     402             : 
     403             : /************************************************************************/
     404             : /*                           GetSpatialRef()                            */
     405             : /************************************************************************/
     406             : 
     407           0 : const OGRSpatialReference *NSIDCbinDataset::GetSpatialRef() const
     408             : {
     409           0 :     return &m_oSRS;
     410             : }
     411             : 
     412             : /************************************************************************/
     413             : /*                          GetGeoTransform()                           */
     414             : /************************************************************************/
     415             : 
     416           0 : CPLErr NSIDCbinDataset::GetGeoTransform(GDALGeoTransform &gt) const
     417             : 
     418             : {
     419           0 :     gt = m_gt;
     420           0 :     return CE_None;
     421             : }
     422             : 
     423             : /************************************************************************/
     424             : /*                             GetUnitType()                            */
     425             : /************************************************************************/
     426             : 
     427           0 : const char *NSIDCbinRasterBand::GetUnitType()
     428             : {
     429             :     // undecided, atm stick with Byte but may switch to Float and lose values >
     430             :     // 250 or generalize to non-raw driver
     431             :     // https://lists.osgeo.org/pipermail/gdal-dev/2022-August/056144.html
     432             :     // if (eDataType == GDT_Float32)
     433             :     //     return "Percentage";  // or "Fraction [0,1]"
     434             : 
     435             :     // Byte values don't have a clear unit type
     436           0 :     return "";
     437             : }
     438             : 
     439             : /************************************************************************/
     440             : /*                          GDALRegister_NSIDCbin()                        */
     441             : /************************************************************************/
     442             : 
     443        2038 : void GDALRegister_NSIDCbin()
     444             : 
     445             : {
     446        2038 :     if (GDALGetDriverByName("NSIDCbin") != nullptr)
     447         283 :         return;
     448             : 
     449        1755 :     GDALDriver *poDriver = new GDALDriver();
     450             : 
     451        1755 :     poDriver->SetDescription("NSIDCbin");
     452        1755 :     poDriver->SetMetadataItem(GDAL_DCAP_RASTER, "YES");
     453        1755 :     poDriver->SetMetadataItem(GDAL_DMD_LONGNAME,
     454        1755 :                               "NSIDC Sea Ice Concentrations binary (.bin)");
     455        1755 :     poDriver->SetMetadataItem(GDAL_DMD_HELPTOPIC,
     456        1755 :                               "drivers/raster/nsidcbin.html");
     457        1755 :     poDriver->SetMetadataItem(GDAL_DCAP_VIRTUALIO, "YES");
     458        1755 :     poDriver->SetMetadataItem(GDAL_DMD_EXTENSION, "bin");
     459             : 
     460        1755 :     poDriver->pfnOpen = NSIDCbinDataset::Open;
     461             : 
     462        1755 :     GetGDALDriverManager()->RegisterDriver(poDriver);
     463             : }

Generated by: LCOV version 1.14