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

Generated by: LCOV version 1.14